@codehz/ai 0.8.0 → 0.8.2

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.
package/dist/index.d.mts CHANGED
@@ -127,6 +127,42 @@ type OutputItem = MessageItem | ReasoningItem | ToolCallItem | OpaqueItem | Serv
127
127
  /** replay 材料的类型等价于 InputItem */
128
128
  type ReplayItem = InputItem;
129
129
  //#endregion
130
+ //#region src/types/cache.d.ts
131
+ /** Request-level portable prompt cache controls and provider extensions. */
132
+ type PromptCacheMode = "off" | "auto" | "explicit";
133
+ type PromptCacheSettings = {
134
+ /** Cache strategy; when omitted, the adapter/provider decides. */mode?: PromptCacheMode; /** Session or tenant routing hint; providers may ignore it. */
135
+ key?: string; /** Provider-specific retention hint. */
136
+ ttl?: "short" | "long" | string;
137
+ };
138
+ type ProviderCacheOptions = {
139
+ responses?: {
140
+ promptCacheKey?: string;
141
+ promptCacheRetention?: "in_memory" | "24h";
142
+ promptCacheMode?: "implicit" | "explicit" | "off";
143
+ };
144
+ chatCompletions?: {
145
+ promptCacheKey?: string;
146
+ promptCacheRetention?: "in_memory" | "24h";
147
+ };
148
+ messages?: {
149
+ cacheTtl?: "5m" | "1h";
150
+ betaHeader?: string;
151
+ };
152
+ gemini?: {
153
+ cachedContent?: string;
154
+ };
155
+ ollama?: {
156
+ keepAlive?: string | number;
157
+ };
158
+ };
159
+ type PromptCacheMetadata = {
160
+ requestedMode: PromptCacheMode;
161
+ appliedMode: "off" | "implicit" | "explicit" | "unsupported";
162
+ keyApplied: boolean;
163
+ ttlApplied: boolean;
164
+ };
165
+ //#endregion
130
166
  //#region src/types/request.d.ts
131
167
  type ToolDefinition = {
132
168
  name: string;
@@ -197,7 +233,11 @@ type AIRequest = {
197
233
  * Messages `thinking`, Ollama `think`, Gemini `thinkingConfig`).
198
234
  * Unsupported levels throw.
199
235
  */
200
- reasoningLevel?: ReasoningLevel; /** AbortSignal 用于打断请求。abort fetch 调用会被取消,流迭代器抛出 AbortError。 */
236
+ reasoningLevel?: ReasoningLevel; /** Portable request-level prompt cache strategy and routing hint. */
237
+ cache?: PromptCacheSettings; /** Typed provider-native cache extensions; unsupported fields are ignored by other adapters. */
238
+ providerOptions?: {
239
+ cache?: ProviderCacheOptions;
240
+ }; /** AbortSignal 用于打断请求。abort 时 fetch 调用会被取消,流迭代器抛出 AbortError。 */
201
241
  signal?: AbortSignal;
202
242
  };
203
243
  //#endregion
@@ -691,7 +731,7 @@ declare class AdapterAuxiliaryState {
691
731
  constructor(request: NormalizedRequest);
692
732
  recordUsage(usage: Partial<Usage>, source: UsageSource, raw?: unknown): void;
693
733
  recordBilling(billing: Partial<BillingInfo>, source: BillingSource, raw?: unknown): void;
694
- recordProviderMetadata(source: string, metadata: Record<string, unknown> | undefined): void;
734
+ recordProviderMetadata(source: string | undefined, metadata: Record<string, unknown> | undefined): void;
695
735
  finalize(factory: EventFactory, options?: AuxiliaryFinalizeOptions): Promise<AuxiliaryFinalizeResult>;
696
736
  private shouldAttemptLookup;
697
737
  }
@@ -886,6 +926,11 @@ type ResponsesAPIRequest = {
886
926
  effort: string;
887
927
  }; /** 服务端多轮续写;opaque replay 的 response id 映射到此字段,而非 item_reference */
888
928
  previous_response_id?: string;
929
+ prompt_cache_key?: string;
930
+ prompt_cache_options?: {
931
+ mode?: "implicit" | "explicit" | "off";
932
+ retention?: "in_memory" | "24h";
933
+ };
889
934
  stream: true;
890
935
  };
891
936
  /** EasyInputMessage:content 可为 string,或 input_* content parts */
@@ -919,7 +964,7 @@ type ResponsesFunctionCall = {
919
964
  type ResponsesFunctionCallOutput = {
920
965
  type: "function_call_output";
921
966
  call_id: string;
922
- output: string;
967
+ output: string | ResponsesInputContentPart[];
923
968
  id?: string;
924
969
  status?: "in_progress" | "completed" | "incomplete";
925
970
  };
@@ -1025,7 +1070,7 @@ type MessagesAPIRequest = {
1025
1070
  model: string;
1026
1071
  max_tokens: number;
1027
1072
  messages: MessagesAPIMessage[];
1028
- system?: string;
1073
+ system?: string | MessagesAPIContentBlock[];
1029
1074
  tools?: MessagesAPITool[];
1030
1075
  tool_choice?: {
1031
1076
  type: "auto" | "none";
@@ -1055,34 +1100,45 @@ type MessagesAPIImageSource = {
1055
1100
  type: "url";
1056
1101
  url: string;
1057
1102
  };
1103
+ type MessagesCacheControl = {
1104
+ type: "ephemeral";
1105
+ ttl?: "5m" | "1h";
1106
+ };
1058
1107
  type MessagesAPIContentBlock = {
1059
1108
  type: "text";
1060
1109
  text: string;
1110
+ cache_control?: MessagesCacheControl;
1061
1111
  } | {
1062
1112
  type: "image";
1063
1113
  source: MessagesAPIImageSource;
1114
+ cache_control?: MessagesCacheControl;
1064
1115
  } | {
1065
1116
  type: "thinking";
1066
1117
  thinking: string;
1067
1118
  signature?: string;
1119
+ cache_control?: MessagesCacheControl;
1068
1120
  } | {
1069
1121
  type: "redacted_thinking";
1070
1122
  data: string;
1123
+ cache_control?: MessagesCacheControl;
1071
1124
  } | {
1072
1125
  type: "tool_use";
1073
1126
  id: string;
1074
1127
  name: string;
1075
1128
  input: Record<string, unknown>;
1129
+ cache_control?: MessagesCacheControl;
1076
1130
  } | {
1077
1131
  type: "tool_result";
1078
1132
  tool_use_id: string;
1079
1133
  content: string | MessagesAPIContentBlock[];
1080
1134
  is_error?: boolean;
1135
+ cache_control?: MessagesCacheControl;
1081
1136
  };
1082
1137
  type MessagesAPITool = {
1083
1138
  name: string;
1084
1139
  description?: string;
1085
1140
  input_schema: Record<string, unknown>;
1141
+ cache_control?: MessagesCacheControl;
1086
1142
  };
1087
1143
  //#endregion
1088
1144
  //#region src/adapters/messages/adapter.d.ts
@@ -1114,6 +1170,8 @@ type ChatRequest = {
1114
1170
  temperature?: number;
1115
1171
  max_tokens?: number; /** Portable reasoningLevel → reasoning_effort */
1116
1172
  reasoning_effort?: string;
1173
+ prompt_cache_key?: string;
1174
+ prompt_cache_retention?: "in_memory" | "24h";
1117
1175
  stream: true;
1118
1176
  n: 1;
1119
1177
  };
@@ -1172,6 +1230,7 @@ type OllamaChatRequest = {
1172
1230
  model: string;
1173
1231
  messages: OllamaMessage[];
1174
1232
  stream: true;
1233
+ keep_alive?: string | number;
1175
1234
  tools?: OllamaTool[]; /** Portable reasoningLevel → think;minimal/xhigh/max 不支持 */
1176
1235
  think?: boolean | "low" | "medium" | "high";
1177
1236
  options?: {
@@ -1253,6 +1312,7 @@ type GeminiFunctionCallingConfig = {
1253
1312
  };
1254
1313
  type GeminiGenerateContentRequest = {
1255
1314
  contents: GeminiContent[];
1315
+ cachedContent?: string;
1256
1316
  systemInstruction?: {
1257
1317
  parts: Array<{
1258
1318
  text: string;
@@ -1477,5 +1537,5 @@ declare function assertMockRequest(request: NormalizedRequest, expectation: Mock
1477
1537
  //#region src/adapters/mock/streaming.d.ts
1478
1538
  declare function withMockStreaming(handler: MockStaticHandler, options: MockTextStreamOptions): MockHandler;
1479
1539
  //#endregion
1480
- export { type AIClient, AIError, AIMappingError, AIProviderError, AIRecoverableError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, type AdapterKind, type AuxiliaryInfo, type BackendAdapter, type BackendTrace, type BillingInfo, ChatCompletionsAdapter, type ChatCompletionsAdapterOptions, type Citation, type CodeExecutionServerTool, type CompressRequest, type CompressResult, type ContainerFileCitation, type ContentBlock, type ContextCompressCapable, type CreateAIClientOptions, type ErrorCode, type FetchFn, GeminiAdapter, type GeminiAdapterOptions, type IncludeSettings, type InputItem, type InstructionBlock, type JsonContentBlock, KNOWN_ADAPTER_KINDS, type KnownAdapterKind, type KnownErrorCode, type KnownWarningCode, type McpServerTool, type MessageCompletedEvent, type MessageDeltaEvent, type MessageItem, type MessageStartedEvent, MessagesAdapter, type MessagesAdapterOptions, MockAdapter, type MockAdapterOptions, type MockAuxiliaryStep, type MockCompleteStep, type MockCompressHandler, type MockErrorStep, type MockHandler, type MockHandlerContext, type MockHistoryRecord, type MockInputExpectation, type MockInterruptStep, type MockMessageStep, type MockOutputStep, type MockReasoningStep, type MockRequestExpectation, type MockServerToolCallStep, type MockServerToolDiscoveryStep, type MockServerToolResultStep, type MockStaticHandler, type MockStep, type MockTextStreamOptions, type MockThrowStep, type MockToolCallStep, type MockWarningStep, type NormalizedRequest, OllamaAdapter, type OllamaAdapterOptions, type OpaqueItem, type OutputItem, REASONING_LEVELS, REASONING_LEVEL_SET, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningLevel, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type ServerToolCallItem, type ServerToolCompletedEvent, type ServerToolDefinition, type ServerToolDeltaEvent, type ServerToolDiscoveryCompletedEvent, type ServerToolDiscoveryItem, type ServerToolResultCompletedEvent, type ServerToolResultItem, type ServerToolStartedEvent, type StopReason, type StreamEventBase, type StreamWarning, type TextContentBlock, type ToolCallCompletedEvent, type ToolCallDeltaEvent, type ToolCallItem, type ToolCallStartedEvent, type ToolChoice, type ToolDefinition, type ToolResultItem, type UrlCitation, type Usage, WarningCode, type WarningCodeName, type WarningCodeValue, type WebSearchServerTool, type WebSearchUserLocation, assertMockRequest, blockToText, coalesceContentBlocks, collectStream, contentBlocksToText, createAIClient, extractText, imageBlock, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, opaqueBlock, opaqueItem, reasoningItem, replayFromOutput, serverToolCallItem, serverToolDiscoveryItem, serverToolResultItem, streamWarningKey, supportsContextCompress, textBlock, toolCallItem, toolResultItem, withMockStreaming };
1540
+ export { type AIClient, AIError, AIMappingError, AIProviderError, AIRecoverableError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, type AdapterKind, type AuxiliaryInfo, type BackendAdapter, type BackendTrace, type BillingInfo, ChatCompletionsAdapter, type ChatCompletionsAdapterOptions, type Citation, type CodeExecutionServerTool, type CompressRequest, type CompressResult, type ContainerFileCitation, type ContentBlock, type ContextCompressCapable, type CreateAIClientOptions, type ErrorCode, type FetchFn, GeminiAdapter, type GeminiAdapterOptions, type IncludeSettings, type InputItem, type InstructionBlock, type JsonContentBlock, KNOWN_ADAPTER_KINDS, type KnownAdapterKind, type KnownErrorCode, type KnownWarningCode, type McpServerTool, type MessageCompletedEvent, type MessageDeltaEvent, type MessageItem, type MessageStartedEvent, MessagesAdapter, type MessagesAdapterOptions, MockAdapter, type MockAdapterOptions, type MockAuxiliaryStep, type MockCompleteStep, type MockCompressHandler, type MockErrorStep, type MockHandler, type MockHandlerContext, type MockHistoryRecord, type MockInputExpectation, type MockInterruptStep, type MockMessageStep, type MockOutputStep, type MockReasoningStep, type MockRequestExpectation, type MockServerToolCallStep, type MockServerToolDiscoveryStep, type MockServerToolResultStep, type MockStaticHandler, type MockStep, type MockTextStreamOptions, type MockThrowStep, type MockToolCallStep, type MockWarningStep, type NormalizedRequest, OllamaAdapter, type OllamaAdapterOptions, type OpaqueItem, type OutputItem, type PromptCacheMetadata, type PromptCacheMode, type PromptCacheSettings, type ProviderCacheOptions, REASONING_LEVELS, REASONING_LEVEL_SET, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningLevel, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type ServerToolCallItem, type ServerToolCompletedEvent, type ServerToolDefinition, type ServerToolDeltaEvent, type ServerToolDiscoveryCompletedEvent, type ServerToolDiscoveryItem, type ServerToolResultCompletedEvent, type ServerToolResultItem, type ServerToolStartedEvent, type StopReason, type StreamEventBase, type StreamWarning, type TextContentBlock, type ToolCallCompletedEvent, type ToolCallDeltaEvent, type ToolCallItem, type ToolCallStartedEvent, type ToolChoice, type ToolDefinition, type ToolResultItem, type UrlCitation, type Usage, WarningCode, type WarningCodeName, type WarningCodeValue, type WebSearchServerTool, type WebSearchUserLocation, assertMockRequest, blockToText, coalesceContentBlocks, collectStream, contentBlocksToText, createAIClient, extractText, imageBlock, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, opaqueBlock, opaqueItem, reasoningItem, replayFromOutput, serverToolCallItem, serverToolDiscoveryItem, serverToolResultItem, streamWarningKey, supportsContextCompress, textBlock, toolCallItem, toolResultItem, withMockStreaming };
1481
1541
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1064,7 +1064,7 @@ var AdapterAuxiliaryState = class {
1064
1064
  recordProviderMetadata(source, metadata) {
1065
1065
  if (this.request.include?.providerMetadata === "off" || !metadata || isEmptyRecord(metadata)) return;
1066
1066
  this.collector.recordMetadata(metadata);
1067
- this.metadataSources.add(source);
1067
+ if (source) this.metadataSources.add(source);
1068
1068
  }
1069
1069
  async finalize(factory, options = {}) {
1070
1070
  if (options.lookup && this.shouldAttemptLookup()) await this.collector.tryLookup(options.lookup, options.lookupTimeoutMs);
@@ -1119,6 +1119,102 @@ function isEmptyRecord(value) {
1119
1119
  return Object.keys(value).length === 0;
1120
1120
  }
1121
1121
  //#endregion
1122
+ //#region src/provider/prompt-cache.ts
1123
+ function requestedPromptCacheMode(request) {
1124
+ return request.cache?.mode ?? "auto";
1125
+ }
1126
+ function promptCacheAdapterForKind(kind) {
1127
+ if (kind === "responses" || kind === "chat-completions" || kind === "messages" || kind === "gemini" || kind === "ollama") return kind;
1128
+ }
1129
+ function promptCacheMetadata(request, adapter, overrides = {}) {
1130
+ const requestedMode = requestedPromptCacheMode(request);
1131
+ if (requestedMode === "off") return {
1132
+ requestedMode,
1133
+ appliedMode: "off",
1134
+ keyApplied: false,
1135
+ ttlApplied: false
1136
+ };
1137
+ const native = request.providerOptions?.cache;
1138
+ const portable = request.cache;
1139
+ let appliedMode = "implicit";
1140
+ let keyApplied = false;
1141
+ let ttlApplied = false;
1142
+ if (adapter === "responses") {
1143
+ const nativeOptions = native?.responses;
1144
+ const mode = nativeOptions?.promptCacheMode ?? (requestedMode === "explicit" ? "explicit" : void 0);
1145
+ keyApplied = mode !== "off" && Boolean(nativeOptions?.promptCacheKey ?? portable?.key);
1146
+ ttlApplied = mode !== "off" && Boolean(nativeOptions?.promptCacheRetention ?? mapPromptCacheRetention(portable?.ttl));
1147
+ appliedMode = mode === "off" ? "off" : mode === "explicit" ? "explicit" : "implicit";
1148
+ } else if (adapter === "chat-completions") {
1149
+ const options = native?.chatCompletions;
1150
+ keyApplied = Boolean(options?.promptCacheKey ?? portable?.key);
1151
+ ttlApplied = Boolean(options?.promptCacheRetention ?? mapPromptCacheRetention(portable?.ttl));
1152
+ appliedMode = requestedMode === "explicit" || Boolean(options?.promptCacheKey || options?.promptCacheRetention) ? "explicit" : "implicit";
1153
+ } else if (adapter === "messages") {
1154
+ const hasStablePrefix = Boolean(request.instructions) || request.input.length > 0 || (request.tools?.length ?? 0) > 0;
1155
+ const explicitRequested = requestedMode === "explicit" || native?.messages?.cacheTtl !== void 0;
1156
+ const explicit = explicitRequested && hasStablePrefix;
1157
+ appliedMode = explicit ? "explicit" : explicitRequested ? "unsupported" : "implicit";
1158
+ ttlApplied = explicit && Boolean(native?.messages?.cacheTtl ?? mapAnthropicCacheTtl(portable?.ttl));
1159
+ } else if (adapter === "gemini") if (native?.gemini?.cachedContent) appliedMode = "explicit";
1160
+ else appliedMode = requestedMode === "explicit" ? "unsupported" : "implicit";
1161
+ else if (adapter === "ollama") {
1162
+ const keepAlive = native?.ollama?.keepAlive;
1163
+ appliedMode = requestedMode === "explicit" && keepAlive === void 0 ? "unsupported" : requestedMode === "explicit" ? "explicit" : "implicit";
1164
+ }
1165
+ return {
1166
+ requestedMode,
1167
+ appliedMode: overrides.explicitApplied === true ? "explicit" : appliedMode,
1168
+ keyApplied: overrides.keyApplied ?? keyApplied,
1169
+ ttlApplied: overrides.ttlApplied ?? ttlApplied
1170
+ };
1171
+ }
1172
+ function applyPromptCacheFields(body, request, adapter) {
1173
+ const portable = request.cache;
1174
+ const native = request.providerOptions?.cache;
1175
+ const metadata = promptCacheMetadata(request, adapter);
1176
+ if (portable?.mode === "off") return metadata;
1177
+ if (adapter === "responses") {
1178
+ const options = native?.responses;
1179
+ const key = options?.promptCacheKey ?? portable?.key;
1180
+ const mode = options?.promptCacheMode ?? (portable?.mode === "explicit" ? "explicit" : void 0);
1181
+ const retention = options?.promptCacheRetention ?? mapPromptCacheRetention(portable?.ttl);
1182
+ if (mode !== "off" && key) body.prompt_cache_key = key;
1183
+ if (mode || retention) body.prompt_cache_options = {
1184
+ ...mode ? { mode } : {},
1185
+ ...mode !== "off" && retention ? { retention } : {}
1186
+ };
1187
+ return metadata;
1188
+ }
1189
+ if (adapter === "chat-completions") {
1190
+ const options = native?.chatCompletions;
1191
+ const key = options?.promptCacheKey ?? portable?.key;
1192
+ const retention = options?.promptCacheRetention ?? mapPromptCacheRetention(portable?.ttl);
1193
+ if (key) body.prompt_cache_key = key;
1194
+ if (retention) body.prompt_cache_retention = retention;
1195
+ return metadata;
1196
+ }
1197
+ if (adapter === "gemini") {
1198
+ const cachedContent = native?.gemini?.cachedContent;
1199
+ if (cachedContent) body.cachedContent = cachedContent;
1200
+ return metadata;
1201
+ }
1202
+ if (adapter === "ollama") {
1203
+ const keepAlive = native?.ollama?.keepAlive;
1204
+ if (keepAlive !== void 0) body.keep_alive = keepAlive;
1205
+ }
1206
+ return metadata;
1207
+ }
1208
+ function mapPromptCacheRetention(ttl) {
1209
+ if (ttl === "short") return "in_memory";
1210
+ if (ttl === "long") return "24h";
1211
+ if (ttl === "in_memory" || ttl === "24h") return ttl;
1212
+ }
1213
+ function mapAnthropicCacheTtl(ttl) {
1214
+ if (ttl === "short" || ttl === "5m") return "5m";
1215
+ if (ttl === "long" || ttl === "1h") return "1h";
1216
+ }
1217
+ //#endregion
1122
1218
  //#region src/provider/base.ts
1123
1219
  var AdapterBase = class {
1124
1220
  /**
@@ -1201,7 +1297,10 @@ var AdapterBase = class {
1201
1297
  yield factory.responseCompleted(payload);
1202
1298
  }
1203
1299
  createAuxiliaryState(request) {
1204
- return new AdapterAuxiliaryState(request);
1300
+ const auxiliary = new AdapterAuxiliaryState(request);
1301
+ const adapter = promptCacheAdapterForKind(this.kind);
1302
+ if (adapter) auxiliary.recordProviderMetadata(void 0, { promptCache: promptCacheMetadata(request, adapter) });
1303
+ return auxiliary;
1205
1304
  }
1206
1305
  };
1207
1306
  function mergeWarnings(...groups) {
@@ -1564,6 +1663,7 @@ function usageFromChatCompletions(raw) {
1564
1663
  outputTokens: num(raw.completion_tokens),
1565
1664
  totalTokens: num(raw.total_tokens),
1566
1665
  cachedInputTokens: num(raw.prompt_tokens_details?.cached_tokens),
1666
+ cacheWriteInputTokens: num(raw.cache_write_tokens ?? raw.prompt_tokens_details?.cache_write_tokens),
1567
1667
  reasoningTokens: num(raw.completion_tokens_details?.reasoning_tokens)
1568
1668
  });
1569
1669
  }
@@ -1574,6 +1674,7 @@ function usageFromOpenAIResponses(raw) {
1574
1674
  outputTokens: num(raw.output_tokens),
1575
1675
  totalTokens: num(raw.total_tokens),
1576
1676
  cachedInputTokens: num(raw.input_tokens_details?.cached_tokens),
1677
+ cacheWriteInputTokens: num(raw.cache_write_tokens ?? raw.input_tokens_details?.cache_write_tokens),
1577
1678
  reasoningTokens: num(raw.output_tokens_details?.reasoning_tokens)
1578
1679
  });
1579
1680
  }
@@ -2111,6 +2212,29 @@ function mapResponsesMessageContent(role, blocks, field) {
2111
2212
  throw new AIRequestError(`${mapper$7.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
2112
2213
  });
2113
2214
  }
2215
+ function mapResponsesToolResultOutput(blocks, field) {
2216
+ mapper$7.ensureBlocks(blocks, field, [
2217
+ "text",
2218
+ "json",
2219
+ "image"
2220
+ ], "only text/json/image blocks are supported");
2221
+ if (!blocks.some((block) => block.type === "image")) return mapper$7.textFromBlocks(blocks, field);
2222
+ return blocks.map((block) => {
2223
+ if (block.type === "text") return {
2224
+ type: "input_text",
2225
+ text: block.text
2226
+ };
2227
+ if (block.type === "json") return {
2228
+ type: "input_text",
2229
+ text: JSON.stringify(block.json)
2230
+ };
2231
+ if (block.type === "image") return {
2232
+ type: "input_image",
2233
+ image_url: block.imageUrl
2234
+ };
2235
+ throw new AIRequestError(`${mapper$7.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
2236
+ });
2237
+ }
2114
2238
  function mapReasoningInput(item, index) {
2115
2239
  const text = mapper$7.textFromBlocks(mapper$7.ensureReasoningBlocks(item.content, "reasoning content"), "reasoning content");
2116
2240
  const id = item.id && item.id.length > 0 ? item.id : `reasoning_replay_${index}`;
@@ -2186,7 +2310,7 @@ function mapResponsesCore(request, options) {
2186
2310
  });
2187
2311
  break;
2188
2312
  case "tool_result": {
2189
- const output = mapper$7.textFromBlocks(item.content, `tool_result ${item.callId} content`);
2313
+ const output = mapResponsesToolResultOutput(item.content, `tool_result ${item.callId} content`);
2190
2314
  input.push({
2191
2315
  type: "function_call_output",
2192
2316
  call_id: item.callId,
@@ -2256,6 +2380,7 @@ function buildResponsesRequest(request, options) {
2256
2380
  if (request.maxOutputTokens !== void 0) body.max_output_tokens = request.maxOutputTokens;
2257
2381
  if (request.metadata) body.metadata = request.metadata;
2258
2382
  if (request.reasoningLevel !== void 0) body.reasoning = mapResponsesReasoning(request.reasoningLevel);
2383
+ applyPromptCacheFields(body, request, "responses");
2259
2384
  return body;
2260
2385
  }
2261
2386
  /**
@@ -3207,6 +3332,15 @@ function mapMessagesUserContent(blocks, field) {
3207
3332
  }
3208
3333
  return blocks.map((block) => canonicalToMessagesBlock(block, field));
3209
3334
  }
3335
+ function mapMessagesToolResultContent(blocks, field) {
3336
+ mapper$6.ensureBlocks(blocks, field, [
3337
+ "text",
3338
+ "json",
3339
+ "image"
3340
+ ], "only text/json/image blocks are supported");
3341
+ if (!blocks.some((block) => block.type === "image")) return mapper$6.textFromBlocks(blocks, field);
3342
+ return blocks.map((block) => canonicalToMessagesBlock(block, field));
3343
+ }
3210
3344
  function buildMessagesRequest(request, options) {
3211
3345
  mapper$6.assertNoServerTools(request.serverTools);
3212
3346
  const messages = [];
@@ -3252,7 +3386,7 @@ function buildMessagesRequest(request, options) {
3252
3386
  break;
3253
3387
  }
3254
3388
  case "tool_result": {
3255
- const content = mapper$6.textFromBlocks(item.content, `tool_result ${item.callId} content`);
3389
+ const content = mapMessagesToolResultContent(item.content, `tool_result ${item.callId} content`);
3256
3390
  const block = {
3257
3391
  type: "tool_result",
3258
3392
  tool_use_id: item.callId,
@@ -3319,8 +3453,45 @@ function buildMessagesRequest(request, options) {
3319
3453
  });
3320
3454
  if (request.temperature !== void 0) body.temperature = request.temperature;
3321
3455
  if (request.reasoningLevel !== void 0) body.thinking = mapMessagesThinking(request.reasoningLevel, body.max_tokens);
3456
+ applyMessagesPromptCache(body, request, systemPrompt);
3322
3457
  return body;
3323
3458
  }
3459
+ function applyMessagesPromptCache(body, request, systemPrompt) {
3460
+ const requestedMode = requestedPromptCacheMode(request);
3461
+ if (requestedMode === "off") return;
3462
+ if (requestedMode !== "explicit" && request.providerOptions?.cache?.messages?.cacheTtl === void 0) return;
3463
+ const ttl = request.providerOptions?.cache?.messages?.cacheTtl ?? mapAnthropicCacheTtl(request.cache?.ttl);
3464
+ const cacheControl = {
3465
+ type: "ephemeral",
3466
+ ...ttl ? { ttl } : {}
3467
+ };
3468
+ if (systemPrompt) {
3469
+ body.system = [{
3470
+ type: "text",
3471
+ text: systemPrompt,
3472
+ cache_control: cacheControl
3473
+ }];
3474
+ return;
3475
+ }
3476
+ const lastMessage = body.messages[body.messages.length - 1];
3477
+ if (lastMessage) {
3478
+ if (typeof lastMessage.content === "string") {
3479
+ lastMessage.content = [{
3480
+ type: "text",
3481
+ text: lastMessage.content,
3482
+ cache_control: cacheControl
3483
+ }];
3484
+ return;
3485
+ }
3486
+ const lastBlock = lastMessage.content[lastMessage.content.length - 1];
3487
+ if (lastBlock) {
3488
+ lastBlock.cache_control = cacheControl;
3489
+ return;
3490
+ }
3491
+ }
3492
+ const lastTool = body.tools?.[body.tools.length - 1];
3493
+ if (lastTool) lastTool.cache_control = cacheControl;
3494
+ }
3324
3495
  new NormalizedRequestMapper("messages");
3325
3496
  /** 用 response 级别的命名空间合成 content block 的 item ID,避免多轮工具循环 ID 碰撞 */
3326
3497
  function synthesizeItemId(kind, blockIndex, responseId) {
@@ -3358,7 +3529,8 @@ async function* mapMessagesStream(host, providerRequest, factory, request) {
3358
3529
  headers: host.mergeHeaders({
3359
3530
  "Content-Type": "application/json",
3360
3531
  "x-api-key": host.apiKey ?? "",
3361
- "anthropic-version": host.apiVersion
3532
+ "anthropic-version": host.apiVersion,
3533
+ ...request.providerOptions?.cache?.messages?.betaHeader ? { "anthropic-beta": request.providerOptions.cache.messages.betaHeader } : {}
3362
3534
  }),
3363
3535
  body: providerRequest
3364
3536
  });
@@ -3688,6 +3860,29 @@ function mapChatUserContent(blocks, field) {
3688
3860
  throw new AIRequestError(`${mapper$4.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
3689
3861
  });
3690
3862
  }
3863
+ function mapChatToolResultContent(blocks, field) {
3864
+ mapper$4.ensureBlocks(blocks, field, [
3865
+ "text",
3866
+ "json",
3867
+ "image"
3868
+ ], "only text/json/image blocks are supported");
3869
+ if (!blocks.some((block) => block.type === "image")) return mapper$4.textFromBlocks(blocks, field);
3870
+ return blocks.map((block) => {
3871
+ if (block.type === "text") return {
3872
+ type: "text",
3873
+ text: block.text
3874
+ };
3875
+ if (block.type === "json") return {
3876
+ type: "text",
3877
+ text: JSON.stringify(block.json)
3878
+ };
3879
+ if (block.type === "image") return {
3880
+ type: "image_url",
3881
+ image_url: { url: block.imageUrl }
3882
+ };
3883
+ throw new AIRequestError(`${mapper$4.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
3884
+ });
3885
+ }
3691
3886
  function mapChatMessageContent(role, blocks, field) {
3692
3887
  if (role === "user") return mapChatUserContent(blocks, field);
3693
3888
  return mapper$4.textFromBlocks(blocks, field) || null;
@@ -3731,7 +3926,7 @@ function buildChatCompletionsRequest(request, options) {
3731
3926
  role: "tool",
3732
3927
  tool_call_id: item.callId,
3733
3928
  name: item.toolName,
3734
- content: mapper$4.textFromBlocks(item.content, `tool_result ${item.callId} content`)
3929
+ content: mapChatToolResultContent(item.content, `tool_result ${item.callId} content`)
3735
3930
  });
3736
3931
  break;
3737
3932
  case "reasoning":
@@ -3770,6 +3965,7 @@ function buildChatCompletionsRequest(request, options) {
3770
3965
  if (request.maxOutputTokens !== void 0) body.max_tokens = request.maxOutputTokens;
3771
3966
  if (request.metadata) body.metadata = request.metadata;
3772
3967
  if (request.reasoningLevel !== void 0) body.reasoning_effort = mapChatCompletionsReasoningEffort(request.reasoningLevel);
3968
+ applyPromptCacheFields(body, request, "chat-completions");
3773
3969
  return body;
3774
3970
  }
3775
3971
  //#endregion
@@ -4057,6 +4253,21 @@ function mapOllamaUserMessage(blocks, field) {
4057
4253
  ...images.length > 0 ? { images } : {}
4058
4254
  };
4059
4255
  }
4256
+ function mapOllamaToolResultContent(blocks, field) {
4257
+ mapper$3.ensureBlocks(blocks, field, [
4258
+ "text",
4259
+ "json",
4260
+ "image"
4261
+ ], "only text/json/image blocks are supported");
4262
+ const textBlocks = [];
4263
+ const images = [];
4264
+ for (const block of blocks) if (block.type === "image") images.push(mapOllamaImageData(block.imageUrl, field));
4265
+ else textBlocks.push(block);
4266
+ return {
4267
+ content: contentBlocksToText(textBlocks),
4268
+ ...images.length > 0 ? { images } : {}
4269
+ };
4270
+ }
4060
4271
  function buildOllamaRequest(request, options) {
4061
4272
  mapper$3.assertNoServerTools(request.serverTools);
4062
4273
  const messages = [];
@@ -4104,7 +4315,7 @@ function buildOllamaRequest(request, options) {
4104
4315
  if (queue && queue.length > 0) queue.shift();
4105
4316
  messages.push({
4106
4317
  role: "tool",
4107
- content: mapper$3.textFromBlocks(item.content, `tool_result ${item.callId} content`)
4318
+ ...mapOllamaToolResultContent(item.content, `tool_result ${item.callId} content`)
4108
4319
  });
4109
4320
  break;
4110
4321
  }
@@ -4154,6 +4365,7 @@ function buildOllamaRequest(request, options) {
4154
4365
  if (request.maxOutputTokens !== void 0) body.options.num_predict = request.maxOutputTokens;
4155
4366
  }
4156
4367
  if (request.reasoningLevel !== void 0) body.think = mapOllamaThink(request.reasoningLevel);
4368
+ applyPromptCacheFields(body, request, "ollama");
4157
4369
  return body;
4158
4370
  }
4159
4371
  new NormalizedRequestMapper("ollama");
@@ -4330,6 +4542,29 @@ function mapGeminiImagePart(imageUrl, field) {
4330
4542
  data: dataUrl.data
4331
4543
  } };
4332
4544
  }
4545
+ function mapGeminiToolResultContent(blocks, field) {
4546
+ mapper$1.ensureBlocks(blocks, field, [
4547
+ "text",
4548
+ "json",
4549
+ "image"
4550
+ ], "only text/json/image blocks are supported");
4551
+ const textBlocks = [];
4552
+ const imageParts = [];
4553
+ for (const block of blocks) if (block.type === "image") imageParts.push(mapGeminiImagePart(block.imageUrl, field));
4554
+ else textBlocks.push(block);
4555
+ const text = contentBlocksToText(textBlocks);
4556
+ let response;
4557
+ try {
4558
+ const parsed = text ? JSON.parse(text) : {};
4559
+ response = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: text };
4560
+ } catch {
4561
+ response = { result: text };
4562
+ }
4563
+ return {
4564
+ response,
4565
+ imageParts
4566
+ };
4567
+ }
4333
4568
  function textPartsFromBlocks(blocks, field) {
4334
4569
  return mapper$1.ensureTextBlocks(blocks, field).map((block) => {
4335
4570
  if (block.type === "text") return { text: block.text };
@@ -4381,19 +4616,13 @@ function buildGeminiRequest(request, options) {
4381
4616
  } });
4382
4617
  break;
4383
4618
  case "tool_result": {
4384
- let response;
4385
- try {
4386
- const text = mapper$1.textFromBlocks(item.content, `tool_result ${item.callId} content`);
4387
- const parsed = text ? JSON.parse(text) : {};
4388
- response = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: text };
4389
- } catch {
4390
- response = { result: mapper$1.textFromBlocks(item.content, `tool_result ${item.callId} content`) };
4391
- }
4619
+ const { response, imageParts } = mapGeminiToolResultContent(item.content, `tool_result ${item.callId} content`);
4392
4620
  appendPart(contents, "user", { functionResponse: {
4393
4621
  id: item.callId,
4394
4622
  name: item.toolName,
4395
4623
  response
4396
4624
  } });
4625
+ for (const imagePart of imageParts) appendPart(contents, "user", imagePart);
4397
4626
  break;
4398
4627
  }
4399
4628
  case "reasoning":
@@ -4438,6 +4667,7 @@ function buildGeminiRequest(request, options) {
4438
4667
  if (request.maxOutputTokens !== void 0) generationConfig.maxOutputTokens = request.maxOutputTokens;
4439
4668
  if (request.reasoningLevel !== void 0) generationConfig.thinkingConfig = mapGeminiThinking(request.reasoningLevel);
4440
4669
  if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
4670
+ applyPromptCacheFields(body, request, "gemini");
4441
4671
  return body;
4442
4672
  }
4443
4673
  new NormalizedRequestMapper("gemini");