@opencode-ai/ai 0.0.0-dev-18017 → 0.0.0-dev-18039

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.
@@ -15,6 +15,7 @@ export type ThinkingInput = {
15
15
  readonly type: "disabled";
16
16
  } | ({
17
17
  readonly type: "enabled";
18
+ readonly display?: "summarized" | "omitted";
18
19
  } & ({
19
20
  readonly budgetTokens: number;
20
21
  readonly budget_tokens?: number;
@@ -194,6 +195,7 @@ export declare const AnthropicMessagesBody: Schema.Struct<{
194
195
  thinking: Schema.optional<Schema.Union<readonly [Schema.Struct<{
195
196
  readonly type: Schema.tag<"enabled">;
196
197
  readonly budget_tokens: Schema.Number;
198
+ readonly display: Schema.optional<Schema.Literals<readonly ["summarized", "omitted"]>>;
197
199
  }>, Schema.Struct<{
198
200
  readonly type: Schema.tag<"adaptive">;
199
201
  readonly display: Schema.optional<Schema.Literals<readonly ["summarized", "omitted"]>>;
@@ -369,6 +371,7 @@ export declare const protocol: Protocol<{
369
371
  readonly thinking?: {
370
372
  readonly type: "enabled";
371
373
  readonly budget_tokens: number;
374
+ readonly display?: "summarized" | "omitted" | undefined;
372
375
  } | {
373
376
  readonly type: "adaptive";
374
377
  readonly display?: "summarized" | "omitted" | undefined;
@@ -611,6 +614,7 @@ export declare const route: Route<{
611
614
  readonly thinking?: {
612
615
  readonly type: "enabled";
613
616
  readonly budget_tokens: number;
617
+ readonly display?: "summarized" | "omitted" | undefined;
614
618
  } | {
615
619
  readonly type: "adaptive";
616
620
  readonly display?: "summarized" | "omitted" | undefined;
@@ -146,6 +146,7 @@ const AnthropicThinking = Schema.Union([
146
146
  Schema.Struct({
147
147
  type: Schema.tag("enabled"),
148
148
  budget_tokens: Schema.Number,
149
+ display: Schema.optional(Schema.Literals(["summarized", "omitted"])),
149
150
  }),
150
151
  Schema.Struct({
151
152
  type: Schema.tag("adaptive"),
@@ -491,14 +492,11 @@ const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (
491
492
  const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function* (input) {
492
493
  if (!ProviderShared.isRecord(input))
493
494
  return undefined;
494
- if (input.type === "adaptive") {
495
- const display = input.display === "summarized"
496
- ? "summarized"
497
- : input.display === "omitted"
498
- ? "omitted"
499
- : undefined;
495
+ const display = input.display === "summarized" || input.display === "omitted"
496
+ ? input.display
497
+ : undefined;
498
+ if (input.type === "adaptive")
500
499
  return { type: "adaptive", ...(display === undefined ? {} : { display }) };
501
- }
502
500
  if (input.type === "disabled")
503
501
  return { type: "disabled" };
504
502
  if (input.type !== "enabled")
@@ -510,7 +508,7 @@ const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function*
510
508
  : undefined;
511
509
  if (budget === undefined)
512
510
  return yield* ProviderShared.invalidRequest("Anthropic thinking provider option requires budgetTokens");
513
- return { type: "enabled", budget_tokens: budget };
511
+ return { type: "enabled", budget_tokens: budget, ...(display === undefined ? {} : { display }) };
514
512
  });
515
513
  const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request) {
516
514
  const generation = request.generation;
@@ -57,6 +57,9 @@ const driver = (options, body) => {
57
57
  responseID = created;
58
58
  return { type: "frame", frame };
59
59
  }
60
+ // Keepalives carry no response state and may arrive before response.created.
61
+ if (event.type === "keepalive")
62
+ return { type: "frame", frame };
60
63
  if (!responseID)
61
64
  return yield* ProviderShared.eventError(options.id, `${options.name} emitted ${event.type} before response.created`, frame);
62
65
  if (event.response?.id && event.response.id !== responseID)
@@ -1,7 +1,7 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { HttpTransport } from "../route/transport/index.js";
3
3
  import { Protocol } from "../route/protocol.js";
4
- import { AIError, LLMEvent, Usage, } from "../schema/index.js";
4
+ import { AIError, LLMEvent, ProviderInternalReason, Usage, } from "../schema/index.js";
5
5
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
6
6
  import { classifyProviderFailure } from "../provider-error.js";
7
7
  import { OpenResponsesOptions } from "./utils/open-responses-options.js";
@@ -234,7 +234,7 @@ export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protoco
234
234
  type: "function",
235
235
  name: tool.name,
236
236
  description: tool.description,
237
- parameters: ToolSchemaProjection.responses(inputSchema),
237
+ parameters: inputSchema,
238
238
  // The common tool definition does not currently express Responses strict-schema policy.
239
239
  strict: false,
240
240
  };
@@ -416,8 +416,12 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
416
416
  const id = itemID(part.providerMetadata, providerMetadataKey);
417
417
  if (store !== false && id && !hostedToolReferences.has(id))
418
418
  input.push({ type: "item_reference", id });
419
- if (store === false && part.result.type === "content") {
420
- const content = part.result.value;
419
+ if (store === false) {
420
+ // The server is not storing this exchange, so the tool outcome has to
421
+ // travel in the input. Non-content results degrade to their text form.
422
+ const content = part.result.type === "content"
423
+ ? part.result.value
424
+ : [{ type: "text", text: ProviderShared.toolResultText(part) }];
421
425
  input.push({
422
426
  role: "user",
423
427
  content: yield* Effect.forEach(content, (item) => lowerHostedToolResultContentItem(item, request, extension)),
@@ -456,6 +460,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
456
460
  });
457
461
  const lowerOptions = (request) => {
458
462
  const options = OpenResponsesOptions.resolve(request);
463
+ const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
459
464
  return {
460
465
  ...(options.instructions ? { instructions: options.instructions } : {}),
461
466
  ...(options.store !== undefined ? { store: options.store } : {}),
@@ -465,7 +470,7 @@ const lowerOptions = (request) => {
465
470
  ? { stream_options: { include_obfuscation: options.streamOptions.includeObfuscation } }
466
471
  : {}),
467
472
  ...(options.topLogprobs !== undefined ? { top_logprobs: options.topLogprobs } : {}),
468
- ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
473
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
469
474
  ...(options.include ? { include: options.include } : {}),
470
475
  ...(options.reasoningEffort || options.reasoningSummary
471
476
  ? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } }
@@ -856,11 +861,18 @@ export const providerFailure = (id, event, fallback) => {
856
861
  : typeof event.status_code === "number"
857
862
  ? event.status_code
858
863
  : undefined;
864
+ const reason = event.type === "error" &&
865
+ event.error === undefined &&
866
+ event.response === undefined &&
867
+ summary === undefined &&
868
+ status === undefined
869
+ ? new ProviderInternalReason({ message })
870
+ : classifyProviderFailure({ message, code, status, rawBody: body });
859
871
  return new AIError({
860
872
  module: id,
861
873
  method: "stream",
862
874
  body,
863
- reason: classifyProviderFailure({ message, code, status, rawBody: body }),
875
+ reason,
864
876
  });
865
877
  };
866
878
  const providerError = (state, event, fallback) => providerFailure(state.id, event, fallback);
@@ -175,7 +175,7 @@ const lowerTool = (tool, inputSchema, options) => ({
175
175
  function: {
176
176
  name: tool.name,
177
177
  description: tool.description,
178
- parameters: ToolSchemaProjection.openAI(inputSchema),
178
+ parameters: inputSchema,
179
179
  },
180
180
  cache_control: options.cacheControl?.(tool.cache),
181
181
  });
@@ -415,9 +415,10 @@ const hasToolHistory = (messages) => {
415
415
  };
416
416
  const lowerOptions = (request) => {
417
417
  const options = OpenAIOptions.resolve(request);
418
+ const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
418
419
  return {
419
420
  ...(options.store !== undefined ? { store: options.store } : {}),
420
- ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
421
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
421
422
  ...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
422
423
  };
423
424
  };
@@ -10,6 +10,8 @@ export declare const encodeJson: (input: unknown, options?: import("effect/Schem
10
10
  export declare const JsonObject: Schema.$Record<Schema.String, Schema.Unknown>;
11
11
  export declare const optionalArray: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.$Array<S>>;
12
12
  export declare const optionalNull: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.NullOr<S>>;
13
+ export declare const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
14
+ export declare const clampPromptCacheKey: (key: string | undefined) => string | undefined;
13
15
  /**
14
16
  * Streaming tool-call accumulator. Adapters that build a tool call across
15
17
  * multiple `tool-input-delta` chunks store the partial JSON input string here
@@ -13,6 +13,17 @@ const isJson = Schema.is(Schema.Json);
13
13
  export const JsonObject = Schema.Record(Schema.String, Schema.Unknown);
14
14
  export const optionalArray = (schema) => Schema.optional(Schema.Array(schema));
15
15
  export const optionalNull = (schema) => Schema.optional(Schema.NullOr(schema));
16
+ export const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
17
+ // OpenAI limits `prompt_cache_key` to 64 chars; DeepSeek and Zai inherit the same
18
+ // limit via their OpenAI-compatible APIs. Clamp with unicode-aware slicing.
19
+ export const clampPromptCacheKey = (key) => {
20
+ if (key === undefined)
21
+ return undefined;
22
+ const chars = Array.from(key);
23
+ if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH)
24
+ return key;
25
+ return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
26
+ };
16
27
  /**
17
28
  * `Usage.totalTokens` policy shared by every route. Honors a provider-
18
29
  * supplied total; otherwise falls back to `inputTokens + outputTokens` only
@@ -1,20 +1,5 @@
1
1
  import { isRecord } from "../../utils/record.js";
2
2
  import { GeminiToolSchema } from "./gemini-tool-schema.js";
3
- const removeNullSchemas = (value) => {
4
- if (Array.isArray(value))
5
- return value.map(removeNullSchemas);
6
- if (!isRecord(value))
7
- return value;
8
- const fields = Object.fromEntries(Object.entries(value)
9
- .filter(([key]) => key !== "anyOf")
10
- .map(([key, field]) => [key, removeNullSchemas(field)]));
11
- if (!Array.isArray(value.anyOf))
12
- return fields;
13
- const variants = value.anyOf.filter((variant) => !isRecord(variant) || variant.type !== "null").map(removeNullSchemas);
14
- if (variants.length === 1 && isRecord(variants[0]))
15
- return { ...fields, ...variants[0] };
16
- return { ...fields, anyOf: variants };
17
- };
18
3
  const tupleItemsSchema = (items) => {
19
4
  const projected = items.map(moonshotNode);
20
5
  if (projected.length === 0)
@@ -47,19 +32,7 @@ const moonshot = (schema) => {
47
32
  const projected = moonshotNode(schema);
48
33
  return isRecord(projected) ? projected : {};
49
34
  };
50
- const openAI = (schema) => {
51
- const variants = Array.isArray(schema.anyOf) ? schema.anyOf.filter(isRecord) : [];
52
- const flattened = variants.length === 0
53
- ? { ...schema, type: "object" }
54
- : {
55
- ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "anyOf")),
56
- type: "object",
57
- properties: variants.reduce((properties, variant) => ({ ...(isRecord(variant.properties) ? variant.properties : {}), ...properties }), {}),
58
- additionalProperties: false,
59
- };
60
- const normalized = removeNullSchemas(flattened);
61
- return isRecord(normalized) ? normalized : { type: "object" };
62
- };
35
+ const openAI = (schema) => schema;
63
36
  const responses = openAI;
64
37
  const gemini = (schema) => GeminiToolSchema.convert(schema) ?? {};
65
38
  const modelCompatibility = (schema, compatibility) => {
@@ -182,6 +182,7 @@ export declare const routes: import("../route/client.js").Route<{
182
182
  readonly thinking?: {
183
183
  readonly type: "enabled";
184
184
  readonly budget_tokens: number;
185
+ readonly display?: "summarized" | "omitted" | undefined;
185
186
  } | {
186
187
  readonly type: "adaptive";
187
188
  readonly display?: "summarized" | "omitted" | undefined;
@@ -166,6 +166,7 @@ export declare const routes: import("../route/client.js").Route<{
166
166
  readonly thinking?: {
167
167
  readonly type: "enabled";
168
168
  readonly budget_tokens: number;
169
+ readonly display?: "summarized" | "omitted" | undefined;
169
170
  } | {
170
171
  readonly type: "adaptive";
171
172
  readonly display?: "summarized" | "omitted" | undefined;
@@ -180,6 +180,7 @@ export declare const routes: Route<{
180
180
  thinking?: {
181
181
  readonly type: "enabled";
182
182
  readonly budget_tokens: number;
183
+ readonly display?: "summarized" | "omitted" | undefined;
183
184
  } | {
184
185
  readonly type: "adaptive";
185
186
  readonly display?: "summarized" | "omitted" | undefined;
@@ -8,7 +8,7 @@ import { ProviderID } from "../schema/index.js";
8
8
  import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js";
9
9
  import * as OpenAIChat from "../protocols/openai-chat.js";
10
10
  import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js";
11
- import { isRecord } from "../protocols/shared.js";
11
+ import { isRecord, ProviderShared } from "../protocols/shared.js";
12
12
  export const profile = OpenAICompatibleProfiles.profiles.openrouter;
13
13
  export const id = ProviderID.make(profile.provider);
14
14
  const ADAPTER = "openrouter";
@@ -39,11 +39,12 @@ export const protocol = Protocol.make({
39
39
  reasoning_details: reasoningDetails,
40
40
  };
41
41
  });
42
+ const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
42
43
  return {
43
44
  ...body,
44
45
  messages,
45
46
  ...bodyOptions(request.providerOptions),
46
- ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
47
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
47
48
  };
48
49
  })),
49
50
  },
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-18017",
3
+ "version": "0.0.0-dev-18039",
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-18017",
33
+ "@opencode-ai/http-recorder": "0.0.0-dev-18039",
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-18017",
42
+ "@opencode-ai/schema": "0.0.0-dev-18039",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.111",
45
45
  "google-auth-library": "10.5.0"