@namzu/sdk 52.0.0 → 52.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/compaction/verifier.d.ts.map +1 -1
  3. package/dist/compaction/verifier.js +57 -17
  4. package/dist/compaction/verifier.js.map +1 -1
  5. package/dist/contracts/a2a.d.ts +2 -2
  6. package/dist/provider/capabilities.d.ts +9 -0
  7. package/dist/provider/capabilities.d.ts.map +1 -1
  8. package/dist/provider/capabilities.js +15 -0
  9. package/dist/provider/capabilities.js.map +1 -1
  10. package/dist/provider/errors.d.ts.map +1 -1
  11. package/dist/provider/errors.js +13 -0
  12. package/dist/provider/errors.js.map +1 -1
  13. package/dist/provider/fallback.d.ts.map +1 -1
  14. package/dist/provider/fallback.js +10 -0
  15. package/dist/provider/fallback.js.map +1 -1
  16. package/dist/provider/idle-timeout.d.ts.map +1 -1
  17. package/dist/provider/idle-timeout.js +5 -0
  18. package/dist/provider/idle-timeout.js.map +1 -1
  19. package/dist/provider/image-input-refusal.d.ts +9 -0
  20. package/dist/provider/image-input-refusal.d.ts.map +1 -0
  21. package/dist/provider/image-input-refusal.js +54 -0
  22. package/dist/provider/image-input-refusal.js.map +1 -0
  23. package/dist/provider/index.d.ts +2 -1
  24. package/dist/provider/index.d.ts.map +1 -1
  25. package/dist/provider/index.js +2 -1
  26. package/dist/provider/index.js.map +1 -1
  27. package/dist/provider/retry.d.ts +7 -2
  28. package/dist/provider/retry.d.ts.map +1 -1
  29. package/dist/provider/retry.js +38 -4
  30. package/dist/provider/retry.js.map +1 -1
  31. package/dist/public-runtime.d.ts +1 -1
  32. package/dist/public-runtime.d.ts.map +1 -1
  33. package/dist/public-runtime.js +1 -1
  34. package/dist/public-runtime.js.map +1 -1
  35. package/dist/runtime/query/deferred-sandbox.d.ts +54 -0
  36. package/dist/runtime/query/deferred-sandbox.d.ts.map +1 -0
  37. package/dist/runtime/query/deferred-sandbox.js +75 -0
  38. package/dist/runtime/query/deferred-sandbox.js.map +1 -0
  39. package/dist/runtime/query/executor.d.ts +10 -0
  40. package/dist/runtime/query/executor.d.ts.map +1 -1
  41. package/dist/runtime/query/executor.js +24 -3
  42. package/dist/runtime/query/executor.js.map +1 -1
  43. package/dist/runtime/query/index.d.ts.map +1 -1
  44. package/dist/runtime/query/index.js +93 -25
  45. package/dist/runtime/query/index.js.map +1 -1
  46. package/dist/runtime/query/iteration/index.d.ts +26 -0
  47. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  48. package/dist/runtime/query/iteration/index.js +164 -21
  49. package/dist/runtime/query/iteration/index.js.map +1 -1
  50. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +10 -7
  51. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
  52. package/dist/runtime/query/iteration/phases/compaction-breaker.js +10 -7
  53. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
  54. package/dist/runtime/query/iteration/phases/compaction.d.ts +42 -1
  55. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  56. package/dist/runtime/query/iteration/phases/compaction.js +119 -21
  57. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  58. package/dist/runtime/query/iteration/provider-rejected-image.d.ts +21 -1
  59. package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -1
  60. package/dist/runtime/query/iteration/provider-rejected-image.js +53 -7
  61. package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -1
  62. package/dist/runtime/query/iteration/request-repair.d.ts +108 -0
  63. package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -0
  64. package/dist/runtime/query/iteration/request-repair.js +593 -0
  65. package/dist/runtime/query/iteration/request-repair.js.map +1 -0
  66. package/dist/runtime/query/iteration/stream-turn.d.ts +6 -0
  67. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  68. package/dist/runtime/query/iteration/stream-turn.js +2 -2
  69. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  70. package/dist/runtime/query/request-rich-content.d.ts +5 -1
  71. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  72. package/dist/runtime/query/request-rich-content.js +67 -25
  73. package/dist/runtime/query/request-rich-content.js.map +1 -1
  74. package/dist/tools/builtins/computer-use.d.ts +1 -1
  75. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  76. package/dist/tools/builtins/computer-use.js +12 -5
  77. package/dist/tools/builtins/computer-use.js.map +1 -1
  78. package/dist/types/provider/errors.d.ts.map +1 -1
  79. package/dist/types/provider/errors.js +30 -5
  80. package/dist/types/provider/errors.js.map +1 -1
  81. package/dist/types/provider/interface.d.ts +12 -0
  82. package/dist/types/provider/interface.d.ts.map +1 -1
  83. package/dist/types/provider/stream.d.ts +1 -1
  84. package/dist/types/session/config.d.ts +14 -0
  85. package/dist/types/session/config.d.ts.map +1 -1
  86. package/dist/types/session/events.d.ts +20 -5
  87. package/dist/types/session/events.d.ts.map +1 -1
  88. package/dist/types/session/events.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/compaction/verifier.ts +57 -20
  91. package/src/provider/capabilities.ts +17 -0
  92. package/src/provider/errors.ts +13 -0
  93. package/src/provider/fallback.ts +10 -0
  94. package/src/provider/idle-timeout.ts +5 -0
  95. package/src/provider/image-input-refusal.ts +54 -0
  96. package/src/provider/index.ts +2 -0
  97. package/src/provider/retry.ts +42 -4
  98. package/src/public-runtime.ts +3 -0
  99. package/src/runtime/query/deferred-sandbox.ts +110 -0
  100. package/src/runtime/query/executor.ts +25 -3
  101. package/src/runtime/query/index.ts +124 -49
  102. package/src/runtime/query/iteration/index.ts +188 -23
  103. package/src/runtime/query/iteration/phases/compaction-breaker.ts +10 -8
  104. package/src/runtime/query/iteration/phases/compaction.ts +151 -35
  105. package/src/runtime/query/iteration/provider-rejected-image.ts +71 -5
  106. package/src/runtime/query/iteration/request-repair.ts +743 -0
  107. package/src/runtime/query/iteration/stream-turn.ts +11 -1
  108. package/src/runtime/query/request-rich-content.ts +77 -27
  109. package/src/tools/builtins/computer-use.ts +13 -4
  110. package/src/types/provider/errors.ts +33 -5
  111. package/src/types/provider/interface.ts +13 -0
  112. package/src/types/provider/stream.ts +1 -1
  113. package/src/types/session/config.ts +14 -0
  114. package/src/types/session/events.ts +25 -5
@@ -6,7 +6,7 @@ import { CHARS_PER_TOKEN } from '../../../constants/limits.js';
6
6
  import { DEFAULT_STRUCTURED_OUTPUT_RETRIES, STRUCTURED_OUTPUT_REPROMPT, } from '../../../constants/tools/index.js';
7
7
  import { withStreamedMessageIdentity } from '../../../manager/session/streamed-message-identity.js';
8
8
  import { renderSkillsSection } from '../../../persona/assembler.js';
9
- import { resolveProviderCapabilities } from '../../../provider/capabilities.js';
9
+ import { modelReadsImages, resolveProviderCapabilities } from '../../../provider/capabilities.js';
10
10
  import { collectChatCompletion } from '../../../provider/collect-chat-completion.js';
11
11
  import { renderToolSchema } from '../../../registry/tool/schema.js';
12
12
  import { formatCompletionNotification } from '../../../scheduler/completion-inbox.js';
@@ -21,6 +21,7 @@ import { stableDigest } from '../../../utils/hash.js';
21
21
  import { generateMessageId } from '../../../utils/id.js';
22
22
  import { parseStructuredResultJson } from '../../../utils/structured-result-json.js';
23
23
  import { createCallbackInference } from '../callback-inference.js';
24
+ import { SandboxAcquisitionStopped } from '../deferred-sandbox.js';
24
25
  import { deliverDurableInbound } from '../durable-inbound.js';
25
26
  import { projectObservationContext } from '../observation-context.js';
26
27
  import { applyLifecycleHookResults } from '../plugin-hooks.js';
@@ -32,12 +33,13 @@ import { drainArrivedTaskCompletions, holdForOutstandingWork, settleOutstandingW
32
33
  import { runAdvisoryPhase } from './phases/advisory.js';
33
34
  import { runIterationCheckpoint } from './phases/checkpoint.js';
34
35
  import { compactionBreaker } from './phases/compaction-breaker.js';
35
- import { activeContextWindow, measureContext, relieveOverflow, runCompactionCheck, } from './phases/compaction.js';
36
+ import { AUTOMATIC_COMPACTION_FAILED, OVERFLOW_RELIEF_LIMIT, activeContextWindow, clearCompactionErrorNote, compactionErrorNote, measureContext, relieveOverflow, runCompactionCheck, stopOnRefill, } from './phases/compaction.js';
36
37
  import { runHandoffPause } from './phases/handoff.js';
37
38
  import { runPlanGate } from './phases/plan.js';
38
39
  import { runToolReview } from './phases/tool-review.js';
39
40
  import { refreshWorkingMemory, splitWorkingMemoryForRequest } from './phases/working-memory.js';
40
- import { streamWithProviderRejectedImageRecovery } from './provider-rejected-image.js';
41
+ import { streamWithProviderRejectedImageRecovery, } from './provider-rejected-image.js';
42
+ import { RequestRepair } from './request-repair.js';
41
43
  import { appendWorkContext, beforeStep, prepareStep, selectContextModel, stepContextMessage, } from './step-shaping.js';
42
44
  import { streamProviderTurn } from './stream-turn.js';
43
45
  /** A host reviewer is not the model transport, even when its cause is an HTTP failure. */
@@ -69,6 +71,10 @@ const MIN_OUTPUT_CAP_TOKENS = 3000;
69
71
  // Use the same standard for warning closure and empty-completion recovery.
70
72
  const CLOSING_RESPONSE_GUIDANCE = 'Give a concise response using only what the available evidence supports. Attribute unverified statements to their source instead of presenting them as observed facts. If evidence is missing or conflicting, state what cannot be established. Do not claim unfinished work is complete. Do not request any more tool calls.';
71
73
  export { awaitedJobGraceMs, settleGraceMs } from './outstanding-work.js';
74
+ /** One provider's model, as {@link IterationOrchestrator} remembers it. */
75
+ function textOnlyKey(providerId, model) {
76
+ return `${providerId}\u0000${model}`;
77
+ }
72
78
  export class IterationOrchestrator {
73
79
  ctx;
74
80
  advisoryTurn;
@@ -103,6 +109,68 @@ export class IterationOrchestrator {
103
109
  }
104
110
  /** Rich tool blocks already reported; durable history is scanned every turn. */
105
111
  warnedRichToolResults = new Set();
112
+ /** What this run has learned about requests its provider rejects as invalid, and how to repair them. */
113
+ requestRepair = new RequestRepair({
114
+ debug: (message) => this.ctx.log.debug(message),
115
+ onMediaRemoved: (removal) => this.noteMediaRemoved(removal),
116
+ servedBy: () => this.ctx.servingMember?.(),
117
+ });
118
+ /**
119
+ * Provider and model pairs that refused a request for holding an image; they
120
+ * get none for the rest of the run. Keyed by both: one model id can be served
121
+ * by two members of a fallback chain, and only the one that refused is known.
122
+ */
123
+ textOnlyModels = new Set();
124
+ /**
125
+ * The member a request for `model` goes to first. A fallback chain keeps
126
+ * serving from the member it last moved to, so that member, not the head,
127
+ * is the one whose reading of images counts.
128
+ */
129
+ servingModel(model) {
130
+ const serving = this.ctx.servingMember?.();
131
+ return {
132
+ providerId: serving?.providerId ?? this.ctx.provider.id,
133
+ model: serving?.model ?? model,
134
+ };
135
+ }
136
+ /** Whether a request for `model` may carry images: the driver's catalogue and this run's own evidence agree. */
137
+ modelReadsImages(model) {
138
+ const serving = this.servingModel(model);
139
+ return (!this.textOnlyModels.has(textOnlyKey(serving.providerId, serving.model)) &&
140
+ modelReadsImages(this.ctx.provider, model));
141
+ }
142
+ /** What a request for `model` does when its model turns out to read text only. */
143
+ textOnlyRecovery(model) {
144
+ return {
145
+ servedBy: () => this.servingModel(model),
146
+ onAccepted: (refused) => this.noteTextOnlyModel(refused),
147
+ };
148
+ }
149
+ /**
150
+ * A provider refused a request for holding an image its model cannot read, and
151
+ * the retry without images was accepted. Stop sending images to it, and say so
152
+ * once, so the host can remember the model beyond this run.
153
+ */
154
+ async noteTextOnlyModel(refused) {
155
+ const key = textOnlyKey(refused.providerId, refused.model ?? '');
156
+ if (this.textOnlyModels.has(key))
157
+ return;
158
+ this.textOnlyModels.add(key);
159
+ const { providerId, model } = refused;
160
+ this.ctx.log.warn('Capability mismatch: the model cannot read images', {
161
+ [GENAI.SYSTEM]: providerId,
162
+ [GENAI.REQUEST_MODEL]: model ?? '',
163
+ });
164
+ await this.ctx.emitEvent?.({
165
+ type: 'capability_warning',
166
+ turnId: this.ctx.recorder.turnId,
167
+ capability: 'vision',
168
+ contentSource: 'history',
169
+ providerId,
170
+ ...(model !== undefined ? { model } : {}),
171
+ message: `Model '${model ?? 'unknown'}' on provider '${providerId}' refused a request that held an image, so images are left out of its requests. They stay in the conversation and are sent again to a model that reads them.`,
172
+ });
173
+ }
106
174
  /**
107
175
  * The previous iteration held a `stopWhen` decision open for a worker.
108
176
  *
@@ -330,16 +398,19 @@ export class IterationOrchestrator {
330
398
  // paused it. Taken after the intent above is known, so the
331
399
  // checkpoint names the message the turn is answering.
332
400
  await this.ctx.checkpointMgr.markLoopStart?.(recorder);
333
- // One context-overflow relief per *stuck point*, not per turn.
401
+ // Context-overflow relief is a two-rung ladder per *stuck point*, not per
402
+ // turn, as in the reference terminal (at most two reactive compactions per
403
+ // overflow): the first relief, then one deeper pass if the retry still
404
+ // overflows.
334
405
  //
335
- // The latch exists so that a second overflow immediately after a
336
- // successful compaction — meaning the prompt is irreducible — stops
337
- // instead of looping. It was never meant to disarm the mechanism for
338
- // the rest of the turn, which is what a turn-scoped flag did: one
339
- // relief at iteration 3 left iteration 40 to die on an overflow with
340
- // obvious moves left. It is cleared by a turn that actually
341
- // succeeded, which is the evidence that the turn is no longer stuck.
342
- let overflowRelieved = false;
406
+ // The limit exists so that an overflow that survives both rungs — an
407
+ // irreducible prompt — stops instead of looping. It was never meant to
408
+ // disarm the mechanism for the rest of the turn, which is what a
409
+ // turn-scoped flag did: one relief at iteration 3 left iteration 40 to die
410
+ // on an overflow with obvious moves left. The count is cleared by a turn
411
+ // that actually succeeded, which is the evidence that the turn is no
412
+ // longer stuck.
413
+ let overflowReliefs = 0;
343
414
  // The output cap a provider told us fits beside this prompt. One request's
344
415
  // worth: it is cleared by a call that went through.
345
416
  let outputCapOverride;
@@ -662,7 +733,7 @@ export class IterationOrchestrator {
662
733
  if (step.context)
663
734
  requestHistory.push(stepContextMessage(step.context));
664
735
  const messages = projectRequestRichContent(this.projectObservations(requestHistory), this.ctx.turnConfig.maxRequestRichContentBytes ??
665
- DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES);
736
+ DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES, { imagesReadable: this.modelReadsImages(stepModel) });
666
737
  appendWorkContext(shaping, messages, iterationNum, step);
667
738
  if (closingDirective)
668
739
  messages.push(closingDirective);
@@ -814,6 +885,8 @@ export class IterationOrchestrator {
814
885
  signal: this.ctx.abortController.signal,
815
886
  }, this.ctx.emitEvent, this.ctx.drainPending, recorder.turnId, iterationNum, forceFinalize, this.ctx.log, iterSpan, stepMessageId, {
816
887
  onAccepted: (identity) => this.acceptProviderRejectedImage(identity),
888
+ textOnly: this.textOnlyRecovery(stepModel),
889
+ repair: this.requestRepair,
817
890
  }, Boolean(this.ctx.reviewAnswer || this.ctx.structuredOutput?.review || this.ctx.advisoryCtx), recorder.sessionId);
818
891
  stepResponse = response;
819
892
  const reviewRequest = {
@@ -859,7 +932,8 @@ export class IterationOrchestrator {
859
932
  });
860
933
  // The turn went through, so the turn is not sitting on an
861
934
  // irreducible prompt any more. Re-arm relief for the next one.
862
- overflowRelieved = false;
935
+ overflowReliefs = 0;
936
+ clearCompactionErrorNote(this.ctx);
863
937
  outputCapOverride = undefined;
864
938
  if (response.finishReason !== 'length')
865
939
  this.outputContinuations = 0;
@@ -1591,6 +1665,12 @@ export class IterationOrchestrator {
1591
1665
  }
1592
1666
  catch (err) {
1593
1667
  const cancelled = this.ctx.abortController.signal.aborted;
1668
+ // A deferred sandbox that was cancelled or ran out of time. The
1669
+ // turn settles it as `cancelled` / `timeout` in `query()`, which is
1670
+ // not a failure of this iteration: it is recorded the way a turn
1671
+ // stopped by its own abort or timeout is (a cancelled step, no
1672
+ // error span), so the ledger and the stop reason agree.
1673
+ const sandboxStopped = err instanceof SandboxAcquisitionStopped;
1594
1674
  // This iteration gets a step too, and it is the one the
1595
1675
  // argument three hundred lines above was actually about.
1596
1676
  //
@@ -1651,7 +1731,7 @@ export class IterationOrchestrator {
1651
1731
  startedAt: stepStartedAt,
1652
1732
  usageBefore,
1653
1733
  costBefore,
1654
- unfinished: cancelled
1734
+ unfinished: cancelled || sandboxStopped
1655
1735
  ? { finishReason: 'cancelled' }
1656
1736
  : {
1657
1737
  finishReason: 'error',
@@ -1706,21 +1786,59 @@ export class IterationOrchestrator {
1706
1786
  continue;
1707
1787
  }
1708
1788
  }
1709
- if (!overflowRelieved &&
1710
- !(err instanceof AnswerReviewFailure) &&
1789
+ if (!(err instanceof AnswerReviewFailure) &&
1711
1790
  classifyProviderError(err, this.ctx.provider.id).code === 'context_length_exceeded') {
1712
- overflowRelieved = true;
1713
- const shed = await relieveOverflow(this.ctx);
1791
+ // The rapid-refill check comes first, before the first reactive relief
1792
+ // of an overflow, as in the reference terminal. A relief that follows
1793
+ // the previous compaction within the refill window counts toward the
1794
+ // limit, so a tool that re-fills the window every time it is called
1795
+ // stops the turn instead of being cleared and called again without end.
1796
+ // The deeper rung of the same overflow is not counted again.
1797
+ if (overflowReliefs === 0 && (await stopOnRefill(this.ctx))) {
1798
+ if (iterationActivity) {
1799
+ this.ctx.activityStore.fail(iterationActivity.id, toErrorMessage(err));
1800
+ }
1801
+ if (this.stopForCompactionBreaker())
1802
+ break;
1803
+ }
1804
+ // A rung that sheds too little to change the verdict is not worth a
1805
+ // provider call; climb to the next one at once.
1806
+ let shed = false;
1807
+ while (!shed && overflowReliefs < OVERFLOW_RELIEF_LIMIT) {
1808
+ overflowReliefs++;
1809
+ shed = await relieveOverflow(this.ctx, { deeper: overflowReliefs > 1 });
1810
+ }
1714
1811
  if (shed) {
1812
+ // Clearing results is a compaction too: only summaries and reducers
1813
+ // recorded themselves, so a clear-only relief was invisible to the
1814
+ // breaker.
1815
+ compactionBreaker(this.ctx)?.recordCompaction(iterationNum);
1715
1816
  this.ctx.log.info('Retrying the turn after relieving a context overflow', {
1716
1817
  [NAMZU.TURN_ID]: recorder.turnId,
1717
1818
  [NAMZU.ITERATION]: iterationNum,
1819
+ 'namzu.runtime.relief_rung': overflowReliefs,
1718
1820
  });
1719
1821
  if (iterationActivity) {
1720
1822
  this.ctx.activityStore.complete(iterationActivity.id);
1721
1823
  }
1722
1824
  continue;
1723
1825
  }
1826
+ // Out of rungs. The conversation is kept; say why compaction could not
1827
+ // make room when it errored, as the reference does.
1828
+ const cause = compactionErrorNote(this.ctx);
1829
+ if (cause !== undefined && err instanceof Error) {
1830
+ try {
1831
+ err.message = `${err.message}${AUTOMATIC_COMPACTION_FAILED}${cause}`;
1832
+ }
1833
+ catch {
1834
+ // A frozen error keeps its own message; the cause stays in the log.
1835
+ }
1836
+ }
1837
+ }
1838
+ if (sandboxStopped) {
1839
+ if (iterationActivity)
1840
+ this.ctx.activityStore.complete(iterationActivity.id);
1841
+ throw err;
1724
1842
  }
1725
1843
  if (iterationActivity) {
1726
1844
  this.ctx.activityStore.fail(iterationActivity.id, toErrorMessage(err));
@@ -2257,6 +2375,31 @@ export class IterationOrchestrator {
2257
2375
  .listTasks()
2258
2376
  .some((t) => t.state !== 'completed' && t.state !== 'failed' && t.state !== 'canceled');
2259
2377
  }
2378
+ /** Media the provider could not process was left out of a request that then went through. */
2379
+ async noteMediaRemoved(removal) {
2380
+ this.ctx.log.warn('Media the provider rejected was left out of the request', {
2381
+ [GENAI.SYSTEM]: this.ctx.provider.id,
2382
+ 'namzu.request.media_removed': removal.count,
2383
+ });
2384
+ const noun = removal.count === 1 ? 'a file' : `${removal.count} files`;
2385
+ const it = removal.count === 1 ? 'it' : 'they';
2386
+ await this.ctx.emitEvent?.({
2387
+ type: 'capability_warning',
2388
+ turnId: this.ctx.recorder.turnId,
2389
+ capability: removal.kind === 'document' ? 'documents' : 'vision',
2390
+ // The person's own latest attachment is named as such; a count limit is
2391
+ // not a fault in any file.
2392
+ contentSource: removal.current
2393
+ ? 'media-removed-current'
2394
+ : removal.limit
2395
+ ? 'media-limit'
2396
+ : 'media-removed',
2397
+ providerId: this.ctx.provider.id,
2398
+ message: removal.limit
2399
+ ? `The conversation holds more files than the provider accepts at once, so ${noun} (the oldest) ${removal.count === 1 ? 'was' : 'were'} left out of the request. The conversation still holds ${it}.`
2400
+ : `The provider could not process ${noun}${removal.current ? ' attached to your latest message' : ' in this conversation'}, so ${it} ${removal.count === 1 ? 'was' : 'were'} left out of the request. The conversation still holds ${it}.`,
2401
+ });
2402
+ }
2260
2403
  async acceptProviderRejectedImage(identity) {
2261
2404
  const repaired = markProviderRejectedImage(this.ctx.recorder.messages, identity);
2262
2405
  if (repaired.count === 0) {
@@ -2301,7 +2444,7 @@ export class IterationOrchestrator {
2301
2444
  ...workingMemory.history,
2302
2445
  ...(workingMemory.context ? [workingMemory.context] : []),
2303
2446
  ];
2304
- const finalMessages = projectRequestRichContent(this.projectObservations(finalHistory), this.ctx.turnConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES);
2447
+ const finalMessages = projectRequestRichContent(this.projectObservations(finalHistory), this.ctx.turnConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES, { imagesReadable: this.modelReadsImages(model) });
2305
2448
  appendWorkContext(this.stepShaping(), finalMessages, this.steps.length + 1, { model });
2306
2449
  // The closing directive is the last thing the model reads: after
2307
2450
  // the working memory and any work context above.
@@ -2340,7 +2483,7 @@ export class IterationOrchestrator {
2340
2483
  // stream to completion.
2341
2484
  signal: this.ctx.abortController.signal,
2342
2485
  };
2343
- const response = await collectChatCompletion(streamWithProviderRejectedImageRecovery(this.ctx.provider, finalParams, (identity) => this.acceptProviderRejectedImage(identity)));
2486
+ const response = await collectChatCompletion(streamWithProviderRejectedImageRecovery(this.ctx.provider, finalParams, (identity) => this.acceptProviderRejectedImage(identity), undefined, this.textOnlyRecovery(model), this.requestRepair));
2344
2487
  const servingMember = this.ctx.servingMember?.() ?? requestedMember;
2345
2488
  const servedBy = {
2346
2489
  providerId: servingMember.providerId,