diff --git a/packages/ai/src/protocols/open-responses.ts b/packages/ai/src/protocols/open-responses.ts index 2d56648067c..129cc1441d0 100644 --- a/packages/ai/src/protocols/open-responses.ts +++ b/packages/ai/src/protocols/open-responses.ts @@ -454,6 +454,11 @@ export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protoco } }) +export const lowerTools = (tools: ReadonlyArray, adapter: ProviderAdapter) => + Effect.forEach(tools, (tool) => + tool.native !== undefined && adapter.nativeTool ? adapter.nativeTool(tool.native) : lowerTool(adapter.name, tool), + ) + export const lowerToolChoice = (protocolName: string, toolChoice: NonNullable) => ProviderShared.matchToolChoice(protocolName, toolChoice, { auto: () => "auto" as const, @@ -815,14 +820,7 @@ export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAd return { ...(yield* lowerConversation(projected.request, adapter)), ...lowerGeneration(request), - tools: - projected.tools.length === 0 - ? undefined - : yield* Effect.forEach(projected.tools, (tool) => - tool.native !== undefined && adapter.nativeTool - ? adapter.nativeTool(tool.native) - : lowerTool(adapter.name, tool), - ), + tools: projected.tools.length === 0 ? undefined : yield* lowerTools(projected.tools, adapter), tool_choice: allowedToolChoice(request) ?? (request.toolChoice ? yield* lowerToolChoice(adapter.name, request.toolChoice) : undefined), diff --git a/packages/ai/src/protocols/openai-responses.ts b/packages/ai/src/protocols/openai-responses.ts index 13f32d1e9d8..71140613213 100644 --- a/packages/ai/src/protocols/openai-responses.ts +++ b/packages/ai/src/protocols/openai-responses.ts @@ -5,7 +5,7 @@ import { Auth } from "../route/auth.js" import { Endpoint } from "../route/endpoint.js" import { Protocol } from "../route/protocol.js" import { HttpTransport } from "../route/transport/index.js" -import { LLMRequest, mergeJsonRecords, type ToolDefinition, type ToolEntry } from "../schema/index.js" +import { LLMRequest, type ToolDefinition, type ToolEntry } from "../schema/index.js" import { resolveEffortUpdates } from "../effort-updates.js" import { OpenResponses } from "./open-responses.js" import { OpenResponsesOptions } from "./utils/open-responses-options.js" @@ -135,11 +135,6 @@ export const CompactionTrigger = Schema.Struct({ type: Schema.Literal("compactio const CheckpointBody = Schema.Struct({ ...OpenAIResponsesBody.fields, input: Schema.Array(Schema.Union([OpenAIResponsesInputItem, CompactionTrigger])), - store: Schema.Literal(false), - prompt_cache_retention: optionalNull(Schema.String), - prompt_cache_options: optionalNull( - Schema.Struct({ mode: Schema.optional(Schema.String), ttl: Schema.optional(Schema.String) }), - ), }) const adapter = { @@ -191,6 +186,8 @@ const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (to } }) +const lowerTools = (request: LLMRequest) => Effect.forEach(request.tools, lowerToolEntry) + const lowerToolChoice = (toolChoice: NonNullable, tools: ReadonlyArray) => ProviderShared.matchToolChoice(NAME, toolChoice, { auto: () => "auto" as const, @@ -214,7 +211,7 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request: ...(yield* OpenResponses.lowerConversation(updates.request, adapter)), ...OpenResponses.lowerGeneration(request, { ...options, reasoningEffort: updates.effort }), context_management: management?.map((edit) => ({ type: edit.type, compact_threshold: edit.compactThreshold })), - tools: request.tools.length === 0 ? undefined : yield* Effect.forEach(request.tools, lowerToolEntry), + tools: request.tools.length === 0 ? undefined : yield* lowerTools(request), tool_choice: request.tools.length === 0 ? undefined @@ -226,7 +223,6 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request: const checkpointBody = { schema: CheckpointBody, from: Effect.fn("OpenAIResponses.checkpointBody")(function* (request: LLMRequest) { - const native = yield* fromRequest(LLMRequest.update(request, { toolChoice: undefined })) const overlay = request.http?.body // Complete history is required for stateless replay and SSE recovery. Raw input overrides bypass that contract. if ( @@ -237,18 +233,13 @@ const checkpointBody = { return yield* ProviderShared.invalidRequest( "Trigger compaction requires complete canonical history, not an input or continuation override", ) - return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(CheckpointBody))({ - ...mergeJsonRecords(native, overlay), - input: [...native.input, { type: "compaction_trigger" }], - stream: true, - store: false, - parallel_tool_calls: true, - tool_choice: undefined, - context_management: undefined, - text: undefined, - max_output_tokens: undefined, - max_tool_calls: undefined, - }) + if (overlay?.stream !== undefined && overlay.stream !== true) + return yield* ProviderShared.invalidRequest("Trigger compaction requires a streamed response") + const native = yield* fromRequest(request) + return { + ...native, + input: [...native.input, { type: "compaction_trigger" as const }], + } }), } @@ -330,7 +321,10 @@ export const transport = channelTransport({ }) export const route = Route.make({ - compact: { endpoint: ResponsesCompaction.make(adapter), trigger: ResponsesCheckpoint.make(checkpointBody) }, + compact: { + endpoint: ResponsesCompaction.make(adapter, lowerTools), + trigger: ResponsesCheckpoint.make(checkpointBody), + }, id: ADAPTER, provider: "openai", providerMetadataKey: "openai", diff --git a/packages/ai/src/protocols/utils/responses-checkpoint.ts b/packages/ai/src/protocols/utils/responses-checkpoint.ts index 6231d657959..7d4cf1ab14a 100644 --- a/packages/ai/src/protocols/utils/responses-checkpoint.ts +++ b/packages/ai/src/protocols/utils/responses-checkpoint.ts @@ -1,7 +1,7 @@ import { Effect, Schema, Stream } from "effect" import { Route, type RouteBody, type TriggerCompactOperation } from "../../route/client.js" import { Protocol } from "../../route/protocol.js" -import { CompactionCheckpointResponse, HttpOptions, LLMEvent, LLMRequest } from "../../schema/index.js" +import { CompactionCheckpointResponse, LLMEvent, LLMRequest } from "../../schema/index.js" import { OpenResponses } from "../open-responses.js" import { ProviderShared } from "../shared.js" @@ -109,12 +109,8 @@ export const make = (body: RouteBody): TriggerCompactOperation => transport: source.transport, }) const native = yield* body.from(request) - // The body builder already applied and validated overlays. Do not let transport reapply them. - const preparedRequest = LLMRequest.update(request, { - http: request.http === undefined ? undefined : new HttpOptions({ ...request.http, body: undefined }), - }) - const prepared = yield* route.prepareTransport(native, preparedRequest, options) - yield* route.streamPrepared(prepared, preparedRequest, { http: executor }, options).pipe(Stream.runDrain) + const prepared = yield* route.prepareTransport(native, request, options) + yield* route.streamPrepared(prepared, request, { http: executor }, options).pipe(Stream.runDrain) if (!result) return yield* ProviderShared.eventError(source.id, "Compaction response ended without a checkpoint") return result }) diff --git a/packages/ai/src/protocols/utils/responses-compaction.ts b/packages/ai/src/protocols/utils/responses-compaction.ts index 08975af4370..6c142e6f520 100644 --- a/packages/ai/src/protocols/utils/responses-compaction.ts +++ b/packages/ai/src/protocols/utils/responses-compaction.ts @@ -19,12 +19,18 @@ import { OpenResponses } from "../open-responses.js" import { JsonObject, optionalNull, ProviderShared } from "../shared.js" import { Media } from "../../media.js" +// /compact has a smaller wire contract than /responses; keep the request controls it accepts. const Body = Schema.Struct({ model: Schema.String, input: Schema.Array(Schema.Unknown), instructions: optionalNull(Schema.String), previous_response_id: optionalNull(Schema.String), service_tier: optionalNull(Schema.String), + reasoning: Schema.optional(JsonObject), + text: Schema.optional(JsonObject), + include: OpenResponses.coreFields.include, + parallel_tool_calls: OpenResponses.coreFields.parallel_tool_calls, + tools: Schema.optional(Schema.Array(JsonObject)), prompt_cache_key: optionalNull(Schema.String), prompt_cache_retention: optionalNull(Schema.String), prompt_cache_options: optionalNull( @@ -74,17 +80,27 @@ const Response = Schema.Struct({ usage: Schema.optional(Schema.StructWithRest(OpenResponses.OpenResponsesUsage, [JsonObject])), }) -export const make = (adapter: OpenResponses.ProviderAdapter): CompactOperation => +export const make = ( + adapter: OpenResponses.ProviderAdapter, + lowerTools: (request: LLMRequest) => Effect.Effect>, AIError>, +): CompactOperation => Effect.fn("ResponsesCompaction.execute")(function* (request, executor, options) { const route = request.model.route // The standalone compaction endpoint rejects histories containing configuration updates. const native = yield* OpenResponses.lowerConversation(stripEffortUpdates(request), adapter) + const generation = OpenResponses.lowerGeneration(request) + const tools = request.tools.length === 0 ? undefined : yield* lowerTools(request) const body = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Body))( mergeJsonRecords( { ...native, - service_tier: request.providerOptions?.serviceTier, - prompt_cache_key: ProviderShared.promptCacheKey(request), + service_tier: generation.service_tier, + reasoning: generation.reasoning, + text: generation.text, + include: generation.include, + parallel_tool_calls: generation.parallel_tool_calls, + tools, + prompt_cache_key: generation.prompt_cache_key, }, request.http?.body, ), diff --git a/packages/ai/src/protocols/xai-responses.ts b/packages/ai/src/protocols/xai-responses.ts index 00a059e1019..bb19ebd843a 100644 --- a/packages/ai/src/protocols/xai-responses.ts +++ b/packages/ai/src/protocols/xai-responses.ts @@ -50,7 +50,8 @@ const fromRequest = Effect.fn("XAIResponses.fromRequest")(function* (request: LL operation: "in-band-compaction", provider: request.model.provider, route: request.model.route.id, - message: "xAI requires explicit compaction through LLMClient.compact; automatic context management is not supported", + message: + "xAI requires explicit compaction through LLMClient.compact; automatic context management is not supported", }) return yield* decodeBody(yield* OpenResponses.fromRequestWithAdapter(request, adapter)) }) @@ -93,6 +94,8 @@ export const protocol = Protocol.make({ }, }) -export const compact = ResponsesCompaction.make(adapter) +export const compact = ResponsesCompaction.make(adapter, (request) => + OpenResponses.lowerTools(ProviderShared.flattenTools(request.tools), adapter), +) export * as XAIResponses from "./xai-responses.js" diff --git a/packages/ai/src/providers/openai-options.ts b/packages/ai/src/providers/openai-options.ts index ac5a1cdf8c1..7fb39834db6 100644 --- a/packages/ai/src/providers/openai-options.ts +++ b/packages/ai/src/providers/openai-options.ts @@ -32,10 +32,7 @@ const openAIProviderOptions = (options: OpenAIOptionsInput | undefined): Provide return result } -export const gpt5DefaultOptions = ( - modelID: string, - options: { readonly textVerbosity?: boolean } = {}, -): ProviderOptions | undefined => { +export const gpt5DefaultOptions = (modelID: string): ProviderOptions | undefined => { const id = modelID.toLowerCase() if (!id.includes("gpt-5") || id.includes("gpt-5-chat") || id.includes("gpt-5-pro")) return undefined return openAIProviderOptions({ @@ -47,27 +44,19 @@ export const gpt5DefaultOptions = ( // this, callers using the default model facade get reasoning summaries // they cannot replay statelessly. include: ["reasoning.encrypted_content"], - textVerbosity: - options.textVerbosity === true && id.includes("gpt-5.") && !id.includes("codex") && !id.includes("-chat") - ? "low" - : undefined, }) } -export const openAIDefaultOptions = ( - modelID: string, - options: { readonly textVerbosity?: boolean } = {}, -): ProviderOptions | undefined => - mergeProviderOptions(openAIProviderOptions({ store: false }), gpt5DefaultOptions(modelID, options)) +export const openAIDefaultOptions = (modelID: string): ProviderOptions | undefined => + mergeProviderOptions(openAIProviderOptions({ store: false }), gpt5DefaultOptions(modelID)) export const withOpenAIOptions = ( modelID: string, options: Options, - defaults: { readonly textVerbosity?: boolean } = {}, ): Omit & { readonly providerOptions?: ProviderOptions } => { return { ...options, - providerOptions: mergeProviderOptions(openAIDefaultOptions(modelID, defaults), options.providerOptions), + providerOptions: mergeProviderOptions(openAIDefaultOptions(modelID), options.providerOptions), } } diff --git a/packages/ai/src/providers/openai.ts b/packages/ai/src/providers/openai.ts index 17654f62d55..8dee9fe6ac2 100644 --- a/packages/ai/src/providers/openai.ts +++ b/packages/ai/src/providers/openai.ts @@ -100,7 +100,7 @@ export const configure = (input: Config = {}) => { const modelDefaults = defaults(input) const responses = (id: string | ModelID) => responsesRoute - .with(withOpenAIOptions(id, modelDefaults, { textVerbosity: true })) + .with(withOpenAIOptions(id, modelDefaults)) .model({ id }) const chat = (id: string | ModelID) => chatRoute.with(withOpenAIOptions(id, modelDefaults)).model({ diff --git a/packages/ai/test/fixtures/recordings/openai-responses-websocket/continues-a-tool-call-over-one-socket.json b/packages/ai/test/fixtures/recordings/openai-responses-websocket/continues-a-tool-call-over-one-socket.json index 1a549a18417..cef2a9b8b3c 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses-websocket/continues-a-tool-call-over-one-socket.json +++ b/packages/ai/test/fixtures/recordings/openai-responses-websocket/continues-a-tool-call-over-one-socket.json @@ -30,7 +30,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]}],\"instructions\":\"Call get_weather once, then reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":50}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]}],\"instructions\":\"Call get_weather once, then reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":50}" }, { "direction": "server", @@ -525,7 +525,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"function_call_output\",\"call_id\":\"call_hejtTYDa1IfLyNIzb3fq9gJs\",\"output\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}],\"instructions\":\"Call get_weather once, then reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":50,\"previous_response_id\":\"resp_0d9a44b6df400533016aa8c8e36de887d1be260913d131b2ca\"}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"function_call_output\",\"call_id\":\"call_hejtTYDa1IfLyNIzb3fq9gJs\",\"output\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}],\"instructions\":\"Call get_weather once, then reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":50,\"previous_response_id\":\"resp_0d9a44b6df400533016aa8c8e36de887d1be260913d131b2ca\"}" }, { "direction": "server", diff --git a/packages/ai/test/fixtures/recordings/openai-responses-websocket/reconstructs-full-context-after-reconnect.json b/packages/ai/test/fixtures/recordings/openai-responses-websocket/reconstructs-full-context-after-reconnect.json index cf3a5a72078..bfc89f85e08 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses-websocket/reconstructs-full-context-after-reconnect.json +++ b/packages/ai/test/fixtures/recordings/openai-responses-websocket/reconstructs-full-context-after-reconnect.json @@ -30,7 +30,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Alpha.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":30}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Alpha.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":30}" }, { "direction": "server", @@ -109,7 +109,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Alpha.\"}]},{\"type\":\"reasoning\",\"id\":\"rs_0a6277dd90b94da1016aa8c946e33487d1b725d8e9dc874d82\",\"summary\":[],\"encrypted_content\":\"gAAAAABqqMlHtG6yltzgW_UjUcYxvl2hoMkk7cSEH5SJMe9CR5gKIaCwCh4peUo8XZrd-EU-tPCXthv7JzHxYXGVG1fIgTJ7BjBoOh-jgd_oGlyCHj2jVPj7nVoB763tZHdyw_ovL3V7GpJ6VeLGIsRGqNTnBz47ZuKikTKrbTDupn6fT2wOM_69zwdg4-UVnoF2J9E_jIS0E5XRtRwOidqANl61HCwo7LV-Ut1aqCbXb-59vkrVGWPxD_8n4smf7Qjywc0FCH9zwuEDX4cPdqj3MmjvYPrn0jand9LKkAy9rblaKFJSQfVuritbHrdQrnF7hLxu7QCpehOzNXOkpNEqTmXnAZjfMc53hq-ahH6_KoyYlZpompwyGPngFeI2fKRqP8rlpDilD1BHBsh-bl7kXzI8HQ_jameXwPZ1La6gjtFkThXp53BD6BOx11SB9Nypqolu2at5rR32UYcGrYeGtTu-HmYGp0oHVFkHumTNuHKmGXV14dI9swgryfygLhX8EAJrWrjm3e8rvAkHKpAZ0IkmlCcp15UCFDKNeS560fQVRaKXWnQ7m0Ih3C1xG0ifJ89j27c8GHo1kAhEJk-lSB1-FZr9Ls_w7N772kmZ2a7LLswu3kNW78kPas_CtcOnBOHwE1DhcVh5YpxwNftOHZnK1v8NPNF8EWqio4ArZy1thMCzH5zXBNcFzgd8tePMFukBblSP8QGQoVYRqTne2sFoOZXjslXnDvEe-Ycj8X38zWgRiwAk7guOloFC7Se36KCDP357773Vah86gWCt55mSEyhVW_GF1oTuHvJ18GZsXcyN21scF1PSr8YaCM_jR7ZkU1GYXbQK2Y4oTAV9XDptQA5YzEREnn7muC_6v5ZTAglZF1lhn9Q0NwmylZEAXJdSGHaqXt1Hv-vQlprA_9m22vrreBOTLPnVK946J8absKrwfe-jK_1n_9YQR43uwH8XwFBFND0c4lICCQGbxwM8pX4ACWR0c19aORCYm-M5FrJsxmG29_aDVNhcvkoQ3mlP7ITQeqzkrjfytSwLb2BYpXYZKjEHNfV9j3JoxJobUkK5hrxXBhTvZzbVBnE0LSXQNwR-JAcOliP_jXBEeQ-28B-aGW8TI1vEP2i260QKgzOzPC-pOoFp0-vCvxojNO0kE16ECVTLfDNLNGSMZs9vekdLJP2akBp6PsUXsQUTbWO_wr1E2oNU7ctfMRxoh-yP0ZW2_xz_NjE72O5LF_6zuDVV7Q==\"},{\"type\":\"message\",\"id\":\"msg_0a6277dd90b94da1016aa8c947253887d184c150fcbcbcd8da\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Alpha.\"}],\"phase\":\"final_answer\",\"status\":\"completed\"},{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Beta.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":30}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Alpha.\"}]},{\"type\":\"reasoning\",\"id\":\"rs_0a6277dd90b94da1016aa8c946e33487d1b725d8e9dc874d82\",\"summary\":[],\"encrypted_content\":\"gAAAAABqqMlHtG6yltzgW_UjUcYxvl2hoMkk7cSEH5SJMe9CR5gKIaCwCh4peUo8XZrd-EU-tPCXthv7JzHxYXGVG1fIgTJ7BjBoOh-jgd_oGlyCHj2jVPj7nVoB763tZHdyw_ovL3V7GpJ6VeLGIsRGqNTnBz47ZuKikTKrbTDupn6fT2wOM_69zwdg4-UVnoF2J9E_jIS0E5XRtRwOidqANl61HCwo7LV-Ut1aqCbXb-59vkrVGWPxD_8n4smf7Qjywc0FCH9zwuEDX4cPdqj3MmjvYPrn0jand9LKkAy9rblaKFJSQfVuritbHrdQrnF7hLxu7QCpehOzNXOkpNEqTmXnAZjfMc53hq-ahH6_KoyYlZpompwyGPngFeI2fKRqP8rlpDilD1BHBsh-bl7kXzI8HQ_jameXwPZ1La6gjtFkThXp53BD6BOx11SB9Nypqolu2at5rR32UYcGrYeGtTu-HmYGp0oHVFkHumTNuHKmGXV14dI9swgryfygLhX8EAJrWrjm3e8rvAkHKpAZ0IkmlCcp15UCFDKNeS560fQVRaKXWnQ7m0Ih3C1xG0ifJ89j27c8GHo1kAhEJk-lSB1-FZr9Ls_w7N772kmZ2a7LLswu3kNW78kPas_CtcOnBOHwE1DhcVh5YpxwNftOHZnK1v8NPNF8EWqio4ArZy1thMCzH5zXBNcFzgd8tePMFukBblSP8QGQoVYRqTne2sFoOZXjslXnDvEe-Ycj8X38zWgRiwAk7guOloFC7Se36KCDP357773Vah86gWCt55mSEyhVW_GF1oTuHvJ18GZsXcyN21scF1PSr8YaCM_jR7ZkU1GYXbQK2Y4oTAV9XDptQA5YzEREnn7muC_6v5ZTAglZF1lhn9Q0NwmylZEAXJdSGHaqXt1Hv-vQlprA_9m22vrreBOTLPnVK946J8absKrwfe-jK_1n_9YQR43uwH8XwFBFND0c4lICCQGbxwM8pX4ACWR0c19aORCYm-M5FrJsxmG29_aDVNhcvkoQ3mlP7ITQeqzkrjfytSwLb2BYpXYZKjEHNfV9j3JoxJobUkK5hrxXBhTvZzbVBnE0LSXQNwR-JAcOliP_jXBEeQ-28B-aGW8TI1vEP2i260QKgzOzPC-pOoFp0-vCvxojNO0kE16ECVTLfDNLNGSMZs9vekdLJP2akBp6PsUXsQUTbWO_wr1E2oNU7ctfMRxoh-yP0ZW2_xz_NjE72O5LF_6zuDVV7Q==\"},{\"type\":\"message\",\"id\":\"msg_0a6277dd90b94da1016aa8c947253887d184c150fcbcbcd8da\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Alpha.\"}],\"phase\":\"final_answer\",\"status\":\"completed\"},{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Beta.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":30}" }, { "direction": "server", diff --git a/packages/ai/test/fixtures/recordings/openai-responses-websocket/recovers-from-explicit-continuation-rejection.json b/packages/ai/test/fixtures/recordings/openai-responses-websocket/recovers-from-explicit-continuation-rejection.json index acc508f35a6..46d4b9e9be2 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses-websocket/recovers-from-explicit-continuation-rejection.json +++ b/packages/ai/test/fixtures/recordings/openai-responses-websocket/recovers-from-explicit-continuation-rejection.json @@ -30,7 +30,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Ready.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":30}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Ready.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":30}" }, { "direction": "server", @@ -109,7 +109,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Recovered.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":30,\"previous_response_id\":\"resp_01cc0cda24c36acf016aa8ca3c1d3c87d1853283f43675e411\"}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Recovered.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":30,\"previous_response_id\":\"resp_01cc0cda24c36acf016aa8ca3c1d3c87d1853283f43675e411\"}" }, { "direction": "server", @@ -119,7 +119,7 @@ { "direction": "client", "kind": "text", - "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Ready.\"}]},{\"type\":\"reasoning\",\"id\":\"rs_01cc0cda24c36acf016aa8ca3ced8c87d1a14c5c6a2ced8544\",\"summary\":[],\"encrypted_content\":\"gAAAAABqqMo9M9B15LsV1CLsXpNJpI08GiwCAK97hTheQ2s1lAkKgARMs1HIIXVeAr-tbUq88yN51fiQE8HvGwdF9ZcNI6bZle8D2PS7m8O7UE7C1Z_HX_fz2f0pRHyRfWpVAT2gYQWIDeZu6UdgxbKPBW0lXWzlk_relHG5x6nXkYzZoEeVasqavWoyMMSX7cexe-IYJh6e_3DgRpOchueS8Z-70P0w5R83Ea7UXZQNhMA0yDEYu_td2PmE2Pd2PUTOB3mxF2pb1z7-2t6S0UryhHx0az7Gh2eT60GGUqz9CIZzNE_FX--tszeuO0eI92Cen5tirOUHBTyyDqE0eG26DRl_p_U-xDZwaQONbtYbvkvrj-G7FA2oZXxjJPHuQZsNgBslXS-H0KT1lx3Y8XJ9QMVjFLFaucFG64wCmXPfCH8dYtX_YqfYQR4lwNfiSbyJEX2oDvTVVD_aCJ9NRo7c0aCTtmKBvr6fvvAy3MAFxAp_Sm2nMx4P5GYO4qAmJDByywKw-VK1vHlv3NRmVsAgbArIFgm-axoCs2PLpvZjDqeQGPavaq8zKWTyZYqBsEzKZUtGOZfYjD4mud0Z08I4i2H4K-L00ccVauode3548ZipOIuslbhJxonQXsF6TFdW2Hj8E5JjoEr5IbmwHyI0PBcDWW5AmkjHLwr9v08mFppRoD-2wzPAd5igROAuUbvJhiQd2A-uOaohwMjdpFxrjyUqgGTlI5g7tmI1ceeQWms0bKm8Pd0wIqVM3Nq6YvV7XfEyeogfRMVUQezr_lES42ZMVAoKBSFzMysDwCFkhVNVclcTUpcUUbVp21FChG7Ag-xuq8Cl6OGLA8nWX1C0aCf2HNa-n3dkYr1DtUziurh1MD-UIs5jdGiq3ptrc0VaVZwNdD4jVfAoHB_Ws7GiISXuclfpqsG3DTJEfzlbukI1vxXrt3FArsHiQvQjW5UM7gGel32M6p8AlXRxnez9PgIuU1WrtBUJetk7m39AZwp_aqbqC-AJ-MF70xP1VJZwFN-GeNL3VZsRHePFG4h7Pj---CCZRGlmzuE1-b-sIE7Bn_gbue_qHFMZJhAY55MO25vfZbSoLWHZCGmLMjJVHOYCPoy6l7zyxNmoIcC58QILNWal31KLCDmCsASmZC-xQRjyFwt-kvLbvk38Dc02IKcP3ujhf6WRRr1A0hh1K-gXmv_XI_MUFDcOVIjjhB-rXgjWSCoKfaKI9GCIsb9mnNaBq65BWg==\"},{\"type\":\"message\",\"id\":\"msg_01cc0cda24c36acf016aa8ca3d4a0087d1950121aed4802434\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Ready.\"}],\"phase\":\"final_answer\",\"status\":\"completed\"},{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Recovered.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":30}" + "body": "{\"type\":\"response.create\",\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Ready.\"}]},{\"type\":\"reasoning\",\"id\":\"rs_01cc0cda24c36acf016aa8ca3ced8c87d1a14c5c6a2ced8544\",\"summary\":[],\"encrypted_content\":\"gAAAAABqqMo9M9B15LsV1CLsXpNJpI08GiwCAK97hTheQ2s1lAkKgARMs1HIIXVeAr-tbUq88yN51fiQE8HvGwdF9ZcNI6bZle8D2PS7m8O7UE7C1Z_HX_fz2f0pRHyRfWpVAT2gYQWIDeZu6UdgxbKPBW0lXWzlk_relHG5x6nXkYzZoEeVasqavWoyMMSX7cexe-IYJh6e_3DgRpOchueS8Z-70P0w5R83Ea7UXZQNhMA0yDEYu_td2PmE2Pd2PUTOB3mxF2pb1z7-2t6S0UryhHx0az7Gh2eT60GGUqz9CIZzNE_FX--tszeuO0eI92Cen5tirOUHBTyyDqE0eG26DRl_p_U-xDZwaQONbtYbvkvrj-G7FA2oZXxjJPHuQZsNgBslXS-H0KT1lx3Y8XJ9QMVjFLFaucFG64wCmXPfCH8dYtX_YqfYQR4lwNfiSbyJEX2oDvTVVD_aCJ9NRo7c0aCTtmKBvr6fvvAy3MAFxAp_Sm2nMx4P5GYO4qAmJDByywKw-VK1vHlv3NRmVsAgbArIFgm-axoCs2PLpvZjDqeQGPavaq8zKWTyZYqBsEzKZUtGOZfYjD4mud0Z08I4i2H4K-L00ccVauode3548ZipOIuslbhJxonQXsF6TFdW2Hj8E5JjoEr5IbmwHyI0PBcDWW5AmkjHLwr9v08mFppRoD-2wzPAd5igROAuUbvJhiQd2A-uOaohwMjdpFxrjyUqgGTlI5g7tmI1ceeQWms0bKm8Pd0wIqVM3Nq6YvV7XfEyeogfRMVUQezr_lES42ZMVAoKBSFzMysDwCFkhVNVclcTUpcUUbVp21FChG7Ag-xuq8Cl6OGLA8nWX1C0aCf2HNa-n3dkYr1DtUziurh1MD-UIs5jdGiq3ptrc0VaVZwNdD4jVfAoHB_Ws7GiISXuclfpqsG3DTJEfzlbukI1vxXrt3FArsHiQvQjW5UM7gGel32M6p8AlXRxnez9PgIuU1WrtBUJetk7m39AZwp_aqbqC-AJ-MF70xP1VJZwFN-GeNL3VZsRHePFG4h7Pj---CCZRGlmzuE1-b-sIE7Bn_gbue_qHFMZJhAY55MO25vfZbSoLWHZCGmLMjJVHOYCPoy6l7zyxNmoIcC58QILNWal31KLCDmCsASmZC-xQRjyFwt-kvLbvk38Dc02IKcP3ujhf6WRRr1A0hh1K-gXmv_XI_MUFDcOVIjjhB-rXgjWSCoKfaKI9GCIsb9mnNaBq65BWg==\"},{\"type\":\"message\",\"id\":\"msg_01cc0cda24c36acf016aa8ca3d4a0087d1950121aed4802434\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Ready.\"}],\"phase\":\"final_answer\",\"status\":\"completed\"},{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Reply exactly: Recovered.\"}]}],\"instructions\":\"Follow the user's exact reply instruction.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":30}" }, { "direction": "server", diff --git a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json index c565cf413de..7f22029af73 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json +++ b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json @@ -28,7 +28,7 @@ "headers": { "content-type": "application/json" }, - "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Use the read_screenshot tool, then reply with the words shown.\"}]},{\"type\":\"function_call\",\"call_id\":\"call_screenshot_1\",\"name\":\"read_screenshot\",\"arguments\":\"{}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_screenshot_1\",\"output\":[{\"type\":\"input_text\",\"text\":\"Image read successfully\"},{\"type\":\"input_image\",\"image_url\":\"data:image/png;base64,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\"}]}],\"instructions\":\"Read images carefully. Reply only with the visible text, lowercase, no punctuation.\",\"tools\":[{\"type\":\"function\",\"name\":\"read_screenshot\",\"description\":\"Capture a screenshot of the current screen.\",\"parameters\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":40,\"stream\":true}" + "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Use the read_screenshot tool, then reply with the words shown.\"}]},{\"type\":\"function_call\",\"call_id\":\"call_screenshot_1\",\"name\":\"read_screenshot\",\"arguments\":\"{}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_screenshot_1\",\"output\":[{\"type\":\"input_text\",\"text\":\"Image read successfully\"},{\"type\":\"input_image\",\"image_url\":\"data:image/png;base64,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\"}]}],\"instructions\":\"Read images carefully. Reply only with the visible text, lowercase, no punctuation.\",\"tools\":[{\"type\":\"function\",\"name\":\"read_screenshot\",\"description\":\"Capture a screenshot of the current screen.\",\"parameters\":{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":40,\"stream\":true}" }, "response": { "status": 200, diff --git a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json index 4443dfd9eb9..44a1c8daf49 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json +++ b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json @@ -26,7 +26,7 @@ "headers": { "content-type": "application/json" }, - "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Think briefly, then reply exactly with: Hello!\"}]}],\"instructions\":\"Show concise reasoning when the provider supports visible reasoning summaries.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":120,\"stream\":true}" + "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Think briefly, then reply exactly with: Hello!\"}]}],\"instructions\":\"Show concise reasoning when the provider supports visible reasoning summaries.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"},\"max_output_tokens\":120,\"stream\":true}" }, "response": { "status": 200, @@ -44,7 +44,7 @@ "headers": { "content-type": "application/json" }, - "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Think briefly, then reply exactly with: Hello!\"}]},{\"type\":\"reasoning\",\"id\":\"rs_052e7ec551f55289016aa8c8d63eac87d19d6b921611f123e7\",\"summary\":[],\"encrypted_content\":\"gAAAAABqqMjWZ2Eei8_Gf-5FeEFAYp-gSzFL4D4lQBKL_fyyTYXv5iJ-2jql1mOq0wZpqHL8O9MWxebQGW56Ahd-p21qrDD52CyUBqKKlF87eC1d-cTgjXQlFMsPxvwyQeuU6A2l8tanTtJ48sKtzZtHrDuBXZ35u-lONnovjFGMX3Q83xoqG_um_w5rT420TA_SyU4fGt7oiQvOPS1q4PNo97O824oRnI7n_BC1jPYCaJhl2I1rPJg4afuOpjG-u7JcXRD4JPwZdqMa5o2d0uDKHuUYwP25qiPKKDqTTqFka5cDJjZNPF3ZkVHR-cagjZGvMnizXXxgUpPJ9j83gqY4QJLKCkzcaBj9H7mAL-v9yl4I5kn_9_DhpMILs2SZkC8AvIYNgmel3sDV_BG4XZ2JXciZz86ukQ6DwXgQqS4HOB91g-sGHOWMU1ohsZlEvvJBjGkJ_rAdXVMqAbvi2zvE3_NI4sTAGUrIugGJePrQYTe8gqL8f9NsYac6pzHNQL1e_jQNUvp49bu7EsPzCP3KPYVvZCFohDdwe7sMd6wrztrCJwM4CLdAQK61A7sYzU0HyglLtPidmS5QkSmV6U_xgih7JbKnY0oAeCyYw4ADYqdNTi0axmBErh-lbh-XKNG_TnoMa-2IS3X041N8OsDfSdQp3QsAm53fF8seQuLFa27Iaq2etMj3yGeqWVjA-Mae3K34mt2YPGjQ-HbIOMVmYXBLzNr-s2fT35Sp6SDEsFyvzXb0Vij58s1wW5zkuKgaJiroGgkY86NImuaa3_-wpMK3_9O_wwAbRwV4uBCVzT_rY6rDQHR9-VkM0MbGK8drbdtjXwy3KtAzkux4N4g2nadYU0IIIEUNj_JChUFSHC7VRg7L7LpZMgAFGwHmaUyzQt31LyiVix9WFtcKfBgzehoRV6vstln-oBRd-vFjUW-7WLm7R_lFNHQZ0CKUvKCSpQxdIevczpYT0_lQiDU7Rfp1UBBnicndpq4YQwgRppdZX-QHG5IZxxHNWYdIBtn3eO3ooDXmI-rYryyVcFG6VY5xqssf8GgXqgNy84X_SgYfhGmNiPPQTl5wIBa49f1sxAI-7ri1xLr6FJhQMasOoxm_VuZYNkq6NrAXprtKa70cBRpm1wWBNcPdMul4GNMDQatQZRDENcNLe45iMX-HB1YYSvyc-y5rfSJkP4EgKXC-OqcrzxAlRfB3Bm4jOQL_PSsNBbh26CWAdAYSftM88fbnuqZX2w==\"},{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello!\"}],\"status\":\"completed\"},{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Now reply exactly with: Done.\"}]}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":40,\"stream\":true}" + "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Think briefly, then reply exactly with: Hello!\"}]},{\"type\":\"reasoning\",\"id\":\"rs_052e7ec551f55289016aa8c8d63eac87d19d6b921611f123e7\",\"summary\":[],\"encrypted_content\":\"gAAAAABqqMjWZ2Eei8_Gf-5FeEFAYp-gSzFL4D4lQBKL_fyyTYXv5iJ-2jql1mOq0wZpqHL8O9MWxebQGW56Ahd-p21qrDD52CyUBqKKlF87eC1d-cTgjXQlFMsPxvwyQeuU6A2l8tanTtJ48sKtzZtHrDuBXZ35u-lONnovjFGMX3Q83xoqG_um_w5rT420TA_SyU4fGt7oiQvOPS1q4PNo97O824oRnI7n_BC1jPYCaJhl2I1rPJg4afuOpjG-u7JcXRD4JPwZdqMa5o2d0uDKHuUYwP25qiPKKDqTTqFka5cDJjZNPF3ZkVHR-cagjZGvMnizXXxgUpPJ9j83gqY4QJLKCkzcaBj9H7mAL-v9yl4I5kn_9_DhpMILs2SZkC8AvIYNgmel3sDV_BG4XZ2JXciZz86ukQ6DwXgQqS4HOB91g-sGHOWMU1ohsZlEvvJBjGkJ_rAdXVMqAbvi2zvE3_NI4sTAGUrIugGJePrQYTe8gqL8f9NsYac6pzHNQL1e_jQNUvp49bu7EsPzCP3KPYVvZCFohDdwe7sMd6wrztrCJwM4CLdAQK61A7sYzU0HyglLtPidmS5QkSmV6U_xgih7JbKnY0oAeCyYw4ADYqdNTi0axmBErh-lbh-XKNG_TnoMa-2IS3X041N8OsDfSdQp3QsAm53fF8seQuLFa27Iaq2etMj3yGeqWVjA-Mae3K34mt2YPGjQ-HbIOMVmYXBLzNr-s2fT35Sp6SDEsFyvzXb0Vij58s1wW5zkuKgaJiroGgkY86NImuaa3_-wpMK3_9O_wwAbRwV4uBCVzT_rY6rDQHR9-VkM0MbGK8drbdtjXwy3KtAzkux4N4g2nadYU0IIIEUNj_JChUFSHC7VRg7L7LpZMgAFGwHmaUyzQt31LyiVix9WFtcKfBgzehoRV6vstln-oBRd-vFjUW-7WLm7R_lFNHQZ0CKUvKCSpQxdIevczpYT0_lQiDU7Rfp1UBBnicndpq4YQwgRppdZX-QHG5IZxxHNWYdIBtn3eO3ooDXmI-rYryyVcFG6VY5xqssf8GgXqgNy84X_SgYfhGmNiPPQTl5wIBa49f1sxAI-7ri1xLr6FJhQMasOoxm_VuZYNkq6NrAXprtKa70cBRpm1wWBNcPdMul4GNMDQatQZRDENcNLe45iMX-HB1YYSvyc-y5rfSJkP4EgKXC-OqcrzxAlRfB3Bm4jOQL_PSsNBbh26CWAdAYSftM88fbnuqZX2w==\"},{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello!\"}],\"status\":\"completed\"},{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Now reply exactly with: Done.\"}]}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"},\"max_output_tokens\":40,\"stream\":true}" }, "response": { "status": 200, diff --git a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json index 4f83ebf1b2f..20c2277f860 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json +++ b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json @@ -24,7 +24,7 @@ "headers": { "content-type": "application/json" }, - "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Think briefly, then reply exactly with: Hello!\"}]}],\"instructions\":\"Show concise reasoning when the provider supports visible reasoning summaries.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":120,\"stream\":true}" + "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Think briefly, then reply exactly with: Hello!\"}]}],\"instructions\":\"Show concise reasoning when the provider supports visible reasoning summaries.\",\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"},\"max_output_tokens\":120,\"stream\":true}" }, "response": { "status": 200, diff --git a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-tool-loop.json b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-tool-loop.json index 0231d8be24a..d9baeac74d8 100644 --- a/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-tool-loop.json +++ b/packages/ai/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-tool-loop.json @@ -25,7 +25,7 @@ "headers": { "content-type": "application/json" }, - "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]}],\"instructions\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":80,\"stream\":true}" + "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]}],\"instructions\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":80,\"stream\":true}" }, "response": { "status": 200, @@ -43,7 +43,7 @@ "headers": { "content-type": "application/json" }, - "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]},{\"type\":\"function_call\",\"id\":\"fc_09525c04931d1487016aa8c8d8e12887d193bd327a0f313bd7\",\"call_id\":\"call_p57PJbKe0bX44nj908fpHdRn\",\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_p57PJbKe0bX44nj908fpHdRn\",\"output\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}],\"instructions\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"text\":{\"verbosity\":\"low\"},\"max_output_tokens\":80,\"stream\":true}" + "body": "{\"model\":\"gpt-5.5\",\"input\":[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]},{\"type\":\"function_call\",\"id\":\"fc_09525c04931d1487016aa8c8d8e12887d193bd327a0f313bd7\",\"call_id\":\"call_p57PJbKe0bX44nj908fpHdRn\",\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_p57PJbKe0bX44nj908fpHdRn\",\"output\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}],\"instructions\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\",\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}],\"store\":false,\"include\":[\"reasoning.encrypted_content\"],\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":80,\"stream\":true}" }, "response": { "status": 200, diff --git a/packages/ai/test/provider/checkpoint.test.ts b/packages/ai/test/provider/checkpoint.test.ts index 62636e5eb32..30ed0801c7d 100644 --- a/packages/ai/test/provider/checkpoint.test.ts +++ b/packages/ai/test/provider/checkpoint.test.ts @@ -22,21 +22,22 @@ testEffect( expect(body).toMatchObject({ model: "fixture", stream: true, - store: false, + store: true, instructions: "Keep the context", - parallel_tool_calls: true, + parallel_tool_calls: false, prompt_cache_key: "session-key", service_tier: "priority", reasoning: { effort: "high", summary: "auto" }, + context_management: [{ type: "compaction" }], + max_tool_calls: 1, + tool_choice: "required", + text: { verbosity: "high", format: { type: "json_object" } }, prompt_cache_retention: "24h", prompt_cache_options: { mode: "session", ttl: "1h" }, input: [{ role: "user", content: [{ type: "input_text", text: "hello" }] }, { type: "compaction_trigger" }], }) expect(body.tools).toHaveLength(1) expect(body.tools[0].name).toBe("lookup") - expect(body.tool_choice).toBeUndefined() - expect(body.context_management).toBeUndefined() - expect(body.text).toBeUndefined() expect(body.max_output_tokens).toBeUndefined() expect(body.previous_response_id).toBeUndefined() return respond( @@ -57,7 +58,7 @@ testEffect( ) }), ), -).effect("trigger uses normal request preparation, configured deployment, and supplied subscription headers", () => +).effect("trigger keeps request controls, configured deployment, and supplied subscription headers", () => Effect.gen(function* () { const calls: string[] = [] const input = LLM.request({ @@ -67,12 +68,14 @@ testEffect( promptCacheKey: "session-key", tools: [{ name: "lookup", description: "Lookup", inputSchema: { type: "object", properties: {} } }], toolChoice: { type: "tool", name: "lookup" }, - generation: { maxTokens: 1 }, providerOptions: { store: true, reasoningEffort: "high", reasoningSummary: "auto", contextManagement: [{ type: "compaction" }], + parallelToolCalls: false, + maxToolCalls: 1, + textVerbosity: "low", }, http: { headers: { "chatgpt-account-id": "fixture-account", "x-codex-beta-features": "remote_compaction_v2" }, @@ -82,8 +85,7 @@ testEffect( prompt_cache_retention: "24h", prompt_cache_options: { mode: "session", ttl: "1h" }, store: true, - stream: false, - text: { format: { type: "json_object" } }, + text: { verbosity: "high", format: { type: "json_object" } }, tool_choice: "required", }, }, @@ -114,6 +116,75 @@ testEffect( }), ) +testEffect( + dynamicResponse(({ text, respond }) => + Effect.sync(() => { + expect(JSON.parse(text).text).toEqual({ verbosity: "low", format: { type: "json_object" } }) + return respond(sseEvents({ type: "response.completed", response: { id: "resp_1", output: [checkpoint] } }), { + headers: { "content-type": "text/event-stream" }, + }) + }), + ), +).effect("keeps explicit verbosity on a trigger checkpoint for prompt cache reuse", () => + LLMClient.compact( + LLM.request({ + model: OpenAI.configure({ apiKey: "fixture" }).responses("gpt-5.5"), + prompt: "Hello.", + providerOptions: { textVerbosity: "low" }, + http: { body: { text: { format: { type: "json_object" } } } }, + }), + trigger, + ), +) + +testEffect( + dynamicResponse(({ text, respond }) => + Effect.sync(() => { + const body = JSON.parse(text) + expect(body.text).toEqual({ verbosity: "high", format: { type: "json_object" } }) + expect(body.max_output_tokens).toBe(20_000) + return respond(sseEvents({ type: "response.completed", response: { id: "resp_1", output: [checkpoint] } }), { + headers: { "content-type": "text/event-stream" }, + }) + }), + ), +).effect("keeps the effective body-overlay verbosity and text formatting", () => + LLMClient.compact( + LLM.request({ + model: OpenAI.configure({ apiKey: "fixture" }).responses("gpt-5.5"), + prompt: "Hello.", + generation: { maxTokens: 20_000 }, + providerOptions: { textVerbosity: "low" }, + http: { body: { text: { verbosity: "high", format: { type: "json_object" } } } }, + }), + trigger, + ), +) + +testEffect( + dynamicResponse(({ text, respond }) => + Effect.sync(() => { + expect(JSON.parse(text).max_output_tokens).toBe(128) + return respond(JSON.stringify({ error: { message: "max_output_tokens must be at least 20000" } }), { + status: 400, + headers: { "content-type": "application/json" }, + }) + }), + ), +).effect("passes configured output limits through and leaves rejection to the provider", () => + Effect.gen(function* () { + const error = yield* LLMClient.compact( + LLM.request({ + model: OpenAI.configure({ apiKey: "fixture" }).responses("gpt-5.5"), + prompt: "Hello.", + generation: { maxTokens: 128 }, + }), + trigger, + ).pipe(Effect.flip) + expect(error.message).toContain("at least 20000") + }), +) + const idless = { type: "compaction", encrypted_content: "opaque" } testEffect( fixedResponse( @@ -184,7 +255,7 @@ testEffect(fixedResponse(sseEvents({ type: "response.output_item.done", item: ch expect(error.reason._tag).toBe("InvalidProviderOutput") }), ) -for (const body of [{ input: [] }, { previous_response_id: "stale" }]) { +for (const body of [{ input: [] }, { previous_response_id: "stale" }, { stream: false }]) { testEffect(dynamicResponse(() => Effect.die("Must reject before sending"))).effect( `rejects caller-supplied ${Object.keys(body)[0]} before sending trigger`, () => diff --git a/packages/ai/test/provider/explicit-compaction.test.ts b/packages/ai/test/provider/explicit-compaction.test.ts index 51258095513..35e41c1f368 100644 --- a/packages/ai/test/provider/explicit-compaction.test.ts +++ b/packages/ai/test/provider/explicit-compaction.test.ts @@ -110,11 +110,16 @@ for (const model of [ dynamicResponse(({ request, text, respond }) => Effect.sync(() => { expect(new URL(request.url).pathname).toEndWith("/responses/compact") - expect(JSON.parse(text)).toEqual({ model: "fixture", input: [item], instructions: "Keep the context" }) + expect(JSON.parse(text)).toEqual({ + model: "fixture", + input: [item], + instructions: "Keep the context", + include: ["reasoning.encrypted_content"], + }) return respond(JSON.stringify({ object: "response.compaction", output: [checkpoint] })) }), ), - ).effect(`${model.provider} compacts provider-specific history without lowering generation settings`, () => + ).effect(`${model.provider} validates tools but ignores unrelated unsupported generation settings`, () => Effect.gen(function* () { const request = LLM.request({ model, @@ -151,6 +156,11 @@ for (const model of [ ] as const) { const error = yield* LLMClient.generate(candidate).pipe(Effect.flip) expect(error.reason._tag).toBe(tag) + if (candidate.tools.length > 0) { + const compactError = yield* LLMClient.compact(candidate).pipe(Effect.flip) + expect(compactError.reason._tag).toBe("InvalidRequest") + continue + } const response = yield* LLMClient.compact(candidate) expect(response.replacement[0]?.content[0]?.type).toBe("compaction") } @@ -255,6 +265,13 @@ for (const overlay of [undefined, { service_tier: "priority", prompt_cache_key: model: "fixture", input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hello" }] }], service_tier: overlay?.service_tier ?? "flex", + reasoning: { effort: "low" }, + text: { verbosity: "low", format: { type: "json_object" } }, + include: ["reasoning.encrypted_content"], + parallel_tool_calls: false, + tools: [ + { type: "function", name: "lookup", description: "Lookup", parameters: { type: "object" }, strict: false }, + ], prompt_cache_key: overlay?.prompt_cache_key ?? "affinity", prompt_cache_retention: "24h", prompt_cache_options: { mode: "explicit", ttl: "30m" }, @@ -268,12 +285,20 @@ for (const overlay of [undefined, { service_tier: "priority", prompt_cache_key: model: OpenAI.configure({ apiKey: "test" }).responses("fixture"), prompt: "hello", promptCacheKey: "affinity", - providerOptions: { serviceTier: "flex" }, + providerOptions: { + serviceTier: "flex", + reasoningEffort: "low", + textVerbosity: "low", + include: ["reasoning.encrypted_content"], + parallelToolCalls: false, + }, generation: { maxTokens: 100 }, + tools: [{ name: "lookup", description: "Lookup", inputSchema: {} }], http: { body: { stream: true, store: false, + text: { format: { type: "json_object" } }, prompt_cache_retention: "24h", prompt_cache_options: { mode: "explicit", ttl: "30m" }, ...overlay, @@ -396,6 +421,8 @@ for (const model of [ model: model.id, input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "original" }] }], instructions: "system", + include: ["reasoning.encrypted_content"], + ...(model.id === "gpt-5.3-codex" ? { reasoning: { effort: "medium", summary: "auto" } } : {}), }) return respond( JSON.stringify({ @@ -407,7 +434,10 @@ for (const model of [ ) } expect(new URL(request.url).pathname.endsWith("/responses")).toBe(true) - expect(body.input).toEqual([...output, { type: "message", role: "user", content: [{ type: "input_text", text: "continue" }] }]) + expect(body.input).toEqual([ + ...output, + { type: "message", role: "user", content: [{ type: "input_text", text: "continue" }] }, + ]) return respond(sseEvents({ type: "response.completed", response: { id: "resp_1", output: [] } }), { headers: { "content-type": "text/event-stream" }, }) diff --git a/packages/ai/test/provider/openai-responses.test.ts b/packages/ai/test/provider/openai-responses.test.ts index 7115ec4fe67..cb982f88e89 100644 --- a/packages/ai/test/provider/openai-responses.test.ts +++ b/packages/ai/test/provider/openai-responses.test.ts @@ -1945,7 +1945,7 @@ describe("OpenAI Responses route", () => { expect(prepared.body.prompt_cache_key).toBe("session_123") expect(prepared.body.include).toEqual(["reasoning.encrypted_content"]) expect(prepared.body.reasoning).toEqual({ effort: "high", summary: "auto" }) - expect(prepared.body.text).toEqual({ verbosity: "low" }) + expect(prepared.body.text).toBeUndefined() expect(prepared.body.metadata).toEqual({ environment: "test", tenant: "acme" }) expect(prepared.body.safety_identifier).toBe("user_123") expect(prepared.body.stream_options).toEqual({ include_obfuscation: false }) diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index fe1111ec12e..9417691be34 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -101,6 +101,7 @@ import { VcsHgPlugin } from "./vcs/hg.js" import { ToolInputRepairPlugin } from "./tool-input-repair.js" import { OptimizePlugin } from "./optimize.js" import { VcsGitPlugin } from "./vcs/git.js" +import { VerbosityPlugin } from "./verbosity.js" import { WarmingPlugin } from "./warming.js" import { WellKnownPlugin } from "../wellknown/plugin.js" @@ -230,6 +231,7 @@ const pre = [ PatchTool.Plugin, // Render model prompts after the patch plugin selects the available editing tools. ...OptimizePlugin.Plugins, + VerbosityPlugin.Plugin, IdentityPlugin.Plugin, EditTool.Plugin, GlobTool.Plugin, diff --git a/packages/core/src/plugin/verbosity.ts b/packages/core/src/plugin/verbosity.ts new file mode 100644 index 00000000000..ed7cda40058 --- /dev/null +++ b/packages/core/src/plugin/verbosity.ts @@ -0,0 +1,54 @@ +export * as VerbosityPlugin from "./verbosity.js" + +import { define } from "@opencode/plugin/effect/plugin" +import type { SessionRequest } from "@opencode/plugin/effect/session" +import { Effect } from "effect" +import { Model } from "../model.js" +import { Provider } from "../provider.js" +import type { PluginInternal } from "./internal.js" + +const direct = new Set([ + "@opencode/ai/providers/openai", + "@opencode/ai/providers/openai/responses", + "@opencode/ai/providers/azure", + "@opencode/ai/providers/azure/responses", +]) +const gateways = new Set(["@opencode/ai/providers/cloudflare-ai-gateway", Provider.aisdk("@ai-sdk/gateway")]) + +export const Plugin = define({ + id: "opencode.prompt.verbosity", + effect: Effect.fn("VerbosityPlugin")(function* (ctx) { + const models = yield* Model.Service + const hook = (event: SessionRequest) => + Effect.gen(function* () { + if (event.options.textVerbosity !== undefined) return + const model = yield* models.get(event.model.providerID, event.model.id) + if (!model) return + const id = openAIModelID(model) + if (!id || !supportsVerbosity(id)) return + if (model.settings?.textVerbosity !== undefined) return + const variant = model.variants.find((item) => item.id === event.model.variant) + if (variant?.settings?.textVerbosity !== undefined) return + event.options.textVerbosity = "low" + }) + yield* ctx.session.hook("context", hook) + yield* ctx.session.hook("compaction", hook) + yield* ctx.session.hook("generate", hook) + yield* ctx.session.hook("title", hook) + }), +} satisfies PluginInternal.InternalPlugin) + +function supportsVerbosity(id: string) { + if (id.includes("gpt-6")) return true + if (id.includes("-chat") || id.includes("-image")) return false + // New GPT-5 minor versions remain unset until their support is known. + return /(?:^|[/.])gpt-5\.[1-6](?:[.:-]|$)/.test(id) || /(?:^|[/.])gpt-5(?:-(?:mini|nano)(?:[.:-]|$)|$)/.test(id) +} + +function openAIModelID(model: Model.Info) { + const id = model.modelID.toLowerCase() + if (direct.has(model.package ?? "")) return id + if (model.package === "@opencode/ai/providers/amazon-bedrock/mantle/responses" && id.startsWith("openai.")) + return id.slice("openai.".length) + if (gateways.has(model.package ?? "") && id.startsWith("openai/")) return id.slice("openai/".length) +} diff --git a/packages/core/test/aisdk.test.ts b/packages/core/test/aisdk.test.ts index 354f8698ba4..f4a2a2cceaf 100644 --- a/packages/core/test/aisdk.test.ts +++ b/packages/core/test/aisdk.test.ts @@ -375,6 +375,18 @@ it.effect("routes AI Gateway model options by upstream prefix", () => bedrock: { reasoningConfig: { type: "enabled" } }, }) + const openai = yield* aisdk.model({ + ...model("@ai-sdk/gateway", { gateway: { order: ["openai"] } }), + modelID: Model.ID.make("openai/gpt-5.5"), + }) + const openaiPrepared = yield* compileRequest( + LLM.request({ model: openai, prompt: "Hello", providerOptions: { textVerbosity: "low" } }), + ) + expect(openaiPrepared.body.providerOptions).toEqual({ + gateway: { order: ["openai"] }, + openai: { textVerbosity: "low" }, + }) + const fallback = yield* aisdk.model({ ...model("@ai-sdk/gateway", { reasoningEffort: "high" }), modelID: Model.ID.make("deepseek/deepseek-v4"), diff --git a/packages/core/test/plugin/verbosity.test.ts b/packages/core/test/plugin/verbosity.test.ts new file mode 100644 index 00000000000..e7f5ca99b90 --- /dev/null +++ b/packages/core/test/plugin/verbosity.test.ts @@ -0,0 +1,163 @@ +import { expect } from "bun:test" +import { Effect } from "effect" +import { Agent } from "@opencode/core/agent" +import { Model } from "@opencode/core/model" +import { Plugin } from "@opencode/core/plugin" +import { PluginHooks } from "@opencode/core/plugin/hooks" +import { PluginHost } from "@opencode/core/plugin/host" +import { VerbosityPlugin } from "@opencode/core/plugin/verbosity" +import { Provider } from "@opencode/core/provider" +import { Session } from "@opencode/core/session" +import type { SessionHooks } from "@opencode/plugin/effect/session" +import { testEffect } from "../lib/effect" +import { PluginTestLayer } from "./fixture" + +const it = testEffect(PluginTestLayer) +const ref = (providerID: string, id: string, variant?: string) => + Model.Ref.make({ + providerID: Provider.ID.make(providerID), + id: Model.ID.make(id), + ...(variant ? { variant: Model.VariantID.make(variant) } : {}), + }) +const request = (model: Model.Ref, options: SessionHooks["context"]["options"] = {}): SessionHooks["context"] => ({ + sessionID: Session.ID.make("ses_verbosity"), + agent: Agent.ID.make("build"), + model, + system: [], + messages: [], + tools: {}, + options, +}) + +it.effect("sets known OpenAI Responses defaults without overriding configured or unknown models", () => + Effect.gen(function* () { + const providers = yield* Provider.Service + const plugins = yield* Plugin.Service + const hooks = yield* PluginHooks.Service + yield* providers.transform((editor) => { + editor.add({ + info: { ...Provider.Info.empty(Provider.ID.make("openai")), package: "@opencode/ai/providers/openai" }, + models: [ + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5.5")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5.6-luna-fast")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5.2-codex")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5-mini-fast")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-6-astra-pro")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-4o")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-7")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5.7")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5.5-chat")) }, + { ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5.4-image-2")) }, + { + ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-6-astra")), + variants: [{ id: Model.VariantID.make("quiet"), settings: { textVerbosity: null } }], + }, + { + ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("chat")), + modelID: Model.ID.make("gpt-5.5"), + package: "@opencode/ai/providers/openai/chat", + }, + ], + }) + editor.add({ + info: { + ...Provider.Info.empty(Provider.ID.make("opencode")), + package: "@opencode/ai/providers/openai-compatible", + }, + models: [ + { + ...Model.Info.default(Provider.ID.make("opencode"), Model.ID.make("astra-alias")), + modelID: Model.ID.make("gpt-6-astra"), + package: "@opencode/ai/providers/openai/responses", + }, + { + ...Model.Info.default(Provider.ID.make("opencode"), Model.ID.make("no-default")), + modelID: Model.ID.make("gpt-6-astra"), + package: "@opencode/ai/providers/openai/responses", + settings: { textVerbosity: null }, + }, + ], + }) + editor.add({ + info: { ...Provider.Info.empty(Provider.ID.make("openrouter")), package: "@opencode/ai/providers/openrouter" }, + models: [Model.Info.default(Provider.ID.make("openrouter"), Model.ID.make("gpt-5.5"))], + }) + editor.add({ + info: { + ...Provider.Info.empty(Provider.ID.make("configured")), + package: "@opencode/ai/providers/openai", + settings: { textVerbosity: "medium" }, + }, + models: [Model.Info.default(Provider.ID.make("configured"), Model.ID.make("gpt-5.5"))], + }) + for (const [providerID, packageName, modelID] of [ + ["azure", "@opencode/ai/providers/azure/responses", "gpt-5.5"], + ["bedrock-mantle", "@opencode/ai/providers/amazon-bedrock/mantle/responses", "openai.gpt-6-sol"], + ["cloudflare", "@opencode/ai/providers/cloudflare-ai-gateway", "openai/gpt-5.6-sol"], + ["vercel", Provider.aisdk("@ai-sdk/gateway"), "openai/gpt-6-astra-fast"], + ["azure-chat", "@opencode/ai/providers/azure/chat", "gpt-5.5"], + ["bedrock-converse", "@opencode/ai/providers/amazon-bedrock", "global.openai.gpt-6-sol"], + ["cloudflare-chat", "@opencode/ai/providers/cloudflare-ai-gateway", "workers-ai/gpt-5.5"], + ["vercel-other", Provider.aisdk("@ai-sdk/gateway"), "anthropic/gpt-5.5"], + ] as const) { + editor.add({ + info: { ...Provider.Info.empty(Provider.ID.make(providerID)), package: packageName }, + models: [ + { + ...Model.Info.default(Provider.ID.make(providerID), Model.ID.make("selected")), + modelID: Model.ID.make(modelID), + }, + ], + }) + } + }) + yield* VerbosityPlugin.Plugin.effect(yield* PluginHost.make(plugins)) + + for (const kind of ["context", "compaction", "generate", "title"] as const) { + const event = request(ref("openai", "gpt-5.5")) + yield* hooks.trigger("session", kind, event) + expect(event.options.textVerbosity).toBe("low") + } + + for (const id of ["gpt-5.6-luna-fast", "gpt-5.2-codex", "gpt-5-mini-fast", "gpt-6-astra-pro"]) { + const event = request(ref("openai", id)) + yield* hooks.trigger("session", "context", event) + expect(event.options.textVerbosity).toBe("low") + } + + for (const model of [ + ref("openai", "gpt-4o"), + ref("openai", "gpt-7"), + ref("openai", "gpt-5.7"), + ref("openai", "gpt-5.5-chat"), + ref("openai", "gpt-5.4-image-2"), + ref("openai", "chat"), + ref("openrouter", "gpt-5.5"), + ref("configured", "gpt-5.5"), + ref("azure-chat", "selected"), + ref("bedrock-converse", "selected"), + ref("cloudflare-chat", "selected"), + ref("vercel-other", "selected"), + ref("openai", "gpt-6-astra", "quiet"), + ref("opencode", "no-default"), + ]) { + const event = request(model) + yield* hooks.trigger("session", "context", event) + expect(event.options.textVerbosity).toBeUndefined() + } + + const alias = request(ref("opencode", "astra-alias")) + yield* hooks.trigger("session", "context", alias) + expect(alias.options.textVerbosity).toBe("low") + + for (const providerID of ["azure", "bedrock-mantle", "cloudflare", "vercel"]) { + const event = request(ref(providerID, "selected")) + yield* hooks.trigger("session", "context", event) + expect(event.options.textVerbosity).toBe("low") + } + + const overridden = request(ref("openai", "gpt-5.5"), { textVerbosity: "high" }) + yield* hooks.trigger("session", "context", overridden) + expect(overridden.options.textVerbosity).toBe("high") + }), +) diff --git a/packages/core/test/session-native-compaction.test.ts b/packages/core/test/session-native-compaction.test.ts index 502f88e7ce7..756e93b2b61 100644 --- a/packages/core/test/session-native-compaction.test.ts +++ b/packages/core/test/session-native-compaction.test.ts @@ -362,6 +362,7 @@ it.live("manual and automatic endpoint compaction keep the provider replacement expect(JSON.stringify(replacement)).not.toContain("Original user") expect(fixture.state.calls).toBe(2) expect(fixture.headers[0]?.get("x-http-hook")).toBe("compaction") + expect(fixture.bodies[0]).toMatchObject({ tools: [expect.objectContaining({ name: "read" })] }) expect(fixture.bodies[0]).not.toHaveProperty("context_management") }), )