@axlsdk/axl 0.21.1 → 0.22.0

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.js CHANGED
@@ -14,6 +14,75 @@ var AxlError = class extends Error {
14
14
  this.code = code;
15
15
  }
16
16
  };
17
+ var InvalidModelInputError = class extends AxlError {
18
+ constructor(message) {
19
+ super("INVALID_MODEL_INPUT", message);
20
+ this.name = "InvalidModelInputError";
21
+ }
22
+ };
23
+ var UnsupportedModelInputError = class extends AxlError {
24
+ provider;
25
+ model;
26
+ modality;
27
+ source;
28
+ constructor(options) {
29
+ super(
30
+ "UNSUPPORTED_MODEL_INPUT",
31
+ `Provider '${options.provider}' model '${options.model}' does not support ${options.feature ?? options.modality}${options.source ? ` from ${options.source}` : ""}`
32
+ );
33
+ this.name = "UnsupportedModelInputError";
34
+ this.provider = options.provider;
35
+ this.model = options.model;
36
+ this.modality = options.modality;
37
+ this.source = options.source;
38
+ }
39
+ };
40
+ var InvalidTranscriptionInputError = class extends AxlError {
41
+ constructor(message) {
42
+ super("INVALID_TRANSCRIPTION_INPUT", message);
43
+ this.name = "InvalidTranscriptionInputError";
44
+ }
45
+ };
46
+ var UnsupportedTranscriptionInputError = class extends AxlError {
47
+ provider;
48
+ model;
49
+ constructor(options) {
50
+ super(
51
+ "UNSUPPORTED_TRANSCRIPTION_INPUT",
52
+ `Transcription provider '${options.provider}'${options.model ? ` model '${options.model}'` : ""} does not support ${options.feature ?? "this request"}`
53
+ );
54
+ this.name = "UnsupportedTranscriptionInputError";
55
+ this.provider = options.provider;
56
+ this.model = options.model;
57
+ }
58
+ };
59
+ var TranscriptionOperationError = class extends AxlError {
60
+ provider;
61
+ model;
62
+ usage;
63
+ pricingStatus;
64
+ cleanupStatus;
65
+ /** Safe HTTP diagnostics projected from ProviderError when available. */
66
+ status;
67
+ retryable;
68
+ retryAfterMs;
69
+ requestId;
70
+ constructor(options) {
71
+ super("TRANSCRIPTION_PROVIDER_ERROR", "Transcription provider operation failed");
72
+ this.name = "TranscriptionOperationError";
73
+ this.provider = options.provider;
74
+ this.model = options.model;
75
+ this.usage = options.usage;
76
+ this.pricingStatus = options.pricingStatus;
77
+ this.cleanupStatus = options.cleanupStatus;
78
+ this.status = options.status;
79
+ this.retryable = options.retryable;
80
+ this.retryAfterMs = options.retryAfterMs;
81
+ this.requestId = options.requestId;
82
+ if (options.cause !== void 0)
83
+ Object.defineProperty(this, "cause", { enumerable: false, value: options.cause });
84
+ }
85
+ };
17
86
  var EventStreamOverflowError = class extends Error {
18
87
  maxQueued;
19
88
  eventType;
@@ -986,6 +1055,38 @@ function unsafeTransportError(surface, remediation, exposedData) {
986
1055
  }
987
1056
 
988
1057
  // src/providers/openai-compatible.ts
1058
+ var OPENROUTER_IMAGE_MODEL = "openai/gpt-4o-mini";
1059
+ function compatibleImageBase64(source) {
1060
+ return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
1061
+ "base64"
1062
+ );
1063
+ }
1064
+ function openRouterImageParts(parts) {
1065
+ const content = [];
1066
+ for (const part of parts) {
1067
+ if (part.type === "text") {
1068
+ content.push({ type: "text", text: part.text });
1069
+ continue;
1070
+ }
1071
+ const { source } = part;
1072
+ if (source.type === "provider-file") {
1073
+ throw new UnsupportedModelInputError({
1074
+ provider: "openrouter",
1075
+ model: OPENROUTER_IMAGE_MODEL,
1076
+ modality: "image",
1077
+ source: "provider-file"
1078
+ });
1079
+ }
1080
+ content.push({
1081
+ type: "image_url",
1082
+ image_url: {
1083
+ url: source.type === "url" ? source.url : `data:${source.mediaType};base64,${compatibleImageBase64(source)}`
1084
+ }
1085
+ });
1086
+ if (part.label) content.push({ type: "text", text: `[Image: ${part.label}]` });
1087
+ }
1088
+ return content;
1089
+ }
989
1090
  function resolvePerModel(value, model, fallback) {
990
1091
  if (value === void 0) return fallback;
991
1092
  return typeof value === "function" ? value(model) : value;
@@ -1101,6 +1202,41 @@ function normalizeMessageContent(content) {
1101
1202
  }
1102
1203
  var OpenAICompatibleProvider = class {
1103
1204
  name;
1205
+ inputCapabilities(model) {
1206
+ return this.name === "openrouter" && model === OPENROUTER_IMAGE_MODEL ? { image: { sources: ["url", "bytes", "base64"] } } : {};
1207
+ }
1208
+ validateInput(request) {
1209
+ const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
1210
+ const fail = (source, feature) => {
1211
+ throw new UnsupportedModelInputError({
1212
+ provider: this.name,
1213
+ model: effectiveModel || request.model,
1214
+ modality: "image",
1215
+ ...source ? { source } : {},
1216
+ ...feature ? { feature } : {}
1217
+ });
1218
+ };
1219
+ if (this.name !== "openrouter" || effectiveModel !== OPENROUTER_IMAGE_MODEL) {
1220
+ fail(void 0, "image input for this model");
1221
+ }
1222
+ if (request.providerOptions && "messages" in request.providerOptions) {
1223
+ fail(void 0, "raw messages providerOptions");
1224
+ }
1225
+ if (request.hasTools) fail(void 0, "tools with image input");
1226
+ for (const message of request.history) {
1227
+ if (!Array.isArray(message.content)) continue;
1228
+ if (message.role !== "user") fail(void 0, "rich non-user history");
1229
+ if (message.content.some(
1230
+ (part) => part.type === "image" && part.source.type === "provider-file"
1231
+ )) {
1232
+ fail("provider-file");
1233
+ }
1234
+ }
1235
+ if (Array.isArray(request.input) && request.input.some((part) => part.type === "image" && part.source.type === "provider-file")) {
1236
+ fail("provider-file");
1237
+ }
1238
+ return { effectiveModel };
1239
+ }
1104
1240
  /** `json_schema` is honored natively when the profile's `supportsJsonSchema`
1105
1241
  * capability is true for this model (the default); otherwise the engine
1106
1242
  * downgrades the request to plain `json_object` (see `buildRequestBody`),
@@ -1274,7 +1410,7 @@ var OpenAICompatibleProvider = class {
1274
1410
  formatMessage(msg, model) {
1275
1411
  const out = {
1276
1412
  role: this.profile.roleFor ? this.profile.roleFor(msg.role, model) : msg.role,
1277
- content: msg.content
1413
+ content: Array.isArray(msg.content) && this.name === "openrouter" && model === OPENROUTER_IMAGE_MODEL ? openRouterImageParts(msg.content) : msg.content
1278
1414
  };
1279
1415
  if (msg.name && (this.profile.capabilities?.emitsMessageName ?? true)) out.name = msg.name;
1280
1416
  if (msg.tool_calls) out.tool_calls = msg.tool_calls;
@@ -2021,8 +2157,88 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
2021
2157
  };
2022
2158
 
2023
2159
  // src/providers/openai-responses.ts
2160
+ var OPENAI_RESPONSES_IMAGE_MODELS = /* @__PURE__ */ new Set([
2161
+ "gpt-4o",
2162
+ "gpt-4o-2024-08-06",
2163
+ "gpt-4o-2024-11-20",
2164
+ "gpt-4o-mini",
2165
+ "gpt-4o-mini-2024-07-18"
2166
+ ]);
2167
+ function base64FromSource(source) {
2168
+ return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
2169
+ "base64"
2170
+ );
2171
+ }
2172
+ function responseImageParts(parts) {
2173
+ const mapped = [];
2174
+ for (const part of parts) {
2175
+ if (part.type === "text") {
2176
+ mapped.push({ type: "input_text", text: part.text });
2177
+ continue;
2178
+ }
2179
+ const { source } = part;
2180
+ if (source.type === "provider-file") {
2181
+ if (source.provider !== "openai-responses") {
2182
+ throw new UnsupportedModelInputError({
2183
+ provider: "openai-responses",
2184
+ model: "unknown",
2185
+ modality: "image",
2186
+ source: "provider-file"
2187
+ });
2188
+ }
2189
+ mapped.push({ type: "input_image", file_id: source.reference });
2190
+ } else if (source.type === "url") {
2191
+ mapped.push({ type: "input_image", image_url: source.url });
2192
+ } else {
2193
+ mapped.push({
2194
+ type: "input_image",
2195
+ image_url: `data:${source.mediaType};base64,${base64FromSource(source)}`
2196
+ });
2197
+ }
2198
+ if (part.label) mapped.push({ type: "input_text", text: `[Image: ${part.label}]` });
2199
+ }
2200
+ return mapped;
2201
+ }
2024
2202
  var OpenAIResponsesProvider = class {
2025
2203
  name = "openai-responses";
2204
+ inputCapabilities(model) {
2205
+ return OPENAI_RESPONSES_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
2206
+ }
2207
+ validateInput(request) {
2208
+ const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
2209
+ const fail = (source, feature) => {
2210
+ throw new UnsupportedModelInputError({
2211
+ provider: this.name,
2212
+ model: effectiveModel || request.model,
2213
+ modality: "image",
2214
+ ...source ? { source } : {},
2215
+ ...feature ? { feature } : {}
2216
+ });
2217
+ };
2218
+ if (!OPENAI_RESPONSES_IMAGE_MODELS.has(effectiveModel)) {
2219
+ fail(void 0, "image input for this model");
2220
+ }
2221
+ if (request.providerOptions && "input" in request.providerOptions) {
2222
+ fail(void 0, "raw input-container providerOptions");
2223
+ }
2224
+ for (const message of request.history) {
2225
+ if (!Array.isArray(message.content)) continue;
2226
+ if (message.role !== "user") fail(void 0, "rich non-user history");
2227
+ for (const part of message.content) {
2228
+ if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
2229
+ fail("provider-file");
2230
+ }
2231
+ }
2232
+ }
2233
+ if (Array.isArray(request.input)) {
2234
+ for (const part of request.input) {
2235
+ if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
2236
+ fail("provider-file");
2237
+ }
2238
+ }
2239
+ }
2240
+ return { effectiveModel };
2241
+ }
2026
2242
  /** The Responses API honors `json_schema` (structured outputs) natively. */
2027
2243
  nativeStructuredOutputSupport() {
2028
2244
  return "schema";
@@ -2187,11 +2403,12 @@ var OpenAIResponsesProvider = class {
2187
2403
  buildInput(messages) {
2188
2404
  const input = [];
2189
2405
  for (const msg of messages) {
2406
+ const text = typeof msg.content === "string" ? msg.content : "";
2190
2407
  if (msg.role === "tool") {
2191
2408
  input.push({
2192
2409
  type: "function_call_output",
2193
2410
  call_id: msg.tool_call_id ?? "",
2194
- output: msg.content
2411
+ output: text
2195
2412
  });
2196
2413
  } else if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
2197
2414
  const reasoningItems = msg.providerMetadata?.openaiReasoningItems;
@@ -2200,8 +2417,8 @@ var OpenAIResponsesProvider = class {
2200
2417
  input.push(item);
2201
2418
  }
2202
2419
  }
2203
- if (msg.content) {
2204
- input.push({ type: "message", role: "assistant", content: msg.content });
2420
+ if (text) {
2421
+ input.push({ type: "message", role: "assistant", content: text });
2205
2422
  }
2206
2423
  for (const tc of msg.tool_calls) {
2207
2424
  input.push({
@@ -2221,7 +2438,7 @@ var OpenAIResponsesProvider = class {
2221
2438
  input.push({
2222
2439
  type: "message",
2223
2440
  role: msg.role,
2224
- content: msg.content
2441
+ content: typeof msg.content === "string" ? msg.content : responseImageParts(msg.content)
2225
2442
  });
2226
2443
  }
2227
2444
  }
@@ -2288,7 +2505,7 @@ var OpenAIResponsesProvider = class {
2288
2505
  cached_tokens: json.usage.input_tokens_details?.cached_tokens,
2289
2506
  cache_write_tokens: json.usage.input_tokens_details?.cache_write_tokens
2290
2507
  } : void 0;
2291
- const cost = usage ? estimateDirectOpenAICost(json.model ?? model, usage, {
2508
+ const cost = usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(json.model ?? model, usage, {
2292
2509
  baseUrl: this.baseUrl,
2293
2510
  request,
2294
2511
  response: json
@@ -2303,6 +2520,16 @@ var OpenAIResponsesProvider = class {
2303
2520
  providerMetadata
2304
2521
  };
2305
2522
  }
2523
+ /** Responses usage does not report a media-inclusive price, so rich calls stay unpriced. */
2524
+ requestContainsImages(request) {
2525
+ return Array.isArray(request?.input) && request.input.some((item) => {
2526
+ if (!item || typeof item !== "object") return false;
2527
+ const content = item.content;
2528
+ return Array.isArray(content) && content.some(
2529
+ (part) => part !== null && typeof part === "object" && part.type === "input_image"
2530
+ );
2531
+ });
2532
+ }
2306
2533
  // ---------------------------------------------------------------------------
2307
2534
  // Internal: parse SSE stream
2308
2535
  // ---------------------------------------------------------------------------
@@ -2390,7 +2617,7 @@ var OpenAIResponsesProvider = class {
2390
2617
  return {
2391
2618
  type: "done",
2392
2619
  usage,
2393
- cost: usage ? estimateDirectOpenAICost(response?.model ?? model, usage, {
2620
+ cost: usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(response?.model ?? model, usage, {
2394
2621
  baseUrl: this.baseUrl,
2395
2622
  request,
2396
2623
  response
@@ -2427,7 +2654,51 @@ var OpenAIResponsesProvider = class {
2427
2654
  };
2428
2655
 
2429
2656
  // src/providers/anthropic.ts
2657
+ function anthropicBase64(source) {
2658
+ return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
2659
+ "base64"
2660
+ );
2661
+ }
2662
+ function anthropicImageBlocks(parts) {
2663
+ const blocks = [];
2664
+ for (const part of parts) {
2665
+ if (part.type === "text") {
2666
+ blocks.push({ type: "text", text: part.text });
2667
+ continue;
2668
+ }
2669
+ const { source } = part;
2670
+ if (source.type === "provider-file") {
2671
+ if (source.provider !== "anthropic") {
2672
+ throw new UnsupportedModelInputError({
2673
+ provider: "anthropic",
2674
+ model: "unknown",
2675
+ modality: "image",
2676
+ source: "provider-file"
2677
+ });
2678
+ }
2679
+ blocks.push({ type: "image", source: { type: "file", file_id: source.reference } });
2680
+ } else if (source.type === "url") {
2681
+ blocks.push({ type: "image", source: { type: "url", url: source.url } });
2682
+ } else {
2683
+ blocks.push({
2684
+ type: "image",
2685
+ source: { type: "base64", media_type: source.mediaType, data: anthropicBase64(source) }
2686
+ });
2687
+ }
2688
+ if (part.label) blocks.push({ type: "text", text: `[Image: ${part.label}]` });
2689
+ }
2690
+ return blocks;
2691
+ }
2430
2692
  var ANTHROPIC_API_VERSION = "2023-06-01";
2693
+ var ANTHROPIC_FILES_BETA = "files-api-2025-04-14";
2694
+ var ANTHROPIC_IMAGE_MODELS = /* @__PURE__ */ new Set(["claude-sonnet-4-5", "claude-opus-4-8"]);
2695
+ function hasAnthropicProviderFileImage(messages) {
2696
+ return messages.some(
2697
+ (message) => Array.isArray(message.content) && message.content.some(
2698
+ (part) => part.type === "image" && part.source.type === "provider-file" && part.source.provider === "anthropic"
2699
+ )
2700
+ );
2701
+ }
2431
2702
  var CLAUDE_CAPABILITIES = {
2432
2703
  "claude-fable-5": {
2433
2704
  thinking: "adaptive-always-on",
@@ -2688,6 +2959,11 @@ function isEffortAtOrBelow(effort, ceiling) {
2688
2959
  function pricingContextFromBody(body) {
2689
2960
  return {
2690
2961
  model: typeof body.model === "string" ? body.model : void 0,
2962
+ hasRichInput: Array.isArray(body.messages) && body.messages.some(
2963
+ (message) => message !== null && typeof message === "object" && Array.isArray(message.content) && message.content.some(
2964
+ (part) => part !== null && typeof part === "object" && part.type === "image"
2965
+ )
2966
+ ),
2691
2967
  unpricedModifier: isAnthropicModifier("inference_geo", body.inference_geo) || isAnthropicModifier("speed", body.speed) || hasAnthropicServerTool(body.tools) || body.fallbacks !== void 0
2692
2968
  };
2693
2969
  }
@@ -2782,6 +3058,44 @@ function mergeAnthropicUsage(initial, update) {
2782
3058
  }
2783
3059
  var AnthropicProvider = class {
2784
3060
  name = "anthropic";
3061
+ inputCapabilities(model) {
3062
+ return ANTHROPIC_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
3063
+ }
3064
+ validateInput(request) {
3065
+ const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
3066
+ const fail = (source, feature) => {
3067
+ throw new UnsupportedModelInputError({
3068
+ provider: this.name,
3069
+ model: effectiveModel || request.model,
3070
+ modality: "image",
3071
+ ...source ? { source } : {},
3072
+ ...feature ? { feature } : {}
3073
+ });
3074
+ };
3075
+ if (!ANTHROPIC_IMAGE_MODELS.has(effectiveModel)) {
3076
+ fail(void 0, "image input for this model");
3077
+ }
3078
+ if (request.providerOptions && "messages" in request.providerOptions) {
3079
+ fail(void 0, "raw messages providerOptions");
3080
+ }
3081
+ for (const message of request.history) {
3082
+ if (!Array.isArray(message.content)) continue;
3083
+ if (message.role !== "user") fail(void 0, "rich non-user history");
3084
+ for (const part of message.content) {
3085
+ if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
3086
+ fail("provider-file");
3087
+ }
3088
+ }
3089
+ }
3090
+ if (Array.isArray(request.input)) {
3091
+ for (const part of request.input) {
3092
+ if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
3093
+ fail("provider-file");
3094
+ }
3095
+ }
3096
+ }
3097
+ return { effectiveModel };
3098
+ }
2785
3099
  /** Anthropic ignores native `json_schema` structurally — Axl uses a system
2786
3100
  * prompt JSON instruction + client-side Zod validation (see buildRequest). */
2787
3101
  nativeStructuredOutputSupport() {
@@ -2819,7 +3133,10 @@ var AnthropicProvider = class {
2819
3133
  // chat - non-streaming completion
2820
3134
  // ---------------------------------------------------------------------------
2821
3135
  async chat(messages, options) {
2822
- const headers = this.buildHeaders(await this.resolveKey());
3136
+ const headers = this.buildHeaders(
3137
+ await this.resolveKey(),
3138
+ hasAnthropicProviderFileImage(messages)
3139
+ );
2823
3140
  const body = this.buildRequestBody(messages, options, false);
2824
3141
  const pricingContext = pricingContextFromBody(body);
2825
3142
  const res = await fetchWithRetry(
@@ -2850,7 +3167,10 @@ var AnthropicProvider = class {
2850
3167
  // stream - SSE streaming completion
2851
3168
  // ---------------------------------------------------------------------------
2852
3169
  async *stream(messages, options) {
2853
- const headers = this.buildHeaders(await this.resolveKey());
3170
+ const headers = this.buildHeaders(
3171
+ await this.resolveKey(),
3172
+ hasAnthropicProviderFileImage(messages)
3173
+ );
2854
3174
  const body = this.buildRequestBody(messages, options, true);
2855
3175
  const pricingContext = pricingContextFromBody(body);
2856
3176
  const res = await fetchWithRetry(
@@ -2882,11 +3202,12 @@ var AnthropicProvider = class {
2882
3202
  // ---------------------------------------------------------------------------
2883
3203
  // Internal: request building
2884
3204
  // ---------------------------------------------------------------------------
2885
- buildHeaders(apiKey) {
3205
+ buildHeaders(apiKey, requiresFilesBeta = false) {
2886
3206
  return {
2887
3207
  "Content-Type": "application/json",
2888
3208
  "x-api-key": apiKey,
2889
- "anthropic-version": ANTHROPIC_API_VERSION
3209
+ "anthropic-version": ANTHROPIC_API_VERSION,
3210
+ ...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
2890
3211
  };
2891
3212
  }
2892
3213
  /** Extract a human-readable message from an API error response body. */
@@ -2956,12 +3277,13 @@ ${jsonInstruction}` : jsonInstruction;
2956
3277
  mapMessages(messages) {
2957
3278
  const result = [];
2958
3279
  for (const msg of messages) {
3280
+ const text = typeof msg.content === "string" ? msg.content : "";
2959
3281
  if (msg.role === "assistant") {
2960
3282
  const replayedThinking = this.getReplayedThinkingBlocks(msg);
2961
3283
  if (msg.tool_calls && msg.tool_calls.length > 0) {
2962
3284
  const content = [...replayedThinking];
2963
- if (msg.content) {
2964
- content.push({ type: "text", text: msg.content });
3285
+ if (text) {
3286
+ content.push({ type: "text", text });
2965
3287
  }
2966
3288
  for (const tc of msg.tool_calls) {
2967
3289
  let parsedArgs;
@@ -2980,10 +3302,10 @@ ${jsonInstruction}` : jsonInstruction;
2980
3302
  result.push({ role: "assistant", content });
2981
3303
  } else if (replayedThinking.length > 0) {
2982
3304
  const content = [...replayedThinking];
2983
- if (msg.content) content.push({ type: "text", text: msg.content });
3305
+ if (text) content.push({ type: "text", text });
2984
3306
  result.push({ role: "assistant", content });
2985
3307
  } else {
2986
- result.push({ role: "assistant", content: msg.content });
3308
+ result.push({ role: "assistant", content: text });
2987
3309
  }
2988
3310
  } else if (msg.role === "tool") {
2989
3311
  result.push({
@@ -2992,12 +3314,15 @@ ${jsonInstruction}` : jsonInstruction;
2992
3314
  {
2993
3315
  type: "tool_result",
2994
3316
  tool_use_id: msg.tool_call_id,
2995
- content: msg.content
3317
+ content: text
2996
3318
  }
2997
3319
  ]
2998
3320
  });
2999
3321
  } else if (msg.role === "user") {
3000
- result.push({ role: "user", content: msg.content });
3322
+ result.push({
3323
+ role: "user",
3324
+ content: typeof msg.content === "string" ? msg.content : anthropicImageBlocks(msg.content)
3325
+ });
3001
3326
  }
3002
3327
  }
3003
3328
  return this.mergeConsecutiveRoles(result);
@@ -3102,7 +3427,7 @@ ${jsonInstruction}` : jsonInstruction;
3102
3427
  "Anthropic fallback responses cannot be continued safely; retry without fallbacks."
3103
3428
  );
3104
3429
  }
3105
- const cost = normalized && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
3430
+ const cost = normalized && !pricingContext.hasRichInput && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
3106
3431
  return {
3107
3432
  content,
3108
3433
  thinking_content: thinkingContent || void 0,
@@ -3139,7 +3464,7 @@ ${jsonInstruction}` : jsonInstruction;
3139
3464
  const doneChunk = () => ({
3140
3465
  type: "done",
3141
3466
  usage,
3142
- cost: pricingUsage && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
3467
+ cost: pricingUsage && !pricingContext.hasRichInput && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
3143
3468
  providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
3144
3469
  });
3145
3470
  try {
@@ -3287,6 +3612,60 @@ function lastFallbackIndex(blocks) {
3287
3612
  }
3288
3613
 
3289
3614
  // src/providers/gemini.ts
3615
+ function hasRichGeminiMessages(messages) {
3616
+ return messages.some((message) => Array.isArray(message.content));
3617
+ }
3618
+ function geminiImageBase64(source) {
3619
+ return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
3620
+ "base64"
3621
+ );
3622
+ }
3623
+ function geminiInteractionContent(parts, model) {
3624
+ const content = [];
3625
+ for (const part of parts) {
3626
+ if (part.type === "text") {
3627
+ content.push({ type: "text", text: part.text });
3628
+ continue;
3629
+ }
3630
+ const { source } = part;
3631
+ if (source.type === "provider-file") {
3632
+ if (source.provider !== "google") {
3633
+ throw new UnsupportedModelInputError({
3634
+ provider: "google",
3635
+ model,
3636
+ modality: "image",
3637
+ source: "provider-file"
3638
+ });
3639
+ }
3640
+ if (!source.mediaType) {
3641
+ throw new UnsupportedModelInputError({
3642
+ provider: "google",
3643
+ model,
3644
+ modality: "image",
3645
+ source: "provider-file",
3646
+ feature: "Interactions URI image mediaType"
3647
+ });
3648
+ }
3649
+ content.push({
3650
+ type: "image",
3651
+ uri: source.reference,
3652
+ mime_type: source.mediaType
3653
+ });
3654
+ } else if (source.type === "url") {
3655
+ throw new UnsupportedModelInputError({
3656
+ provider: "google",
3657
+ model,
3658
+ modality: "image",
3659
+ source: "url",
3660
+ feature: "direct URL image input; pass bytes/base64 or a Gemini provider-file"
3661
+ });
3662
+ } else {
3663
+ content.push({ type: "image", data: geminiImageBase64(source), mime_type: source.mediaType });
3664
+ }
3665
+ if (part.label) content.push({ type: "text", text: `[Image: ${part.label}]` });
3666
+ }
3667
+ return content;
3668
+ }
3290
3669
  var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
3291
3670
  "additionalProperties",
3292
3671
  "$schema",
@@ -3358,6 +3737,7 @@ var GEMINI_PRICING = {
3358
3737
  "gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
3359
3738
  "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
3360
3739
  };
3740
+ var GEMINI_INTERACTIONS_IMAGE_MODELS = /* @__PURE__ */ new Set(["gemini-3.7-flash"]);
3361
3741
  function isValidTokenCount2(value) {
3362
3742
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3363
3743
  }
@@ -3445,7 +3825,10 @@ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
3445
3825
  ["gemini-3.1-flash-lite-preview", "minimal"],
3446
3826
  ["gemini-3.5-flash", "minimal"],
3447
3827
  ["gemini-3.5-flash-lite", "minimal"],
3448
- ["gemini-3.6-flash", "minimal"]
3828
+ ["gemini-3.6-flash", "minimal"],
3829
+ // L4 live evidence: Interactions gemini-3.7-flash accepts low/medium/high,
3830
+ // but rejects minimal. Keep this exact entry isolated from legacy aliases.
3831
+ ["gemini-3.7-flash", "low"]
3449
3832
  ]);
3450
3833
  var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
3451
3834
  "gemini-3.5-flash-lite",
@@ -3472,6 +3855,61 @@ function warnGemini3xEffortNone(model) {
3472
3855
  }
3473
3856
  var GeminiProvider = class {
3474
3857
  name = "google";
3858
+ inputCapabilities(model) {
3859
+ return GEMINI_INTERACTIONS_IMAGE_MODELS.has(model) ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
3860
+ }
3861
+ validateInput(request) {
3862
+ const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
3863
+ const fail = (source, feature) => {
3864
+ throw new UnsupportedModelInputError({
3865
+ provider: this.name,
3866
+ model: effectiveModel || request.model,
3867
+ modality: "image",
3868
+ ...source ? { source } : {},
3869
+ ...feature ? { feature } : {}
3870
+ });
3871
+ };
3872
+ if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(effectiveModel)) {
3873
+ fail(void 0, "image input for this model");
3874
+ }
3875
+ if (request.providerOptions) {
3876
+ const forbidden = [
3877
+ "input",
3878
+ "contents",
3879
+ "messages",
3880
+ "previous_interaction_id",
3881
+ "background",
3882
+ "stream",
3883
+ "store"
3884
+ ].find((key) => key in request.providerOptions);
3885
+ if (forbidden) fail(void 0, `raw ${forbidden} providerOptions`);
3886
+ }
3887
+ for (const message of request.history) {
3888
+ if (!Array.isArray(message.content)) continue;
3889
+ if (message.role !== "user") fail(void 0, "rich non-user history");
3890
+ for (const part of message.content) {
3891
+ if (part.type !== "image") continue;
3892
+ if (part.source.type === "url")
3893
+ fail("url", "direct URL image input; pass bytes/base64 or a Gemini provider-file");
3894
+ if (part.source.type === "provider-file" && part.source.provider !== this.name)
3895
+ fail("provider-file");
3896
+ if (part.source.type === "provider-file" && !part.source.mediaType)
3897
+ fail("provider-file", "Interactions URI image mediaType");
3898
+ }
3899
+ }
3900
+ if (Array.isArray(request.input)) {
3901
+ for (const part of request.input) {
3902
+ if (part.type !== "image") continue;
3903
+ if (part.source.type === "url")
3904
+ fail("url", "direct URL image input; pass bytes/base64 or a Gemini provider-file");
3905
+ if (part.source.type === "provider-file" && part.source.provider !== this.name)
3906
+ fail("provider-file");
3907
+ if (part.source.type === "provider-file" && !part.source.mediaType)
3908
+ fail("provider-file", "Interactions URI image mediaType");
3909
+ }
3910
+ }
3911
+ return { effectiveModel };
3912
+ }
3475
3913
  /** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
3476
3914
  * keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
3477
3915
  * derived schema can lose constraints — lossy, not faithful. */
@@ -3510,6 +3948,7 @@ var GeminiProvider = class {
3510
3948
  // chat - non-streaming completion
3511
3949
  // ---------------------------------------------------------------------------
3512
3950
  async chat(messages, options) {
3951
+ if (hasRichGeminiMessages(messages)) return this.chatInteraction(messages, options);
3513
3952
  const body = this.buildRequestBody(messages, options);
3514
3953
  const pricingContext = this.pricingContext(this.requestModel(options), body);
3515
3954
  this.assertSafeGemini3Continuation(messages, pricingContext.model);
@@ -3542,6 +3981,10 @@ var GeminiProvider = class {
3542
3981
  // stream - SSE streaming completion
3543
3982
  // ---------------------------------------------------------------------------
3544
3983
  async *stream(messages, options) {
3984
+ if (hasRichGeminiMessages(messages)) {
3985
+ yield* this.streamInteraction(messages, options);
3986
+ return;
3987
+ }
3545
3988
  const body = this.buildRequestBody(messages, options);
3546
3989
  const pricingContext = this.pricingContext(this.requestModel(options), body);
3547
3990
  this.assertSafeGemini3Continuation(messages, pricingContext.model);
@@ -3573,81 +4016,414 @@ var GeminiProvider = class {
3573
4016
  yield* this.parseSSEStream(res.body, pricingContext);
3574
4017
  }
3575
4018
  // ---------------------------------------------------------------------------
3576
- // Internal: request building
4019
+ // Rich image requests use Gemini Interactions. It is deliberately stateless:
4020
+ // Axl sends the complete application-owned history and never asks Google to
4021
+ // retain an interaction or follow a previous_interaction_id.
3577
4022
  // ---------------------------------------------------------------------------
3578
- buildHeaders(apiKey) {
3579
- return {
3580
- "Content-Type": "application/json",
3581
- "x-goog-api-key": apiKey
3582
- };
4023
+ async chatInteraction(messages, options) {
4024
+ this.assertSafeInteractionOptions(options);
4025
+ const body = this.buildInteractionRequestBody(messages, options, false);
4026
+ const headers = this.buildHeaders(await this.resolveKey());
4027
+ const res = await fetchWithRetry(
4028
+ `${this.baseUrl}/interactions`,
4029
+ { method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
4030
+ { governor: this.governor, provider: this.name }
4031
+ );
4032
+ if (!res.ok) {
4033
+ const errorBody = await res.text();
4034
+ throw buildProviderError({
4035
+ provider: this.name,
4036
+ status: res.status,
4037
+ headers: res.headers,
4038
+ message: this.extractErrorMessage(errorBody, res.status),
4039
+ body: errorBody
4040
+ });
4041
+ }
4042
+ return this.parseInteractionResponse(await res.json());
3583
4043
  }
3584
- extractErrorMessage(body, status) {
3585
- try {
3586
- const json = JSON.parse(body);
3587
- if (json.error?.message) {
3588
- return `Gemini API error (${status}): ${json.error.message}`;
3589
- }
3590
- } catch {
4044
+ async *streamInteraction(messages, options) {
4045
+ this.assertSafeInteractionOptions(options);
4046
+ const body = this.buildInteractionRequestBody(messages, options, true);
4047
+ const headers = this.buildHeaders(await this.resolveKey());
4048
+ const res = await fetchWithRetry(
4049
+ `${this.baseUrl}/interactions?alt=sse`,
4050
+ { method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
4051
+ { governor: this.governor, provider: this.name }
4052
+ );
4053
+ if (!res.ok) {
4054
+ const errorBody = await res.text();
4055
+ throw buildProviderError({
4056
+ provider: this.name,
4057
+ status: res.status,
4058
+ headers: res.headers,
4059
+ message: this.extractErrorMessage(errorBody, res.status),
4060
+ body: errorBody
4061
+ });
3591
4062
  }
3592
- return `Gemini API error (${status}): ${body}`;
4063
+ if (!res.body) throw new Error("Gemini Interactions stream has no body");
4064
+ yield* this.parseInteractionSSEStream(res.body);
3593
4065
  }
3594
- pricingContext(fallbackModel, body) {
3595
- return {
3596
- // Gemini's model is normally selected in the URL. providerOptions is an
3597
- // explicit raw escape hatch, so honor a post-merge model override for both
3598
- // the URL and pricing rather than pricing a different request.
3599
- model: typeof body.model === "string" ? body.model : fallbackModel,
3600
- serviceTier: body.serviceTier,
3601
- eligibleRequest: this.baseUrl === "https://generativelanguage.googleapis.com/v1beta" && this.isTextOnlyClientRequest(body)
4066
+ buildInteractionRequestBody(messages, options, stream) {
4067
+ const model = this.requestModel(options);
4068
+ const systemInstruction = messages.filter((message) => message.role === "system").map((message) => typeof message.content === "string" ? message.content : "").filter(Boolean).join("\n\n");
4069
+ const body = {
4070
+ model,
4071
+ input: this.mapInteractionInput(
4072
+ messages.filter((message) => message.role !== "system"),
4073
+ model
4074
+ ),
4075
+ stream
3602
4076
  };
3603
- }
3604
- requestModel(options) {
3605
- return typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
3606
- }
3607
- assertSafeGemini3Continuation(messages, model) {
3608
- if (GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(model)) {
3609
- const mapped = this.mapMessages(messages.filter((message) => message.role !== "system"));
3610
- if (mapped.at(-1)?.role === "model") {
3611
- throw new Error(
3612
- `${model} does not support a terminal assistant/model prefill; end the request with a user or tool message.`
3613
- );
4077
+ if (systemInstruction) body.system_instruction = systemInstruction;
4078
+ if (options.tools?.length) {
4079
+ body.tools = options.tools.map((tool2) => ({
4080
+ type: "function",
4081
+ name: tool2.function.name,
4082
+ description: tool2.function.description,
4083
+ parameters: sanitizeSchemaForGemini(tool2.function.parameters)
4084
+ }));
4085
+ }
4086
+ const generationConfig = {};
4087
+ if (options.maxTokens !== void 0) generationConfig.max_output_tokens = options.maxTokens;
4088
+ if (options.stop) generationConfig.stop_sequences = options.stop;
4089
+ if (options.toolChoice !== void 0) {
4090
+ generationConfig.tool_choice = typeof options.toolChoice === "string" ? options.toolChoice === "required" ? "any" : options.toolChoice : { allowed_tools: { mode: "any", tools: [options.toolChoice.function.name] } };
4091
+ }
4092
+ const thinking = resolveThinkingOptions(options);
4093
+ if (thinking.thinkingDisabled) {
4094
+ generationConfig.thinking_level = isGemini3x(model) ? minThinkingLevel(model) : "minimal";
4095
+ } else if (thinking.hasBudgetOverride) {
4096
+ generationConfig.thinking_level = budgetToThinkingLevel(thinking.thinkingBudget);
4097
+ } else if (thinking.activeEffort) {
4098
+ generationConfig.thinking_level = isGemini3x(model) ? THINKING_LEVELS[thinking.activeEffort] ?? "medium" : budgetToThinkingLevel(THINKING_BUDGETS2[thinking.activeEffort] ?? 5e3);
4099
+ }
4100
+ if (thinking.includeThoughts) generationConfig.thinking_summaries = "auto";
4101
+ if (Object.keys(generationConfig).length) body.generation_config = generationConfig;
4102
+ if (options.responseFormat?.type && options.responseFormat.type !== "text") {
4103
+ body.response_format = {
4104
+ type: "text",
4105
+ mime_type: "application/json",
4106
+ ...options.responseFormat.type === "json_schema" ? { schema: sanitizeSchemaForGemini(options.responseFormat.json_schema.schema) } : {}
4107
+ };
4108
+ }
4109
+ if (options.providerOptions) {
4110
+ const overrides = { ...options.providerOptions };
4111
+ delete overrides.model;
4112
+ delete overrides.input;
4113
+ delete overrides.contents;
4114
+ delete overrides.messages;
4115
+ delete overrides.previous_interaction_id;
4116
+ delete overrides.background;
4117
+ delete overrides.stream;
4118
+ delete overrides.store;
4119
+ const rawGenerationConfig = overrides.generation_config;
4120
+ if (rawGenerationConfig && typeof rawGenerationConfig === "object" && !Array.isArray(rawGenerationConfig)) {
4121
+ const generationConfigOverrides = { ...rawGenerationConfig };
4122
+ delete generationConfigOverrides.temperature;
4123
+ if (Object.keys(generationConfigOverrides).length)
4124
+ overrides.generation_config = generationConfigOverrides;
4125
+ else delete overrides.generation_config;
3614
4126
  }
4127
+ Object.assign(body, overrides);
3615
4128
  }
3616
- if (!isGemini3x(model)) return;
3617
- const replayedNativeCallIds = /* @__PURE__ */ new Set();
4129
+ body.stream = stream;
4130
+ body.store = false;
4131
+ return body;
4132
+ }
4133
+ assertSafeInteractionOptions(options) {
4134
+ const model = this.requestModel(options);
4135
+ const fail = (feature) => {
4136
+ throw new UnsupportedModelInputError({
4137
+ provider: this.name,
4138
+ model,
4139
+ modality: "image",
4140
+ feature
4141
+ });
4142
+ };
4143
+ if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(model)) fail("image input for this model");
4144
+ const forbidden = [
4145
+ "input",
4146
+ "contents",
4147
+ "messages",
4148
+ "previous_interaction_id",
4149
+ "background",
4150
+ "stream",
4151
+ "store"
4152
+ ].find((key) => key in (options.providerOptions ?? {}));
4153
+ if (forbidden) fail(`raw ${forbidden} providerOptions`);
4154
+ }
4155
+ mapInteractionInput(messages, model) {
4156
+ const steps = [];
3618
4157
  for (const message of messages) {
3619
- if (message.role !== "assistant" || !Array.isArray(message.providerMetadata?.geminiParts)) {
3620
- continue;
3621
- }
3622
- for (const part of message.providerMetadata.geminiParts) {
3623
- const id = part !== null && typeof part === "object" ? part.functionCall?.id : void 0;
3624
- if (!id) continue;
3625
- if (replayedNativeCallIds.has(id)) {
3626
- throw new Error(
3627
- `${model} native functionCall ids must be globally unique across a replay.`
3628
- );
4158
+ if (message.role === "user") {
4159
+ steps.push({
4160
+ type: "user_input",
4161
+ content: Array.isArray(message.content) ? geminiInteractionContent(message.content, model) : [{ type: "text", text: message.content }]
4162
+ });
4163
+ } else if (message.role === "assistant") {
4164
+ const nativeSteps = message.providerMetadata?.geminiInteractionSteps;
4165
+ if (Array.isArray(nativeSteps) && nativeSteps.every(isGeminiInteractionStep)) {
4166
+ steps.push(...nativeSteps);
4167
+ continue;
3629
4168
  }
3630
- replayedNativeCallIds.add(id);
4169
+ if (typeof message.content === "string" && message.content) {
4170
+ steps.push({ type: "model_output", content: [{ type: "text", text: message.content }] });
4171
+ }
4172
+ for (const call of message.tool_calls ?? []) {
4173
+ steps.push({
4174
+ type: "function_call",
4175
+ id: call.id,
4176
+ name: call.function.name,
4177
+ arguments: safeJsonObject(call.function.arguments)
4178
+ });
4179
+ }
4180
+ } else if (message.role === "tool") {
4181
+ steps.push({
4182
+ type: "function_result",
4183
+ call_id: message.tool_call_id ?? "",
4184
+ result: [
4185
+ { type: "text", text: typeof message.content === "string" ? message.content : "" }
4186
+ ]
4187
+ });
3631
4188
  }
3632
4189
  }
3633
- const consumedToolMessages = /* @__PURE__ */ new Set();
3634
- for (const [assistantIndex, message] of messages.entries()) {
3635
- if (message.role !== "assistant" || !message.tool_calls?.length) continue;
3636
- const rawParts = message.providerMetadata?.geminiParts;
3637
- if (!Array.isArray(rawParts)) {
3638
- throw new Error(
3639
- `${model} tool continuations require providerMetadata.geminiParts with thought signatures.`
3640
- );
4190
+ return steps;
4191
+ }
4192
+ parseInteractionResponse(json) {
4193
+ let content = "";
4194
+ let thinkingContent = "";
4195
+ const toolCalls = [];
4196
+ for (const step of json.steps ?? []) {
4197
+ if (step.type === "model_output") content += interactionText(step.content);
4198
+ else if (step.type === "thought") thinkingContent += interactionText(step.summary);
4199
+ else if (step.type === "function_call" && step.id && step.name) {
4200
+ toolCalls.push({
4201
+ id: step.id,
4202
+ type: "function",
4203
+ function: { name: step.name, arguments: JSON.stringify(step.arguments ?? {}) }
4204
+ });
3641
4205
  }
3642
- const nativeCalls = rawParts.flatMap((part) => {
3643
- const functionCall = part !== null && typeof part === "object" ? part.functionCall : void 0;
3644
- return functionCall?.id ? [{ id: functionCall.id, name: functionCall.name, part }] : [];
3645
- });
3646
- const expectedCalls = new Map(
3647
- message.tool_calls.map((call) => [call.id, call.function.name])
3648
- );
3649
- if (expectedCalls.size !== message.tool_calls.length || nativeCalls.length !== message.tool_calls.length || new Set(nativeCalls.map((call) => call.id)).size !== nativeCalls.length || typeof nativeCalls[0]?.part.thoughtSignature !== "string" || nativeCalls[0].part.thoughtSignature.length === 0 || nativeCalls.some((call) => expectedCalls.get(call.id) !== call.name)) {
3650
- throw new Error(
4206
+ }
4207
+ this.assertInteractionTerminalStatus(json.status, toolCalls.length);
4208
+ const usage = normalizeInteractionUsage(json.usage);
4209
+ return {
4210
+ content,
4211
+ thinking_content: thinkingContent || void 0,
4212
+ tool_calls: toolCalls.length ? toolCalls : void 0,
4213
+ usage,
4214
+ // Interactions returns modality usage but no authoritative monetary cost.
4215
+ cost: void 0,
4216
+ providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
4217
+ };
4218
+ }
4219
+ async *parseInteractionSSEStream(body) {
4220
+ const reader = body.getReader();
4221
+ const decoder = new TextDecoder();
4222
+ let buffer = "";
4223
+ const steps = /* @__PURE__ */ new Map();
4224
+ const argumentBuffers = /* @__PURE__ */ new Map();
4225
+ const stopped = /* @__PURE__ */ new Set();
4226
+ let completed = false;
4227
+ try {
4228
+ while (true) {
4229
+ const { done, value } = await reader.read();
4230
+ if (done) break;
4231
+ buffer += decoder.decode(value, { stream: true });
4232
+ const lines = buffer.split("\n");
4233
+ buffer = lines.pop() ?? "";
4234
+ for (const line of lines) {
4235
+ const trimmed = line.trim();
4236
+ if (!trimmed.startsWith("data: ")) continue;
4237
+ if (trimmed.slice(6) === "[DONE]") continue;
4238
+ let event;
4239
+ try {
4240
+ event = JSON.parse(trimmed.slice(6));
4241
+ } catch {
4242
+ continue;
4243
+ }
4244
+ if (event.event_type === "error") {
4245
+ throw new Error(event.error?.message ?? "Gemini Interactions stream failed");
4246
+ }
4247
+ if (event.event_type === "step.start" && event.step) {
4248
+ const index = event.index;
4249
+ if (index === void 0 || steps.has(index)) {
4250
+ throw new Error("Gemini Interactions stream contained an invalid step.start index");
4251
+ }
4252
+ const step = cloneInteractionStep(event.step);
4253
+ steps.set(index, step);
4254
+ if (step.type === "model_output") {
4255
+ for (const text of interactionTextChunks(step.content)) {
4256
+ yield { type: "text_delta", content: text };
4257
+ }
4258
+ } else if (step.type === "thought") {
4259
+ for (const text of interactionTextChunks(step.summary)) {
4260
+ yield { type: "thinking_delta", content: text };
4261
+ }
4262
+ }
4263
+ if (step.type === "function_call" && step.id && step.name) {
4264
+ argumentBuffers.set(index, "");
4265
+ yield { type: "tool_call_delta", id: step.id, name: step.name };
4266
+ }
4267
+ } else if (event.event_type === "step.delta" && event.delta) {
4268
+ const delta = event.delta;
4269
+ const index = event.index;
4270
+ if (index === void 0 || stopped.has(index)) {
4271
+ throw new Error("Gemini Interactions stream delta arrived outside an active step");
4272
+ }
4273
+ const step = steps.get(index);
4274
+ if (!step)
4275
+ throw new Error("Gemini Interactions stream delta arrived before step.start");
4276
+ if (delta.type === "text" && typeof delta.text === "string" && step.type === "model_output") {
4277
+ appendInteractionContent(step, "content", { type: "text", text: delta.text });
4278
+ yield { type: "text_delta", content: delta.text };
4279
+ } else if (delta.type === "thought_summary" && step.type === "thought" && delta.content) {
4280
+ appendInteractionContent(step, "summary", delta.content);
4281
+ for (const text of interactionTextChunks([delta.content])) {
4282
+ yield { type: "thinking_delta", content: text };
4283
+ }
4284
+ } else if (delta.type === "thought_signature" && step.type === "thought" && typeof delta.signature === "string") {
4285
+ step.signature = delta.signature;
4286
+ } else if (delta.type === "arguments_delta" && step.type === "function_call" && typeof delta.arguments === "string") {
4287
+ argumentBuffers.set(index, (argumentBuffers.get(index) ?? "") + delta.arguments);
4288
+ if (step.id)
4289
+ yield { type: "tool_call_delta", id: step.id, arguments: delta.arguments };
4290
+ }
4291
+ } else if (event.event_type === "step.stop") {
4292
+ const index = event.index;
4293
+ const step = index === void 0 ? void 0 : steps.get(index);
4294
+ if (step === void 0 || stopped.has(index)) {
4295
+ throw new Error("Gemini Interactions stream contained an invalid step.stop index");
4296
+ }
4297
+ if (step.type === "function_call") {
4298
+ const rawArguments = argumentBuffers.get(index) ?? "";
4299
+ if (rawArguments) {
4300
+ try {
4301
+ const parsed = JSON.parse(rawArguments);
4302
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4303
+ throw new Error("not an object");
4304
+ }
4305
+ step.arguments = parsed;
4306
+ } catch {
4307
+ throw new Error(
4308
+ "Gemini Interactions function_call arguments were not valid JSON"
4309
+ );
4310
+ }
4311
+ }
4312
+ }
4313
+ stopped.add(index);
4314
+ } else if (event.event_type === "interaction.completed") {
4315
+ if (steps.size !== stopped.size) {
4316
+ throw new Error("Gemini Interactions stream completed before all steps stopped");
4317
+ }
4318
+ const toolCallCount = [...steps.values()].filter(
4319
+ (step) => step.type === "function_call" && !!step.id && !!step.name
4320
+ ).length;
4321
+ this.assertInteractionTerminalStatus(event.interaction?.status, toolCallCount);
4322
+ completed = true;
4323
+ yield {
4324
+ type: "done",
4325
+ usage: normalizeInteractionUsage(event.interaction?.usage),
4326
+ providerMetadata: steps.size ? {
4327
+ geminiInteractionSteps: [...steps.entries()].sort(([a], [b]) => a - b).map(([, step]) => step)
4328
+ } : void 0
4329
+ };
4330
+ return;
4331
+ }
4332
+ }
4333
+ }
4334
+ if (!completed)
4335
+ throw new Error("Gemini Interactions stream ended before interaction.completed");
4336
+ } finally {
4337
+ reader.releaseLock();
4338
+ }
4339
+ }
4340
+ assertInteractionTerminalStatus(status, toolCallCount) {
4341
+ if (status === "completed") return;
4342
+ if (status === "requires_action" && toolCallCount > 0) return;
4343
+ const normalizedStatus = typeof status === "string" ? status : "unknown";
4344
+ throw new ProviderError({
4345
+ provider: this.name,
4346
+ status: 0,
4347
+ retryable: false,
4348
+ message: `Gemini Interactions ended with non-success status: ${normalizedStatus}`
4349
+ });
4350
+ }
4351
+ // ---------------------------------------------------------------------------
4352
+ // Internal: request building
4353
+ // ---------------------------------------------------------------------------
4354
+ buildHeaders(apiKey) {
4355
+ return {
4356
+ "Content-Type": "application/json",
4357
+ "x-goog-api-key": apiKey
4358
+ };
4359
+ }
4360
+ extractErrorMessage(body, status) {
4361
+ try {
4362
+ const json = JSON.parse(body);
4363
+ if (json.error?.message) {
4364
+ return `Gemini API error (${status}): ${json.error.message}`;
4365
+ }
4366
+ } catch {
4367
+ }
4368
+ return `Gemini API error (${status}): ${body}`;
4369
+ }
4370
+ pricingContext(fallbackModel, body) {
4371
+ return {
4372
+ // Gemini's model is normally selected in the URL. providerOptions is an
4373
+ // explicit raw escape hatch, so honor a post-merge model override for both
4374
+ // the URL and pricing rather than pricing a different request.
4375
+ model: typeof body.model === "string" ? body.model : fallbackModel,
4376
+ serviceTier: body.serviceTier,
4377
+ eligibleRequest: this.baseUrl === "https://generativelanguage.googleapis.com/v1beta" && this.isTextOnlyClientRequest(body)
4378
+ };
4379
+ }
4380
+ requestModel(options) {
4381
+ return typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
4382
+ }
4383
+ assertSafeGemini3Continuation(messages, model) {
4384
+ if (GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(model)) {
4385
+ const mapped = this.mapMessages(messages.filter((message) => message.role !== "system"));
4386
+ if (mapped.at(-1)?.role === "model") {
4387
+ throw new Error(
4388
+ `${model} does not support a terminal assistant/model prefill; end the request with a user or tool message.`
4389
+ );
4390
+ }
4391
+ }
4392
+ if (!isGemini3x(model)) return;
4393
+ const replayedNativeCallIds = /* @__PURE__ */ new Set();
4394
+ for (const message of messages) {
4395
+ if (message.role !== "assistant" || !Array.isArray(message.providerMetadata?.geminiParts)) {
4396
+ continue;
4397
+ }
4398
+ for (const part of message.providerMetadata.geminiParts) {
4399
+ const id = part !== null && typeof part === "object" ? part.functionCall?.id : void 0;
4400
+ if (!id) continue;
4401
+ if (replayedNativeCallIds.has(id)) {
4402
+ throw new Error(
4403
+ `${model} native functionCall ids must be globally unique across a replay.`
4404
+ );
4405
+ }
4406
+ replayedNativeCallIds.add(id);
4407
+ }
4408
+ }
4409
+ const consumedToolMessages = /* @__PURE__ */ new Set();
4410
+ for (const [assistantIndex, message] of messages.entries()) {
4411
+ if (message.role !== "assistant" || !message.tool_calls?.length) continue;
4412
+ const rawParts = message.providerMetadata?.geminiParts;
4413
+ if (!Array.isArray(rawParts)) {
4414
+ throw new Error(
4415
+ `${model} tool continuations require providerMetadata.geminiParts with thought signatures.`
4416
+ );
4417
+ }
4418
+ const nativeCalls = rawParts.flatMap((part) => {
4419
+ const functionCall = part !== null && typeof part === "object" ? part.functionCall : void 0;
4420
+ return functionCall?.id ? [{ id: functionCall.id, name: functionCall.name, part }] : [];
4421
+ });
4422
+ const expectedCalls = new Map(
4423
+ message.tool_calls.map((call) => [call.id, call.function.name])
4424
+ );
4425
+ if (expectedCalls.size !== message.tool_calls.length || nativeCalls.length !== message.tool_calls.length || new Set(nativeCalls.map((call) => call.id)).size !== nativeCalls.length || typeof nativeCalls[0]?.part.thoughtSignature !== "string" || nativeCalls[0].part.thoughtSignature.length === 0 || nativeCalls.some((call) => expectedCalls.get(call.id) !== call.name)) {
4426
+ throw new Error(
3651
4427
  `${model} tool continuations require exact native functionCall id/name and a thoughtSignature on the first function call.`
3652
4428
  );
3653
4429
  }
@@ -3827,14 +4603,15 @@ var GeminiProvider = class {
3827
4603
  }
3828
4604
  const result = [];
3829
4605
  for (const msg of messages) {
4606
+ const text = typeof msg.content === "string" ? msg.content : "";
3830
4607
  if (msg.role === "assistant") {
3831
4608
  const rawParts = msg.providerMetadata?.geminiParts;
3832
4609
  if (rawParts && rawParts.length > 0) {
3833
4610
  result.push({ role: "model", parts: rawParts });
3834
4611
  } else {
3835
4612
  const parts = [];
3836
- if (msg.content) {
3837
- parts.push({ text: msg.content });
4613
+ if (text) {
4614
+ parts.push({ text });
3838
4615
  }
3839
4616
  if (msg.tool_calls && msg.tool_calls.length > 0) {
3840
4617
  for (const tc of msg.tool_calls) {
@@ -3860,7 +4637,7 @@ var GeminiProvider = class {
3860
4637
  const functionName = (msg.tool_call_id ? geminiNativeNames.get(msg.tool_call_id) : void 0) ?? toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
3861
4638
  const functionResponse = {
3862
4639
  name: functionName,
3863
- response: parseGeminiFunctionResponse(msg.content)
4640
+ response: parseGeminiFunctionResponse(text)
3864
4641
  };
3865
4642
  if (msg.tool_call_id && geminiNativeIds.has(msg.tool_call_id)) {
3866
4643
  functionResponse.id = msg.tool_call_id;
@@ -3870,7 +4647,7 @@ var GeminiProvider = class {
3870
4647
  parts: [{ functionResponse }]
3871
4648
  });
3872
4649
  } else if (msg.role === "user") {
3873
- result.push({ role: "user", parts: [{ text: msg.content }] });
4650
+ result.push({ role: "user", parts: [{ text }] });
3874
4651
  }
3875
4652
  }
3876
4653
  return this.mergeConsecutiveRoles(result);
@@ -4058,6 +4835,56 @@ var GeminiProvider = class {
4058
4835
  }
4059
4836
  }
4060
4837
  };
4838
+ function isGeminiInteractionStep(value) {
4839
+ return value !== null && typeof value === "object" && ["user_input", "model_output", "thought", "function_call", "function_result"].includes(
4840
+ value.type
4841
+ );
4842
+ }
4843
+ function interactionText(content) {
4844
+ return content?.map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").join("") ?? "";
4845
+ }
4846
+ function interactionTextChunks(content) {
4847
+ return content?.flatMap(
4848
+ (part) => part.type === "text" && typeof part.text === "string" ? [part.text] : []
4849
+ ) ?? [];
4850
+ }
4851
+ function cloneInteractionStep(step) {
4852
+ return JSON.parse(JSON.stringify(step));
4853
+ }
4854
+ function appendInteractionContent(step, field, content) {
4855
+ const existing = step[field] ?? [];
4856
+ const last = existing.at(-1);
4857
+ if (content.type === "text" && last?.type === "text" && typeof content.text === "string" && typeof last.text === "string") {
4858
+ last.text += content.text;
4859
+ } else {
4860
+ existing.push(content);
4861
+ }
4862
+ step[field] = existing;
4863
+ }
4864
+ function safeJsonObject(value) {
4865
+ try {
4866
+ const parsed = JSON.parse(value);
4867
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4868
+ } catch {
4869
+ return {};
4870
+ }
4871
+ }
4872
+ function normalizeInteractionUsage(usage) {
4873
+ if (!usage || !isValidTokenCount2(usage.total_input_tokens) || !isValidTokenCount2(usage.total_output_tokens) || !isValidTokenCount2(usage.total_tokens)) {
4874
+ return void 0;
4875
+ }
4876
+ const cached = usage.total_cached_tokens;
4877
+ const thinking = usage.total_thought_tokens;
4878
+ if (cached !== void 0 && (!isValidTokenCount2(cached) || cached > usage.total_input_tokens) || thinking !== void 0 && !isValidTokenCount2(thinking))
4879
+ return void 0;
4880
+ return {
4881
+ prompt_tokens: usage.total_input_tokens,
4882
+ completion_tokens: usage.total_output_tokens,
4883
+ total_tokens: usage.total_tokens,
4884
+ ...cached ? { cached_tokens: cached } : {},
4885
+ ...thinking ? { reasoning_tokens: thinking } : {}
4886
+ };
4887
+ }
4061
4888
 
4062
4889
  // src/providers/profiles/openrouter.ts
4063
4890
  var OPENROUTER_PROFILE = {
@@ -4498,59 +5325,1211 @@ var ProviderRegistry = class {
4498
5325
  }
4499
5326
  throw new Error(`Unknown provider "${name}". Registered providers: ${this.list().join(", ")}`);
4500
5327
  }
4501
- /**
4502
- * Resolve a "provider:model" URI string into a Provider instance and model name.
4503
- *
4504
- * Supported formats:
4505
- * - "openai:gpt-4o" -> provider=openai, model=gpt-4o
4506
- * - "anthropic:claude-3" -> provider=anthropic, model=claude-3
4507
- * - "gpt-4o" -> uses defaultProvider from config, model=gpt-4o
4508
- * - undefined / empty -> uses defaultProvider and defaultModel from config
4509
- *
4510
- * @param uri Provider:model string, or just a model name
4511
- * @param config Axl configuration for provider options and defaults
4512
- */
4513
- resolve(uri, config = {}) {
4514
- if (!uri) {
4515
- const providerName2 = config.defaultProvider ?? "openai";
4516
- const model2 = config.defaultModel ?? "gpt-4o";
4517
- return { provider: this.get(providerName2, config), model: model2 };
4518
- }
4519
- const colonIndex = uri.indexOf(":");
4520
- if (colonIndex === -1) {
4521
- const providerName2 = config.defaultProvider ?? "openai";
4522
- return { provider: this.get(providerName2, config), model: uri };
4523
- }
4524
- const providerName = uri.slice(0, colonIndex);
4525
- const model = uri.slice(colonIndex + 1);
4526
- if (!providerName || !model) {
4527
- throw new Error(
4528
- `Invalid provider URI "${uri}". Expected format: "provider:model" (e.g. "openai:gpt-4o")`
4529
- );
4530
- }
4531
- return { provider: this.get(providerName, config), model };
5328
+ /**
5329
+ * Resolve a "provider:model" URI string into a Provider instance and model name.
5330
+ *
5331
+ * Supported formats:
5332
+ * - "openai:gpt-4o" -> provider=openai, model=gpt-4o
5333
+ * - "anthropic:claude-3" -> provider=anthropic, model=claude-3
5334
+ * - "gpt-4o" -> uses defaultProvider from config, model=gpt-4o
5335
+ * - undefined / empty -> uses defaultProvider and defaultModel from config
5336
+ *
5337
+ * @param uri Provider:model string, or just a model name
5338
+ * @param config Axl configuration for provider options and defaults
5339
+ */
5340
+ resolve(uri, config = {}) {
5341
+ if (!uri) {
5342
+ const providerName2 = config.defaultProvider ?? "openai";
5343
+ const model2 = config.defaultModel ?? "gpt-4o";
5344
+ return { provider: this.get(providerName2, config), model: model2 };
5345
+ }
5346
+ const colonIndex = uri.indexOf(":");
5347
+ if (colonIndex === -1) {
5348
+ const providerName2 = config.defaultProvider ?? "openai";
5349
+ return { provider: this.get(providerName2, config), model: uri };
5350
+ }
5351
+ const providerName = uri.slice(0, colonIndex);
5352
+ const model = uri.slice(colonIndex + 1);
5353
+ if (!providerName || !model) {
5354
+ throw new Error(
5355
+ `Invalid provider URI "${uri}". Expected format: "provider:model" (e.g. "openai:gpt-4o")`
5356
+ );
5357
+ }
5358
+ return { provider: this.get(providerName, config), model };
5359
+ }
5360
+ /**
5361
+ * Clear all cached provider instances. Useful for testing or reconfiguration.
5362
+ */
5363
+ clearCache() {
5364
+ this.instances.clear();
5365
+ }
5366
+ /**
5367
+ * Clear all registered factories (including built-ins).
5368
+ * Useful for test runtimes where only explicitly registered instances should be used.
5369
+ */
5370
+ clearFactories() {
5371
+ this.factories.clear();
5372
+ }
5373
+ };
5374
+ var defaultRegistry = new ProviderRegistry();
5375
+
5376
+ // src/transcription.ts
5377
+ var MAX_INLINE_TRANSCRIPTION_BYTES = 25 * 1024 * 1024;
5378
+ var RESERVED_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
5379
+ "model",
5380
+ "audio",
5381
+ "signal",
5382
+ "cleanup",
5383
+ "cleanupStatus",
5384
+ "usage",
5385
+ "cost",
5386
+ "pricingStatus",
5387
+ "accounting"
5388
+ ]);
5389
+ function nonEmptyString(value) {
5390
+ return typeof value === "string" && value.trim().length > 0;
5391
+ }
5392
+ function cloneValue(value) {
5393
+ try {
5394
+ return structuredClone(value);
5395
+ } catch {
5396
+ throw new InvalidTranscriptionInputError("Transcription options must be structured-cloneable");
5397
+ }
5398
+ }
5399
+ function normalizeTranscriptionRequest(request) {
5400
+ if (!request || typeof request !== "object" || !nonEmptyString(request.model)) {
5401
+ throw new InvalidTranscriptionInputError("A transcription model URI is required");
5402
+ }
5403
+ const audio = request.audio;
5404
+ if (!audio || typeof audio !== "object") {
5405
+ throw new InvalidTranscriptionInputError("A transcription audio source is required");
5406
+ }
5407
+ let normalizedAudio;
5408
+ if (audio.type === "bytes") {
5409
+ if (!(audio.data instanceof Uint8Array) || audio.data.byteLength === 0 || !nonEmptyString(audio.mediaType)) {
5410
+ throw new InvalidTranscriptionInputError(
5411
+ "Inline transcription bytes require nonempty data and mediaType"
5412
+ );
5413
+ }
5414
+ if (audio.data.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES) {
5415
+ throw new InvalidTranscriptionInputError(
5416
+ "Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
5417
+ );
5418
+ }
5419
+ normalizedAudio = {
5420
+ type: "bytes",
5421
+ data: new Uint8Array(audio.data),
5422
+ mediaType: audio.mediaType
5423
+ };
5424
+ } else if (audio.type === "base64") {
5425
+ if (!nonEmptyString(audio.data) || !nonEmptyString(audio.mediaType)) {
5426
+ throw new InvalidTranscriptionInputError(
5427
+ "Inline base64 transcription audio requires data and mediaType"
5428
+ );
5429
+ }
5430
+ if (audio.data.length > 4 * Math.ceil(MAX_INLINE_TRANSCRIPTION_BYTES / 3)) {
5431
+ throw new InvalidTranscriptionInputError(
5432
+ "Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
5433
+ );
5434
+ }
5435
+ let decoded;
5436
+ try {
5437
+ decoded = Uint8Array.from(Buffer.from(audio.data, "base64"));
5438
+ } catch {
5439
+ throw new InvalidTranscriptionInputError(
5440
+ "Transcription base64 must be canonical and decode to audio"
5441
+ );
5442
+ }
5443
+ if (!decoded.byteLength || Buffer.from(decoded).toString("base64") !== audio.data) {
5444
+ throw new InvalidTranscriptionInputError(
5445
+ "Transcription base64 must be canonical and decode to nonempty audio"
5446
+ );
5447
+ }
5448
+ if (decoded.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES) {
5449
+ throw new InvalidTranscriptionInputError(
5450
+ "Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
5451
+ );
5452
+ }
5453
+ normalizedAudio = { type: "base64", data: audio.data, mediaType: audio.mediaType };
5454
+ } else if (audio.type === "provider-file") {
5455
+ if (!nonEmptyString(audio.provider) || !nonEmptyString(audio.reference)) {
5456
+ throw new InvalidTranscriptionInputError(
5457
+ "A provider-file transcription source requires provider and reference"
5458
+ );
5459
+ }
5460
+ if (audio.mediaType !== void 0 && !nonEmptyString(audio.mediaType)) {
5461
+ throw new InvalidTranscriptionInputError(
5462
+ "Transcription provider-file mediaType must be nonempty when supplied"
5463
+ );
5464
+ }
5465
+ normalizedAudio = {
5466
+ type: "provider-file",
5467
+ provider: audio.provider,
5468
+ reference: audio.reference,
5469
+ ...audio.mediaType !== void 0 ? { mediaType: audio.mediaType } : {}
5470
+ };
5471
+ } else {
5472
+ throw new InvalidTranscriptionInputError(
5473
+ "Transcription audio must be bytes, base64, or provider-file"
5474
+ );
5475
+ }
5476
+ if (request.language !== void 0 && !nonEmptyString(request.language)) {
5477
+ throw new InvalidTranscriptionInputError(
5478
+ "Transcription language must be nonempty when supplied"
5479
+ );
5480
+ }
5481
+ if (request.timestamps !== void 0 && request.timestamps !== "segment" && request.timestamps !== "word") {
5482
+ throw new InvalidTranscriptionInputError("Transcription timestamps must be segment or word");
5483
+ }
5484
+ if (request.diarization !== void 0 && typeof request.diarization !== "boolean") {
5485
+ throw new InvalidTranscriptionInputError("Transcription diarization must be boolean");
5486
+ }
5487
+ if (request.providerOptions !== void 0) {
5488
+ if (!request.providerOptions || Array.isArray(request.providerOptions) || typeof request.providerOptions !== "object") {
5489
+ throw new InvalidTranscriptionInputError("Transcription providerOptions must be an object");
5490
+ }
5491
+ for (const key of Object.keys(request.providerOptions)) {
5492
+ if (RESERVED_PROVIDER_OPTION_KEYS.has(key)) {
5493
+ throw new InvalidTranscriptionInputError(
5494
+ "Transcription providerOptions cannot override reserved operation fields"
5495
+ );
5496
+ }
5497
+ }
5498
+ }
5499
+ return {
5500
+ model: request.model,
5501
+ audio: normalizedAudio,
5502
+ ...request.language !== void 0 ? { language: request.language } : {},
5503
+ ...request.timestamps !== void 0 ? { timestamps: request.timestamps } : {},
5504
+ ...request.diarization !== void 0 ? { diarization: request.diarization } : {},
5505
+ ...request.providerOptions !== void 0 ? { providerOptions: cloneValue(request.providerOptions) } : {}
5506
+ };
5507
+ }
5508
+ function cloneTranscript(transcript) {
5509
+ if (!transcript || typeof transcript !== "object" || typeof transcript.text !== "string") {
5510
+ throw new InvalidTranscriptionInputError(
5511
+ "Transcription provider returned an invalid transcript"
5512
+ );
5513
+ }
5514
+ const cloned = cloneValue(transcript);
5515
+ const accounting = normalizeTranscriptionAccounting(cloned.usage, cloned.pricingStatus);
5516
+ return {
5517
+ text: cloned.text,
5518
+ ...cloned.detectedLanguages ? { detectedLanguages: cloned.detectedLanguages } : {},
5519
+ ...cloned.segments ? { segments: cloned.segments } : {},
5520
+ ...cloned.words ? { words: cloned.words } : {},
5521
+ ...accounting.usage ? { usage: accounting.usage } : {},
5522
+ ...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
5523
+ ...cloned.providerMetadata ? { providerMetadata: cloned.providerMetadata } : {}
5524
+ };
5525
+ }
5526
+ function normalizeTranscriptionAccounting(usageValue, pricingValue) {
5527
+ const value = usageValue && typeof usageValue === "object" ? usageValue : void 0;
5528
+ const number = (key) => {
5529
+ const candidate = value?.[key];
5530
+ return typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0 ? candidate : void 0;
5531
+ };
5532
+ const audioSeconds = number("audioSeconds");
5533
+ const inputTokens = number("inputTokens");
5534
+ const outputTokens = number("outputTokens");
5535
+ const totalTokens = number("totalTokens");
5536
+ const reportedCost = number("cost");
5537
+ const usage = value && (audioSeconds !== void 0 || inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 || reportedCost !== void 0) ? {
5538
+ ...audioSeconds !== void 0 ? { audioSeconds } : {},
5539
+ ...inputTokens !== void 0 ? { inputTokens } : {},
5540
+ ...outputTokens !== void 0 ? { outputTokens } : {},
5541
+ ...totalTokens !== void 0 ? { totalTokens } : {},
5542
+ ...reportedCost !== void 0 ? { cost: reportedCost } : {}
5543
+ } : void 0;
5544
+ const hasWork = (audioSeconds ?? 0) > 0 || (inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0 || (totalTokens ?? 0) > 0;
5545
+ const requested = pricingValue === "priced" || pricingValue === "unpriced" || pricingValue === "zero" ? pricingValue : void 0;
5546
+ const cost = requested === "zero" && (reportedCost === void 0 || reportedCost === 0) ? 0 : reportedCost;
5547
+ const pricingStatus = requested === "zero" && (reportedCost === void 0 || reportedCost === 0) ? "zero" : requested === "priced" && cost !== void 0 ? "priced" : requested === "unpriced" && cost === void 0 ? "unpriced" : cost !== void 0 ? "priced" : hasWork || requested === "unpriced" || requested === "priced" ? "unpriced" : void 0;
5548
+ const tokens = inputTokens !== void 0 || outputTokens !== void 0 ? {
5549
+ ...inputTokens !== void 0 ? { input: inputTokens } : {},
5550
+ ...outputTokens !== void 0 ? { output: outputTokens } : {}
5551
+ } : void 0;
5552
+ return {
5553
+ ...usage ? { usage } : {},
5554
+ ...pricingStatus ? { pricingStatus } : {},
5555
+ ...cost !== void 0 ? { cost } : {},
5556
+ ...tokens ? { tokens } : {},
5557
+ hasWork
5558
+ };
5559
+ }
5560
+
5561
+ // src/providers/transcription-utils.ts
5562
+ var OPENAI_TRANSCRIPTION_MODEL = "gpt-transcribe";
5563
+ var GEMINI_TRANSCRIPTION_MODEL = "gemini-3.5-transcribe";
5564
+ var OPENROUTER_TRANSCRIPTION_MODEL = "openai/whisper-1";
5565
+ function requireExactTranscriptionModel(provider, model, expected) {
5566
+ if (model !== expected) {
5567
+ throw new UnsupportedTranscriptionInputError({
5568
+ provider,
5569
+ model,
5570
+ feature: "this transcription model"
5571
+ });
5572
+ }
5573
+ }
5574
+ function inlineAudioBytes(audio) {
5575
+ if (audio.type === "bytes") return new Uint8Array(audio.data);
5576
+ if (audio.type === "base64") return Uint8Array.from(Buffer.from(audio.data, "base64"));
5577
+ throw new UnsupportedTranscriptionInputError({
5578
+ provider: audio.provider,
5579
+ model: "",
5580
+ feature: "provider-file transcription input"
5581
+ });
5582
+ }
5583
+ function requireNoProviderFile(audio, provider, model) {
5584
+ if (audio.type === "provider-file") {
5585
+ throw new UnsupportedTranscriptionInputError({
5586
+ provider,
5587
+ model,
5588
+ feature: "provider-file transcription input"
5589
+ });
5590
+ }
5591
+ }
5592
+ function assertOnlyProviderOptions(request, provider, model, allowed) {
5593
+ const options = request.providerOptions ?? {};
5594
+ for (const key of Object.keys(options)) {
5595
+ if (!allowed.includes(key)) {
5596
+ throw new InvalidTranscriptionInputError(
5597
+ `Unsupported ${provider} transcription provider option for model '${model}'`
5598
+ );
5599
+ }
5600
+ }
5601
+ return options;
5602
+ }
5603
+ function transcriptUsage(value) {
5604
+ const source = value && typeof value === "object" ? value : {};
5605
+ const number = (...keys) => {
5606
+ for (const key of keys) {
5607
+ const candidate = source[key];
5608
+ if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0)
5609
+ return candidate;
5610
+ }
5611
+ return void 0;
5612
+ };
5613
+ const inputTokens = number("total_input_tokens", "input_tokens", "prompt_tokens", "inputTokens");
5614
+ const outputTokens = number(
5615
+ "total_output_tokens",
5616
+ "output_tokens",
5617
+ "completion_tokens",
5618
+ "outputTokens"
5619
+ );
5620
+ const totalTokens = number("total_tokens", "totalTokens");
5621
+ const audioSeconds = number("seconds", "audio_seconds", "audioSeconds", "duration_seconds");
5622
+ const cost = number("cost");
5623
+ const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 || audioSeconds !== void 0 || cost !== void 0 ? {
5624
+ ...audioSeconds !== void 0 ? { audioSeconds } : {},
5625
+ ...inputTokens !== void 0 ? { inputTokens } : {},
5626
+ ...outputTokens !== void 0 ? { outputTokens } : {},
5627
+ ...totalTokens !== void 0 ? { totalTokens } : {},
5628
+ ...cost !== void 0 ? { cost } : {}
5629
+ } : void 0;
5630
+ const hasWork = (audioSeconds ?? 0) > 0 || (inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0 || (totalTokens ?? 0) > 0;
5631
+ return {
5632
+ ...usage ? { usage } : {},
5633
+ ...cost !== void 0 ? { pricingStatus: "priced" } : hasWork ? { pricingStatus: "unpriced" } : {}
5634
+ };
5635
+ }
5636
+ function safeProviderMessage(provider, status, body) {
5637
+ try {
5638
+ const parsed = JSON.parse(body);
5639
+ const message = parsed.error?.message ?? parsed.message;
5640
+ if (typeof message === "string")
5641
+ return `${provider} transcription API error (${status}): ${message}`;
5642
+ } catch {
5643
+ }
5644
+ return `${provider} transcription API error (${status})`;
5645
+ }
5646
+ function attachTranscriptionFailureMetadata(error, metadata) {
5647
+ for (const [key, value] of Object.entries(metadata)) {
5648
+ if (value !== void 0) Object.defineProperty(error, key, { enumerable: true, value });
5649
+ }
5650
+ return error;
5651
+ }
5652
+
5653
+ // src/providers/openai-transcription.ts
5654
+ var OPENAI_FILE_EXTENSIONS = {
5655
+ "audio/flac": "flac",
5656
+ "audio/m4a": "m4a",
5657
+ "audio/mp3": "mp3",
5658
+ "audio/mp4": "mp4",
5659
+ "audio/mpeg": "mp3",
5660
+ "audio/mpga": "mpga",
5661
+ "audio/ogg": "ogg",
5662
+ "audio/wav": "wav",
5663
+ "audio/webm": "webm"
5664
+ };
5665
+ var OpenAITranscriptionProvider = class {
5666
+ name = "openai-transcription";
5667
+ baseUrl;
5668
+ apiKeySource;
5669
+ governor;
5670
+ constructor(options = {}) {
5671
+ this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
5672
+ this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
5673
+ assertSafeProviderBaseUrl(
5674
+ this.baseUrl,
5675
+ "OpenAI transcription provider",
5676
+ options.dangerouslyAllowInsecureHttp
5677
+ );
5678
+ this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
5679
+ }
5680
+ capabilities(model) {
5681
+ return model === OPENAI_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64"] } : void 0;
5682
+ }
5683
+ async transcribe(request) {
5684
+ requireExactTranscriptionModel(this.name, request.model, OPENAI_TRANSCRIPTION_MODEL);
5685
+ requireNoProviderFile(request.audio, this.name, request.model);
5686
+ if (request.audio.type === "provider-file")
5687
+ throw new UnsupportedTranscriptionInputError({
5688
+ provider: this.name,
5689
+ model: request.model,
5690
+ feature: "provider-file transcription input"
5691
+ });
5692
+ const options = assertOnlyProviderOptions(request, this.name, request.model, [
5693
+ "temperature",
5694
+ "prompt"
5695
+ ]);
5696
+ if (options.temperature !== void 0 && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 1))
5697
+ throw new InvalidTranscriptionInputError(
5698
+ "OpenAI transcription temperature must be a number between 0 and 1"
5699
+ );
5700
+ if (options.prompt !== void 0 && typeof options.prompt !== "string")
5701
+ throw new InvalidTranscriptionInputError("OpenAI transcription prompt must be a string");
5702
+ const mediaType2 = request.audio.mediaType;
5703
+ const extension = OPENAI_FILE_EXTENSIONS[mediaType2];
5704
+ if (!extension)
5705
+ throw new UnsupportedTranscriptionInputError({
5706
+ provider: this.name,
5707
+ model: request.model,
5708
+ feature: "this audio media type"
5709
+ });
5710
+ const bytes = inlineAudioBytes(request.audio);
5711
+ if (bytes.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES)
5712
+ throw new InvalidTranscriptionInputError("OpenAI transcription audio must not exceed 25 MiB");
5713
+ const form = new FormData();
5714
+ form.set("file", new Blob([Buffer.from(bytes)], { type: mediaType2 }), `audio.${extension}`);
5715
+ form.set("model", request.model);
5716
+ form.set("response_format", "json");
5717
+ if (request.language) form.set("language", request.language);
5718
+ if (options.temperature !== void 0) form.set("temperature", String(options.temperature));
5719
+ if (options.prompt !== void 0) form.set("prompt", options.prompt);
5720
+ const key = await this.resolveKey();
5721
+ const res = await fetchWithRetry(
5722
+ `${this.baseUrl}/audio/transcriptions`,
5723
+ {
5724
+ method: "POST",
5725
+ headers: { Authorization: `Bearer ${key}` },
5726
+ body: form,
5727
+ signal: request.signal
5728
+ },
5729
+ { governor: this.governor, provider: this.name }
5730
+ );
5731
+ if (!res.ok) {
5732
+ const body = await res.text();
5733
+ throw buildProviderError({
5734
+ provider: this.name,
5735
+ status: res.status,
5736
+ headers: res.headers,
5737
+ message: safeProviderMessage("OpenAI", res.status, body),
5738
+ body
5739
+ });
5740
+ }
5741
+ const json = await res.json();
5742
+ if (typeof json.text !== "string")
5743
+ throw new InvalidTranscriptionInputError(
5744
+ "OpenAI transcription response did not contain text"
5745
+ );
5746
+ return {
5747
+ transcript: { text: json.text, ...transcriptUsage(json.usage) },
5748
+ cleanupStatus: "not_required"
5749
+ };
5750
+ }
5751
+ async resolveKey() {
5752
+ const key = await resolveApiKey(this.apiKeySource);
5753
+ if (!key)
5754
+ throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
5755
+ return key;
5756
+ }
5757
+ };
5758
+
5759
+ // src/providers/gemini-transcription.ts
5760
+ var CLEANUP_TIMEOUT_MS = 3e3;
5761
+ var READY_TIMEOUT_MS = 3e4;
5762
+ var READY_POLL_MS = 250;
5763
+ var GEMINI_AUDIO_MEDIA_TYPES = /* @__PURE__ */ new Set([
5764
+ "audio/wav",
5765
+ "audio/mp3",
5766
+ "audio/aiff",
5767
+ "audio/aac",
5768
+ "audio/ogg",
5769
+ "audio/flac",
5770
+ "audio/mpeg",
5771
+ "audio/m4a",
5772
+ "audio/l16",
5773
+ "audio/opus",
5774
+ "audio/alaw",
5775
+ "audio/mulaw",
5776
+ "audio/webm"
5777
+ ]);
5778
+ function abortError2() {
5779
+ return new DOMException("The transcription operation was aborted", "AbortError");
5780
+ }
5781
+ function audioMime(audio) {
5782
+ if (!audio.mediaType)
5783
+ throw new UnsupportedTranscriptionInputError({
5784
+ provider: "gemini-transcription",
5785
+ model: GEMINI_TRANSCRIPTION_MODEL,
5786
+ feature: "provider-file audio mediaType"
5787
+ });
5788
+ return audio.mediaType;
5789
+ }
5790
+ function assertGeminiAudioMime(mediaType2) {
5791
+ if (!mediaType2 || !GEMINI_AUDIO_MEDIA_TYPES.has(mediaType2)) {
5792
+ throw new UnsupportedTranscriptionInputError({
5793
+ provider: "gemini-transcription",
5794
+ model: GEMINI_TRANSCRIPTION_MODEL,
5795
+ feature: "this audio media type"
5796
+ });
5797
+ }
5798
+ return mediaType2;
5799
+ }
5800
+ function secondsOffset(value) {
5801
+ if (typeof value !== "string") return void 0;
5802
+ const match = /^(\d+(?:\.\d+)?)s$/.exec(value);
5803
+ if (!match) return void 0;
5804
+ const seconds = Number(match[1]);
5805
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
5806
+ }
5807
+ function sleep3(ms, signal) {
5808
+ return new Promise((resolve2, reject) => {
5809
+ if (signal?.aborted) {
5810
+ reject(abortError2());
5811
+ return;
5812
+ }
5813
+ const timer = setTimeout(done, ms);
5814
+ const onAbort = () => {
5815
+ clearTimeout(timer);
5816
+ done(abortError2());
5817
+ };
5818
+ function done(error) {
5819
+ signal?.removeEventListener("abort", onAbort);
5820
+ if (error) reject(error);
5821
+ else resolve2();
5822
+ }
5823
+ signal?.addEventListener("abort", onAbort, { once: true });
5824
+ });
5825
+ }
5826
+ var GeminiTranscriptionProvider = class {
5827
+ name = "gemini-transcription";
5828
+ baseUrl;
5829
+ apiKeySource;
5830
+ governor;
5831
+ constructor(options = {}) {
5832
+ this.apiKeySource = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
5833
+ this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
5834
+ /\/$/,
5835
+ ""
5836
+ );
5837
+ assertSafeProviderBaseUrl(
5838
+ this.baseUrl,
5839
+ "Gemini transcription provider",
5840
+ options.dangerouslyAllowInsecureHttp
5841
+ );
5842
+ this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
5843
+ }
5844
+ capabilities(model) {
5845
+ return model === GEMINI_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64", "provider-file"], timestamps: ["word"], diarization: true } : void 0;
5846
+ }
5847
+ async transcribe(request) {
5848
+ requireExactTranscriptionModel(this.name, request.model, GEMINI_TRANSCRIPTION_MODEL);
5849
+ const options = assertOnlyProviderOptions(request, this.name, request.model, [
5850
+ "mode",
5851
+ "customVocabulary"
5852
+ ]);
5853
+ const mode = options.mode ?? "verbatim";
5854
+ if (mode !== "smart" && mode !== "verbatim")
5855
+ throw new InvalidTranscriptionInputError(
5856
+ "Gemini transcription mode must be smart or verbatim"
5857
+ );
5858
+ if (options.customVocabulary !== void 0 && (!Array.isArray(options.customVocabulary) || options.customVocabulary.length > 1e3 || options.customVocabulary.some(
5859
+ (value) => typeof value !== "string" || !value.trim() || value.length > 256
5860
+ )))
5861
+ throw new InvalidTranscriptionInputError(
5862
+ "Gemini transcription customVocabulary must contain at most 1000 nonempty strings up to 256 characters"
5863
+ );
5864
+ if (mode === "smart" && (request.timestamps || request.diarization))
5865
+ throw new InvalidTranscriptionInputError(
5866
+ "Gemini smart transcription does not support timestamps or diarization"
5867
+ );
5868
+ if (request.timestamps && options.customVocabulary !== void 0)
5869
+ throw new InvalidTranscriptionInputError(
5870
+ "Gemini transcription customVocabulary cannot be combined with timestamps"
5871
+ );
5872
+ if (request.timestamps && request.timestamps !== "word")
5873
+ throw new UnsupportedTranscriptionInputError({
5874
+ provider: this.name,
5875
+ model: request.model,
5876
+ feature: "segment timestamps"
5877
+ });
5878
+ if (request.diarization && request.timestamps !== "word")
5879
+ throw new InvalidTranscriptionInputError("Gemini diarization requires word timestamps");
5880
+ if (request.audio.type === "provider-file") {
5881
+ if (request.audio.provider !== this.name || typeof request.audio.reference !== "string" || !request.audio.reference.trim()) {
5882
+ throw new UnsupportedTranscriptionInputError({
5883
+ provider: this.name,
5884
+ model: request.model,
5885
+ feature: "a matching nonempty provider-file reference"
5886
+ });
5887
+ }
5888
+ assertGeminiAudioMime(request.audio.mediaType);
5889
+ } else {
5890
+ assertGeminiAudioMime(request.audio.mediaType);
5891
+ }
5892
+ const key = await this.resolveKey();
5893
+ if (request.audio.type === "provider-file")
5894
+ return this.transcribeReference(request, key, mode, options);
5895
+ return this.transcribeUpload(request, key, mode, options);
5896
+ }
5897
+ async transcribeReference(request, key, mode, options) {
5898
+ const audio = request.audio;
5899
+ if (audio.type !== "provider-file" || audio.provider !== this.name)
5900
+ throw new UnsupportedTranscriptionInputError({
5901
+ provider: this.name,
5902
+ model: request.model,
5903
+ feature: "a provider-file owned by another provider"
5904
+ });
5905
+ const mimeType = audioMime(audio);
5906
+ const transcript = await this.infer(request, key, audio.reference, mimeType, mode, options);
5907
+ return { transcript, cleanupStatus: "not_required" };
5908
+ }
5909
+ async transcribeUpload(request, key, mode, options) {
5910
+ let created = false;
5911
+ let fileName;
5912
+ let primaryError;
5913
+ let transcript;
5914
+ let cleanupStatus = "not_required";
5915
+ try {
5916
+ const uploaded = await this.upload(request, key);
5917
+ created = true;
5918
+ fileName = uploaded.name;
5919
+ const ready = await this.waitForReady(
5920
+ this.fileFromJson(uploaded.file, uploaded.mimeType),
5921
+ key,
5922
+ request.signal
5923
+ );
5924
+ transcript = await this.infer(request, key, ready.uri, ready.mimeType, mode, options);
5925
+ } catch (error) {
5926
+ primaryError = error;
5927
+ } finally {
5928
+ if (created && fileName) cleanupStatus = await this.cleanup(fileName, key);
5929
+ }
5930
+ if (primaryError) {
5931
+ throw attachTranscriptionFailureMetadata(
5932
+ primaryError instanceof Error ? primaryError : new Error("Gemini transcription failed"),
5933
+ { cleanupStatus }
5934
+ );
5935
+ }
5936
+ if (!transcript)
5937
+ throw new InvalidTranscriptionInputError(
5938
+ "Gemini transcription response did not contain text"
5939
+ );
5940
+ return { transcript, cleanupStatus };
5941
+ }
5942
+ /** A finalization response with a name creates a caller-owned cleanup obligation
5943
+ * even when its URI/state is malformed. `transcribeUpload` stores the name before
5944
+ * strictly parsing the rest of the file representation. */
5945
+ async upload(request, key) {
5946
+ if (request.audio.type === "provider-file")
5947
+ throw new UnsupportedTranscriptionInputError({
5948
+ provider: this.name,
5949
+ model: request.model,
5950
+ feature: "provider-file upload"
5951
+ });
5952
+ const mimeType = assertGeminiAudioMime(request.audio.mediaType);
5953
+ const bytes = inlineAudioBytes(request.audio);
5954
+ const start = await fetchWithRetry(
5955
+ `${this.baseUrl.replace(/\/v1beta$/, "")}/upload/v1beta/files`,
5956
+ {
5957
+ method: "POST",
5958
+ headers: {
5959
+ "x-goog-api-key": key,
5960
+ "X-Goog-Upload-Protocol": "resumable",
5961
+ "X-Goog-Upload-Command": "start",
5962
+ "X-Goog-Upload-Header-Content-Length": String(bytes.byteLength),
5963
+ "X-Goog-Upload-Header-Content-Type": mimeType,
5964
+ "Content-Type": "application/json"
5965
+ },
5966
+ body: JSON.stringify({ file: { display_name: "axl-transcription" } }),
5967
+ signal: request.signal
5968
+ },
5969
+ { governor: this.governor, provider: this.name, maxRetries: 0 }
5970
+ );
5971
+ if (!start.ok) {
5972
+ const body = await start.text();
5973
+ throw buildProviderError({
5974
+ provider: this.name,
5975
+ status: start.status,
5976
+ headers: start.headers,
5977
+ message: safeProviderMessage("Gemini", start.status, body),
5978
+ body
5979
+ });
5980
+ }
5981
+ const uploadUrl = start.headers.get("x-goog-upload-url");
5982
+ if (!uploadUrl)
5983
+ throw new InvalidTranscriptionInputError(
5984
+ "Gemini transcription upload did not return an upload URL"
5985
+ );
5986
+ let parsedUploadUrl;
5987
+ try {
5988
+ parsedUploadUrl = new URL(uploadUrl);
5989
+ } catch {
5990
+ throw new InvalidTranscriptionInputError(
5991
+ "Gemini transcription upload returned an invalid upload URL"
5992
+ );
5993
+ }
5994
+ const baseOrigin = new URL(this.baseUrl).origin;
5995
+ if (parsedUploadUrl.username || parsedUploadUrl.password || parsedUploadUrl.origin !== baseOrigin) {
5996
+ throw new InvalidTranscriptionInputError(
5997
+ "Gemini transcription upload returned an untrusted upload URL"
5998
+ );
5999
+ }
6000
+ assertSafeProviderBaseUrl(
6001
+ parsedUploadUrl.toString(),
6002
+ "Gemini transcription upload endpoint",
6003
+ new URL(this.baseUrl).protocol === "http:"
6004
+ );
6005
+ const finalize = await fetchWithRetry(
6006
+ parsedUploadUrl.toString(),
6007
+ {
6008
+ method: "POST",
6009
+ headers: {
6010
+ "X-Goog-Upload-Command": "upload, finalize",
6011
+ "X-Goog-Upload-Offset": "0",
6012
+ "Content-Length": String(bytes.byteLength)
6013
+ },
6014
+ body: bytes,
6015
+ signal: request.signal
6016
+ },
6017
+ { governor: this.governor, provider: this.name, maxRetries: 0 }
6018
+ );
6019
+ if (!finalize.ok) {
6020
+ const body = await finalize.text();
6021
+ throw buildProviderError({
6022
+ provider: this.name,
6023
+ status: finalize.status,
6024
+ headers: finalize.headers,
6025
+ message: safeProviderMessage("Gemini", finalize.status, body),
6026
+ body
6027
+ });
6028
+ }
6029
+ const payload = await finalize.json();
6030
+ const file = payload.file;
6031
+ if (!file || typeof file.name !== "string") {
6032
+ throw new InvalidTranscriptionInputError(
6033
+ "Gemini transcription upload did not return a file name"
6034
+ );
6035
+ }
6036
+ return { name: file.name, file, mimeType };
6037
+ }
6038
+ async waitForReady(file, key, signal) {
6039
+ let current = file;
6040
+ const deadline = Date.now() + READY_TIMEOUT_MS;
6041
+ if (!current.state) {
6042
+ throw new InvalidTranscriptionInputError(
6043
+ "Gemini transcription file readiness state is missing"
6044
+ );
6045
+ }
6046
+ while (current.state && current.state !== "ACTIVE") {
6047
+ if (current.state === "FAILED")
6048
+ throw new InvalidTranscriptionInputError("Gemini transcription file processing failed");
6049
+ if (Date.now() >= deadline)
6050
+ throw new InvalidTranscriptionInputError("Gemini transcription file readiness timed out");
6051
+ await sleep3(Math.min(READY_POLL_MS, deadline - Date.now()), signal);
6052
+ const res = await fetchWithRetry(
6053
+ `${this.baseUrl}/${current.name}`,
6054
+ { method: "GET", headers: { "x-goog-api-key": key }, signal },
6055
+ { governor: this.governor, provider: this.name, maxRetries: 0 }
6056
+ );
6057
+ if (!res.ok) {
6058
+ const body = await res.text();
6059
+ throw buildProviderError({
6060
+ provider: this.name,
6061
+ status: res.status,
6062
+ headers: res.headers,
6063
+ message: safeProviderMessage("Gemini", res.status, body),
6064
+ body
6065
+ });
6066
+ }
6067
+ current = this.fileFromJson(await res.json(), current.mimeType);
6068
+ }
6069
+ return current;
6070
+ }
6071
+ async infer(request, key, uri, mimeType, mode, options) {
6072
+ const transcriptionConfig = {
6073
+ mode: mode === "smart" ? "smart" : {
6074
+ type: "verbatim",
6075
+ ...request.timestamps ? { timestamp_granularities: ["word"] } : {},
6076
+ ...request.diarization ? { diarization_mode: "speaker" } : {}
6077
+ },
6078
+ ...request.language ? { language_codes: [request.language] } : {},
6079
+ ...options.customVocabulary ? { custom_vocabulary: options.customVocabulary } : {}
6080
+ };
6081
+ const body = {
6082
+ model: request.model,
6083
+ input: [{ type: "audio", uri, mime_type: mimeType }],
6084
+ store: false,
6085
+ generation_config: { transcription_config: transcriptionConfig }
6086
+ };
6087
+ const res = await fetchWithRetry(
6088
+ `${this.baseUrl}/interactions`,
6089
+ {
6090
+ method: "POST",
6091
+ headers: { "x-goog-api-key": key, "Content-Type": "application/json" },
6092
+ body: JSON.stringify(body),
6093
+ signal: request.signal
6094
+ },
6095
+ { governor: this.governor, provider: this.name }
6096
+ );
6097
+ if (!res.ok) {
6098
+ const errorBody = await res.text();
6099
+ throw buildProviderError({
6100
+ provider: this.name,
6101
+ status: res.status,
6102
+ headers: res.headers,
6103
+ message: safeProviderMessage("Gemini", res.status, errorBody),
6104
+ body: errorBody
6105
+ });
6106
+ }
6107
+ const json = await res.json();
6108
+ if (json.status !== "completed") {
6109
+ throw new InvalidTranscriptionInputError("Gemini transcription interaction did not complete");
6110
+ }
6111
+ const words = [];
6112
+ let text = "";
6113
+ let hasModelOutputText = false;
6114
+ if (Array.isArray(json.steps)) {
6115
+ for (const step of json.steps) {
6116
+ if (!step || typeof step !== "object") continue;
6117
+ const modelOutput = step;
6118
+ if (modelOutput.type !== "model_output" || !Array.isArray(modelOutput.content)) continue;
6119
+ for (const block of modelOutput.content) {
6120
+ if (!block || typeof block !== "object") continue;
6121
+ const content = block;
6122
+ if (content.type !== "text" || typeof content.text !== "string") continue;
6123
+ hasModelOutputText = true;
6124
+ text += content.text;
6125
+ if (!Array.isArray(content.annotations)) continue;
6126
+ for (const raw of content.annotations) {
6127
+ if (!raw || typeof raw !== "object") continue;
6128
+ const annotation = raw;
6129
+ if (annotation.type !== "word_info") continue;
6130
+ if (typeof annotation.text !== "string") continue;
6131
+ const start = secondsOffset(annotation.start_offset);
6132
+ const end = secondsOffset(annotation.end_offset);
6133
+ if (start === void 0 || end === void 0 || end < start) continue;
6134
+ words.push({
6135
+ text: annotation.text,
6136
+ start,
6137
+ end,
6138
+ ...typeof annotation.speaker === "string" ? { speaker: annotation.speaker } : {}
6139
+ });
6140
+ }
6141
+ }
6142
+ }
6143
+ }
6144
+ if (!hasModelOutputText)
6145
+ throw new InvalidTranscriptionInputError(
6146
+ "Gemini transcription response did not contain model output text"
6147
+ );
6148
+ return { text, ...words.length ? { words } : {}, ...transcriptUsage(json.usage) };
6149
+ }
6150
+ fileFromJson(value, fallbackMimeType) {
6151
+ const file = "file" in value && value.file ? value.file : value;
6152
+ if (typeof file.name !== "string" || typeof file.uri !== "string")
6153
+ throw new InvalidTranscriptionInputError(
6154
+ "Gemini transcription upload did not return a file reference"
6155
+ );
6156
+ return {
6157
+ name: file.name,
6158
+ uri: file.uri,
6159
+ mimeType: typeof file.mimeType === "string" ? file.mimeType : typeof file.mime_type === "string" ? file.mime_type : fallbackMimeType,
6160
+ ...typeof file.state === "string" ? { state: file.state } : {}
6161
+ };
6162
+ }
6163
+ async cleanup(fileName, key) {
6164
+ const controller = new AbortController();
6165
+ const timer = setTimeout(() => controller.abort(), CLEANUP_TIMEOUT_MS);
6166
+ try {
6167
+ const res = await fetchWithRetry(
6168
+ `${this.baseUrl}/${fileName}`,
6169
+ { method: "DELETE", headers: { "x-goog-api-key": key }, signal: controller.signal },
6170
+ { governor: this.governor, provider: this.name, maxRetries: 0 }
6171
+ );
6172
+ return res.ok ? "deleted" : "failed";
6173
+ } catch {
6174
+ return controller.signal.aborted ? "timed_out" : "failed";
6175
+ } finally {
6176
+ clearTimeout(timer);
6177
+ }
6178
+ }
6179
+ async resolveKey() {
6180
+ const key = await resolveApiKey(this.apiKeySource);
6181
+ if (!key)
6182
+ throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
6183
+ return key;
6184
+ }
6185
+ };
6186
+
6187
+ // src/providers/openrouter-transcription.ts
6188
+ var FORMAT_BY_MIME = {
6189
+ "audio/aac": "aac",
6190
+ "audio/flac": "flac",
6191
+ "audio/m4a": "m4a",
6192
+ "audio/mp3": "mp3",
6193
+ "audio/mp4": "mp4",
6194
+ "audio/mpeg": "mp3",
6195
+ "audio/ogg": "ogg",
6196
+ "audio/wav": "wav",
6197
+ "audio/webm": "webm"
6198
+ };
6199
+ var OpenRouterTranscriptionProvider = class {
6200
+ name = "openrouter-transcription";
6201
+ baseUrl;
6202
+ apiKeySource;
6203
+ governor;
6204
+ constructor(options = {}) {
6205
+ this.apiKeySource = options.apiKey ?? process.env.OPENROUTER_API_KEY ?? "";
6206
+ this.baseUrl = (options.baseUrl ?? process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1").replace(/\/$/, "");
6207
+ assertSafeProviderBaseUrl(
6208
+ this.baseUrl,
6209
+ "OpenRouter transcription provider",
6210
+ options.dangerouslyAllowInsecureHttp
6211
+ );
6212
+ this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
6213
+ }
6214
+ capabilities(model) {
6215
+ return model === OPENROUTER_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64"] } : void 0;
6216
+ }
6217
+ async transcribe(request) {
6218
+ requireExactTranscriptionModel(this.name, request.model, OPENROUTER_TRANSCRIPTION_MODEL);
6219
+ requireNoProviderFile(request.audio, this.name, request.model);
6220
+ if (request.audio.type === "provider-file")
6221
+ throw new UnsupportedTranscriptionInputError({
6222
+ provider: this.name,
6223
+ model: request.model,
6224
+ feature: "provider-file transcription input"
6225
+ });
6226
+ const options = assertOnlyProviderOptions(request, this.name, request.model, [
6227
+ "temperature",
6228
+ "provider"
6229
+ ]);
6230
+ if (options.temperature !== void 0 && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 1))
6231
+ throw new InvalidTranscriptionInputError(
6232
+ "OpenRouter transcription temperature must be a number between 0 and 1"
6233
+ );
6234
+ if (options.provider !== void 0 && (!options.provider || Array.isArray(options.provider) || typeof options.provider !== "object"))
6235
+ throw new InvalidTranscriptionInputError(
6236
+ "OpenRouter transcription provider routing must be an object"
6237
+ );
6238
+ const format = FORMAT_BY_MIME[request.audio.mediaType];
6239
+ if (!format)
6240
+ throw new UnsupportedTranscriptionInputError({
6241
+ provider: this.name,
6242
+ model: request.model,
6243
+ feature: "this audio media type"
6244
+ });
6245
+ const bytes = inlineAudioBytes(request.audio);
6246
+ const body = {
6247
+ model: request.model,
6248
+ input_audio: { data: Buffer.from(bytes).toString("base64"), format },
6249
+ ...request.language ? { language: request.language } : {},
6250
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
6251
+ ...options.provider !== void 0 ? { provider: options.provider } : {}
6252
+ };
6253
+ const key = await this.resolveKey();
6254
+ const res = await fetchWithRetry(
6255
+ `${this.baseUrl}/audio/transcriptions`,
6256
+ {
6257
+ method: "POST",
6258
+ headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
6259
+ body: JSON.stringify(body),
6260
+ signal: request.signal
6261
+ },
6262
+ { governor: this.governor, provider: this.name }
6263
+ );
6264
+ if (!res.ok) {
6265
+ const errorBody = await res.text();
6266
+ throw buildProviderError({
6267
+ provider: this.name,
6268
+ status: res.status,
6269
+ headers: res.headers,
6270
+ message: safeProviderMessage("OpenRouter", res.status, errorBody),
6271
+ body: errorBody
6272
+ });
6273
+ }
6274
+ const json = await res.json();
6275
+ if (typeof json.text !== "string")
6276
+ throw new InvalidTranscriptionInputError(
6277
+ "OpenRouter transcription response did not contain text"
6278
+ );
6279
+ return {
6280
+ transcript: { text: json.text, ...transcriptUsage(json.usage) },
6281
+ cleanupStatus: "not_required"
6282
+ };
6283
+ }
6284
+ async resolveKey() {
6285
+ const key = await resolveApiKey(this.apiKeySource);
6286
+ if (!key)
6287
+ throw new Error(
6288
+ "OpenRouter API key is required. Set OPENROUTER_API_KEY or pass apiKey in options."
6289
+ );
6290
+ return key;
6291
+ }
6292
+ };
6293
+
6294
+ // src/providers/transcription-registry.ts
6295
+ var builtinFactories2 = {
6296
+ "openai-transcription": (config) => {
6297
+ const opts = config.providers?.openai ?? {};
6298
+ return new OpenAITranscriptionProvider({
6299
+ apiKey: opts.apiKey,
6300
+ baseUrl: opts.baseUrl,
6301
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
6302
+ rateLimit: opts.rateLimit
6303
+ });
6304
+ },
6305
+ "gemini-transcription": (config) => {
6306
+ const opts = config.providers?.google ?? {};
6307
+ return new GeminiTranscriptionProvider({
6308
+ apiKey: opts.apiKey,
6309
+ baseUrl: opts.baseUrl,
6310
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
6311
+ rateLimit: opts.rateLimit
6312
+ });
6313
+ },
6314
+ "openrouter-transcription": (config) => {
6315
+ const opts = config.providers?.openrouter ?? {};
6316
+ return new OpenRouterTranscriptionProvider({
6317
+ apiKey: opts.apiKey,
6318
+ baseUrl: opts.baseUrl,
6319
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
6320
+ rateLimit: opts.rateLimit
6321
+ });
6322
+ }
6323
+ };
6324
+ var TranscriptionProviderRegistry = class {
6325
+ instances = /* @__PURE__ */ new Map();
6326
+ factories = /* @__PURE__ */ new Map();
6327
+ constructor() {
6328
+ for (const [name, factory] of Object.entries(builtinFactories2))
6329
+ this.factories.set(name, factory);
6330
+ }
6331
+ register(name, factory) {
6332
+ this.factories.set(name, factory);
6333
+ this.instances.delete(name);
6334
+ }
6335
+ registerInstance(name, provider) {
6336
+ this.instances.set(name, provider);
6337
+ }
6338
+ has(name) {
6339
+ return this.instances.has(name) || this.factories.has(name);
4532
6340
  }
4533
- /**
4534
- * Clear all cached provider instances. Useful for testing or reconfiguration.
4535
- */
4536
- clearCache() {
4537
- this.instances.clear();
6341
+ get(name, config = {}) {
6342
+ const instance = this.instances.get(name);
6343
+ if (instance) return instance;
6344
+ const factory = this.factories.get(name);
6345
+ if (factory) {
6346
+ const created = factory(config);
6347
+ this.instances.set(name, created);
6348
+ return created;
6349
+ }
6350
+ throw new UnsupportedTranscriptionInputError({
6351
+ provider: name,
6352
+ model: "",
6353
+ feature: "transcription"
6354
+ });
4538
6355
  }
4539
- /**
4540
- * Clear all registered factories (including built-ins).
4541
- * Useful for test runtimes where only explicitly registered instances should be used.
4542
- */
4543
- clearFactories() {
4544
- this.factories.clear();
6356
+ resolve(uri, config = {}) {
6357
+ const colon = typeof uri === "string" ? uri.indexOf(":") : -1;
6358
+ if (colon <= 0 || colon === uri.length - 1 || uri.indexOf(":", colon + 1) !== -1) {
6359
+ throw new UnsupportedTranscriptionInputError({
6360
+ provider: "unknown",
6361
+ model: "",
6362
+ feature: "an explicit transcription provider:model URI"
6363
+ });
6364
+ }
6365
+ const providerName = uri.slice(0, colon);
6366
+ const model = uri.slice(colon + 1);
6367
+ return { provider: this.get(providerName, config), model, providerName };
4545
6368
  }
4546
6369
  };
4547
- var defaultRegistry = new ProviderRegistry();
4548
6370
 
4549
6371
  // src/context.ts
4550
6372
  import { AsyncLocalStorage } from "async_hooks";
4551
6373
  import { randomUUID } from "crypto";
4552
6374
  import { z, ZodError as ZodError2 } from "zod";
4553
6375
 
6376
+ // src/input.ts
6377
+ var MAX_INLINE_MODEL_INPUT_BYTES = 25 * 1024 * 1024;
6378
+ function invalid(message) {
6379
+ throw new InvalidModelInputError(message);
6380
+ }
6381
+ function nonEmptyString2(value, field) {
6382
+ if (typeof value !== "string" || value.length === 0)
6383
+ invalid(`${field} must be a non-empty string`);
6384
+ return value;
6385
+ }
6386
+ function mediaType(value, field) {
6387
+ return nonEmptyString2(value, field);
6388
+ }
6389
+ function validBase64(value) {
6390
+ return value.length > 0 && value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value);
6391
+ }
6392
+ function decodedBase64Bytes(value) {
6393
+ return Math.floor(value.length * 3 / 4) - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
6394
+ }
6395
+ function normalizeModelInput(input) {
6396
+ if (typeof input === "string") return input;
6397
+ if (!Array.isArray(input) || input.length === 0)
6398
+ invalid("ModelInput parts must be a non-empty array");
6399
+ let inlineBytes = 0;
6400
+ const reserveInlineBytes = (bytes) => {
6401
+ if (bytes > MAX_INLINE_MODEL_INPUT_BYTES - inlineBytes) {
6402
+ invalid(
6403
+ "Inline image data must not exceed 25 MiB total; use a URL or provider-file source where supported"
6404
+ );
6405
+ }
6406
+ inlineBytes += bytes;
6407
+ };
6408
+ return input.map((part, index) => {
6409
+ if (!part || typeof part !== "object") invalid(`ModelInput part ${index} must be an object`);
6410
+ if (part.type === "text")
6411
+ return { type: "text", text: nonEmptyString2(part.text, `part ${index}.text`) };
6412
+ if (part.type !== "image") invalid(`part ${index}.type must be 'text' or 'image'`);
6413
+ const source = part.source;
6414
+ if (!source || typeof source !== "object") invalid(`part ${index}.source must be an object`);
6415
+ let clone;
6416
+ switch (source.type) {
6417
+ case "url": {
6418
+ const url = nonEmptyString2(source.url, `part ${index}.source.url`);
6419
+ let parsed;
6420
+ try {
6421
+ parsed = new URL(url);
6422
+ } catch {
6423
+ invalid(`part ${index}.source.url must be an http(s) URL`);
6424
+ }
6425
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
6426
+ invalid(`part ${index}.source.url must be an http(s) URL`);
6427
+ clone = {
6428
+ type: "url",
6429
+ url,
6430
+ ...source.mediaType === void 0 ? {} : { mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`) }
6431
+ };
6432
+ break;
6433
+ }
6434
+ case "bytes":
6435
+ if (!(source.data instanceof Uint8Array) || source.data.byteLength === 0)
6436
+ invalid(`part ${index}.source.data must be a non-empty Uint8Array`);
6437
+ reserveInlineBytes(source.data.byteLength);
6438
+ clone = {
6439
+ type: "bytes",
6440
+ data: source.data.slice(),
6441
+ mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`)
6442
+ };
6443
+ break;
6444
+ case "base64": {
6445
+ const data = nonEmptyString2(source.data, `part ${index}.source.data`);
6446
+ if (data.length > 4 * Math.ceil(MAX_INLINE_MODEL_INPUT_BYTES / 3)) {
6447
+ invalid(
6448
+ "Inline image data must not exceed 25 MiB total; use a URL or provider-file source where supported"
6449
+ );
6450
+ }
6451
+ if (!validBase64(data)) invalid(`part ${index}.source.data must be valid base64`);
6452
+ reserveInlineBytes(decodedBase64Bytes(data));
6453
+ clone = {
6454
+ type: "base64",
6455
+ data,
6456
+ mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`)
6457
+ };
6458
+ break;
6459
+ }
6460
+ case "provider-file":
6461
+ clone = {
6462
+ type: "provider-file",
6463
+ provider: nonEmptyString2(source.provider, `part ${index}.source.provider`),
6464
+ reference: nonEmptyString2(source.reference, `part ${index}.source.reference`),
6465
+ ...source.mediaType === void 0 ? {} : { mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`) }
6466
+ };
6467
+ break;
6468
+ default:
6469
+ invalid(`part ${index}.source.type is unsupported`);
6470
+ }
6471
+ return {
6472
+ type: "image",
6473
+ source: clone,
6474
+ ...part.label === void 0 ? {} : { label: nonEmptyString2(part.label, `part ${index}.label`) }
6475
+ };
6476
+ });
6477
+ }
6478
+ function inputText(input) {
6479
+ return typeof input === "string" ? input : input.filter((part) => part.type === "text").map((part) => part.text).join("\n");
6480
+ }
6481
+ function cloneModelInput(input) {
6482
+ return typeof input === "string" ? input : normalizeModelInput(input);
6483
+ }
6484
+ function describeModelInput(input) {
6485
+ if (typeof input === "string") return void 0;
6486
+ return {
6487
+ parts: input.map((part) => {
6488
+ if (part.type === "text") return { type: "text", characters: part.text.length };
6489
+ const { source } = part;
6490
+ return {
6491
+ type: "image",
6492
+ source: source.type,
6493
+ ...source.mediaType ? { mediaType: source.mediaType } : {},
6494
+ ...source.type === "bytes" ? { bytes: source.data.byteLength } : {},
6495
+ ...source.type === "base64" ? {
6496
+ bytes: decodedBase64Bytes(source.data)
6497
+ } : {},
6498
+ ...(source.type === "url" ? source.url : source.type === "provider-file" ? source.reference : void 0) ? {
6499
+ locator: source.type === "url" ? source.url : source.type === "provider-file" ? source.reference : void 0
6500
+ } : {},
6501
+ ...part.label ? { label: part.label } : {}
6502
+ };
6503
+ })
6504
+ };
6505
+ }
6506
+ function summarizeModelInput(input) {
6507
+ if (typeof input === "string") return input;
6508
+ return input.map(
6509
+ (part) => part.type === "text" ? part.text : `[image ${part.source.mediaType ?? "media"}]`
6510
+ ).join("\n");
6511
+ }
6512
+ function sanitizeModelInputForTrace(input) {
6513
+ return inputText(input);
6514
+ }
6515
+ function normalizePersistedSessionHistory(history) {
6516
+ return history.map((message, index) => {
6517
+ if (typeof message.content === "string") return { ...message };
6518
+ if (message.role !== "user") {
6519
+ throw new InvalidModelInputError(
6520
+ `History message ${index} has non-text content on a non-user role`
6521
+ );
6522
+ }
6523
+ const content = normalizeModelInput(message.content);
6524
+ if (typeof content !== "string" && content.some((part) => part.type === "image" && part.source.type === "bytes")) {
6525
+ throw new InvalidModelInputError(
6526
+ "Uint8Array image input cannot be persisted in session history"
6527
+ );
6528
+ }
6529
+ return { ...message, content };
6530
+ });
6531
+ }
6532
+
4554
6533
  // src/partial-json.ts
4555
6534
  var MAX_DEPTH = 256;
4556
6535
  function parsePartialJson(input) {
@@ -5122,7 +7101,8 @@ var COST_BEARING_LEAF_TYPES = [
5122
7101
  "agent_call_end",
5123
7102
  "tool_call_end",
5124
7103
  "memory_remember",
5125
- "memory_recall"
7104
+ "memory_recall",
7105
+ "transcription_end"
5126
7106
  ];
5127
7107
  var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
5128
7108
  function hasPositiveTokens(event) {
@@ -5130,6 +7110,12 @@ function hasPositiveTokens(event) {
5130
7110
  if (!t) return false;
5131
7111
  return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cached ?? 0) + (t.cacheWrite ?? 0) > 0;
5132
7112
  }
7113
+ function hasPositiveBillableWork(event) {
7114
+ if (hasPositiveTokens(event)) return true;
7115
+ const seconds = event.data?.usage?.audioSeconds;
7116
+ const totalTokens = event.data?.usage?.totalTokens;
7117
+ return typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 || typeof totalTokens === "number" && Number.isFinite(totalTokens) && totalTokens > 0;
7118
+ }
5133
7119
  function isUsableCost(cost) {
5134
7120
  return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
5135
7121
  }
@@ -5142,7 +7128,7 @@ function isCostBearingLeaf(event) {
5142
7128
  return COST_LEAF_SET.has(event.type);
5143
7129
  }
5144
7130
  function isUnpricedLeaf(event) {
5145
- return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveTokens(event);
7131
+ return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveBillableWork(event);
5146
7132
  }
5147
7133
  function isRootLevel(event) {
5148
7134
  const d = event.depth;
@@ -5157,6 +7143,9 @@ var AXL_EVENT_TYPES = [
5157
7143
  // Workflow lifecycle
5158
7144
  "workflow_start",
5159
7145
  "workflow_end",
7146
+ // Transcription operation lifecycle (separate from chat/agent turns)
7147
+ "transcription_start",
7148
+ "transcription_end",
5160
7149
  // Ask boundary
5161
7150
  "ask_start",
5162
7151
  "ask_end",
@@ -5247,6 +7236,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
5247
7236
  "schema_diagnostic",
5248
7237
  "workflow_start",
5249
7238
  "workflow_end",
7239
+ "transcription_start",
7240
+ "transcription_end",
5250
7241
  "checkpoint_save",
5251
7242
  "checkpoint_replay",
5252
7243
  "await_human",
@@ -6062,6 +8053,21 @@ function redactGate(event) {
6062
8053
  }
6063
8054
  };
6064
8055
  }
8056
+ function redactInputDescriptor(input) {
8057
+ return {
8058
+ ...input,
8059
+ parts: input.parts.map((part) => {
8060
+ if (!part || typeof part !== "object" || !("type" in part) || part.type === "text")
8061
+ return part;
8062
+ const value = part;
8063
+ return {
8064
+ ...part,
8065
+ ...value.locator !== void 0 ? { locator: REDACTED } : {},
8066
+ ...value.label !== void 0 ? { label: REDACTED } : {}
8067
+ };
8068
+ })
8069
+ };
8070
+ }
6065
8071
  function redactMemory(event) {
6066
8072
  const d = event.data;
6067
8073
  if (!d || typeof d !== "object") return event;
@@ -6085,7 +8091,11 @@ var LEGACY_REDACTION_RULES = {
6085
8091
  }
6086
8092
  };
6087
8093
  },
6088
- ask_start: (e) => ({ ...e, prompt: REDACTED }),
8094
+ ask_start: (e) => ({
8095
+ ...e,
8096
+ prompt: REDACTED,
8097
+ ...e.input ? { input: redactInputDescriptor(e.input) } : {}
8098
+ }),
6089
8099
  ask_end: (e) => ({
6090
8100
  ...e,
6091
8101
  outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
@@ -6093,6 +8103,14 @@ var LEGACY_REDACTION_RULES = {
6093
8103
  agent_call_start: (e) => {
6094
8104
  const d = e.data;
6095
8105
  const out = { ...d, prompt: REDACTED };
8106
+ if (d.input) {
8107
+ out.input = redactInputDescriptor(d.input);
8108
+ }
8109
+ if (d.messageInputs)
8110
+ out.messageInputs = d.messageInputs.map((entry) => ({
8111
+ ...entry,
8112
+ input: redactInputDescriptor(entry.input)
8113
+ }));
6096
8114
  if (d.system !== void 0) out.system = REDACTED;
6097
8115
  if (Array.isArray(d.messages)) {
6098
8116
  out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
@@ -6248,6 +8266,18 @@ function reuseLegacyRule(type) {
6248
8266
  var COMMON_V2_REDACTION_RULES = {
6249
8267
  workflow_start: reuseLegacyRule("workflow_start"),
6250
8268
  workflow_end: reuseLegacyRule("workflow_end"),
8269
+ transcription_start: (e) => e,
8270
+ transcription_end: (e) => {
8271
+ const d = e.data;
8272
+ return {
8273
+ ...e,
8274
+ data: {
8275
+ ...d,
8276
+ ...d.text !== void 0 ? { text: REDACTED } : {},
8277
+ ...d.error !== void 0 ? { error: REDACTED } : {}
8278
+ }
8279
+ };
8280
+ },
6251
8281
  ask_start: reuseLegacyRule("ask_start"),
6252
8282
  ask_end: reuseLegacyRule("ask_end"),
6253
8283
  agent_call_start: reuseLegacyRule("agent_call_start"),
@@ -6442,7 +8472,7 @@ function resolveConfig(config) {
6442
8472
 
6443
8473
  // src/tool-model-output.ts
6444
8474
  import { types as nodeTypes } from "util";
6445
- function invalid(path, reason) {
8475
+ function invalid2(path, reason) {
6446
8476
  return new Error(`Invalid tool model output at ${path}: ${reason}`);
6447
8477
  }
6448
8478
  function propertyPath(parent, key) {
@@ -6460,14 +8490,14 @@ function propertyPath(parent, key) {
6460
8490
  function normalize(value, path, stack) {
6461
8491
  if (value === null || typeof value === "string" || typeof value === "boolean") return value;
6462
8492
  if (typeof value === "number") {
6463
- if (!Number.isFinite(value)) throw invalid(path, "number must be finite");
8493
+ if (!Number.isFinite(value)) throw invalid2(path, "number must be finite");
6464
8494
  return value;
6465
8495
  }
6466
- if (value === void 0) throw invalid(path, "undefined is only allowed on object properties");
8496
+ if (value === void 0) throw invalid2(path, "undefined is only allowed on object properties");
6467
8497
  if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
6468
- throw invalid(path, `unsupported ${typeof value}`);
8498
+ throw invalid2(path, `unsupported ${typeof value}`);
6469
8499
  }
6470
- if (typeof value !== "object") throw invalid(path, `unsupported ${typeof value}`);
8500
+ if (typeof value !== "object") throw invalid2(path, `unsupported ${typeof value}`);
6471
8501
  let prototype;
6472
8502
  let descriptors;
6473
8503
  try {
@@ -6475,38 +8505,38 @@ function normalize(value, path, stack) {
6475
8505
  descriptors = Object.getOwnPropertyDescriptors(value);
6476
8506
  Object.setPrototypeOf(descriptors, null);
6477
8507
  } catch (cause) {
6478
- throw invalid(
8508
+ throw invalid2(
6479
8509
  path,
6480
8510
  `object inspection failed (${cause instanceof Error ? cause.name : "error"})`
6481
8511
  );
6482
8512
  }
6483
- if (stack.has(value)) throw invalid(path, "cyclic reference");
8513
+ if (stack.has(value)) throw invalid2(path, "cyclic reference");
6484
8514
  for (const key of Reflect.ownKeys(descriptors)) {
6485
8515
  const descriptor = descriptors[key];
6486
8516
  if (descriptor && !("value" in descriptor)) {
6487
- throw invalid(
8517
+ throw invalid2(
6488
8518
  typeof key === "string" ? propertyPath(path, key) : `${path}[symbol]`,
6489
8519
  "accessor properties are not supported"
6490
8520
  );
6491
8521
  }
6492
8522
  if (typeof key === "symbol") {
6493
- throw invalid(`${path}[symbol]`, "symbol keys are not supported");
8523
+ throw invalid2(`${path}[symbol]`, "symbol keys are not supported");
6494
8524
  }
6495
8525
  }
6496
8526
  if (typeof descriptors.toJSON?.value === "function") {
6497
- throw invalid(propertyPath(path, "toJSON"), "custom toJSON is not supported");
8527
+ throw invalid2(propertyPath(path, "toJSON"), "custom toJSON is not supported");
6498
8528
  }
6499
8529
  const array = Array.isArray(value);
6500
8530
  if (array && prototype !== Array.prototype && prototype !== null) {
6501
- throw invalid(path, "array subclasses are not supported");
8531
+ throw invalid2(path, "array subclasses are not supported");
6502
8532
  }
6503
8533
  const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
6504
8534
  if (typeof inheritedToJSON?.value === "function") {
6505
- throw invalid(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
8535
+ throw invalid2(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
6506
8536
  }
6507
8537
  const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
6508
8538
  if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
6509
- throw invalid(path, "thenable output is not supported");
8539
+ throw invalid2(path, "thenable output is not supported");
6510
8540
  }
6511
8541
  stack.add(value);
6512
8542
  try {
@@ -6514,15 +8544,15 @@ function normalize(value, path, stack) {
6514
8544
  const normalized2 = [];
6515
8545
  const length = descriptors.length?.value;
6516
8546
  if (!Number.isSafeInteger(length) || length < 0) {
6517
- throw invalid(propertyPath(path, "length"), "array length is invalid");
8547
+ throw invalid2(propertyPath(path, "length"), "array length is invalid");
6518
8548
  }
6519
8549
  for (let index = 0; index < length; index++) {
6520
8550
  const descriptor = descriptors[String(index)];
6521
8551
  if (!descriptor || !("value" in descriptor)) {
6522
- throw invalid(`${path}[${index}]`, "arrays must be dense data properties");
8552
+ throw invalid2(`${path}[${index}]`, "arrays must be dense data properties");
6523
8553
  }
6524
8554
  if (descriptor.value === void 0) {
6525
- throw invalid(`${path}[${index}]`, "array values cannot be undefined");
8555
+ throw invalid2(`${path}[${index}]`, "array values cannot be undefined");
6526
8556
  }
6527
8557
  normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
6528
8558
  }
@@ -6531,21 +8561,21 @@ function normalize(value, path, stack) {
6531
8561
  if (!descriptor?.enumerable) continue;
6532
8562
  const index = Number(key);
6533
8563
  if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
6534
- throw invalid(propertyPath(path, key), "non-index array properties are not supported");
8564
+ throw invalid2(propertyPath(path, key), "non-index array properties are not supported");
6535
8565
  }
6536
8566
  }
6537
8567
  Object.setPrototypeOf(normalized2, null);
6538
8568
  return normalized2;
6539
8569
  }
6540
8570
  if (prototype !== Object.prototype && prototype !== null) {
6541
- throw invalid(path, "only plain objects and null-prototype records are supported");
8571
+ throw invalid2(path, "only plain objects and null-prototype records are supported");
6542
8572
  }
6543
8573
  const normalized = /* @__PURE__ */ Object.create(null);
6544
8574
  for (const key of Object.keys(descriptors)) {
6545
8575
  const descriptor = descriptors[key];
6546
8576
  if (!descriptor?.enumerable) continue;
6547
8577
  if (!("value" in descriptor)) {
6548
- throw invalid(propertyPath(path, key), "accessor properties are not supported");
8578
+ throw invalid2(propertyPath(path, key), "accessor properties are not supported");
6549
8579
  }
6550
8580
  if (descriptor.value === void 0) continue;
6551
8581
  normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
@@ -7053,44 +9083,44 @@ function resolveBranchDrainTimeoutMs(value) {
7053
9083
  return resolved;
7054
9084
  }
7055
9085
  function parseHumanDecision(value) {
7056
- const invalid2 = () => {
9086
+ const invalid3 = () => {
7057
9087
  throw new AxlError(
7058
9088
  "INVALID_HUMAN_DECISION",
7059
9089
  "Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
7060
9090
  );
7061
9091
  };
7062
- if (typeof value !== "object" || value === null || Array.isArray(value)) invalid2();
9092
+ if (typeof value !== "object" || value === null || Array.isArray(value)) invalid3();
7063
9093
  let prototype;
7064
9094
  try {
7065
9095
  prototype = Object.getPrototypeOf(value);
7066
9096
  } catch {
7067
- return invalid2();
9097
+ return invalid3();
7068
9098
  }
7069
- if (prototype !== Object.prototype && prototype !== null) invalid2();
9099
+ if (prototype !== Object.prototype && prototype !== null) invalid3();
7070
9100
  let ownKeys;
7071
9101
  try {
7072
9102
  ownKeys = Reflect.ownKeys(value);
7073
9103
  } catch {
7074
- return invalid2();
9104
+ return invalid3();
7075
9105
  }
7076
9106
  if (ownKeys.some(
7077
9107
  (key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
7078
9108
  )) {
7079
- invalid2();
9109
+ invalid3();
7080
9110
  }
7081
9111
  const readDataProperty = (key) => {
7082
9112
  let descriptor;
7083
9113
  try {
7084
9114
  descriptor = Object.getOwnPropertyDescriptor(value, key);
7085
9115
  } catch {
7086
- return invalid2();
9116
+ return invalid3();
7087
9117
  }
7088
9118
  if (!descriptor) {
7089
- if (ownKeys.includes(key)) invalid2();
9119
+ if (ownKeys.includes(key)) invalid3();
7090
9120
  return { present: false, value: void 0 };
7091
9121
  }
7092
- if (!ownKeys.includes(key)) return invalid2();
7093
- if (!("value" in descriptor)) return invalid2();
9122
+ if (!ownKeys.includes(key)) return invalid3();
9123
+ if (!("value" in descriptor)) return invalid3();
7094
9124
  return { present: true, value: descriptor.value };
7095
9125
  };
7096
9126
  const approved = readDataProperty("approved");
@@ -7109,7 +9139,7 @@ function parseHumanDecision(value) {
7109
9139
  };
7110
9140
  }
7111
9141
  }
7112
- return invalid2();
9142
+ return invalid3();
7113
9143
  }
7114
9144
  var askStorage = new AsyncLocalStorage();
7115
9145
  var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
@@ -7217,8 +9247,12 @@ function appendRetryMessages(messages, content, feedbackMessage, providerMetadat
7217
9247
  }
7218
9248
  function estimateMessagesTokens(messages) {
7219
9249
  let total = 0;
9250
+ let unmeasured = false;
7220
9251
  for (const msg of messages) {
7221
- total += estimateTokens(msg.content);
9252
+ total += estimateTokens(inputText(msg.content));
9253
+ if (typeof msg.content !== "string") {
9254
+ unmeasured ||= msg.content.some((part) => part.type === "image");
9255
+ }
7222
9256
  if (msg.tool_calls) {
7223
9257
  for (const tc of msg.tool_calls) {
7224
9258
  total += estimateTokens(tc.function.name + tc.function.arguments);
@@ -7226,7 +9260,73 @@ function estimateMessagesTokens(messages) {
7226
9260
  }
7227
9261
  total += 4;
7228
9262
  }
7229
- return total;
9263
+ return { tokens: total, unmeasured };
9264
+ }
9265
+ function cloneMessagesForProvider(messages) {
9266
+ return messages.map((message) => ({
9267
+ ...message,
9268
+ content: cloneModelInput(message.content),
9269
+ ...message.tool_calls ? {
9270
+ tool_calls: message.tool_calls.map((call) => ({
9271
+ ...call,
9272
+ function: { ...call.function }
9273
+ }))
9274
+ } : {}
9275
+ }));
9276
+ }
9277
+ function assertRichMessagesAreUserOnly(messages) {
9278
+ for (const message of messages) {
9279
+ if (typeof message.content !== "string" && message.role !== "user") {
9280
+ throw new InvalidModelInputError(
9281
+ "Non-text model input parts are only supported on user messages"
9282
+ );
9283
+ }
9284
+ }
9285
+ }
9286
+ function normalizeSessionHistory(messages) {
9287
+ return messages.map((message) => {
9288
+ if (typeof message.content === "string") return { ...message };
9289
+ if (message.role !== "user") {
9290
+ throw new InvalidModelInputError(
9291
+ "Non-text model input parts are only supported on user messages"
9292
+ );
9293
+ }
9294
+ return { ...message, content: normalizeModelInput(message.content) };
9295
+ });
9296
+ }
9297
+ function hasRichMessage(messages) {
9298
+ return messages.some((message) => typeof message.content !== "string");
9299
+ }
9300
+ function firstImageSource(input, history) {
9301
+ const find = (value) => typeof value === "string" ? void 0 : value.find((part) => part.type === "image")?.source.type;
9302
+ return find(input) ?? history.map((message) => find(message.content)).find(Boolean);
9303
+ }
9304
+ function appendHandoffInstruction(input, originalText, instruction) {
9305
+ if (typeof input === "string" || instruction.length === 0 || instruction === originalText)
9306
+ return input;
9307
+ return [...input, { type: "text", text: instruction }];
9308
+ }
9309
+ var RICH_MODEL_INPUT_ERROR_MESSAGE = "Provider request failed while processing rich model input";
9310
+ var RICH_MODEL_INPUT_ERROR_NAME = "RichModelInputError";
9311
+ var MAX_RICH_FAILURE_ASSOCIATIONS = 32;
9312
+ var MAX_RICH_FAILURE_CAUSE_DEPTH = 4;
9313
+ function legacyErrorMessage(error) {
9314
+ return error instanceof Error ? error.message : String(error);
9315
+ }
9316
+ function providerErrorEventMetadata(error, richRequest) {
9317
+ if (!(error instanceof ProviderError)) {
9318
+ return error instanceof AxlError && richRequest ? { code: error.code } : {};
9319
+ }
9320
+ return {
9321
+ status: error.status,
9322
+ retryable: error.retryable,
9323
+ ...richRequest ? {
9324
+ code: error.code,
9325
+ provider: error.provider,
9326
+ ...error.requestId ? { requestId: error.requestId } : {},
9327
+ ...error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
9328
+ } : {}
9329
+ };
7230
9330
  }
7231
9331
  var WorkflowContext = class _WorkflowContext {
7232
9332
  input;
@@ -7242,6 +9342,7 @@ var WorkflowContext = class _WorkflowContext {
7242
9342
  }
7243
9343
  config;
7244
9344
  providerRegistry;
9345
+ transcriptionProviderRegistry;
7245
9346
  sessionHistory;
7246
9347
  onTrace;
7247
9348
  pendingDecisions;
@@ -7267,6 +9368,10 @@ var WorkflowContext = class _WorkflowContext {
7267
9368
  */
7268
9369
  autoCheckpointCounters;
7269
9370
  branchDrainState;
9371
+ /** Bounded exact-identity failure association for this execution tree.
9372
+ * Unlike a process-global marker, this cannot affect another context that
9373
+ * happens to reuse the same Error object. */
9374
+ richFailureState;
7270
9375
  /** Root-shared idempotency guards for workflow lifecycle emission.
7271
9376
  * Both `runtime.execute()` and `runtime.stream()` have paths where a
7272
9377
  * post-emit side-effect (checkpoint deletion, state-store persistence)
@@ -7350,7 +9455,8 @@ var WorkflowContext = class _WorkflowContext {
7350
9455
  get events() {
7351
9456
  if (!this._busRef.current) {
7352
9457
  this._busRef.current = new AxlEventBus(this._eventStreamOptions);
7353
- if (this.signal?.aborted) {
9458
+ const signal = this.currentSignal;
9459
+ if (signal?.aborted) {
7354
9460
  this._busRef.current._finish();
7355
9461
  }
7356
9462
  }
@@ -7388,6 +9494,7 @@ var WorkflowContext = class _WorkflowContext {
7388
9494
  this.metadata = init.metadata ?? {};
7389
9495
  this.config = init.config;
7390
9496
  this.providerRegistry = init.providerRegistry;
9497
+ this.transcriptionProviderRegistry = init.transcriptionProviderRegistry;
7391
9498
  this.sessionHistory = init.sessionHistory ?? [];
7392
9499
  this.onTrace = init.onTrace;
7393
9500
  this.pendingDecisions = init.pendingDecisions;
@@ -7421,6 +9528,11 @@ var WorkflowContext = class _WorkflowContext {
7421
9528
  byAgent: /* @__PURE__ */ new Map(),
7422
9529
  root: 0
7423
9530
  };
9531
+ this.richFailureState = init.richFailureState ?? {
9532
+ associations: [],
9533
+ overflowed: false,
9534
+ indeterminate: false
9535
+ };
7424
9536
  const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
7425
9537
  this.branchDrainState = init.branchDrainState ?? {
7426
9538
  pending: /* @__PURE__ */ new Set(),
@@ -7453,6 +9565,7 @@ var WorkflowContext = class _WorkflowContext {
7453
9565
  executionId: this.executionId,
7454
9566
  config: this.config,
7455
9567
  providerRegistry: this.providerRegistry,
9568
+ transcriptionProviderRegistry: this.transcriptionProviderRegistry,
7456
9569
  metadata: { ...this.metadata },
7457
9570
  // Shared infrastructure
7458
9571
  budgetContext: this.budgetContext,
@@ -7475,6 +9588,7 @@ var WorkflowContext = class _WorkflowContext {
7475
9588
  autoCheckpointCounters: this.autoCheckpointCounters,
7476
9589
  branchDrainState: this.branchDrainState,
7477
9590
  workflowLifecycleState: this.workflowLifecycleState,
9591
+ richFailureState: this.richFailureState,
7478
9592
  // Share the parent's bus slot (mutable ref). Late allocation in
7479
9593
  // either parent or child propagates because both read the same
7480
9594
  // slot. Nested-ask events (agent-as-tool pattern) bubble up to
@@ -7486,6 +9600,52 @@ var WorkflowContext = class _WorkflowContext {
7486
9600
  // Isolated: sessionHistory (empty)
7487
9601
  });
7488
9602
  }
9603
+ /** Associate an exact thrown value with rich input in this execution only. */
9604
+ recordRichFailure(error) {
9605
+ const state = this.richFailureState;
9606
+ if (state.associations.some((known) => Object.is(known, error))) return;
9607
+ if (state.associations.length >= MAX_RICH_FAILURE_ASSOCIATIONS) {
9608
+ state.associations.shift();
9609
+ state.overflowed = true;
9610
+ state.indeterminate = true;
9611
+ }
9612
+ state.associations.push(error);
9613
+ }
9614
+ /** @internal Safe observer projection for a failure associated with rich input. */
9615
+ _observerErrorProjection(error) {
9616
+ const state = this.richFailureState;
9617
+ const safe = () => ({
9618
+ message: RICH_MODEL_INPUT_ERROR_MESSAGE,
9619
+ name: RICH_MODEL_INPUT_ERROR_NAME
9620
+ });
9621
+ const indeterminate = () => {
9622
+ if (state.associations.length === 0) return void 0;
9623
+ state.indeterminate = true;
9624
+ return safe();
9625
+ };
9626
+ let current = error;
9627
+ const seen = /* @__PURE__ */ new WeakSet();
9628
+ for (let depth = 0; depth < MAX_RICH_FAILURE_CAUSE_DEPTH; depth++) {
9629
+ if (state.associations.some((known) => Object.is(known, current))) {
9630
+ return safe();
9631
+ }
9632
+ if ((typeof current !== "object" || current === null) && typeof current !== "function") {
9633
+ return state.overflowed || state.indeterminate ? safe() : void 0;
9634
+ }
9635
+ if (seen.has(current)) return indeterminate();
9636
+ seen.add(current);
9637
+ let descriptor;
9638
+ try {
9639
+ descriptor = Object.getOwnPropertyDescriptor(current, "cause");
9640
+ } catch {
9641
+ return indeterminate();
9642
+ }
9643
+ if (!descriptor) return state.overflowed || state.indeterminate ? safe() : void 0;
9644
+ if (!("value" in descriptor)) return indeterminate();
9645
+ current = descriptor.value;
9646
+ }
9647
+ return indeterminate();
9648
+ }
7489
9649
  /**
7490
9650
  * Resolve the current abort signal.
7491
9651
  * Branch-scoped signals (from race/spawn/map/budget) in AsyncLocalStorage
@@ -7687,6 +9847,17 @@ var WorkflowContext = class _WorkflowContext {
7687
9847
  }
7688
9848
  // ── ctx.ask() ─────────────────────────────────────────────────────────
7689
9849
  async ask(agent2, prompt, options) {
9850
+ const normalizedInput = normalizeModelInput(prompt);
9851
+ const normalizedHistory = normalizeSessionHistory(this.sessionHistory);
9852
+ const delegateInputHolder = agent2;
9853
+ if (delegateInputHolder._delegateOriginalInput !== void 0) {
9854
+ delegateInputHolder._delegateOriginalInput = normalizeModelInput(
9855
+ delegateInputHolder._delegateOriginalInput
9856
+ );
9857
+ }
9858
+ const richAsk = typeof normalizedInput !== "string" || hasRichMessage(normalizedHistory) || delegateInputHolder._delegateOriginalInput !== void 0 && typeof delegateInputHolder._delegateOriginalInput !== "string";
9859
+ const promptText = inputText(normalizedInput);
9860
+ const inputDescriptor = describeModelInput(normalizedInput);
7690
9861
  const agentName = agent2._name;
7691
9862
  return this._checkpoint(
7692
9863
  this._autoCheckpointName("ask", agentName),
@@ -7706,7 +9877,11 @@ var WorkflowContext = class _WorkflowContext {
7706
9877
  };
7707
9878
  return askStorage.run(frame, async () => {
7708
9879
  const askStart = Date.now();
7709
- this.emitEvent({ type: "ask_start", prompt });
9880
+ this.emitEvent({
9881
+ type: "ask_start",
9882
+ prompt: promptText,
9883
+ ...inputDescriptor ? { input: inputDescriptor } : {}
9884
+ });
7710
9885
  const costBefore = this.budgetContext?.totalCost ?? 0;
7711
9886
  const unpricedCountBefore = this.budgetContext?.unpricedCount ?? 0;
7712
9887
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
@@ -7714,10 +9889,11 @@ var WorkflowContext = class _WorkflowContext {
7714
9889
  const doCall = async () => {
7715
9890
  const result2 = await this.executeAgentCall(
7716
9891
  agent2,
7717
- prompt,
9892
+ normalizedInput,
7718
9893
  options,
7719
9894
  void 0,
7720
- usageCapture
9895
+ usageCapture,
9896
+ normalizedHistory
7721
9897
  );
7722
9898
  return result2;
7723
9899
  };
@@ -7734,6 +9910,9 @@ var WorkflowContext = class _WorkflowContext {
7734
9910
  },
7735
9911
  async (span) => {
7736
9912
  const r = await doCall();
9913
+ if (usageCapture.modelUri) {
9914
+ span.setAttribute("axl.agent.model", usageCapture.modelUri);
9915
+ }
7737
9916
  const costAfter2 = this.budgetContext?.totalCost ?? 0;
7738
9917
  span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
7739
9918
  span.setAttribute("axl.agent.duration", Date.now() - askStart);
@@ -7767,9 +9946,10 @@ var WorkflowContext = class _WorkflowContext {
7767
9946
  try {
7768
9947
  this.onAgentCallComplete({
7769
9948
  agent: agent2._name,
7770
- prompt,
9949
+ prompt: promptText,
9950
+ ...inputDescriptor ? { input: inputDescriptor } : {},
7771
9951
  response: typeof result === "string" ? result : JSON.stringify(result),
7772
- model: agent2.resolveModel(resolveCtx),
9952
+ model: usageCapture.modelUri ?? agent2.resolveModel(resolveCtx),
7773
9953
  cost: costAfter - costBefore,
7774
9954
  unpriced: frame.askUnpriced || unpricedCountAfter > unpricedCountBefore,
7775
9955
  duration: Date.now() - askStart,
@@ -7793,9 +9973,10 @@ var WorkflowContext = class _WorkflowContext {
7793
9973
  } catch (err) {
7794
9974
  askFailed = true;
7795
9975
  askFailure = err;
9976
+ if (richAsk) this.recordRichFailure(err);
7796
9977
  outcome = {
7797
9978
  ok: false,
7798
- error: err instanceof Error ? err.message : String(err)
9979
+ error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
7799
9980
  };
7800
9981
  }
7801
9982
  let terminalFailure;
@@ -7823,7 +10004,7 @@ var WorkflowContext = class _WorkflowContext {
7823
10004
  { agent: agentName }
7824
10005
  );
7825
10006
  }
7826
- async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
10007
+ async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
7827
10008
  if (this.budgetContext?.exceeded) {
7828
10009
  const { limit, totalCost: spent, policy } = this.budgetContext;
7829
10010
  if (policy === "warn") {
@@ -7840,7 +10021,82 @@ var WorkflowContext = class _WorkflowContext {
7840
10021
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
7841
10022
  const modelUri = agent2.resolveModel(resolveCtx);
7842
10023
  const systemPrompt = agent2.resolveSystem(resolveCtx);
7843
- const { provider, model } = this.providerRegistry.resolve(modelUri, this.config);
10024
+ const { provider, model: resolvedModel } = this.providerRegistry.resolve(modelUri, this.config);
10025
+ let model = resolvedModel;
10026
+ const prompt = inputText(input);
10027
+ const providerOptions = options?.providerOptions ?? agent2._config.providerOptions;
10028
+ const richRequest = typeof input !== "string" || hasRichMessage(sessionHistory);
10029
+ if (richRequest) {
10030
+ if (providerOptions && ("messages" in providerOptions || "input" in providerOptions)) {
10031
+ throw new UnsupportedModelInputError({
10032
+ provider: provider.name ?? modelUri.split(":", 1)[0],
10033
+ model,
10034
+ modality: "image",
10035
+ feature: "raw input-container providerOptions"
10036
+ });
10037
+ }
10038
+ const source = firstImageSource(input, sessionHistory);
10039
+ if (!provider.validateInput) {
10040
+ throw new UnsupportedModelInputError({
10041
+ provider: provider.name ?? modelUri.split(":", 1)[0],
10042
+ model,
10043
+ modality: "image",
10044
+ ...source ? { source } : {}
10045
+ });
10046
+ }
10047
+ const validation = provider.validateInput({
10048
+ model,
10049
+ input: cloneModelInput(input),
10050
+ history: cloneMessagesForProvider(sessionHistory),
10051
+ stream: this._streamingEnabled,
10052
+ hasTools: Boolean(
10053
+ agent2._config.tools?.length || agent2._config.mcp?.length || agent2._config.mcpTools?.length || agent2._config.handoffs
10054
+ ),
10055
+ responseMode: options?.schema ? "structured" : "text",
10056
+ providerOptions
10057
+ });
10058
+ if (!validation || typeof validation.effectiveModel !== "string" || validation.effectiveModel.length === 0) {
10059
+ throw new UnsupportedModelInputError({
10060
+ provider: provider.name ?? modelUri.split(":", 1)[0],
10061
+ model,
10062
+ modality: "image",
10063
+ ...source ? { source } : {}
10064
+ });
10065
+ }
10066
+ model = validation.effectiveModel;
10067
+ }
10068
+ const effectiveModelUri = !richRequest ? modelUri : `${modelUri.slice(0, modelUri.indexOf(":") + 1)}${model}`;
10069
+ if (usageCapture) usageCapture.modelUri ??= effectiveModelUri;
10070
+ if (richRequest) {
10071
+ const descriptors = [
10072
+ describeModelInput(input),
10073
+ ...sessionHistory.map((message) => describeModelInput(message.content))
10074
+ ].filter(
10075
+ (descriptor) => descriptor !== void 0
10076
+ );
10077
+ const parts = descriptors.flatMap((descriptor) => descriptor.parts);
10078
+ this.spanManager?.addEventToActiveSpan("axl.model_input", {
10079
+ "axl.input.parts": parts.length,
10080
+ "axl.input.images": parts.filter((part) => part.type === "image").length,
10081
+ "axl.input.source.bytes": parts.filter(
10082
+ (part) => part.type === "image" && part.source === "bytes"
10083
+ ).length,
10084
+ "axl.input.source.base64": parts.filter(
10085
+ (part) => part.type === "image" && part.source === "base64"
10086
+ ).length,
10087
+ "axl.input.source.url": parts.filter(
10088
+ (part) => part.type === "image" && part.source === "url"
10089
+ ).length,
10090
+ "axl.input.source.provider_file": parts.filter(
10091
+ (part) => part.type === "image" && part.source === "provider-file"
10092
+ ).length,
10093
+ "axl.input.inline_bytes": parts.reduce(
10094
+ (sum, part) => sum + (part.type === "image" ? part.bytes ?? 0 : 0),
10095
+ 0
10096
+ )
10097
+ });
10098
+ }
10099
+ assertRichMessagesAreUserOnly(sessionHistory);
7844
10100
  let resolvedHandoffs;
7845
10101
  if (typeof agent2._config.handoffs === "function") {
7846
10102
  try {
@@ -7861,50 +10117,60 @@ var WorkflowContext = class _WorkflowContext {
7861
10117
  messages.push({ role: "system", content: systemPrompt });
7862
10118
  }
7863
10119
  const maxContext = agent2._config.maxContext;
7864
- if (maxContext && this.sessionHistory.length > 0) {
10120
+ if (maxContext && sessionHistory.length > 0) {
7865
10121
  const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
7866
10122
  const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
7867
10123
  const toolTokens = toolDefs.length > 0 ? estimateTokens(JSON.stringify(toolDefs)) : 0;
7868
10124
  const overhead = systemTokens + toolTokens + reserveTokens;
7869
10125
  const availableForHistory = maxContext - overhead;
7870
- const historyTokens = estimateMessagesTokens(this.sessionHistory);
7871
- if (historyTokens > availableForHistory) {
10126
+ const historyEstimate = estimateMessagesTokens(sessionHistory);
10127
+ if (historyEstimate.unmeasured) {
10128
+ this.emitEvent({
10129
+ type: "log",
10130
+ data: {
10131
+ warning: "Context media contribution is unmeasured; provider will enforce its context limit"
10132
+ }
10133
+ });
10134
+ }
10135
+ if (historyEstimate.tokens > availableForHistory) {
7872
10136
  const summarizedMessages = await this.summarizeHistory(
7873
10137
  provider,
7874
10138
  model,
7875
- this.sessionHistory,
10139
+ sessionHistory,
7876
10140
  availableForHistory
7877
10141
  );
7878
10142
  for (const msg of summarizedMessages) {
7879
10143
  messages.push(msg);
7880
10144
  }
7881
10145
  } else {
7882
- for (const msg of this.sessionHistory) {
10146
+ for (const msg of sessionHistory) {
7883
10147
  messages.push(msg);
7884
10148
  }
7885
10149
  }
7886
10150
  } else {
7887
- for (const msg of this.sessionHistory) {
10151
+ for (const msg of sessionHistory) {
7888
10152
  messages.push(msg);
7889
10153
  }
7890
10154
  }
7891
10155
  const schemaPromptMode = options?.schemaPrompt ?? agent2._config.schemaPrompt ?? "json-schema";
7892
- let userContent = prompt;
10156
+ let userContent = input;
7893
10157
  let appendedSchemaText;
7894
10158
  if (options?.schema) {
7895
10159
  const schema = options.schema;
7896
10160
  if (schemaPromptMode === "none") {
7897
10161
  } else if (schemaPromptMode === "json-schema") {
7898
10162
  appendedSchemaText = renderSchemaForPrompt(schema);
7899
- userContent += `
10163
+ const guidance = `
7900
10164
 
7901
10165
  Respond with valid JSON matching this schema:
7902
10166
  ${appendedSchemaText}`;
10167
+ userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
7903
10168
  } else {
7904
10169
  appendedSchemaText = typeof schemaPromptMode.render === "function" ? schemaPromptMode.render(schema) : schemaPromptMode.render;
7905
- userContent += `
10170
+ const guidance = `
7906
10171
 
7907
10172
  ${appendedSchemaText}`;
10173
+ userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
7908
10174
  }
7909
10175
  }
7910
10176
  messages.push({ role: "user", content: userContent });
@@ -7932,7 +10198,10 @@ ${appendedSchemaText}`;
7932
10198
  }
7933
10199
  const guardrails = agent2._config.guardrails;
7934
10200
  if (guardrails?.input) {
7935
- const inputResult = await guardrails.input(prompt, { metadata: this.metadata });
10201
+ const inputResult = await guardrails.input(prompt, {
10202
+ metadata: this.metadata,
10203
+ input: cloneModelInput(input)
10204
+ });
7936
10205
  this.emitEvent({
7937
10206
  type: "guardrail",
7938
10207
  agent: agent2._name,
@@ -8023,7 +10292,7 @@ ${appendedSchemaText}`;
8023
10292
  includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
8024
10293
  toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
8025
10294
  stop: options?.stop ?? agent2._config.stop,
8026
- providerOptions: options?.providerOptions ?? agent2._config.providerOptions,
10295
+ providerOptions,
8027
10296
  signal: this.currentSignal
8028
10297
  };
8029
10298
  if (options?.schema && toolDefs.length === 0) {
@@ -8049,15 +10318,26 @@ ${appendedSchemaText}`;
8049
10318
  ...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
8050
10319
  };
8051
10320
  let messagesSnapshot;
10321
+ let messageInputs;
8052
10322
  if (verboseTrace) {
8053
10323
  try {
8054
- messagesSnapshot = structuredClone(currentMessages);
10324
+ messageInputs = currentMessages.flatMap((message, index) => {
10325
+ const descriptor = describeModelInput(message.content);
10326
+ return descriptor ? [{ index, input: descriptor }] : [];
10327
+ });
10328
+ messagesSnapshot = currentMessages.map((message) => ({
10329
+ ...message,
10330
+ content: sanitizeModelInputForTrace(message.content)
10331
+ }));
8055
10332
  } catch (err) {
8056
10333
  console.warn(
8057
10334
  "[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
8058
10335
  err instanceof Error ? err.message : String(err)
8059
10336
  );
8060
- messagesSnapshot = [...currentMessages];
10337
+ messagesSnapshot = currentMessages.map((message) => ({
10338
+ ...message,
10339
+ content: sanitizeModelInputForTrace(message.content)
10340
+ }));
8061
10341
  }
8062
10342
  }
8063
10343
  const retryReason = pendingRetryReason;
@@ -8065,16 +10345,18 @@ ${appendedSchemaText}`;
8065
10345
  this.emitEvent({
8066
10346
  type: "agent_call_start",
8067
10347
  agent: agent2._name,
8068
- model: modelUri,
10348
+ model: effectiveModelUri,
8069
10349
  turn: turns,
8070
10350
  data: {
8071
10351
  prompt,
10352
+ ...typeof input === "string" ? {} : { input: describeModelInput(input) },
8072
10353
  ...systemPrompt ? { system: systemPrompt } : {},
8073
10354
  params: callParams,
8074
10355
  turn: turns,
8075
10356
  ...retryReason ? { retryReason } : {},
8076
10357
  ...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
8077
- ...messagesSnapshot ? { messages: messagesSnapshot } : {}
10358
+ ...messagesSnapshot ? { messages: messagesSnapshot } : {},
10359
+ ...messageInputs && messageInputs.length > 0 ? { messageInputs } : {}
8078
10360
  }
8079
10361
  });
8080
10362
  let response;
@@ -8098,7 +10380,10 @@ ${appendedSchemaText}`;
8098
10380
  });
8099
10381
  }
8100
10382
  }) : void 0;
8101
- for await (const chunk of provider.stream(currentMessages, chatOptions)) {
10383
+ for await (const chunk of provider.stream(
10384
+ cloneMessagesForProvider(currentMessages),
10385
+ chatOptions
10386
+ )) {
8102
10387
  if (chunk.type === "text_delta") {
8103
10388
  content2 += chunk.content;
8104
10389
  this.emitEvent({ type: "token", data: chunk.content });
@@ -8164,24 +10449,25 @@ ${appendedSchemaText}`;
8164
10449
  response.thinking_content = thinkingContent;
8165
10450
  }
8166
10451
  } else {
8167
- response = await provider.chat(currentMessages, chatOptions);
10452
+ response = await provider.chat(cloneMessagesForProvider(currentMessages), chatOptions);
8168
10453
  }
8169
10454
  } catch (err) {
10455
+ if (richRequest) this.recordRichFailure(err);
8170
10456
  this.emitEvent({
8171
10457
  type: "agent_call_end",
8172
10458
  agent: agent2._name,
8173
- model: modelUri,
10459
+ model: effectiveModelUri,
8174
10460
  promptVersion: agent2._config.version,
8175
10461
  duration: Date.now() - turnStart,
8176
10462
  data: {
8177
10463
  response: "",
8178
10464
  turn: turns,
8179
10465
  ...retryReason ? { retryReason } : {},
8180
- error: err instanceof Error ? err.message : String(err),
10466
+ error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err),
8181
10467
  // Enrich with typed-error metadata when available. `body` is
8182
10468
  // deliberately NOT emitted — it's redaction-eligible (see
8183
10469
  // docs/security.md). `data.error` already carries the message.
8184
- ...err instanceof ProviderError ? { status: err.status, retryable: err.retryable } : {}
10470
+ ...providerErrorEventMetadata(err, richRequest)
8185
10471
  }
8186
10472
  });
8187
10473
  throw err;
@@ -8195,7 +10481,7 @@ ${appendedSchemaText}`;
8195
10481
  this.emitEvent({
8196
10482
  type: "agent_call_end",
8197
10483
  agent: agent2._name,
8198
- model: modelUri,
10484
+ model: effectiveModelUri,
8199
10485
  promptVersion: agent2._config.version,
8200
10486
  cost: response.cost,
8201
10487
  tokens: response.usage ? {
@@ -8229,12 +10515,12 @@ ${appendedSchemaText}`;
8229
10515
  if (descriptor) {
8230
10516
  const mode = descriptor.mode ?? "oneway";
8231
10517
  let handoffPrompt = prompt;
8232
- if (mode === "roundtrip") {
8233
- try {
8234
- const args = JSON.parse(toolCall.function.arguments);
8235
- if (args.message) handoffPrompt = args.message;
8236
- } catch {
10518
+ try {
10519
+ const args = JSON.parse(toolCall.function.arguments);
10520
+ if (typeof args.message === "string" && args.message.length > 0) {
10521
+ handoffPrompt = args.message;
8237
10522
  }
10523
+ } catch {
8238
10524
  }
8239
10525
  const handoffStart = Date.now();
8240
10526
  const sourceFrame = askStorage.getStore();
@@ -8261,15 +10547,27 @@ ${appendedSchemaText}`;
8261
10547
  validateRetries: options.validateRetries
8262
10548
  } : void 0;
8263
10549
  const handoffFn = () => askStorage.run(targetFrame, async () => {
8264
- this.emitEvent({ type: "ask_start", prompt: handoffPrompt });
10550
+ const delegateOriginal = agent2._delegateOriginalInput;
10551
+ const handoffInput = delegateOriginal ?? (typeof input === "string" ? handoffPrompt : input);
10552
+ const enrichedHandoffInput = appendHandoffInstruction(
10553
+ handoffInput,
10554
+ inputText(input),
10555
+ handoffPrompt
10556
+ );
10557
+ this.emitEvent({
10558
+ type: "ask_start",
10559
+ prompt: inputText(enrichedHandoffInput),
10560
+ ...typeof enrichedHandoffInput === "string" ? {} : { input: describeModelInput(enrichedHandoffInput) }
10561
+ });
8265
10562
  const targetAskStart = Date.now();
8266
10563
  try {
8267
10564
  const result = await this.executeAgentCall(
8268
10565
  descriptor.agent,
8269
- handoffPrompt,
10566
+ enrichedHandoffInput,
8270
10567
  handoffOptions,
8271
10568
  currentMessages,
8272
- usageCapture
10569
+ usageCapture,
10570
+ sessionHistory
8273
10571
  );
8274
10572
  this.emitEvent({
8275
10573
  type: "ask_end",
@@ -8280,11 +10578,13 @@ ${appendedSchemaText}`;
8280
10578
  });
8281
10579
  return result;
8282
10580
  } catch (err) {
10581
+ const richHandoff = typeof enrichedHandoffInput !== "string" || hasRichMessage(sessionHistory);
10582
+ if (richHandoff) this.recordRichFailure(err);
8283
10583
  this.emitEvent({
8284
10584
  type: "ask_end",
8285
10585
  outcome: {
8286
10586
  ok: false,
8287
- error: err instanceof Error ? err.message : String(err)
10587
+ error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
8288
10588
  },
8289
10589
  cost: targetFrame.askCost.value,
8290
10590
  ...targetFrame.askUnpriced ? { unpriced: true } : {},
@@ -8793,12 +11093,12 @@ ${appendedSchemaText}`;
8793
11093
  content: `Summary of earlier conversation:
8794
11094
  ${this.summaryCache}`
8795
11095
  };
8796
- const summaryTokens = estimateTokens(summaryMsg2.content) + 4;
11096
+ const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
8797
11097
  const remaining = availableTokens - summaryTokens;
8798
11098
  let recentTokens2 = 0;
8799
11099
  let splitIdx2 = history.length;
8800
11100
  for (let i = history.length - 1; i >= 0; i--) {
8801
- const msgTokens = estimateTokens(history[i].content) + 4;
11101
+ const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
8802
11102
  if (recentTokens2 + msgTokens > remaining) break;
8803
11103
  recentTokens2 += msgTokens;
8804
11104
  splitIdx2 = i;
@@ -8811,7 +11111,7 @@ ${this.summaryCache}`
8811
11111
  let splitIdx = history.length;
8812
11112
  const targetRecent = Math.floor(availableTokens * 0.6);
8813
11113
  for (let i = history.length - 1; i >= 0; i--) {
8814
- const msgTokens = estimateTokens(history[i].content) + 4;
11114
+ const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
8815
11115
  if (recentTokens + msgTokens > targetRecent) break;
8816
11116
  recentTokens += msgTokens;
8817
11117
  splitIdx = i;
@@ -8829,7 +11129,7 @@ ${this.summaryCache}`
8829
11129
  summaryProvider = provider;
8830
11130
  summaryModel = model;
8831
11131
  }
8832
- const oldContent = oldMessages.map((m) => `${m.role}: ${m.content}`).join("\n");
11132
+ const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
8833
11133
  const summaryResponse = await summaryProvider.chat(
8834
11134
  [
8835
11135
  {
@@ -10063,7 +12363,9 @@ ${summaryResponse.content}`
10063
12363
  reason: "routed"
10064
12364
  }
10065
12365
  });
10066
- return this.ask(routerAgent, prompt, {
12366
+ const routerInput = options?.routerInput === "text" ? inputText(prompt) : prompt;
12367
+ routerAgent._delegateOriginalInput = prompt;
12368
+ return this.ask(routerAgent, routerInput, {
10067
12369
  schema: options?.schema,
10068
12370
  schemaPrompt: options?.schemaPrompt,
10069
12371
  nativeStructuredOutput: options?.nativeStructuredOutput,
@@ -10073,6 +12375,278 @@ ${summaryResponse.content}`
10073
12375
  validateRetries: options?.validateRetries
10074
12376
  });
10075
12377
  }
12378
+ /** Transcribe recorded audio through the dedicated transcription registry.
12379
+ * This operation never enters the chat history or chat-provider path. */
12380
+ async transcribe(request) {
12381
+ const startedAt = Date.now();
12382
+ const transcriptionId = randomUUID();
12383
+ const signal = this.currentSignal;
12384
+ const rawAudio = request && typeof request === "object" && "audio" in request ? request.audio : void 0;
12385
+ const source = rawAudio?.type;
12386
+ const requestedUri = typeof request?.model === "string" ? request.model : void 0;
12387
+ const colon = requestedUri?.indexOf(":") ?? -1;
12388
+ const requestedProvider = colon > 0 ? requestedUri.slice(0, colon) : void 0;
12389
+ const requestedModel = colon > 0 && colon < requestedUri.length - 1 ? requestedUri.slice(colon + 1) : void 0;
12390
+ const startAudio = {
12391
+ source: typeof source === "string" ? source : "unknown",
12392
+ ...rawAudio?.data instanceof Uint8Array ? { bytes: rawAudio.data.byteLength } : {},
12393
+ ...typeof rawAudio?.mediaType === "string" ? { mediaType: rawAudio.mediaType } : {}
12394
+ };
12395
+ this.emitEvent({
12396
+ type: "transcription_start",
12397
+ transcriptionId,
12398
+ model: requestedModel,
12399
+ data: {
12400
+ ...requestedProvider ? { provider: requestedProvider } : {},
12401
+ ...requestedModel ? { model: requestedModel } : {},
12402
+ audio: startAudio
12403
+ }
12404
+ });
12405
+ let terminalEmitted = false;
12406
+ const emitTerminal = (partial) => {
12407
+ if (terminalEmitted) return;
12408
+ terminalEmitted = true;
12409
+ this.emitEvent({
12410
+ type: "transcription_end",
12411
+ transcriptionId,
12412
+ model: partial.model,
12413
+ duration: Date.now() - startedAt,
12414
+ ...partial.cost !== void 0 ? { cost: partial.cost } : {},
12415
+ ...partial.tokens ? { tokens: partial.tokens } : {},
12416
+ data: partial.data
12417
+ });
12418
+ };
12419
+ const abortError3 = () => new DOMException("The transcription operation was aborted", "AbortError");
12420
+ const cleanupStatus = (value) => value === "not_required" || value === "deleted" || value === "failed" || value === "timed_out" ? value : void 0;
12421
+ const safeProviderFailure = (error, provider, model) => {
12422
+ const details = error && typeof error === "object" ? error : void 0;
12423
+ const accounting = normalizeTranscriptionAccounting(details?.usage, details?.pricingStatus);
12424
+ const cleanup = cleanupStatus(details?.cleanupStatus);
12425
+ const providerError = error instanceof ProviderError ? error : void 0;
12426
+ const diagnostics = providerError ? {
12427
+ status: providerError.status,
12428
+ retryable: providerError.retryable,
12429
+ ...providerError.retryAfterMs !== void 0 ? { retryAfterMs: providerError.retryAfterMs } : {},
12430
+ ...providerError.requestId ? { requestId: providerError.requestId } : {}
12431
+ } : void 0;
12432
+ return {
12433
+ accounting,
12434
+ cleanup,
12435
+ diagnostics,
12436
+ error: new TranscriptionOperationError({
12437
+ provider,
12438
+ model,
12439
+ ...accounting.usage ? { usage: accounting.usage } : {},
12440
+ ...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
12441
+ ...cleanup ? { cleanupStatus: cleanup } : {},
12442
+ ...diagnostics ?? {},
12443
+ cause: error
12444
+ })
12445
+ };
12446
+ };
12447
+ const terminalData = (status, provider, model, audio, accounting, extras = {}) => ({
12448
+ status,
12449
+ ...provider ? { provider } : {},
12450
+ ...model ? { model } : {},
12451
+ ...audio ? { audio } : {},
12452
+ ...accounting.usage ? { usage: accounting.usage } : {},
12453
+ ...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
12454
+ ...extras
12455
+ });
12456
+ try {
12457
+ const normalized = normalizeTranscriptionRequest(request);
12458
+ const descriptor = {
12459
+ source: normalized.audio.type,
12460
+ ...normalized.audio.type === "bytes" ? { bytes: normalized.audio.data.byteLength } : {},
12461
+ ...normalized.audio.mediaType !== void 0 ? { mediaType: normalized.audio.mediaType } : {}
12462
+ };
12463
+ if (this.currentSignal?.aborted || signal?.aborted) {
12464
+ emitTerminal({
12465
+ model: requestedModel,
12466
+ data: terminalData("aborted", requestedProvider, requestedModel, descriptor, {
12467
+ hasWork: false
12468
+ })
12469
+ });
12470
+ throw abortError3();
12471
+ }
12472
+ if (this.budgetContext?.exceeded) {
12473
+ const { limit, totalCost: spent, policy } = this.budgetContext;
12474
+ if (policy === "warn") {
12475
+ this.emitEvent({
12476
+ type: "log",
12477
+ data: { warning: "Budget exceeded", limit, spent, policy }
12478
+ });
12479
+ } else {
12480
+ throw new BudgetExceededError(limit, spent, policy);
12481
+ }
12482
+ }
12483
+ if (!this.transcriptionProviderRegistry) {
12484
+ throw new InvalidTranscriptionInputError(
12485
+ "No transcription provider registry is configured"
12486
+ );
12487
+ }
12488
+ const resolved = this.transcriptionProviderRegistry.resolve(normalized.model, this.config);
12489
+ if (normalized.audio.type === "provider-file" && normalized.audio.provider !== resolved.providerName) {
12490
+ throw new InvalidTranscriptionInputError(
12491
+ "Transcription provider-file source does not match the requested provider"
12492
+ );
12493
+ }
12494
+ const capabilities = resolved.provider.capabilities(resolved.model);
12495
+ if (!capabilities || !capabilities.sources.includes(normalized.audio.type)) {
12496
+ throw new InvalidTranscriptionInputError(
12497
+ "The requested transcription model does not support this audio source"
12498
+ );
12499
+ }
12500
+ if (normalized.timestamps && !capabilities.timestamps?.includes(normalized.timestamps)) {
12501
+ throw new InvalidTranscriptionInputError(
12502
+ "The requested transcription model does not support requested timestamps"
12503
+ );
12504
+ }
12505
+ if (normalized.diarization && !capabilities.diarization) {
12506
+ throw new InvalidTranscriptionInputError(
12507
+ "The requested transcription model does not support diarization"
12508
+ );
12509
+ }
12510
+ const providerRequest = {
12511
+ ...normalized,
12512
+ model: resolved.model,
12513
+ ...signal ? { signal } : {}
12514
+ };
12515
+ let result;
12516
+ try {
12517
+ result = await resolved.provider.transcribe(providerRequest);
12518
+ } catch (error) {
12519
+ const failure = safeProviderFailure(error, resolved.providerName, resolved.model);
12520
+ if (this.currentSignal?.aborted || signal?.aborted) {
12521
+ if (failure.accounting.cost !== void 0)
12522
+ this._accumulateBudgetCost(failure.accounting.cost);
12523
+ emitTerminal({
12524
+ model: resolved.model,
12525
+ ...failure.accounting.cost !== void 0 ? { cost: failure.accounting.cost } : {},
12526
+ ...failure.accounting.tokens ? { tokens: failure.accounting.tokens } : {},
12527
+ data: terminalData(
12528
+ "aborted",
12529
+ resolved.providerName,
12530
+ resolved.model,
12531
+ descriptor,
12532
+ failure.accounting,
12533
+ ...failure.cleanup ? [{ cleanupStatus: failure.cleanup }] : []
12534
+ )
12535
+ });
12536
+ throw abortError3();
12537
+ }
12538
+ if (failure.accounting.cost !== void 0)
12539
+ this._accumulateBudgetCost(failure.accounting.cost);
12540
+ emitTerminal({
12541
+ model: resolved.model,
12542
+ ...failure.accounting.cost !== void 0 ? { cost: failure.accounting.cost } : {},
12543
+ ...failure.accounting.tokens ? { tokens: failure.accounting.tokens } : {},
12544
+ data: terminalData(
12545
+ "failed",
12546
+ resolved.providerName,
12547
+ resolved.model,
12548
+ descriptor,
12549
+ failure.accounting,
12550
+ {
12551
+ error: "Transcription operation failed",
12552
+ errorCode: failure.error.code,
12553
+ ...failure.diagnostics ? { providerError: failure.diagnostics } : {},
12554
+ ...failure.cleanup ? { cleanupStatus: failure.cleanup } : {}
12555
+ }
12556
+ )
12557
+ });
12558
+ throw failure.error;
12559
+ }
12560
+ const accounting = normalizeTranscriptionAccounting(
12561
+ result.transcript?.usage,
12562
+ result.transcript?.pricingStatus
12563
+ );
12564
+ if (accounting.cost !== void 0) this._accumulateBudgetCost(accounting.cost);
12565
+ if (this.currentSignal?.aborted || signal?.aborted) {
12566
+ emitTerminal({
12567
+ model: resolved.model,
12568
+ ...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
12569
+ ...accounting.tokens ? { tokens: accounting.tokens } : {},
12570
+ data: terminalData(
12571
+ "aborted",
12572
+ resolved.providerName,
12573
+ resolved.model,
12574
+ descriptor,
12575
+ accounting,
12576
+ ...cleanupStatus(result.cleanupStatus) ? [{ cleanupStatus: cleanupStatus(result.cleanupStatus) }] : []
12577
+ )
12578
+ });
12579
+ throw abortError3();
12580
+ }
12581
+ let transcript;
12582
+ try {
12583
+ transcript = cloneTranscript(result.transcript);
12584
+ } catch (error) {
12585
+ const cleanup = cleanupStatus(result.cleanupStatus);
12586
+ const failure = new TranscriptionOperationError({
12587
+ provider: resolved.providerName,
12588
+ model: resolved.model,
12589
+ ...accounting.usage ? { usage: accounting.usage } : {},
12590
+ ...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
12591
+ ...cleanup ? { cleanupStatus: cleanup } : {},
12592
+ cause: error
12593
+ });
12594
+ emitTerminal({
12595
+ model: resolved.model,
12596
+ ...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
12597
+ ...accounting.tokens ? { tokens: accounting.tokens } : {},
12598
+ data: terminalData(
12599
+ "failed",
12600
+ resolved.providerName,
12601
+ resolved.model,
12602
+ descriptor,
12603
+ accounting,
12604
+ {
12605
+ error: "Transcription operation failed",
12606
+ errorCode: "TRANSCRIPTION_PROVIDER_ERROR",
12607
+ ...cleanup ? { cleanupStatus: cleanup } : {}
12608
+ }
12609
+ )
12610
+ });
12611
+ throw failure;
12612
+ }
12613
+ emitTerminal({
12614
+ model: resolved.model,
12615
+ ...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
12616
+ ...accounting.tokens ? { tokens: accounting.tokens } : {},
12617
+ data: terminalData(
12618
+ "completed",
12619
+ resolved.providerName,
12620
+ resolved.model,
12621
+ descriptor,
12622
+ accounting,
12623
+ {
12624
+ text: transcript.text,
12625
+ ...cleanupStatus(result.cleanupStatus) ? { cleanupStatus: cleanupStatus(result.cleanupStatus) } : {}
12626
+ }
12627
+ )
12628
+ });
12629
+ return transcript;
12630
+ } catch (error) {
12631
+ if (!terminalEmitted) {
12632
+ const aborted = signal?.aborted || error instanceof Error && error.name === "AbortError";
12633
+ emitTerminal({
12634
+ model: requestedModel,
12635
+ data: terminalData(
12636
+ aborted ? "aborted" : "failed",
12637
+ requestedProvider,
12638
+ requestedModel,
12639
+ void 0,
12640
+ { hasWork: false },
12641
+ {
12642
+ error: aborted ? "Transcription operation aborted" : "Transcription operation failed"
12643
+ }
12644
+ )
12645
+ });
12646
+ }
12647
+ throw error;
12648
+ }
12649
+ }
10076
12650
  // ── Private ───────────────────────────────────────────────────────────
10077
12651
  /**
10078
12652
  * Internal emitter input — intentionally loose so call sites don't need to
@@ -10088,7 +12662,7 @@ ${summaryResponse.content}`
10088
12662
  const cost = partial.cost;
10089
12663
  if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
10090
12664
  const usable = isUsableCost(cost);
10091
- const unpriced = !usable && hasPositiveTokens(
12665
+ const unpriced = !usable && hasPositiveBillableWork(
10092
12666
  partial
10093
12667
  );
10094
12668
  if (frame) {
@@ -10193,11 +12767,13 @@ function agent(config) {
10193
12767
  _name: defaultName,
10194
12768
  async ask(prompt, options) {
10195
12769
  const registry = new ProviderRegistry();
12770
+ const transcriptionRegistry = new TranscriptionProviderRegistry();
10196
12771
  const ctx = new WorkflowContext({
10197
12772
  input: prompt,
10198
12773
  executionId: randomUUID2(),
10199
12774
  config: {},
10200
- providerRegistry: registry
12775
+ providerRegistry: registry,
12776
+ transcriptionProviderRegistry: transcriptionRegistry
10201
12777
  });
10202
12778
  return ctx.ask(agentInstance, prompt, options);
10203
12779
  },
@@ -10257,7 +12833,7 @@ var MemoryStore = class {
10257
12833
  return data !== void 0 ? structuredClone(data) : null;
10258
12834
  }
10259
12835
  async saveSession(sessionId, history) {
10260
- this.sessions.set(sessionId, structuredClone(history));
12836
+ this.sessions.set(sessionId, structuredClone(normalizePersistedSessionHistory(history)));
10261
12837
  }
10262
12838
  async getSession(sessionId) {
10263
12839
  const history = this.sessions.get(sessionId);
@@ -10681,7 +13257,7 @@ var SQLiteStore = class {
10681
13257
  const stmt = this.db.prepare(
10682
13258
  "INSERT OR REPLACE INTO sessions (session_id, history) VALUES (?, ?)"
10683
13259
  );
10684
- stmt.run(sessionId, JSON.stringify(history));
13260
+ stmt.run(sessionId, JSON.stringify(normalizePersistedSessionHistory(history)));
10685
13261
  }
10686
13262
  async getSession(sessionId) {
10687
13263
  const stmt = this.db.prepare("SELECT history FROM sessions WHERE session_id = ?");
@@ -11249,7 +13825,7 @@ var AxlStream = class extends Readable {
11249
13825
  /** Signal an error. `executionId` is required for the same reason as
11250
13826
  * `_done`: terminal events must carry a real id even when the
11251
13827
  * failure happens before any real trace event fires (review S4). */
11252
- _error(error, executionId) {
13828
+ _error(error, executionId, observerMessage = error.message, observerName = error.name) {
11253
13829
  if (this.events.isFinished) return;
11254
13830
  const errorEvent = {
11255
13831
  schemaVersion: 2,
@@ -11257,7 +13833,7 @@ var AxlStream = class extends Readable {
11257
13833
  executionId,
11258
13834
  step: Number.MAX_SAFE_INTEGER,
11259
13835
  timestamp: Date.now(),
11260
- data: { message: error.message, name: error.name }
13836
+ data: { message: observerMessage, name: observerName }
11261
13837
  };
11262
13838
  this.events._push(errorEvent);
11263
13839
  this.push(errorEvent);
@@ -11984,6 +14560,10 @@ var StreamingFlusher = class {
11984
14560
  function isAbortError4(err) {
11985
14561
  return typeof err === "object" && err !== null && err.name === "AbortError";
11986
14562
  }
14563
+ function executionErrorProjection(ctx, error) {
14564
+ const safe = ctx?._observerErrorProjection(error);
14565
+ return safe ?? { message: error instanceof Error ? error.message : String(error) };
14566
+ }
11987
14567
  function pushEventBounded(execInfo, event, cap) {
11988
14568
  if (STREAMING_EXCLUDED_TYPES.has(event.type)) return;
11989
14569
  if (cap === Infinity || execInfo.events.length < cap) {
@@ -12078,6 +14658,7 @@ var AxlRuntime = class extends EventEmitter2 {
12078
14658
  tools = /* @__PURE__ */ new Map();
12079
14659
  agents = /* @__PURE__ */ new Map();
12080
14660
  providerRegistry;
14661
+ transcriptionProviderRegistry;
12081
14662
  stateStore;
12082
14663
  executions = /* @__PURE__ */ new Map();
12083
14664
  pendingDecisionResolvers = /* @__PURE__ */ new Map();
@@ -12133,6 +14714,7 @@ var AxlRuntime = class extends EventEmitter2 {
12133
14714
  super();
12134
14715
  this.config = resolveConfig(config ?? {});
12135
14716
  this.providerRegistry = new ProviderRegistry();
14717
+ this.transcriptionProviderRegistry = new TranscriptionProviderRegistry();
12136
14718
  this.stateStore = this.createStateStore();
12137
14719
  const requested = this.config.state?.maxEventsPerExecution;
12138
14720
  if (requested === void 0) {
@@ -12281,7 +14863,7 @@ var AxlRuntime = class extends EventEmitter2 {
12281
14863
  execInfo.status = "failed";
12282
14864
  execInfo.completedAt = Date.now();
12283
14865
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
12284
- execInfo.error = terminalError instanceof Error ? terminalError.message : String(terminalError);
14866
+ execInfo.error = executionErrorProjection(ctx, terminalError).message;
12285
14867
  execInfo.observation = observation;
12286
14868
  ctx._emitWorkflowEnd({
12287
14869
  status: "failed",
@@ -12536,6 +15118,7 @@ var AxlRuntime = class extends EventEmitter2 {
12536
15118
  metadata: options?.metadata,
12537
15119
  config: this.config,
12538
15120
  providerRegistry: this.providerRegistry,
15121
+ transcriptionProviderRegistry: this.transcriptionProviderRegistry,
12539
15122
  stateStore: this.stateStore,
12540
15123
  mcpManager: this.mcpManager,
12541
15124
  spanManager: this.spanManager,
@@ -12564,6 +15147,10 @@ var AxlRuntime = class extends EventEmitter2 {
12564
15147
  registerProvider(name, provider) {
12565
15148
  this.providerRegistry.registerInstance(name, provider);
12566
15149
  }
15150
+ /** Register a dedicated transcription adapter. It is never used for chat. */
15151
+ registerTranscriptionProvider(name, provider) {
15152
+ this.transcriptionProviderRegistry.registerInstance(name, provider);
15153
+ }
12567
15154
  /** Resolve a provider:model URI to a Provider instance and model name. */
12568
15155
  resolveProvider(uri) {
12569
15156
  return this.providerRegistry.resolve(uri, this.config);
@@ -12607,6 +15194,7 @@ var AxlRuntime = class extends EventEmitter2 {
12607
15194
  metadata: options?.metadata,
12608
15195
  config: this.config,
12609
15196
  providerRegistry: this.providerRegistry,
15197
+ transcriptionProviderRegistry: this.transcriptionProviderRegistry,
12610
15198
  sessionHistory,
12611
15199
  signal: controller.signal,
12612
15200
  eventStreamOptions: options?.events,
@@ -12694,6 +15282,7 @@ var AxlRuntime = class extends EventEmitter2 {
12694
15282
  const executionId = randomUUID3();
12695
15283
  this.abortControllers.set(executionId, controller);
12696
15284
  let execInfo;
15285
+ let wfCtx;
12697
15286
  const run = async () => {
12698
15287
  const workflow2 = this.workflows.get(name);
12699
15288
  if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
@@ -12717,12 +15306,13 @@ var AxlRuntime = class extends EventEmitter2 {
12717
15306
  metadata: liftPersistedMetadata(options?.metadata)
12718
15307
  };
12719
15308
  this.executions.set(executionId, execInfo);
12720
- const wfCtx = new WorkflowContext({
15309
+ const context = new WorkflowContext({
12721
15310
  input: validated,
12722
15311
  executionId,
12723
15312
  metadata: options?.metadata,
12724
15313
  config: this.config,
12725
15314
  providerRegistry: this.providerRegistry,
15315
+ transcriptionProviderRegistry: this.transcriptionProviderRegistry,
12726
15316
  sessionHistory,
12727
15317
  signal: controller.signal,
12728
15318
  eventStreamOptions: options?.events,
@@ -12787,6 +15377,7 @@ var AxlRuntime = class extends EventEmitter2 {
12787
15377
  unpricedWarned: false
12788
15378
  }
12789
15379
  });
15380
+ wfCtx = context;
12790
15381
  return this.spanManager.withSpanAsync(
12791
15382
  "axl.workflow.execute",
12792
15383
  {
@@ -12796,7 +15387,7 @@ var AxlRuntime = class extends EventEmitter2 {
12796
15387
  },
12797
15388
  (span) => this.runWorkflowBody({
12798
15389
  workflow: workflow2,
12799
- ctx: wfCtx,
15390
+ ctx: context,
12800
15391
  execInfo,
12801
15392
  validated,
12802
15393
  span,
@@ -12809,12 +15400,14 @@ var AxlRuntime = class extends EventEmitter2 {
12809
15400
  execInfo.status = "failed";
12810
15401
  execInfo.completedAt = Date.now();
12811
15402
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
12812
- execInfo.error = err instanceof Error ? err.message : String(err);
15403
+ execInfo.error = executionErrorProjection(wfCtx, err).message;
12813
15404
  this.persistExecution(execInfo);
12814
15405
  }
12815
15406
  this.abortControllers.delete(executionId);
12816
15407
  cleanupAbortForwarder();
12817
- axlStream._error(err instanceof Error ? err : new Error(String(err)), executionId);
15408
+ const originalError = err instanceof Error ? err : new Error(String(err));
15409
+ const projection = executionErrorProjection(wfCtx, err);
15410
+ axlStream._error(originalError, executionId, projection.message, projection.name);
12818
15411
  });
12819
15412
  return axlStream;
12820
15413
  }
@@ -13225,7 +15818,7 @@ var AxlRuntime = class extends EventEmitter2 {
13225
15818
  },
13226
15819
  {
13227
15820
  role: "user",
13228
- content: messages.map((m) => `${m.role}: ${m.content}`).join("\n")
15821
+ content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
13229
15822
  }
13230
15823
  ],
13231
15824
  { model, maxTokens: 1024 }
@@ -13672,7 +16265,7 @@ async function registerConditions(conditions) {
13672
16265
  ].join("\n");
13673
16266
  nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);
13674
16267
  } catch {
13675
- console.warn("Warning: --conditions requires Node.js 20.6+");
16268
+ console.warn("Warning: this Node.js runtime does not support --conditions");
13676
16269
  }
13677
16270
  }
13678
16271
 
@@ -13981,7 +16574,11 @@ var RedisStore = class _RedisStore {
13981
16574
  async saveSession(sessionId, history) {
13982
16575
  const ttl = this.ttlFor("session");
13983
16576
  const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
13984
- await this.client.multi().set(this.sessionKey(sessionId), JSON.stringify(history), setOptions).sAdd(this.sessionIdsKey(), sessionId).exec();
16577
+ await this.client.multi().set(
16578
+ this.sessionKey(sessionId),
16579
+ JSON.stringify(normalizePersistedSessionHistory(history)),
16580
+ setOptions
16581
+ ).sAdd(this.sessionIdsKey(), sessionId).exec();
13985
16582
  }
13986
16583
  async getSession(sessionId) {
13987
16584
  const raw = await this.client.get(this.sessionKey(sessionId));
@@ -14469,10 +17066,15 @@ export {
14469
17066
  EventStreamOverflowError,
14470
17067
  GROQ_PROFILE,
14471
17068
  GeminiProvider,
17069
+ GeminiTranscriptionProvider,
14472
17070
  GuardrailError,
14473
17071
  InMemoryVectorStore,
17072
+ InvalidModelInputError,
17073
+ InvalidTranscriptionInputError,
14474
17074
  LLAMACPP_PROFILE,
14475
17075
  LMSTUDIO_PROFILE,
17076
+ MAX_INLINE_MODEL_INPUT_BYTES,
17077
+ MAX_INLINE_TRANSCRIPTION_BYTES,
14476
17078
  MISTRAL_PROFILE,
14477
17079
  MaxTurnsError,
14478
17080
  McpManager,
@@ -14488,6 +17090,8 @@ export {
14488
17090
  OpenAIEmbedder,
14489
17091
  OpenAIProvider,
14490
17092
  OpenAIResponsesProvider,
17093
+ OpenAITranscriptionProvider,
17094
+ OpenRouterTranscriptionProvider,
14491
17095
  ProviderError,
14492
17096
  ProviderRegistry,
14493
17097
  QuorumNotMet,
@@ -14503,7 +17107,11 @@ export {
14503
17107
  TimeoutError,
14504
17108
  ToolFailure,
14505
17109
  ToolModelOutputError,
17110
+ TranscriptionOperationError,
17111
+ TranscriptionProviderRegistry,
14506
17112
  UnsupportedEventSchemaVersionError,
17113
+ UnsupportedModelInputError,
17114
+ UnsupportedTranscriptionInputError,
14507
17115
  VLLM_PROFILE,
14508
17116
  ValidationError,
14509
17117
  VerifyError,
@@ -14520,8 +17128,10 @@ export {
14520
17128
  findConfig,
14521
17129
  getEventSchemaVersion,
14522
17130
  getExecutionEventSchemaVersion,
17131
+ hasPositiveBillableWork,
14523
17132
  hasPositiveTokens,
14524
17133
  importModule,
17134
+ inputText,
14525
17135
  isCostBearingLeaf,
14526
17136
  isRetryableStatus,
14527
17137
  isRootLevel,