@opencode-ai/ai
Advanced tools
@@ -606,2 +606,3 @@ import { Schema } from "effect"; | ||
| }, { | ||
| providerMetadataKey: string; | ||
| tools: Partial<Record<number, ToolStream.PendingTool>>; | ||
@@ -608,0 +609,0 @@ reasoningSignatures: {}; |
@@ -316,14 +316,14 @@ import { Buffer } from "node:buffer"; | ||
| }; | ||
| const anthropicMetadata = (metadata) => ({ anthropic: metadata }); | ||
| const signatureFromMetadata = (metadata) => { | ||
| const anthropic = metadata?.anthropic; | ||
| if (!ProviderShared.isRecord(anthropic)) | ||
| const providerMetadata = (key, metadata) => ({ [key]: metadata }); | ||
| const signatureFromMetadata = (metadata, key) => { | ||
| const provider = metadata?.[key]; | ||
| if (!ProviderShared.isRecord(provider)) | ||
| return undefined; | ||
| return typeof anthropic.signature === "string" ? anthropic.signature : undefined; | ||
| return typeof provider.signature === "string" ? provider.signature : undefined; | ||
| }; | ||
| const redactedDataFromMetadata = (metadata) => { | ||
| const anthropic = metadata?.anthropic; | ||
| if (!ProviderShared.isRecord(anthropic)) | ||
| const redactedDataFromMetadata = (metadata, key) => { | ||
| const provider = metadata?.[key]; | ||
| if (!ProviderShared.isRecord(provider)) | ||
| return undefined; | ||
| return typeof anthropic.redactedData === "string" ? anthropic.redactedData : undefined; | ||
| return typeof provider.redactedData === "string" ? provider.redactedData : undefined; | ||
| }; | ||
@@ -383,3 +383,3 @@ const lowerTool = (breakpoints, tool, inputSchema) => ({ | ||
| }; | ||
| const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part) { | ||
| const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part, providerMetadataKey) { | ||
| const wireType = serverToolResultType(part.name); | ||
@@ -390,3 +390,3 @@ if (!wireType) | ||
| // histories constructed directly from provider events. | ||
| const payload = part.providerMetadata?.anthropic?.["result"] ?? part.result.value; | ||
| const payload = part.providerMetadata?.[providerMetadataKey]?.["result"] ?? part.result.value; | ||
| return { | ||
@@ -664,2 +664,3 @@ type: wireType, | ||
| const messages = []; | ||
| const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider); | ||
| for (const [index, message] of request.messages.entries()) { | ||
@@ -708,4 +709,4 @@ if (message.role === "system") { | ||
| // redactedData round-trip as opaque redacted_thinking blocks. | ||
| const signature = part.encrypted ?? signatureFromMetadata(part.providerMetadata); | ||
| const redactedData = redactedDataFromMetadata(part.providerMetadata); | ||
| const signature = part.encrypted ?? signatureFromMetadata(part.providerMetadata, providerMetadataKey); | ||
| const redactedData = redactedDataFromMetadata(part.providerMetadata, providerMetadataKey); | ||
| if (signature === undefined && redactedData !== undefined) { | ||
@@ -740,3 +741,3 @@ content.push({ type: "redacted_thinking", data: redactedData }); | ||
| if (part.type === "tool-result" && part.providerExecuted) { | ||
| content.push(yield* lowerServerToolResult(part)); | ||
| content.push(yield* lowerServerToolResult(part, providerMetadataKey)); | ||
| continue; | ||
@@ -906,3 +907,3 @@ } | ||
| // expose that subset through `output_tokens_details.thinking_tokens`. | ||
| const mapUsage = (usage) => { | ||
| const mapUsage = (usage, providerMetadataKey) => { | ||
| if (!usage) | ||
@@ -922,3 +923,3 @@ return undefined; | ||
| totalTokens: ProviderShared.totalTokens(inputTokens, usage.output_tokens, undefined), | ||
| providerMetadata: { anthropic: usage }, | ||
| providerMetadata: { [providerMetadataKey]: usage }, | ||
| }); | ||
@@ -931,3 +932,3 @@ }; | ||
| // consistent with `nonCached + cacheRead + cacheWrite`. | ||
| const mergeUsage = (left, right) => { | ||
| const mergeUsage = (left, right, providerMetadataKey) => { | ||
| if (!left) | ||
@@ -952,3 +953,4 @@ return right; | ||
| providerMetadata: { | ||
| anthropic: mergeJsonRecords(left.providerMetadata?.["anthropic"], right.providerMetadata?.["anthropic"]) ?? {}, | ||
| [providerMetadataKey]: mergeJsonRecords(left.providerMetadata?.[providerMetadataKey], right.providerMetadata?.[providerMetadataKey]) ?? | ||
| {}, | ||
| }, | ||
@@ -968,3 +970,3 @@ }); | ||
| const isServerToolResultType = (type) => type in SERVER_TOOL_RESULT_NAMES; | ||
| const serverToolResultEvent = (block) => { | ||
| const serverToolResultEvent = (block, providerMetadataKey) => { | ||
| if (!block.type || !isServerToolResultType(block.type)) | ||
@@ -983,3 +985,3 @@ return undefined; | ||
| // stateless requests must round-trip the typed result block verbatim. | ||
| providerMetadata: anthropicMetadata({ blockType: block.type, result: block.content }), | ||
| providerMetadata: providerMetadata(providerMetadataKey, { blockType: block.type, result: block.content }), | ||
| }); | ||
@@ -989,4 +991,4 @@ }; | ||
| const onMessageStart = (state, event) => { | ||
| const usage = mapUsage(event.message?.usage); | ||
| return [usage ? { ...state, usage: mergeUsage(state.usage, usage) } : state, NO_EVENTS]; | ||
| const usage = mapUsage(event.message?.usage, state.providerMetadataKey); | ||
| return [usage ? { ...state, usage: mergeUsage(state.usage, usage, state.providerMetadataKey) } : state, NO_EVENTS]; | ||
| }; | ||
@@ -1037,4 +1039,6 @@ const onContentBlockStart = (state, event) => { | ||
| const id = `reasoning-${event.index ?? 0}`; | ||
| const providerMetadata = block.signature === undefined ? undefined : anthropicMetadata({ signature: block.signature }); | ||
| const lifecycle = Lifecycle.reasoningStart(state.lifecycle, events, id, providerMetadata); | ||
| const metadata = block.signature === undefined | ||
| ? undefined | ||
| : providerMetadata(state.providerMetadataKey, { signature: block.signature }); | ||
| const lifecycle = Lifecycle.reasoningStart(state.lifecycle, events, id, metadata); | ||
| return [ | ||
@@ -1044,3 +1048,3 @@ { | ||
| lifecycle: block.thinking | ||
| ? Lifecycle.reasoningDelta(lifecycle, events, id, block.thinking, providerMetadata) | ||
| ? Lifecycle.reasoningDelta(lifecycle, events, id, block.thinking, metadata) | ||
| : lifecycle, | ||
@@ -1062,3 +1066,3 @@ reasoningSignatures: event.index === undefined || block.signature === undefined | ||
| ...state, | ||
| lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${event.index ?? 0}`, anthropicMetadata({ redactedData: block.data })), | ||
| lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${event.index ?? 0}`, providerMetadata(state.providerMetadataKey, { redactedData: block.data })), | ||
| }, | ||
@@ -1068,3 +1072,3 @@ events, | ||
| } | ||
| const result = serverToolResultEvent(block); | ||
| const result = serverToolResultEvent(block, state.providerMetadataKey); | ||
| if (!result) | ||
@@ -1134,3 +1138,3 @@ return [state, NO_EVENTS]; | ||
| ? Lifecycle.stepStart(state.lifecycle, events) | ||
| : Lifecycle.reasoningEnd(Lifecycle.textEnd(state.lifecycle, events, `text-${event.index}`), events, `reasoning-${event.index}`, signature === undefined ? undefined : anthropicMetadata({ signature })); | ||
| : Lifecycle.reasoningEnd(Lifecycle.textEnd(state.lifecycle, events, `text-${event.index}`), events, `reasoning-${event.index}`, signature === undefined ? undefined : providerMetadata(state.providerMetadataKey, { signature })); | ||
| events.push(...resultEvents); | ||
@@ -1142,3 +1146,3 @@ const reasoningSignatures = { ...state.reasoningSignatures }; | ||
| const onMessageDelta = (state, event) => { | ||
| const usage = mergeUsage(state.usage, mapUsage(event.usage)); | ||
| const usage = mergeUsage(state.usage, mapUsage(event.usage, state.providerMetadataKey), state.providerMetadataKey); | ||
| return [ | ||
@@ -1155,3 +1159,3 @@ { | ||
| ? undefined | ||
| : anthropicMetadata({ stopSequence: event.delta.stop_sequence }), | ||
| : providerMetadata(state.providerMetadataKey, { stopSequence: event.delta.stop_sequence }), | ||
| }, | ||
@@ -1269,3 +1273,4 @@ }, | ||
| event: Protocol.jsonEvent(AnthropicEvent), | ||
| initial: () => ({ | ||
| initial: (request) => ({ | ||
| providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), | ||
| tools: ToolStream.empty(), | ||
@@ -1272,0 +1277,0 @@ reasoningSignatures: {}, |
@@ -130,2 +130,3 @@ import { Schema } from "effect"; | ||
| interface ParserState { | ||
| readonly providerMetadataKey: string; | ||
| readonly tools: ToolStream.State<number>; | ||
@@ -132,0 +133,0 @@ readonly pendingFinish: { |
@@ -184,11 +184,13 @@ import { Effect, Schema } from "effect"; | ||
| }); | ||
| const bedrockMetadata = (metadata) => ({ bedrock: metadata }); | ||
| const reasoningSignature = (part) => { | ||
| const bedrock = part.providerMetadata?.bedrock; | ||
| const providerMetadata = (key, metadata) => ({ [key]: metadata }); | ||
| const reasoningSignature = (part, providerMetadataKey) => { | ||
| const metadata = part.providerMetadata?.[providerMetadataKey]; | ||
| return (part.encrypted ?? | ||
| (ProviderShared.isRecord(bedrock) && typeof bedrock.signature === "string" ? bedrock.signature : undefined)); | ||
| (ProviderShared.isRecord(metadata) && typeof metadata.signature === "string" ? metadata.signature : undefined)); | ||
| }; | ||
| const reasoningRedactedData = (part) => { | ||
| const bedrock = part.providerMetadata?.bedrock; | ||
| return ProviderShared.isRecord(bedrock) && typeof bedrock.redactedData === "string" ? bedrock.redactedData : undefined; | ||
| const reasoningRedactedData = (part, providerMetadataKey) => { | ||
| const metadata = part.providerMetadata?.[providerMetadataKey]; | ||
| return ProviderShared.isRecord(metadata) && typeof metadata.redactedData === "string" | ||
| ? metadata.redactedData | ||
| : undefined; | ||
| }; | ||
@@ -234,2 +236,3 @@ const lowerToolCall = (part) => ({ | ||
| const messages = []; | ||
| const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider); | ||
| for (const message of request.messages) { | ||
@@ -281,4 +284,4 @@ if (message.role === "system") { | ||
| if (part.type === "reasoning") { | ||
| const signature = reasoningSignature(part); | ||
| const redactedData = reasoningRedactedData(part); | ||
| const signature = reasoningSignature(part, providerMetadataKey); | ||
| const redactedData = reasoningRedactedData(part, providerMetadataKey); | ||
| if (signature === undefined && redactedData !== undefined) { | ||
@@ -377,3 +380,3 @@ content.push({ reasoningContent: { redactedContent: redactedData } }); | ||
| // Bedrock does not break reasoning out of outputTokens for current models. | ||
| const mapUsage = (usage) => { | ||
| const mapUsage = (usage, providerMetadataKey) => { | ||
| if (!usage) | ||
@@ -389,3 +392,3 @@ return undefined; | ||
| totalTokens: ProviderShared.totalTokens(inputTokens, usage.outputTokens, usage.totalTokens), | ||
| providerMetadata: { bedrock: usage }, | ||
| providerMetadata: { [providerMetadataKey]: usage }, | ||
| }); | ||
@@ -431,9 +434,9 @@ }; | ||
| const redactedData = reasoning.redactedContent ?? reasoning.data; | ||
| const providerMetadata = reasoning.signature | ||
| ? bedrockMetadata({ signature: reasoning.signature }) | ||
| const metadata = reasoning.signature | ||
| ? providerMetadata(state.providerMetadataKey, { signature: reasoning.signature }) | ||
| : redactedData !== undefined | ||
| ? bedrockMetadata({ redactedData }) | ||
| ? providerMetadata(state.providerMetadataKey, { redactedData }) | ||
| : undefined; | ||
| const lifecycle = reasoning.text !== undefined || providerMetadata !== undefined | ||
| ? Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", providerMetadata) | ||
| const lifecycle = reasoning.text !== undefined || metadata !== undefined | ||
| ? Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", metadata) | ||
| : state.lifecycle; | ||
@@ -469,3 +472,3 @@ return [ | ||
| : Lifecycle.reasoningEnd(Lifecycle.textEnd(state.lifecycle, events, `text-${index}`), events, `reasoning-${index}`, state.reasoningSignatures[index] | ||
| ? bedrockMetadata({ signature: state.reasoningSignatures[index] }) | ||
| ? providerMetadata(state.providerMetadataKey, { signature: state.reasoningSignatures[index] }) | ||
| : undefined); | ||
@@ -501,3 +504,3 @@ events.push(...resultEvents); | ||
| if (event.metadata) { | ||
| const usage = mapUsage(event.metadata.usage) ?? state.pendingFinish?.usage; | ||
| const usage = mapUsage(event.metadata.usage, state.providerMetadataKey) ?? state.pendingFinish?.usage; | ||
| return [ | ||
@@ -559,3 +562,4 @@ { | ||
| event: BedrockEvent, | ||
| initial: () => ({ | ||
| initial: (request) => ({ | ||
| providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), | ||
| tools: ToolStream.empty(), | ||
@@ -562,0 +566,0 @@ pendingFinish: undefined, |
@@ -200,2 +200,3 @@ import { Schema } from "effect"; | ||
| route: string; | ||
| providerMetadataKey: string; | ||
| hasToolCalls: boolean; | ||
@@ -202,0 +203,0 @@ lifecycle: Lifecycle.State; |
+35
-21
@@ -200,15 +200,16 @@ import { Effect, Option, Schema } from "effect"; | ||
| }); | ||
| const googleMetadata = (metadata) => ({ google: metadata }); | ||
| const thoughtSignature = (providerMetadata) => { | ||
| const google = providerMetadata?.google; | ||
| return ProviderShared.isRecord(google) && typeof google.thoughtSignature === "string" | ||
| ? google.thoughtSignature | ||
| const providerMetadata = (key, metadata) => ({ [key]: metadata }); | ||
| const thoughtSignature = (metadata, key) => { | ||
| const value = metadata?.[key]; | ||
| return ProviderShared.isRecord(value) && typeof value.thoughtSignature === "string" | ||
| ? value.thoughtSignature | ||
| : undefined; | ||
| }; | ||
| const lowerToolCall = (part, omitIds) => ({ | ||
| const lowerToolCall = (part, omitIds, metadataKey) => ({ | ||
| functionCall: { ...(omitIds ? {} : { id: part.id }), name: part.name, args: part.input }, | ||
| thoughtSignature: thoughtSignature(part.providerMetadata), | ||
| thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey), | ||
| }); | ||
| const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) { | ||
| const contents = []; | ||
| const metadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider); | ||
| const omitCallIds = omitsFunctionCallIds(request.model.id); | ||
@@ -255,11 +256,15 @@ const legacyToolMedia = routesLegacyToolMedia(request.model.id); | ||
| if (part.type === "text") { | ||
| parts.push({ text: part.text, thoughtSignature: thoughtSignature(part.providerMetadata) }); | ||
| parts.push({ text: part.text, thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey) }); | ||
| continue; | ||
| } | ||
| if (part.type === "reasoning") { | ||
| parts.push({ text: part.text, thought: true, thoughtSignature: thoughtSignature(part.providerMetadata) }); | ||
| parts.push({ | ||
| text: part.text, | ||
| thought: true, | ||
| thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey), | ||
| }); | ||
| continue; | ||
| } | ||
| if (part.type === "tool-call") { | ||
| const lowered = lowerToolCall(part, omitCallIds); | ||
| const lowered = lowerToolCall(part, omitCallIds, metadataKey); | ||
| const signature = lowered.thoughtSignature; | ||
@@ -401,3 +406,3 @@ parts.push({ | ||
| // to produce the inclusive `outputTokens` the rest of the contract expects. | ||
| const mapUsage = (usage) => { | ||
| const mapUsage = (usage, metadataKey) => { | ||
| if (!usage) | ||
@@ -424,3 +429,3 @@ return undefined; | ||
| totalTokens: ProviderShared.totalTokens(promptTokens, outputTokens, usage.totalTokenCount ?? undefined), | ||
| providerMetadata: { google: usage }, | ||
| providerMetadata: providerMetadata(metadataKey, usage), | ||
| }); | ||
@@ -465,5 +470,5 @@ }; | ||
| if (state.reasoningSignature !== undefined) | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", googleMetadata({ thoughtSignature: state.reasoningSignature })); | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", providerMetadata(state.providerMetadataKey, { thoughtSignature: state.reasoningSignature })); | ||
| if (state.textSignature !== undefined) | ||
| lifecycle = Lifecycle.textEnd(lifecycle, events, "text-0", googleMetadata({ thoughtSignature: state.textSignature })); | ||
| lifecycle = Lifecycle.textEnd(lifecycle, events, "text-0", providerMetadata(state.providerMetadataKey, { thoughtSignature: state.textSignature })); | ||
| Lifecycle.finish(lifecycle, events, { | ||
@@ -475,3 +480,5 @@ reason: { | ||
| usage: state.usage, | ||
| providerMetadata: state.promptFeedback === undefined ? undefined : googleMetadata({ promptFeedback: state.promptFeedback }), | ||
| providerMetadata: state.promptFeedback === undefined | ||
| ? undefined | ||
| : providerMetadata(state.providerMetadataKey, { promptFeedback: state.promptFeedback }), | ||
| }); | ||
@@ -484,3 +491,5 @@ return events; | ||
| promptFeedback: event.promptFeedback ?? state.promptFeedback, | ||
| usage: event.usageMetadata ? (mapUsage(event.usageMetadata) ?? state.usage) : state.usage, | ||
| usage: event.usageMetadata | ||
| ? (mapUsage(event.usageMetadata, state.providerMetadataKey) ?? state.usage) | ||
| : state.usage, | ||
| }; | ||
@@ -520,7 +529,9 @@ const candidate = event.candidates?.[0]; | ||
| if (part.thought) { | ||
| lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text, signature ? googleMetadata({ thoughtSignature: signature }) : undefined); | ||
| lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text, signature ? providerMetadata(state.providerMetadataKey, { thoughtSignature: signature }) : undefined); | ||
| continue; | ||
| } | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined); | ||
| lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text, textSignature ? googleMetadata({ thoughtSignature: textSignature }) : undefined); | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature | ||
| ? providerMetadata(state.providerMetadataKey, { thoughtSignature: reasoningSignature }) | ||
| : undefined); | ||
| lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text, textSignature ? providerMetadata(state.providerMetadataKey, { thoughtSignature: textSignature }) : undefined); | ||
| textSignature = undefined; | ||
@@ -540,3 +551,5 @@ continue; | ||
| const id = supplied !== undefined && !duplicate ? supplied : `tool_${crypto.randomUUID().replaceAll("-", "")}`; | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined); | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature | ||
| ? providerMetadata(state.providerMetadataKey, { thoughtSignature: reasoningSignature }) | ||
| : undefined); | ||
| lifecycle = Lifecycle.stepStart(lifecycle, events); | ||
@@ -548,3 +561,3 @@ events.push(LLMEvent.toolCall({ | ||
| providerMetadata: part.thoughtSignature | ||
| ? googleMetadata({ thoughtSignature: part.thoughtSignature }) | ||
| ? providerMetadata(state.providerMetadataKey, { thoughtSignature: part.thoughtSignature }) | ||
| : undefined, | ||
@@ -585,2 +598,3 @@ })); | ||
| route: `${request.model.provider}/${request.model.route.id}`, | ||
| providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), | ||
| hasToolCalls: false, | ||
@@ -587,0 +601,0 @@ lifecycle: Lifecycle.initial(), |
@@ -498,3 +498,3 @@ import { Effect, Schema } from "effect"; | ||
| readonly summary_index: Schema.optional<Schema.Number>; | ||
| readonly item: Schema.optional<Schema.StructWithRest<Schema.Struct<{ | ||
| readonly item: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{ | ||
| readonly type: Schema.String; | ||
@@ -506,3 +506,3 @@ readonly id: Schema.optional<Schema.String>; | ||
| readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>; | ||
| }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>; | ||
| }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>; | ||
| readonly response: Schema.optional<Schema.StructWithRest<Schema.Struct<{ | ||
@@ -1170,3 +1170,3 @@ readonly id: Schema.optional<Schema.String>; | ||
| readonly call_id?: string | undefined; | ||
| } | undefined; | ||
| } | null | undefined; | ||
| readonly delta?: string | undefined; | ||
@@ -1173,0 +1173,0 @@ readonly response?: { |
@@ -245,3 +245,4 @@ import { Effect, Schema } from "effect"; | ||
| summary_index: Schema.optional(Schema.Number), | ||
| item: Schema.optional(StreamItem), | ||
| // OutputItemAdded/Done permit a null item in the Open Responses OpenAPI schema. | ||
| item: optionalNull(StreamItem), | ||
| response: Schema.optional(Schema.StructWithRest(Schema.Struct({ | ||
@@ -602,3 +603,3 @@ id: Schema.optional(Schema.String), | ||
| }); | ||
| const isReasoningItem = (item) => item.type === "reasoning" && typeof item.id === "string" && item.id.length > 0; | ||
| const isReasoningItem = (item) => item.type === "reasoning" && typeof item.id === "string"; | ||
| const NO_EVENTS = []; | ||
@@ -674,3 +675,3 @@ // `response.completed` / `response.incomplete` are clean finishes that emit a | ||
| const item = event.item; | ||
| if (item?.type === "message" && item.id) { | ||
| if (item?.type === "message" && item.id !== undefined) { | ||
| const phase = messagePhase(item.phase); | ||
@@ -707,3 +708,3 @@ return [ | ||
| const id = item.id ?? item.call_id; | ||
| const metadata = item.id ? providerMetadata(state, { itemId: item.id }) : undefined; | ||
| const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined; | ||
| const events = []; | ||
@@ -726,3 +727,3 @@ const lifecycle = Lifecycle.stepStart(state.lifecycle, events); | ||
| const onReasoningSummaryPartAdded = (state, event) => { | ||
| if (!event.item_id || event.summary_index === undefined) | ||
| if (event.item_id === undefined || event.summary_index === undefined) | ||
| return [state, NO_EVENTS]; | ||
@@ -757,3 +758,3 @@ const item = state.reasoningItems[event.item_id]; | ||
| const onReasoningSummaryPartDone = (state, event) => { | ||
| if (!event.item_id || event.summary_index === undefined) | ||
| if (event.item_id === undefined || event.summary_index === undefined) | ||
| return [state, NO_EVENTS]; | ||
@@ -781,3 +782,3 @@ const item = state.reasoningItems[event.item_id]; | ||
| const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgumentsDelta")(function* (state, event) { | ||
| if (!event.item_id) | ||
| if (event.item_id === undefined) | ||
| return [state, NO_EVENTS]; | ||
@@ -810,3 +811,3 @@ const tool = state.tools[event.item_id]; | ||
| return [state, NO_EVENTS]; | ||
| if (item.type === "message" && item.id) { | ||
| if (item.type === "message" && item.id !== undefined) { | ||
| const itemPhase = messagePhase(item.phase); | ||
@@ -837,3 +838,3 @@ const phase = itemPhase === undefined ? state.messagePhases[item.id] : itemPhase; | ||
| name: item.name, | ||
| providerMetadata: item.id ? providerMetadata(state, { itemId: item.id }) : undefined, | ||
| providerMetadata: item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined, | ||
| }); | ||
@@ -886,3 +887,3 @@ const result = item.arguments === undefined | ||
| const id = item.id ?? (item.type === "function_call" ? item.call_id : undefined); | ||
| if (!id || | ||
| if (id === undefined || | ||
| ((item.type !== "function_call" || !current.tools[id]) && | ||
@@ -958,7 +959,8 @@ (item.type !== "reasoning" || !current.reasoningItems[id]))) | ||
| export const step = (state, input) => { | ||
| const event = input.item_id && outputItemID(state, input) !== input.item_id | ||
| // The OpenAPI requires string IDs but imposes no minLength; empty is not missing. | ||
| const event = input.item_id !== undefined && outputItemID(state, input) !== input.item_id | ||
| ? { ...input, item_id: outputItemID(state, input) } | ||
| : input; | ||
| if (event.type === "response.output_text.delta" || event.type === "response.output_text.done") { | ||
| if (!event.item_id) | ||
| if (event.item_id === undefined) | ||
| return ProviderShared.eventError(state.id, `${event.type} is missing item_id`); | ||
@@ -971,3 +973,3 @@ return Effect.succeed(event.type === "response.output_text.delta" | ||
| const value = event.type === "response.refusal.delta" ? event.delta : event.refusal; | ||
| if (!event.item_id || typeof value !== "string") | ||
| if (event.item_id === undefined || typeof value !== "string") | ||
| return ProviderShared.eventError(state.id, `${event.type} is malformed`); | ||
@@ -979,3 +981,3 @@ return Effect.succeed(event.type === "response.refusal.delta" | ||
| if (event.type === "response.reasoning.delta" || event.type === "response.reasoning_summary_text.delta") { | ||
| if (!event.item_id) | ||
| if (event.item_id === undefined) | ||
| return ProviderShared.eventError(state.id, `${event.type} is missing item_id`); | ||
@@ -987,3 +989,3 @@ return Effect.succeed(onReasoningDelta(state, event, event.item_id)); | ||
| event.type === "response.reasoning_text.done") { | ||
| if (!event.item_id) | ||
| if (event.item_id === undefined) | ||
| return ProviderShared.eventError(state.id, `${event.type} is missing item_id`); | ||
@@ -993,14 +995,14 @@ return Effect.succeed(onReasoningDone(state, event, event.item_id)); | ||
| if (event.type === "response.reasoning_summary_part.added") | ||
| return event.item_id | ||
| return event.item_id !== undefined | ||
| ? Effect.succeed(onReasoningSummaryPartAdded(state, event)) | ||
| : ProviderShared.eventError(state.id, `${event.type} is missing item_id`); | ||
| if (event.type === "response.reasoning_summary_part.done") | ||
| return event.item_id | ||
| return event.item_id !== undefined | ||
| ? Effect.succeed(onReasoningSummaryPartDone(state, event)) | ||
| : ProviderShared.eventError(state.id, `${event.type} is missing item_id`); | ||
| if (event.type === "response.output_item.added") { | ||
| if (event.item?.type === "message" && !event.item.id) | ||
| if (event.item?.type === "message" && event.item.id === undefined) | ||
| return ProviderShared.eventError(state.id, `${event.type} message is missing id`); | ||
| const id = event.item?.id ?? (event.item?.type === "function_call" ? event.item.call_id : undefined); | ||
| return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && id | ||
| return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && id !== undefined | ||
| ? { ...state, outputItems: { ...state.outputItems, [event.output_index]: id } } | ||
@@ -1010,7 +1012,7 @@ : state, event)); | ||
| if (event.type === "response.function_call_arguments.delta" || event.type === "response.function_call_arguments.done") | ||
| return event.item_id | ||
| return event.item_id !== undefined | ||
| ? onFunctionCallArgumentsDelta(state, event) | ||
| : ProviderShared.eventError(state.id, `${event.type} is missing item_id`); | ||
| if (event.type === "response.output_item.done") { | ||
| if (event.item?.type === "message" && !event.item.id) | ||
| if (event.item?.type === "message" && event.item.id === undefined) | ||
| return ProviderShared.eventError(state.id, `${event.type} message is missing id`); | ||
@@ -1017,0 +1019,0 @@ return onOutputItemDone(state, event); |
@@ -272,2 +272,3 @@ import { Effect, Schema } from "effect"; | ||
| export interface ParserState { | ||
| readonly providerMetadataKey: string; | ||
| readonly tools: ToolStream.State<number>; | ||
@@ -274,0 +275,0 @@ readonly pendingTools: Partial<Record<number, PendingToolDelta>>; |
@@ -210,12 +210,12 @@ import { Effect, Schema } from "effect"; | ||
| const openAICompatibleReasoningContent = (native) => isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined; | ||
| const reasoningField = (part) => { | ||
| const field = part.providerMetadata?.openai?.reasoningField; | ||
| const reasoningField = (part, providerMetadataKey) => { | ||
| const field = part.providerMetadata?.[providerMetadataKey]?.reasoningField; | ||
| return typeof field === "string" ? field : undefined; | ||
| }; | ||
| const reasoningDetails = (parts, native) => { | ||
| const reasoningDetails = (parts, native, providerMetadataKey) => { | ||
| const observed = parts.flatMap((part) => { | ||
| const details = part.providerMetadata?.openai?.reasoningDetails; | ||
| const details = part.providerMetadata?.[providerMetadataKey]?.reasoningDetails; | ||
| return Array.isArray(details) ? details : []; | ||
| }); | ||
| if (parts.some((part) => Array.isArray(part.providerMetadata?.openai?.reasoningDetails))) | ||
| if (parts.some((part) => Array.isArray(part.providerMetadata?.[providerMetadataKey]?.reasoningDetails))) | ||
| return observed; | ||
@@ -266,6 +266,8 @@ if (isRecord(native) && Array.isArray(native.reasoning_details)) | ||
| const text = reasoning.map((part) => part.text).join(""); | ||
| const details = reasoningDetails(reasoning, message.native?.openaiCompatible); | ||
| const observedField = reasoning.map(reasoningField).find((value) => value !== undefined); | ||
| const details = reasoningDetails(reasoning, message.native?.openaiCompatible, options.providerMetadataKey); | ||
| const observedField = reasoning | ||
| .map((part) => reasoningField(part, options.providerMetadataKey)) | ||
| .find((value) => value !== undefined); | ||
| const nativeReasoning = openAICompatibleReasoningContent(message.native?.openaiCompatible); | ||
| const fullyStructured = reasoning.every((part) => Array.isArray(part.providerMetadata?.openai?.reasoningDetails)); | ||
| const fullyStructured = reasoning.every((part) => Array.isArray(part.providerMetadata?.[options.providerMetadataKey]?.reasoningDetails)); | ||
| const field = (() => { | ||
@@ -364,2 +366,3 @@ if (configuredField !== undefined && (requireReasoning || reasoning.length > 0 || nativeReasoning !== undefined)) | ||
| ...options, | ||
| providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), | ||
| toolCallID: (id) => { | ||
@@ -650,3 +653,3 @@ if (mistral) | ||
| // `prompt_cache_hit_tokens`, and Zai uses top-level `cached_tokens`. | ||
| const mapUsage = (usage) => { | ||
| const mapUsage = (usage, providerMetadataKey) => { | ||
| if (!usage) | ||
@@ -671,3 +674,3 @@ return undefined; | ||
| totalTokens: ProviderShared.totalTokens(input, output, usage.total_tokens ?? undefined), | ||
| providerMetadata: { openai: usage }, | ||
| providerMetadata: { [providerMetadataKey]: usage }, | ||
| }); | ||
@@ -733,4 +736,4 @@ }; | ||
| const conflictingDetailValue = (previous, current) => previous !== undefined && previous !== null && current !== undefined && current !== null && previous !== current; | ||
| const reasoningMetadata = (field, details) => ({ | ||
| openai: { | ||
| const reasoningMetadata = (providerMetadataKey, field, details) => ({ | ||
| [providerMetadataKey]: { | ||
| ...(field ? { reasoningField: field } : {}), | ||
@@ -760,3 +763,5 @@ ...(details ? { reasoningDetails: details } : {}), | ||
| const choiceUsage = choice?.usage; | ||
| const usage = mapUsage(event.usage) ?? (choiceUsage ? mapUsage(choiceUsage) : undefined) ?? state.usage; | ||
| const usage = mapUsage(event.usage, state.providerMetadataKey) ?? | ||
| (choiceUsage ? mapUsage(choiceUsage, state.providerMetadataKey) : undefined) ?? | ||
| state.usage; | ||
| const rawFinishReason = choice?.finish_reason; | ||
@@ -792,3 +797,3 @@ const finishReason = rawFinishReason | ||
| const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined; | ||
| const deltaMetadata = reasoningMetadata(reasoningField); | ||
| const deltaMetadata = reasoningMetadata(state.providerMetadataKey, reasoningField); | ||
| const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text; | ||
@@ -803,7 +808,7 @@ if (text !== undefined) | ||
| if (delta?.content) { | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningMetadata(reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined)); | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningMetadata(state.providerMetadataKey, reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined)); | ||
| lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content); | ||
| } | ||
| if (delta?.refusal) { | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningMetadata(reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined)); | ||
| lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningMetadata(state.providerMetadataKey, reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined)); | ||
| lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.refusal); | ||
@@ -853,2 +858,3 @@ } | ||
| { | ||
| providerMetadataKey: state.providerMetadataKey, | ||
| tools: finished?.tools ?? tools, | ||
@@ -893,5 +899,5 @@ pendingTools, | ||
| : { normalized: hasToolCalls ? "tool-calls" : "stop" }; | ||
| const metadata = reasoningMetadata(state.reasoningField, state.reasoningDetailsObserved ? state.reasoningDetails : undefined); | ||
| const metadata = reasoningMetadata(state.providerMetadataKey, state.reasoningField, state.reasoningDetailsObserved ? state.reasoningDetails : undefined); | ||
| const started = state.reasoningDetailsObserved && !state.reasoningEmitted | ||
| ? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField)) | ||
| ? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.providerMetadataKey, state.reasoningField)) | ||
| : state.lifecycle; | ||
@@ -922,2 +928,3 @@ const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata); | ||
| initial: (request) => ({ | ||
| providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), | ||
| tools: ToolStream.empty(), | ||
@@ -924,0 +931,0 @@ pendingTools: {}, |
@@ -389,3 +389,3 @@ import { Schema } from "effect"; | ||
| readonly call_id?: string | undefined; | ||
| } | undefined; | ||
| } | null | undefined; | ||
| readonly delta?: string | undefined; | ||
@@ -392,0 +392,0 @@ readonly response?: { |
@@ -150,3 +150,3 @@ import { Effect, Encoding, Schema } from "effect"; | ||
| if (event.type === "response.reasoning_text.delta") | ||
| return event.item_id | ||
| return event.item_id !== undefined | ||
| ? Effect.succeed(OpenResponses.onReasoningDelta(state, event, OpenResponses.outputItemID(state, event) ?? event.item_id)) | ||
@@ -153,0 +153,0 @@ : ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`); |
@@ -190,3 +190,3 @@ import { Protocol } from "../route/protocol.js"; | ||
| readonly call_id?: string | undefined; | ||
| } | undefined; | ||
| } | null | undefined; | ||
| readonly delta?: string | undefined; | ||
@@ -193,0 +193,0 @@ readonly response?: { |
@@ -20,2 +20,3 @@ import { Route, type RouteDefaultsInput } from "../route/client.js"; | ||
| readonly region?: string; | ||
| readonly topP?: number; | ||
| readonly providerOptions?: OpenAIProviderOptionsInput; | ||
@@ -22,0 +23,0 @@ } |
@@ -12,3 +12,3 @@ import { Auth } from "../route/auth.js"; | ||
| provider: id, | ||
| providerMetadataKey: OpenAIResponses.route.providerMetadataKey, | ||
| providerMetadataKey: "mantle", | ||
| protocol: OpenAIResponses.protocol, | ||
@@ -23,2 +23,3 @@ endpoint: OpenAIResponses.route.endpoint, | ||
| provider: id, | ||
| providerMetadataKey: "mantle", | ||
| }); | ||
@@ -52,3 +53,3 @@ export const routes = [responsesRoute, chatRoute]; | ||
| id, | ||
| model: chat, | ||
| model: responses, | ||
| chat, | ||
@@ -69,2 +70,3 @@ responses, | ||
| credentials: settings.credentials, | ||
| generation: settings.topP === undefined ? undefined : { topP: settings.topP }, | ||
| headers: settings.headers === undefined ? undefined : { ...settings.headers }, | ||
@@ -78,2 +80,2 @@ http: settings.body === undefined ? undefined : { body: { ...settings.body } }, | ||
| export const responsesModel = (modelID, settings) => configure(config(settings)).responses(modelID); | ||
| export const model = chatModel; | ||
| export const model = responsesModel; |
@@ -13,2 +13,3 @@ import { Auth } from "../route/auth.js"; | ||
| provider: id, | ||
| providerMetadataKey: "bedrock", | ||
| endpoint: { baseURL: baseURL ?? bedrockBaseURL(resolvedRegion) }, | ||
@@ -15,0 +16,0 @@ auth: apiKey === undefined ? BedrockConverse.sigV4Auth(credentials) : Auth.bearer(apiKey), |
@@ -1,2 +0,2 @@ | ||
| export { chatModel as model } from "../amazon-bedrock-mantle.js"; | ||
| export { responsesModel as model } from "../amazon-bedrock-mantle.js"; | ||
| export type { Settings } from "../amazon-bedrock-mantle.js"; |
@@ -1,1 +0,1 @@ | ||
| export { chatModel as model } from "../amazon-bedrock-mantle.js"; | ||
| export { responsesModel as model } from "../amazon-bedrock-mantle.js"; |
@@ -8,2 +8,3 @@ import { OpenAICompatibleChat } from "../protocols/openai-compatible-chat.js"; | ||
| provider: id, | ||
| providerMetadataKey: "vertex", | ||
| }); | ||
@@ -10,0 +11,0 @@ export const routes = [route]; |
@@ -8,2 +8,3 @@ import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses.js"; | ||
| provider: id, | ||
| providerMetadataKey: "vertex", | ||
| providerOptions: { store: false }, | ||
@@ -10,0 +11,0 @@ }); |
@@ -41,3 +41,3 @@ import { Effect } from "effect"; | ||
| provider: id, | ||
| providerMetadataKey: "google", | ||
| providerMetadataKey: "vertex", | ||
| protocol, | ||
@@ -44,0 +44,0 @@ endpoint: Endpoint.path(({ request }) => { |
@@ -87,2 +87,3 @@ import { Effect, Schema } from "effect"; | ||
| provider: profile.provider, | ||
| providerMetadataKey: "openrouter", | ||
| protocol, | ||
@@ -89,0 +90,0 @@ endpoint: Endpoint.path("/chat/completions", { baseURL: profile.baseURL }), |
@@ -52,2 +52,3 @@ import { Context, Effect, Layer, Schema, Stream } from "effect"; | ||
| readonly provider?: string | ProviderID; | ||
| readonly providerMetadataKey?: string; | ||
| readonly auth?: Auth.Definition; | ||
@@ -54,0 +55,0 @@ readonly transport?: Transport<Body, Prepared, unknown>; |
@@ -101,3 +101,3 @@ import { Cause, Context, Effect, Layer, Schema, Stream } from "effect"; | ||
| with: (patch) => { | ||
| const { id, provider, auth, transport, endpoint, ...defaults } = patch; | ||
| const { id, provider, providerMetadataKey, auth, transport, endpoint, ...defaults } = patch; | ||
| return build({ | ||
@@ -107,2 +107,6 @@ ...routeInput, | ||
| provider: provider ?? routeInput.provider, | ||
| providerMetadataKey: providerMetadataKey ?? | ||
| (provider !== undefined && String(provider) !== String(routeInput.provider) | ||
| ? String(provider) | ||
| : routeInput.providerMetadataKey), | ||
| auth: auth ?? routeInput.auth, | ||
@@ -109,0 +113,0 @@ endpoint: endpoint ? Endpoint.merge(routeInput.endpoint, endpoint) : routeInput.endpoint, |
@@ -9,10 +9,2 @@ import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect"; | ||
| const normalizedHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])); | ||
| const requestId = (headers) => { | ||
| return (headers["x-request-id"] ?? | ||
| headers["request-id"] ?? | ||
| headers["x-amzn-requestid"] ?? | ||
| headers["x-amz-request-id"] ?? | ||
| headers["x-goog-request-id"] ?? | ||
| headers["cf-ray"]); | ||
| }; | ||
| const retryAfterMs = (headers) => { | ||
@@ -99,3 +91,2 @@ const millis = Number(headers["retry-after-ms"]); | ||
| ...input.body, | ||
| requestId: input.requestId, | ||
| rateLimit: input.rateLimit, | ||
@@ -123,3 +114,2 @@ }); | ||
| body: details, | ||
| requestId: requestId(headers), | ||
| rateLimit, | ||
@@ -152,3 +142,2 @@ }), | ||
| ...details, | ||
| requestId: requestId(headers), | ||
| rateLimit, | ||
@@ -155,0 +144,0 @@ }), |
@@ -31,3 +31,2 @@ import { Schema } from "effect"; | ||
| readonly bodyTruncated: Schema.optional<Schema.Boolean>; | ||
| readonly requestId: Schema.optional<Schema.String>; | ||
| readonly rateLimit: Schema.optional<typeof HttpRateLimitDetails>; | ||
@@ -34,0 +33,0 @@ }>, {}>; |
@@ -29,3 +29,2 @@ import { Schema } from "effect"; | ||
| bodyTruncated: Schema.optional(Schema.Boolean), | ||
| requestId: Schema.optional(Schema.String), | ||
| rateLimit: Schema.optional(HttpRateLimitDetails), | ||
@@ -32,0 +31,0 @@ }) { |
+3
-3
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "version": "0.0.0-beta-18314", | ||
| "version": "0.0.0-beta-18371", | ||
| "name": "@opencode-ai/ai", | ||
@@ -33,3 +33,3 @@ "type": "module", | ||
| "@effect/platform-node": "4.0.0-rc.111", | ||
| "@opencode-ai/http-recorder": "0.0.0-beta-18314", | ||
| "@opencode-ai/http-recorder": "0.0.0-beta-18371", | ||
| "@tsconfig/bun": "1.0.9", | ||
@@ -43,3 +43,3 @@ "@types/bun": "1.3.13", | ||
| "@smithy/util-utf8": "4.2.2", | ||
| "@opencode-ai/schema": "0.0.0-beta-18314", | ||
| "@opencode-ai/schema": "0.0.0-beta-18371", | ||
| "aws4fetch": "1.0.20", | ||
@@ -46,0 +46,0 @@ "effect": "4.0.0-rc.111", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1365812
0.25%31414
0.11%+ Added
- Removed