mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-02 13:36:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b963d3a7a7 |
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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")({
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+3
-3
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+4
-10
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+3
-3
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+7
-7
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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,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,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
@@ -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", {
|
||||
|
||||
@@ -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") {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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,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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * as Identifier from "@opencode/schema/identifier"
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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* () {
|
||||
|
||||
@@ -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 },
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
@@ -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(
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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 () => {
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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") },
|
||||
]
|
||||
@@ -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))
|
||||
|
||||
@@ -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
Reference in new issue
Block a user