@opencode-ai/ai
Advanced tools
@@ -553,3 +553,2 @@ import { Effect, Schema } from "effect"; | ||
| export type Event = Schema.Schema.Type<typeof Event>; | ||
| export type ItemKind = "message" | "reasoning" | "function-call" | "hosted-tool"; | ||
| export interface Extension { | ||
@@ -564,3 +563,2 @@ readonly id: string; | ||
| readonly lowerHostedToolItem?: (item: unknown) => ExtendedHostedToolItem | undefined; | ||
| readonly acceptsItemID?: (kind: ItemKind, id: string) => boolean; | ||
| } | ||
@@ -567,0 +565,0 @@ export interface ParserState { |
@@ -284,21 +284,16 @@ import { Effect, Schema } from "effect"; | ||
| }); | ||
| // Servers validate item ids on replayed history, and a malformed or oversized | ||
| // id can fail an otherwise valid request. Only server-issued tokens are worth | ||
| // resending; anything else is treated as absent so the item is resent without | ||
| // an id (or skipped, for items that cannot be expressed without one). | ||
| const ITEM_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/; | ||
| // Server-issued item ids need a nonempty prefix and suffix, but the prefix is | ||
| // provider-defined and does not necessarily identify the item's semantic type. | ||
| const itemID = (providerMetadata, providerMetadataKey) => { | ||
| const metadata = providerMetadata?.[providerMetadataKey]; | ||
| return ProviderShared.isRecord(metadata) && | ||
| typeof metadata.itemId === "string" && | ||
| ITEM_ID_PATTERN.test(metadata.itemId) | ||
| ? metadata.itemId | ||
| : undefined; | ||
| if (!ProviderShared.isRecord(metadata) || typeof metadata.itemId !== "string") | ||
| return undefined; | ||
| const separator = metadata.itemId.indexOf("_"); | ||
| return separator > 0 && separator < metadata.itemId.length - 1 ? metadata.itemId : undefined; | ||
| }; | ||
| const acceptsItemID = (extension, kind, id) => id !== undefined && (extension.acceptsItemID?.(kind, id) ?? true); | ||
| const lowerToolCall = (part, providerMetadataKey, extension) => { | ||
| const lowerToolCall = (part, providerMetadataKey) => { | ||
| const id = itemID(part.providerMetadata, providerMetadataKey); | ||
| return { | ||
| type: "function_call", | ||
| ...(acceptsItemID(extension, "function-call", id) ? { id } : {}), | ||
| ...(id === undefined ? {} : { id }), | ||
| call_id: part.id, | ||
@@ -309,7 +304,7 @@ name: part.name, | ||
| }; | ||
| const lowerReasoning = (part, providerMetadataKey, extension) => { | ||
| const lowerReasoning = (part, providerMetadataKey) => { | ||
| const metadata = part.providerMetadata?.[providerMetadataKey]; | ||
| if (!ProviderShared.isRecord(metadata)) | ||
| return undefined; | ||
| const id = itemID(part.providerMetadata, providerMetadataKey); | ||
| if (!ProviderShared.isRecord(metadata) || !acceptsItemID(extension, "reasoning", id)) | ||
| return undefined; | ||
| const encryptedContent = typeof metadata.reasoningEncryptedContent === "string" || metadata.reasoningEncryptedContent === null | ||
@@ -320,3 +315,3 @@ ? metadata.reasoningEncryptedContent | ||
| type: "reasoning", | ||
| id, | ||
| ...(id === undefined ? {} : { id }), | ||
| summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [], | ||
@@ -406,4 +401,3 @@ encrypted_content: encryptedContent, | ||
| const metadata = part.providerMetadata?.[providerMetadataKey]; | ||
| const rawID = itemID(part.providerMetadata, providerMetadataKey); | ||
| const id = acceptsItemID(extension, "message", rawID) ? rawID : undefined; | ||
| const id = itemID(part.providerMetadata, providerMetadataKey); | ||
| const phase = ProviderShared.isRecord(metadata) ? messagePhase(metadata.phase) : undefined; | ||
@@ -433,6 +427,6 @@ const group = groups.at(-1); | ||
| flushText(); | ||
| const reasoning = lowerReasoning(part, providerMetadataKey, extension); | ||
| const reasoning = lowerReasoning(part, providerMetadataKey); | ||
| if (!reasoning) | ||
| continue; | ||
| const existing = reasoningItems[reasoning.id]; | ||
| const existing = reasoning.id === undefined ? undefined : reasoningItems[reasoning.id]; | ||
| if (existing) { | ||
@@ -444,3 +438,4 @@ existing.summary.push(...reasoning.summary); | ||
| } | ||
| reasoningItems[reasoning.id] = reasoning; | ||
| if (reasoning.id !== undefined) | ||
| reasoningItems[reasoning.id] = reasoning; | ||
| input.push(reasoning); | ||
@@ -453,3 +448,3 @@ continue; | ||
| continue; | ||
| input.push(lowerToolCall(part, providerMetadataKey, extension)); | ||
| input.push(lowerToolCall(part, providerMetadataKey)); | ||
| continue; | ||
@@ -465,3 +460,3 @@ } | ||
| : extension.lowerHostedToolItem?.(part.result.value); | ||
| if (acceptsItemID(extension, "hosted-tool", id) && hosted?.id === id) { | ||
| if (id !== undefined && hosted?.id === id) { | ||
| if (!hostedToolItems.has(id)) { | ||
@@ -468,0 +463,0 @@ input.push(hosted); |
@@ -71,15 +71,2 @@ import { Effect, Encoding, Schema } from "effect"; | ||
| }); | ||
| // Replayed items are paired with stored server state by id, so a foreign or | ||
| // synthetic token can fail request validation even when `call_id` pairing is | ||
| // intact. Only resend ids in each item kind's own grammar; hosted tool | ||
| // items keep generic validation because every hosted tool mints its own | ||
| // prefix. The same allowlist approach codex uses before resending history | ||
| // (codex-rs core/src/client.rs, `prepare_response_items_for_request`). | ||
| const ITEM_ID_PREFIXES = { | ||
| message: ["msg_"], | ||
| reasoning: ["rs_"], | ||
| "function-call": ["fc_"], | ||
| // Every hosted tool mints its own id prefix, so items keep generic validation. | ||
| "hosted-tool": [], | ||
| }; | ||
| const extension = { | ||
@@ -89,6 +76,2 @@ id: ADAPTER, | ||
| lowerHostedToolItem: (item) => (Schema.is(OpenAIResponsesHostedToolItem)(item) ? item : undefined), | ||
| acceptsItemID: (kind, id) => { | ||
| const prefixes = ITEM_ID_PREFIXES[kind]; | ||
| return prefixes.length === 0 || prefixes.some((prefix) => id.startsWith(prefix)); | ||
| }, | ||
| }; | ||
@@ -95,0 +78,0 @@ const nativeImageToolInput = (tool) => { |
+3
-3
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "version": "0.0.0-dev-18263", | ||
| "version": "0.0.0-dev-18266", | ||
| "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-dev-18263", | ||
| "@opencode-ai/http-recorder": "0.0.0-dev-18266", | ||
| "@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-dev-18263", | ||
| "@opencode-ai/schema": "0.0.0-dev-18266", | ||
| "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.
1345194
-0.1%30947
-0.08%+ Added
- Removed