@opencode/ai 0.0.0-dev-20867 → 0.0.0-dev-20869

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.
@@ -143,17 +143,7 @@ const countHints = (request) => countToolHints(request.tools) +
143
143
  request.messages.reduce((count, message) => count +
144
144
  message.content.reduce((contentCount, part) => contentCount + ("cache" in part && part.cache !== undefined ? 1 : 0), 0), 0);
145
145
  export const applyCachePolicy = (request) => {
146
- const route = request.model.route.id;
147
- if (!RESPECTS_INLINE_HINTS.has(route))
148
- return request;
149
- const policy = (route === "openrouter" || route === "vercel-ai-gateway-messages") &&
150
- (request.cache === undefined || request.cache === "auto")
151
- ? gatewayPolicy(request.model.id)
152
- : route === "alibaba-chat" && (request.cache === undefined || request.cache === "auto")
153
- ? request.model.id.toLowerCase().startsWith("qwen")
154
- ? QWEN
155
- : NONE
156
- : resolve(request.cache);
146
+ const policy = selectPolicy(request);
157
147
  if (!policy.tools && !policy.system && !policy.messages)
158
148
  return request;
159
149
  const hint = makeHint(policy.ttlSeconds);
@@ -165,3 +155,22 @@ export const applyCachePolicy = (request) => {
165
155
  return request;
166
156
  return LLMRequest.update(request, { tools, system, messages });
167
157
  };
158
+ // A declared cache format overrides the route allowlist and its model-ID heuristics.
159
+ const selectPolicy = (request) => {
160
+ const format = request.model.compatibility?.cacheFormat;
161
+ const route = request.model.route.id;
162
+ const auto = request.cache === undefined || request.cache === "auto";
163
+ if (format === "alibaba")
164
+ return auto ? QWEN : { ...resolve(request.cache), tools: false };
165
+ if (format === "anthropic")
166
+ return resolve(request.cache);
167
+ if (!RESPECTS_INLINE_HINTS.has(route))
168
+ return NONE;
169
+ if (!auto)
170
+ return resolve(request.cache);
171
+ if (route === "openrouter" || route === "vercel-ai-gateway-messages")
172
+ return gatewayPolicy(request.model.id);
173
+ if (route === "alibaba-chat")
174
+ return request.model.id.toLowerCase().startsWith("qwen") ? QWEN : NONE;
175
+ return AUTO;
176
+ };
@@ -9,6 +9,7 @@ import { Protocol } from "../route/protocol.js";
9
9
  import { AIError, AIErrorReason, InvalidProviderOutputError, LLMEvent, ProviderInternalError, UnknownProviderError, Usage, } from "../schema/index.js";
10
10
  import { classifyProviderFailure } from "../provider-error.js";
11
11
  import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
12
+ import { cacheControl } from "./utils/cache.js";
12
13
  import { OpenAIOptions } from "./utils/openai-options.js";
13
14
  import { Lifecycle } from "./utils/lifecycle.js";
14
15
  import { ToolStream } from "./utils/tool-stream.js";
@@ -646,14 +647,15 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
646
647
  const zaiToolStream = request.model.compatibility?.zaiToolStream ?? detectZaiToolStream(provider, baseURL, request.model.id);
647
648
  const hasHistory = hasToolHistory(request.messages);
648
649
  const hasActiveTools = flattened.tools.length > 0;
650
+ const lowering = { ...options, cacheControl: options.cacheControl ?? declaredCacheControl(request) };
649
651
  return {
650
652
  model: request.model.id,
651
- messages: yield* lowerMessages(flattened.request, options),
653
+ messages: yield* lowerMessages(flattened.request, lowering),
652
654
  tools: flattened.tools.length === 0
653
655
  ? hasHistory
654
656
  ? []
655
657
  : undefined
656
- : flattened.tools.map((tool) => lowerTool(tool, options, supportsStrictMode)),
658
+ : flattened.tools.map((tool) => lowerTool(tool, lowering, supportsStrictMode)),
657
659
  tool_choice: hasActiveTools && request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
658
660
  stream: true,
659
661
  ...(supportsUsageInStreaming ? { stream_options: { include_usage: true } } : {}),
@@ -670,6 +672,8 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
670
672
  ...lowerOptions(request, supportsStore),
671
673
  };
672
674
  });
675
+ // Provider wrappers that pass their own marker writer keep it; otherwise a declared cache format adds one.
676
+ const declaredCacheControl = (request) => request.model.compatibility?.cacheFormat === undefined ? undefined : cacheControl();
673
677
  // =============================================================================
674
678
  // Stream Parsing
675
679
  // =============================================================================
@@ -94,6 +94,8 @@ export declare const LanguageModelSanitizerCompatibility: Schema.Literals<readon
94
94
  export type LanguageModelSanitizerCompatibility = Schema.Schema.Type<typeof LanguageModelSanitizerCompatibility>;
95
95
  export declare const LanguageModelMaxTokensFieldCompatibility: Schema.Literals<readonly ["max_completion_tokens", "max_tokens"]>;
96
96
  export type LanguageModelMaxTokensFieldCompatibility = Schema.Schema.Type<typeof LanguageModelMaxTokensFieldCompatibility>;
97
+ export declare const LanguageModelCacheFormatCompatibility: Schema.Literals<readonly ["anthropic", "alibaba"]>;
98
+ export type LanguageModelCacheFormatCompatibility = Schema.Schema.Type<typeof LanguageModelCacheFormatCompatibility>;
97
99
  declare const LanguageModelCompatibility_base: Schema.Class<LanguageModelCompatibility, Schema.Struct<{
98
100
  readonly sanitizer: Schema.optional<Schema.Literals<readonly ["gemini", "moonshot", "none"]>>;
99
101
  readonly reasoningField: Schema.optional<Schema.String>;
@@ -106,6 +108,8 @@ declare const LanguageModelCompatibility_base: Schema.Class<LanguageModelCompati
106
108
  readonly supportsUsageInStreaming: Schema.optional<Schema.Boolean>;
107
109
  readonly supportsStrictMode: Schema.optional<Schema.Boolean>;
108
110
  readonly supportsPromptCacheKey: Schema.optional<Schema.Boolean>;
111
+ /** Prompt-cache marker syntax the endpoint accepts. Overrides the route default; both send `cache_control`, and `alibaba` skips tool definitions. */
112
+ readonly cacheFormat: Schema.optional<Schema.Literals<readonly ["anthropic", "alibaba"]>>;
109
113
  readonly zaiToolStream: Schema.optional<Schema.Boolean>;
110
114
  readonly requireSignature: Schema.optional<Schema.Boolean>;
111
115
  /** Supports Anthropic's thinking-prefix mismatch controls. Overrides model-ID detection. */
@@ -130,6 +130,7 @@ export const ReasoningEffort = Schema.declare((value) => typeof value === "strin
130
130
  /** Tool schema sanitizer for a model family. `none` opts out of the protocol and model-name defaults. */
131
131
  export const LanguageModelSanitizerCompatibility = Schema.Literals(["gemini", "moonshot", "none"]);
132
132
  export const LanguageModelMaxTokensFieldCompatibility = Schema.Literals(["max_completion_tokens", "max_tokens"]);
133
+ export const LanguageModelCacheFormatCompatibility = Schema.Literals(["anthropic", "alibaba"]);
133
134
  export class LanguageModelCompatibility extends Schema.Class("LLM.LanguageModelCompatibility")({
134
135
  sanitizer: Schema.optional(LanguageModelSanitizerCompatibility),
135
136
  reasoningField: Schema.optional(Schema.String),
@@ -145,6 +146,8 @@ export class LanguageModelCompatibility extends Schema.Class("LLM.LanguageModelC
145
146
  // this is true; other protocols omit it only when this is false.
146
147
  // Session-affinity headers still flow regardless.
147
148
  supportsPromptCacheKey: Schema.optional(Schema.Boolean),
149
+ /** Prompt-cache marker syntax the endpoint accepts. Overrides the route default; both send `cache_control`, and `alibaba` skips tool definitions. */
150
+ cacheFormat: Schema.optional(LanguageModelCacheFormatCompatibility),
148
151
  zaiToolStream: Schema.optional(Schema.Boolean),
149
152
  requireSignature: Schema.optional(Schema.Boolean),
150
153
  /** Supports Anthropic's thinking-prefix mismatch controls. Overrides model-ID detection. */
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-20867",
3
+ "version": "0.0.0-dev-20869",
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.1",
37
- "@opencode/http-recorder": "0.0.0-dev-20867",
37
+ "@opencode/http-recorder": "0.0.0-dev-20869",
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-20867",
47
+ "@opencode/schema": "0.0.0-dev-20869",
48
48
  "aws4fetch": "1.0.20",
49
49
  "effect": "4.0.1",
50
50
  "google-auth-library": "10.5.0"