@opencode/ai 0.0.0-dev-20138 → 0.0.0-dev-20140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/protocols/anthropic-messages.js +3 -6
- package/dist/protocols/bedrock-converse.js +5 -6
- package/dist/protocols/gemini.js +6 -6
- package/dist/protocols/meta-responses.js +1 -2
- package/dist/protocols/mistral-chat.js +3 -6
- package/dist/protocols/open-responses.d.ts +1 -3
- package/dist/protocols/open-responses.js +3 -4
- package/dist/protocols/openai-chat.js +3 -4
- package/dist/protocols/openai-responses.js +7 -10
- package/dist/protocols/utils/tool-schema.d.ts +2 -2
- package/dist/protocols/utils/tool-schema.js +10 -3
- package/dist/route/client.d.ts +2 -1
- package/dist/route/client.js +3 -1
- package/dist/route/protocol.d.ts +3 -1
- package/package.json +3 -3
|
@@ -14,7 +14,6 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
14
14
|
import { effortUpdate, resolveEffortUpdates } from "../effort-updates.js";
|
|
15
15
|
import * as Cache from "./utils/cache.js";
|
|
16
16
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
17
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
18
17
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
19
18
|
const ADAPTER = "anthropic-messages";
|
|
20
19
|
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
|
|
@@ -408,10 +407,10 @@ const redactedDataFromMetadata = (metadata, key) => {
|
|
|
408
407
|
return undefined;
|
|
409
408
|
return typeof provider.redactedData === "string" ? provider.redactedData : undefined;
|
|
410
409
|
};
|
|
411
|
-
const lowerTool = (breakpoints, tool
|
|
410
|
+
const lowerTool = (breakpoints, tool) => ({
|
|
412
411
|
name: tool.name,
|
|
413
412
|
description: tool.description,
|
|
414
|
-
input_schema: inputSchema,
|
|
413
|
+
input_schema: tool.inputSchema,
|
|
415
414
|
cache_control: cacheControl(breakpoints, tool.cache),
|
|
416
415
|
});
|
|
417
416
|
const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("Anthropic Messages", toolChoice, {
|
|
@@ -910,9 +909,7 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
|
|
|
910
909
|
// over-mark we keep their tool hints and shed the message-tail ones first.
|
|
911
910
|
const breakpoints = Cache.newBreakpoints(ANTHROPIC_BREAKPOINT_CAP);
|
|
912
911
|
const flattened = ProviderShared.flattenToolRequest(updates.request);
|
|
913
|
-
const tools = flattened.tools.length === 0
|
|
914
|
-
? undefined
|
|
915
|
-
: flattened.tools.map((tool) => lowerTool(breakpoints, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model)));
|
|
912
|
+
const tools = flattened.tools.length === 0 ? undefined : flattened.tools.map((tool) => lowerTool(breakpoints, tool));
|
|
916
913
|
// Anthropic rejects tool_choice when tools are absent; "none" is only meaningful with tools present.
|
|
917
914
|
const toolChoice = tools === undefined || !request.toolChoice ? undefined : yield* lowerToolChoice(request.toolChoice);
|
|
918
915
|
const systemParts = request.system.filter((part) => part.text.length > 0);
|
|
@@ -11,7 +11,6 @@ import { BedrockCache } from "./utils/bedrock-cache.js";
|
|
|
11
11
|
import { BedrockMedia } from "./utils/bedrock-media.js";
|
|
12
12
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
13
13
|
import { MistralToolID } from "./utils/mistral-tool-id.js";
|
|
14
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
15
14
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
16
15
|
import { concatBytes } from "../utils/bytes.js";
|
|
17
16
|
const ADAPTER = "bedrock-converse";
|
|
@@ -156,17 +155,17 @@ const BedrockEvent = Schema.Struct({
|
|
|
156
155
|
// =============================================================================
|
|
157
156
|
// Request Lowering
|
|
158
157
|
// =============================================================================
|
|
159
|
-
const lowerToolSpec = (tool
|
|
158
|
+
const lowerToolSpec = (tool) => ({
|
|
160
159
|
toolSpec: {
|
|
161
160
|
name: tool.name,
|
|
162
161
|
...(tool.description.trim().length > 0 ? { description: tool.description } : {}),
|
|
163
|
-
inputSchema: { json: inputSchema },
|
|
162
|
+
inputSchema: { json: tool.inputSchema },
|
|
164
163
|
},
|
|
165
164
|
});
|
|
166
|
-
const lowerTools = (
|
|
165
|
+
const lowerTools = (breakpoints, tools) => {
|
|
167
166
|
const result = [];
|
|
168
167
|
for (const tool of tools) {
|
|
169
|
-
result.push(lowerToolSpec(tool
|
|
168
|
+
result.push(lowerToolSpec(tool));
|
|
170
169
|
const cachePoint = BedrockCache.block(breakpoints, tool.cache);
|
|
171
170
|
if (cachePoint)
|
|
172
171
|
result.push(cachePoint);
|
|
@@ -371,7 +370,7 @@ const fromRequest = Effect.fn("BedrockConverse.fromRequest")(function* (request)
|
|
|
371
370
|
if (flattened.tools.length === 0)
|
|
372
371
|
return undefined;
|
|
373
372
|
return {
|
|
374
|
-
tools: lowerTools(
|
|
373
|
+
tools: lowerTools(breakpoints, flattened.tools),
|
|
375
374
|
// Converse has no native "none". Keep definitions stable for prompt
|
|
376
375
|
// caching and omit only the unsupported choice.
|
|
377
376
|
toolChoice,
|
package/dist/protocols/gemini.js
CHANGED
|
@@ -11,7 +11,6 @@ import { Media } from "../media.js";
|
|
|
11
11
|
import { JsonObject, knownString, lenient, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
12
12
|
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
13
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
14
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
15
14
|
const ADAPTER = "gemini";
|
|
16
15
|
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
|
17
16
|
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
|
|
@@ -190,12 +189,11 @@ const GeminiEvent = Schema.Struct({
|
|
|
190
189
|
// =============================================================================
|
|
191
190
|
// Request Lowering
|
|
192
191
|
// =============================================================================
|
|
193
|
-
// Tool schemas go in `parametersJsonSchema`, which accepts standard JSON Schema.
|
|
194
|
-
|
|
195
|
-
const lowerTool = (tool, model) => ({
|
|
192
|
+
// Tool schemas go in `parametersJsonSchema`, which accepts standard JSON Schema.
|
|
193
|
+
const lowerTool = (tool) => ({
|
|
196
194
|
name: tool.name,
|
|
197
195
|
description: tool.description,
|
|
198
|
-
parametersJsonSchema:
|
|
196
|
+
parametersJsonSchema: tool.inputSchema,
|
|
199
197
|
});
|
|
200
198
|
const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini", toolChoice, {
|
|
201
199
|
auto: () => ({ functionCallingConfig: { mode: "AUTO" } }),
|
|
@@ -388,7 +386,7 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
|
|
|
388
386
|
tools: hasTools
|
|
389
387
|
? [
|
|
390
388
|
{
|
|
391
|
-
functionDeclarations: flattened.tools.map(
|
|
389
|
+
functionDeclarations: flattened.tools.map(lowerTool),
|
|
392
390
|
},
|
|
393
391
|
]
|
|
394
392
|
: undefined,
|
|
@@ -654,6 +652,8 @@ export const protocol = Protocol.make({
|
|
|
654
652
|
schema: GeminiBody,
|
|
655
653
|
from: fromRequest,
|
|
656
654
|
},
|
|
655
|
+
// Gemini's schema rules are this API's default, including for tuned endpoints whose IDs do not name Gemini.
|
|
656
|
+
sanitizer: "gemini",
|
|
657
657
|
stream: {
|
|
658
658
|
event: Protocol.jsonEvent(GeminiEvent),
|
|
659
659
|
initial: (request) => ({
|
|
@@ -5,7 +5,6 @@ import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.j
|
|
|
5
5
|
import { OpenResponses } from "./open-responses.js";
|
|
6
6
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
7
7
|
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js";
|
|
8
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
9
8
|
import { detectMediaType } from "../utils/media-type.js";
|
|
10
9
|
const ADAPTER = "meta-responses";
|
|
11
10
|
const NAME = "Meta Responses";
|
|
@@ -82,7 +81,7 @@ const fromRequest = Effect.fn("MetaResponses.fromRequest")(function* (request) {
|
|
|
82
81
|
? undefined
|
|
83
82
|
: yield* Effect.forEach(projected.tools, (tool) => Effect.gen(function* () {
|
|
84
83
|
if (tool.native === undefined)
|
|
85
|
-
return yield* OpenResponses.lowerTool(NAME, tool
|
|
84
|
+
return yield* OpenResponses.lowerTool(NAME, tool);
|
|
86
85
|
return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(NativeTool))(tool.native.meta);
|
|
87
86
|
})),
|
|
88
87
|
tool_choice: OpenResponses.allowedToolChoice(request) ??
|
|
@@ -10,7 +10,6 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
10
10
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
11
11
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
12
12
|
import { MistralToolID } from "./utils/mistral-tool-id.js";
|
|
13
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
14
13
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
15
14
|
const ADAPTER = "mistral-chat";
|
|
16
15
|
const DONE = "[DONE]";
|
|
@@ -273,9 +272,9 @@ const lowerMessages = Effect.fn("MistralChat.lowerMessages")(function* (request)
|
|
|
273
272
|
}
|
|
274
273
|
return messages;
|
|
275
274
|
});
|
|
276
|
-
const lowerTool = (tool
|
|
275
|
+
const lowerTool = (tool) => ({
|
|
277
276
|
type: "function",
|
|
278
|
-
function: { name: tool.name, description: tool.description, parameters: inputSchema, strict: false },
|
|
277
|
+
function: { name: tool.name, description: tool.description, parameters: tool.inputSchema, strict: false },
|
|
279
278
|
});
|
|
280
279
|
export const fromRequest = Effect.fn("MistralChat.fromRequest")(function* (request) {
|
|
281
280
|
const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(MistralOptions))(request.providerOptions ?? {});
|
|
@@ -296,9 +295,7 @@ export const fromRequest = Effect.fn("MistralChat.fromRequest")(function* (reque
|
|
|
296
295
|
return {
|
|
297
296
|
model: request.model.id,
|
|
298
297
|
messages: yield* lowerMessages(flattened.request),
|
|
299
|
-
tools: flattened.tools.length > 0
|
|
300
|
-
? flattened.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model)))
|
|
301
|
-
: undefined,
|
|
298
|
+
tools: flattened.tools.length > 0 ? flattened.tools.map(lowerTool) : undefined,
|
|
302
299
|
tool_choice: toolChoice,
|
|
303
300
|
stream: true,
|
|
304
301
|
max_tokens: request.generation?.maxTokens,
|
|
@@ -716,9 +716,7 @@ interface ReasoningStreamItem {
|
|
|
716
716
|
readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>;
|
|
717
717
|
readonly deltaIndexes: ReadonlySet<number>;
|
|
718
718
|
}
|
|
719
|
-
export declare const lowerTool: (protocolName: string, tool: ToolDefinition
|
|
720
|
-
readonly [x: string]: unknown;
|
|
721
|
-
}) => Effect.Effect<{
|
|
719
|
+
export declare const lowerTool: (protocolName: string, tool: ToolDefinition) => Effect.Effect<{
|
|
722
720
|
type: "function";
|
|
723
721
|
name: string;
|
|
724
722
|
description: string;
|
|
@@ -7,7 +7,6 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
7
7
|
import { effortUpdate } from "../effort-updates.js";
|
|
8
8
|
import { OpenResponsesOptions } from "./utils/open-responses-options.js";
|
|
9
9
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
10
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
11
10
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
12
11
|
const ADAPTER = "open-responses";
|
|
13
12
|
const NAME = "Open Responses";
|
|
@@ -281,14 +280,14 @@ const BASE_ADAPTER = { id: ADAPTER, name: NAME };
|
|
|
281
280
|
// =============================================================================
|
|
282
281
|
// Request Lowering
|
|
283
282
|
// =============================================================================
|
|
284
|
-
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool
|
|
283
|
+
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool) {
|
|
285
284
|
if (tool.native !== undefined)
|
|
286
285
|
return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`);
|
|
287
286
|
return {
|
|
288
287
|
type: "function",
|
|
289
288
|
name: tool.name,
|
|
290
289
|
description: tool.description,
|
|
291
|
-
parameters: inputSchema,
|
|
290
|
+
parameters: tool.inputSchema,
|
|
292
291
|
// The common tool definition does not currently express Responses strict-schema policy.
|
|
293
292
|
strict: false,
|
|
294
293
|
};
|
|
@@ -610,7 +609,7 @@ export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAd
|
|
|
610
609
|
? undefined
|
|
611
610
|
: yield* Effect.forEach(projected.tools, (tool) => tool.native !== undefined && adapter.nativeTool
|
|
612
611
|
? adapter.nativeTool(tool.native)
|
|
613
|
-
: lowerTool(adapter.name, tool
|
|
612
|
+
: lowerTool(adapter.name, tool)),
|
|
614
613
|
tool_choice: allowedToolChoice(request) ??
|
|
615
614
|
(request.toolChoice ? yield* lowerToolChoice(adapter.name, request.toolChoice) : undefined),
|
|
616
615
|
};
|
|
@@ -11,7 +11,6 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
11
11
|
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
12
12
|
import { OpenAIOptions } from "./utils/openai-options.js";
|
|
13
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
14
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
15
14
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
16
15
|
const ADAPTER = "openai-chat";
|
|
17
16
|
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "refusal", "tool_calls"]);
|
|
@@ -209,12 +208,12 @@ export const OpenAIChatEvent = Schema.StructWithRest(Schema.Struct({
|
|
|
209
208
|
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
210
209
|
const DONE = "[DONE]";
|
|
211
210
|
const OpenAIChatStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(OpenAIChatEvent)]);
|
|
212
|
-
const lowerTool = (tool,
|
|
211
|
+
const lowerTool = (tool, options, supportsStrictMode) => ({
|
|
213
212
|
type: "function",
|
|
214
213
|
function: {
|
|
215
214
|
name: tool.name,
|
|
216
215
|
description: tool.description,
|
|
217
|
-
parameters: inputSchema,
|
|
216
|
+
parameters: tool.inputSchema,
|
|
218
217
|
...(supportsStrictMode ? { strict: false } : {}),
|
|
219
218
|
},
|
|
220
219
|
cache_control: options.cacheControl?.(tool.cache),
|
|
@@ -649,7 +648,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
|
|
|
649
648
|
? hasHistory
|
|
650
649
|
? []
|
|
651
650
|
: undefined
|
|
652
|
-
: flattened.tools.map((tool) => lowerTool(tool,
|
|
651
|
+
: flattened.tools.map((tool) => lowerTool(tool, options, supportsStrictMode)),
|
|
653
652
|
tool_choice: hasActiveTools && request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
|
|
654
653
|
stream: true,
|
|
655
654
|
...(supportsUsageInStreaming ? { stream_options: { include_usage: true } } : {}),
|
|
@@ -5,13 +5,12 @@ import { Auth } from "../route/auth.js";
|
|
|
5
5
|
import { Endpoint } from "../route/endpoint.js";
|
|
6
6
|
import { Protocol } from "../route/protocol.js";
|
|
7
7
|
import { HttpTransport } from "../route/transport/index.js";
|
|
8
|
-
import { LLMRequest, mergeJsonRecords
|
|
8
|
+
import { LLMRequest, mergeJsonRecords } from "../schema/index.js";
|
|
9
9
|
import { resolveEffortUpdates } from "../effort-updates.js";
|
|
10
10
|
import { OpenResponses } from "./open-responses.js";
|
|
11
11
|
import { OpenResponsesOptions } from "./utils/open-responses-options.js";
|
|
12
12
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
13
13
|
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js";
|
|
14
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
15
14
|
import { OpenResponsesChannel } from "./open-responses-channel.js";
|
|
16
15
|
import { ResponsesCompaction } from "./utils/responses-compaction.js";
|
|
17
16
|
import { ResponsesCheckpoint } from "./utils/responses-checkpoint.js";
|
|
@@ -132,27 +131,27 @@ const nativeImageTool = (tool) => {
|
|
|
132
131
|
const native = nativeImageToolInput(tool);
|
|
133
132
|
return Schema.is(OpenAIResponsesImageGenerationTool)(native) ? native : undefined;
|
|
134
133
|
};
|
|
135
|
-
const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool
|
|
134
|
+
const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool) {
|
|
136
135
|
const native = nativeImageToolInput(tool);
|
|
137
136
|
if (native !== undefined) {
|
|
138
137
|
if (Schema.is(OpenAIResponsesImageGenerationTool)(native))
|
|
139
138
|
return native;
|
|
140
139
|
return yield* ProviderShared.invalidRequest("OpenAI Responses image generation tool options are invalid");
|
|
141
140
|
}
|
|
142
|
-
return yield* OpenResponses.lowerTool(NAME, tool
|
|
141
|
+
return yield* OpenResponses.lowerTool(NAME, tool);
|
|
143
142
|
});
|
|
144
143
|
// Native namespaces hold only function tools, so deeper levels flatten into
|
|
145
144
|
// the leaf names the same way non-native protocols flatten the whole tree.
|
|
146
|
-
const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool
|
|
145
|
+
const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool) {
|
|
147
146
|
if (tool.type === "tool")
|
|
148
|
-
return yield* lowerTool(tool
|
|
147
|
+
return yield* lowerTool(tool);
|
|
149
148
|
// OpenAI requires a namespace description; fall back to a generic one so a
|
|
150
149
|
// missing description never blocks the request.
|
|
151
150
|
return {
|
|
152
151
|
type: "namespace",
|
|
153
152
|
name: tool.name,
|
|
154
153
|
description: tool.description ?? `Tools in the ${tool.name} namespace.`,
|
|
155
|
-
tools: yield* Effect.forEach(ProviderShared.flattenTools(tool.tools), (leaf) => OpenResponses.lowerTool(NAME, leaf
|
|
154
|
+
tools: yield* Effect.forEach(ProviderShared.flattenTools(tool.tools), (leaf) => OpenResponses.lowerTool(NAME, leaf)),
|
|
156
155
|
};
|
|
157
156
|
});
|
|
158
157
|
const lowerToolChoice = (toolChoice, tools) => ProviderShared.matchToolChoice(NAME, toolChoice, {
|
|
@@ -172,9 +171,7 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request)
|
|
|
172
171
|
...(yield* OpenResponses.lowerConversation(updates.request, adapter)),
|
|
173
172
|
...OpenResponses.lowerGeneration(request, { ...options, reasoningEffort: updates.effort }),
|
|
174
173
|
context_management: management?.map((edit) => ({ type: edit.type, compact_threshold: edit.compactThreshold })),
|
|
175
|
-
tools: request.tools.length === 0
|
|
176
|
-
? undefined
|
|
177
|
-
: yield* Effect.forEach(request.tools, (tool) => lowerToolEntry(tool, request.model)),
|
|
174
|
+
tools: request.tools.length === 0 ? undefined : yield* Effect.forEach(request.tools, lowerToolEntry),
|
|
178
175
|
tool_choice: request.tools.length === 0
|
|
179
176
|
? undefined
|
|
180
177
|
: (OpenResponses.allowedToolChoice(request) ??
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type JsonSchema, type LanguageModel, type LLMRequest } from "../../schema/index.js";
|
|
2
2
|
export declare const ToolSchemaProjection: {
|
|
3
3
|
readonly gemini: (schema: JsonSchema) => JsonSchema;
|
|
4
|
-
readonly modelCompatibility: (schema: JsonSchema, model: LanguageModel, protocolDefault?: LanguageModelSanitizerCompatibility) => JsonSchema;
|
|
5
4
|
readonly moonshot: (schema: JsonSchema) => JsonSchema;
|
|
6
5
|
readonly openAI: (schema: JsonSchema) => JsonSchema;
|
|
7
6
|
readonly responses: (schema: JsonSchema) => JsonSchema;
|
|
7
|
+
readonly tools: (entries: LLMRequest["tools"], model: LanguageModel) => LLMRequest["tools"];
|
|
8
8
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ToolDefinition } from "../../schema/index.js";
|
|
1
2
|
import { isRecord } from "../../utils/record.js";
|
|
2
3
|
import { GeminiJsonSchema } from "./gemini-json-schema.js";
|
|
3
4
|
const tupleItemsSchema = (items) => {
|
|
@@ -69,9 +70,11 @@ const objectRoot = (schema) => {
|
|
|
69
70
|
// Otherwise the protocol's own default applies (the Gemini API always uses Gemini's rules), then the
|
|
70
71
|
// model name selects the family's rules so models reached through gateways and OpenAI-compatible
|
|
71
72
|
// endpoints get the same handling.
|
|
72
|
-
const modelCompatibility = (schema, model
|
|
73
|
+
const modelCompatibility = (schema, model) => {
|
|
73
74
|
const root = objectRoot(schema);
|
|
74
|
-
switch (model.compatibility?.sanitizer ??
|
|
75
|
+
switch (model.compatibility?.sanitizer ??
|
|
76
|
+
model.route.sanitizer ??
|
|
77
|
+
MODEL_NAMES.find(([name]) => name.test(model.id))?.[1]) {
|
|
75
78
|
case "gemini":
|
|
76
79
|
return gemini(root);
|
|
77
80
|
case "moonshot":
|
|
@@ -81,10 +84,14 @@ const modelCompatibility = (schema, model, protocolDefault) => {
|
|
|
81
84
|
return root;
|
|
82
85
|
}
|
|
83
86
|
};
|
|
87
|
+
// Applied once to every request before any protocol builds its body, including tools in namespaces.
|
|
88
|
+
const tools = (entries, model) => entries.map((tool) => tool.type === "tool"
|
|
89
|
+
? new ToolDefinition({ ...tool, inputSchema: modelCompatibility(tool.inputSchema, model) })
|
|
90
|
+
: { ...tool, tools: tools(tool.tools, model) });
|
|
84
91
|
export const ToolSchemaProjection = {
|
|
85
92
|
gemini,
|
|
86
|
-
modelCompatibility,
|
|
87
93
|
moonshot,
|
|
88
94
|
openAI,
|
|
89
95
|
responses,
|
|
96
|
+
tools,
|
|
90
97
|
};
|
package/dist/route/client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Framing } from "./framing.js";
|
|
|
6
6
|
import { HttpTransport } from "./transport/index.js";
|
|
7
7
|
import type { HttpMiddleware, Transport, TransportRuntime, WebSocketChannelExecutor } from "./transport/index.js";
|
|
8
8
|
import type { Protocol } from "./protocol.js";
|
|
9
|
-
import type { ProtocolID, ProviderOptions } from "../schema/index.js";
|
|
9
|
+
import type { LanguageModelSanitizerCompatibility, ProtocolID, ProviderOptions } from "../schema/index.js";
|
|
10
10
|
import { AIError, CompactionResponse, CompactionCheckpointResponse, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, ProviderID } from "../schema/index.js";
|
|
11
11
|
export interface RouteBody<Body> {
|
|
12
12
|
/** Schema for the validated provider-native body sent as the JSON request. */
|
|
@@ -31,6 +31,7 @@ export interface Route<Body, Prepared = unknown, Compact extends CompactionOpera
|
|
|
31
31
|
readonly defaults: RouteDefaults;
|
|
32
32
|
readonly body: RouteBody<Body>;
|
|
33
33
|
readonly supportsEffortUpdates?: (request: LLMRequest) => boolean;
|
|
34
|
+
readonly sanitizer?: LanguageModelSanitizerCompatibility;
|
|
34
35
|
readonly with: {
|
|
35
36
|
<Next extends CompactionOperations | undefined>(patch: RoutePatch<Body, Prepared> & {
|
|
36
37
|
readonly compact: Next;
|
package/dist/route/client.js
CHANGED
|
@@ -9,6 +9,7 @@ import { applyEffortUpdates } from "../effort-updates.js";
|
|
|
9
9
|
import { normalizeToolHistory } from "../tool-history.js";
|
|
10
10
|
import { sanitizeSurrogates } from "../utils/sanitize.js";
|
|
11
11
|
import * as ProviderShared from "../protocols/shared.js";
|
|
12
|
+
import { ToolSchemaProjection } from "../protocols/utils/tool-schema.js";
|
|
12
13
|
import { AIError, CompactionResponse, CompactionCheckpointResponse, AIErrorReason, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, InvalidProviderOutputError, ProviderConfigurationError, ProviderID, mergeGenerationOptions, mergeHttpOptions, mergeProviderOptions, } from "../schema/index.js";
|
|
13
14
|
const makeRouteLanguageModel = (route, mapped) => {
|
|
14
15
|
const provider = route.provider ?? ("provider" in mapped ? mapped.provider : undefined);
|
|
@@ -118,6 +119,7 @@ function makeFromTransport(input) {
|
|
|
118
119
|
defaults: routeInput.defaults ?? {},
|
|
119
120
|
body: protocol.body,
|
|
120
121
|
supportsEffortUpdates: protocol.supportsEffortUpdates,
|
|
122
|
+
sanitizer: protocol.sanitizer,
|
|
121
123
|
with: (patch) => {
|
|
122
124
|
const { compact, id, provider, providerMetadataKey, auth, transport, endpoint, ...defaults } = patch;
|
|
123
125
|
return build({
|
|
@@ -210,7 +212,7 @@ const prepareRequest = (request) => {
|
|
|
210
212
|
const sanitized = LLMRequest.update(original, sanitizeSurrogates({ ...LLMRequest.input(original), model: undefined }));
|
|
211
213
|
// Deduplicate per sibling level; a tool and a namespace may share a name.
|
|
212
214
|
const dedupe = (tools) => [...new Map(tools.map((tool) => [`${tool.type}:${tool.name}`, tool])).values()].map((tool) => tool.type === "tool" ? tool : { ...tool, tools: dedupe(tool.tools) });
|
|
213
|
-
const resolved = applyCachePolicy(applyEffortUpdates(LLMRequest.update(sanitized, { tools: dedupe(sanitized.tools) })));
|
|
215
|
+
const resolved = applyCachePolicy(applyEffortUpdates(LLMRequest.update(sanitized, { tools: ToolSchemaProjection.tools(dedupe(sanitized.tools), sanitized.model) })));
|
|
214
216
|
const headers = resolved.model.route.headers?.({ request: resolved });
|
|
215
217
|
return headers === undefined
|
|
216
218
|
? resolved
|
package/dist/route/protocol.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema, type Effect } from "effect";
|
|
2
|
-
import type { AIError, LLMEvent, LLMRequest, ProtocolID } from "../schema/index.js";
|
|
2
|
+
import type { AIError, LanguageModelSanitizerCompatibility, LLMEvent, LLMRequest, ProtocolID } from "../schema/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* The semantic API contract of one model server family.
|
|
5
5
|
*
|
|
@@ -42,6 +42,8 @@ export interface Protocol<Body, Frame, Event, State> {
|
|
|
42
42
|
readonly stream: ProtocolStream<Frame, Event, State>;
|
|
43
43
|
/** Whether `body.from` lowers `Message.effort(...)` markers; wrappers around another `body.from` must forward it. */
|
|
44
44
|
readonly supportsEffortUpdates?: (request: LLMRequest) => boolean;
|
|
45
|
+
/** Tool schema sanitizer for every model on this protocol unless the model's compatibility sets one; wrappers around another `body.from` must forward it. */
|
|
46
|
+
readonly sanitizer?: LanguageModelSanitizerCompatibility;
|
|
45
47
|
}
|
|
46
48
|
export interface ProtocolBody<Body> {
|
|
47
49
|
/** Schema for the validated provider-native body sent as the JSON request. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20140",
|
|
4
4
|
"name": "@opencode/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
36
36
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
37
|
-
"@opencode/http-recorder": "0.0.0-dev-
|
|
37
|
+
"@opencode/http-recorder": "0.0.0-dev-20140",
|
|
38
38
|
"@tsconfig/bun": "1.0.9",
|
|
39
39
|
"@types/bun": "1.4.0",
|
|
40
40
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@aws-sdk/credential-providers": "3.1057.0",
|
|
45
45
|
"@smithy/eventstream-codec": "4.2.14",
|
|
46
46
|
"@smithy/util-utf8": "4.2.2",
|
|
47
|
-
"@opencode/schema": "0.0.0-dev-
|
|
47
|
+
"@opencode/schema": "0.0.0-dev-20140",
|
|
48
48
|
"aws4fetch": "1.0.20",
|
|
49
49
|
"effect": "4.0.0-rc.112",
|
|
50
50
|
"google-auth-library": "10.5.0"
|