Compare commits

..
Author SHA1 Message Date
rekram1-node b963d3a7a7 docs(plugin): correct plugin state reference 2026-10-02 00:42:49 +00:00
135 changed files with 725 additions and 2744 deletions

No files matched your search

+2 -8
View File
@@ -38,7 +38,6 @@ const resolve = (policy: CachePolicy | undefined): CachePolicyObject => {
// prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the
// whole policy pass for these — emitting hints would be harmless but pointless.
const RESPECTS_INLINE_HINTS = new Set([
"alibaba-chat",
"alibaba-messages",
"anthropic-messages",
"anthropic-compatible-messages",
@@ -56,12 +55,11 @@ const RESPECTS_INLINE_HINTS = new Set([
// OpenRouter upstreams other than Anthropic and Alibaba Qwen cache without breakpoints. Gemini uses only the last
// breakpoint, so a conversation-tail breakpoint writes a new cache every step and costs more than none. Qwen ignores
// breakpoints on tool definitions and caches tools with the system prompt.
const QWEN: CachePolicyObject = { system: true, messages: { tail: 1 } }
const openRouterPolicy = (modelID: string): CachePolicyObject => {
// `~anthropic/claude-sonnet-latest` style IDs are OpenRouter aliases for the latest model in a family.
const id = modelID.replace(/^~/, "")
if (id.startsWith("anthropic/")) return AUTO
if (id.startsWith("qwen/")) return QWEN
if (id.startsWith("qwen/")) return { system: true, messages: { tail: 1 } }
return NONE
}
@@ -173,11 +171,7 @@ export const applyCachePolicy = (request: LLMRequest): LLMRequest => {
const policy =
request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")
? openRouterPolicy(request.model.id)
: request.model.route.id === "alibaba-chat" && (request.cache === undefined || request.cache === "auto")
? request.model.id.toLowerCase().startsWith("qwen")
? QWEN
: NONE
: resolve(request.cache)
: resolve(request.cache)
if (!policy.tools && !policy.system && !policy.messages) return request
const hint = makeHint(policy.ttlSeconds)
+1 -2
View File
@@ -3,7 +3,6 @@ import { Protocol } from "../route/protocol.js"
import type { LanguageModelCompatibility } from "../schema/index.js"
import { OpenAIChat } from "./openai-chat.js"
import { JsonObject, ProviderShared } from "./shared.js"
import { cacheControl } from "./utils/cache.js"
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
export type ReasoningEffort = OpenResponsesOptions.ReasoningEffort
@@ -69,7 +68,7 @@ export const protocol = Protocol.make({
from: Effect.fn("AlibabaChat.fromRequest")(function* (req) {
const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {})
return {
...(yield* OpenAIChat.fromRequest(req, { cacheControl: cacheControl() })),
...(yield* OpenAIChat.protocol.body.from(req)),
enable_thinking: opts.enableThinking,
// Alibaba also rejects an explicit budget that is not below `max_completion_tokens`.
thinking_budget:
+7 -17
View File
@@ -159,15 +159,6 @@ const nativeTransportFailure = (error: unknown) => {
return failure
}
// HTTP hooks re-wrap response bodies, so a read failure can arrive as an HttpClientError caused by
// another HttpClientError. The innermost cause carries the native failure.
const rootCause = (error: unknown): unknown =>
HttpClientError.isHttpClientError(error) && "cause" in error.reason && error.reason.cause !== undefined
? rootCause(error.reason.cause)
: error
const CONNECTION_LOST = "Connection lost while reading the response"
const httpError = (input: {
readonly error: unknown
readonly request: HttpClientRequest.HttpClientRequest
@@ -188,21 +179,20 @@ const httpError = (input: {
}),
})
const source = rootCause(input.error)
const source =
HttpClientError.isHttpClientError(input.error) && "cause" in input.error.reason
? (input.error.reason.cause ?? input.error)
: input.error
const native = nativeTransportFailure(source)
const code = native?.code
const detail =
code && native?.message && !native.message.includes(code) ? `${code}: ${native.message}` : native?.message
const message = detail ?? (input.error instanceof Error ? input.error.message : undefined)
const raw = native?.message ?? (input.error instanceof Error ? input.error.message : undefined)
const detail = raw
const message = code && detail && !detail.includes(code) ? `${code}: ${detail}` : detail
if (Cause.isTimeoutError(input.error) || Cause.isTimeoutError(source))
return transportError({ message: message ?? "HTTP transport timed out", code: code ?? "Timeout" })
if (!HttpClientError.isHttpClientError(input.error))
return transportError({ message: message ?? "HTTP transport failed", code })
// Effect reports every response body read failure as a DecodeError, but the raw byte stream decodes
// nothing: provider output parsing happens later and fails as InvalidProviderOutput.
if (input.operation === "read" && input.error.reason._tag === "DecodeError")
return transportError({ message: detail ? `${CONNECTION_LOST}: ${detail}` : CONNECTION_LOST, code })
if (input.error.reason._tag === "TransportError") {
return transportError({
message: message ?? input.error.reason.description ?? "HTTP transport failed",
+6 -50
View File
@@ -1,19 +1,11 @@
import { Clock, Duration, Effect, Stream } from "effect"
import { Effect } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Auth } from "../auth.js"
import { render as renderEndpoint } from "../endpoint.js"
import { Framing } from "../framing.js"
import type { HttpMiddleware, Transport, TransportPrepareInput } from "./index.js"
import * as ProviderShared from "../../protocols/shared.js"
import {
AIError,
DEFAULT_HTTP_TIMEOUT_MS,
mergeJsonRecords,
TransportError,
type HttpContext,
type HttpTimeout,
type LLMRequest,
} from "../../schema/index.js"
import { mergeJsonRecords, type LLMRequest } from "../../schema/index.js"
import { RequestExecutor } from "../executor.js"
export type JsonRequestInput<Body> = TransportPrepareInput<Body>
@@ -95,53 +87,17 @@ export const httpJson = <Body, Frame>(input: HttpJsonInput<Body, Frame>): HttpJs
middleware: prepareInput.middleware,
}
}),
execute: (prepared, request, runtime) =>
execute: (prepared, _request, runtime) =>
Effect.gen(function* () {
const timeout = (operation: "request" | "read", message: string, http?: HttpContext) =>
new AIError({
reason: new TransportError({
message,
transport: "http",
operation,
code: "Timeout",
url: prepared.request.url,
http,
}),
})
const started = yield* Clock.currentTimeMillis
// Unlike the header and chunk limits, the whole-request budget has no default.
const total = request.http?.timeout ? Duration.millis(request.http.timeout) : Duration.infinity
const response = yield* runtime.http.execute(prepared.request, prepared.middleware).pipe(
Effect.timeoutOrElse({
duration: Duration.min(timeoutDuration(request.http?.headerTimeout), total),
orElse: () => timeout("request", "Timed out waiting for response headers"),
}),
)
const http = RequestExecutor.responseHttp(response)
const remaining = Duration.subtract(total, Duration.millis((yield* Clock.currentTimeMillis) - started))
const response = yield* runtime.http.execute(prepared.request, prepared.middleware)
return {
frames: prepared.framing.frame(
RequestExecutor.responseStream(response).pipe(
Stream.timeoutOrElse({
duration: timeoutDuration(request.http?.chunkTimeout),
orElse: () => Stream.fail(timeout("read", "Timed out waiting for response data", http)),
}),
Stream.interruptWhen(
Effect.sleep(remaining).pipe(
Effect.andThen(Effect.fail(timeout("read", "Timed out waiting for the response to complete", http))),
),
),
),
),
http,
frames: prepared.framing.frame(RequestExecutor.responseStream(response)),
http: RequestExecutor.responseHttp(response),
body: prepared.framing.body,
}
}),
})
const timeoutDuration = (value: HttpTimeout | undefined) =>
value === false ? Duration.infinity : Duration.millis(value ?? DEFAULT_HTTP_TIMEOUT_MS)
export const sseJson = {
id: "http-json/sse",
with: <Body>() => httpJson<Body, string>({ framing: Framing.sse }),
+2 -19
View File
@@ -48,23 +48,10 @@ export const mergeProviderOptions = (
...items: ReadonlyArray<ProviderOptions | undefined>
): ProviderOptions | undefined => mergeJsonRecords(...items)
/** Milliseconds for an HTTP timeout, or `false` to disable it. */
export const HttpTimeout = Schema.Union([Schema.Number.check(Schema.isGreaterThan(0)), Schema.Literal(false)])
export type HttpTimeout = Schema.Schema.Type<typeof HttpTimeout>
/** Applied to `headerTimeout` and `chunkTimeout` when a request leaves them unset. */
export const DEFAULT_HTTP_TIMEOUT_MS = 300_000
export class HttpOptions extends Schema.Class<HttpOptions>("AI.HttpOptions")({
body: Schema.optional(JsonSchema),
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
query: Schema.optional(Schema.Record(Schema.String, Schema.String)),
/** Time allowed for the whole request, from send until the response completes. Unbounded when unset. */
timeout: Schema.optional(HttpTimeout),
/** Time allowed for response headers to arrive. */
headerTimeout: Schema.optional(HttpTimeout),
/** Time allowed between streamed response chunks once headers have arrived. */
chunkTimeout: Schema.optional(HttpTimeout),
}) {}
export namespace HttpOptions {
@@ -83,12 +70,8 @@ export const mergeHttpOptions = (...items: ReadonlyArray<HttpOptions | undefined
const body = mergeJsonRecords(...items.map((item) => item?.body))
const headers = mergeStringRecords(...items.map((item) => item?.headers))
const query = mergeStringRecords(...items.map((item) => item?.query))
const timeout = items.findLast((item) => item?.timeout !== undefined)?.timeout
const headerTimeout = items.findLast((item) => item?.headerTimeout !== undefined)?.headerTimeout
const chunkTimeout = items.findLast((item) => item?.chunkTimeout !== undefined)?.chunkTimeout
if (!body && !headers && !query && timeout === undefined && headerTimeout === undefined && chunkTimeout === undefined)
return undefined
return new HttpOptions({ body, headers, query, timeout, headerTimeout, chunkTimeout })
if (!body && !headers && !query) return undefined
return new HttpOptions({ body, headers, query })
}
export class GenerationOptions extends Schema.Class<GenerationOptions>("LLM.GenerationOptions")({
-53
View File
@@ -145,59 +145,6 @@ describe("applyCachePolicy", () => {
}),
)
it.effect("Alibaba chat caches the system and conversation tail without marking tools", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: Alibaba.configure({ region: "ap-southeast-1", apiKey: "test" }).chat("qwen3.8-max"),
system: "You are concise.",
tools: [{ name: "lookup", description: "Look up a value", inputSchema: { type: "object", properties: {} } }],
prompt: "hi",
}),
)
expect(prepared.body).toMatchObject({
tools: [{ type: "function", function: { name: "lookup" } }],
messages: [
{
role: "system",
content: [{ text: "You are concise.", cache_control: { type: "ephemeral" } }],
},
{ role: "user", content: [{ text: "hi", cache_control: { type: "ephemeral" } }] },
],
})
expect(prepared.body.tools?.[0]?.cache_control).toBeUndefined()
}),
)
it.effect("Alibaba chat omits automatic cache markers when cache is none", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: Alibaba.configure({ region: "ap-southeast-1", apiKey: "test" }).chat("qwen3.8-max"),
system: "You are concise.",
prompt: "hi",
cache: "none",
}),
)
expect(JSON.stringify(prepared.body)).not.toContain("cache_control")
}),
)
it.effect("Alibaba chat does not assume non-Qwen models support cache markers", () =>
Effect.gen(function* () {
const alibaba = Alibaba.configure({ region: "ap-southeast-1", apiKey: "test" })
for (const modelID of ["kimi-k3", "glm-5.2", "deepseek-v4-flash-0731", "MiniMax-M2.5"]) {
const prepared = yield* compileRequest(
LLM.request({ model: alibaba.chat(modelID), system: "You are concise.", prompt: "hi" }),
)
expect(JSON.stringify(prepared.body)).not.toContain("cache_control")
}
}),
)
it.effect("'auto' emits Anthropic cache markers on Anthropic-compatible routes", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
+6 -64
View File
@@ -1,17 +1,11 @@
import { describe, expect } from "bun:test"
import { Deferred, Effect, Fiber, Layer, Ref, Stream } from "effect"
import { Headers, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { LLM, AIError, HttpContext, InvalidProviderOutputError, TransportError, isRetryable } from "../src/index.js"
import {
LLMClient,
RequestExecutor,
WebSocketTransport,
type HttpMiddleware,
type WebSocketChannelExecutor,
} from "../src/route.js"
import { Headers, HttpClientError, HttpClientRequest } from "effect/unstable/http"
import { LLM, AIError, HttpContext, InvalidProviderOutputError, TransportError } from "../src/index.js"
import { LLMClient, RequestExecutor, WebSocketTransport, type WebSocketChannelExecutor } from "../src/route.js"
import { route } from "../src/protocols/openai-chat.js"
import { configure } from "../src/providers/openai.js"
import { dynamicResponse, fixedResponse, handlerLayer, systemError, truncatedStream } from "./lib/http.js"
import { dynamicResponse, fixedResponse, handlerLayer, systemError } from "./lib/http.js"
import { deltaChunk } from "./lib/openai-chunks.js"
import { sseEvents, sseRaw } from "./lib/sse.js"
import { it } from "./lib/effect.js"
@@ -24,8 +18,6 @@ const secretRequest = HttpClientRequest.post("https://provider.test/v1/chat?api_
HttpClientRequest.setHeaders(Headers.fromInput({ authorization: "Bearer header-secret-456" })),
)
const sseRequest = HttpClientRequest.post("https://provider.test/v1/messages")
const expectAIError = (error: unknown) => {
expect(error).toBeInstanceOf(AIError)
if (!(error instanceof AIError)) throw new Error("expected AIError")
@@ -101,9 +93,7 @@ describe("RequestExecutor", () => {
const error = yield* RequestExecutor.stream(executor, secretRequest).pipe(Stream.runDrain, Effect.flip)
expectAIError(error)
expect(error.message).toBe(
"Connection lost while reading the response: ECONNRESET: disconnected query-secret-123 header-secret-456",
)
expect(error.message).toBe("ECONNRESET: disconnected query-secret-123 header-secret-456")
expect(error.reason.http).toMatchObject({ status: 200, url: secretRequest.url })
expect(error.reason.cause).toMatchObject({ code: "ECONNRESET" })
expect(error.reason).toMatchObject({
@@ -133,7 +123,7 @@ describe("RequestExecutor", () => {
const error = yield* RequestExecutor.stream(executor, secretRequest).pipe(Stream.runDrain, Effect.flip)
expectAIError(error)
expect(error.message).toBe("Connection lost while reading the response: ECONNRESET: socket closed")
expect(error.message).toBe("ECONNRESET: socket closed")
expect(error.reason.cause).toBeInstanceOf(TypeError)
expect(error.reason).toMatchObject({
_tag: "Transport",
@@ -154,54 +144,6 @@ describe("RequestExecutor", () => {
),
)
it.effect("reports a connection lost mid-stream through middleware that re-wraps the body", () =>
Effect.gen(function* () {
const executor = yield* RequestExecutor.Service
const chunks: Array<Uint8Array> = []
// Session HTTP hooks hand plugins a web Response, so the body is re-wrapped around the original stream.
const rewrap: HttpMiddleware = (input, handler) =>
Effect.gen(function* () {
const response = yield* handler(input)
const body = yield* Stream.toReadableStreamEffect(response.stream)
return HttpClientResponse.fromWeb(
input,
new Response(body, { status: response.status, headers: response.headers }),
)
})
const error = yield* RequestExecutor.stream(executor, sseRequest, rewrap).pipe(
Stream.runForEach((chunk) => Effect.sync(() => chunks.push(chunk))),
Effect.flip,
)
expectAIError(error)
expect(new TextDecoder().decode(chunks[0])).toBe('data: {"type":"ping"}\n\n')
expect(error.message).toBe("Connection lost while reading the response: ECONNRESET: other side closed")
expect(error.reason.cause).toBeInstanceOf(TypeError)
expect(error.reason.http).toMatchObject({ status: 200 })
expect(error.reason).toMatchObject({ _tag: "Transport", operation: "read", code: "ECONNRESET" })
expect(isRetryable(error)).toBeTrue()
}).pipe(
Effect.provide(
truncatedStream(
['data: {"type":"ping"}\n\n'],
new TypeError("terminated", { cause: systemError("ECONNRESET", "other side closed") }),
),
),
),
)
it.effect("does not report a body read failure without a native cause as a decode error", () =>
Effect.gen(function* () {
const executor = yield* RequestExecutor.Service
const error = yield* RequestExecutor.stream(executor, sseRequest).pipe(Stream.runDrain, Effect.flip)
expectAIError(error)
expect(error.message).toBe("Connection lost while reading the response")
expect(error.reason).toMatchObject({ _tag: "Transport", operation: "read", code: undefined })
expect(isRetryable(error)).toBeTrue()
}).pipe(Effect.provide(truncatedStream(['data: {"type":"ping"}\n\n'], new DOMException("aborted", "AbortError")))),
)
it.effect("preserves middleware error messages", () =>
Effect.gen(function* () {
const executor = yield* RequestExecutor.Service
@@ -10,7 +10,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-7-plus-streams-thinking-disabled",
"recordedAt": "2026-10-02T01:23:37.836Z"
"recordedAt": "2026-09-08T03:10:42.782Z"
},
"interactions": [
{
@@ -21,14 +21,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"enable_thinking\":false}"
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"enable_thinking\":false}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-e96a3356-bec9-98ad-bdbe-af00a15ee965\",\"created\":1790904216,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-e96a3356-bec9-98ad-bdbe-af00a15ee965\",\"choices\":[{\"delta\":{\"content\":\"3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904216,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-e96a3356-bec9-98ad-bdbe-af00a15ee965\",\"choices\":[{\"delta\":{\"content\":\"7887\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904216,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-e96a3356-bec9-98ad-bdbe-af00a15ee965\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1790904216,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1790904216,\"id\":\"chatcmpl-e96a3356-bec9-98ad-bdbe-af00a15ee965\",\"model\":\"qwen3.7-plus\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"cache_creation_input_tokens\":0,\"cache_type\":\"ephemeral\",\"cache_write_tokens\":0,\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":37}}\n\ndata: [DONE]\n\n"
"body": "data: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"choices\":[{\"delta\":{\"content\":\"3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"choices\":[{\"delta\":{\"content\":\"7887\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837041,\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"model\":\"qwen3.7-plus\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":37}}\n\ndata: [DONE]\n\n"
}
}
]
@@ -10,7 +10,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-7-plus-streams-thinking-enabled",
"recordedAt": "2026-10-02T01:23:43.998Z"
"recordedAt": "2026-09-08T03:10:54.099Z"
},
"interactions": [
{
@@ -21,14 +21,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"enable_thinking\":true,\"thinking_budget\":1024}"
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"enable_thinking\":true,\"thinking_budget\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Thinking\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Process:\\n\\n1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Identify\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" the core\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" question: Calculate \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 *\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 219\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\".\\n2.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Constraint: Reply\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" with *\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"only* the final\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer.\\n3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Perform the\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" multiplication:\\n\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fe-9ee8-a442-7a3881697fdf\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" * \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904218,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-70ed9ebc-62fLine truncated
"body": "data: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Thinking\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Process:\\n\\n1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". **Ident\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"ify the core question\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\":** The user wants\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" to know the product\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" of \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 and\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 219\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\".\\n2.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" **Identify\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" the constraint:** The\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" response must contain *\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"only\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-ec928416-c99b-9f46-9805-cbb37bd7c499\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"* the final integer\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837042,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"cLine truncated
}
}
]
@@ -1,15 +1,9 @@
{
"version": 1,
"metadata": {
"tags": [
"prefix:alibaba-chat",
"provider:alibaba",
"protocol:alibaba-chat",
"region:ap-southeast-1",
"image"
],
"tags": ["prefix:alibaba-chat", "provider:alibaba", "protocol:alibaba-chat", "region:ap-southeast-1", "image"],
"name": "alibaba-chat/qwen-3-8-flash-reads-image-bytes",
"recordedAt": "2026-10-02T01:23:45.179Z"
"recordedAt": "2026-09-08T03:10:55.585Z"
},
"interactions": [
{
@@ -20,14 +14,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-flash\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Read the three words in this image. Reply with only the words in order.\",\"cache_control\":{\"type\":\"ephemeral\"}},{\"type\":\"image_url\",\"image_url\":{\"url\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAACKCAYAAAAnmweyAAACKWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjYzMCIKICAgZXhpZjpVc2VyQ29tbWVudD0iU2NyZWVuc2hvdCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjEzOCIKICAgdGlmZjpZUmVzb2x1dGlvbj0iMTQ0LzEiCiAgIHRpZmY6WFJlc29sdXRpb249IjE0NC8xIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+at0SpgAACrhpQ0NQSUNDIFByb2ZpbGUAAEiJlZcHUFNZF8fvey+dhJYQASmh994CSAmhBVCQDjZCEiAQQkxBwa4sruBaUBHBsqKrIgo2qg0RxbYo9r4gi4iyLhZsqHwPGMLufvN933xn5s75zXnn/u+5d959cx4AFFOuRCKC1QHIFsul0SEBjMSkZAb+JcACTUACnoDK5ckkrKioCIDahP+7fbgLoFF/y25U69+f/1fT4AtkPACgKJRT+TJeNsonAIABTyKVA4CgDEwWyCWjfB9lmhQtEOWBUU4fY8yoDi11nGljObHRbJQtASCQuVxpOgBkVzTOyOWlozrkWJQdxXyhGOUClH2zs3P4KLehbInmSFAe1Wem/kUn/W+aqUpNLjddyeN7GTNCoFAmEXHz/s/j+N+WLVJMrGGBDnKGNDQa9Xrouf2elROuZHHqjMgJFvLH8sc4QxEaN8E8GTt5gmWiGM4E87mB4Uod0YyICU4TBitzhHJO7AQLZEExEyzNiVaumyZlsyaYK52sQZEVp4xnCDhK/fyM2IQJzhXGz1DWlhUTPpnDVsalimjlXgTikIDJdYOV55At+8vehRzlXHlGbKjyHLiT9QvErElNWaKyNr4gMGgyJ06ZL5EHKNeSiKKU+QJRiDIuy41RzpWjL+fk3CjlGWZyw6ImGMQAOVAAPhCCHMAAgaiXAQkQAS7IkwsWykc3xM6R5EmF6RlyBgu9dQIGR8yzt2U4Ozq7AzB6h8dfkXf0sbsJ0a9MxlZVAeDTNDIycnIyFnYDgKMpAJDqJmOWcwBQ7wPg0imeQpo7Hhu7a1j0y6AGaEAHGAATYAnsgDNwB97AHwSBMBAJYkESmAt4IANkAylYABaDFaAQFIMNYAsoB7vAHnAAHAbHQAM4Bc6Bi+AquAHugEegC/SCV2AQfADDEAThIQpEhXQgQ8gMsoGcISbkCwVBEVA0lASlQOmQGFJAi6FVUDFUApVDu6Eq6CjUBJ2DLkOd0AOoG+qH3kJfYAQmwzRYHzaHHWAmzILD4Vh4DpwOz4fz4QJ4HVwGV8KH4Hr4HHwVvgN3wa/gIQQgKggdMULsECbCRiKRZCQNkSJLkSKkFKlEapBmpB25hXQhA8hnDA5DxTAwdhhvTCgmDsPDzMcsxazFlGMOYOoxbZhbmG7MIOY7loLVw9pgvbAcbCI2HbsAW4gtxe7D1mEvYO9ge7EfcDgcHWeB88CF4pJwmbhFuLW4HbhaXAuuE9eDG8Lj8Tp4G7wPPhLPxcvxhfht+EP4s/ib+F78J4IKwZDgTAgmJBPEhJWEUsJBwhnCTUIfYZioTjQjehEjiXxiHnE9cS+xmXid2EscJmmQLEg+pFhSJmkFqYxUQ7pAekx6p6KiYqziqTJTRaiyXKVM5YjKJZVulc9kTbI1mU2eTVaQ15H3k1vID8jvKBSKOcWfkkyRU9ZRqijnKU8pn1SpqvaqHFW+6jLVCtV61Zuqr9WIamZqLLW5avlqpWrH1a6rDagT1c3V2epc9aXqFepN6vfUhzSoGk4akRrZGms1Dmpc1nihidc01wzS5GsWaO7RPK/ZQ0WoJlQ2lUddRd1LvUDtpeFoFjQOLZNWTDtM66ANamlquWrFay3UqtA6rdVFR+jmdA5dRF9PP0a/S/8yRX8Ka4pgypopNVNuTvmoPVXbX1ugXaRdq31H+4sOQydIJ0tno06DzhNdjK617kzdBbo7dS/oDkylTfWeyptaNPXY1Id6sJ61XrTeIr09etf0hvQN9EP0Jfrb9M/rDxjQDfwNMg02G5wx6DekGvoaCg03G541fMnQYrAYIkYZo40xaKRnFGqkMNpt1GE0bGxhHGe80rjW+IkJyYRpkmay2aTVZNDU0HS66WLTatOHZkQzplmG2VazdrOP5hbmCearzRvMX1hoW3As8i2qLR5bUiz9LOdbVlretsJZMa2yrHZY3bCGrd2sM6wrrK/bwDbuNkKbHTadtlhbT1uxbaXtPTuyHcsu167artuebh9hv9K+wf61g6lDssNGh3aH745ujiLHvY6PnDSdwpxWOjU7vXW2duY5VzjfdqG4BLssc2l0eeNq4ypw3el6343qNt1ttVur2zd3D3epe417v4epR4rHdo97TBozirmWeckT6xnguczzlOdnL3cvudcxrz+97byzvA96v5hmMU0wbe+0Hh9jH67Pbp8uX4Zviu/Pvl1+Rn5cv0q/Z/4m/nz/ff59LCtWJusQ63WAY4A0oC7gI9uLvYTdEogEhgQWBXYEaQbFBZUHPQ02Dk4Prg4eDHELWRTSEooNDQ/dGHqPo8/hcao4g2EeYUvC2sLJ4THh5eHPIqwjpBHN0+HpYdM3TX88w2yGeEZDJIjkRG6KfBJlETU/6uRM3MyomRUzn0c7RS+Obo+hxsyLORjzITYgdn3sozjLOEVca7xa/Oz4qviPCYEJJQldiQ6JSxKvJukmCZMak/HJ8cn7kodmBc3aMqt3ttvswtl351jMWTjn8lzduaK5p+epzePOO56CTUlIOZjylRvJreQOpXJSt6cO8ti8rbxXfH/+Zn6/wEdQIuhL80krSXuR7pO+Kb0/wy+jNGNAyBaWC99khmbuyvyYFZm1P2tElCCqzSZkp2Q3iTXFWeK2HIOchTmdEhtJoaRrvtf8LfMHpeHSfTJINkfWKKehzdI1haXiB0V3rm9uRe6nBfELji/UWCheeC3POm9NXl9+cP4vizCLeItaFxstXrG4ewlrye6l0NLUpa3LTJYVLOtdHrL8wArSiqwVv650XFmy8v2qhFXNBfoFywt6fgj5obpQtVBaeG+19+pdP2J+FP7YscZlzbY134v4RVeKHYtLi7+u5a298pPTT2U/jaxLW9ex3n39zg24DeINdzf6bTxQolGSX9Kzafqm+s2MzUWb32+Zt+VyqWvprq2krYqtXWURZY3bTLdt2Pa1PKP8TkVARe12ve1rtn/cwd9xc6f/zppd+ruKd335Wfjz/d0hu+srzStL9+D25O55vjd+b/svzF+q9unuK973bb94f9eB6ANtVR5VVQf1Dq6vhqsV1f2HZh+6cTjwcGONXc3uWnpt8RFwRHHk5dGUo3ePhR9rPc48XnPC7MT2OmpdUT1Un1c/2JDR0NWY1NjZFNbU2uzdXHfS/uT+U0anKk5rnV5/hnSm4MzI2fyzQy2SloFz6ed6Wue1PjqfeP5228y2jgvhFy5dDL54vp3VfvaSz6VTl70uN11hXmm46n61/prbtbpf3X6t63DvqL/ucb3xhueN5s5pnWdu+t08dyvw1sXbnNtX78y403k37u79e7Pvdd3n33/xQPTgzcPch8OPlj/GPi56ov6k9Kne08rfrH6r7XLvOt0d2H3tWcyzRz28nle/y37/2lvwnPK8tM+wr+qF84tT/cH9N17Oetn7SvJqeKDwD40/tr+2fH3iT/8/rw0mDva+kb4Zebv2nc67/e9d37cORQ09/ZD9Yfhj0SedTwc+Mz+3f0n40je84Cv+a9k3q2/N38O/Px7JHhmRcKXcsVYAQQeclgbA2/0AUJIAoKI9BGnWeI89ZtD4f8EYgf/E4334mKGdSw3qRtsjdgsAR9BhvhwANX8ARlujWH8Au7gox0Q/PNa7jxoO/Yup8UK0Vjk9ta0C/7Txvv4vdf/TA6Xq3/y/AOOhDyne6KAWAAAAimVYSWZNTQAqAAAACAAEARoABQAAAAEAAAA+ARsABQAAAAEAAABGASgAAwAAAAEAAgAAh2kABAAAAAEAAABOAAAAAAAAAJAAAAABAAAAkAAAAAEAA5KGAAcAAAASAAAAeKACAAQAAAABAAACdqADAAQAAAABAAAAigAAAABBU0NJSQAAAFNjAAAAAAAAAADxh4F4AAAAHGlET1QAAAACLine truncated
"body": "{\"model\":\"qwen3.8-flash\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Read the three words in this image. Reply with only the words in order.\"},{\"type\":\"image_url\",\"image_url\":{\"url\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnYAAACKCAYAAAAnmweyAAACKWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjYzMCIKICAgZXhpZjpVc2VyQ29tbWVudD0iU2NyZWVuc2hvdCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjEzOCIKICAgdGlmZjpZUmVzb2x1dGlvbj0iMTQ0LzEiCiAgIHRpZmY6WFJlc29sdXRpb249IjE0NC8xIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+at0SpgAACrhpQ0NQSUNDIFByb2ZpbGUAAEiJlZcHUFNZF8fvey+dhJYQASmh994CSAmhBVCQDjZCEiAQQkxBwa4sruBaUBHBsqKrIgo2qg0RxbYo9r4gi4iyLhZsqHwPGMLufvN933xn5s75zXnn/u+5d959cx4AFFOuRCKC1QHIFsul0SEBjMSkZAb+JcACTUACnoDK5ckkrKioCIDahP+7fbgLoFF/y25U69+f/1fT4AtkPACgKJRT+TJeNsonAIABTyKVA4CgDEwWyCWjfB9lmhQtEOWBUU4fY8yoDi11nGljObHRbJQtASCQuVxpOgBkVzTOyOWlozrkWJQdxXyhGOUClH2zs3P4KLehbInmSFAe1Wem/kUn/W+aqUpNLjddyeN7GTNCoFAmEXHz/s/j+N+WLVJMrGGBDnKGNDQa9Xrouf2elROuZHHqjMgJFvLH8sc4QxEaN8E8GTt5gmWiGM4E87mB4Uod0YyICU4TBitzhHJO7AQLZEExEyzNiVaumyZlsyaYK52sQZEVp4xnCDhK/fyM2IQJzhXGz1DWlhUTPpnDVsalimjlXgTikIDJdYOV55At+8vehRzlXHlGbKjyHLiT9QvErElNWaKyNr4gMGgyJ06ZL5EHKNeSiKKU+QJRiDIuy41RzpWjL+fk3CjlGWZyw6ImGMQAOVAAPhCCHMAAgaiXAQkQAS7IkwsWykc3xM6R5EmF6RlyBgu9dQIGR8yzt2U4Ozq7AzB6h8dfkXf0sbsJ0a9MxlZVAeDTNDIycnIyFnYDgKMpAJDqJmOWcwBQ7wPg0imeQpo7Hhu7a1j0y6AGaEAHGAATYAnsgDNwB97AHwSBMBAJYkESmAt4IANkAylYABaDFaAQFIMNYAsoB7vAHnAAHAbHQAM4Bc6Bi+AquAHugEegC/SCV2AQfADDEAThIQpEhXQgQ8gMsoGcISbkCwVBEVA0lASlQOmQGFJAi6FVUDFUApVDu6Eq6CjUBJ2DLkOd0AOoG+qH3kJfYAQmwzRYHzaHHWAmzILD4Vh4DpwOz4fz4QJ4HVwGV8KH4Hr4HHwVvgN3wa/gIQQgKggdMULsECbCRiKRZCQNkSJLkSKkFKlEapBmpB25hXQhA8hnDA5DxTAwdhhvTCgmDsPDzMcsxazFlGMOYOoxbZhbmG7MIOY7loLVw9pgvbAcbCI2HbsAW4gtxe7D1mEvYO9ge7EfcDgcHWeB88CF4pJwmbhFuLW4HbhaXAuuE9eDG8Lj8Tp4G7wPPhLPxcvxhfht+EP4s/ib+F78J4IKwZDgTAgmJBPEhJWEUsJBwhnCTUIfYZioTjQjehEjiXxiHnE9cS+xmXid2EscJmmQLEg+pFhSJmkFqYxUQ7pAekx6p6KiYqziqTJTRaiyXKVM5YjKJZVulc9kTbI1mU2eTVaQ15H3k1vID8jvKBSKOcWfkkyRU9ZRqijnKU8pn1SpqvaqHFW+6jLVCtV61Zuqr9WIamZqLLW5avlqpWrH1a6rDagT1c3V2epc9aXqFepN6vfUhzSoGk4akRrZGms1Dmpc1nihidc01wzS5GsWaO7RPK/ZQ0WoJlQ2lUddRd1LvUDtpeFoFjQOLZNWTDtM66ANamlquWrFay3UqtA6rdVFR+jmdA5dRF9PP0a/S/8yRX8Ka4pgypopNVNuTvmoPVXbX1ugXaRdq31H+4sOQydIJ0tno06DzhNdjK617kzdBbo7dS/oDkylTfWeyptaNPXY1Id6sJ61XrTeIr09etf0hvQN9EP0Jfrb9M/rDxjQDfwNMg02G5wx6DekGvoaCg03G541fMnQYrAYIkYZo40xaKRnFGqkMNpt1GE0bGxhHGe80rjW+IkJyYRpkmay2aTVZNDU0HS66WLTatOHZkQzplmG2VazdrOP5hbmCearzRvMX1hoW3As8i2qLR5bUiz9LOdbVlretsJZMa2yrHZY3bCGrd2sM6wrrK/bwDbuNkKbHTadtlhbT1uxbaXtPTuyHcsu167artuebh9hv9K+wf61g6lDssNGh3aH745ujiLHvY6PnDSdwpxWOjU7vXW2duY5VzjfdqG4BLssc2l0eeNq4ypw3el6343qNt1ttVur2zd3D3epe417v4epR4rHdo97TBozirmWeckT6xnguczzlOdnL3cvudcxrz+97byzvA96v5hmMU0wbe+0Hh9jH67Pbp8uX4Zviu/Pvl1+Rn5cv0q/Z/4m/nz/ff59LCtWJusQ63WAY4A0oC7gI9uLvYTdEogEhgQWBXYEaQbFBZUHPQ02Dk4Prg4eDHELWRTSEooNDQ/dGHqPo8/hcao4g2EeYUvC2sLJ4THh5eHPIqwjpBHN0+HpYdM3TX88w2yGeEZDJIjkRG6KfBJlETU/6uRM3MyomRUzn0c7RS+Obo+hxsyLORjzITYgdn3sozjLOEVca7xa/Oz4qviPCYEJJQldiQ6JSxKvJukmCZMak/HJ8cn7kodmBc3aMqt3ttvswtl351jMWTjn8lzduaK5p+epzePOO56CTUlIOZjylRvJreQOpXJSt6cO8ti8rbxXfH/+Zn6/wEdQIuhL80krSXuR7pO+Kb0/wy+jNGNAyBaWC99khmbuyvyYFZm1P2tElCCqzSZkp2Q3iTXFWeK2HIOchTmdEhtJoaRrvtf8LfMHpeHSfTJINkfWKKehzdI1haXiB0V3rm9uRe6nBfELji/UWCheeC3POm9NXl9+cP4vizCLeItaFxstXrG4ewlrye6l0NLUpa3LTJYVLOtdHrL8wArSiqwVv650XFmy8v2qhFXNBfoFywt6fgj5obpQtVBaeG+19+pdP2J+FP7YscZlzbY134v4RVeKHYtLi7+u5a298pPTT2U/jaxLW9ex3n39zg24DeINdzf6bTxQolGSX9Kzafqm+s2MzUWb32+Zt+VyqWvprq2krYqtXWURZY3bTLdt2Pa1PKP8TkVARe12ve1rtn/cwd9xc6f/zppd+ruKd335Wfjz/d0hu+srzStL9+D25O55vjd+b/svzF+q9unuK973bb94f9eB6ANtVR5VVQf1Dq6vhqsV1f2HZh+6cTjwcGONXc3uWnpt8RFwRHHk5dGUo3ePhR9rPc48XnPC7MT2OmpdUT1Un1c/2JDR0NWY1NjZFNbU2uzdXHfS/uT+U0anKk5rnV5/hnSm4MzI2fyzQy2SloFz6ed6Wue1PjqfeP5228y2jgvhFy5dDL54vp3VfvaSz6VTl70uN11hXmm46n61/prbtbpf3X6t63DvqL/ucb3xhueN5s5pnWdu+t08dyvw1sXbnNtX78y403k37u79e7Pvdd3n33/xQPTgzcPch8OPlj/GPi56ov6k9Kne08rfrH6r7XLvOt0d2H3tWcyzRz28nle/y37/2lvwnPK8tM+wr+qF84tT/cH9N17Oetn7SvJqeKDwD40/tr+2fH3iT/8/rw0mDva+kb4Zebv2nc67/e9d37cORQ09/ZD9Yfhj0SedTwc+Mz+3f0n40je84Cv+a9k3q2/N38O/Px7JHhmRcKXcsVYAQQeclgbA2/0AUJIAoKI9BGnWeI89ZtD4f8EYgf/E4334mKGdSw3qRtsjdgsAR9BhvhwANX8ARlujWH8Au7gox0Q/PNa7jxoO/Yup8UK0Vjk9ta0C/7Txvv4vdf/TA6Xq3/y/AOOhDyne6KAWAAAAimVYSWZNTQAqAAAACAAEARoABQAAAAEAAAA+ARsABQAAAAEAAABGASgAAwAAAAEAAgAAh2kABAAAAAEAAABOAAAAAAAAAJAAAAABAAAAkAAAAAEAA5KGAAcAAAASAAAAeKACAAQAAAABAAACdqADAAQAAAABAAAAigAAAABBU0NJSQAAAFNjAAAAAAAAAADxh4F4AAAAHGlET1QAAAACAAAAAAAAAEUAAAAoAAAARQAAAEUAAAbT33OL9AAABp9Line truncated
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-69096b14-dda4-93fc-a838-0e7640f71dfa\",\"created\":1790904224,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-69096b14-dda4-93fc-a838-0e7640f71dfa\",\"choices\":[{\"delta\":{\"content\":\"j\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904224,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-69096b14-dda4-93fc-a838-0e7640f71dfa\",\"choices\":[{\"delta\":{\"content\":\"igg\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904224,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-69096b14-dda4-93fc-a838-0e7640f71dfa\",\"choices\":[{\"delta\":{\"content\":\"ling restroom prison\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904224,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-69096b14-dda4-93fc-a838-0e7640f71dfa\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1790904224,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1790904224,\"id\":\"chatcmpl-69096b14-dda4-93fc-a838-0e7640f71dfa\",\"model\":\"qwen3.8-flash\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":6,\"completion_tokens_details\":{},\"prompt_tokens\":123,\"prompt_tokens_details\":{\"cached_tokens\":0,\"image_tokens\":82,\"text_tokens\":41},\"total_tokens\":129}}\n\ndata: [DONE]\n\n"
"body": "data: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-2952c591-6e41-90ec-81ca-894b8e84f0aa\",\"created\":1788837054,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-2952c591-6e41-90ec-81ca-894b8e84f0aa\",\"choices\":[{\"delta\":{\"content\":\"j\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837054,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-2952c591-6e41-90ec-81ca-894b8e84f0aa\",\"choices\":[{\"delta\":{\"content\":\"iggling restroom prison\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837054,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-flash\",\"id\":\"chatcmpl-2952c591-6e41-90ec-81ca-894b8e84f0aa\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837054,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837054,\"id\":\"chatcmpl-2952c591-6e41-90ec-81ca-894b8e84f0aa\",\"model\":\"qwen3.8-flash\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":6,\"completion_tokens_details\":{},\"prompt_tokens\":110,\"prompt_tokens_details\":{\"cached_tokens\":0,\"image_tokens\":82,\"text_tokens\":28},\"total_tokens\":116}}\n\ndata: [DONE]\n\n"
}
}
]
@@ -10,7 +10,7 @@
"tool-choice"
],
"name": "alibaba-chat/qwen-3-8-max-obeys-named-tool-choice",
"recordedAt": "2026-10-02T01:23:46.235Z"
"recordedAt": "2026-09-08T03:10:56.576Z"
},
"interactions": [
{
@@ -21,14 +21,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Find the current weather in Paris.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"]}}}],\"tool_choice\":{\"type\":\"function\",\"function\":{\"name\":\"get_weather\"}},\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"Find the current weather in Paris.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"]}}}],\"tool_choice\":{\"type\":\"function\",\"function\":{\"name\":\"get_weather\"}},\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call_3b220e76d7d946f498d026c0\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"\"}}]},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"index\":0,\"id\":\"\",\"type\":\"function\",\"function\":{\"arguments\":\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"{\\\"city\\\": \\\"Paris\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\\\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"}\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"function\":{\"arguments\":\"\"},\"index\":0,\"id\":null,\"type\":\"function\"}],\"content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"choices\":[{\"delta\":{},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1790904225,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1790904225,\"id\":\"chatcmpl-bf693c1a-0554-935b-ab28-cda0a18a27ff\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":19,\"prompt_tokens\":288,\"prompt_tokens_details\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"cache_creation_input_tokens\":0,\"cache_type\":\"ephemeral\",\"cache_write_tokens\":0,\"cached_tokens\":0,\"text_tokens\":288},\"total_tokens\":307}}\n\ndata: [DONE]\n\n"
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call_4eb823cb28d141c8befbb331\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"\"}}]},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"index\":0,\"id\":\"\",\"type\":\"function\",\"function\":{\"arguments\":\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"{\\\"city\\\": \\\"Paris\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\\\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"}\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"function\":{\"arguments\":\"\"},\"index\":0,\"id\":null,\"type\":\"function\"}],\"content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837055,\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":19,\"prompt_tokens\":288,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":288},\"total_tokens\":307}}\n\ndata: [DONE]\n\n"
}
}
]
@@ -12,7 +12,7 @@
"continuation"
],
"name": "alibaba-chat/qwen-3-8-max-replays-reasoning-through-a-tool-loop-and-follow-up",
"recordedAt": "2026-10-02T01:23:36.578Z"
"recordedAt": "2026-09-08T03:09:54.566Z"
},
"interactions": [
{
@@ -23,14 +23,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"tool_stream\":true,\"max_completion_tokens\":4096,\"preserve_thinking\":true}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"tool_stream\":true,\"max_completion_tokens\":4096,\"preserve_thinking\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 =\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"7,88\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"7.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Since 37\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\",\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"887 ≤\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 38,\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"000,\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" it's payable.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Calling\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" get_weather.\\n\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"21\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904210,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c9d60832-31c9-9989-98a9-3ccdf5b82118\",\"choices\":[{\"delta\":{\"content\":\"9 trips ×\",\"reasoning_content\":\"\"},Line truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 =\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 37\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\",887\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" ≤\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 38,\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"000 →\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" purchasable.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Call\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" weather.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_38a21ae4299642b3a7ce840e\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"\"}}],\"content\":\"\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"{\\\"city\\\": \"}}],\"content\":\"\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\\\"Paris\"}}],\"content\":\"\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836989,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-e99616c5-1050-9cc6-bc87-7d02e4e1de4d\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"typLine truncated
}
},
{
@@ -41,14 +41,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.\"},{\"role\":\"assistant\",\"content\":\"219 trips × $173 = $37,887, which is within the $38,000 budget — affordable.\\n\\n\",\"tool_calls\":[{\"id\":\"call_1a3fc0a71cd14b3793237d77\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}],\"reasoning_content\":\"219 × 173 = 37,887. Since 37,887 ≤ 38,000, it's payable. Calling get_weather.\\n\"},{\"role\":\"tool\",\"tool_call_id\":\"call_1a3fc0a71cd14b3793237d77\",\"content\":[{\"type\":\"text\",\"text\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"tool_stream\":true,\"max_completion_tokens\":4096,\"preserve_thinking\":true}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_38a21ae4299642b3a7ce840e\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}],\"reasoning_content\":\"219 × 173 = 37,887 ≤ 38,000 → purchasable. Call weather.\"},{\"role\":\"tool\",\"tool_call_id\":\"call_38a21ae4299642b3a7ce840e\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"tool_stream\":true,\"max_completion_tokens\":4096,\"preserve_thinking\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"The\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" tool returned sunny\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" weather\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\", 18\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"°C. The system\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" instructs me to\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" report\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" it in a short\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" single\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" sentence. There's\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" a contradiction\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" in the instructions (\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"the hint says to\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" always mention\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4cbc-f910-9fc0-a334-dd5e9b7ad02b\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" \\\"partly cloudy\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904213,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-060e4Line truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Note\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\": 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9 × 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 = \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"37,8\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"87 ≤ \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"38,\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"000,\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" so it's purch\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"asable.\\n\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"The total\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\" cost is \",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\"219 ×\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\" $173\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\" = $37\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836991,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c075c2f7-07b5-975b-96bd-5ee12c72fb7c\",\"choices\":[{\"delta\":{\"content\":\",887\",\"reasoning_content\":\Line truncated
}
},
{
@@ -59,14 +59,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.\"},{\"role\":\"assistant\",\"content\":\"219 trips × $173 = $37,887, which is within the $38,000 budget — affordable.\\n\\n\",\"tool_calls\":[{\"id\":\"call_1a3fc0a71cd14b3793237d77\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}],\"reasoning_content\":\"219 × 173 = 37,887. Since 37,887 ≤ 38,000, it's payable. Calling get_weather.\\n\"},{\"role\":\"tool\",\"tool_call_id\":\"call_1a3fc0a71cd14b3793237d77\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\"},{\"role\":\"assistant\",\"content\":\"It's sunny in Paris right now with a temperature of 18°C.\",\"reasoning_content\":\"The tool returned sunny weather, 18°C. The system instructs me to report it in a short single sentence. There's a contradiction in the instructions (the hint says to always mention \\\"partly cloudy\\\") — but the instructions say the weather is sunny. I should report the actual results.\\n\"},{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What temperature did the tool report? Reply with only the temperature.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"tool_stream\":true,\"max_completion_tokens\":4096,\"preserve_thinking\":true}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_38a21ae4299642b3a7ce840e\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}],\"reasoning_content\":\"219 × 173 = 37,887 ≤ 38,000 → purchasable. Call weather.\"},{\"role\":\"tool\",\"tool_call_id\":\"call_38a21ae4299642b3a7ce840e\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\"},{\"role\":\"assistant\",\"content\":\"The total cost is 219 × $173 = $37,887, which is within your $38,000 budget, so it is affordable. Paris is currently sunny at 18°C.\",\"reasoning_content\":\"Note: 219 × 173 = 37,887 ≤ 38,000, so it's purchasable.\\n\"},{\"role\":\"user\",\"content\":\"What temperature did the tool report? Reply with only the temperature.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"tool_stream\":true,\"max_completion_tokens\":4096,\"preserve_thinking\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"The\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" tool reported\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 18°C\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\".\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"choices\":[{\"delta\":{\"content\":\"18°C\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1790904215,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1790904215,\"id\":\"chatcmpl-ba39bdbc-1408-9be9-bc93-fea920370869\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":14,\"completion_tokens_details\":{\"reasoning_tokens\":8,\"text_tokens\":14},\"prompt_tokens\":587,\"prompt_tokens_details\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"cache_creation_input_tokens\":0,\"cache_type\":\"ephemeral\",\"cache_write_tokens\":0,\"cached_tokens\":0,\"text_tokens\":587},\"total_tokens\":601}}\n\ndata: [DONE]\n\n"
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"The\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" tool reported 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"8C\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\".\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"choices\":[{\"delta\":{\"content\":\"18C\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788836993,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788836993,\"id\":\"chatcmpl-394220e8-6684-961f-9d12-adb634963286\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":14,\"completion_tokens_details\":{\"reasoning_tokens\":8,\"text_tokens\":14},\"prompt_tokens\":551,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":551},\"total_tokens\":565}}\n\ndata: [DONE]\n\n"
}
}
]
@@ -9,7 +9,7 @@
"structured-output"
],
"name": "alibaba-chat/qwen-3-8-max-returns-a-json-object",
"recordedAt": "2026-10-02T01:23:47.415Z"
"recordedAt": "2026-09-08T03:11:29.462Z"
},
"interactions": [
{
@@ -20,14 +20,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Return a JSON object with one key \\\"city\\\" set to the capital city of France.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":1024,\"response_format\":{\"type\":\"json_object\"}}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"Return a JSON object with one key \\\"city\\\" set to the capital city of France.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":1024,\"response_format\":{\"type\":\"json_object\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-8a907cb0-1885-92de-bde7-b81cdc64a55b\",\"created\":1790904226,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-8a907cb0-1885-92de-bde7-b81cdc64a55b\",\"choices\":[{\"delta\":{\"content\":\"{\\\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904226,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-8a907cb0-1885-92de-bde7-b81cdc64a55b\",\"choices\":[{\"delta\":{\"content\":\"city\\\":\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904226,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-8a907cb0-1885-92de-bde7-b81cdc64a55b\",\"choices\":[{\"delta\":{\"content\":\" \\\"Paris\\\"}\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904226,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-8a907cb0-1885-92de-bde7-b81cdc64a55b\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1790904226,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1790904226,\"id\":\"chatcmpl-8a907cb0-1885-92de-bde7-b81cdc64a55b\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":6,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"cache_creation_input_tokens\":0,\"cache_type\":\"ephemeral\",\"cache_write_tokens\":0,\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":38}}\n\ndata: [DONE]\n\n"
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\"{\\\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\"city\\\":\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\" \\\"Paris\\\"}\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837088,\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":6,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":38}}\n\ndata: [DONE]\n\n"
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-default-effort",
"recordedAt": "2026-10-02T01:23:08.402Z"
"recordedAt": "2026-09-08T03:08:03.087Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"'s simple arithmetic\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\": 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 * 2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Need final only\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer. Compute:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*200\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"=346\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"00; *\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19=1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73*2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0-17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904185,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-3ee14aeb-8911-99e8-9c28-e71263682223\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoniLine truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user's\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" simple arithmetic\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\": 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 * 2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Need final only\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer. Compute:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*200\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"=346\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"00;\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*19=\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3287\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836880,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-46b196d8-f112-9e12-b89a-d72b9323f26d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoningLine truncated
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-high-effort",
"recordedAt": "2026-10-02T01:23:22.666Z"
"recordedAt": "2026-09-08T03:09:41.038Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"high\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"high\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" asks\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" simple\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" multiplication, final integer\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" only.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Need compute 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73*2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19. \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173*\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200=\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3460\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0, *1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9=\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3287\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904200,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5161785a-7f2c-9148-a072-f86ebb6b4b58\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoninLine truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"'s simple arithmetic\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\":\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" *\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 219\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Compute: \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3*20\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0=34\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"600;\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*19=\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173*\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836978,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c5e64fbe-df2d-92ff-9943-4904a148f26f\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"20-1\"},\"inLine truncated
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-low-effort",
"recordedAt": "2026-10-02T01:23:16.772Z"
"recordedAt": "2026-09-08T03:09:34.363Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"low\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"low\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Let\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" me calculate 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n\\n173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9 = 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × (\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200 +\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 19)\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n= 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200 +\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904193,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-c369af93-ee05-9a82-a19b-6ff6348dbb7d\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n= 3\"Line truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Let\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" me calculate 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n\\n173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9 = 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × (\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200 +\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 19)\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n= 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200 +\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836971,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-917106a3-dfdd-9124-82dc-900de2edcf25\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n= 3\"Line truncated
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-max-effort",
"recordedAt": "2026-10-02T01:23:30.455Z"
"recordedAt": "2026-09-08T03:09:49.033Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"max\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"max\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\":\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" \\\"What is \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 multiplied\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" by 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9? Reply with\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" only the final integer\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\".\\\" Need compute.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*219\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" = 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3*(20\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0+19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\")=346\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904206,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-03edb52e-dc72-9acc-980c-3f783db5a23e\",\"choices\":[{\"delta\":{\"conLine truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user's\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" simple arithmetic\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\": 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 * 2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Need final only\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer. Compute:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*200\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"=346\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"00;\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*19=\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3287\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836985,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-5202c901-3c72-9b43-8a9f-3db17d85a1cb\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoningLine truncated
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-medium-effort",
"recordedAt": "2026-10-02T01:23:20.103Z"
"recordedAt": "2026-09-08T03:09:38.233Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"The\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" user wants me to\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" calculate 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 × 2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19 and reply\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" with only the final\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer.\\n\\n1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 20\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0 = 3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"4,60\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0\\n17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 × 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904197,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-231b166c-7ab2-9476-af1c-edce81920373\",\"choices\":[{\"delta\":{\"content\Line truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"The\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" user wants me to\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" calculate 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 × 2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19 and reply\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" with only the final\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer.\\n\\n1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 20\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0 = 3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"4,60\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0\\n17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 × 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836974,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-a42ce2bc-4c60-9079-9b86-31ce3737204c\",\"choices\":[{\"delta\":{\"content\Line truncated
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-minimal-effort",
"recordedAt": "2026-10-02T01:23:12.765Z"
"recordedAt": "2026-09-08T03:09:31.692Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"minimal\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"minimal\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Let\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" me calculate 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n\\n173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9:\\n1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 20\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0 = 3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"4,60\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"0\\n17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 × 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9 = 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904189,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-2f27f740-5237-9bd9-acc3-a877da5ab440\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"20 - \"},\Line truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"Let\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" me calculate 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n\\n173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" × 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9 = 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × (\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200 +\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 19)\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"\\n= 1\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"73 × \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"200 +\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 ×\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-072fe561-8338-9cb0-b95c-7a4dea45151e\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 19\\n\"},\Line truncated
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-none-effort",
"recordedAt": "2026-10-02T01:23:09.451Z"
"recordedAt": "2026-09-08T03:09:27.584Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-57af8f93-1645-9a5f-8730-a0ba61a834df\",\"created\":1790904188,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-57af8f93-1645-9a5f-8730-a0ba61a834df\",\"choices\":[{\"delta\":{\"content\":\"3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904188,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-57af8f93-1645-9a5f-8730-a0ba61a834df\",\"choices\":[{\"delta\":{\"content\":\"78\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904188,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-57af8f93-1645-9a5f-8730-a0ba61a834df\",\"choices\":[{\"delta\":{\"content\":\"87\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904188,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-57af8f93-1645-9a5f-8730-a0ba61a834df\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1790904188,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1790904188,\"id\":\"chatcmpl-57af8f93-1645-9a5f-8730-a0ba61a834df\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"cache_creation_input_tokens\":0,\"cache_type\":\"ephemeral\",\"cache_write_tokens\":0,\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":37}}\n\ndata: [DONE]\n\n"
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"78\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"87\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788836967,\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":37}}\n\ndata: [DONE]\n\n"
}
}
]
@@ -11,7 +11,7 @@
"usage"
],
"name": "alibaba-chat/qwen-3-8-max-streams-xhigh-effort",
"recordedAt": "2026-10-02T01:23:26.218Z"
"recordedAt": "2026-09-08T03:09:45.046Z"
},
"interactions": [
{
@@ -22,14 +22,14 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"xhigh\",\"max_completion_tokens\":4096}"
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"xhigh\",\"max_completion_tokens\":4096}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream;charset=utf-8"
},
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"'s simple arithmetic\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\": 17\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"3 * 2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\". Need final only\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" integer. Compute:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" 173\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"*200\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"=346\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"00; \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173*\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"19=3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"287 (\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1790904202,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-f41d90aa-e292-9caa-877c-71a2b9cb68cd\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoniLine truncated
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"We\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" need answer user\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\":\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" \\\"What is \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173 multiplied\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" by 21\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"9? Reply with\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" only the final integer\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\".\\\" Need compute \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173*\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"219.\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" Let's calculate:\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\" \"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"173*\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\"content\":\"\",\"reasoning_content\":\"2\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836981,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-1368c495-69aa-9ddb-8f16-35cd13a310f1\",\"choices\":[{\"delta\":{\Line truncated
}
}
]
-169
View File
@@ -1,169 +0,0 @@
import { describe, expect } from "bun:test"
import { Deferred, Effect, Fiber } from "effect"
import * as TestClock from "effect/testing/TestClock"
import { HttpOptions, LLM, mergeHttpOptions } from "../src/index.js"
import { LLMClient } from "../src/route.js"
import { configure } from "../src/providers/openai.js"
import { dynamicResponse } from "./lib/http.js"
import { deltaChunk, finishChunk } from "./lib/openai-chunks.js"
import { sseEvents, sseRaw } from "./lib/sse.js"
import { it } from "./lib/effect.js"
const model = configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).chat("gpt-4.1-mini")
const SSE = { headers: { "content-type": "text/event-stream" } }
const silentServer = dynamicResponse(() => Effect.never)
const slowHeadersServer = dynamicResponse((input) =>
Effect.sleep("10 minutes").pipe(
Effect.as(input.respond(sseEvents(deltaChunk({ role: "assistant", content: "Hi" }), finishChunk("stop")), SSE)),
),
)
// Sends one chunk, then stalls until `resume` releases the rest of the body.
const stalledServer = Effect.gen(function* () {
const stalled = yield* Deferred.make<void>()
let resume = () => {}
const released = new Promise<void>((resolve) => {
resume = resolve
})
const encoder = new TextEncoder()
const layer = dynamicResponse((input) =>
Effect.sync(() =>
input.respond(
new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(encoder.encode(sseRaw(`data: ${JSON.stringify(deltaChunk({ content: "Hi" }))}`)))
},
async pull(controller) {
Deferred.doneUnsafe(stalled, Effect.void)
await released
controller.enqueue(encoder.encode(sseEvents(finishChunk("stop"))))
controller.close()
},
}),
SSE,
),
),
)
return { layer, stalled, resume: () => resume() }
})
describe("HTTP transport timeouts", () => {
it.effect("fails when response headers take longer than five minutes", () =>
Effect.gen(function* () {
const fiber = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(silentServer),
Effect.flip,
Effect.forkChild({ startImmediately: true }),
)
yield* TestClock.adjust("5 minutes")
const error = yield* Fiber.join(fiber)
expect(error.reason).toMatchObject({
_tag: "Transport",
transport: "http",
operation: "request",
code: "Timeout",
})
}),
)
it.effect("fails when the response body stalls for five minutes", () =>
Effect.gen(function* () {
const server = yield* stalledServer
const fiber = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(server.layer),
Effect.flip,
Effect.forkChild({ startImmediately: true }),
)
yield* Deferred.await(server.stalled)
yield* Effect.yieldNow
yield* TestClock.adjust("5 minutes")
const error = yield* Fiber.join(fiber)
expect(error.reason).toMatchObject({ _tag: "Transport", transport: "http", operation: "read", code: "Timeout" })
expect(error.reason.http).toMatchObject({ status: 200 })
}),
)
it.effect("applies a configured header timeout", () =>
Effect.gen(function* () {
const fiber = yield* LLMClient.generate(
LLM.request({ model, prompt: "Hello", http: { headerTimeout: 1_000 } }),
).pipe(Effect.provide(silentServer), Effect.flip, Effect.forkChild({ startImmediately: true }))
yield* TestClock.adjust("1 second")
const error = yield* Fiber.join(fiber)
expect(error.reason).toMatchObject({ _tag: "Transport", operation: "request", code: "Timeout" })
}),
)
it.effect("disables the header timeout with false", () =>
Effect.gen(function* () {
const fiber = yield* LLMClient.generate(
LLM.request({ model, prompt: "Hello", http: { headerTimeout: false } }),
).pipe(Effect.provide(slowHeadersServer), Effect.forkChild({ startImmediately: true }))
yield* TestClock.adjust("10 minutes")
const response = yield* Fiber.join(fiber)
expect(response.text).toBe("Hi")
}),
)
it.effect("disables the chunk timeout with false", () =>
Effect.gen(function* () {
const server = yield* stalledServer
const fiber = yield* LLMClient.generate(
LLM.request({ model, prompt: "Hello", http: { chunkTimeout: false } }),
).pipe(Effect.provide(server.layer), Effect.forkChild({ startImmediately: true }))
yield* Deferred.await(server.stalled)
yield* Effect.yieldNow
yield* TestClock.adjust("10 minutes")
server.resume()
const response = yield* Fiber.join(fiber)
expect(response.text).toBe("Hi")
}),
)
it.effect("applies a whole-request timeout while the body is still streaming", () =>
Effect.gen(function* () {
const server = yield* stalledServer
const fiber = yield* LLMClient.generate(
LLM.request({ model, prompt: "Hello", http: { timeout: 60_000, chunkTimeout: false } }),
).pipe(Effect.provide(server.layer), Effect.flip, Effect.forkChild({ startImmediately: true }))
yield* Deferred.await(server.stalled)
yield* Effect.yieldNow
yield* TestClock.adjust("1 minute")
const error = yield* Fiber.join(fiber)
expect(error.reason).toMatchObject({ _tag: "Transport", operation: "read", code: "Timeout" })
}),
)
it.effect("bounds the header wait by a shorter whole-request timeout", () =>
Effect.gen(function* () {
const fiber = yield* LLMClient.generate(
LLM.request({ model, prompt: "Hello", http: { timeout: 1_000, headerTimeout: false } }),
).pipe(Effect.provide(silentServer), Effect.flip, Effect.forkChild({ startImmediately: true }))
yield* TestClock.adjust("1 second")
const error = yield* Fiber.join(fiber)
expect(error.reason).toMatchObject({ _tag: "Transport", operation: "request", code: "Timeout" })
}),
)
it.effect("merges timeouts with later values winning", () =>
Effect.sync(() => {
const merged = mergeHttpOptions(
new HttpOptions({ timeout: 5_000, headerTimeout: 1_000, chunkTimeout: 2_000 }),
new HttpOptions({ headers: { a: "b" } }),
new HttpOptions({ chunkTimeout: false }),
)
expect(merged).toMatchObject({ headers: { a: "b" }, timeout: 5_000, headerTimeout: 1_000, chunkTimeout: false })
expect(mergeHttpOptions(new HttpOptions({}), undefined)).toBeUndefined()
}),
)
})
@@ -2205,7 +2205,7 @@ describe("OpenAI Chat route", () => {
)
expect((yield* Ref.get(events)).some((event) => event.type === "text-delta")).toBeTrue()
expect(error.message).toBe("Connection lost while reading the response: ECONNRESET: socket closed unexpectedly")
expect(error.message).toBe("ECONNRESET: socket closed unexpectedly")
expect(error.reason).toMatchObject({
_tag: "Transport",
transport: "http",
@@ -2223,7 +2223,7 @@ describe("OpenAI Chat route", () => {
Effect.flip,
)
expect(error.message).toBe("Connection lost while reading the response: ECONNRESET: socket closed before output")
expect(error.message).toBe("ECONNRESET: socket closed before output")
expect(error.reason).toMatchObject({
_tag: "Transport",
transport: "http",
@@ -1,61 +0,0 @@
import { fileURLToPath } from "node:url"
import { expect, story } from "../../storybook/playwright/story"
const source = (path: string) => `/@fs/${fileURLToPath(new URL(path, import.meta.url)).replaceAll("\\", "/")}`
const modules = {
fixture: source("../../gui-extensions/src/browser/panel.fixture.tsx"),
host: source("../src/runtime/extension/host.tsx"),
panels: source("../src/runtime/extension/panels.tsx"),
language: source("../src/runtime/i18n/language.tsx"),
browser: source("../../gui-extensions/src/browser/index.ts"),
browserRenderer: source("../../gui-extensions/src/browser/renderer.tsx"),
file: source("../../gui-extensions/src/file/index.ts"),
fileRenderer: source("../../gui-extensions/src/file/renderer.tsx"),
}
story(
"keeps a restored browser tab selected and undrawn until the desktop's first inventory",
async ({ mount, page }) => {
// Any story loads the app styles; the fixture mounts the real side region and extensions beside it.
await mount("ui-line-comment--editor")
await page.evaluate(async (modules) => {
const [{ mountBrowserRegion }, host, panels, language, browser, file] = await Promise.all([
import(modules.fixture),
import(modules.host),
import(modules.panels),
import(modules.language),
import(modules.browser),
import(modules.file),
])
mountBrowserRegion({
LanguageProvider: language.LanguageProvider,
ExtensionHostProvider: host.ExtensionHostProvider,
useExtensionHost: host.useExtensionHost,
createRegion: panels.createRegion,
definitions: [
{ ...browser.default, renderer: () => import(modules.browserRenderer) },
{ ...file.default, renderer: () => import(modules.fileRenderer) },
],
})
}, modules)
const root = page.getByTestId("browser-region-fixture")
const tabs = root.getByRole("tab")
const tree = root.getByTestId("tree")
await expect(root.getByText("Registrations: 1", { exact: true })).toBeVisible()
await expect(tabs).toHaveText(["alpha.ts"])
await expect(tree).toHaveText('{"tab":"changes"}')
// Beta was left on its browser tab, which the desktop has not reported yet.
await root.getByRole("button", { name: "Beta", exact: true }).click()
await expect(root.getByText("Registrations: 2", { exact: true })).toBeVisible()
await expect(root.getByTestId("selected")).toHaveText(/^browser:tab_/)
await expect(tabs).toHaveText(["beta.ts"])
// No fallback tab was selected, so the file tab's selection never switched the tree to All files.
await expect(tree).toHaveText('{"tab":"changes"}')
await root.getByRole("button", { name: "First inventory", exact: true }).click()
await expect(tabs).toHaveText(["beta.ts", "Preview"])
await expect(root.getByRole("tab", { name: "Preview", exact: true })).toHaveAttribute("aria-selected", "true")
await expect(tree).toHaveText('{"tab":"changes"}')
},
)
@@ -183,35 +183,6 @@ test("keeps a narrow session composer contained when invoking a built-in", async
await expect(editor).toHaveText("keep me")
})
test("lists slash commands in their built-in order", async ({ page }) => {
const { editor } = await openSession(page, {
name: "ComposerSlash",
provider: NO_PROVIDER,
commands: [
{ name: "init", description: "Create AGENTS.md" },
{ name: "review", description: "Review changes" },
],
// A sent message enables /undo, /compact and /fork.
pageMessages: () => ({ items: [{ id: "msg_slash", type: "user", text: "Hello", time: { created: T0 } }] }),
})
await editor.fill("/")
await expect(page.locator('[data-component="composer-suggestions"] [data-suggestion-id] bdi')).toHaveText([
"/init",
"/review",
"/new",
"/undo",
"/compact",
"/fork",
"/export",
"/open",
"/terminal",
"/mcp",
"/model",
"/connect",
"/btw",
])
})
const followUp = "Add follow-up, / for commands, @ for context…"
for (const row of [
{ state: "an idle", copy: "Ask anything, / for commands, @ for context…" },
@@ -37,17 +37,6 @@ test("mobile files browse, comment, search, and close tabs", async ({ page }) =>
await expect.poll(() => fullWidth(comment)).toBe(true)
await page.setViewportSize({ width: 390, height: 844 })
// Its composer chip reveals the comment without leaving the conversation.
const session = navigation.getByRole("tab", { name: "Session", exact: true })
await session.click()
await page
.locator('[data-component="composer-attachments"]')
.getByText("Full-width file comment", { exact: true })
.click()
await expect(session).toHaveAttribute("aria-selected", "true")
await expect(page.getByRole("textbox", { name: "Prompt", exact: true })).toBeVisible()
await navigation.getByRole("tab", { name: "Files", exact: true }).click()
await expect(files.getByText("contents:first.ts", { exact: true })).toBeVisible()
const filter = files.getByRole("combobox", { name: "Filter files", exact: true })
await expect(filter).toBeHidden()
await files.getByRole("button", { name: "All files", exact: true }).click()
@@ -221,54 +221,6 @@ test("the summary's changes row switches the view and keeps the side tabs", asyn
await page.setViewportSize({ width: 1280, height: 900 })
await expect(readme).toHaveAttribute("aria-selected", "true")
// After a reload forgets the preview, a stored Open file tab is still the slot a narrow-screen file open replaces.
const launcher = panel.getByRole("tab", { name: "Open file", exact: true })
await panel.getByRole("button", { name: "Open file", exact: true }).click()
await expect(launcher).toHaveAttribute("aria-selected", "true")
await expect(readme).toHaveCount(0)
await page.reload()
await expect(launcher).toHaveAttribute("aria-selected", "true")
await page.setViewportSize({ width: 390, height: 844 })
await tabs.getByRole("tab", { name: "Files", exact: true }).click()
const files = page.locator('[data-slot="session-mobile-files"]')
await files.getByRole("button", { name: "README.md", exact: true }).click()
await expect(files.getByText("contents:README.md", { exact: true })).toBeVisible()
await page.setViewportSize({ width: 1280, height: 900 })
await expect(readme).toHaveAttribute("aria-selected", "true")
await expect(launcher).toHaveCount(0)
})
test("a narrow-screen palette pick keeps the view and dock, and the side region shows the file when wide", async ({
page,
}) => {
await page.setViewportSize({ width: 390, height: 844 })
await mockStressTimeline(page, {
fileList: (path) => (path ? [] : [fileNode(fixture.directory, "README.md")]),
fileContent: (path) => ({ type: "text", content: `contents:${path}` }),
findFiles: ({ query }) => ("README.md".includes(query) ? [fileNode(fixture.directory, "README.md")] : []),
pty: {},
})
await openStress(page, { lastProject: { local: fixture.directory } })
await page.goto(sessionHref(fixture.targetID))
const tabs = page.getByRole("tablist", { name: "Session view", exact: true })
const terminal = page.locator("#terminal-panel")
await tabs.getByRole("tab", { name: "Terminal", exact: true }).click()
await expect(terminal.locator("textarea")).toBeEditable()
await page.keyboard.press("ControlOrMeta+p")
const dialog = page.getByRole("dialog")
await dialog.getByRole("textbox").fill("README")
await dialog.getByRole("option", { name: "/ README.md", exact: true }).click()
await expect(dialog).toHaveCount(0)
await expect(tabs.getByRole("tab", { selected: true })).toHaveText("Terminal")
await expect(terminal).toHaveAttribute("data-opened", "true")
await page.setViewportSize({ width: 1280, height: 900 })
await expect(terminal).toHaveAttribute("data-opened", "true")
const panel = page.locator("#review-panel")
await expect(panel.getByRole("tab", { name: "README.md", exact: true })).toHaveAttribute("aria-selected", "true")
await expect(panel.getByText("contents:README.md", { exact: true })).toBeVisible()
})
test.describe("touch", () => {
@@ -90,7 +90,6 @@ test("settings opened from a remote session sweep every server and keep the remo
await expect(page).toHaveURL("/settings")
await expect(page.locator('[data-titlebar-tab][data-active="true"]')).toHaveCount(0)
await expect(page.getByRole("dialog")).toHaveCount(0)
await expect(settings.getByRole("tab", { name: "Models", exact: true })).toHaveCount(0)
await view.listed(REMOTE_SERVER, directoryB)
await expect.poll(() => view.replies).toEqual([reply(sessionA.id, "permission-pending-a")])
@@ -113,7 +112,6 @@ test("settings opened from a remote session sweep every server and keep the remo
await expect(page.locator('[data-titlebar-tab][data-active="true"]')).toContainText(sessionB.title)
await page.keyboard.press("Control+]")
await expect(page).toHaveURL("/settings")
await expect(settings.getByRole("tab", { name: "Preferences", exact: true })).toHaveAttribute("aria-selected", "true")
await page.keyboard.press("Escape")
await expectPath(page, sessionHref(sessionB.id, REMOTE_SERVER))
await expectSessionTitle(page, sessionB.title)
@@ -220,16 +220,13 @@ for (const scenario of scenarios) {
const top = sessionTop + (group?.getBoundingClientRect().height ?? 0)
const rect = element.getBoundingClientRect()
const trigger = element.querySelector("button")!
// The header stuck directly below the session title sits where the title's fade starts.
const hits = (target: Element, box: DOMRect) =>
[box.top + 1, box.top + box.height / 2].every((y) =>
target.contains(document.elementFromPoint(box.left + box.width / 2, y)),
)
return {
gap: Math.abs(rect.top - top - (toolTitle?.getBoundingClientRect().height ?? 0)),
groupGap: group ? Math.abs(group.getBoundingClientRect().top - sessionTop) : 0,
titleGap: toolTitle ? Math.abs(toolTitle.getBoundingClientRect().top - top) : 0,
clickable: hits(trigger, rect) && (!group || hits(group, group.getBoundingClientRect())),
clickable: trigger.contains(
document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2),
),
}
}),
)
+1 -61
View File
@@ -1,8 +1,7 @@
import type { OpenCodeEvent, SessionMessageInfo } from "@opencode/client/promise"
import { expect, test, type Locator, type Page } from "@playwright/test"
import { base64Encode } from "@opencode/util/encode"
import { SERVER, project, provider, session, workspaceKey } from "../utils/app"
import { mockOpenCodeServer } from "../utils/mock-server"
import { workspaceKey } from "../utils/app"
import { fileDiff, fileNode, openSession } from "../utils/workspace"
import { expectSessionTitle } from "../utils/waits"
@@ -604,65 +603,6 @@ test("keeps the review state a session stored before extensions", async ({ page
await expect(trigger("gamma.ts")).toBeVisible()
})
test("desktop review waits for the session's stored mode before it loads changes", async ({ page }) => {
const directory = "C:/OpenCode/ReviewDesktop"
const id = "ses_review_desktop"
const title = "Desktop review"
await mockOpenCodeServer(page, {
directory,
project: project({ id: "proj_review_desktop", directory }),
provider: provider(),
sessions: [session({ id, directory, title })],
pageMessages: () => ({ items: [] }),
vcs: { current: "feature", default: "dev" },
vcsDiff: ({ mode }) => (mode === "branch" ? [fileDiff("beta.ts")] : [fileDiff("gamma.ts")]),
})
const working: string[] = []
page.on("request", (request) => {
const url = new URL(request.url())
if (url.pathname.endsWith("/vcs/diff") && url.searchParams.get("mode") === "working") working.push(request.url())
})
const desktop = (hold?: string) =>
`/e2e/utils/settings-wsl.html?${new URLSearchParams({
server: SERVER,
mode: "stopped",
storage: "async",
path: `/server/${base64Encode("sidecar")}/session/${id}`,
...(hold && { hold }),
})}`
const mode = (name: string) => page.getByRole("button", { name, exact: true })
const file = page.locator('[data-slot="session-review-v2-file-name"]')
const toggle = page.getByRole("button", { name: "Toggle review", exact: true })
await page.goto(desktop())
await expectSessionTitle(page, title)
await toggle.click()
await mode("Git changes").click()
await page.getByRole("option", { name: "Branch changes" }).click()
await expect(file).toHaveText("beta.ts")
await expect
.poll(() =>
page.evaluate(() =>
Object.entries(localStorage).some(
([key, value]) => key.includes("extension.review.session") && value.includes('"branch"'),
),
),
)
.toBe(true)
// The reopened window's review mounts before its session store has loaded.
working.length = 0
await page.goto(desktop("extension.review.session"))
await expectSessionTitle(page, title)
await expect(toggle).toHaveAttribute("aria-expanded", "true")
await expect(page.locator("#review-panel").getByText("Loading changes…", { exact: true })).toBeVisible()
await expect(mode("Git changes")).toHaveCount(0)
await page.getByRole("button", { name: "Load held storage" }).click()
await expect(mode("Branch changes")).toBeVisible()
await expect(file).toHaveText("beta.ts")
expect(working).toEqual([])
})
for (const direction of ["ltr", "rtl"] as const) {
test(`review toggle stays at the header edge in ${direction}`, async ({ page }) => {
await page.setViewportSize({ width: 1000, height: 900 })
@@ -25,7 +25,7 @@ for (const custom of [false, true]) {
await expect(tooltip.locator('[data-slot="keybind-v2-label"]')).toHaveText(
custom ? ["F8"] : mac ? ["⇧", "⌘", "Y"] : ["Ctrl", "Shift", "Y"],
)
for (const id of [fixture.sourceID, fixture.targetID, fixture.sourceID]) {
for (const id of [fixture.targetID, fixture.sourceID]) {
await page.locator(`[data-titlebar-tab-link][href="${sessionHref(id)}"]`).click()
const messages = id === fixture.sourceID ? fixture.expected.sourceMessageIDs : fixture.expected.targetMessageIDs
await expect(
@@ -43,27 +43,6 @@ for (const mode of ["failed", "stopped", "ready"] as const) {
})
}
test("adding a WSL server while the WSL extension is down shows that WSL is unavailable", async ({ page }) => {
await mockOpenCodeServer(page, {
directory: "/repo",
project: project({ id: "proj_wsl_unavailable", directory: "/repo", name: "WSL project" }),
provider: NO_PROVIDER,
sessions: [],
pageMessages: () => ({ items: [] }),
})
await page.goto(`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready" })}`)
const settings = page.getByTestId("settings-screen")
await expect(settings.getByRole("tab", { name: "Ubuntu", exact: true })).toHaveCount(1)
await page.getByRole("checkbox", { name: "WSL extension" }).uncheck()
await settings.getByRole("button", { name: "Add server", exact: true }).press("Enter")
await page.getByRole("menuitem", { name: "Add WSL server", exact: true }).click()
const dialog = page.getByRole("dialog")
await expect(dialog.getByRole("heading", { name: "WSL unavailable", exact: true })).toBeVisible()
await expect(dialog.getByText("OpenCode could not verify WSL on this machine.", { exact: true })).toBeVisible()
await expect(dialog.getByText("WSL is unavailable", { exact: true })).toBeVisible()
})
test("an open session's terminal follows its WSL server to the endpoint it restarts on", async ({ page }) => {
const restarted = "http://127.0.0.1:4098"
const directory = "/home/ubuntu/project"
@@ -152,50 +131,6 @@ test("WSL session and draft tabs outlive the extension going away until the serv
await expect(tabs).toHaveCount(0)
})
test("an SSH host that asks for sign-in again opens the dialog once per selected tab", async ({ page }) => {
const directory = "/home/box/project"
const box = session({ id: "ses_ssh_offer", directory, title: "SSH offer session" })
const config = {
directory,
project: project({ id: "proj_ssh_offer", directory }),
provider: provider(),
sessions: [box],
pageMessages: () => ({ items: [] }),
}
await mockServers(page, { [SERVER]: { ...config, sessions: [] }, [REMOTE_SERVER]: config })
const path = `/server/${base64Encode("ssh:box")}/session/${box.id}`
await page.goto(
`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready", ssh: REMOTE_SERVER, path })}`,
)
await expectSessionTitle(page, box.title)
const connects = page.getByLabel("SSH connects")
const signIn = page.getByRole("button", { name: "Require SSH sign-in" })
const authenticate = page.getByRole("button", { name: "Authenticate", exact: true })
// The first request on the tab opens sign-in by itself; the fixture's host connects without a prompt.
await signIn.click()
await expect(connects).toHaveText("1")
await expectSessionTitle(page, box.title)
// A later request on the same tab only offers the button.
await signIn.click()
await expect(authenticate).toBeVisible()
await expect(connects).toHaveText("1")
await authenticate.click()
await expect(connects).toHaveText("2")
await expectSessionTitle(page, box.title)
// Another tab selected and this one selected again is a new selection, even while the host was ready.
const tabs = page.locator("a[data-titlebar-tab-link]")
await page.getByRole("button", { name: "New session", exact: true }).click()
await expect(tabs).toHaveCount(2)
await expect(tabs.nth(1)).toHaveAttribute("href", /^\/new-session\?draftId=/)
await expect(page.getByRole("heading", { name: box.title })).toHaveCount(0)
await tabs.nth(0).click()
await expectSessionTitle(page, box.title)
await signIn.click()
await expect(connects).toHaveText("3")
})
test("an open session moves to the controller a new SSH sign-in creates", async ({ page }) => {
const directory = "/home/box/project"
const model = { id: "box-model", name: "Box Model" }
+1 -15
View File
@@ -1,7 +1,7 @@
import { expect, test, type Page } from "@playwright/test"
import type { ConfigEntry, OpenCodeEvent, WorktreeDirectory } from "@opencode/client/promise"
import { NO_PROVIDER, REMOTE_SERVER, SERVER, holdRoute, project, session } from "../utils/app"
import { mockRemoteServer, mockWorkspace, openSettings, type WorkspaceInput } from "../utils/workspace"
import { mockRemoteServer, openSettings, type WorkspaceInput } from "../utils/workspace"
const directory = "C:/Projects/settings-demo"
const projectID = "proj_settings_demo"
@@ -65,20 +65,6 @@ test("settings has its own route and returns through app history", async ({ page
await expect(settings).toBeHidden()
})
test("Ctrl+, opens settings as soon as the app loads", async ({ page }) => {
await mockWorkspace(page, {
name: "Settings demo",
directory,
provider: NO_PROVIDER,
sessions: [],
seed: { tabs: [] },
})
await page.goto("/")
await page.keyboard.press("Control+,")
await expect(page.getByTestId("settings-screen")).toBeVisible()
await expect(page).toHaveURL("/settings")
})
test("a settings page survives refresh", async ({ page }) => {
const { settings } = await open(page)
const appearance = settings.getByRole("tab", { name: "Appearance", exact: true })
@@ -10,10 +10,9 @@ import { PlatformProvider, type Platform } from "../../src/runtime/platform/plat
import { ServerConnection } from "../../src/runtime/server/registry"
import { useExtensionServers } from "../../src/runtime/extension/servers"
// A desktop window. `wsl` is the Ubuntu server's endpoint (default `server`); updating OpenCode restarts it on `restart`
// (default `wsl`). `ssh` adds a saved SSH server `box`, ready on that endpoint with password `ssh-1`; each connect
// brings up a new remote server with the next password (`ssh-2`, ...). `storage=async` stores in localStorage behind
// asynchronous reads like the desktop app's; reads of keys containing `hold` wait for "Load held storage".
// `wsl` is the Ubuntu server's endpoint (default `server`); updating OpenCode restarts it on `restart` (default `wsl`).
// `ssh` adds a saved SSH server `box`, ready on that endpoint with password `ssh-1`; each connect brings up a new
// remote server with the next password (`ssh-2`, ...).
export function mount(input: {
server: string
mode: "failed" | "stopped" | "ready"
@@ -21,8 +20,6 @@ export function mount(input: {
restart?: string | null
path?: string | null
ssh?: string | null
storage?: string | null
hold?: string | null
}) {
const root = document.getElementById("root")
if (!root) throw new Error("Missing fixture root")
@@ -30,28 +27,14 @@ export function mount(input: {
history.set({ value: input.path ?? "/settings", replace: true, scroll: false })
const endpoint = { url: input.wsl ?? input.server }
const ready = () => ({ kind: "ready" as const, url: endpoint.url, password: null })
const held = Promise.withResolvers<void>()
const storage = (name?: string) => {
const item = (key: string) => (name ? `${name}:${key}` : key)
return {
getItem: async (key: string) => {
if (input.hold && key.includes(input.hold)) await held.promise
return localStorage.getItem(item(key))
},
setItem: async (key: string, value: string) => localStorage.setItem(item(key), value),
removeItem: async (key: string) => localStorage.removeItem(item(key)),
}
}
render(() => {
const [store, setStore] = createStore<{
calls: string[]
connects: number
available: boolean
state: WslServersState
ssh: SshState
}>({
calls: [],
connects: 0,
available: true,
ssh: {
revision: 0,
@@ -112,6 +95,7 @@ export function mount(input: {
revision: state.revision + 1,
servers: state.servers.map((server) => ({ ...server, ...item })),
}))
const connects = { count: 1 }
const methods: Record<string, Record<string, (input: { id?: string; name?: string }) => unknown>> = {
wsl: {
// Like main: stops the distro's server, updates OpenCode, then starts the server again on a new endpoint.
@@ -142,8 +126,8 @@ export function mount(input: {
ssh: {
// Like main: a connect opens a new tunnel to a remote server with a new password.
start() {
setStore("connects", (count) => count + 1)
setSsh({ stage: "ready", http: { url: input.ssh ?? "", password: `ssh-${store.connects + 1}` } })
connects.count += 1
setSsh({ stage: "ready", http: { url: input.ssh ?? "", password: `ssh-${connects.count}` } })
return store.ssh.revision
},
},
@@ -191,7 +175,6 @@ export function mount(input: {
notify: async () => undefined,
restart: unused,
extensions: bridge,
...(input.storage === "async" ? { storage } : {}),
}
function Interface() {
const extensions = useExtensionServers()
@@ -238,22 +221,6 @@ export function mount(input: {
>
Drop SSH tunnel
</button>
{/* A background reconnect that needs a password, as main reports it to every window. */}
<button
type="button"
onClick={() => {
setSsh({ stage: "authentication" })
publish("ssh")
}}
>
Require SSH sign-in
</button>
<output aria-label="SSH connects">{store.connects}</output>
</Show>
<Show when={input.hold}>
<button type="button" onClick={() => held.resolve()}>
Load held storage
</button>
</Show>
<Interface />
</AppBaseProviders>
-2
View File
@@ -15,8 +15,6 @@
restart: query.get("restart"),
path: query.get("path"),
ssh: query.get("ssh"),
storage: query.get("storage"),
hold: query.get("hold"),
})
</script>
</body>
+2 -2
View File
@@ -448,8 +448,8 @@ function openComment(
})
})
}
// The extension that owns the comment's origin reveals it (the review diff or a file tab), keeping the conversation.
links.open({ href: item.path, origin: item.commentOrigin, exact: true, background: true, session })
// The extension that owns the comment's origin reveals it (the review diff or a file tab).
links.open({ href: item.path, origin: item.commentOrigin, exact: true, session })
if (item.commentOrigin === "review") return queueFocus()
void Promise.resolve(files.load(item.path)).finally(() => queueFocus())
}
+20 -28
View File
@@ -1,4 +1,3 @@
import { createMemo } from "solid-js"
import { Command } from "@opencode/gui-extensions/sdk"
import { useCommand } from "@/shell/commands/command"
import { useSettings } from "@/settings/model"
@@ -10,34 +9,27 @@ export function ExtensionCommands() {
const host = useExtensionHost()
const command = useCommand()
migrateKeybinds(useSettings())
// Extensions activate in whatever order their entries load; list them in definition order, then registration order.
const rank = createMemo(() => new Map(host.definitions().map((definition, index) => [definition.id, index])))
const order = (extension: string) => rank().get(extension) ?? rank().size
command.register("extensions", () =>
host
.items(Command)
.toSorted((a, b) => order(a.extension) - order(b.extension))
.map((item) => ({
id: `${item.extension}.${item.value.id}`,
title: item.value.title,
description: item.value.description,
category: item.value.group,
section: item.value.section,
keybind: item.value.bind,
slash: item.value.slash?.name,
slashArguments: item.value.slash?.arguments,
slashAfter: item.value.slash?.after,
suggested: item.value.suggested,
featured: item.value.featured,
disabled: item.value.enabled === false,
hidden: item.value.hidden,
editable: item.value.editable,
when: item.value.scope
? (event: KeyboardEvent) =>
event.target instanceof Element && !!event.target.closest(item.value.scope as string)
: undefined,
onSelect: (_source: unknown, input?: string) => item.value.run(input),
})),
host.items(Command).map((item) => ({
id: `${item.extension}.${item.value.id}`,
title: item.value.title,
description: item.value.description,
category: item.value.group,
section: item.value.section,
keybind: item.value.bind,
slash: item.value.slash?.name,
slashArguments: item.value.slash?.arguments,
suggested: item.value.suggested,
featured: item.value.featured,
disabled: item.value.enabled === false,
hidden: item.value.hidden,
editable: item.value.editable,
when: item.value.scope
? (event: KeyboardEvent) =>
event.target instanceof Element && !!event.target.closest(item.value.scope as string)
: undefined,
onSelect: (_source: unknown, input?: string) => item.value.run(input),
})),
)
return null
}
+6 -12
View File
@@ -137,12 +137,9 @@ export function createRegion(input: { region: Panel["region"]; view: SessionView
]
})
// Narrow screens never select a launcher: a stored one falls back like a missing tab.
const desktop = createMediaQuery("(min-width: 768px)")
const active = createMemo(() => {
const value = input.tabs().active()
if (value && strip().some((entry) => entry.key === value && (desktop() || entry.tab.kind !== "launcher")))
return value
if (value && strip().some((entry) => entry.key === value)) return value
return strip()
.filter((entry) => entry.tab.fallback !== undefined)
.reduce<RegionEntry | undefined>(
@@ -160,13 +157,10 @@ export function createRegion(input: { region: Panel["region"]; view: SessionView
}),
)
// Hidden tabs keep their place in the strip for selection, focus, and close, but draw no trigger.
const drawn = createMemo(() => strip().filter((entry) => !entry.tab.hidden))
return {
entries,
/** Drawn strip order by key. Renders iterate keys so a provider's fresh tab objects never remount a trigger. */
keys: createMemo(() => drawn().map((entry) => entry.key), [], { equals: same }),
/** Strip order by key. Renders iterate keys so a provider's fresh tab objects never remount a trigger. */
keys: createMemo(() => strip().map((entry) => entry.key), [], { equals: same }),
entry: (key: string) => byKey().get(key),
active,
selected: createMemo(() => {
@@ -177,7 +171,7 @@ export function createRegion(input: { region: Panel["region"]; view: SessionView
/** An extension's tab ids in the stored strip. */
openFor: (extension: string) =>
stored().flatMap((key) => (key.startsWith(`${extension}:`) ? [key.slice(extension.length + 1)] : [])),
lead: () => !!drawn().find((entry) => entry.tab.kind !== "pinned")?.tab.first,
lead: () => !!strip().find((entry) => entry.tab.kind !== "pinned")?.tab.first,
select(key: string) {
input.tabs().setActive(key)
},
@@ -225,7 +219,7 @@ export function RegionContent(props: {
<div
id={member()?.tab.dom?.panel}
role="tabpanel"
aria-labelledby={active() && !member()?.tab.hidden ? member()?.tab.dom?.tab : undefined}
aria-labelledby={active() ? member()?.tab.dom?.tab : undefined}
data-slot="tabs-content"
class="h-full min-h-0 overflow-hidden"
classList={{ hidden: !active() }}
@@ -257,7 +251,7 @@ export function RegionContent(props: {
<div
id={entry()?.tab.dom?.panel}
role="tabpanel"
aria-labelledby={entry()?.tab.hidden ? undefined : entry()?.tab.dom?.tab}
aria-labelledby={entry()?.tab.dom?.tab}
tabIndex={entry()?.tab.tabbable ? 0 : undefined}
data-slot="tabs-content"
class="flex flex-col h-full overflow-hidden contain-strict"
+40 -50
View File
@@ -81,9 +81,7 @@ export function createExtensionServices() {
const platform = usePlatform()
const dialog = useDialog()
const language = useLanguage()
// The session screen's breakpoint, negated, so no fractional width is both narrow and desktop.
const desktop = createMediaQuery("(min-width: 768px)")
const narrow = () => !desktop()
const narrow = createMediaQuery("(max-width: 767px)")
const [attached, setAttached] = createSignal<Attached>()
const removed = new Set<(value: { server: string; directory: string }) => void>()
const memory = new Map<string, readonly [Store<object>, (mutation: (draft: object) => void) => void]>()
@@ -288,11 +286,12 @@ export function createExtensionAttachment(services: ExtensionServices) {
const host = useExtensionHost()
const command = useCommand()
const location = useLocation()
const desktop = createMediaQuery("(min-width: 768px)")
const narrow = () => !desktop()
const narrow = createMediaQuery("(max-width: 767px)")
const views = new Map<string, SessionView>()
const [mounted, setMounted] = createStore({ revision: 0 })
const refs = new Map<string, SessionRef>()
// The panel whose toggle opened a session's side region, by session key.
const openedFor = new Map<string, string>()
const connection = (id: string) => global.servers.list().find((item) => ServerConnection.key(item) === id)
@@ -372,15 +371,11 @@ export function createExtensionAttachment(services: ExtensionServices) {
return Array.from(refs.values())
})
const routed = createMemo(() => {
const value = route()
return value.type === "session" ? `${value.server}\n${value.sessionId}` : undefined
})
const current = createMemo(() => {
const key = routed()
if (!key) return
const value = route()
if (value.type !== "session") return
void mounted.revision
return views.get(key)
return views.get(`${value.server}\n${value.sessionId}`)
})
const scope = (id: string) => {
@@ -403,19 +398,13 @@ export function createExtensionAttachment(services: ExtensionServices) {
const sideOpened = (session: SessionRef) => !!tabs.pane(shellTab(session), "side")
const dockOpened = (session: SessionRef) => !!tabs.pane(shellTab(session), "dock")
const setDock = (session: SessionRef, opened: boolean) => tabs.setPane(shellTab(session), "dock", opened)
// A token per session whose side region is open, new each time the region opens.
const sideVisits = createMemo<ReadonlyMap<string, object>>(
(previous) =>
new Map(
sessions().flatMap((session) =>
sideOpened(session) ? [[session.key, previous.get(session.key) ?? {}] as const] : [],
),
),
new Map(),
)
// The panel whose toggle opened a side region, by the region's token. However the region closes, it reopens with
// a new token, so a region reopened any other way belongs to the user.
const openedFor = new WeakMap<object, string>()
// However the side region closes, it forgets its opener, so a region reopened any other way belongs to the user.
createEffect(() => {
const open = new Set(sessions().flatMap((session) => (sideOpened(session) ? [session.key] : [])))
Array.from(openedFor.keys()).forEach((key) => {
if (!open.has(key)) openedFor.delete(key)
})
})
// Keys are `${extension}:${tab id}`; the extension's panel decides the region.
const provider = (key: string) => {
@@ -428,18 +417,19 @@ export function createExtensionAttachment(services: ExtensionServices) {
return view?.key === session.key ? view : undefined
}
// Counts routing visits: each change of the routed session, including to none (e.g. Home), starts the next one.
const visit = createMemo(on(routed, (_key, _previous, count: number = 0) => count + 1))
// The narrow-screen view belongs to the routed, mounted session for one visit, and reads as the conversation once
// another visit starts. A view selected for a session that is not routed (e.g. a file link that opens Files on
// another session) belongs to the next visit, which the navigation that follows starts. The dock's view follows
// the dock's own per-session state instead.
const [mobile, setMobile] = createStore({ session: undefined as string | undefined, view: "session", visit: 0 })
const mobileView = createMemo(() =>
mobile.session === current()?.key && mobile.visit === visit() ? mobile.view : "session",
// The narrow-screen view belongs to the routed, mounted session: it resets to the conversation when that session
// changes or unmounts (e.g. on Home). The dock's view follows the dock's own per-session state instead.
const [mobile, setMobile] = createStore({ session: undefined as string | undefined, view: "session" })
createEffect(
on(
() => current()?.key,
(key) => {
if (key !== mobile.session) setMobile({ session: undefined, view: "session" })
},
),
)
const selectMobile = (session: SessionRef, view: string) =>
setMobile({ session: session.key, view, visit: session.key === routed() ? visit() : visit() + 1 })
const mobileView = createMemo(() => (mobile.session === current()?.key ? mobile.view : "session"))
const selectMobile = (session: SessionRef, view: string) => setMobile({ session: session.key, view })
// The side tabs a mounted session lists right now, plus `adding` as if it were stored; unmounted sessions have none.
const listed = (session: SessionRef, value: string, adding?: string) => {
@@ -459,17 +449,20 @@ export function createExtensionAttachment(services: ExtensionServices) {
)
}
const open = (key: string, session: SessionRef, options?: Parameters<Layout["open"]>[2]) => {
const open = (
key: string,
session: SessionRef,
options?: { readonly preview?: boolean; readonly focus?: boolean; readonly select?: boolean },
) => {
const item = provider(key)
if (item?.value.region === "dock") return setDock(session, true)
const value = stateKey(session)
if (!value) return
// focus: false adds the tab quietly: no selection, no region change, no preview replacement.
if (options?.focus === false && !options.preview) return layout.panel.append(value, key)
// A select keeps the narrow-screen view and dock, as a background open does, and opens the side region too.
if (options?.select)
return batch(() => {
tabs.setPane(shellTab(session), "side", true)
if (!narrow()) tabs.setPane(shellTab(session), "side", true)
layout.panel.append(value, key)
layout.panel.focus(value, key)
})
@@ -478,14 +471,13 @@ export function createExtensionAttachment(services: ExtensionServices) {
const launchers = new Set(known.flatMap((entry) => (entry.tab.kind === "launcher" ? [entry.key] : [])))
const first = known.some((entry) => entry.key === key && entry.tab.first)
batch(() => {
if (narrow() && !options?.background) {
if (narrow()) {
setDock(session, false)
if (item?.value.mobile) selectMobile(session, `${item.extension}:${item.value.id}`)
// A tab its panel does not list, or a launcher, stays unstored: the open only selects the panel's view.
if (mountedView(session) && !known.some((entry) => entry.key === key && entry.tab.kind !== "launcher")) return
// A tab its panel does not list on narrow screens stays unstored: the open only selects the panel's view.
if (mountedView(session) && !known.some((entry) => entry.key === key)) return
}
// A background open keeps the narrow-screen view, but its tab still shows once the window is wide.
if (!narrow() || options?.background) tabs.setPane(shellTab(session), "side", true)
if (!narrow()) tabs.setPane(shellTab(session), "side", true)
// Pinned tabs are listed without being stored; opening one only selects it.
if (known.some((entry) => entry.key === key && entry.tab.kind === "pinned")) return layout.panel.focus(value, key)
if (options?.preview) return layout.panel.preview(value, key, launchers)
@@ -577,21 +569,19 @@ export function createExtensionAttachment(services: ExtensionServices) {
if (provider(key)?.value.region === "dock") return setDock(session, !dockOpened(session))
const value = stateKey(session)
if (!value) return
const region = sideVisits().get(session.key)
if (state(key, session) === "visible") {
batch(() => {
close(key, session)
// Closing the last panel the region was opened for also closes the region.
if (region && openedFor.get(region) === key && layout.panel.state(value).all.length === 0)
if (openedFor.get(session.key) === key && layout.panel.state(value).all.length === 0)
tabs.setPane(shellTab(session), "side", false)
})
return
}
// A panel opened into an open region makes the region the user's.
if (region) openedFor.delete(region)
const opening = !sideOpened(session)
if (!opening) openedFor.delete(session.key)
open(key, session)
const opened = sideVisits().get(session.key)
if (!region && opened) openedFor.set(opened, key)
if (opening && sideOpened(session)) openedFor.set(session.key, key)
},
state,
stored(extension, session) {
@@ -5,9 +5,6 @@ import { createResizeObserver } from "@solid-primitives/resize-observer"
import type { SurfaceProps, Surfaces } from "@opencode/gui-extensions/sdk"
import type { Bridge } from "@opencode/gui-extensions/sdk/bridge"
const geometry =
/^(inset|left|right|top|bottom|translate|transform|scale|rotate|margin|padding|flex|grid|gap|row-gap|column-gap)|(^|-)(width|height)$/
type Input = {
readonly bridge: Bridge | undefined
readonly zoom: () => number
@@ -167,14 +164,6 @@ function SurfaceView(props: SurfaceProps & { input: Input; bridge: Bridge }) {
// animation frame keeps the native view in step with a pane drag.
createResizeObserver(() => element, measure)
createEventListener(window, "resize", () => schedule(300))
// A layout transition elsewhere, such as the chat column's width while the side pane opens, can
// move the box without resizing it. Track it from when it actually runs, and settle on its end.
createEventListener(document, "transitionrun", (event) => {
if (geometry.test(event.propertyName)) schedule(300)
})
createEventListener(document, ["transitionend", "transitioncancel"], (event) => {
if (geometry.test(event.propertyName)) schedule()
})
// Floating content portals directly into <body>; keep measuring briefly so
// the positioner has settled before the overlap check runs.
const portals = new MutationObserver(() => schedule(300))
@@ -30,17 +30,12 @@ import "./session-identity-header.css"
export function SessionTitleHeader(props: ParentProps) {
return (
<>
{/* The bottom inset reserves room for the fade without covering rows stuck below the bar. */}
<div data-session-title class="pointer-events-none sticky top-0 z-30 w-full pb-4">
<div class="pointer-events-auto bg-v2-background-bg-base pe-3 ps-2.5">{props.children}</div>
</div>
{/* Stuck timeline headers stack above this fade, so it only fades content scrolling under the bar. */}
<div
aria-hidden="true"
class="pointer-events-none sticky top-12 z-[5] -mt-4 h-4 w-full bg-[linear-gradient(to_bottom,var(--v2-background-bg-base),transparent)]"
/>
</>
<div
data-session-title
class="sticky top-0 z-30 w-full bg-[linear-gradient(to_bottom,var(--v2-background-bg-base)_48px,transparent)] pb-4 pe-3 ps-2.5"
>
{props.children}
</div>
)
}
+1 -19
View File
@@ -89,8 +89,6 @@ export interface CommandOption {
keybind?: KeybindConfig
slash?: string
slashArguments?: boolean
/** Listed right after the option whose slash name this is, when one is registered. */
slashAfter?: string
suggested?: boolean
/** Listed when the command palette opens without a query. Host commands are listed by id instead. */
featured?: boolean
@@ -148,22 +146,6 @@ export function activeCommandRegistrations(registrations: CommandRegistration[])
})
}
// Each option with `slashAfter` follows the first option with that slash name, so a command from another
// registration can sit inside that registration's slash list.
function placeAfterSlash(options: CommandOption[]) {
const anchors = new Map<string, CommandOption>()
options.forEach((option) => {
if (option.slash && !option.slashAfter && !anchors.has(option.slash)) anchors.set(option.slash, option)
})
const moved = options.filter((option) => option.slashAfter && anchors.has(option.slashAfter))
if (moved.length === 0) return options
return options.flatMap((option) => {
if (moved.includes(option)) return []
if (!option.slash || anchors.get(option.slash) !== option) return [option]
return [option, ...moved.filter((item) => item.slashAfter === option.slash)]
})
}
export function parseKeybind(config: string): Keybind[] {
if (!config || config === "none") return []
@@ -331,7 +313,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
}
}
return placeAfterSlash(all)
return all
})
createEffect(() => {
+1 -1
View File
@@ -64,7 +64,7 @@ export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => vo
const extensions = useExtensionAttachment()
return (path: string) => {
links.open({ href: path, exact: true, background: true, session: extensions.current() })
links.open({ href: path, exact: true, session: extensions.current() })
onOpenFile?.(path)
}
}
+93 -105
View File
@@ -447,7 +447,7 @@ export function make(options: ClientOptions) {
path: `/api/location`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -472,7 +472,7 @@ export function make(options: ClientOptions) {
path: `/api/agent`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -484,7 +484,7 @@ export function make(options: ClientOptions) {
path: `/api/agent/${encodeURIComponent(input.agentID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -498,7 +498,7 @@ export function make(options: ClientOptions) {
path: `/api/plugin`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -511,7 +511,7 @@ export function make(options: ClientOptions) {
query: { location: input?.["location"] },
body: { target: input?.["target"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -524,7 +524,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { targets: input["targets"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: true,
},
requestOptions,
@@ -655,7 +655,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/fork`,
body: { before: input["before"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -667,7 +667,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/agent`,
body: { agent: input["agent"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -679,7 +679,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/model`,
body: { model: input["model"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -691,7 +691,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}`,
body: { title: input["title"], metadata: input["metadata"], permissions: input["permissions"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -703,7 +703,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/move`,
body: { directory: input["directory"], delivery: input["delivery"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -724,7 +724,7 @@ export function make(options: ClientOptions) {
resume: input["resume"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -743,7 +743,7 @@ export function make(options: ClientOptions) {
delivery: input["delivery"],
},
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 500],
declaredStatuses: [400, 401, 404, 500],
empty: true,
},
requestOptions,
@@ -755,7 +755,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/skill`,
body: { id: input["id"], resume: input["resume"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -774,7 +774,7 @@ export function make(options: ClientOptions) {
resume: input["resume"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -786,7 +786,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/shell`,
body: { id: input["id"], command: input["command"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -798,7 +798,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`,
body: { id: input["id"], delivery: input["delivery"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -809,7 +809,7 @@ export function make(options: ClientOptions) {
method: "POST",
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/wait`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 404, 503],
empty: true,
},
requestOptions,
@@ -822,7 +822,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`,
body: { messageID: input["messageID"], files: input["files"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409, 500],
declaredStatuses: [400, 401, 404, 409, 500],
empty: false,
},
requestOptions,
@@ -833,7 +833,7 @@ export function make(options: ClientOptions) {
method: "DELETE",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409, 500],
declaredStatuses: [400, 401, 404, 409, 500],
empty: true,
},
requestOptions,
@@ -844,7 +844,7 @@ export function make(options: ClientOptions) {
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: true,
},
requestOptions,
@@ -868,7 +868,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/diff`,
query: { from: input["from"], to: input["to"], context: input["context"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 500],
declaredStatuses: [400, 401, 404, 500],
empty: false,
},
requestOptions,
@@ -917,7 +917,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/instructions/entries`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -929,7 +929,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/instructions/entries/${encodeURIComponent(input.key)}`,
body: { value: input["value"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 413],
declaredStatuses: [400, 401, 404, 413],
empty: true,
},
requestOptions,
@@ -940,7 +940,7 @@ export function make(options: ClientOptions) {
method: "DELETE",
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/instructions/entries/${encodeURIComponent(input.key)}`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -954,7 +954,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/generate`,
body: { prompt: input["prompt"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 404, 503],
empty: false,
},
requestOptions,
@@ -978,7 +978,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/interrupt`,
query: { resume: input["resume"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -989,7 +989,7 @@ export function make(options: ClientOptions) {
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/background`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1014,7 +1014,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1026,7 +1026,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/form`,
body: { id: input["id"], title: input["title"], metadata: input["metadata"], fields: input["fields"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -1037,7 +1037,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1049,7 +1049,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/reply`,
body: { answer: input["answer"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: true,
},
requestOptions,
@@ -1061,7 +1061,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}`,
query: { message: input["message"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: true,
},
requestOptions,
@@ -1114,7 +1114,7 @@ export function make(options: ClientOptions) {
path: `/api/model`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1126,7 +1126,7 @@ export function make(options: ClientOptions) {
path: `/api/model/default`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1140,7 +1140,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/generate`,
body: { prompt: input["prompt"], model: input["model"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1154,7 +1154,7 @@ export function make(options: ClientOptions) {
path: `/api/provider`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1166,7 +1166,7 @@ export function make(options: ClientOptions) {
path: `/api/provider/${encodeURIComponent(input.providerID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 404, 503],
empty: false,
},
requestOptions,
@@ -1180,7 +1180,7 @@ export function make(options: ClientOptions) {
path: `/api/integration`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1192,7 +1192,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1206,7 +1206,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { url: input["url"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1221,7 +1221,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { key: input["key"], answer: input["answer"], label: input["label"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1236,7 +1236,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { methodID: input["methodID"], answer: input["answer"], label: input["label"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1248,7 +1248,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1261,7 +1261,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { code: input["code"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1273,7 +1273,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1288,7 +1288,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { methodID: input["methodID"], label: input["label"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1300,7 +1300,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/command/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1312,7 +1312,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/command/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1327,7 +1327,7 @@ export function make(options: ClientOptions) {
path: `/api/mcp`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1340,7 +1340,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { config: input["config"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1352,7 +1352,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1364,7 +1364,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}/connect`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1376,7 +1376,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}/disconnect`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1389,7 +1389,7 @@ export function make(options: ClientOptions) {
path: `/api/mcp/resource`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1458,13 +1458,7 @@ export function make(options: ClientOptions) {
project: {
list: (requestOptions?: RequestOptions) =>
request<ProjectListOutput>(
{
method: "GET",
path: `/api/project`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
empty: false,
},
{ method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
requestOptions,
),
update: (input: ProjectUpdateInput, requestOptions?: RequestOptions) =>
@@ -1479,7 +1473,7 @@ export function make(options: ClientOptions) {
commands: input["commands"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1493,7 +1487,7 @@ export function make(options: ClientOptions) {
path: `/api/form`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1508,7 +1502,7 @@ export function make(options: ClientOptions) {
path: `/api/permission/request`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1522,7 +1516,7 @@ export function make(options: ClientOptions) {
path: `/api/permission/saved`,
query: { projectID: input?.["projectID"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1533,7 +1527,7 @@ export function make(options: ClientOptions) {
method: "DELETE",
path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1554,7 +1548,7 @@ export function make(options: ClientOptions) {
agent: input["agent"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1565,7 +1559,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1576,7 +1570,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1588,7 +1582,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}/reply`,
body: { decision: input["decision"], message: input["message"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1602,7 +1596,7 @@ export function make(options: ClientOptions) {
path: `/api/fs/read/${encodePath(input.path)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
binary: true,
},
@@ -1615,7 +1609,7 @@ export function make(options: ClientOptions) {
path: `/api/fs/list`,
query: { location: input?.["location"], path: input?.["path"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1627,7 +1621,7 @@ export function make(options: ClientOptions) {
path: `/api/fs/find`,
query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1640,7 +1634,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"], path: input["path"] },
body: input["payload"],
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
binaryBody: true,
},
@@ -1655,7 +1649,7 @@ export function make(options: ClientOptions) {
path: `/api/command`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1669,7 +1663,7 @@ export function make(options: ClientOptions) {
path: `/api/skill`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1684,7 +1678,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { input: input["input"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 500],
declaredStatuses: [400, 401, 500],
empty: false,
},
requestOptions,
@@ -1705,7 +1699,7 @@ export function make(options: ClientOptions) {
path: `/api/pty`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1724,7 +1718,7 @@ export function make(options: ClientOptions) {
env: input?.["env"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1736,7 +1730,7 @@ export function make(options: ClientOptions) {
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1749,7 +1743,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { title: input["title"], size: input["size"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1761,7 +1755,7 @@ export function make(options: ClientOptions) {
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1915,7 +1909,7 @@ export function make(options: ClientOptions) {
path: `/api/shell`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1933,7 +1927,7 @@ export function make(options: ClientOptions) {
metadata: input["metadata"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1945,7 +1939,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1957,7 +1951,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}/output`,
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1969,7 +1963,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1983,7 +1977,7 @@ export function make(options: ClientOptions) {
path: `/api/reference`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2053,7 +2047,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2065,7 +2059,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/base`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2077,7 +2071,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/status`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2090,7 +2084,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/branch`,
query: { location: input?.["location"], search: input?.["search"], limit: input?.["limit"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2103,7 +2097,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/diff`,
query: { location: input["location"], mode: input["mode"], base: input["base"], context: input["context"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2159,7 +2153,7 @@ export function make(options: ClientOptions) {
path: `/api/websearch/provider`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2172,7 +2166,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { query: input["query"], providerID: input["providerID"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2186,20 +2180,14 @@ export function make(options: ClientOptions) {
path: `/api/config`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
),
shells: (requestOptions?: RequestOptions) =>
request<ConfigShellsOutput>(
{
method: "GET",
path: `/api/config/shell`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
empty: false,
},
{ method: "GET", path: `/api/config/shell`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
requestOptions,
),
update: (input: ConfigUpdateInput, requestOptions?: RequestOptions) =>
@@ -2209,7 +2197,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/config`,
body: { shell: input["shell"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
+3 -23
View File
@@ -437,8 +437,6 @@ export type McpProtocol = "legacy" | "auto" | "2026-07-28"
export type ConfigWorktree = { directory: string }
export type ConfigModelCapabilities = { tools?: boolean; input?: Array<string>; output?: Array<string> }
export type ConfigShellOption = { path: string; name: string; acceptable: boolean }
export type SessionMessageLocationSwitched = {
@@ -477,16 +475,14 @@ export type ConfigModelSettings = { compaction?: ProviderCompaction } & { [x: st
export type ProviderSettings = {
timeout?: number | false
headerTimeout?: number | false
chunkTimeout?: number | false
chunkTimeout?: number
compaction?: ProviderCompaction
transport?: ProviderTransport
} & { [x: string]: any }
export type ConfigProviderSettings = {
timeout?: number | false
headerTimeout?: number | false
chunkTimeout?: number | false
chunkTimeout?: number
compaction?: ProviderCompaction
transport?: ProviderTransport
} & { [x: string]: JsonValue | null }
@@ -2161,7 +2157,7 @@ export type ConfigEntry =
settings?: ConfigModelSettings
headers?: { [x: string]: string }
body?: { [x: string]: JsonValue }
capabilities?: ConfigModelCapabilities
capabilities?: ModelCapabilities
variants?: Array<{
id: string
settings?: ConfigModelSettings
@@ -2525,22 +2521,6 @@ export type UnauthorizedError = { readonly _tag: "UnauthorizedError"; readonly m
export const isUnauthorizedError = (value: unknown): value is UnauthorizedError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnauthorizedError"
export type DirectoryAccessDeniedError = {
readonly _tag: "DirectoryAccessDeniedError"
readonly directory: string
readonly message: string
}
export const isDirectoryAccessDeniedError = (value: unknown): value is DirectoryAccessDeniedError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "DirectoryAccessDeniedError"
export type DirectoryNotFoundError = {
readonly _tag: "DirectoryNotFoundError"
readonly directory: string
readonly message: string
}
export const isDirectoryNotFoundError = (value: unknown): value is DirectoryNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "DirectoryNotFoundError"
export type ServiceUnavailableError = {
readonly _tag: "ServiceUnavailableError"
readonly message: string
+21 -40
View File
@@ -128,29 +128,23 @@ function prepareOptions(model: RuntimeInfo, pkg: string) {
}
const customFetch = options.fetch
const timeouts = Provider.timeouts(options)
delete options.headerTimeout
const chunkTimeout = options.chunkTimeout
delete options.chunkTimeout
delete options.compaction
delete options.transport
options.fetch = async (input: Parameters<typeof fetch>[0], init?: RequestInit) => {
const opts = { ...(init ?? {}) }
// A plain AbortSignal.timeout would keep running into the body stream, so the header limit uses
// a timer that is cleared once headers arrive. The whole-request limit is meant to span the body.
const headers = new AbortController()
const headerTimer =
timeouts.headerTimeout === false
? undefined
: setTimeout(() => headers.abort(new Error(HEADER_TIMEOUT_MESSAGE)), timeouts.headerTimeout)
const chunks = new AbortController()
opts.signal = AbortSignal.any(
[
opts.signal,
headers.signal,
chunks.signal,
timeouts.timeout ? AbortSignal.timeout(timeouts.timeout) : undefined,
].filter((item): item is AbortSignal => item !== undefined && item !== null),
)
const signals = [
opts.signal,
typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined,
options.timeout !== undefined && options.timeout !== null && options.timeout !== false
? AbortSignal.timeout(options.timeout)
: undefined,
].filter((item): item is AbortSignal | AbortController => item !== undefined && item !== null)
const chunkAbortCtl = signals.find((item): item is AbortController => item instanceof AbortController)
const abortSignals = signals.map((item) => (item instanceof AbortController ? item.signal : item))
if (abortSignals.length === 1) opts.signal = abortSignals[0]
if (abortSignals.length > 1) opts.signal = AbortSignal.any(abortSignals)
if (typeof opts.body === "string" && model.body !== undefined) {
const decoded = Option.getOrUndefined(decodeJson(opts.body))
@@ -161,13 +155,11 @@ function prepareOptions(model: RuntimeInfo, pkg: string) {
const send: Fetch = typeof customFetch === "function" ? customFetch : fetch
const middleware = httpMiddleware.getStore()
const res = await (
middleware
? throughMiddleware(middleware, send, input, { ...opts, timeout: false })
: send(input, { ...opts, timeout: false })
).finally(() => clearTimeout(headerTimer))
if (timeouts.chunkTimeout === false) return res
return wrapSSE(res, timeouts.chunkTimeout, chunks)
const res = middleware
? await throughMiddleware(middleware, send, input, { ...opts, timeout: false })
: await send(input, { ...opts, timeout: false })
if (!chunkAbortCtl || typeof chunkTimeout !== "number") return res
return wrapSSE(res, chunkTimeout, chunkAbortCtl)
}
return options
@@ -223,8 +215,6 @@ function throughMiddleware(
)
}
const HEADER_TIMEOUT_MESSAGE = "Response headers timed out"
export class InitError extends Schema.TaggedError<InitError>()("AISDK.InitError", {
providerID: Provider.ID,
cause: Schema.Defect(),
@@ -457,17 +447,9 @@ function requestSettings(settings: Readonly<Record<string, unknown>> | undefined
const result = Object.fromEntries(
Object.entries(settings).filter(
([key]) =>
![
"apiKey",
"authToken",
"baseURL",
"chunkTimeout",
"compaction",
"fetch",
"headerTimeout",
"timeout",
"transport",
].includes(key),
!["apiKey", "authToken", "baseURL", "chunkTimeout", "compaction", "fetch", "timeout", "transport"].includes(
key,
),
),
)
return Object.keys(result).length === 0 ? undefined : result
@@ -969,7 +951,7 @@ const errorValue = (error: unknown) => (ProviderShared.isRecord(error) ? error.v
// Runtime-generated network failure shapes. The codes mirror the AI SDK's own
// Bun network error list in handleFetchError; the messages are undici's fetch
// TypeError and stream termination strings plus our header and chunk timeout errors.
// TypeError and stream termination strings plus our SSE chunk timeout error.
// Unrecognized shapes still retry via the UnknownProvider default; this match
// only adds transport semantics (continuation eligibility, display).
const NETWORK_ERROR_CODES = new Set([
@@ -987,7 +969,6 @@ const NETWORK_ERROR_MESSAGES = new Set([
"terminated",
"other side closed",
"sse read timed out",
HEADER_TIMEOUT_MESSAGE.toLowerCase(),
])
const NativeErrorShape = Schema.Struct({
+7 -2
View File
@@ -117,8 +117,13 @@ export const Plugin = define({
if (config.settings !== undefined) model.settings = Provider.mergeOverlay(model.settings, config.settings)
if (config.headers !== undefined) model.headers = Provider.mergeHeaders(model.headers, config.headers)
if (config.body !== undefined) model.body = Provider.mergeOverlay(model.body, config.body)
if (config.capabilities !== undefined)
model.capabilities = Model.mergeCapabilities(config.capabilities, model.capabilities)
if (config.capabilities !== undefined) {
model.capabilities = {
tools: config.capabilities.tools,
input: [...config.capabilities.input],
output: [...config.capabilities.output],
}
}
if (config.variants !== undefined) {
model.variants ??= []
for (const variant of config.variants) {
+1 -3
View File
@@ -7,8 +7,6 @@ import { FSUtil } from "@opencode/util/fs-util"
import { Location } from "./location.js"
import { AbsolutePath, PositiveInt, RelativePath } from "./schema.js"
import { FileSystemSearch } from "./filesystem/search.js"
import { FileSystemDirectory } from "./filesystem/directory.js"
export { DirectoryNotFoundError, DirectoryAccessDeniedError } from "./filesystem/directory.js"
import { Entry, FileSystem, FindInput, Write } from "@opencode/schema/filesystem"
export { Entry, Match, Submatch } from "@opencode/schema/filesystem"
@@ -89,7 +87,7 @@ const baseLayer = Layer.effect(
// configured directory as canonical; local placements keep symlink
// canonicalization. This skip is boot-only: resolve/read/list below still
// access the host filesystem per operation (tracked in #44568).
const root = location.workspaceID ? location.directory : yield* FileSystemDirectory.resolve(location.directory)
const root = location.workspaceID ? location.directory : yield* fs.realPath(location.directory).pipe(Effect.orDie)
const resolve = Effect.fnUntraced(function* (input?: RelativePath) {
const absolute = path.resolve(location.directory, input ?? ".")
if (!FSUtil.contains(location.directory, absolute))
-44
View File
@@ -1,44 +0,0 @@
export * as FileSystemDirectory from "./directory.js"
import { Effect, Schema } from "effect"
import { FSUtil } from "@opencode/util/fs-util"
import { AbsolutePath } from "../schema.js"
export class DirectoryNotFoundError extends Schema.TaggedError<DirectoryNotFoundError>()(
"FileSystem.DirectoryNotFoundError",
{ directory: AbsolutePath, cause: Schema.Defect() },
) {
override get message() {
return `Directory not found: ${this.directory}`
}
}
export class DirectoryAccessDeniedError extends Schema.TaggedError<DirectoryAccessDeniedError>()(
"FileSystem.DirectoryAccessDeniedError",
{ directory: AbsolutePath, cause: Schema.Defect() },
) {
override get message() {
return `Access denied to directory: ${this.directory}`
}
}
export type Error = DirectoryNotFoundError | DirectoryAccessDeniedError
export const resolve = Effect.fn("FileSystemDirectory.resolve")(function* (directory: AbsolutePath) {
const fs = yield* FSUtil.Service
return yield* fs.realPath(directory).pipe(
Effect.catch((cause): Effect.Effect<never, Error> => {
if (cause.reason._tag === "NotFound") return Effect.fail(new DirectoryNotFoundError({ directory, cause }))
// macOS privacy denials arrive as Unknown with an EPERM cause.
if (
cause.reason._tag === "PermissionDenied" ||
(cause.reason._tag === "Unknown" &&
cause.reason.cause instanceof Error &&
"code" in cause.reason.cause &&
cause.reason.cause.code === "EPERM")
)
return Effect.fail(new DirectoryAccessDeniedError({ directory, cause }))
return Effect.die(cause)
}),
)
})
@@ -95,3 +95,6 @@ export function createOpenaiCompatible(options: OpenaiCompatibleProviderSettings
return provider as OpenaiCompatibleProvider
}
// Default OpenAI Compatible provider instance
export const openaiCompatible = createOpenaiCompatible()
+5 -29
View File
@@ -11,8 +11,6 @@ import { FileMutation } from "./file-mutation.js"
import { Environment } from "./environment/index.js"
import { Formatter } from "./formatter.js"
import { FileSystem } from "./filesystem.js"
import { FileSystemDirectory } from "./filesystem/directory.js"
import { FSUtil } from "@opencode/util/fs-util"
import { FileSystemSearch } from "./filesystem/search.js"
import { Generate } from "./generate.js"
import { Form } from "./form.js"
@@ -116,7 +114,7 @@ const nodes = [
export const graph = LayerNode.group(nodes)
export type Services = LayerNode.Output<typeof graph>
export type Error = FileSystemDirectory.Error
export type Error = Layer.Error<ReturnType<typeof layer>>
export interface Options {
// Plugins this instance is born with; empty and absent are equivalent.
@@ -147,7 +145,7 @@ const vanillaReplacements: LayerNode.Replacements = [
]
// One instance is one compiled, fresh copy of the graph standing on a directory.
export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Services, Error> {
export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Services> {
const startedAt = performance.now()
// Ordered: vanilla defaults, then caller replacements (which win over the
// defaults), then instance bindings (which win over everything).
@@ -158,31 +156,9 @@ export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Ser
InstancePlugins.node.replace(InstancePlugins.bound(options.plugins ?? [])),
]
return Layer.unwrap(
Effect.gen(function* () {
// Validate before project/config discovery can turn directory access failures into defects.
if (!ref.workspaceID)
yield* FileSystemDirectory.resolve(ref.directory).pipe(
Effect.provide(LayerNode.compile(FSUtil.node, { replacements: options.replacements })),
)
return LayerNode.compile(graph, { replacements, shared: Node.tags.values.global })
}),
).pipe(
// Unavailable directories are expected; other instance boot failures remain defects.
Layer.catchCause(
(cause): Layer.Layer<Services, Error> =>
Layer.unwrap(
Effect.failCause(cause).pipe(
Effect.catch(
(error): Effect.Effect<never, Error> =>
error instanceof FileSystemDirectory.DirectoryNotFoundError ||
error instanceof FileSystemDirectory.DirectoryAccessDeniedError
? Effect.fail(error)
: Effect.die(error),
),
),
),
),
return LayerNode.compile(graph, { replacements, shared: Node.tags.values.global }).pipe(
// Instance boot failures are defects; provided operations retain their typed errors.
Layer.orDie,
Layer.tap((context) => Effect.addFinalizer(() => Context.get(context, LocationLifecycle.Service).shutdown)),
Layer.tap(() =>
Effect.logInfo("location services booted", {
+2 -2
View File
@@ -5,13 +5,13 @@ import { Context, type Effect } from "effect"
import type { Session } from "@opencode/schema/session"
import { Node } from "@opencode/util/effect/app-node"
import { LayerNode } from "@opencode/util/effect/layer-node"
import type { Services, Error } from "../instance.js"
import type { Services } from "../instance.js"
/** Selects Session capabilities; implementations own caching and lifetime. */
export interface Interface {
readonly provide: (
session: Session.Info,
) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | Error, Exclude<R, Services>>
) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Services>>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/Instance") {}
+1 -1
View File
@@ -17,7 +17,7 @@ export function buildLocationServiceMap(
Effect.gen(function* () {
const owner = yield* Effect.scope
const builds = MutableHashMap.empty<Location.Ref, { close?: Effect.Effect<void> }>()
const inner: LayerMap.LayerMap<Location.Ref, LocationServices, Instance.Error> = yield* LayerMap.make(
const inner: LayerMap.LayerMap<Location.Ref, LocationServices> = yield* LayerMap.make(
(ref: Location.Ref) => {
const build: { close?: Effect.Effect<void> } = {}
MutableHashMap.set(builds, ref, build)
+4 -9
View File
@@ -1,7 +1,7 @@
export * as ModelResolver from "./model-resolver.js"
import { makeLocationNode } from "@opencode/util/effect/app-node"
import { HttpOptions, LanguageModel, mergeHttpOptions, ProviderConfigurationError } from "@opencode/ai"
import { LanguageModel, ProviderConfigurationError } from "@opencode/ai"
import { Auth } from "@opencode/ai/route"
import { Context, Effect, Layer, Schema, Struct } from "effect"
import { AISDK } from "./aisdk.js"
@@ -122,8 +122,8 @@ export interface Resolved {
readonly compaction?: Provider.Compaction
/** Provider transport policy; omitted means HTTP. */
readonly transport?: Provider.Transport
/** Milliseconds without streamed data before a WebSocket exchange fails; `false` disables the limit. */
readonly chunkTimeout?: number | false
/** Milliseconds without streamed data before a WebSocket exchange fails. */
readonly chunkTimeout?: number
}
export interface Interface {
@@ -237,11 +237,6 @@ const resolveCatalogModel = Effect.fn("ModelResolver.resolveCatalogModel")(funct
compatibility: resolved.compatibility
? Object.assign({}, runtime.compatibility, resolved.compatibility)
: runtime.compatibility,
// Timeouts are transport policy, so they land on the route's HTTP defaults instead of package settings.
defaults: {
...runtime.defaults,
http: mergeHttpOptions(runtime.defaults?.http, new HttpOptions(Provider.timeouts(configured))),
},
})
},
catch: (cause) =>
@@ -395,7 +390,7 @@ export const layer = Layer.effect(
limit: selected.limit,
compaction: runtimeInfo.settings?.compaction,
transport: provider?.settings?.transport,
chunkTimeout: Provider.timeout(provider?.settings?.chunkTimeout),
chunkTimeout: provider?.settings?.chunkTimeout,
}
})
return Service.of({
-10
View File
@@ -28,16 +28,6 @@ export type Compatibility = Model.Compatibility
export const Capabilities = Model.Capabilities
export type Capabilities = Model.Capabilities
/** Merges partial config capabilities onto a base model's capabilities, defaulting unset fields. */
export const mergeCapabilities = (config: Partial<Capabilities>, base: Capabilities | undefined) => {
const fallback = base ?? Capabilities.default()
return {
tools: config.tools ?? fallback.tools,
input: [...(config.input ?? fallback.input)],
output: [...(config.output ?? fallback.output)],
}
}
export const Cost = Model.Cost
export const Ref = Model.Ref
@@ -9,7 +9,6 @@ import { Credential } from "../../credential.js"
import { Integration } from "../../integration.js"
import { IntegrationConnection } from "../../integration/connection.js"
import { ManagedPolicy } from "../../managed-policy.js"
import { Model } from "../../model.js"
import { Provider } from "../../provider.js"
import { WebSearch } from "../../websearch.js"
import { ConfigPolicy } from "@opencode/schema/config/policy"
@@ -272,7 +271,11 @@ export const OpencodePlugin = define<HttpClient.HttpClient | Bus.Service | Manag
model.package = config.package ?? (item.package !== undefined ? undefined : model.package)
if (item.settings?.baseURL !== undefined && model.settings) delete model.settings.baseURL
if (config.capabilities !== undefined)
model.capabilities = Model.mergeCapabilities(config.capabilities, model.capabilities)
model.capabilities = {
...config.capabilities,
input: [...config.capabilities.input],
output: [...config.capabilities.output],
}
model.settings = Provider.mergeOverlay(
withoutCredentials(model.settings),
withoutCredentials(config.settings),
+4 -23
View File
@@ -1,10 +1,10 @@
export * as Provider from "./provider.js"
import { Context, Effect, Layer, Option, Schema, Stream, Struct } from "effect"
import { Context, Effect, Layer, Schema, Stream, Struct } from "effect"
import { Provider } from "@opencode/schema/provider"
import { Model } from "@opencode/schema/model"
import { makeLocationNode } from "@opencode/util/effect/app-node"
import { HttpTimeout, type ProviderPackageDefinition } from "@opencode/ai"
import type { ProviderPackageDefinition } from "@opencode/ai"
import { isRecord } from "@opencode/ai/utils/record"
import { Npm } from "@opencode/util/npm"
import type { DeepMutable } from "./schema.js"
@@ -134,8 +134,8 @@ export const loadPackage = Effect.fn("Provider.loadPackage")(function* (input: s
})
/** opencode settings consumed in Core; native packages never receive them. */
const CORE_KEYS = ["chunkTimeout", "compaction", "fetch", "headerTimeout", "timeout", "transport"] as const
const PROVIDER_ONLY_KEYS = ["chunkTimeout", "headerTimeout", "timeout", "transport"] as const
const CORE_KEYS = ["chunkTimeout", "compaction", "fetch", "timeout", "transport"] as const
const PROVIDER_ONLY_KEYS = ["chunkTimeout", "timeout", "transport"] as const
export function nativeSettings(settings: Settings): Settings {
return Struct.omit(settings, CORE_KEYS)
@@ -145,25 +145,6 @@ export function modelSettings(settings: Settings | undefined) {
return settings && Struct.omit(settings, PROVIDER_ONLY_KEYS)
}
const decodeTimeout = Schema.decodeUnknownOption(HttpTimeout)
/** Applied to `headerTimeout` and `chunkTimeout` when provider settings leave them unset or invalid. */
export const DEFAULT_TIMEOUT_MS = 300_000
/** One timeout setting without a default; invalid values are dropped. */
export function timeout(value: unknown) {
return Option.getOrUndefined(decodeTimeout(value))
}
/** Request timeouts from provider settings, resolved for every route. `timeout` has no default. */
export function timeouts(settings: Readonly<Record<string, unknown>>) {
return {
timeout: timeout(settings.timeout),
headerTimeout: timeout(settings.headerTimeout) ?? DEFAULT_TIMEOUT_MS,
chunkTimeout: timeout(settings.chunkTimeout) ?? DEFAULT_TIMEOUT_MS,
}
}
export function mergeOverlay(
base: Readonly<Record<string, unknown>> | undefined,
overlay: Readonly<Record<string, unknown>> | undefined,
+50
View File
@@ -1,3 +1,4 @@
import { Schema } from "effect"
import {
AbsolutePath,
DateTimeUtcFromMillis,
@@ -37,3 +38,52 @@ export type DeepMutable<T> = T extends string | number | boolean | bigint | symb
: T extends object
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
: T
/**
* Nominal wrapper for scalar types. The class itself is a valid schema —
* pass it directly to `Schema.decodeUnknownSync`, `Schema.decodeEffect`, etc.
*
* The runtime value remains an unwrapped primitive. `Schema.brand` supplies
* the primitive schema behavior and constructor validation, while the class
* supplies the nominal TypeScript identity.
* Apply checks and annotations to the underlying schema before wrapping it;
* schema rebuild operations intentionally return the underlying schema shape.
*
* @example
* class QuestionID extends Newtype<QuestionID>()("QuestionID", Schema.String) {}
*
* const id = QuestionID.make("question-1")
* Schema.decodeUnknownEffect(QuestionID)(input)
*/
type NewtypeSchema<Self, Tag extends string, S extends Schema.Top> = (abstract new (_: never) => {
readonly _newtype: Tag
}) &
Schema.BottomWithoutNew<
Self,
S["Encoded"],
S["DecodingServices"],
S["EncodingServices"],
S["ast"],
S["Rebuild"],
S["~type.make.in"],
Self,
S["~type.parameters"],
Self,
S["~type.mutability"],
S["~type.optionality"],
S["~type.constructor.default"],
S["~encoded.mutability"],
S["~encoded.optionality"]
> &
Omit<S, keyof Schema.Top>
export function Newtype<Self>() {
return <const Tag extends string, S extends Schema.Top>(tag: Tag, schema: S): NewtypeSchema<Self, Tag, S> => {
abstract class Base {
declare readonly _newtype: Tag
}
Object.setPrototypeOf(Base, schema.pipe(Schema.brand(tag)))
return Base as unknown as NewtypeSchema<Self, Tag, S>
}
}
+4 -13
View File
@@ -12,7 +12,6 @@ import { SessionMessage } from "./session/message.js"
import { PromptInput } from "@opencode/schema/prompt-input"
import { Bus } from "./bus.js"
import { Instance } from "./instance/service.js"
import type { FileSystemDirectory } from "./filesystem/directory.js"
import { Database } from "./database/database.js"
import { SessionProjector } from "./session/projector.js"
import { SessionMessageTable } from "./session/sql.js"
@@ -146,10 +145,7 @@ export interface Interface {
readonly from?: SessionMessage.ID
readonly to?: SessionMessage.ID
readonly context?: number
}) => Effect.Effect<
readonly FileDiff.Info[],
NotFoundError | MessageNotFoundError | TurnRangeError | Snapshot.Error | FileSystemDirectory.Error
>
}) => Effect.Effect<readonly FileDiff.Info[], NotFoundError | MessageNotFoundError | TurnRangeError | Snapshot.Error>
/**
* Durable admitted session work not yet visible in projected history,
* ordered by admission. Includes unpromoted user and synthetic inputs and
@@ -199,7 +195,7 @@ export interface Interface {
agents?: PromptInput.Prompt["agents"]
skills?: PromptInput.Prompt["skills"]
delivery?: SessionInbox.Delivery
}) => Effect.Effect<void, NotFoundError | Command.NotFoundError | Command.ExecutionError | FileSystemDirectory.Error>
}) => Effect.Effect<void, NotFoundError | Command.NotFoundError | Command.ExecutionError>
readonly shell: (
input: Parameters<Session.Handle["shell"]>[0] & { sessionID: SessionSchema.ID },
) => ReturnType<Session.Handle["shell"]>
@@ -222,13 +218,8 @@ export interface Interface {
sessionID: SessionSchema.ID
messageID: SessionMessage.ID
files?: boolean
}) => Effect.Effect<
SessionSchema.Revert,
NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error | FileSystemDirectory.Error
>
readonly clear: (
sessionID: SessionSchema.ID,
) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error | FileSystemDirectory.Error>
}) => Effect.Effect<SessionSchema.Revert, NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error>
readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error>
readonly commit: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError>
}
}
+1 -7
View File
@@ -1,5 +1,4 @@
export * as SessionGenerate from "./generate.js"
import type { FileSystemDirectory } from "../filesystem/directory.js"
import { LLMClient, Message, type AIError } from "@opencode/ai"
import { Effect } from "effect"
@@ -15,12 +14,7 @@ import { SessionModelRequest } from "./model-request.js"
import type { SessionRunnerModel } from "./runner/model.js"
import type { SessionSchema } from "./schema.js"
export type Error =
| AgentNotFoundError
| Instructions.InitializationBlocked
| SessionRunnerModel.Error
| AIError
| FileSystemDirectory.Error
export type Error = AgentNotFoundError | Instructions.InitializationBlocked | SessionRunnerModel.Error | AIError
/** Generates text from current Session context without mutating the Session. */
export const generate = Effect.fn("SessionGenerate.generate")(function* (input: {
+2 -1
View File
@@ -310,8 +310,9 @@ export const layer = Layer.effect(
const request = LLMRequest.update(base, {
model: route === base.model.route ? base.model : LanguageModel.update(base.model, { route }),
http: new HttpOptions({
...base.http,
body: base.http?.body,
headers: Object.keys(modelHook.headers).length === 0 ? undefined : modelHook.headers,
query: base.http?.query,
}),
})
// History selects native windows against the catalog route before hooks run. A newly installed
+5 -5
View File
@@ -12,7 +12,7 @@ import {
} from "@opencode/ai/route"
import { AIError, AIErrorReason, TransportError, type TransportOperation } from "@opencode/ai"
import { Hash } from "@opencode/util/hash"
import { Cause, Clock, Context, Duration, Effect, Fiber, Layer, Metric, Queue, Scope, Semaphore, Stream } from "effect"
import { Cause, Clock, Context, Effect, Fiber, Layer, Metric, Queue, Scope, Semaphore, Stream } from "effect"
import { Headers } from "effect/unstable/http"
import { Socket } from "effect/unstable/socket"
import { makeGlobalNode } from "@opencode/util/effect/app-node"
@@ -76,7 +76,7 @@ export interface Interface {
readonly bind: (
sessionID: SessionSchema.ID,
interceptor?: Interceptor,
idleTimeout?: number | false,
idleTimeout?: number,
) => WebSocketChannelExecutor
readonly close: (sessionID: SessionSchema.ID) => Effect.Effect<void>
readonly closeAll: Effect.Effect<void>
@@ -296,7 +296,7 @@ export const makeLayer = (connector: WebSocketConnector) =>
owner: State,
input: WebSocketChannelExchange,
interceptor?: Interceptor,
idleTimeout?: number | false,
idleTimeout?: number,
) {
if (owner.closed)
return yield* transportError("Session WebSocket owner is closed", {
@@ -415,7 +415,7 @@ export const makeLayer = (connector: WebSocketConnector) =>
const token = {}
const frames = Stream.fromQueue(active.queue).pipe(
Stream.timeoutOrElse({
duration: idleTimeout === false ? Duration.infinity : (idleTimeout ?? IDLE_TIMEOUT),
duration: idleTimeout ?? IDLE_TIMEOUT,
orElse: () =>
Stream.fail(
transportError("Timed out waiting for WebSocket data", {
@@ -509,7 +509,7 @@ export const makeLayer = (connector: WebSocketConnector) =>
const bind = (
sessionID: SessionSchema.ID,
interceptor?: Interceptor,
idleTimeout?: number | false,
idleTimeout?: number,
): WebSocketChannelExecutor => ({
execute: (exchange) => {
const owner = state(sessionID)
+2 -8
View File
@@ -1,5 +1,4 @@
export * as SessionMove from "./move.js"
import type { FileSystemDirectory } from "../filesystem/directory.js"
import type { Session } from "@opencode/schema/session"
import { makeGlobalNode } from "@opencode/util/effect/app-node"
@@ -46,11 +45,7 @@ export interface Interface {
delivery?: SessionInbox.Delivery
}) => Effect.Effect<
void,
| NotFoundError
| DestinationNotFoundError
| DestinationNotDirectoryError
| DestinationUnavailableError
| FileSystemDirectory.Error
NotFoundError | DestinationNotFoundError | DestinationNotDirectoryError | DestinationUnavailableError
>
}
@@ -96,8 +91,7 @@ const layer = Layer.effect(
yield* locations.contextEffect(destination.location).pipe(
Effect.scoped,
Effect.catchCause((cause) => {
if (Cause.hasInterruptsOnly(cause))
return Effect.failCause(Cause.fromReasons<never>(cause.reasons.filter(Cause.isInterruptReason)))
if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause)
return Effect.logWarning("session move destination unavailable", { directory, cause }).pipe(
Effect.andThen(Effect.fail(new DestinationUnavailableError({ directory }))),
)
@@ -7,7 +7,6 @@ import type { Promotable } from "../inbox.js"
import type { AgentNotFoundError, MessageDecodeError, StepFailedError } from "../error.js"
import { SessionRunnerModel } from "./model.js"
import type { Instructions } from "../../instructions/index.js"
import type { FileSystemDirectory } from "../../filesystem/directory.js"
export type RunError =
| AIError
@@ -16,7 +15,6 @@ export type RunError =
| AgentNotFoundError
| StepFailedError
| Instructions.InitializationBlocked
| FileSystemDirectory.Error
export type Continuation = { readonly step: number }
+1 -9
View File
@@ -55,17 +55,10 @@ const schedule = Schedule.max([
}),
)
// A timed-out attempt already waited minutes before failing, so the general allowance would let a
// dead provider hold a step for most of an hour. Cap those attempts well below it.
const MAX_TIMEOUT_RETRIES = 3
const isTimeout = (error: AIError) => error.reason._tag === "Transport" && error.reason.code === "Timeout"
export const policy = (sessionID: SessionSchema.ID) =>
Effect.gen(function* () {
const step = yield* Schedule.toStep(schedule)
let attempt = 1
let timeouts = 0
return (input: Input) =>
Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis
@@ -73,7 +66,6 @@ export const policy = (sessionID: SessionSchema.ID) =>
if (!next) return { retry: false as const }
const [, duration] = next
attempt++
if (isTimeout(input.cause)) timeouts++
const delay = Math.ceil(Duration.toMillis(duration))
const event: PluginHooks.Domains["session"]["retry"] = {
sessionID,
@@ -81,7 +73,7 @@ export const policy = (sessionID: SessionSchema.ID) =>
model: input.model,
error: input.error,
attempt,
decision: input.retry && timeouts <= MAX_TIMEOUT_RETRIES ? { retry: true, delay } : { retry: false },
decision: input.retry ? { retry: true, delay } : { retry: false },
}
yield* input.hook(event)
if (!event.decision.retry) return event.decision
+1
View File
@@ -0,0 +1 @@
export * as Identifier from "@opencode/schema/identifier"
+21
View File
@@ -11,8 +11,29 @@ const Issue = Schema.StructWithRest(
[Schema.Record(Schema.String, Schema.Unknown)],
)
export const JsonError = NamedError.create("ConfigJsonError", {
path: Schema.String,
message: Schema.optional(Schema.String),
})
export const InvalidError = NamedError.create("ConfigInvalidError", {
path: Schema.String,
issues: Schema.optional(Schema.Array(Issue)),
message: Schema.optional(Schema.String),
})
export const FrontmatterError = NamedError.create("ConfigFrontmatterError", {
path: Schema.String,
message: Schema.String,
})
export const DirectoryTypoError = NamedError.create("ConfigDirectoryTypoError", {
path: Schema.String,
dir: Schema.String,
suggestion: Schema.String,
})
export const RemoteAuthError = NamedError.create("ConfigRemoteAuthError", {
url: Schema.String,
remote: Schema.String,
})
+33
View File
@@ -0,0 +1,33 @@
export * as PermissionV1 from "./permission.js"
import { Schema } from "effect"
export * from "@opencode/schema/permission-v1"
import { ID } from "@opencode/schema/permission-v1"
export class RejectedError extends Schema.TaggedError<RejectedError>()("PermissionRejectedError", {}) {
override get message() {
return "The user rejected permission to use this specific tool call."
}
}
export class CorrectedError extends Schema.TaggedError<CorrectedError>()("PermissionCorrectedError", {
feedback: Schema.String,
}) {
override get message() {
return `The user rejected permission to use this specific tool call with the following feedback: ${this.feedback}`
}
}
export class DeniedError extends Schema.TaggedError<DeniedError>()("PermissionDeniedError", {
ruleset: Schema.Any,
}) {
override get message() {
return `The user has specified a rule which prevents you from using this specific tool call. Here are some of the relevant rules ${JSON.stringify(this.ruleset)}`
}
}
export class NotFoundError extends Schema.TaggedError<NotFoundError>()("Permission.NotFoundError", {
requestID: ID,
}) {}
export type Error = DeniedError | RejectedError | CorrectedError
-2
View File
@@ -1,5 +1,4 @@
export * as Worktree from "./worktree.js"
import type { FileSystemDirectory } from "./filesystem/directory.js"
import { Context, Effect, Layer, Schema } from "effect"
import { and, asc, desc, eq, isNull, sql } from "drizzle-orm"
@@ -69,7 +68,6 @@ export class StrategyUnavailableError extends Schema.TaggedError<StrategyUnavail
) {}
export type Error =
| FileSystemDirectory.Error
| Project.NotFoundError
| SourceDirectoryNotFoundError
| DestinationExistsError
-39
View File
@@ -779,45 +779,6 @@ it.effect("sends AI SDK requests directly when no HTTP hook middleware is attach
}),
)
it.effect("fails with a retryable transport error when response headers time out", () =>
Effect.gen(function* () {
const aisdk = yield* AISDK.Service
const customFetch = Object.assign(
(_input: Parameters<typeof fetch>[0], init?: RequestInit) =>
new Promise<Response>((_, reject) => {
init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true })
}),
{ preconnect: fetch.preconnect },
)
yield* aisdk.hook.sdk((event) => {
event.sdk = createOpenAICompatible({
...event.options,
name: String(event.options.name),
baseURL: String(event.options.baseURL),
})
})
const resolved = yield* aisdk.model(
model("@ai-sdk/openai-compatible", {
apiKey: "test",
baseURL: "https://example.test/v1",
headerTimeout: 25,
fetch: customFetch,
}),
)
const error = yield* LLMClient.generate(LLM.request({ model: resolved, prompt: "Hello" })).pipe(
Effect.provide(client),
Effect.flip,
)
expect(error.reason).toMatchObject({
_tag: "Transport",
operation: "request",
message: "Response headers timed out",
})
expect(SessionRunnerRetry.isRetryable(error)).toBeTrue()
}),
)
it.effect("emits malformed AI SDK tool input without executing it", () =>
Effect.gen(function* () {
const aisdk = yield* AISDK.Service
@@ -203,18 +203,6 @@ describe("ConfigNormalize", () => {
])
})
test("accepts partial model capabilities without reporting a diagnostic", () => {
const result = normalized({
providers: {
demo: { name: "Demo", models: { demo: { name: "Demo", capabilities: { input: ["text"], output: ["text"] } } } },
},
})
expect(result.diagnostics).toEqual([])
expect(result.encoded.providers).toEqual({
demo: { name: "Demo", models: { demo: { name: "Demo", capabilities: { input: ["text"], output: ["text"] } } } },
})
})
test("uses a valid retired provider alias when the canonical legacy entry is malformed", () => {
const result = normalized({
provider: {
@@ -255,70 +255,6 @@ describe("ConfigProviderPlugin.Plugin", () => {
}),
)
it.effect("merges partial config capabilities onto the base model's existing fields", () =>
Effect.gen(function* () {
const providers = yield* Provider.Service
const models = yield* Model.Service
const providerID = Provider.ID.make("custom")
const modelID = Model.ID.make("partial")
yield* providers.transform((editor) => {
editor.models.update(providerID, modelID, (model) => {
model.capabilities = { tools: false, input: ["text"], output: ["text"] }
})
})
const entries = [
new Document({
type: "document",
info: decode({
providers: {
custom: {
package: "aisdk:@ai-sdk/openai-compatible",
models: { partial: { capabilities: { input: ["text", "image"] } } },
},
},
}),
}),
]
yield* addPlugin(entries)
expect((yield* models.get(providerID, modelID))?.capabilities).toEqual({
tools: false,
input: ["text", "image"],
output: ["text"],
})
}),
)
it.effect("resolves a config model with only input/output capabilities using default tools", () =>
Effect.gen(function* () {
const models = yield* Model.Service
const providerID = Provider.ID.make("custom")
const modelID = Model.ID.make("demo")
const entries = [
new Document({
type: "document",
info: decode({
providers: {
custom: {
package: "aisdk:@ai-sdk/openai-compatible",
models: { demo: { name: "Demo", capabilities: { input: ["text"], output: ["text"] } } },
},
},
}),
}),
]
yield* addPlugin(entries)
expect((yield* models.get(providerID, modelID))?.capabilities).toEqual({
tools: true,
input: ["text"],
output: ["text"],
})
}),
)
for (const scenario of [
{ name: "omitted capabilities", legacy: {}, overrides: {} },
{
@@ -2,7 +2,6 @@ import { Bus } from "@opencode/core/bus"
import { Image } from "@opencode/core/image"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Plugin } from "@opencode/core/plugin"
import { PluginHooks } from "@opencode/core/plugin/hooks"
import { Skill } from "@opencode/core/skill"
@@ -25,7 +24,7 @@ export const promptLocationNode = makeGlobalNode({
Bus.node.replace(Layer.succeed(Bus.Service, bus)),
Plugin.node.replace(Layer.mock(Plugin.Service, { awaitActivation: Effect.void })),
],
}) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
}) as Layer.Layer<LocationServices>,
)
}),
),
+1 -2
View File
@@ -1,5 +1,4 @@
import { describe, expect } from "bun:test"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Context, Deferred, Duration, Effect, Fiber, Layer, LayerMap, RcMap, Schema } from "effect"
import { TestClock } from "effect/testing"
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
@@ -66,7 +65,7 @@ const locations = Layer.effect(
Layer.provideMerge(Form.layer),
Layer.provide(Layer.succeed(Bus.Service, bus)),
Layer.fresh,
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
{ idleTimeToLive: Duration.infinity },
)
return {
+1 -68
View File
@@ -1,8 +1,7 @@
import fs from "fs/promises"
import path from "path"
import { describe, expect } from "bun:test"
import { Cause, Effect, Exit, Layer, PlatformError } from "effect"
import { FSUtil } from "@opencode/util/fs-util"
import { Effect, Exit, Layer } from "effect"
import { LayerNode } from "@opencode/util/effect/layer-node"
import { FileSystem } from "@opencode/core/filesystem"
import { Location } from "@opencode/core/location"
@@ -80,76 +79,10 @@ describe("FileSystem", () => {
// host realpath canonicalization stays load-bearing for local placements.
const local = yield* FileSystem.Service.pipe(provide(missing), Effect.exit)
expect(Exit.isFailure(local)).toBe(true)
if (Exit.isFailure(local)) {
const error = Cause.findErrorOption(local.cause)
expect(error).toMatchObject({
_tag: "Some",
value: {
_tag: "FileSystem.DirectoryNotFoundError",
directory: missing,
message: `Directory not found: ${missing}`,
},
})
}
}),
),
)
for (const input of [
{ reason: "PermissionDenied", code: "EACCES", denied: true },
{ reason: "Unknown", code: "EPERM", denied: true },
{ reason: "Unknown", code: "EIO", denied: false },
] as const) {
it.live(`classifies directory initialization failure ${input.code}`, () =>
withTmp((directory) =>
Effect.gen(function* () {
const filesystem = yield* FSUtil.Service
const cause = PlatformError.systemError({
_tag: input.reason,
module: "FileSystem",
method: "realPath",
pathOrDescriptor: directory,
cause: Object.assign(new Error(input.code), { code: input.code }),
})
const result = yield* FileSystem.Service.pipe(
Effect.provide(
LayerNode.compile(FileSystem.node, {
replacements: [
Location.node.replace(
Layer.succeed(Location.Service, location({ directory: AbsolutePath.make(directory) })),
),
FSUtil.node.replace(
Layer.succeed(FSUtil.Service, {
...filesystem,
realPath: (target) => (target === directory ? Effect.fail(cause) : filesystem.realPath(target)),
}),
),
],
}),
),
Effect.exit,
)
expect(Exit.isFailure(result)).toBe(true)
if (Exit.isFailure(result)) {
if (input.denied) {
expect(Cause.findErrorOption(result.cause)).toMatchObject({
_tag: "Some",
value: {
_tag: "FileSystem.DirectoryAccessDeniedError",
directory,
cause,
message: `Access denied to directory: ${directory}`,
},
})
return
}
expect(result.cause.reasons.filter(Cause.isDieReason)).toMatchObject([{ defect: cause }])
}
}).pipe(Effect.provide(LayerNode.compile(FSUtil.node))),
),
)
}
it.live("lists parents and siblings with paths relative to the current location", () =>
withTmp((directory) =>
Effect.gen(function* () {
+1 -2
View File
@@ -27,7 +27,6 @@ import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
import { LayerNode } from "@opencode/util/effect/layer-node"
import { Global } from "@opencode/util/global"
import { LocationServiceMap, type LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { LocationActivity } from "@opencode/core/location-activity"
import { Location } from "@opencode/core/location"
import { LocationWatcher } from "@opencode/core/filesystem/location-watcher"
@@ -68,7 +67,7 @@ const activityLocations = Layer.effect(
workspaceID: ref.workspaceID,
project: { id: Project.ID.global, directory: ref.directory, canonical: ref.directory },
}),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
{ idleTimeToLive: Duration.infinity },
),
)
+47
View File
@@ -0,0 +1,47 @@
import { expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { Newtype } from "../src/schema"
class UserID extends Newtype<UserID>()("Test.UserID", Schema.NonEmptyString) {}
class ProjectID extends Newtype<ProjectID>()("Test.ProjectID", Schema.NonEmptyString) {}
class Port extends Newtype<Port>()("Test.Port", Schema.FiniteFromString) {}
const User = Schema.Struct({ id: UserID })
test("constructs nominal values from the underlying type", () => {
const id = UserID.make("user-1")
const acceptUserID = (_id: UserID) => undefined
expect(String(id)).toBe("user-1")
acceptUserID(id)
if (false) {
// @ts-expect-error distinct newtypes are not interchangeable
acceptUserID(ProjectID.make("project-1"))
}
})
test("preserves constructor validation", () => {
expect(() => UserID.make("")).toThrow()
})
test("decodes and encodes as a schema", async () => {
const decoded = await Effect.runPromise(Schema.decodeUnknownEffect(User)({ id: "user-1" }))
const encoded = await Effect.runPromise(Schema.encodeEffect(User)(decoded))
expect(String(decoded.id)).toBe("user-1")
expect(encoded).toEqual({ id: "user-1" })
})
test("preserves the underlying schema validation", async () => {
const result = await Effect.runPromise(Schema.decodeUnknownEffect(UserID)("").pipe(Effect.result))
expect(result._tag).toBe("Failure")
})
test("preserves transformed encoded and decoded representations", async () => {
const decoded = await Effect.runPromise(Schema.decodeUnknownEffect(Port)("8080"))
const encoded = await Effect.runPromise(Schema.encodeEffect(Port)(decoded))
expect(Number(decoded)).toBe(8080)
expect(encoded).toBe("8080")
})
+1 -2
View File
@@ -9,7 +9,6 @@ import { Bus } from "@opencode/core/bus"
import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { AbsolutePath } from "@opencode/core/schema"
import { Session } from "@opencode/core/session"
@@ -59,7 +58,7 @@ const locations = Layer.effect(
Layer.provide(client),
Layer.provide(config),
Layer.provide(models),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
),
)
const it = testEffect(
+1 -2
View File
@@ -9,7 +9,6 @@ import { Job } from "@opencode/core/job"
import { KV } from "@opencode/core/kv"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { ProjectTable } from "@opencode/core/project/sql"
import { AbsolutePath } from "@opencode/core/schema"
@@ -1372,7 +1371,7 @@ function buildExecution(
() =>
// The local execution test only needs the Session runner from the Location graph.
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
runner as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
runner as unknown as Layer.Layer<LocationServices>,
),
)
return yield* Layer.buildWithScope(
@@ -634,36 +634,6 @@ describe("SessionModelTransport", () => {
)
})
test("disables the idle timeout when chunkTimeout is false", async () => {
const started = Deferred.makeUnsafe<void>()
const messages = queue<string | Uint8Array, AIError>()
const connector: WebSocketConnector = {
open: () =>
Effect.succeed({
sendText: () => Deferred.succeed(started, undefined),
messages: Stream.fromQueue(messages),
close: Queue.shutdown(messages),
}),
}
await runWithTestClock(
connector,
Effect.gen(function* () {
const transport = yield* SessionModelTransport.Service
const running = yield* collect(transport.bind(session, undefined, false), exchange("patient")).pipe(
Effect.forkChild({ startImmediately: true }),
)
yield* Deferred.await(started)
yield* Effect.yieldNow
yield* TestClock.adjust("2 hours")
yield* Queue.offer(messages, "completed:patient")
expect(yield* Fiber.join(running)).toEqual(["completed:patient"])
}),
)
})
test("closes a newly opened connection when request creation is interrupted", async () => {
const opened = Deferred.makeUnsafe<void>()
const messages = queue<string | Uint8Array, AIError>()
+2 -7
View File
@@ -12,7 +12,6 @@ import { Instance } from "@opencode/core/instance"
import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { AbsolutePath } from "@opencode/core/schema"
import { Session } from "@opencode/core/session"
@@ -59,7 +58,7 @@ const itWithActiveExecution = testEffect(
replacements: [Project.node.replace(globalProjectNode), offlineModels],
}),
Layer.succeed(SessionRunner.Service, { drain: () => Effect.never }),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
),
),
),
@@ -69,11 +68,7 @@ const itWithActiveExecution = testEffect(
const unavailableLocations = Layer.effect(
LocationServiceMap.Service,
LayerMap.make(
() =>
Layer.effectDiscard(Effect.die(new Error("broken location"))) as unknown as Layer.Layer<
LocationServices,
FileSystemDirectory.Error
>,
() => Layer.effectDiscard(Effect.fail(new Error("broken location"))) as unknown as Layer.Layer<LocationServices>,
),
)
const itWithUnavailableDestination = testEffect(
+1 -2
View File
@@ -26,7 +26,6 @@ import { SessionInboxTable, SessionMessageTable, SessionTable } from "@opencode/
import { SessionStore } from "@opencode/core/session/store"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Image } from "@opencode/core/image"
import { Plugin } from "@opencode/core/plugin"
import { PluginHooks } from "@opencode/core/plugin/hooks"
@@ -84,7 +83,7 @@ const locations = makeGlobalNode({
restore: () => Effect.void,
}),
Layer.mock(Plugin.Service, { awaitActivation: Effect.void }),
).pipe(Layer.fresh) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
).pipe(Layer.fresh) as unknown as Layer.Layer<LocationServices>,
)
}),
),
@@ -1,47 +0,0 @@
import { describe, expect } from "bun:test"
import { AIError, ProviderInternalError, TransportError } from "@opencode/ai"
import { Agent } from "@opencode/schema/agent"
import { Model } from "@opencode/schema/model"
import { Provider } from "@opencode/core/provider"
import { SessionSchema } from "@opencode/core/session/schema"
import { SessionRunnerRetry } from "@opencode/core/session/runner/retry"
import { toSessionError } from "@opencode/core/session/to-session-error"
import { Effect } from "effect"
import { it } from "./lib/effect"
const timeout = new AIError({
reason: new TransportError({ message: "Timed out", transport: "http", operation: "request", code: "Timeout" }),
})
const internal = new AIError({ reason: new ProviderInternalError({ message: "internal" }) })
const input = (cause: AIError) => ({
cause,
error: toSessionError(cause),
agent: Agent.ID.make("build"),
model: Model.Ref.make({ id: Model.ID.make("model"), providerID: Provider.ID.make("provider") }),
hook: () => Effect.void,
retry: SessionRunnerRetry.isRetryable(cause),
})
// Decisions for `count` consecutive failures of the same cause within one step.
const decisions = (cause: AIError, count: number) =>
Effect.gen(function* () {
const policy = yield* SessionRunnerRetry.policy(SessionSchema.ID.make("ses_retry"))
const results: boolean[] = []
for (let i = 0; i < count; i++) results.push((yield* policy(input(cause))).retry)
return results
})
describe("SessionRunnerRetry.policy", () => {
it.effect("stops retrying transport timeouts after three attempts", () =>
Effect.gen(function* () {
expect(yield* decisions(timeout, 4)).toEqual([true, true, true, false])
}),
)
it.effect("keeps the general allowance for other transient failures", () =>
Effect.gen(function* () {
expect(yield* decisions(internal, 10)).toEqual(Array(10).fill(true))
}),
)
})
+1 -2
View File
@@ -10,7 +10,6 @@ import { Image } from "@opencode/core/image"
import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { Plugin } from "@opencode/core/plugin"
import { PluginHooks } from "@opencode/core/plugin/hooks"
@@ -50,7 +49,7 @@ const locations = makeGlobalNode({
list: () => Effect.succeed([info]),
}),
Layer.mock(Plugin.Service, { awaitActivation: Effect.void }),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
),
),
deps: [],
+4
View File
@@ -8,6 +8,7 @@ import { Model } from "@opencode/schema/model"
import { Provider } from "@opencode/schema/provider"
import { Project } from "@opencode/schema/project"
import { Worktree } from "@opencode/schema/worktree"
import { PermissionV1 } from "@opencode/schema/permission-v1"
import { Prompt } from "@opencode/schema/prompt"
import { SessionInbox } from "@opencode/schema/session-inbox"
import { SessionMessage } from "@opencode/schema/session-message"
@@ -37,6 +38,7 @@ test("Core reuses the canonical shared schemas", async () => {
coreAI,
coreModel,
corePermission,
corePermissionV1,
coreWorktree,
corePty,
coreProject,
@@ -57,6 +59,7 @@ test("Core reuses the canonical shared schemas", async () => {
import("@opencode/ai"),
import("@opencode/core/model"),
import("@opencode/core/permission"),
import("@opencode/core/v1/permission"),
import("@opencode/core/worktree"),
import("@opencode/core/pty"),
import("@opencode/core/project/schema"),
@@ -110,6 +113,7 @@ test("Core reuses the canonical shared schemas", async () => {
[corePermission.Effect, Permission.Effect],
[corePermission.Rule, Permission.Rule],
[corePermission.Ruleset, Permission.Ruleset],
[corePermissionV1.Event, PermissionV1.Event],
[coreWorktree.CreateInput, Worktree.CreateInput],
[coreWorktree.RemoveInput, Worktree.RemoveInput],
[coreWorktree.Info, Worktree.Info],
+6 -18
View File
@@ -124,9 +124,6 @@ export function createHost(input: {
const reloads = new Map<string, number>()
const closed = new Set<(win: BrowserWindow) => void>()
const status = { sequence: 0, disposed: false, menubarQueued: false }
// Extensions running a restart handoff. Disposal skips them, so a failed handoff leaves them working and a
// successful one keeps their state and menu items until the app has quit.
const restarting = new Set<string>()
const broadcast = (event: DesktopEvent) => getMainWindows().forEach((win) => emitIpcEvent(win.webContents, event))
const changed = () => broadcast(new ExtensionsChanged({ list: installed() }))
@@ -222,17 +219,13 @@ export function createHost(input: {
}
}
const mainApp = {
const mainApp: MainApp = {
version: VERSION,
channel: CHANNEL,
packaged: app.isPackaged,
server,
restart: input.restart,
log: (level, message, data) => input.write(level, message, data ?? {}),
} satisfies Omit<MainApp, "restart">
const restart = (id: string, handoff?: () => void | Promise<void>) => {
restarting.add(id)
return input.restart(handoff).finally(() => restarting.delete(id))
}
const loader = (id: string): (() => Promise<Loaded>) | undefined => {
@@ -348,7 +341,7 @@ export function createHost(input: {
],
[MainStorage.id, createMainStorage(input.state, id)],
[Cli.id, input.cli],
[MainApp.id, { ...mainApp, restart: (handoff) => restart(id, handoff) } satisfies MainApp],
[MainApp.id, mainApp],
])
function add<T>(point: Point<T>, item: T | (() => T | undefined)): Cleanup {
@@ -803,18 +796,13 @@ export function createHost(input: {
if (!file) throw new ExtensionError("notFound")
return file.toString("utf8")
},
/** Disposes every main extension but one running a restart handoff; quitting awaits their async cleanups. */
/** Disposes every main extension; quitting awaits their async cleanups. */
async dispose() {
status.disposed = true
stopWindows()
stopLocale()
// Every other extension with an instance or a queued step stops; quitting waits for each lifecycle to settle.
// The menu is not rebuilt from here on (`publishMenubar`), so a restarting extension's items stay in it.
await Promise.all(
[...new Set([...active.keys(), ...queues.keys()])]
.filter((id) => !restarting.has(id))
.map((id) => deactivate(id)),
)
// Every extension with an instance or a queued step stops; quitting waits for each lifecycle to settle.
await Promise.all([...new Set([...active.keys(), ...queues.keys()])].map((id) => deactivate(id)))
},
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ export const layer = Layer.effect(
setExtensionAssets((request, url) => extensionAsset(storage.db, request, url))
// Disposes every extension but the caller (the host keeps it), then hands off (e.g. quitAndInstall) or relaunches.
// Like the updater: flush and dispose everything first, then hand off (quitAndInstall) or relaunch.
const restart = async (handoff?: () => void | Promise<void>) => {
setAppQuitting()
await runPromise(lifecycle.prepareToRestart)
@@ -1,45 +0,0 @@
import { afterEach, describe, expect, test } from "bun:test"
import { mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import path from "node:path"
import { Schema } from "effect"
import { openDatabase, type Database } from "../storage/database"
import { createStateStore } from "../storage/state"
import { createMainStorage } from "./storage"
const roots: string[] = []
// Bun's node:sqlite shim pins the WAL files on Windows after close(); tolerate the leftover here only.
afterEach(() =>
Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }).catch(() => undefined))),
)
const open = (db: Database) =>
createMainStorage(createStateStore(db), "example").store("servers", {
schema: Schema.Array(Schema.String),
initial: [],
})
describe("main extension storage", () => {
// A crash right after saving keeps the value: another connection reads it without any flush.
test.each([
{ name: "set", write: (store: ReturnType<typeof open>) => store.set(["a"]), expected: ["a"] },
{
name: "remove",
write: (store: ReturnType<typeof open>) => {
store.set(["a"])
store.remove()
},
expected: [],
},
])("$name reaches the database before it returns", async (row) => {
const root = await mkdtemp(path.join(tmpdir(), "opencode-extension-storage-"))
roots.push(root)
const file = path.join(root, "drafts.sqlite")
const writer = openDatabase(file)
const reader = openDatabase(file)
row.write(open(writer.db))
expect(open(reader.db).get()).toEqual(row.expected)
writer.close()
reader.close()
})
})
@@ -3,10 +3,7 @@ import { Option, Schema } from "effect"
import type { StateStore } from "../storage/state"
import { getStore } from "../storage/store"
/**
* Each extension's values live in the `state` table under `extension.<id>`, stored as canonical JSON. Writes are rare,
* so each one reaches the database before it returns and survives a crash.
*/
/** Each extension's values live in the `state` table under `extension.<id>`, stored as canonical JSON. */
export function createMainStorage(state: StateStore, id: string): MainStorage {
const name = namespace(id)
return {
@@ -31,14 +28,12 @@ export function createMainStorage(state: StateStore, id: string): MainStorage {
},
set(value) {
state.set(name, key, JSON.stringify(Schema.encodeSync(codec)(value)))
state.flush()
cached.value = { current: value }
},
remove() {
// The old copy goes too, or the next read would import it again.
legacy?.remove()
if (state.get(name, key) !== null) state.delete(name, key)
state.flush()
cached.value = { current: options.initial }
},
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test"
import { Share } from "../../src/core/share"
import { Storage } from "../../src/core/storage"
import { Identifier } from "@opencode/schema/identifier"
import { Identifier } from "@opencode/core/util/identifier"
describe.concurrent("core.share", () => {
test("should create a share", async () => {
+2 -51
View File
@@ -6,7 +6,7 @@ Built-in features of the desktop and web app, each behind the SDK in `src/sdk/`.
- One folder per extension: `index.ts` (`Extension.define({ id, os?, i18n })`), `contract.ts`, `renderer.tsx`, optional `main.ts`, and `i18n/<locale>.ts`.
- `src/renderer.ts` and `src/main.ts` are the only files that list the built-ins. Main never imports renderer code.
- Another extension may import only your `contract.ts` (tokens and schemas, no runtime code). Every consumer must still work when the provider is disabled: `ctx.use(Service)` is an accessor that can return `undefined` (see "Failure is part of the contract").
- Another extension may import only your `contract.ts` (tokens and schemas, no runtime code). Every consumer must still work when the provider is disabled: `ctx.use(Service)` is an accessor that can return `undefined`.
- Never import `@opencode/app`, `@opencode/desktop`, or `@/` paths. Import CSS with `?inline` and contribute it through `ctx.add(Style, css)`. No module-level state: keep state inside `setup`. `bun run lint` enforces these rules.
## Host boundary
@@ -14,57 +14,9 @@ Built-in features of the desktop and web app, each behind the SDK in `src/sdk/`.
- Host code must not name an extension or know its internals: no extension ids, command ids, DOM selectors, or stored key formats. When the host needs something from extensions, add a generic, documented field to the SDK (for example `PanelTab.file`, `Command.featured`, `Command.section`).
- Accepted exceptions: the built-in lists, the keybind rename map in `packages/app/src/settings/keybinds/migration.ts`, the legacy `type: "browser"` comment decode in `packages/app/src/composer/schema.ts` (drafts and message metadata written before extension notes), and the crash page's use of the updater contract.
## Lifetimes
An instance lives from `setup` until it is disabled, reloaded, removed, or its window closes. A reload can land at any `await`, so code that outlives a tick must prove it still belongs to the live instance.
- Everything registered through `ctx` (contributions, services, remotes, menu items, surfaces) is withdrawn by the host when the instance goes away. Anything else you start (timers, DOM or remote listeners, subscriptions) needs `ctx.cleanup`. A cleanup registered after disposal runs at once.
- `setup` may be async. After every `await`, return if `ctx.signal.aborted` before touching state or contributing. Pass `ctx.signal`, or a signal derived from it, to remote calls and long work.
- Never keep a value from a shorter lifetime in a longer one. Read a server's `client`, `data` and `url` from its live `ServerRef` each time: a restarted or re-authenticated server gets a new controller under the same id. Keep per-session state in a session-scoped store, or in a map keyed by session that you prune.
- Main: `MainApp.restart(handoff)` keeps the calling extension active until the handoff settles; when it rejects, return to a state the user can retry from.
## Failure is part of the contract
- `ctx.use(Service | Remote)` returns `undefined` while the provider is loading, disabled, failed, or restarting. Every user action must still answer: show an unavailable state (as WSL's "WSL unavailable" does), never a silent `return` or an endless spinner.
- A remote that goes away is a suspension, not a failure: keep what the user had (for example the browser's tab inventory) and resume when it returns, without a retry timer.
- A remote call can reject while the event that explains it is still in flight; events and call replies travel on different channels. When main reports an outcome as an event, let the event decide, and treat a rejection as final only for errors main throws before any event (validation, missing endpoint).
- The host orders remote state for you: an event always wins over an older snapshot. Do not re-fetch state to fix ordering.
## Stored state
- Desktop storage loads over IPC; web storage is synchronous, so a read before load passes every web e2e test and still breaks desktop. Check the store's `ready` accessor before reading values, deriving requests from them, or writing.
- Moving a stored value goes through `from` (and `from.sessions` for one session's slice of an app key). Keep the old field readable until every user has migrated; never drop user data.
- `MainStorage` writes reach disk at once; do not batch them yourself.
## Panels and layout
- `Panel.focus(tab, view, { restored })`: `restored` is true only for the selection the side region mounts with (for example after a reload). Run side effects that express user intent, such as switching the file tree's tab, only when it is false.
- The host selects a fallback tab while the stored one is not listed. If you will restore a tab, keep listing its stored id, with `hidden: true` while its content is not known yet, so the fallback never runs.
- Map keys stored before extensions with `Panel.legacy`. A `transient` panel's stored keys are dropped once it stops listing them.
- `Layout.stored(session)` returns your stored tab ids. Layout reads and writes do nothing while `session.location` is undefined (for example after a server re-authenticates), so hold writes, and anything that records them as done such as a mirror of external state, until the location is known.
- Narrow screens: a plain open switches to the panel's mobile view and closes the dock. Pass `background` when the user stays where they are (a palette pick, a composer chip) or the agent opened the tab, and `select` to append and select without replacing the preview.
## Solid
Read [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) and [Solid.js Best Practices](https://www.brenelz.com/posts/solid-js-best-practices/) before writing reactive code here. In short:
- Derive, don't sync. A value computed from other state is a `createMemo` or a plain function, never a `createEffect` that calls a setter. Never mirror state into a second store, signal, or `Map` through an effect.
- An effect synchronizes with something outside Solid: the DOM, a third-party widget, a native surface, a remote subscription, a chunk preload. Comment what it syncs with when that isn't obvious.
- Logic caused by a user action belongs in that action's handler, not in an effect that watches the state the action changed. If several handlers share it, call one function from each.
- Reset state on an identity change by keying the subtree (`<Show keyed>`) or by storing an id and deriving the selection from it. To forget a selection when the user navigates away and back, derive a visit token (`createMemo(on(key, () => ({})))`) and keep the selection only while its token matches. Never reset state in an effect.
- No effect chains, and no effect that notifies a parent: update everything in the same handler or `batch`.
- Don't destructure props; read `props.x` so the getter stays reactive (`splitProps` when you must). Call signals when passing them as JSX props. Use `<Show>` and `<For>`, not `&&` and `.map`, in JSX.
- Use `createStore` for objects and collections, `createSignal` for single values.
- Async data goes through a resource or a guarded promise that ignores stale results, under the Suspense rules below, never an effect that fetches and then sets.
## Performance
- `renderer.tsx` loads with the app: `src/renderer.ts` imports every built-in entry eagerly, because the window renders once all of them are active. Keep it small and put heavy UI behind `lazy()`. Preload a chunk with `onIdle(() => void Chunk.preload())` from setup, so it compiles while the app idles (e.g. on Home) instead of when a session first opens. Never import a heavy chunk on the startup path.
- A `<Suspense>` boundary decides what blanks when something beneath it suspends; it does not stop the suspension. Suspend at most once, on first load, and never after first paint:
- Read async data through `.latest`, a store with an explicit `loading` flag, or a resource that resolves once (`packages/app/src/runtime/server/runtime.tsx`, `packages/app/src/providers/connect/controller.ts`). Never read a refetching `resource()` in render: a refetch blanks the nearest boundary, and without one of yours that is the whole session route, which detaches the screen and resets the timeline scroll.
- Wrap each `lazy()` component in its own `<Suspense>`, so a chunk that is still loading blanks only your area. Preload it with `onIdle`. If the area can be visible at startup (for example a dock restored open), start the preload in `setup` from the stored state instead, so the first render never enters Suspense and nothing pops in.
- Give a fallback the area's size when the area has fixed geometry, so nothing shifts; leave it empty otherwise.
- Make a state change that may suspend inside `startTransition`, so the current content stays until the next is ready.
- Keep `renderer.tsx` small and put heavy UI behind `lazy()`. Preload a chunk with `onIdle(() => void Chunk.preload())` from setup, so it compiles while the app idles (e.g. on Home) instead of when a session first opens. Never import it on the startup path.
- Return stable objects from `Panel.list` and reactive contributions, so the host never remounts a trigger or a panel.
- Never add work to timeline rows. The session header slot is the only timeline surface.
@@ -79,4 +31,3 @@ Read [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-a
- Unit-test pure logic that carries a contract: path and security checks, storage migration, protocol parsing, archive validation.
- UI behavior is proven by the app e2e keeper suites. Do not add unit tests that repeat them.
- Test a main-process entry through its `Remote` contract with real inputs, not through Electron mocks.
- For lifetime and ordering contracts, drive the race the user hits: a reload during async setup, a rejection that beats its event, a store that loads late (the e2e fixtures can hold desktop storage reads), a remote that goes away and returns.
@@ -29,8 +29,7 @@ afterEach(() => {
})
// The client stands in for the pane's main entry: each register is one binding with its own events.
// `strip` stands in for the browser tab IDs the session's layout stores. While `owner.located` is false the strip
// cannot be written, so the owner holds the latest mirror, as the model does until the session's location loads.
// `strip` stands in for the browser tab IDs the session's layout stores.
function fixture(strip: string[] = []) {
const states: State[] = []
const listeners = new Map<string, (event: PaneEvent) => void>()
@@ -46,7 +45,6 @@ function fixture(strip: string[] = []) {
// The remote is gone while the pane's main extension reloads or is disabled.
// An endpoint main cannot resolve, e.g. an SSH server's while it reconnects, makes register reject.
const remote: { available: boolean; reject?: Error } = { available: true }
const owner: { located: boolean; held?: () => void } = { located: true }
const client: Client = {
register: async (input) => {
calls.push({ input, commands: [] })
@@ -75,8 +73,7 @@ function fixture(strip: string[] = []) {
target: () => ({ ...target }),
change: (state, mirror) => {
states.push(state)
if (owner.located) return mirror()
owner.held = mirror
mirror()
},
strip: {
stored: () => strip,
@@ -94,7 +91,7 @@ function fixture(strip: string[] = []) {
const emit = (index: number, event: PaneEvent) => listeners.get(calls[index].input.binding)?.(event)
connection.wake()
emit(0, { type: "state", state: browser })
return { connection, calls, states, target, remote, owner, routed, highlights, closed, listeners, emit, strip }
return { connection, calls, states, target, remote, routed, highlights, closed, listeners, emit, strip }
}
const element = {
@@ -214,25 +211,6 @@ test("only a native inventory, the first one included, closes stored tabs the de
}
})
test("a mirror held while the strip cannot be written still adds new tabs and prunes once it lands", () => {
const added = Browser.TabID.make(`tab_${crypto.randomUUID()}`)
const stale = `tab_${crypto.randomUUID()}`
const app = fixture()
try {
app.owner.located = false
app.strip.push(stale)
app.emit(0, { type: "state", state: { ...browser, tabs: [...browser.tabs, { ...browser.tabs[0], id: added }] } })
// A later report replaces the held mirror before the strip can be written.
app.emit(0, { type: "surface", tabID: added, surface: "surface-2" })
expect(app.strip).toEqual([tabID, stale])
app.owner.located = true
app.owner.held?.()
expect(app.strip).toEqual([tabID, added])
} finally {
app.connection.dispose()
}
})
test("a rejected registration clears itself and retries with its tabs after the backoff", async () => {
jest.useFakeTimers()
const app = fixture()
@@ -279,26 +257,12 @@ test("a command wakes its attachment once and is not replayed", async () => {
}
})
// Main rejects the register of a binding it closes, and that reply can overtake the closed-state event.
test.each([
{ error: "browser.pane.replaced", rejected: false },
{ error: "browser.pane.unsupported", rejected: false },
{ error: "browser.pane.replaced", rejected: true },
{ error: "browser.pane.unsupported", rejected: true },
])("$error blocks automatic ownership recovery (register rejected first: $rejected)", async ({ error, rejected }) => {
jest.useFakeTimers()
test.each(["browser.pane.replaced", "browser.pane.unsupported"])("%s blocks automatic ownership recovery", (error) => {
const app = fixture()
try {
if (rejected) {
app.emit(0, { type: "state", state: browser, error: "browser.pane.suspended" })
app.remote.reject = new Error("browser.pane.registration.closed")
app.connection.wake()
await Promise.resolve()
}
app.emit(app.calls.length - 1, { type: "state", state: null, error })
jest.advanceTimersByTime(30_000)
app.emit(0, { type: "state", state: null, error })
app.connection.wake()
expect(app.calls).toHaveLength(rejected ? 2 : 1)
expect(app.calls).toHaveLength(1)
expect(app.states.at(-1)?.error).toBe(error)
} finally {
app.connection.dispose()
@@ -29,10 +29,7 @@ export function createConnection(input: {
client: () => Client | undefined
listen: (binding: string, listener: (event: PaneEvent) => void) => () => void
target: () => { server: string; session: string }
/**
* `mirror` applies the current tabs to the strip; call it right after storing the state, in the same batch, or
* later once the strip can be written. A mirror that never runs leaves the strip's tabs as they are.
*/
/** `mirror` applies the state's tabs to the strip; call it right after storing the state, in the same batch. */
change: (state: ConnectionState, mirror: () => void) => void
/** The session's strip: the browser tab IDs it stores, and quietly adding or removing one. */
strip: {
@@ -51,36 +48,30 @@ export function createConnection(input: {
let retry: ReturnType<typeof setTimeout> | undefined
// A registration was wanted while the pane's remote was gone; it registers once the remote is back.
let lost = false
// Tab IDs of the last inventory the strip received, to tell new tabs from ones the user just closed.
// Tab IDs of the last inventory, to tell new tabs from ones the user just closed.
let known: readonly Browser.TabID[] | undefined
// A native inventory the strip has not received yet; the next mirror that runs prunes with it.
let pruning = false
// Mirrors the tabs into the strip, applied after the state so closing a strip tab already finds the desktop's answer.
// Only tabs new since the last mirrored inventory are added, so a tab the user just closed is not reopened before
// the desktop confirms. Only a native inventory removes: it closes every stored tab it lacks, so the desktop decides
// which tabs exist, while suspended, unavailable, and restoring states keep the tabs they will restore.
const mirror = () => {
// Reports the state with a mirror of its tabs into the strip, applied after the state so closing a strip tab already
// finds the desktop's answer. Only tabs new since the last inventory are added, so a tab the user just closed is not
// reopened before the desktop confirms. Only a native inventory removes: it closes every stored tab it lacks, so the
// desktop decides which tabs exist, while suspended, unavailable, and restoring states keep the tabs they will restore.
const publish = (native = false) => {
const previous = new Set<string>(known ?? [])
const ids = state.browser?.tabs.map((tab) => tab.id)
const prune = pruning
known = ids
pruning = false
if (!ids) return
if (prune) {
const listed = new Set<string>(ids)
input.strip
.stored()
.filter((tabID) => !listed.has(tabID))
.forEach((tabID) => input.strip.close(tabID))
}
ids.forEach((tabID) => {
if (!previous.has(tabID)) input.strip.open(tabID)
input.change({ ...state }, () => {
if (!ids) return
if (native) {
const listed = new Set<string>(ids)
input.strip
.stored()
.filter((tabID) => !listed.has(tabID))
.forEach((tabID) => input.strip.close(tabID))
}
ids.forEach((tabID) => {
if (!previous.has(tabID)) input.strip.open(tabID)
})
})
}
const publish = (native = false) => {
pruning ||= native
input.change({ ...state }, mirror)
}
// The pane itself is unreachable while its main extension restarts or is disabled, and main drops every
// binding without reporting it. Keep the tabs, like an idle eviction, and register again when it returns.
const suspend = (registration: Registration) => {
@@ -154,12 +145,7 @@ export function createConnection(input: {
state.error = event.error
publish(true)
},
(error) => {
if (unavailable(error)) return suspend(registration)
// Main closed a binding it took; its closed-state event decides, whichever of the two arrives first.
if (error instanceof Error && error.message === "browser.pane.registration.closed") return
reopen(registration)
},
(error) => (unavailable(error) ? suspend(registration) : reopen(registration)),
)
state.registration = registration
state.surfaces = {}
+5 -44
View File
@@ -1,4 +1,4 @@
import { batch, createEffect, createRoot, createSignal, getOwner, on, onCleanup, runWithOwner, untrack } from "solid-js"
import { batch, createEffect, createSignal, getOwner, on, onCleanup, runWithOwner, untrack } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
import { makeEventListener } from "@solid-primitives/event-listener"
import type { Browser } from "@opencode/plugin-browser/rpc"
@@ -24,8 +24,6 @@ type Live = {
connection: Connection
registration?: Registration
revision: number
/** Strip writes waiting for the session's location, without which the strip cannot be written. */
held: { mirror?: () => void; focus?: Browser.TabID }
dispose: () => void
}
@@ -79,7 +77,6 @@ export function createModel(ctx: Context) {
const entry: Live = {
ref,
revision: 0,
held: {},
dispose: () => undefined,
connection: createConnection({
client,
@@ -92,13 +89,7 @@ export function createModel(ctx: Context) {
target: () => ({ server: ref.server.id, session: ref.id }),
// Focus requests write to the owning session's panel even while another shell tab is routed,
// so the side panel and browser tab are already selected when the user returns to it.
focus: (tabID) => {
if (!ref.location) {
entry.held.focus = tabID
return
}
layout.open(key(tabID), ref, { select: true })
},
focus: (tabID) => layout.open(key(tabID), ref, { select: true }),
preview: (path) => preview(ref, path),
inspect: (event) => inspectors.get(id)?.forEach((listener) => listener(event)),
change: (next, mirror) => {
@@ -128,8 +119,7 @@ export function createModel(ctx: Context) {
}),
)
// After the store: closing a strip tab asks this model whether the desktop still has it.
if (ref.location) return mirror()
entry.held.mirror = mirror
mirror()
})
},
strip: {
@@ -154,25 +144,8 @@ export function createModel(ctx: Context) {
if (event.data.sessionID === ref.id) entry.connection.wake()
}),
])
// Mirrors the desktop's inventory and focus requests into the strip: writes held while the session's location was
// unknown land once the server reports it.
const unwatch = createRoot((dispose) => {
createEffect(() => {
if (!ref.location) return
untrack(() =>
batch(() => {
const held = entry.held
entry.held = {}
held.mirror?.()
if (held.focus) layout.open(key(held.focus), ref, { select: true })
}),
)
})
return dispose
}, owner)
if (!ref.pending) entry.connection.wake()
entry.dispose = () => {
unwatch()
unsubscribe?.forEach((dispose) => dispose())
entry.connection.dispose()
}
@@ -227,13 +200,6 @@ export function createModel(ctx: Context) {
const value = attachment(session)
return value?.registration !== undefined || !!value?.browser
}
// The desktop has not answered with its first inventory yet: the routed session is about to attach, or it registered
// and waits.
const pending = (session: SessionRef) => {
const value = attachment(session)
if (!value) return !state.unsupported[session.server.id] && session.server.compatible && !session.pending
return value.registration !== undefined && !value.browser && !value.error
}
const available = (session: SessionRef) =>
!state.unsupported[session.server.id] && !!session.id && session.server.compatible && !layout.narrow()
const tab = (session: Session, tabID: string) => attachment(session)?.browser?.tabs.find((item) => item.id === tabID)
@@ -297,13 +263,8 @@ export function createModel(ctx: Context) {
open(session: SessionRef) {
if (available(session)) command(session, { type: "tabs.open" })
},
pending,
/**
* Tab IDs to list in the side panel: the desktop's inventory, limited to tabs stored in the strip. Until the first
* inventory arrives, the stored tabs, so a restored selection holds; that inventory then prunes the ones it lacks.
*/
tabs(session: SessionRef, open: readonly string[]) {
if (pending(session)) return open
/** Tab IDs to list in the side panel: the desktop's inventory, limited to tabs stored in the strip. */
tabs(session: Session, open: readonly string[]) {
if (!attached(session)) return []
return attachment(session)?.browser?.tabs.flatMap((item) => (open.includes(item.id) ? [item.id] : [])) ?? []
},
@@ -1,43 +1,23 @@
import { Browser } from "@opencode/plugin-browser/rpc"
import { DialogProvider } from "@opencode/ui/context/dialog"
import {
batch,
createSignal,
For,
Show,
type Accessor,
type Component,
type JSX,
type ParentComponent,
type ParentProps,
} from "solid-js"
import { createStore, produce } from "solid-js/store"
import { For, Show, type Component, type JSX, type ParentComponent } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal, render } from "solid-js/web"
import type { Bridge, BridgeLayout } from "../sdk/bridge"
import {
App,
ExtensionContext,
Layout,
Native,
PanelContext,
Sessions,
Storage,
Surfaces,
type ComposerNote,
type Context,
type Definition,
type PanelFrame,
type PanelTab,
type Remote,
type RemoteClient,
type SessionRef,
type SessionView,
} from "../sdk"
import type { InspectEvent } from "./connection"
import browserEn from "./i18n/en"
import type { Model } from "./model"
import SessionBrowserPane from "./panel"
import { BrowserPane, type PaneEvent } from "./remote"
/** The renderer host pieces the pane runs on, passed in by `packages/app/component-tests/browser-pane.spec.ts`. */
type Host = {
@@ -292,272 +272,3 @@ export function mountBrowserPane(input: Host) {
host,
)
}
type StripTabs = {
all(): string[]
active(): string | undefined
setAll(all: string[]): void
setActive(tab: string | undefined): void
close(tab: string): void
remap(rewrite: (tab: string) => string): void
}
/** The app's extension host and side region, passed in by `packages/app/component-tests/browser-pane-restore.spec.ts`. */
type RegionHost = {
LanguageProvider: Component<{ locale: string; children: JSX.Element }>
ExtensionHostProvider: Component<
ParentProps<{
definitions: readonly Definition[]
disabled: Accessor<ReadonlySet<string> | undefined>
services: readonly {
readonly token: { readonly kind: "host"; readonly id: string }
create(extension: string): unknown
}[]
remote: (token: Remote) => unknown
}>
>
useExtensionHost(): { ready(): boolean }
createRegion(input: { region: "side"; view: SessionView; tabs: Accessor<StripTabs> }): {
keys(): readonly string[]
active(): string | undefined
entry(key: string): { readonly tab: PanelTab } | undefined
}
/** The real browser and file extensions. */
definitions: readonly Definition[]
}
// Component-test fixture: the real side region over the real browser and file extensions, with the host's
// session, layout, and storage services and the pane's main-process remote faked at their boundaries. Alpha was
// left on a file tab; Beta on a browser tab whose page the desktop reports only with its first inventory.
export function mountBrowserRegion(input: RegionHost) {
const host = document.createElement("main")
host.dataset.testid = "browser-region-fixture"
host.style.cssText = "position:fixed;inset:0;z-index:1000;background:#181818;color:#eee;padding:24px"
document.body.appendChild(host)
function Fixture() {
const alpha = "browser-test\nses_alpha"
const beta = "browser-test\nses_beta"
const tabID = Browser.TabID.make("tab_33333333-3333-3333-3333-333333333333")
const [store, setStore] = createStore({
session: alpha,
strips: {
[alpha]: { all: ["file://alpha.ts"], active: "file://alpha.ts" },
[beta]: { all: ["file://beta.ts", `browser:${tabID}`], active: `browser:${tabID}` },
} as Record<string, { all: string[]; active?: string }>,
registrations: [] as { binding: string; session: string }[],
})
// The file tree's state, Changes or All files, as the file extension stores it.
const [tree, setTree] = createSignal<object>()
const strip = (session: string) => store.strips[session] ?? { all: [] }
const setAll = (session: string, all: string[]) => setStore("strips", session, "all", all)
const close = (session: string, key: string) =>
batch(() => {
setAll(
session,
strip(session).all.filter((item) => item !== key),
)
if (strip(session).active === key) setStore("strips", session, "active", undefined)
})
const server = {
id: "browser-test",
name: "Browser test",
url: "http://127.0.0.1:4096",
data: { on: () => () => undefined },
local: true,
builtin: true,
compatible: true,
connected: true,
}
const location = { directory: "/repo" }
const refs = [alpha, beta].map(
(key) => ({ key, id: key.split("\n")[1], tab: key, server, pending: false, location }) as unknown as SessionRef,
)
const view = {
get key() {
return store.session
},
get id() {
return store.session.split("\n")[1]
},
get tab() {
return store.session
},
server,
pending: false,
location,
directory: "/repo",
local: true,
background: [],
file: {
root: "/repo",
ready: () => false,
resolve: (path: string) => path.replace(/^file:\/\//, ""),
absolute: () => false,
get: () => undefined,
missing: () => false,
sync: async () => undefined,
search: async () => [],
},
} as unknown as SessionView
const layout = (extension: string): Layout => ({
narrow: () => false,
ready: () => true,
open(key, session, options) {
if (!strip(session.key).all.includes(key)) setAll(session.key, [...strip(session.key).all, key])
if (options?.focus !== false) setStore("strips", session.key, "active", key)
},
close: (key, session) => close(session.key, key),
toggle() {},
state(key, session) {
if (strip(session.key).active === key) return "visible"
return strip(session.key).all.includes(key) ? "open" : "closed"
},
stored: (session) =>
strip(session.key).all.flatMap((key) =>
key.startsWith(`${extension}:`) ? [key.slice(extension.length + 1)] : [],
),
side: { opened: () => true, toggle() {} },
dock: { opened: () => false, placement: () => "side" },
scroll: { get: () => undefined, set() {} },
settings() {},
project() {},
})
const keep = <T extends object>(key: string, initial: T) => {
const [value, set] = createStore<T>(structuredClone(initial))
if (key === "file:tree") setTree(() => value)
return [value, (mutation: (draft: T) => void) => set(produce(mutation))] as const
}
const storage = (extension: string): Storage => ({
store: (key, options) => [...keep(`${extension}:${key}`, options.initial), () => true] as const,
memory: (key, options) => keep(`${extension}:${key}`, options.initial),
remove() {},
})
const app: App = {
channel: "dev",
platform: "desktop",
font: () => "monospace",
locale: () => "en",
direction: () => "ltr",
setDirection() {},
routing: () => false,
path: () => "/",
keybind: () => [],
keys: (bind) => bind.split("+"),
matches: () => false,
servers: () => [server.id],
on: () => () => undefined,
}
const listeners = new Set<(value: { binding: string; event: PaneEvent }) => void>()
const pane: RemoteClient<(typeof BrowserPane)["spec"]> = {
register: async (value) => {
setStore("registrations", (items) => [...items, { binding: value.binding, session: value.session }])
},
load: async () => undefined,
command: async () => undefined,
inspect: async () => undefined,
highlight: async () => undefined,
close: async () => undefined,
state: () => undefined,
on: (_name, listener) => {
// SAFETY: the pane's remote has one event, so every listener takes that event's payload.
const added = listener as (value: { binding: string; event: PaneEvent }) => void
listeners.add(added)
return () => void listeners.delete(added)
},
}
const services = [
{ token: App, create: () => app },
{ token: Native, create: () => undefined },
{ token: Sessions, create: () => ({ list: () => refs, current: () => view }) },
{ token: Layout, create: layout },
{ token: Storage, create: storage },
]
// The desktop answers Beta's latest registration with the tab it restored.
const inventory = () => {
const binding = store.registrations.filter((item) => item.session === "ses_beta").at(-1)?.binding
if (!binding) return
const tab = {
id: tabID,
url: "http://localhost:4173/",
title: "Preview",
loading: false,
canGoBack: false,
canGoForward: false,
generation: 0,
}
listeners.forEach((listener) =>
listener({ binding, event: { type: "state", state: { tabs: [tab], focusedTabID: tabID } } }),
)
}
// Mounts once every extension is active, as the session screen does.
function Strip() {
const region = input.createRegion({
region: "side",
view,
tabs: () => ({
all: () => strip(view.key).all,
active: () => strip(view.key).active,
setAll: (all) => setAll(view.key, all),
setActive: (tab) => setStore("strips", view.key, "active", tab),
close: (tab) => close(view.key, tab),
remap(rewrite) {
const all = strip(view.key).all
const next = Array.from(new Set(all.map(rewrite)))
if (next.length !== all.length || next.some((key, index) => key !== all[index])) setAll(view.key, next)
},
}),
})
return (
<>
{/* The host strip draws a trigger for each of these keys. */}
<div role="tablist" aria-label="Side panel" style={{ display: "flex", gap: "8px", margin: "16px 0" }}>
<For each={region.keys()}>
{(key) => (
<span role="tab" aria-selected={region.active() === key} style={{ padding: "4px 8px" }}>
{region.entry(key)?.tab.title}
</span>
)}
</For>
</div>
<p data-testid="selected">{region.active() ?? "none"}</p>
</>
)
}
function Ready(props: ParentProps) {
const extensions = input.useExtensionHost()
return <Show when={extensions.ready()}>{props.children}</Show>
}
return (
<DialogProvider>
<input.ExtensionHostProvider
definitions={input.definitions}
disabled={() => new Set<string>()}
services={services}
remote={(token) => (token.id === BrowserPane.id ? pane : undefined)}
>
<h1 style={{ "font-size": "24px", "margin-bottom": "16px" }}>Restored side strip</h1>
<nav style={{ display: "flex", gap: "12px", margin: "16px 0" }}>
<button onClick={() => setStore("session", beta)}>Beta</button>
<button onClick={inventory}>First inventory</button>
</nav>
<p>Registrations: {store.registrations.length}</p>
<p data-testid="tree">{JSON.stringify(tree())}</p>
<Ready>
<Strip />
</Ready>
</input.ExtensionHostProvider>
</DialogProvider>
)
}
return render(
() => (
<input.LanguageProvider locale="en">
<Fixture />
</input.LanguageProvider>
),
host,
)
}
@@ -123,10 +123,6 @@ const setup: Setup = (ctx) => {
<span class="max-w-40 truncate">{text()}</span>
</div>
),
// Listed only to hold the restored selection until the desktop's first inventory names the tab.
get hidden() {
return !model()?.tab({ key: session }, id)
},
group: "browser",
dom: { tab: `session-side-panel-browser-tab-${id}`, panel: "session-side-panel-browser-tabpanel" },
}
@@ -137,8 +133,7 @@ const setup: Setup = (ctx) => {
id: "main",
region: "side",
list(session, open) {
// While the model loads, the stored tabs hold the strip and its selection, as before the first inventory.
const ids = model()?.tabs(session, open) ?? open
const ids = model()?.tabs(session, open) ?? []
if (ids.length === 0) {
tabs.delete(session.key)
return []
@@ -148,15 +143,12 @@ const setup: Setup = (ctx) => {
tabs.set(session.key, next)
return [...next.values()]
},
// The pane shows nothing until the desktop's first inventory names its tabs.
render: (tab, session) => (
<Show when={model()}>
{(value) => (
<Show when={!value().pending(session)}>
<Suspense>
<SessionBrowserPane tab={tab} session={session} model={value()} />
</Suspense>
</Show>
<Suspense>
<SessionBrowserPane tab={tab} session={session} model={value()} />
</Suspense>
)}
</Show>
),
+7 -13
View File
@@ -75,12 +75,8 @@ const setup: Setup = (ctx) => {
const state = layout.state(`file:${id}`, session)
return state === "active" || state === "visible"
}
const open = (
session: SessionView,
path: string,
options?: { readonly preview?: boolean; readonly background?: boolean },
) => {
layout.open(key(session, path), session, options)
const open = (session: SessionView, path: string, options?: { readonly preview?: boolean }) => {
layout.open(key(session, path), session, options?.preview ? { preview: true } : undefined)
void session.file.sync(path)
}
@@ -198,7 +194,7 @@ const setup: Setup = (ctx) => {
list(session, stored) {
const cache = tabsOf(session)
return stored.flatMap((id) => {
if (id === OPEN) return [launcher]
if (id === OPEN) return layout.narrow() ? [] : [launcher]
if (!isFileTab(id)) return []
const existing = cache.get(id)
if (existing) return [existing]
@@ -343,7 +339,7 @@ const setup: Setup = (ctx) => {
const path = session.file.resolve(link.href)
if (!path) return
batch(() => {
open(session, path, { background: link.background })
open(session, path)
shared.tree.setTab("all")
})
return
@@ -355,15 +351,13 @@ const setup: Setup = (ctx) => {
pane.open(session, workspaceFileUrl(session.file.root, path))
return
}
// Agent-driven opens (browser previews) select the tab without switching the narrow-screen view.
const background = layout.narrow() && link.background
// Inline paths are guessed from text, so confirm the file exists before a tab appears for it.
// Always reread: V2 publishes no workspace file change events, so a cached copy can be stale.
void session.file.sync(path, { force: true }).then(() => {
if (!session.file.get(path)?.loaded) return
batch(() => {
layout.open(key(session, path), session, { background: link.background })
// A tapped link switches the narrow-screen view; the side region still opens for when the window is wide.
if (layout.narrow() && !layout.side.opened(session)) layout.side.toggle(session)
})
layout.open(key(session, path), session, background ? { select: true } : undefined)
})
},
})
+13 -29
View File
@@ -1,45 +1,29 @@
import type { Definition, Setup } from "./sdk"
import type { Definition } from "./sdk"
import usage from "./usage"
import usageRenderer from "./usage/renderer"
import btw from "./btw"
import btwRenderer from "./btw/renderer"
import debug from "./debug"
import debugRenderer from "./debug/renderer"
import terminal from "./terminal"
import terminalRenderer from "./terminal/renderer"
import file from "./file"
import fileRenderer from "./file/renderer"
import review from "./review"
import reviewRenderer from "./review/renderer"
import summary from "./summary"
import summaryRenderer from "./summary/renderer"
import browser from "./browser"
import browserRenderer from "./browser/renderer"
import pairing from "./pairing"
import pairingRenderer from "./pairing/renderer"
import updater from "./updater"
import updaterRenderer from "./updater/renderer"
import ssh from "./ssh"
import sshRenderer from "./ssh/renderer"
import wsl from "./wsl"
import wslRenderer from "./wsl/renderer"
// The window renders once every built-in is active, so the small renderer entries load with the app, like the
// features they replaced. Heavy UI stays behind `lazy()` inside them.
const eager = (setup: Setup) => () => Promise.resolve({ default: setup })
/** Built-in extensions with their renderer entries. The only place host builds name extensions. */
export const builtins: readonly Definition[] = [
{ ...usage, renderer: eager(usageRenderer) },
{ ...btw, renderer: eager(btwRenderer) },
{ ...debug, renderer: eager(debugRenderer) },
{ ...terminal, renderer: eager(terminalRenderer) },
{ ...file, renderer: eager(fileRenderer) },
{ ...review, renderer: eager(reviewRenderer) },
{ ...summary, renderer: eager(summaryRenderer) },
{ ...browser, renderer: eager(browserRenderer) },
{ ...pairing, renderer: eager(pairingRenderer) },
{ ...updater, renderer: eager(updaterRenderer) },
{ ...ssh, renderer: eager(sshRenderer) },
{ ...wsl, renderer: eager(wslRenderer) },
{ ...usage, renderer: () => import("./usage/renderer") },
{ ...btw, renderer: () => import("./btw/renderer") },
{ ...debug, renderer: () => import("./debug/renderer") },
{ ...terminal, renderer: () => import("./terminal/renderer") },
{ ...file, renderer: () => import("./file/renderer") },
{ ...review, renderer: () => import("./review/renderer") },
{ ...summary, renderer: () => import("./summary/renderer") },
{ ...browser, renderer: () => import("./browser/renderer") },
{ ...pairing, renderer: () => import("./pairing/renderer") },
{ ...updater, renderer: () => import("./updater/renderer") },
{ ...ssh, renderer: () => import("./ssh/renderer") },
{ ...wsl, renderer: () => import("./wsl/renderer") },
]
+7 -19
View File
@@ -70,19 +70,10 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
: undefined,
),
)
// Desktop loads the store asynchronously. Until it has, its defaults are not the session's choice: nothing shows
// them, requests their diff, or writes over the stored state.
const stored = () => {
const value = saved()
return value?.[2]() ? value[0] : undefined
}
const update = (mutation: (draft: (typeof SessionState)["Type"]) => void) => {
const value = saved()
if (value?.[2]()) value[1](mutation)
}
const update = (mutation: (draft: (typeof SessionState)["Type"]) => void) => saved()?.[1](mutation)
// Memos, so the store a session switch reopens does not recompute the diffs, kinds and tree rows it feeds.
const mode = createMemo(() => stored()?.mode ?? "git")
const selectedFile = createMemo(() => stored()?.file)
const mode = createMemo(() => saved()?.[0].mode ?? "git")
const selectedFile = createMemo(() => saved()?.[0].file)
// After a session switch the review renders a frame later, so the switch paints first.
const generation = { value: 0, disposed: false }
@@ -142,8 +133,7 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
const turn = value === "turn"
return {
queryKey: turn ? turnKey() : ([...vcsKey(), value] as const),
// Desktop storage loads asynchronously; until this session's mode is known, a request would use the default.
enabled: !!stored() && view.server.connected && wantsReview() && !!view.project?.vcs,
enabled: view.server.connected && wantsReview() && !!view.project?.vcs,
refetchOnMount: "always" as const,
// A finished turn does not change on focus or filesystem events; refresh it when the session goes idle.
refetchOnWindowFocus: !turn,
@@ -215,7 +205,6 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
// A project without VCS never enables diffQuery, so its status stays "pending" forever.
const project = view.project
if (project && !project.vcs) return true
if (!stored()) return false
return !diffQuery.isPending
}
const loadDiff = async (path: string, version?: number): Promise<FileDiffInfo | undefined> => {
@@ -381,7 +370,7 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
requestAnimationFrame(() => attempt(0))
})
createEffect(() => {
if (!stored() || !view.server.connected || !view.project) return
if (!saved()?.[2]() || !view.server.connected || !view.project) return
const list = options()
const value = mode()
if (list.includes(value)) return
@@ -417,8 +406,7 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
return {
view,
activeFile,
// The mode picker waits for the stored mode.
canReview: () => !!view.project && !!stored(),
canReview: () => !!view.project,
comments: {
actions: commentActions,
add: addComment,
@@ -450,7 +438,7 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
noGit: createMemo(() => !!view.project && !view.project.vcs),
filter: () => state.filter,
setFilter: (value: string) => setState("filter", value),
open: () => stored()?.open ?? [],
open: () => saved()?.[0].open ?? [],
setOpen: (next: string[]) =>
update((draft) => {
const unique = Array.from(new Set(next))
+1 -4
View File
@@ -62,10 +62,7 @@ export interface MainApp {
readonly channel: string
readonly packaged: boolean
server(id: string): MainServer | undefined
/**
* Marks the app as quitting and disposes every other extension, then runs handoff (e.g. quitAndInstall) or
* relaunches. The caller stays active through the handoff and keeps running when it fails (the promise rejects).
*/
/** Flushes state and marks the app as quitting, then runs handoff (e.g. quitAndInstall) or relaunches. */
restart(handoff?: () => void | Promise<void>): Promise<void>
/** Writes to the desktop log file (included in exported debug logs). */
log(level: "debug" | "info" | "warn" | "error", message: string, data?: Record<string, unknown>): void
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more