@opencode/ai 2.0.3 → 2.0.4

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.
Files changed (77) hide show
  1. package/dist/cache-policy.js +10 -2
  2. package/dist/effort-updates.d.ts +12 -0
  3. package/dist/effort-updates.js +23 -0
  4. package/dist/protocols/alibaba-messages.d.ts +4 -1
  5. package/dist/protocols/alibaba-responses.d.ts +3 -1
  6. package/dist/protocols/anthropic-messages.d.ts +11 -2
  7. package/dist/protocols/anthropic-messages.js +48 -17
  8. package/dist/protocols/bedrock-converse.js +17 -12
  9. package/dist/protocols/meta-messages.d.ts +4 -1
  10. package/dist/protocols/meta-responses.d.ts +8 -4
  11. package/dist/protocols/mistral-chat.js +5 -33
  12. package/dist/protocols/open-responses-channel.js +4 -0
  13. package/dist/protocols/open-responses.d.ts +27 -9
  14. package/dist/protocols/open-responses.js +24 -8
  15. package/dist/protocols/openai-chat.d.ts +7 -7
  16. package/dist/protocols/openai-chat.js +10 -4
  17. package/dist/protocols/openai-compatible-chat.d.ts +1 -1
  18. package/dist/protocols/openai-compatible-responses.d.ts +4 -2
  19. package/dist/protocols/openai-responses.d.ts +74 -31
  20. package/dist/protocols/openai-responses.js +27 -6
  21. package/dist/protocols/utils/bedrock-media.d.ts +10 -8
  22. package/dist/protocols/utils/bedrock-media.js +29 -5
  23. package/dist/protocols/utils/mistral-tool-id.d.ts +5 -0
  24. package/dist/protocols/utils/mistral-tool-id.js +31 -0
  25. package/dist/protocols/utils/open-responses-options.d.ts +2 -4
  26. package/dist/protocols/utils/open-responses-options.js +2 -2
  27. package/dist/protocols/utils/responses-compaction.js +3 -1
  28. package/dist/protocols/xai-responses.d.ts +4 -2
  29. package/dist/protocols/zai-chat.d.ts +1 -1
  30. package/dist/protocols/zai-messages.d.ts +4 -1
  31. package/dist/provider-error.js +40 -4
  32. package/dist/providers/alibaba.d.ts +15 -10
  33. package/dist/providers/amazon-bedrock-mantle.d.ts +5 -3
  34. package/dist/providers/anthropic-compatible.d.ts +4 -1
  35. package/dist/providers/anthropic.d.ts +4 -1
  36. package/dist/providers/azure.d.ts +19 -5
  37. package/dist/providers/azure.js +1 -8
  38. package/dist/providers/baseten.d.ts +2 -2
  39. package/dist/providers/cerebras.d.ts +2 -2
  40. package/dist/providers/cerebras.js +1 -1
  41. package/dist/providers/cloudflare-ai-gateway.d.ts +1024 -13
  42. package/dist/providers/cloudflare-ai-gateway.js +64 -27
  43. package/dist/providers/cloudflare-workers-ai.d.ts +3 -3
  44. package/dist/providers/cloudflare-workers-ai.js +1 -1
  45. package/dist/providers/deepinfra.d.ts +2 -2
  46. package/dist/providers/deepinfra.js +1 -1
  47. package/dist/providers/deepseek.d.ts +2 -2
  48. package/dist/providers/fireworks.d.ts +2 -2
  49. package/dist/providers/fireworks.js +1 -1
  50. package/dist/providers/google-vertex-chat.d.ts +1 -1
  51. package/dist/providers/google-vertex-messages.d.ts +4 -1
  52. package/dist/providers/google-vertex-responses.d.ts +4 -2
  53. package/dist/providers/google-vertex.js +5 -1
  54. package/dist/providers/groq.d.ts +2 -2
  55. package/dist/providers/meta.d.ts +9 -4
  56. package/dist/providers/meta.js +1 -1
  57. package/dist/providers/minimax.d.ts +9 -4
  58. package/dist/providers/moonshot.d.ts +9 -4
  59. package/dist/providers/moonshot.js +1 -0
  60. package/dist/providers/openai-compatible-responses.d.ts +4 -2
  61. package/dist/providers/openai-compatible.d.ts +1 -1
  62. package/dist/providers/openai.d.ts +10 -3
  63. package/dist/providers/openai.js +4 -1
  64. package/dist/providers/openrouter.d.ts +4 -4
  65. package/dist/providers/openrouter.js +1 -1
  66. package/dist/providers/togetherai.d.ts +2 -2
  67. package/dist/providers/xai.d.ts +1 -1
  68. package/dist/providers/zai-coding-plan.d.ts +9 -4
  69. package/dist/providers/zai.d.ts +1 -1
  70. package/dist/route/client.d.ts +1 -0
  71. package/dist/route/client.js +3 -1
  72. package/dist/route/protocol.d.ts +2 -0
  73. package/dist/schema/messages.d.ts +22 -3
  74. package/dist/schema/messages.js +9 -1
  75. package/dist/schema/options.d.ts +6 -0
  76. package/dist/schema/options.js +7 -0
  77. package/package.json +3 -3
@@ -12,6 +12,7 @@
12
12
  // count against the four-breakpoint budget; auto only fills remaining slots.
13
13
  import { CacheHint } from "./schema/options.js";
14
14
  import { LLMRequest, Message, ToolDefinition } from "./schema/messages.js";
15
+ import { effortUpdate } from "./effort-updates.js";
15
16
  const AUTO = {
16
17
  tools: true,
17
18
  system: true,
@@ -100,10 +101,17 @@ const markMessages = (messages, strategy, hint, budget) => {
100
101
  return markMessageAt(messages, lastIndexOfRole(messages, "user"), hint, budget);
101
102
  if (strategy === "latest-assistant")
102
103
  return markMessageAt(messages, lastIndexOfRole(messages, "assistant"), hint, budget);
103
- const start = Math.max(0, messages.length - strategy.tail);
104
+ let start = messages.length;
105
+ let remaining = strategy.tail;
106
+ while (remaining > 0 && start > 0) {
107
+ start -= 1;
108
+ if (effortUpdate(messages[start]) === undefined)
109
+ remaining -= 1;
110
+ }
104
111
  let next = messages;
105
112
  for (let i = start; i < messages.length; i++)
106
- next = markMessageAt(next, i, hint, budget);
113
+ if (effortUpdate(messages[i]) === undefined)
114
+ next = markMessageAt(next, i, hint, budget);
107
115
  return next;
108
116
  };
109
117
  const countHints = (request) => countToolHints(request.tools) +
@@ -0,0 +1,12 @@
1
+ import { LLMRequest, type EffortPart, type Message } from "./schema/messages.js";
2
+ export declare const effortUpdate: (message: Message) => EffortPart | undefined;
3
+ export declare const stripEffortUpdates: (request: LLMRequest) => LLMRequest<import("./index.js").LanguageModel<{
4
+ readonly [x: string]: unknown;
5
+ }, import("./route.js").CompactionOperations | undefined>>;
6
+ export declare const applyEffortUpdates: (request: LLMRequest) => LLMRequest;
7
+ export declare const resolveEffortUpdates: (request: LLMRequest, current: string | undefined) => {
8
+ request: LLMRequest<import("./index.js").LanguageModel<{
9
+ readonly [x: string]: unknown;
10
+ }, import("./route.js").CompactionOperations | undefined>>;
11
+ effort: string | undefined;
12
+ };
@@ -0,0 +1,23 @@
1
+ // Changing the top-level reasoning effort invalidates the provider prompt cache. Protocols with a native
2
+ // per-message update keep it frozen and lower `Message.effort(...)` markers instead; other routes strip them.
3
+ import { LLMRequest } from "./schema/messages.js";
4
+ export const effortUpdate = (message) => {
5
+ if (message.role !== "system" || message.content.length !== 1)
6
+ return undefined;
7
+ const part = message.content[0];
8
+ return part.type === "effort" ? part : undefined;
9
+ };
10
+ export const stripEffortUpdates = (request) => {
11
+ const messages = request.messages.filter((message) => effortUpdate(message) === undefined);
12
+ return messages.length === request.messages.length ? request : LLMRequest.update(request, { messages });
13
+ };
14
+ export const applyEffortUpdates = (request) => request.model.route.supportsEffortUpdates?.(request) ? request : stripEffortUpdates(request);
15
+ // Reverted or forked history can leave the last marker disagreeing with the requested effort.
16
+ export const resolveEffortUpdates = (request, current) => {
17
+ const updates = request.messages.flatMap((message) => effortUpdate(message) ?? []);
18
+ if (updates.length === 0)
19
+ return { request, effort: current };
20
+ if (updates.at(-1)?.effort !== current)
21
+ return { request: stripEffortUpdates(request), effort: current };
22
+ return { request, effort: updates[0]?.previous };
23
+ };
@@ -187,7 +187,6 @@ export declare const protocol: Protocol<{
187
187
  readonly content: string | null;
188
188
  })[];
189
189
  } | {
190
- readonly role: "system";
191
190
  readonly content: readonly {
192
191
  readonly type: "text";
193
192
  readonly text: string;
@@ -196,6 +195,10 @@ export declare const protocol: Protocol<{
196
195
  readonly ttl?: "1h" | "5m" | undefined;
197
196
  } | undefined;
198
197
  }[];
198
+ readonly role: "system";
199
+ readonly output_config?: {
200
+ readonly effort: string;
201
+ } | undefined;
199
202
  })[];
200
203
  readonly stream: true;
201
204
  readonly metadata?: {
@@ -61,12 +61,15 @@ export declare const protocol: Protocol<{
61
61
  readonly encrypted_content: string;
62
62
  readonly id?: string | null | undefined;
63
63
  } | {
64
+ readonly type: "message";
64
65
  readonly role: "system";
65
66
  readonly content: string;
66
67
  } | {
68
+ readonly type: "message";
67
69
  readonly role: "developer";
68
70
  readonly content: string;
69
71
  } | {
72
+ readonly type: "message";
70
73
  readonly content: readonly ({
71
74
  readonly type: "input_image";
72
75
  readonly image_url: string;
@@ -83,7 +86,6 @@ export declare const protocol: Protocol<{
83
86
  })[];
84
87
  readonly role: "user";
85
88
  readonly id?: string | undefined;
86
- readonly type?: "message" | undefined;
87
89
  readonly status?: string | undefined;
88
90
  } | {
89
91
  readonly type: "message";
@@ -286,6 +286,9 @@ export declare const AnthropicMessagesBody: Schema.Struct<{
286
286
  readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
287
287
  }>>;
288
288
  }>>;
289
+ readonly output_config: Schema.optional<Schema.Struct<{
290
+ readonly effort: Schema.String;
291
+ }>>;
289
292
  }>]>>;
290
293
  tools: Schema.optional<Schema.$Array<Schema.Struct<{
291
294
  readonly name: Schema.String;
@@ -529,7 +532,6 @@ export declare const protocol: Protocol<{
529
532
  readonly content: string | null;
530
533
  })[];
531
534
  } | {
532
- readonly role: "system";
533
535
  readonly content: readonly {
534
536
  readonly type: "text";
535
537
  readonly text: string;
@@ -538,6 +540,10 @@ export declare const protocol: Protocol<{
538
540
  readonly ttl?: "1h" | "5m" | undefined;
539
541
  } | undefined;
540
542
  }[];
543
+ readonly role: "system";
544
+ readonly output_config?: {
545
+ readonly effort: string;
546
+ } | undefined;
541
547
  })[];
542
548
  readonly stream: true;
543
549
  readonly metadata?: {
@@ -883,7 +889,6 @@ export declare const route: Route<{
883
889
  readonly content: string | null;
884
890
  })[];
885
891
  } | {
886
- readonly role: "system";
887
892
  readonly content: readonly {
888
893
  readonly type: "text";
889
894
  readonly text: string;
@@ -892,6 +897,10 @@ export declare const route: Route<{
892
897
  readonly ttl?: "1h" | "5m" | undefined;
893
898
  } | undefined;
894
899
  }[];
900
+ readonly role: "system";
901
+ readonly output_config?: {
902
+ readonly effort: string;
903
+ } | undefined;
895
904
  })[];
896
905
  readonly stream: true;
897
906
  readonly metadata?: {
@@ -11,6 +11,7 @@ import { HttpTransport } from "../route/transport/index.js";
11
11
  import { AIError, HttpOptions, LLMRequest, LLMEvent, mergeJsonRecords, Usage, } from "../schema/index.js";
12
12
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
13
13
  import { classifyProviderFailure } from "../provider-error.js";
14
+ import { effortUpdate, resolveEffortUpdates } from "../effort-updates.js";
14
15
  import * as Cache from "./utils/cache.js";
15
16
  import { Lifecycle } from "./utils/lifecycle.js";
16
17
  import { ToolSchemaProjection } from "./utils/tool-schema.js";
@@ -19,6 +20,7 @@ const ADAPTER = "anthropic-messages";
19
20
  export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
20
21
  export const PATH = "/messages";
21
22
  export const DEFAULT_MAX_TOKENS = 32_000;
23
+ const DEFAULT_EFFORT = "high";
22
24
  const SSE_EVENTS = new Set([
23
25
  "message",
24
26
  "message_start",
@@ -183,7 +185,11 @@ const AnthropicAssistantBlock = Schema.Union([
183
185
  const AnthropicMessage = Schema.Union([
184
186
  Schema.Struct({ role: Schema.Literal("user"), content: Schema.Array(AnthropicUserBlock) }),
185
187
  Schema.Struct({ role: Schema.Literal("assistant"), content: Schema.Array(AnthropicAssistantBlock) }),
186
- Schema.Struct({ role: Schema.Literal("system"), content: Schema.Array(AnthropicTextBlock) }),
188
+ Schema.Struct({
189
+ role: Schema.Literal("system"),
190
+ content: Schema.Array(AnthropicTextBlock),
191
+ output_config: Schema.optional(Schema.Struct({ effort: Schema.String })),
192
+ }),
187
193
  ]).pipe(Schema.toTaggedUnion("role"));
188
194
  const AnthropicTool = Schema.Struct({
189
195
  name: Schema.String,
@@ -697,6 +703,12 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
697
703
  const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
698
704
  for (const [index, message] of request.messages.entries()) {
699
705
  if (message.role === "system") {
706
+ const update = effortUpdate(message);
707
+ if (update) {
708
+ // Accepted at any position, so the text-update placement rules do not apply.
709
+ messages.push({ role: "system", content: [], output_config: { effort: update.effort ?? DEFAULT_EFFORT } });
710
+ continue;
711
+ }
700
712
  if (splitsLocalToolResults(request.messages, index))
701
713
  return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result");
702
714
  if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request, index)) {
@@ -842,17 +854,11 @@ const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (
842
854
  const outputConfigFormat = ProviderShared.isRecord(rawOutputConfig) && ProviderShared.isRecord(rawOutputConfig.format)
843
855
  ? rawOutputConfig.format
844
856
  : undefined;
845
- const output_config = outputConfigEffort === undefined && outputConfigFormat === undefined
846
- ? undefined
847
- : {
848
- ...(outputConfigEffort === undefined ? {} : { effort: outputConfigEffort }),
849
- ...(outputConfigFormat === undefined ? {} : { format: outputConfigFormat }),
850
- };
851
857
  const thinking = yield* resolveThinking(input?.thinking);
852
858
  return {
853
859
  thinking: applyThinkingBindingDefault(request.model, thinking),
854
860
  effort: outputConfigEffort,
855
- output_config,
861
+ format: outputConfigFormat,
856
862
  service_tier,
857
863
  metadata,
858
864
  container,
@@ -860,17 +866,32 @@ const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (
860
866
  cache_control,
861
867
  };
862
868
  });
869
+ // Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
870
+ const claudeVersion = (id) => {
871
+ const match = /(?:^|[./])claude-(?<family>[a-z]+)-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/.exec(id.toLowerCase())?.groups;
872
+ if (!match)
873
+ return undefined;
874
+ return { family: match.family, major: Number(match.major), minor: Number(match.minor ?? 0) };
875
+ };
863
876
  const supportsThinkingBlockBinding = (model) => {
864
877
  const override = model.compatibility?.supportsThinkingBlockBinding;
865
878
  if (override !== undefined)
866
879
  return override;
867
- // Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
868
- const version = /(?:^|[./])claude-[a-z]+-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/i.exec(model.id)?.groups;
869
- if (!version)
880
+ const version = claudeVersion(model.id);
881
+ return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1));
882
+ };
883
+ const supportsEffortUpdates = (model) => {
884
+ const override = model.compatibility?.supportsEffortUpdates;
885
+ if (override !== undefined)
886
+ return override;
887
+ const version = claudeVersion(model.id);
888
+ if (version === undefined)
870
889
  return false;
871
- const major = Number(version.major);
872
- const minor = Number(version.minor ?? 0);
873
- return major > 5 || (major === 5 && minor >= 1);
890
+ if (version.family === "opus")
891
+ return version.major >= 5;
892
+ if (version.family !== "fable" && version.family !== "mythos")
893
+ return false;
894
+ return version.major > 5 || (version.major === 5 && version.minor >= 1);
874
895
  };
875
896
  const applyThinkingBindingDefault = (model, thinking) => {
876
897
  if (thinking?.type === "disabled")
@@ -909,13 +930,15 @@ const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function*
909
930
  });
910
931
  const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request) {
911
932
  const management = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Schema.UndefinedOr(ContextManagement)))(request.providerOptions?.contextManagement);
933
+ const options = yield* resolveOptions(request);
934
+ const updates = resolveEffortUpdates(request, options.effort);
912
935
  const generation = request.generation;
913
936
  const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
914
937
  // Allocate the 4-breakpoint budget in invalidation order: tools → system →
915
938
  // messages. Tools live highest in the cache hierarchy, so when callers
916
939
  // over-mark we keep their tool hints and shed the message-tail ones first.
917
940
  const breakpoints = Cache.newBreakpoints(ANTHROPIC_BREAKPOINT_CAP);
918
- const flattened = ProviderShared.flattenToolRequest(request);
941
+ const flattened = ProviderShared.flattenToolRequest(updates.request);
919
942
  const tools = flattened.tools.length === 0
920
943
  ? undefined
921
944
  : flattened.tools.map((tool) => lowerTool(breakpoints, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility)));
@@ -933,7 +956,12 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
933
956
  if (breakpoints.dropped > 0) {
934
957
  yield* Effect.logWarning(`Anthropic Messages: dropped ${breakpoints.dropped} cache breakpoint(s); the API allows at most ${ANTHROPIC_BREAKPOINT_CAP} per request.`);
935
958
  }
936
- const options = yield* resolveOptions(request);
959
+ const output_config = updates.effort === undefined && options.format === undefined
960
+ ? undefined
961
+ : {
962
+ ...(updates.effort === undefined ? {} : { effort: updates.effort }),
963
+ ...(options.format === undefined ? {} : { format: options.format }),
964
+ };
937
965
  const body = {
938
966
  model: request.model.id,
939
967
  system,
@@ -947,7 +975,7 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
947
975
  top_k: generation?.topK,
948
976
  stop_sequences: generation?.stop,
949
977
  thinking: options.thinking,
950
- output_config: options.output_config,
978
+ output_config,
951
979
  // top-level passthrough per SDK MessageCreateParamsBase:4638,4643,4649,4654,4670
952
980
  cache_control: options.cache_control,
953
981
  container: options.container,
@@ -1404,6 +1432,7 @@ export const protocol = Protocol.make({
1404
1432
  }),
1405
1433
  step,
1406
1434
  },
1435
+ supportsEffortUpdates: (request) => supportsEffortUpdates(request.model),
1407
1436
  });
1408
1437
  export const transport = () => {
1409
1438
  const http = HttpTransport.httpJson({ framing });
@@ -1440,6 +1469,8 @@ function requiredBetaHeaders(body) {
1440
1469
  const replaysCompaction = body.messages.some((message) => message.content.some((block) => block.type === "compaction"));
1441
1470
  if (requestsCompaction || replaysCompaction)
1442
1471
  betas.push("compact-2026-01-12");
1472
+ if (body.messages.some((message) => message.role === "system" && message.output_config !== undefined))
1473
+ betas.push("mid-conversation-output-config-2026-07-01");
1443
1474
  const thinking = body.thinking;
1444
1475
  if (thinking && thinking.type !== "disabled" && thinking.block_binding)
1445
1476
  betas.push("thinking-binding-controls-2026-08-01");
@@ -10,6 +10,7 @@ import { BedrockAuth } from "./utils/bedrock-auth.js";
10
10
  import { BedrockCache } from "./utils/bedrock-cache.js";
11
11
  import { BedrockMedia } from "./utils/bedrock-media.js";
12
12
  import { Lifecycle } from "./utils/lifecycle.js";
13
+ import { MistralToolID } from "./utils/mistral-tool-id.js";
13
14
  import { ToolSchemaProjection } from "./utils/tool-schema.js";
14
15
  import { ToolStream } from "./utils/tool-stream.js";
15
16
  const ADAPTER = "bedrock-converse";
@@ -201,14 +202,15 @@ const removeEmptyToolInputKeys = (input) => {
201
202
  return input;
202
203
  return Object.fromEntries(Object.entries(input).flatMap(([key, value]) => (key === "" ? [] : [[key, removeEmptyToolInputKeys(value)]])));
203
204
  };
204
- const lowerToolCall = (part) => ({
205
+ const lowerToolCall = (part, normalizeID) => ({
205
206
  toolUse: {
206
- toolUseId: part.id,
207
- name: part.name,
207
+ toolUseId: normalizeID(part.id),
208
+ // Models can emit names that Converse rejects when replayed in history.
209
+ name: part.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64) || "_",
208
210
  input: removeEmptyToolInputKeys(part.input),
209
211
  },
210
212
  });
211
- const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part) {
213
+ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part, documentNames) {
212
214
  if (part.result.type === "text" || part.result.type === "error")
213
215
  return [{ text: ProviderShared.toolResultText(part) }];
214
216
  if (part.result.type === "json")
@@ -224,22 +226,25 @@ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent
224
226
  mediaType: item.mime,
225
227
  data: item.uri,
226
228
  filename: item.name,
227
- });
228
- content.push(media);
229
+ }, documentNames);
230
+ content.push(...media);
229
231
  }
230
232
  return content;
231
233
  });
232
- const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part) {
234
+ const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part, documentNames, normalizeID) {
233
235
  return {
234
236
  toolResult: {
235
- toolUseId: part.id,
236
- content: yield* lowerToolResultContent(part),
237
+ toolUseId: normalizeID(part.id),
238
+ content: yield* lowerToolResultContent(part, documentNames),
237
239
  status: part.result.type === "error" ? "error" : "success",
238
240
  },
239
241
  };
240
242
  });
241
243
  const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (request, breakpoints) {
242
244
  const messages = [];
245
+ const documentNames = new Set();
246
+ // Mistral can reject replay IDs even when they satisfy Converse's broader ID syntax.
247
+ const normalizeID = request.model.id.includes("mistral.") ? MistralToolID.normalizer(request) : (id) => id;
243
248
  const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
244
249
  for (const message of request.messages) {
245
250
  if (message.role === "system") {
@@ -262,7 +267,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
262
267
  continue;
263
268
  }
264
269
  if (part.type === "media") {
265
- content.push(yield* BedrockMedia.lower(part));
270
+ content.push(...(yield* BedrockMedia.lower(part, documentNames)));
266
271
  continue;
267
272
  }
268
273
  }
@@ -304,7 +309,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
304
309
  continue;
305
310
  }
306
311
  if (part.type === "tool-call") {
307
- content.push(lowerToolCall(part));
312
+ content.push(lowerToolCall(part, normalizeID));
308
313
  continue;
309
314
  }
310
315
  }
@@ -316,7 +321,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
316
321
  for (const part of message.content) {
317
322
  if (!ProviderShared.supportsContent(part, ["tool-result"]))
318
323
  return yield* ProviderShared.unsupportedContent("Bedrock Converse", "tool", ["tool-result"]);
319
- content.push(yield* lowerToolResult(part));
324
+ content.push(yield* lowerToolResult(part, documentNames, normalizeID));
320
325
  const cachePoint = BedrockCache.block(breakpoints, part.cache);
321
326
  if (cachePoint)
322
327
  content.push(cachePoint);
@@ -175,7 +175,6 @@ export declare const protocol: Protocol<{
175
175
  readonly content: string | null;
176
176
  })[];
177
177
  } | {
178
- readonly role: "system";
179
178
  readonly content: readonly {
180
179
  readonly type: "text";
181
180
  readonly text: string;
@@ -184,6 +183,10 @@ export declare const protocol: Protocol<{
184
183
  readonly ttl?: "1h" | "5m" | undefined;
185
184
  } | undefined;
186
185
  }[];
186
+ readonly role: "system";
187
+ readonly output_config?: {
188
+ readonly effort: string;
189
+ } | undefined;
187
190
  })[];
188
191
  readonly stream: true;
189
192
  readonly metadata?: {
@@ -76,12 +76,15 @@ export declare const protocol: Protocol<{
76
76
  readonly encrypted_content: string;
77
77
  readonly id?: string | null | undefined;
78
78
  } | {
79
+ readonly type: "message";
79
80
  readonly role: "system";
80
81
  readonly content: string;
81
82
  } | {
83
+ readonly type: "message";
82
84
  readonly role: "developer";
83
85
  readonly content: string;
84
86
  } | {
87
+ readonly type: "message";
85
88
  readonly content: readonly ({
86
89
  readonly type: "input_image";
87
90
  readonly image_url: string;
@@ -98,7 +101,6 @@ export declare const protocol: Protocol<{
98
101
  })[];
99
102
  readonly role: "user";
100
103
  readonly id?: string | undefined;
101
- readonly type?: "message" | undefined;
102
104
  readonly status?: string | undefined;
103
105
  } | {
104
106
  readonly type: "message";
@@ -153,7 +155,7 @@ export declare const protocol: Protocol<{
153
155
  readonly instructions?: string | undefined;
154
156
  readonly reasoning?: {
155
157
  readonly summary?: "auto" | "concise" | "detailed" | undefined;
156
- readonly effort?: import("./utils/open-responses-options.js").ReasoningEffort | undefined;
158
+ readonly effort?: import("../schema/options.js").ReasoningEffort | undefined;
157
159
  } | undefined;
158
160
  readonly tools?: readonly ({
159
161
  readonly type: "function";
@@ -332,12 +334,15 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
332
334
  readonly encrypted_content: string;
333
335
  readonly id?: string | null | undefined;
334
336
  } | {
337
+ readonly type: "message";
335
338
  readonly role: "system";
336
339
  readonly content: string;
337
340
  } | {
341
+ readonly type: "message";
338
342
  readonly role: "developer";
339
343
  readonly content: string;
340
344
  } | {
345
+ readonly type: "message";
341
346
  readonly content: readonly ({
342
347
  readonly type: "input_image";
343
348
  readonly image_url: string;
@@ -354,7 +359,6 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
354
359
  })[];
355
360
  readonly role: "user";
356
361
  readonly id?: string | undefined;
357
- readonly type?: "message" | undefined;
358
362
  readonly status?: string | undefined;
359
363
  } | {
360
364
  readonly type: "message";
@@ -409,7 +413,7 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
409
413
  readonly instructions?: string | undefined;
410
414
  readonly reasoning?: {
411
415
  readonly summary?: "auto" | "concise" | "detailed" | undefined;
412
- readonly effort?: import("./utils/open-responses-options.js").ReasoningEffort | undefined;
416
+ readonly effort?: import("../schema/options.js").ReasoningEffort | undefined;
413
417
  } | undefined;
414
418
  readonly tools?: readonly ({
415
419
  readonly type: "function";
@@ -9,10 +9,10 @@ import { AIError, InvalidProviderOutputError, LLMEvent, ProviderInternalError, U
9
9
  import { classifyProviderFailure } from "../provider-error.js";
10
10
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
11
11
  import { Lifecycle } from "./utils/lifecycle.js";
12
+ import { MistralToolID } from "./utils/mistral-tool-id.js";
12
13
  import { ToolStream } from "./utils/tool-stream.js";
13
14
  const ADAPTER = "mistral-chat";
14
15
  const DONE = "[DONE]";
15
- const TOOL_ID = /^[A-Za-z0-9]{9}$/;
16
16
  export const DEFAULT_BASE_URL = "https://api.mistral.ai/v1";
17
17
  export const PATH = "/chat/completions";
18
18
  const MistralTextContent = Schema.Struct({
@@ -140,34 +140,6 @@ const MistralEvent = Schema.StructWithRest(Schema.Struct({
140
140
  error: optionalNull(MistralError),
141
141
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
142
142
  const MistralStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(MistralEvent)]);
143
- const hashID = (value) => {
144
- const hash = (seed) => {
145
- let result = seed;
146
- for (const char of value)
147
- result = Math.imul(result ^ char.charCodeAt(0), 16777619);
148
- return (result >>> 0).toString(36);
149
- };
150
- return `${hash(2166136261).padStart(7, "0")}${hash(2246822519).padStart(7, "0")}`.slice(-9);
151
- };
152
- const toolIDNormalizer = (request) => {
153
- const ids = request.messages.flatMap((message) => message.content.flatMap((part) => (part.type === "tool-call" || part.type === "tool-result" ? [part.id] : [])));
154
- const used = new Set(ids.filter((id) => TOOL_ID.test(id)));
155
- const normalized = new Map();
156
- return (id) => {
157
- if (TOOL_ID.test(id))
158
- return id;
159
- const previous = normalized.get(id);
160
- if (previous)
161
- return previous;
162
- let attempt = 0;
163
- let candidate = hashID(id);
164
- while (used.has(candidate))
165
- candidate = hashID(`${id}:${++attempt}`);
166
- used.add(candidate);
167
- normalized.set(id, candidate);
168
- return candidate;
169
- };
170
- };
171
143
  const lowerMedia = Effect.fn("MistralChat.lowerMedia")(function* (part) {
172
144
  const media = ProviderShared.normalizeMedia(part);
173
145
  const url = typeof part.data === "string" && /^(?:https?:|data:)/.test(part.data) ? part.data : media.dataUrl;
@@ -269,7 +241,7 @@ const lowerToolResults = Effect.fn("MistralChat.lowerToolResults")(function* (me
269
241
  return output;
270
242
  });
271
243
  const lowerMessages = Effect.fn("MistralChat.lowerMessages")(function* (request) {
272
- const normalizeID = toolIDNormalizer(request);
244
+ const normalizeID = MistralToolID.normalizer(request);
273
245
  const messages = request.system.length === 0 ? [] : [{ role: "system", content: ProviderShared.joinText(request.system) }];
274
246
  for (const message of request.messages) {
275
247
  if (message.role === "system") {
@@ -439,15 +411,15 @@ const appendContent = (state, events, content) => {
439
411
  }, state);
440
412
  };
441
413
  const normalizeStreamToolID = (state, source) => {
442
- if (TOOL_ID.test(source))
414
+ if (MistralToolID.valid.test(source))
443
415
  return { id: source, state: { ...state, usedToolIDs: new Set([...state.usedToolIDs, source]) } };
444
416
  const previous = state.toolIDs.get(source);
445
417
  if (previous)
446
418
  return { id: previous, state };
447
419
  let attempt = 0;
448
- let id = hashID(source);
420
+ let id = MistralToolID.hash(source);
449
421
  while (state.usedToolIDs.has(id))
450
- id = hashID(`${source}:${++attempt}`);
422
+ id = MistralToolID.hash(`${source}:${++attempt}`);
451
423
  return {
452
424
  id,
453
425
  state: {
@@ -101,6 +101,10 @@ export const transport = (options) => {
101
101
  };
102
102
  })
103
103
  : undefined;
104
+ if (input.webSocket && !channel)
105
+ yield* Effect.logWarning(`${options.name} does not offer WebSocket for this endpoint; using HTTP`, {
106
+ url: parts.url,
107
+ });
104
108
  return {
105
109
  http: {
106
110
  request: ProviderShared.jsonPost({ url: parts.url, body: parts.bodyText, headers: parts.headers }),