@opencode-ai/ai 0.0.0-dev-18263 → 0.0.0-dev-18266

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.
@@ -550,7 +550,6 @@ export declare const Event: Schema.StructWithRest<Schema.Struct<{
550
550
  readonly headers: Schema.optional<Schema.Unknown>;
551
551
  }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
552
552
  export type Event = Schema.Schema.Type<typeof Event>;
553
- export type ItemKind = "message" | "reasoning" | "function-call" | "hosted-tool";
554
553
  export interface Extension {
555
554
  readonly id: string;
556
555
  readonly name: string;
@@ -560,7 +559,6 @@ export interface Extension {
560
559
  readonly request: LLMRequest;
561
560
  }) => MediaInput | undefined;
562
561
  readonly lowerHostedToolItem?: (item: unknown) => ExtendedHostedToolItem | undefined;
563
- readonly acceptsItemID?: (kind: ItemKind, id: string) => boolean;
564
562
  }
565
563
  export interface ParserState {
566
564
  readonly id: string;
@@ -281,41 +281,36 @@ export const lowerToolChoice = (protocolName, toolChoice) => ProviderShared.matc
281
281
  required: () => "required",
282
282
  tool: (toolName) => ({ type: "function", name: toolName }),
283
283
  });
284
- // Servers validate item ids on replayed history, and a malformed or oversized
285
- // id can fail an otherwise valid request. Only server-issued tokens are worth
286
- // resending; anything else is treated as absent so the item is resent without
287
- // an id (or skipped, for items that cannot be expressed without one).
288
- const ITEM_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
284
+ // Server-issued item ids need a nonempty prefix and suffix, but the prefix is
285
+ // provider-defined and does not necessarily identify the item's semantic type.
289
286
  const itemID = (providerMetadata, providerMetadataKey) => {
290
287
  const metadata = providerMetadata?.[providerMetadataKey];
291
- return ProviderShared.isRecord(metadata) &&
292
- typeof metadata.itemId === "string" &&
293
- ITEM_ID_PATTERN.test(metadata.itemId)
294
- ? metadata.itemId
295
- : undefined;
288
+ if (!ProviderShared.isRecord(metadata) || typeof metadata.itemId !== "string")
289
+ return undefined;
290
+ const separator = metadata.itemId.indexOf("_");
291
+ return separator > 0 && separator < metadata.itemId.length - 1 ? metadata.itemId : undefined;
296
292
  };
297
- const acceptsItemID = (extension, kind, id) => id !== undefined && (extension.acceptsItemID?.(kind, id) ?? true);
298
- const lowerToolCall = (part, providerMetadataKey, extension) => {
293
+ const lowerToolCall = (part, providerMetadataKey) => {
299
294
  const id = itemID(part.providerMetadata, providerMetadataKey);
300
295
  return {
301
296
  type: "function_call",
302
- ...(acceptsItemID(extension, "function-call", id) ? { id } : {}),
297
+ ...(id === undefined ? {} : { id }),
303
298
  call_id: part.id,
304
299
  name: part.name,
305
300
  arguments: ProviderShared.encodeJson(part.input),
306
301
  };
307
302
  };
308
- const lowerReasoning = (part, providerMetadataKey, extension) => {
303
+ const lowerReasoning = (part, providerMetadataKey) => {
309
304
  const metadata = part.providerMetadata?.[providerMetadataKey];
310
- const id = itemID(part.providerMetadata, providerMetadataKey);
311
- if (!ProviderShared.isRecord(metadata) || !acceptsItemID(extension, "reasoning", id))
305
+ if (!ProviderShared.isRecord(metadata))
312
306
  return undefined;
307
+ const id = itemID(part.providerMetadata, providerMetadataKey);
313
308
  const encryptedContent = typeof metadata.reasoningEncryptedContent === "string" || metadata.reasoningEncryptedContent === null
314
309
  ? metadata.reasoningEncryptedContent
315
310
  : undefined;
316
311
  return {
317
312
  type: "reasoning",
318
- id,
313
+ ...(id === undefined ? {} : { id }),
319
314
  summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [],
320
315
  encrypted_content: encryptedContent,
321
316
  };
@@ -400,8 +395,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
400
395
  return;
401
396
  const groups = content.reduce((groups, part) => {
402
397
  const metadata = part.providerMetadata?.[providerMetadataKey];
403
- const rawID = itemID(part.providerMetadata, providerMetadataKey);
404
- const id = acceptsItemID(extension, "message", rawID) ? rawID : undefined;
398
+ const id = itemID(part.providerMetadata, providerMetadataKey);
405
399
  const phase = ProviderShared.isRecord(metadata) ? messagePhase(metadata.phase) : undefined;
406
400
  const group = groups.at(-1);
407
401
  if (group && group.id === id && group.phase === phase)
@@ -426,17 +420,18 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
426
420
  }
427
421
  if (part.type === "reasoning") {
428
422
  flushText();
429
- const reasoning = lowerReasoning(part, providerMetadataKey, extension);
423
+ const reasoning = lowerReasoning(part, providerMetadataKey);
430
424
  if (!reasoning)
431
425
  continue;
432
- const existing = reasoningItems[reasoning.id];
426
+ const existing = reasoning.id === undefined ? undefined : reasoningItems[reasoning.id];
433
427
  if (existing) {
434
428
  existing.summary.push(...reasoning.summary);
435
429
  if (typeof reasoning.encrypted_content === "string")
436
430
  existing.encrypted_content = reasoning.encrypted_content;
437
431
  continue;
438
432
  }
439
- reasoningItems[reasoning.id] = reasoning;
433
+ if (reasoning.id !== undefined)
434
+ reasoningItems[reasoning.id] = reasoning;
440
435
  input.push(reasoning);
441
436
  continue;
442
437
  }
@@ -444,7 +439,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
444
439
  flushText();
445
440
  if (part.providerExecuted === true)
446
441
  continue;
447
- input.push(lowerToolCall(part, providerMetadataKey, extension));
442
+ input.push(lowerToolCall(part, providerMetadataKey));
448
443
  continue;
449
444
  }
450
445
  if (part.type === "tool-result" && part.providerExecuted === true) {
@@ -455,7 +450,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
455
450
  : Schema.is(HostedToolItem)(part.result.value)
456
451
  ? part.result.value
457
452
  : extension.lowerHostedToolItem?.(part.result.value);
458
- if (acceptsItemID(extension, "hosted-tool", id) && hosted?.id === id) {
453
+ if (id !== undefined && hosted?.id === id) {
459
454
  if (!hostedToolItems.has(id)) {
460
455
  input.push(hosted);
461
456
  hostedToolItems.add(id);
@@ -68,27 +68,10 @@ const OpenAIResponsesBody = Schema.Struct({
68
68
  ...OpenAIResponsesCoreFields,
69
69
  stream: Schema.Literal(true),
70
70
  });
71
- // Replayed items are paired with stored server state by id, so a foreign or
72
- // synthetic token can fail request validation even when `call_id` pairing is
73
- // intact. Only resend ids in each item kind's own grammar; hosted tool
74
- // items keep generic validation because every hosted tool mints its own
75
- // prefix. The same allowlist approach codex uses before resending history
76
- // (codex-rs core/src/client.rs, `prepare_response_items_for_request`).
77
- const ITEM_ID_PREFIXES = {
78
- message: ["msg_"],
79
- reasoning: ["rs_"],
80
- "function-call": ["fc_"],
81
- // Every hosted tool mints its own id prefix, so items keep generic validation.
82
- "hosted-tool": [],
83
- };
84
71
  const extension = {
85
72
  id: ADAPTER,
86
73
  name: NAME,
87
74
  lowerHostedToolItem: (item) => (Schema.is(OpenAIResponsesHostedToolItem)(item) ? item : undefined),
88
- acceptsItemID: (kind, id) => {
89
- const prefixes = ITEM_ID_PREFIXES[kind];
90
- return prefixes.length === 0 || prefixes.some((prefix) => id.startsWith(prefix));
91
- },
92
75
  };
93
76
  const nativeImageToolInput = (tool) => {
94
77
  const native = tool.native?.openai;
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-18263",
3
+ "version": "0.0.0-dev-18266",
4
4
  "name": "@opencode-ai/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@clack/prompts": "1.0.0-alpha.1",
32
32
  "@effect/platform-node": "4.0.0-rc.111",
33
- "@opencode-ai/http-recorder": "0.0.0-dev-18263",
33
+ "@opencode-ai/http-recorder": "0.0.0-dev-18266",
34
34
  "@tsconfig/bun": "1.0.9",
35
35
  "@types/bun": "1.3.13",
36
36
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@smithy/eventstream-codec": "4.2.14",
41
41
  "@smithy/util-utf8": "4.2.2",
42
- "@opencode-ai/schema": "0.0.0-dev-18263",
42
+ "@opencode-ai/schema": "0.0.0-dev-18266",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.111",
45
45
  "google-auth-library": "10.5.0"