@axlsdk/axl 0.21.1 → 0.22.1

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