@moonshot-ai/kimi-code 0.23.4 → 0.23.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/main.mjs +518 -107
  2. package/dist-web/assets/{CodeBlockNode-DI6gCVXa.js → CodeBlockNode-KR0ZBibg.js} +1 -1
  3. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-BeZI5g5_.js → CodeBlockShell.vue_vue_type_style_index_0_lang-Bl31Ov6p.js} +1 -1
  4. package/dist-web/assets/{DesignSystemView-DQSXggUP.js → DesignSystemView-DU67euCs.js} +1 -1
  5. package/dist-web/assets/{Tooltip-B3YtzPNZ.js → Tooltip-CXVZw3LD.js} +1 -1
  6. package/dist-web/assets/{arc-bQMfCHSI.js → arc-D-rA9VJS.js} +1 -1
  7. package/dist-web/assets/{architectureDiagram-3BPJPVTR-sZkP4A5D.js → architectureDiagram-3BPJPVTR-DCwhZ0-3.js} +1 -1
  8. package/dist-web/assets/{blockDiagram-GPEHLZMM-Cnf-tp-z.js → blockDiagram-GPEHLZMM-7zi2-mzu.js} +1 -1
  9. package/dist-web/assets/{c4Diagram-AAUBKEIU-D1Ej05NX.js → c4Diagram-AAUBKEIU-BUhBYlbI.js} +1 -1
  10. package/dist-web/assets/channel-B7bRl8z3.js +1 -0
  11. package/dist-web/assets/{chunk-2J33WTMH-BhnDBJIi.js → chunk-2J33WTMH-DXfD8jVe.js} +1 -1
  12. package/dist-web/assets/{chunk-4BX2VUAB-CiOLp8_3.js → chunk-4BX2VUAB-RnAXB6Jd.js} +1 -1
  13. package/dist-web/assets/{chunk-55IACEB6-aUOZP4x-.js → chunk-55IACEB6-CzyvmHuE.js} +1 -1
  14. package/dist-web/assets/{chunk-727SXJPM-CHY14gq8.js → chunk-727SXJPM-BwBvbidm.js} +1 -1
  15. package/dist-web/assets/{chunk-AQP2D5EJ-BKQr3gru.js → chunk-AQP2D5EJ-DHeLjRn1.js} +1 -1
  16. package/dist-web/assets/{chunk-FMBD7UC4-BNnO5ZRK.js → chunk-FMBD7UC4-Bm646OfX.js} +1 -1
  17. package/dist-web/assets/{chunk-ND2GUHAM-CzE2E83w.js → chunk-ND2GUHAM-D3VSc8VX.js} +1 -1
  18. package/dist-web/assets/{chunk-QZHKN3VN-BVjSv3p6.js → chunk-QZHKN3VN-BvZn7Sbo.js} +1 -1
  19. package/dist-web/assets/classDiagram-4FO5ZUOK-CxRebqWF.js +1 -0
  20. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-CxRebqWF.js +1 -0
  21. package/dist-web/assets/{cose-bilkent-S5V4N54A-DaSWvU-5.js → cose-bilkent-S5V4N54A-DdsbNmIL.js} +1 -1
  22. package/dist-web/assets/{dagre-BM42HDAG-VuVwg9z6.js → dagre-BM42HDAG--3VOObN-.js} +1 -1
  23. package/dist-web/assets/{diagram-2AECGRRQ-BI5_WXHg.js → diagram-2AECGRRQ-DeWvFgbz.js} +1 -1
  24. package/dist-web/assets/{diagram-5GNKFQAL-3M89yso9.js → diagram-5GNKFQAL-DthtQYgS.js} +1 -1
  25. package/dist-web/assets/{diagram-KO2AKTUF-C9Qx0fJd.js → diagram-KO2AKTUF-Bt4BtRLc.js} +1 -1
  26. package/dist-web/assets/{diagram-LMA3HP47-yJ-bCZyw.js → diagram-LMA3HP47-7N499o4j.js} +1 -1
  27. package/dist-web/assets/{diagram-OG6HWLK6-CZnqkQin.js → diagram-OG6HWLK6-Br3CxOQX.js} +1 -1
  28. package/dist-web/assets/{erDiagram-TEJ5UH35-BQucaPqh.js → erDiagram-TEJ5UH35-BrhRvGW3.js} +1 -1
  29. package/dist-web/assets/{flowDiagram-I6XJVG4X-CNpuVBgN.js → flowDiagram-I6XJVG4X-D-u112mz.js} +1 -1
  30. package/dist-web/assets/{ganttDiagram-6RSMTGT7-KIhg0NCT.js → ganttDiagram-6RSMTGT7-DpXOgY93.js} +1 -1
  31. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-C4YXqCii.js → gitGraphDiagram-PVQCEYII-BzV-RpYW.js} +1 -1
  32. package/dist-web/assets/{index-CBO7u0oz.js → index-6oRZtoED.js} +26 -26
  33. package/dist-web/assets/{index-CL0UgQBD.js → index-DEjpFBq9.js} +2 -2
  34. package/dist-web/assets/{index-v28kS3Hg.js → index-DrI3jjno.js} +1 -1
  35. package/dist-web/assets/{index10-RB_ZIgWs.js → index10-UVo4gjYR.js} +1 -1
  36. package/dist-web/assets/{index11-DyrslmWY.js → index11-m1oYWviR.js} +1 -1
  37. package/dist-web/assets/{index5-Bn5298gF.js → index5-B45mMYRO.js} +1 -1
  38. package/dist-web/assets/{index6-Di9uDNmo.js → index6-DvvrfRua.js} +1 -1
  39. package/dist-web/assets/{index7-CL9Ie_DH.js → index7-BUH5Peml.js} +1 -1
  40. package/dist-web/assets/{index8-CHlcChT0.js → index8-1BPQUZEF.js} +2 -2
  41. package/dist-web/assets/{infoDiagram-5YYISTIA-DTw-WQIn.js → infoDiagram-5YYISTIA-Cu7fADeQ.js} +1 -1
  42. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-BUmTsigt.js → ishikawaDiagram-YF4QCWOH-CVvnTORb.js} +1 -1
  43. package/dist-web/assets/{journeyDiagram-JHISSGLW-B8yfgocJ.js → journeyDiagram-JHISSGLW-C-69S6wV.js} +1 -1
  44. package/dist-web/assets/{kanban-definition-UN3LZRKU-BUcF33Xd.js → kanban-definition-UN3LZRKU-pwEooP5T.js} +1 -1
  45. package/dist-web/assets/{linear-DY3q771w.js → linear-BQ3ylJG8.js} +1 -1
  46. package/dist-web/assets/{mermaid.core-D1ZdIpxL.js → mermaid.core-DcE0EhAy.js} +4 -4
  47. package/dist-web/assets/{mindmap-definition-RKZ34NQL-ltEULHzt.js → mindmap-definition-RKZ34NQL-DKQ2AZ3h.js} +1 -1
  48. package/dist-web/assets/{pieDiagram-4H26LBE5-iCUA44fk.js → pieDiagram-4H26LBE5-CHb1kYTj.js} +1 -1
  49. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-Zrit1Fzo.js → quadrantDiagram-W4KKPZXB-DzFGGLJt.js} +1 -1
  50. package/dist-web/assets/{requirementDiagram-4Y6WPE33-n610iR9M.js → requirementDiagram-4Y6WPE33-Bk-5_Exk.js} +1 -1
  51. package/dist-web/assets/{sankeyDiagram-5OEKKPKP-RQEY2tBl.js → sankeyDiagram-5OEKKPKP-CSxfPrmb.js} +1 -1
  52. package/dist-web/assets/{sequenceDiagram-3UESZ5HK-QrJ3Ysl6.js → sequenceDiagram-3UESZ5HK-DHFbmk49.js} +1 -1
  53. package/dist-web/assets/{stateDiagram-AJRCARHV-D0mG4Tjg.js → stateDiagram-AJRCARHV-DR8SEStc.js} +1 -1
  54. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-Cp9aas86.js +1 -0
  55. package/dist-web/assets/{timeline-definition-PNZ67QCA-DacB6B1v.js → timeline-definition-PNZ67QCA-O5r5mf9f.js} +1 -1
  56. package/dist-web/assets/{vennDiagram-CIIHVFJN-CXO9WlfO.js → vennDiagram-CIIHVFJN-BRtD6E2o.js} +1 -1
  57. package/dist-web/assets/{vue.runtime.esm-bundler-Dj6KWBND.js → vue.runtime.esm-bundler-ClW1ucng.js} +1 -1
  58. package/dist-web/assets/{wardley-L42UT6IY-BbMWJCmQ.js → wardley-L42UT6IY-BqzYPkrr.js} +1 -1
  59. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-BIcvmjmV.js → wardleyDiagram-YWT4CUSO-BXjVelBc.js} +1 -1
  60. package/dist-web/assets/{xychartDiagram-2RQKCTM6-CX9xzSCM.js → xychartDiagram-2RQKCTM6-Cc97QEmc.js} +1 -1
  61. package/dist-web/index.html +1 -1
  62. package/package.json +6 -6
  63. package/dist-web/assets/channel-UBfbidLp.js +0 -1
  64. package/dist-web/assets/classDiagram-4FO5ZUOK-B6r9FPXR.js +0 -1
  65. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-B6r9FPXR.js +0 -1
  66. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-CctN1EOf.js +0 -1
package/dist/main.mjs CHANGED
@@ -1083,13 +1083,42 @@ var init_capability = __esmMin((() => {
1083
1083
  function isRetryableGenerateError(error) {
1084
1084
  if (error instanceof APIConnectionError$3 || error instanceof APITimeoutError) return true;
1085
1085
  if (error instanceof APIEmptyResponseError) return true;
1086
- return error instanceof APIStatusError && [
1086
+ if (error instanceof APIStatusError) return [
1087
+ 408,
1088
+ 409,
1087
1089
  429,
1088
1090
  500,
1089
1091
  502,
1090
1092
  503,
1091
- 504
1093
+ 504,
1094
+ 529
1092
1095
  ].includes(error.statusCode);
1096
+ return error instanceof ChatProviderError && !isImageFormatError(error);
1097
+ }
1098
+ /**
1099
+ * Whether the provider rejected an IMAGE in the request because of its
1100
+ * FORMAT or DATA — an unsupported media type or undecodable image bytes.
1101
+ * The rejection is deterministic for a given history (the same image is
1102
+ * re-sent on every request, so the session would fail every turn), and the
1103
+ * only recovery is to resend once with all media stripped (see the
1104
+ * media-stripped resend in the agent loop). Body-size (413), context
1105
+ * overflow, image count/size limits, image-input-disabled rejections, and
1106
+ * non-image (audio/video) media rejections are excluded — the first two
1107
+ * have their own recoveries, and the rest are not fixed by stripping media.
1108
+ */
1109
+ function isImageFormatError(error) {
1110
+ if (error instanceof APIStatusError) {
1111
+ if (error instanceof APIContextOverflowError) return false;
1112
+ if (error instanceof APIRequestTooLargeError) return false;
1113
+ if (error.statusCode !== 400) return false;
1114
+ const lowerMessage = error.message.toLowerCase();
1115
+ return IMAGE_FORMAT_STATUS_MESSAGE_PATTERNS.some((pattern) => pattern.test(lowerMessage)) || MEDIA_TYPE_FIELD_PATTERN.test(lowerMessage) && lowerMessage.includes("image");
1116
+ }
1117
+ if (error instanceof ChatProviderError) {
1118
+ const lowerMessage = error.message.toLowerCase();
1119
+ return IMAGE_FORMAT_PROVIDER_MESSAGE_PATTERNS.some((pattern) => pattern.test(lowerMessage));
1120
+ }
1121
+ return false;
1093
1122
  }
1094
1123
  /**
1095
1124
  * Classify a raw (non-SDK) error message into the right transport-layer
@@ -1107,11 +1136,25 @@ function classifyBaseApiError(message) {
1107
1136
  function isContextOverflowErrorCode(code) {
1108
1137
  return code === "context_length_exceeded";
1109
1138
  }
1110
- function normalizeAPIStatusError(statusCode, message, requestId) {
1111
- if (statusCode === 429) return new APIProviderRateLimitError(message, requestId);
1112
- if (isContextOverflowStatusError(statusCode, message)) return new APIContextOverflowError(statusCode, message, requestId);
1113
- if (isRequestTooLargeStatusError(statusCode, message)) return new APIRequestTooLargeError(statusCode, message, requestId);
1114
- return new APIStatusError(statusCode, message, requestId);
1139
+ function normalizeAPIStatusError(statusCode, message, requestId, retryAfterMs) {
1140
+ if (statusCode === 429) return new APIProviderRateLimitError(message, requestId, retryAfterMs);
1141
+ if (isContextOverflowStatusError(statusCode, message)) return new APIContextOverflowError(statusCode, message, requestId, retryAfterMs);
1142
+ if (isRequestTooLargeStatusError(statusCode, message)) return new APIRequestTooLargeError(statusCode, message, requestId, retryAfterMs);
1143
+ return new APIStatusError(statusCode, message, requestId, retryAfterMs);
1144
+ }
1145
+ /**
1146
+ * Parse a `retry-after` response header into milliseconds. Only integer
1147
+ * seconds is honored; an HTTP-date (or any non-integer / missing value)
1148
+ * returns null and the caller falls back to its computed backoff. Shared by
1149
+ * the provider error converters so every backend honors the same server
1150
+ * backoff directive.
1151
+ */
1152
+ function parseRetryAfterMs(headers) {
1153
+ const raw = headers !== null && typeof headers === "object" && typeof headers.get === "function" ? headers.get("retry-after") : null;
1154
+ if (raw === null || raw === void 0) return null;
1155
+ const seconds = Number.parseInt(raw, 10);
1156
+ if (!Number.isFinite(seconds) || seconds < 0) return null;
1157
+ return seconds * 1e3;
1115
1158
  }
1116
1159
  function isContextOverflowStatusError(statusCode, message) {
1117
1160
  if (statusCode !== 400 && statusCode !== 413 && statusCode !== 422) return false;
@@ -1163,7 +1206,7 @@ function getStatusCode(error) {
1163
1206
  function errorMessage$6(error) {
1164
1207
  return error instanceof Error ? error.message : String(error);
1165
1208
  }
1166
- var ChatProviderError, APIConnectionError$3, APITimeoutError, APIStatusError, APIContextOverflowError, APIRequestTooLargeError, APIProviderRateLimitError, APIEmptyResponseError, NETWORK_RE$1, TIMEOUT_RE$1, CONTEXT_OVERFLOW_MESSAGE_PATTERNS, PROVIDER_RATE_LIMIT_MESSAGE_PATTERNS, REQUEST_TOO_LARGE_MESSAGE_PATTERNS, TOOL_EXCHANGE_ADJACENCY_MESSAGE_PATTERNS, STRUCTURAL_REQUEST_MESSAGE_PATTERNS;
1209
+ var ChatProviderError, APIConnectionError$3, APITimeoutError, APIStatusError, APIContextOverflowError, APIRequestTooLargeError, APIProviderRateLimitError, APIEmptyResponseError, IMAGE_FORMAT_PROVIDER_MESSAGE_PATTERNS, IMAGE_FORMAT_STATUS_MESSAGE_PATTERNS, MEDIA_TYPE_FIELD_PATTERN, NETWORK_RE$1, TIMEOUT_RE$1, CONTEXT_OVERFLOW_MESSAGE_PATTERNS, PROVIDER_RATE_LIMIT_MESSAGE_PATTERNS, REQUEST_TOO_LARGE_MESSAGE_PATTERNS, TOOL_EXCHANGE_ADJACENCY_MESSAGE_PATTERNS, STRUCTURAL_REQUEST_MESSAGE_PATTERNS;
1167
1210
  var init_errors$12 = __esmMin((() => {
1168
1211
  ChatProviderError = class extends Error {
1169
1212
  constructor(message) {
@@ -1186,28 +1229,36 @@ var init_errors$12 = __esmMin((() => {
1186
1229
  APIStatusError = class extends ChatProviderError {
1187
1230
  statusCode;
1188
1231
  requestId;
1189
- constructor(statusCode, message, requestId) {
1232
+ /**
1233
+ * Server-requested backoff from the `retry-after` response header, in
1234
+ * milliseconds. When present, the retry loop honors it instead of its own
1235
+ * computed backoff — a server `Retry-After` directive overrides the local
1236
+ * exponential delay.
1237
+ */
1238
+ retryAfterMs;
1239
+ constructor(statusCode, message, requestId, retryAfterMs) {
1190
1240
  super(message);
1191
1241
  this.name = "APIStatusError";
1192
1242
  this.statusCode = statusCode;
1193
1243
  this.requestId = requestId ?? null;
1244
+ this.retryAfterMs = retryAfterMs ?? null;
1194
1245
  }
1195
1246
  };
1196
1247
  APIContextOverflowError = class extends APIStatusError {
1197
- constructor(statusCode, message, requestId) {
1198
- super(statusCode, message, requestId);
1248
+ constructor(statusCode, message, requestId, retryAfterMs) {
1249
+ super(statusCode, message, requestId, retryAfterMs);
1199
1250
  this.name = "APIContextOverflowError";
1200
1251
  }
1201
1252
  };
1202
1253
  APIRequestTooLargeError = class extends APIStatusError {
1203
- constructor(statusCode, message, requestId) {
1204
- super(statusCode, message, requestId);
1254
+ constructor(statusCode, message, requestId, retryAfterMs) {
1255
+ super(statusCode, message, requestId, retryAfterMs);
1205
1256
  this.name = "APIRequestTooLargeError";
1206
1257
  }
1207
1258
  };
1208
1259
  APIProviderRateLimitError = class extends APIStatusError {
1209
- constructor(message, requestId) {
1210
- super(429, message, requestId);
1260
+ constructor(message, requestId, retryAfterMs) {
1261
+ super(429, message, requestId, retryAfterMs);
1211
1262
  this.name = "APIProviderRateLimitError";
1212
1263
  }
1213
1264
  };
@@ -1221,6 +1272,16 @@ var init_errors$12 = __esmMin((() => {
1221
1272
  this.rawFinishReason = options.rawFinishReason ?? null;
1222
1273
  }
1223
1274
  };
1275
+ IMAGE_FORMAT_PROVIDER_MESSAGE_PATTERNS = [/unsupported media type for base64 image/, /invalid data url for image/];
1276
+ IMAGE_FORMAT_STATUS_MESSAGE_PATTERNS = [
1277
+ /unsupported image (?:url|format|type)/,
1278
+ /does not represent a valid image/,
1279
+ /could not (?:process|decode) (?:the |input )?image/,
1280
+ /unable to process (?:the |input )?image/,
1281
+ /failed to decode (?:the )?image/,
1282
+ /invalid image(?: data| type| format)?/
1283
+ ];
1284
+ MEDIA_TYPE_FIELD_PATTERN = /(?:media|mime)_?type/;
1224
1285
  NETWORK_RE$1 = /network|connection|connect|disconnect|terminated/i;
1225
1286
  TIMEOUT_RE$1 = /timed?\s*out|timeout|deadline/i;
1226
1287
  CONTEXT_OVERFLOW_MESSAGE_PATTERNS = [
@@ -9608,7 +9669,7 @@ function convertAnthropicError(error) {
9608
9669
  if (error instanceof APIConnectionError$2) return new APIConnectionError$3(error.message);
9609
9670
  if (error instanceof APIError$2 && typeof error.status === "number") {
9610
9671
  const reqId = error.requestID ?? null;
9611
- return normalizeAPIStatusError(error.status, error.message, reqId);
9672
+ return normalizeAPIStatusError(error.status, error.message, reqId, parseRetryAfterMs(error.headers));
9612
9673
  }
9613
9674
  if (error instanceof AnthropicError) return new ChatProviderError(`Anthropic error: ${error.message}`);
9614
9675
  if (error instanceof Error) return classifyBaseApiError(error.message);
@@ -53199,7 +53260,7 @@ function convertOpenAIError(error) {
53199
53260
  if (error instanceof APIConnectionError) return new APIConnectionError$3(error.message);
53200
53261
  if (error instanceof APIError && typeof error.status === "number") {
53201
53262
  const reqId = error.requestID ?? null;
53202
- return normalizeAPIStatusError(error.status, error.message, reqId);
53263
+ return normalizeAPIStatusError(error.status, error.message, reqId, parseRetryAfterMs(error.headers));
53203
53264
  }
53204
53265
  if (error instanceof APIError && error.constructor === APIError && error.error === void 0) return classifyBaseApiError(error.message);
53205
53266
  if (error instanceof OpenAIError) return new ChatProviderError(`Error: ${error.message}`);
@@ -54315,10 +54376,14 @@ function functionCallId(callId) {
54315
54376
  function formatResponsesErrorEvent(code, message, param) {
54316
54377
  return `${code ?? "unknown"}: ${message}${param === null ? "" : ` (param: ${param})`}`;
54317
54378
  }
54379
+ function readEmbeddedStatusCode(message) {
54380
+ const match = EMBEDDED_STATUS_CODE_RE.exec(message);
54381
+ return match === null ? void 0 : Number(match[1]);
54382
+ }
54318
54383
  function errorFromOpenAIResponsesEvent(prefix, code, message, param) {
54319
54384
  const fullMessage = `${prefix}: ${formatResponsesErrorEvent(code, message, param)}`;
54320
54385
  if (isContextOverflowErrorCode(code)) return new APIContextOverflowError(400, fullMessage);
54321
- if (code === "rate_limit_exceeded") return new APIProviderRateLimitError(fullMessage);
54386
+ if (code === "rate_limit_exceeded" || readEmbeddedStatusCode(message) === 429) return new APIProviderRateLimitError(fullMessage);
54322
54387
  return new ChatProviderError(fullMessage);
54323
54388
  }
54324
54389
  function parseNestedGatewayStreamError(message) {
@@ -54589,7 +54654,7 @@ function convertHistoryMessages(history, modelName, toolMessageConversion) {
54589
54654
  flushPendingMedia();
54590
54655
  return input;
54591
54656
  }
54592
- var OPENAI_RESPONSES_TOOL_CALL_ID_POLICY, OMITTED_AUDIO_PLACEHOLDER, OMITTED_VIDEO_PLACEHOLDER, OpenAIResponsesStreamedMessage, OpenAIResponsesChatProvider;
54657
+ var OPENAI_RESPONSES_TOOL_CALL_ID_POLICY, EMBEDDED_STATUS_CODE_RE, OMITTED_AUDIO_PLACEHOLDER, OMITTED_VIDEO_PLACEHOLDER, OpenAIResponsesStreamedMessage, OpenAIResponsesChatProvider;
54593
54658
  var init_openai_responses = __esmMin((() => {
54594
54659
  init_errors$12();
54595
54660
  init_message$3();
@@ -54602,6 +54667,7 @@ var init_openai_responses = __esmMin((() => {
54602
54667
  normalize: (id) => sanitizeOpenAIResponsesCallId(id, 64),
54603
54668
  maxLength: 64
54604
54669
  };
54670
+ EMBEDDED_STATUS_CODE_RE = /\bstatus_code\s*[:=]\s*(\d{3})\b/;
54605
54671
  OMITTED_AUDIO_PLACEHOLDER = "(audio omitted: unsupported audio format)";
54606
54672
  OMITTED_VIDEO_PLACEHOLDER = "(video omitted: not supported by this provider)";
54607
54673
  OpenAIResponsesStreamedMessage = class {
@@ -69080,6 +69146,174 @@ var init_file_type$1 = __esmMin((() => {
69080
69146
  });
69081
69147
  }));
69082
69148
  //#endregion
69149
+ //#region ../../packages/agent-core/src/tools/support/image-format-policy.ts
69150
+ /**
69151
+ * Lowercase, drop MIME parameters, and apply the `image/jpg` alias. Parameter
69152
+ * stripping keeps a declared media type like `image/jpeg; charset=utf-8`
69153
+ * consistent with a data-URL MIME token (which the parser already clips at
69154
+ * the first `;`), so an accepted image with parameters is treated exactly
69155
+ * like the bare form instead of being misread as unsupported.
69156
+ */
69157
+ function normalizeImageMime(mimeType) {
69158
+ const lower = mimeType.trim().toLowerCase();
69159
+ const semi = lower.indexOf(";");
69160
+ const base = (semi === -1 ? lower : lower.slice(0, semi)).trim();
69161
+ return base === "image/jpg" ? "image/jpeg" : base;
69162
+ }
69163
+ /**
69164
+ * Decode just the prefix of a base64 payload needed for magic-byte
69165
+ * sniffing, without allocating the full image. `Buffer.from` never throws
69166
+ * on malformed base64 — it decodes what it can.
69167
+ */
69168
+ function decodeBase64Prefix(base64) {
69169
+ return Buffer.from(base64.slice(0, BASE64_SNIFF_CHARS), "base64");
69170
+ }
69171
+ /**
69172
+ * The MIME an image should be judged by: the sniffed bytes when the magic
69173
+ * header is recognized (bytes are authoritative — a mislabeled image, e.g.
69174
+ * AVIF bytes an MCP image search tool labels `image/png`, is gated on what
69175
+ * it IS, because the provider decodes bytes not labels), else the declared
69176
+ * MIME. A header recognized as a non-image container also wins, so a video
69177
+ * file hiding in an image part is refused instead of trusted.
69178
+ */
69179
+ function resolveEffectiveImageMime(declaredMime, header) {
69180
+ const sniffed = sniffMediaFromMagic(header);
69181
+ return sniffed !== null ? sniffed.mimeType : declaredMime;
69182
+ }
69183
+ /**
69184
+ * Whether a non-data image URL points at a format providers reject, judged
69185
+ * by its path extension. A best-effort heuristic for the case where there
69186
+ * are no bytes to sniff (a remote http(s) image): catches the common
69187
+ * search-tool direct link ending in `.avif`. Query string and fragment are
69188
+ * ignored; the match is case-insensitive. URLs without an extension, or
69189
+ * whose extension lies, fall through to the provider — and to the 400
69190
+ * recovery — unchanged.
69191
+ *
69192
+ * Returns the unsupported MIME when the extension is known and not in the
69193
+ * accepted set (so the notice names the right format), else null.
69194
+ */
69195
+ function unsupportedImageMimeFromUrl(url) {
69196
+ let path = url;
69197
+ const query = path.indexOf("?");
69198
+ if (query !== -1) path = path.slice(0, query);
69199
+ const hash = path.indexOf("#");
69200
+ if (hash !== -1) path = path.slice(0, hash);
69201
+ const dot = path.lastIndexOf(".");
69202
+ if (dot === -1) return null;
69203
+ const ext = path.slice(dot).toLowerCase();
69204
+ const mime = ext === ".svg" ? "image/svg+xml" : IMAGE_MIME_BY_SUFFIX[ext];
69205
+ if (mime === void 0 || isModelAcceptedImageMime(mime)) return null;
69206
+ return mime;
69207
+ }
69208
+ /**
69209
+ * Parse an image `data:` URL into its MIME and base64 payload. The MIME is
69210
+ * returned raw — callers decide via {@link isModelAcceptedImageMime} and
69211
+ * forward {@link normalizeImageMime}. MIME parameters are tolerated and
69212
+ * ignored (`data:image/avif;charset=utf-8;base64,…`), so a parameter-bearing
69213
+ * URL cannot slip past the format gate. The scheme and `base64` marker are
69214
+ * matched case-insensitively (RFC 2045 encoding names are case-insensitive),
69215
+ * so an uppercase `;BASE64,` cannot slip past either — and since callers
69216
+ * rebuild to the canonical URL, the marker comes back out lowercase.
69217
+ * Returns null for non-data URLs (e.g. a remote http(s) image — see the
69218
+ * scope note in the module header).
69219
+ */
69220
+ function parseImageDataUrl(url) {
69221
+ const match = /^data:([^;,]+)(?:;[^;,]+)*?;base64,(.*)$/is.exec(url);
69222
+ if (match === null) return null;
69223
+ return {
69224
+ mimeType: match[1],
69225
+ base64: match[2]
69226
+ };
69227
+ }
69228
+ /**
69229
+ * Whether a URL claims to be a `data:` URL (the scheme is case-insensitive).
69230
+ * Used to distinguish "failed to parse a data URL" (malformed — guaranteed
69231
+ * to fail at the provider) from "not a data URL" (a remote http(s) image
69232
+ * the provider fetches).
69233
+ */
69234
+ function isDataUrl(url) {
69235
+ return url.toLowerCase().startsWith("data:");
69236
+ }
69237
+ /**
69238
+ * Whether an image with this MIME may be sent to the model. Only the closed
69239
+ * accepted set passes; everything else must be refused at the entry point —
69240
+ * once an unsupported `image_url` lands in the session history, every later
69241
+ * request in the session is rejected by the provider.
69242
+ */
69243
+ function isModelAcceptedImageMime(mimeType) {
69244
+ return MODEL_ACCEPTED_IMAGE_MIMES.has(normalizeImageMime(mimeType));
69245
+ }
69246
+ /**
69247
+ * Refusal for an unsupported image that has a readable file path, with a
69248
+ * conversion command matching the execution environment (`kaos.osEnv.osKind`
69249
+ * — where Bash actually runs, so SSH/container sessions get the right command
69250
+ * too). The model can run the command through Bash (under the normal
69251
+ * permission flow) and read the converted file.
69252
+ *
69253
+ * macOS converts with the built-in `sips`; Linux and Windows have no built-in
69254
+ * decoder for these formats, so the guidance names ImageMagick (plus the
69255
+ * format's dedicated Linux decoder when one exists, e.g. heif-convert).
69256
+ */
69257
+ function buildImageConversionGuidance(path, mimeType, osKind) {
69258
+ const converted = path.replace(/\.[^./\\]+$/, "") + ".jpg";
69259
+ return `"${path}" is an ${mimeType} image, which the provider does not accept. Convert it to JPEG first, then read the converted file. ` + imageConversionCommand(path, converted, osKind, UNSUPPORTED_IMAGE_FORMATS[normalizeImageMime(mimeType)]);
69260
+ }
69261
+ function imageConversionCommand(path, converted, osKind, format) {
69262
+ const magick = `magick "${path}" "${converted}"`;
69263
+ const linuxDecoder = format?.linuxDecoder;
69264
+ switch (osKind) {
69265
+ case "macOS": return `On macOS: sips -s format jpeg "${path}" --out "${converted}"`;
69266
+ case "Linux": return linuxDecoder === void 0 ? `On Linux, with ImageMagick: ${magick}` : `On Linux: ${linuxDecoder.command} "${path}" "${converted}" (package ${linuxDecoder.packageName}), or with ImageMagick: ${magick}`;
69267
+ case "Windows": return `On Windows, with ImageMagick: ${magick} (install it first if missing: winget install ImageMagick.ImageMagick)`;
69268
+ default: return `Options: sips -s format jpeg "${path}" --out "${converted}" (macOS)` + (linuxDecoder === void 0 ? "" : `, ${linuxDecoder.command} "${path}" "${converted}" (Linux, package ${linuxDecoder.packageName})`) + `, or ${magick} (ImageMagick)`;
69269
+ }
69270
+ }
69271
+ /**
69272
+ * Short notice standing in for an unsupported image where there is no file
69273
+ * path to point at (MCP tool results, prompt uploads): the image part is
69274
+ * dropped and this text replaces it, so the model knows what happened and
69275
+ * the session history stays free of formats the provider rejects.
69276
+ */
69277
+ function buildUnsupportedImageNotice(mimeType, name) {
69278
+ return `[Image omitted: ${name === void 0 || name.length === 0 ? `unsupported image format ${mimeType}` : `"${name}" uses unsupported image format ${mimeType}`}. Model providers accept only ${ACCEPTED_FORMATS_TEXT} — convert it to PNG or JPEG and try again.]`;
69279
+ }
69280
+ /**
69281
+ * Notice standing in for an image part whose `data:` URL could not be parsed
69282
+ * at all (missing `;base64,` separator, empty MIME, …): the provider is
69283
+ * guaranteed to reject it, so it is dropped at ingestion instead of being
69284
+ * left to poison the session and trigger the media-stripped resend on every
69285
+ * later turn. The URL is truncated — a malformed payload can be huge.
69286
+ */
69287
+ function buildMalformedImageNotice(url) {
69288
+ return `[Image omitted: "${url.length > 80 ? `${url.slice(0, 80)}…` : url}" is not a valid data URL (its header or payload could not be parsed). Re-encode the image as PNG or JPEG and try again.]`;
69289
+ }
69290
+ var MODEL_ACCEPTED_IMAGE_MIMES, ACCEPTED_FORMATS_TEXT, UNSUPPORTED_IMAGE_FORMATS, BASE64_SNIFF_CHARS;
69291
+ var init_image_format_policy = __esmMin((() => {
69292
+ init_file_type$1();
69293
+ MODEL_ACCEPTED_IMAGE_MIMES = new Set([
69294
+ "image/png",
69295
+ "image/jpeg",
69296
+ "image/gif",
69297
+ "image/webp"
69298
+ ]);
69299
+ ACCEPTED_FORMATS_TEXT = "PNG, JPEG, GIF, and WebP";
69300
+ UNSUPPORTED_IMAGE_FORMATS = Object.freeze({
69301
+ "image/avif": {},
69302
+ "image/heic": { linuxDecoder: {
69303
+ command: "heif-convert",
69304
+ packageName: "libheif-examples"
69305
+ } },
69306
+ "image/heif": { linuxDecoder: {
69307
+ command: "heif-convert",
69308
+ packageName: "libheif-examples"
69309
+ } },
69310
+ "image/bmp": {},
69311
+ "image/tiff": {},
69312
+ "image/x-icon": {}
69313
+ });
69314
+ BASE64_SNIFF_CHARS = 48;
69315
+ }));
69316
+ //#endregion
69083
69317
  //#region ../../node_modules/.pnpm/@jsquash+webp@1.5.0/node_modules/@jsquash/webp/codec/dec/webp_dec.js
69084
69318
  var Module;
69085
69319
  var init_webp_dec = __esmMin((() => {
@@ -111969,7 +112203,7 @@ async function compressImageForModel(bytes, mimeType, options = {}) {
111969
112203
  const maxEdge = options.maxEdge ?? resolveMaxImageEdgePx();
111970
112204
  const byteBudget = options.byteBudget ?? 3932160;
111971
112205
  const maxDecodeBytes = options.maxDecodeBytes ?? MAX_DECODE_BYTES;
111972
- const normalizedMime = normalizeMime(mimeType);
112206
+ const normalizedMime = normalizeImageMime(mimeType);
111973
112207
  const dims = sniffImageDimensions(bytes);
111974
112208
  const passthrough = () => ({
111975
112209
  data: bytes,
@@ -112031,7 +112265,10 @@ async function compressImageForModel(bytes, mimeType, options = {}) {
112031
112265
  /**
112032
112266
  * Convenience wrapper for call sites that already hold base64 (MCP results,
112033
112267
  * data URLs). Decodes, compresses, and re-encodes to base64. Best effort:
112034
- * returns the original base64 unchanged on any failure.
112268
+ * returns the original base64 unchanged on any failure — including formats it
112269
+ * cannot re-encode, so callers must refuse MIME types outside the
112270
+ * provider-accepted set (`isModelAcceptedImageMime`) before building an
112271
+ * image part from the result.
112035
112272
  */
112036
112273
  async function compressBase64ForModel(base64, mimeType, options = {}) {
112037
112274
  const startedAt = Date.now();
@@ -112052,7 +112289,7 @@ async function compressBase64ForModel(base64, mimeType, options = {}) {
112052
112289
  reportCompressEvent(options.telemetry, {
112053
112290
  outcome: "passthrough_guard",
112054
112291
  startedAt,
112055
- inputMime: normalizeMime(mimeType),
112292
+ inputMime: normalizeImageMime(mimeType),
112056
112293
  exifTransposed: false,
112057
112294
  result
112058
112295
  });
@@ -112076,7 +112313,7 @@ async function compressBase64ForModel(base64, mimeType, options = {}) {
112076
112313
  reportCompressEvent(options.telemetry, {
112077
112314
  outcome: "passthrough_error",
112078
112315
  startedAt,
112079
- inputMime: normalizeMime(mimeType),
112316
+ inputMime: normalizeImageMime(mimeType),
112080
112317
  exifTransposed: false,
112081
112318
  result
112082
112319
  });
@@ -112107,6 +112344,76 @@ async function compressBase64ForModel(base64, mimeType, options = {}) {
112107
112344
  };
112108
112345
  }
112109
112346
  /**
112347
+ * Enforce the provider-accepted image format set (see ./image-format-policy)
112348
+ * on a content-part list. Inline `data:` image parts whose MIME is outside
112349
+ * the accepted set are dropped and replaced with a text notice, so one
112350
+ * unsupported image cannot poison the session history. Accepted images are
112351
+ * forwarded only as the byte-exact canonical data URL: an alias
112352
+ * (`image/jpg`), case/whitespace variants, or MIME parameters
112353
+ * (`image/jpeg;charset=utf-8`) all rebuild to the bare canonical form,
112354
+ * because strict provider whitelists exact-match the full header. Remote
112355
+ * (non-data) image URLs and non-image parts pass through — a URL carries no
112356
+ * bytes to inspect.
112357
+ *
112358
+ * The BYTES are authoritative, not the declared MIME: the header of each
112359
+ * inline image is sniffed, and a mismatch (e.g. AVIF bytes an MCP image
112360
+ * search tool labels `image/png`) is gated on what the image IS — the
112361
+ * provider decodes bytes, not labels. When the sniffer doesn't recognize
112362
+ * the bytes (corrupt image, exotic container), the declared MIME stands
112363
+ * and the 400-recovery path remains the backstop.
112364
+ *
112365
+ * This is the format gate shared by every ingestion point; run it BEFORE
112366
+ * compression so unsupported bytes are never decoded.
112367
+ */
112368
+ function gateImageFormatParts(parts) {
112369
+ const out = [];
112370
+ for (const part of parts) {
112371
+ if (part.type === "image_url") {
112372
+ const parsed = parseImageDataUrl(part.imageUrl.url);
112373
+ if (parsed === null) {
112374
+ if (isDataUrl(part.imageUrl.url)) {
112375
+ out.push({
112376
+ type: "text",
112377
+ text: buildMalformedImageNotice(part.imageUrl.url)
112378
+ });
112379
+ continue;
112380
+ }
112381
+ const extMime = unsupportedImageMimeFromUrl(part.imageUrl.url);
112382
+ if (extMime !== null) {
112383
+ out.push({
112384
+ type: "text",
112385
+ text: buildUnsupportedImageNotice(extMime, part.imageUrl.url)
112386
+ });
112387
+ continue;
112388
+ }
112389
+ out.push(part);
112390
+ continue;
112391
+ }
112392
+ const effectiveMime = resolveEffectiveImageMime(parsed.mimeType, decodeBase64Prefix(parsed.base64));
112393
+ if (!isModelAcceptedImageMime(effectiveMime)) {
112394
+ out.push({
112395
+ type: "text",
112396
+ text: buildUnsupportedImageNotice(effectiveMime)
112397
+ });
112398
+ continue;
112399
+ }
112400
+ const canonicalUrl = `data:${normalizeImageMime(effectiveMime)};base64,${parsed.base64}`;
112401
+ if (part.imageUrl.url !== canonicalUrl) {
112402
+ out.push({
112403
+ type: "image_url",
112404
+ imageUrl: {
112405
+ ...part.imageUrl,
112406
+ url: canonicalUrl
112407
+ }
112408
+ });
112409
+ continue;
112410
+ }
112411
+ }
112412
+ out.push(part);
112413
+ }
112414
+ return out;
112415
+ }
112416
+ /**
112110
112417
  * Compress any inline base64 image parts in a content-part list — used by
112111
112418
  * the MCP tool-result path (prompt ingestion compresses per image with
112112
112419
  * {@link compressBase64ForModel} while constructing the part). Image parts
@@ -112114,6 +112421,13 @@ async function compressBase64ForModel(base64, mimeType, options = {}) {
112114
112421
  * through, as are non-image parts. Best effort: a part that fails to
112115
112422
  * compress is left unchanged.
112116
112423
  *
112424
+ * The format gate ({@link gateImageFormatParts}) runs first: parts whose
112425
+ * MIME is outside the provider-accepted set are never forwarded — the part
112426
+ * is dropped and a text notice stands in, so one unsupported image cannot
112427
+ * poison the session history. This is the MCP funnel's enforcement point —
112428
+ * MCP servers can return any `image/*` MIME (e.g. AVIF from an image search
112429
+ * tool).
112430
+ *
112117
112431
  * With `annotate` set, every image that was actually re-encoded gets a
112118
112432
  * caption in {@link CompressedContentParts.captions} so the model knows it
112119
112433
  * is looking at a downsampled copy. `annotate.persistOriginal` additionally
@@ -112125,9 +112439,9 @@ async function compressImageContentParts(parts, options = {}) {
112125
112439
  const { annotate, ...compressOptions } = options;
112126
112440
  const out = [];
112127
112441
  const captions = [];
112128
- for (const part of parts) {
112442
+ for (const part of gateImageFormatParts(parts)) {
112129
112443
  if (part.type === "image_url") {
112130
- const parsed = parseImageDataUrl$1(part.imageUrl.url);
112444
+ const parsed = parseImageDataUrl(part.imageUrl.url);
112131
112445
  if (parsed !== null) {
112132
112446
  const result = await compressBase64ForModel(parsed.base64, parsed.mimeType, compressOptions);
112133
112447
  if (result.changed) {
@@ -112189,7 +112503,7 @@ async function cropImageForModel(bytes, mimeType, region, options = {}) {
112189
112503
  const maxEdge = options.maxEdge ?? resolveMaxImageEdgePx();
112190
112504
  const byteBudget = options.byteBudget ?? 3932160;
112191
112505
  const maxDecodeBytes = options.maxDecodeBytes ?? MAX_DECODE_BYTES;
112192
- const normalizedMime = normalizeMime(mimeType);
112506
+ const normalizedMime = normalizeImageMime(mimeType);
112193
112507
  const fail = (errorKind, error) => {
112194
112508
  reportCropEvent(options.telemetry, {
112195
112509
  startedAt,
@@ -112339,14 +112653,6 @@ function formatByteSize$2(bytes) {
112339
112653
  if (bytes < 1024 * 1024) return `${String(Math.round(bytes / 1024))} KB`;
112340
112654
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
112341
112655
  }
112342
- function parseImageDataUrl$1(url) {
112343
- const match = /^data:([^;,]+);base64,(.*)$/s.exec(url);
112344
- if (match === null) return null;
112345
- return {
112346
- mimeType: match[1],
112347
- base64: match[2]
112348
- };
112349
- }
112350
112656
  /**
112351
112657
  * Decode `bytes` into a jimp image. PNG/JPEG decode through jimp itself
112352
112658
  * (which applies EXIF orientation); WebP decodes through the bundled wasm
@@ -112455,10 +112761,6 @@ function fitWithinEdge(image, edge) {
112455
112761
  });
112456
112762
  return true;
112457
112763
  }
112458
- function normalizeMime(mimeType) {
112459
- const lower = mimeType.trim().toLowerCase();
112460
- return lower === "image/jpg" ? "image/jpeg" : lower;
112461
- }
112462
112764
  /**
112463
112765
  * Emit the `image_compress` event. Properties are all numeric/enum — never
112464
112766
  * paths or content — and a throwing client is swallowed so telemetry can
@@ -112471,7 +112773,7 @@ function reportCompressEvent(telemetry, input) {
112471
112773
  source: telemetry.source,
112472
112774
  outcome: input.outcome,
112473
112775
  input_mime: input.inputMime,
112474
- output_mime: normalizeMime(input.result.mimeType),
112776
+ output_mime: normalizeImageMime(input.result.mimeType),
112475
112777
  original_bytes: input.result.originalByteLength,
112476
112778
  final_bytes: input.result.finalByteLength,
112477
112779
  original_width: input.result.originalWidth,
@@ -112508,6 +112810,7 @@ function reportCropEvent(telemetry, input) {
112508
112810
  var MAX_IMAGE_EDGE_ENV, IMAGE_BYTE_BUDGET, READ_IMAGE_BYTE_BUDGET_ENV, JPEG_QUALITY_STEPS, FALLBACK_EDGES_PX, PNG_RESCALE_FLOOR_PX, MAX_DECODE_PIXELS, MAX_DECODE_BYTES, RECODABLE_MIME, CAPTION_OPENING, CAPTION_PATTERN;
112509
112811
  var init_image_compress = __esmMin((() => {
112510
112812
  init_file_type$1();
112813
+ init_image_format_policy();
112511
112814
  init_webp_decode();
112512
112815
  MAX_IMAGE_EDGE_ENV = "KIMI_IMAGE_MAX_EDGE_PX";
112513
112816
  IMAGE_BYTE_BUDGET = 3.75 * 1024 * 1024;
@@ -121073,7 +121376,7 @@ async function chatWithRetry(input) {
121073
121376
  logRequestFailure(input, error, attempt, maxAttempts);
121074
121377
  throw error;
121075
121378
  }
121076
- const delayMs = delays[attempt - 1] ?? 0;
121379
+ const delayMs = readRetryAfterMs(error) ?? delays[attempt - 1] ?? 0;
121077
121380
  input.params.signal.throwIfAborted();
121078
121381
  input.dispatchEvent({
121079
121382
  type: "step.retrying",
@@ -121113,13 +121416,24 @@ function paramsForAttempt(input, attempt, maxAttempts) {
121113
121416
  };
121114
121417
  }
121115
121418
  function retryBackoffDelays(maxAttempts) {
121116
- return import_retry$1.timeouts({
121117
- retries: Math.max(maxAttempts - 1, 0),
121118
- minTimeout: RETRY_MIN_TIMEOUT_MS,
121119
- maxTimeout: RETRY_MAX_TIMEOUT_MS,
121120
- factor: RETRY_FACTOR,
121121
- randomize: true
121122
- });
121419
+ const count = Math.max(maxAttempts - 1, 0);
121420
+ const delays = [];
121421
+ for (let i = 0; i < count; i += 1) {
121422
+ const base = Math.min(BASE_DELAY_MS * Math.pow(RETRY_FACTOR, i), MAX_DELAY_MS);
121423
+ delays.push(base + Math.random() * JITTER_FACTOR * base);
121424
+ }
121425
+ return delays;
121426
+ }
121427
+ /**
121428
+ * Server-requested backoff carried on a kosong `APIStatusError` (parsed from
121429
+ * the `retry-after` response header). When present and positive it overrides
121430
+ * the computed backoff — a server `Retry-After` directive takes precedence
121431
+ * over the local exponential delay.
121432
+ */
121433
+ function readRetryAfterMs(error) {
121434
+ if (typeof error !== "object" || error === null) return null;
121435
+ const value = error.retryAfterMs;
121436
+ return typeof value === "number" && value > 0 ? value : null;
121123
121437
  }
121124
121438
  async function sleepForRetry(delayMs, signal) {
121125
121439
  signal.throwIfAborted();
@@ -121137,15 +121451,15 @@ function maybeStatusCode(error) {
121137
121451
  const statusCode = error.statusCode;
121138
121452
  return typeof statusCode === "number" ? statusCode : void 0;
121139
121453
  }
121140
- var import_retry$1, RETRY_MIN_TIMEOUT_MS, RETRY_MAX_TIMEOUT_MS, RETRY_FACTOR;
121454
+ var BASE_DELAY_MS, MAX_DELAY_MS, RETRY_FACTOR, JITTER_FACTOR;
121141
121455
  var init_retry = __esmMin((() => {
121142
121456
  init_dist$4();
121143
- import_retry$1 = /* @__PURE__ */ __toESM(require_retry$4(), 1);
121144
121457
  init_abort();
121145
121458
  init_errors$4();
121146
- RETRY_MIN_TIMEOUT_MS = 300;
121147
- RETRY_MAX_TIMEOUT_MS = 5e3;
121459
+ BASE_DELAY_MS = 500;
121460
+ MAX_DELAY_MS = 32e3;
121148
121461
  RETRY_FACTOR = 2;
121462
+ JITTER_FACTOR = .25;
121149
121463
  }));
121150
121464
  //#endregion
121151
121465
  //#region ../../packages/agent-core/src/tools/support/input-schema.ts
@@ -121988,7 +122302,7 @@ var init_full = __esmMin((() => {
121988
122302
  summary = extractCompactionSummary(response);
121989
122303
  break;
121990
122304
  } catch (error) {
121991
- if (error instanceof APIRequestTooLargeError && !mediaStripAttempted) {
122305
+ if ((error instanceof APIRequestTooLargeError || isImageFormatError(error)) && !mediaStripAttempted) {
121992
122306
  mediaStripAttempted = true;
121993
122307
  const stripped = replaceMediaPartsWithMarkers(historyForModel);
121994
122308
  if (stripped !== historyForModel) {
@@ -124015,15 +124329,17 @@ function isDegradableMediaPart(part) {
124015
124329
  /**
124016
124330
  * Replace all but the `keepRecent` most recent media parts with deterministic
124017
124331
  * text markers. This is the media-degraded projection used to resend a request
124018
- * the provider rejected as too large (HTTP 413 on accumulated base64 media):
124019
- * a purely read-side transform — the underlying history is left untouched —
124020
- * that trades old pixels for bytes while the surrounding text (including
124021
- * ReadMediaFile's `<image path="...">` wrapper) survives, so the model can
124022
- * re-read any file it still needs. Untouched messages are returned by
124023
- * reference, and when nothing needs degrading the input array itself is
124024
- * returned.
124025
- */
124026
- function degradeOlderMediaParts(messages, keepRecent) {
124332
+ * the provider rejected as too large (HTTP 413 on accumulated base64 media)
124333
+ * and — with `keepRecent = 0` and `MEDIA_STRIPPED_PLACEHOLDERS` — the resend
124334
+ * after an image-format rejection, where the poisoned image could be anywhere
124335
+ * and only a full strip guarantees a clean request. A purely read-side
124336
+ * transform — the underlying history is left untouched — that trades pixels
124337
+ * for deliverability while the surrounding text (including ReadMediaFile's
124338
+ * `<image path="...">` wrapper) survives, so the model can re-read any file
124339
+ * it still needs. Untouched messages are returned by reference, and when
124340
+ * nothing needs degrading the input array itself is returned.
124341
+ */
124342
+ function degradeOlderMediaParts(messages, keepRecent, placeholders = MEDIA_DEGRADED_PLACEHOLDERS) {
124027
124343
  const mediaCount = messages.reduce((count, message) => count + message.content.filter(isDegradableMediaPart).length, 0);
124028
124344
  let toDegrade = Math.max(0, mediaCount - keepRecent);
124029
124345
  if (toDegrade === 0) return messages;
@@ -124034,7 +124350,7 @@ function degradeOlderMediaParts(messages, keepRecent) {
124034
124350
  toDegrade -= 1;
124035
124351
  return {
124036
124352
  type: "text",
124037
- text: MEDIA_DEGRADED_PLACEHOLDERS[part.type]
124353
+ text: placeholders[part.type]
124038
124354
  };
124039
124355
  });
124040
124356
  return {
@@ -124043,7 +124359,7 @@ function degradeOlderMediaParts(messages, keepRecent) {
124043
124359
  };
124044
124360
  });
124045
124361
  }
124046
- var SYNTHETIC_TOOL_RESULT_TEXT, MEDIA_DEGRADED_PLACEHOLDERS;
124362
+ var SYNTHETIC_TOOL_RESULT_TEXT, MEDIA_DEGRADED_PLACEHOLDERS, MEDIA_STRIPPED_PLACEHOLDERS;
124047
124363
  var init_projector = __esmMin((() => {
124048
124364
  init_errors$8();
124049
124365
  init_tool_result_render();
@@ -124053,6 +124369,11 @@ var init_projector = __esmMin((() => {
124053
124369
  audio_url: "[audio omitted: dropped to fit the provider request size limit; re-read the file to hear it]",
124054
124370
  video_url: "[video omitted: dropped to fit the provider request size limit; re-read the file to view it]"
124055
124371
  };
124372
+ MEDIA_STRIPPED_PLACEHOLDERS = {
124373
+ image_url: "[image omitted: the provider rejected this image; re-read the file for conversion instructions]",
124374
+ audio_url: "[audio omitted: dropped along with a rejected image; re-read the file to hear it]",
124375
+ video_url: "[video omitted: dropped along with a rejected image; re-read the file to view it]"
124376
+ };
124056
124377
  }));
124057
124378
  //#endregion
124058
124379
  //#region ../../packages/agent-core/src/agent/context/types.ts
@@ -124452,6 +124773,9 @@ var init_context$2 = __esmMin((() => {
124452
124773
  get mediaDegradedMessages() {
124453
124774
  return degradeOlderMediaParts(this.messages, 2);
124454
124775
  }
124776
+ get mediaStrippedMessages() {
124777
+ return degradeOlderMediaParts(this.messages, 0, MEDIA_STRIPPED_PLACEHOLDERS);
124778
+ }
124455
124779
  useProjectedHistoryFrom(source) {
124456
124780
  this.clear();
124457
124781
  this.pushHistory(...trimTrailingOpenToolExchange(source.project(source.history)));
@@ -137278,7 +137602,7 @@ var init_tool_call = __esmMin((() => {
137278
137602
  * does not lose model usage that was already spent.
137279
137603
  */
137280
137604
  async function executeLoopStep(deps) {
137281
- const { turnId, signal, buildMessages, buildMessagesStrict, buildMessagesMediaDegraded, dispatchEvent, llm, tools, buildTools, describeMissingTool, hooks, log, currentStep, maxRetryAttempts, recordUsage } = deps;
137605
+ const { turnId, signal, buildMessages, buildMessagesStrict, buildMessagesMediaDegraded, buildMessagesMediaStripped, dispatchEvent, llm, tools, buildTools, describeMissingTool, hooks, log, currentStep, maxRetryAttempts, recordUsage } = deps;
137282
137606
  if (hooks?.beforeStep !== void 0) {
137283
137607
  const beforeStep = await hooks.beforeStep({
137284
137608
  turnId,
@@ -137333,6 +137657,7 @@ async function executeLoopStep(deps) {
137333
137657
  };
137334
137658
  let response;
137335
137659
  let mediaDegradedResendUsed = false;
137660
+ let mediaStrippedResendUsed = false;
137336
137661
  try {
137337
137662
  response = await chatWithRetry({
137338
137663
  ...retryInput,
@@ -137367,6 +137692,34 @@ async function executeLoopStep(deps) {
137367
137692
  }
137368
137693
  mediaDegradedResendUsed = true;
137369
137694
  log?.info("recovered after media-degraded resend", { turnStep: `${turnId}.${String(currentStep)}` });
137695
+ } else if (buildMessagesMediaStripped !== void 0 && isImageFormatError(error)) {
137696
+ signal.throwIfAborted();
137697
+ log?.warn("provider rejected an image in the request; resending with all media stripped", {
137698
+ turnStep: `${turnId}.${String(currentStep)}`,
137699
+ model: llm.modelName
137700
+ });
137701
+ const strippedMessages = await buildMessagesMediaStripped();
137702
+ signal.throwIfAborted();
137703
+ try {
137704
+ response = await chatWithRetry({
137705
+ ...retryInput,
137706
+ params: {
137707
+ ...chatParams,
137708
+ messages: strippedMessages,
137709
+ requestLogFields: { projection: "media-stripped" }
137710
+ }
137711
+ });
137712
+ } catch (strippedError) {
137713
+ log?.error("media-stripped resend still rejected by provider", {
137714
+ turnStep: `${turnId}.${String(currentStep)}`,
137715
+ model: llm.modelName,
137716
+ originalError: errorMessage$5(error),
137717
+ strippedError: errorMessage$5(strippedError)
137718
+ });
137719
+ throw strippedError;
137720
+ }
137721
+ mediaStrippedResendUsed = true;
137722
+ log?.info("recovered after media-stripped resend", { turnStep: `${turnId}.${String(currentStep)}` });
137370
137723
  } else if (buildMessagesStrict !== void 0 && isRecoverableRequestStructureError(error)) {
137371
137724
  signal.throwIfAborted();
137372
137725
  log?.warn("provider rejected request structure; resending with strict projection", {
@@ -137436,7 +137789,8 @@ async function executeLoopStep(deps) {
137436
137789
  return {
137437
137790
  usage,
137438
137791
  stopReason: stopTurnAfterStep && effectiveStopReason === "tool_use" ? "end_turn" : effectiveStopReason,
137439
- mediaDegradedResendUsed
137792
+ mediaDegradedResendUsed,
137793
+ mediaStrippedResendUsed
137440
137794
  };
137441
137795
  }
137442
137796
  /**
@@ -137535,12 +137889,13 @@ var init_turn_step = __esmMin((() => {
137535
137889
  //#endregion
137536
137890
  //#region ../../packages/agent-core/src/loop/run-turn.ts
137537
137891
  async function runTurn(input) {
137538
- const { turnId, signal, llm, buildMessages, buildMessagesStrict, buildMessagesMediaDegraded, dispatchEvent, tools, buildTools, describeMissingTool, hooks, log, maxSteps, maxRetryAttempts, recordStepUsage: hostRecordStepUsage } = input;
137892
+ const { turnId, signal, llm, buildMessages, buildMessagesStrict, buildMessagesMediaDegraded, buildMessagesMediaStripped, dispatchEvent, tools, buildTools, describeMissingTool, hooks, log, maxSteps, maxRetryAttempts, recordStepUsage: hostRecordStepUsage } = input;
137539
137893
  let usage = emptyUsage();
137540
137894
  let steps = 0;
137541
137895
  let stopReason = "end_turn";
137542
137896
  let activeStep;
137543
137897
  let mediaDegradedActive = false;
137898
+ let mediaStrippedActive = false;
137544
137899
  const recordStepUsage = async (stepUsage) => {
137545
137900
  usage = addUsage$1(usage, stepUsage);
137546
137901
  return hostRecordStepUsage?.(stepUsage);
@@ -137554,9 +137909,10 @@ async function runTurn(input) {
137554
137909
  const stepResult = await executeLoopStep({
137555
137910
  turnId,
137556
137911
  signal,
137557
- buildMessages: mediaDegradedActive && buildMessagesMediaDegraded !== void 0 ? buildMessagesMediaDegraded : buildMessages,
137912
+ buildMessages: mediaStrippedActive && buildMessagesMediaStripped !== void 0 ? buildMessagesMediaStripped : mediaDegradedActive && buildMessagesMediaDegraded !== void 0 ? buildMessagesMediaDegraded : buildMessages,
137558
137913
  buildMessagesStrict,
137559
137914
  buildMessagesMediaDegraded,
137915
+ buildMessagesMediaStripped,
137560
137916
  dispatchEvent,
137561
137917
  llm,
137562
137918
  tools,
@@ -137570,6 +137926,7 @@ async function runTurn(input) {
137570
137926
  });
137571
137927
  activeStep = void 0;
137572
137928
  mediaDegradedActive = mediaDegradedActive || stepResult.mediaDegradedResendUsed === true;
137929
+ mediaStrippedActive = mediaStrippedActive || stepResult.mediaStrippedResendUsed === true;
137573
137930
  if (stepResult.stopReason === "tool_use") continue;
137574
137931
  const terminalStopReason = stepResult.stopReason;
137575
137932
  stopReason = terminalStopReason;
@@ -143796,10 +144153,16 @@ function convertMCPContentBlock(block) {
143796
144153
  }
143797
144154
  if (block.type === "resource_link" && typeof block.uri === "string") {
143798
144155
  const mimeType = block.mimeType ?? "application/octet-stream";
143799
- if (mimeType.startsWith("image/")) return {
143800
- type: "image_url",
143801
- imageUrl: { url: block.uri }
143802
- };
144156
+ if (mimeType.startsWith("image/")) {
144157
+ if (!isModelAcceptedImageMime(mimeType)) return {
144158
+ type: "text",
144159
+ text: buildUnsupportedImageNotice(mimeType, block.uri)
144160
+ };
144161
+ return {
144162
+ type: "image_url",
144163
+ imageUrl: { url: block.uri }
144164
+ };
144165
+ }
143803
144166
  if (mimeType.startsWith("audio/")) return {
143804
144167
  type: "audio_url",
143805
144168
  audioUrl: { url: block.uri }
@@ -143980,6 +144343,7 @@ function collapseSingleText(parts) {
143980
144343
  var MCP_MAX_OUTPUT_CHARS, MCP_OUTPUT_TRUNCATED_TEXT, MCP_MAX_BINARY_PART_BYTES, MCP_MAX_BINARY_PART_CHARS;
143981
144344
  var init_output = __esmMin((() => {
143982
144345
  init_image_compress();
144346
+ init_image_format_policy();
143983
144347
  init_image_originals();
143984
144348
  MCP_MAX_OUTPUT_CHARS = 1e5;
143985
144349
  MCP_OUTPUT_TRUNCATED_TEXT = `\n\n[Output truncated: exceeded ${String(MCP_MAX_OUTPUT_CHARS)} character limit. Use pagination or more specific queries to get remaining content.]`;
@@ -152113,25 +152477,6 @@ function buildMediaNote(input) {
152113
152477
  parts.push("If you generate or edit images or videos via commands or scripts, read the result back immediately before continuing.");
152114
152478
  return `<system>${parts.join(" ")}</system>`;
152115
152479
  }
152116
- /**
152117
- * Refusal message for HEIC/HEIF with a conversion command matching the
152118
- * execution environment (`kaos.osEnv.osKind` — where Bash actually runs, so
152119
- * SSH/container sessions get the right command too). macOS converts with the
152120
- * built-in `sips`; Linux and Windows have no built-in HEIC decoder, so the
152121
- * guidance names the common tools and how to get them.
152122
- */
152123
- function buildHeicConversionGuidance(path, mimeType, osKind) {
152124
- const converted = path.replace(/\.[^./\\]+$/, "") + ".jpg";
152125
- return `"${path}" is a ${mimeType} image, which the provider does not accept. Convert it to JPEG first, then read the converted file. ` + heicConversionCommand(path, converted, osKind);
152126
- }
152127
- function heicConversionCommand(path, converted, osKind) {
152128
- switch (osKind) {
152129
- case "macOS": return `On macOS: sips -s format jpeg "${path}" --out "${converted}"`;
152130
- case "Linux": return `On Linux: heif-convert "${path}" "${converted}" (package libheif-examples), or with ImageMagick: magick "${path}" "${converted}"`;
152131
- case "Windows": return `On Windows, with ImageMagick: magick "${path}" "${converted}" (install it first if missing: winget install ImageMagick.ImageMagick)`;
152132
- default: return `Options: sips -s format jpeg "${path}" --out "${converted}" (macOS), heif-convert "${path}" "${converted}" (Linux, package libheif-examples), or magick "${path}" "${converted}" (ImageMagick)`;
152133
- }
152134
- }
152135
152480
  var MAX_MEDIA_MEGABYTES, MAX_MEDIA_BYTES, ReadMediaFileInputSchema, ReadMediaFileTool;
152136
152481
  var init_read_media = __esmMin((() => {
152137
152482
  init_zod$1();
@@ -152140,6 +152485,7 @@ var init_read_media = __esmMin((() => {
152140
152485
  init_path_access();
152141
152486
  init_file_type$1();
152142
152487
  init_image_compress();
152488
+ init_image_format_policy();
152143
152489
  init_image_limits();
152144
152490
  init_input_schema();
152145
152491
  init_rule_match();
@@ -152225,9 +152571,9 @@ var init_read_media = __esmMin((() => {
152225
152571
  isError: true,
152226
152572
  output: "The current model does not support image input. Tell the user to use a model with image input capability."
152227
152573
  };
152228
- if (fileType.mimeType === "image/heic" || fileType.mimeType === "image/heif") return {
152574
+ if (fileType.kind === "image" && !isModelAcceptedImageMime(fileType.mimeType)) return {
152229
152575
  isError: true,
152230
- output: buildHeicConversionGuidance(args.path, fileType.mimeType, this.kaos.osEnv.osKind)
152576
+ output: buildImageConversionGuidance(args.path, fileType.mimeType, this.kaos.osEnv.osKind)
152231
152577
  };
152232
152578
  if (fileType.kind === "video" && !this.capabilities.video_in) return {
152233
152579
  isError: true,
@@ -154723,6 +155069,7 @@ var init_turn = __esmMin((() => {
154723
155069
  init_errors$8();
154724
155070
  init_errors$4();
154725
155071
  init_loop();
155072
+ init_image_compress();
154726
155073
  init_abort();
154727
155074
  init_context$2();
154728
155075
  init_hooks();
@@ -154792,27 +155139,29 @@ var init_turn = __esmMin((() => {
154792
155139
  return this.agent.homedir ? basename$2(this.agent.homedir) : this.agent.type;
154793
155140
  }
154794
155141
  prompt(input, origin = USER_PROMPT_ORIGIN) {
155142
+ const gated = gateImageFormatParts(input);
154795
155143
  this.agent.records.logRecord({
154796
155144
  type: "turn.prompt",
154797
- input,
155145
+ input: gated,
154798
155146
  origin
154799
155147
  });
154800
- return this.launch(input, origin);
155148
+ return this.launch(gated, origin);
154801
155149
  }
154802
155150
  steer(input, origin = USER_PROMPT_ORIGIN) {
155151
+ const gated = gateImageFormatParts(input);
154803
155152
  this.agent.records.logRecord({
154804
155153
  type: "turn.steer",
154805
- input,
155154
+ input: gated,
154806
155155
  origin
154807
155156
  });
154808
155157
  if (this.activeTurn || this.agent.fullCompaction.isCompacting) {
154809
155158
  this.steerBuffer.push({
154810
- input,
155159
+ input: gated,
154811
155160
  origin
154812
155161
  });
154813
155162
  return null;
154814
155163
  }
154815
- return this.launch(input, origin);
155164
+ return this.launch(gated, origin);
154816
155165
  }
154817
155166
  retry(trigger) {
154818
155167
  return this.prompt([], {
@@ -155245,6 +155594,7 @@ var init_turn = __esmMin((() => {
155245
155594
  buildMessages: () => this.agent.context.messages,
155246
155595
  buildMessagesStrict: () => this.agent.context.strictMessages,
155247
155596
  buildMessagesMediaDegraded: () => this.agent.context.mediaDegradedMessages,
155597
+ buildMessagesMediaStripped: () => this.agent.context.mediaStrippedMessages,
155248
155598
  dispatchEvent: this.buildDispatchEvent(turnId),
155249
155599
  buildTools: () => this.agent.tools.loopTools,
155250
155600
  describeMissingTool: (name) => this.agent.tools.missingToolMessage(name),
@@ -218888,6 +219238,7 @@ var init_src = __esmMin((() => {
218888
219238
  init_context$2();
218889
219239
  init_tool_result_render();
218890
219240
  init_image_compress();
219241
+ init_image_format_policy();
218891
219242
  init_image_limits();
218892
219243
  init_image_originals();
218893
219244
  init_provider_manager();
@@ -240329,6 +240680,9 @@ function isHideOutputMarker(value) {
240329
240680
  * Convert an array of ACP {@link ContentBlock}s into the SDK's
240330
240681
  * {@link PromptPart} array.
240331
240682
  *
240683
+ * Image parts are built from the client-declared MIME verbatim; run the
240684
+ * result through {@link compressPromptImageParts} before submitting so
240685
+ * unsupported formats are dropped and MIME aliases canonicalized.
240332
240686
  */
240333
240687
  function acpBlocksToPromptParts(blocks) {
240334
240688
  const out = [];
@@ -240394,6 +240748,13 @@ function acpBlocksToPromptParts(blocks) {
240394
240748
  * server's upload-time step. Best effort: a part that cannot be compressed is
240395
240749
  * passed through unchanged.
240396
240750
  *
240751
+ * The format gate (`gateImageFormatParts`) runs first: parts whose MIME is
240752
+ * outside the provider-accepted set are never forwarded — the part is
240753
+ * dropped and a text notice stands in, so one unsupported image cannot
240754
+ * poison the session history; accepted MIME aliases (`image/jpg`,
240755
+ * case/whitespace variants) are rewritten to the canonical form strict
240756
+ * provider whitelists require.
240757
+ *
240397
240758
  * Compression is never silent: a re-encoded image gains a caption text part
240398
240759
  * immediately before it stating what the original was, and the original bytes
240399
240760
  * are persisted (into `originalsDir` — typically the session's
@@ -240402,7 +240763,7 @@ function acpBlocksToPromptParts(blocks) {
240402
240763
  */
240403
240764
  async function compressPromptImageParts(parts, options = {}) {
240404
240765
  const out = [];
240405
- for (const part of parts) {
240766
+ for (const part of gateImageFormatParts(parts)) {
240406
240767
  if (part.type === "image_url") {
240407
240768
  const parsed = parseImageDataUrl(part.imageUrl.url);
240408
240769
  if (parsed !== null) {
@@ -240448,14 +240809,6 @@ async function compressPromptImageParts(parts, options = {}) {
240448
240809
  }
240449
240810
  return out;
240450
240811
  }
240451
- function parseImageDataUrl(url) {
240452
- const match = /^data:([^;,]+);base64,(.*)$/s.exec(url);
240453
- if (match === null) return null;
240454
- return {
240455
- mimeType: match[1],
240456
- base64: match[2]
240457
- };
240458
- }
240459
240812
  /**
240460
240813
  * Minimum-viable XML-attribute escaping for prompt-embedded resource
240461
240814
  * wrappers. The output is consumed by an LLM, not parsed by a canonical
@@ -278086,7 +278439,17 @@ async function resolvePromptMediaFiles(body, store, cacheDir, options = {}) {
278086
278439
  const content = [];
278087
278440
  for (const part of body.content) {
278088
278441
  if (part.type === "image" && part.source.kind === "base64") {
278089
- const compressed = await compressBase64ForModel(part.source.data, part.source.media_type, {
278442
+ const effectiveMime = resolveEffectiveImageMime(part.source.media_type, decodeBase64Prefix(part.source.data));
278443
+ if (!isModelAcceptedImageMime(effectiveMime)) {
278444
+ content.push({
278445
+ type: "text",
278446
+ text: buildUnsupportedImageNotice(effectiveMime)
278447
+ });
278448
+ changed = true;
278449
+ continue;
278450
+ }
278451
+ const canonicalMime = normalizeImageMime(effectiveMime);
278452
+ const compressed = await compressBase64ForModel(part.source.data, canonicalMime, {
278090
278453
  maxEdge: options.maxImageEdgePx,
278091
278454
  telemetry: telemetryFor("prompt_inline")
278092
278455
  });
@@ -278120,9 +278483,31 @@ async function resolvePromptMediaFiles(body, store, cacheDir, options = {}) {
278120
278483
  }
278121
278484
  });
278122
278485
  changed = true;
278486
+ } else if (canonicalMime !== part.source.media_type) {
278487
+ content.push({
278488
+ ...part,
278489
+ source: {
278490
+ ...part.source,
278491
+ media_type: canonicalMime
278492
+ }
278493
+ });
278494
+ changed = true;
278123
278495
  } else content.push(part);
278124
278496
  continue;
278125
278497
  }
278498
+ if (part.type === "image" && part.source.kind === "url") {
278499
+ const extMime = unsupportedImageMimeFromUrl(part.source.url);
278500
+ if (extMime !== null) {
278501
+ content.push({
278502
+ type: "text",
278503
+ text: buildUnsupportedImageNotice(extMime, part.source.url)
278504
+ });
278505
+ changed = true;
278506
+ continue;
278507
+ }
278508
+ content.push(part);
278509
+ continue;
278510
+ }
278126
278511
  if (part.type !== "image" && part.type !== "video" || part.source.kind !== "file") {
278127
278512
  content.push(part);
278128
278513
  continue;
@@ -278142,6 +278527,16 @@ async function resolvePromptMediaFiles(body, store, cacheDir, options = {}) {
278142
278527
  let mediaType = file.meta.media_type;
278143
278528
  let bytes = data;
278144
278529
  if (part.type === "image") {
278530
+ mediaType = resolveEffectiveImageMime(mediaType, data);
278531
+ if (!isModelAcceptedImageMime(mediaType)) {
278532
+ content.push({
278533
+ type: "text",
278534
+ text: buildUnsupportedImageNotice(mediaType, file.meta.name)
278535
+ });
278536
+ changed = true;
278537
+ continue;
278538
+ }
278539
+ mediaType = normalizeImageMime(mediaType);
278145
278540
  const compressed = await compressImageForModel(data, mediaType, {
278146
278541
  maxEdge: options.maxImageEdgePx,
278147
278542
  telemetry: telemetryFor("prompt_file")
@@ -370621,6 +371016,7 @@ function runPromptTurn(session, prompt, outputFormat, stdout, stderr, waitForGoa
370621
371016
  return;
370622
371017
  case "turn.step.retrying":
370623
371018
  outputWriter.discardAssistant();
371019
+ outputWriter.writeRetrying(event);
370624
371020
  return;
370625
371021
  case "assistant.delta":
370626
371022
  outputWriter.writeAssistantDelta(event.delta);
@@ -370727,6 +371123,7 @@ var PromptTranscriptWriter = class {
370727
371123
  writeToolCall() {}
370728
371124
  writeToolCallDelta() {}
370729
371125
  writeToolResult() {}
371126
+ writeRetrying() {}
370730
371127
  flushAssistant() {
370731
371128
  this.assistantWriter.finish();
370732
371129
  }
@@ -370813,6 +371210,20 @@ var PromptJsonWriter = class {
370813
371210
  this.assistantText = "";
370814
371211
  this.toolCalls.length = 0;
370815
371212
  }
371213
+ writeRetrying(event) {
371214
+ const message = {
371215
+ role: "meta",
371216
+ type: "turn.step.retrying",
371217
+ failed_attempt: event.failedAttempt,
371218
+ next_attempt: event.nextAttempt,
371219
+ max_attempts: event.maxAttempts,
371220
+ delay_ms: event.delayMs,
371221
+ error_name: event.errorName,
371222
+ error_message: event.errorMessage,
371223
+ status_code: event.statusCode
371224
+ };
371225
+ this.writeJsonLine(message);
371226
+ }
370816
371227
  finish() {
370817
371228
  this.flushAssistant();
370818
371229
  }