@namzu/sdk 62.4.0 → 62.5.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 (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +3 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/runAgent.d.ts +3 -1
  6. package/dist/agents/runAgent.d.ts.map +1 -1
  7. package/dist/agents/runAgent.js +1 -0
  8. package/dist/agents/runAgent.js.map +1 -1
  9. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  10. package/dist/bridge/a2a/mapper.js +5 -3
  11. package/dist/bridge/a2a/mapper.js.map +1 -1
  12. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  13. package/dist/pricing/catalogue.generated.js +5 -0
  14. package/dist/pricing/catalogue.generated.js.map +1 -1
  15. package/dist/provider/fallback.d.ts.map +1 -1
  16. package/dist/provider/fallback.js +23 -1
  17. package/dist/provider/fallback.js.map +1 -1
  18. package/dist/public-runtime.d.ts +1 -1
  19. package/dist/public-runtime.d.ts.map +1 -1
  20. package/dist/public-runtime.js +1 -1
  21. package/dist/public-runtime.js.map +1 -1
  22. package/dist/runtime/query/index.d.ts +21 -0
  23. package/dist/runtime/query/index.d.ts.map +1 -1
  24. package/dist/runtime/query/index.js +4 -0
  25. package/dist/runtime/query/index.js.map +1 -1
  26. package/dist/runtime/query/iteration/index.d.ts +10 -0
  27. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  28. package/dist/runtime/query/iteration/index.js +59 -6
  29. package/dist/runtime/query/iteration/index.js.map +1 -1
  30. package/dist/runtime/query/iteration/phases/context.d.ts +4 -0
  31. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  32. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  33. package/dist/runtime/query/iteration/step-shaping.d.ts.map +1 -1
  34. package/dist/runtime/query/iteration/step-shaping.js +3 -0
  35. package/dist/runtime/query/iteration/step-shaping.js.map +1 -1
  36. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  37. package/dist/runtime/query/prepare-turn.js +5 -2
  38. package/dist/runtime/query/prepare-turn.js.map +1 -1
  39. package/dist/runtime/query/request-rich-content.d.ts +7 -1
  40. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  41. package/dist/runtime/query/request-rich-content.js +8 -2
  42. package/dist/runtime/query/request-rich-content.js.map +1 -1
  43. package/dist/turn/index.d.ts +1 -1
  44. package/dist/turn/index.d.ts.map +1 -1
  45. package/dist/turn/index.js +1 -1
  46. package/dist/turn/index.js.map +1 -1
  47. package/dist/turn/memory-recall.d.ts +13 -0
  48. package/dist/turn/memory-recall.d.ts.map +1 -1
  49. package/dist/turn/memory-recall.js +38 -1
  50. package/dist/turn/memory-recall.js.map +1 -1
  51. package/dist/types/agent/base.d.ts +2 -0
  52. package/dist/types/agent/base.d.ts.map +1 -1
  53. package/dist/types/agent/query.d.ts +4 -0
  54. package/dist/types/agent/query.d.ts.map +1 -1
  55. package/dist/types/common/index.d.ts +7 -0
  56. package/dist/types/common/index.d.ts.map +1 -1
  57. package/dist/types/common/index.js +3 -0
  58. package/dist/types/common/index.js.map +1 -1
  59. package/dist/types/provider/chat.d.ts +10 -0
  60. package/dist/types/provider/chat.d.ts.map +1 -1
  61. package/dist/types/provider/config.d.ts +8 -0
  62. package/dist/types/provider/config.d.ts.map +1 -1
  63. package/dist/types/provider/index.d.ts +1 -1
  64. package/dist/types/provider/index.d.ts.map +1 -1
  65. package/dist/types/session/config.d.ts +10 -0
  66. package/dist/types/session/config.d.ts.map +1 -1
  67. package/dist/types/session/events.d.ts +8 -1
  68. package/dist/types/session/events.d.ts.map +1 -1
  69. package/dist/types/session/events.js.map +1 -1
  70. package/dist/types/session/prepare-step.d.ts +7 -0
  71. package/dist/types/session/prepare-step.d.ts.map +1 -1
  72. package/dist/utils/cost.d.ts +7 -0
  73. package/dist/utils/cost.d.ts.map +1 -1
  74. package/dist/utils/cost.js +15 -0
  75. package/dist/utils/cost.js.map +1 -1
  76. package/package.json +1 -1
  77. package/src/agents/QueryAgent.ts +3 -0
  78. package/src/agents/runAgent.ts +9 -1
  79. package/src/bridge/a2a/mapper.ts +5 -3
  80. package/src/pricing/catalogue.generated.ts +5 -0
  81. package/src/pricing/rates.source.json +15 -5
  82. package/src/provider/fallback.ts +33 -1
  83. package/src/public-runtime.ts +1 -1
  84. package/src/runtime/query/index.ts +27 -0
  85. package/src/runtime/query/iteration/index.ts +70 -5
  86. package/src/runtime/query/iteration/phases/context.ts +4 -0
  87. package/src/runtime/query/iteration/step-shaping.ts +3 -0
  88. package/src/runtime/query/prepare-turn.ts +6 -2
  89. package/src/runtime/query/request-rich-content.ts +11 -2
  90. package/src/turn/index.ts +1 -1
  91. package/src/turn/memory-recall.ts +40 -0
  92. package/src/types/agent/base.ts +2 -0
  93. package/src/types/agent/query.ts +4 -0
  94. package/src/types/common/index.ts +11 -0
  95. package/src/types/provider/chat.ts +12 -0
  96. package/src/types/provider/config.ts +8 -0
  97. package/src/types/provider/index.ts +1 -0
  98. package/src/types/session/config.ts +11 -0
  99. package/src/types/session/events.ts +8 -1
  100. package/src/types/session/prepare-step.ts +7 -0
  101. package/src/utils/cost.ts +23 -0
@@ -13,7 +13,12 @@ import type { AuthorizationGateConfig } from '../types/authorization/index.js'
13
13
  import type { InputGuardrailSpec, OutputGuardrailSpec } from '../types/guardrail/index.js'
14
14
  import type { ProjectId, SessionId, TenantId, TopicId } from '../types/ids/index.js'
15
15
  import type { Message } from '../types/message/index.js'
16
- import type { LLMProvider, ReasoningEffort, ThinkingConfig } from '../types/provider/index.js'
16
+ import type {
17
+ InferenceSpeed,
18
+ LLMProvider,
19
+ ReasoningEffort,
20
+ ThinkingConfig,
21
+ } from '../types/provider/index.js'
17
22
  import type { SandboxProvider } from '../types/sandbox/index.js'
18
23
  import type { TurnConfig } from '../types/session/config.js'
19
24
  import type { SessionEventListener } from '../types/session/events.js'
@@ -202,6 +207,8 @@ export interface RunAgentOptions extends AgentIdentity {
202
207
  */
203
208
  thinking?: ThinkingConfig
204
209
  effort?: ReasoningEffort
210
+ /** Inference speed for every model call; see `TurnConfig.speed`. */
211
+ speed?: InferenceSpeed
205
212
 
206
213
  /** Names the agent in traces and events. Defaults to `Agent`. */
207
214
  name?: string
@@ -295,6 +302,7 @@ const RUN_AGENT_OPTION_ROUTES = {
295
302
  structuredOutput: 'query',
296
303
  thinking: 'special',
297
304
  effort: 'special',
305
+ speed: 'turn',
298
306
  name: 'special',
299
307
  signal: 'query',
300
308
  listener: 'special',
@@ -152,9 +152,11 @@ const MAPPING: {
152
152
  text:
153
153
  e.outcome === 'fallback' && e.fallbackModel
154
154
  ? `${e.model} declined to answer; continuing on ${e.fallbackModel}`
155
- : e.outcome === 'retry'
156
- ? `${e.model} stopped its reply; continuing once with that noted`
157
- : `${e.model} declined to answer`,
155
+ : e.outcome === 'withheld'
156
+ ? `${e.model} declined to answer; continuing once without the recorded context it carried`
157
+ : e.outcome === 'retry'
158
+ ? `${e.model} stopped its reply; continuing once with that noted`
159
+ : `${e.model} declined to answer`,
158
160
  },
159
161
  ],
160
162
  }),
@@ -49,6 +49,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
49
49
  inputCostPer1M: 8,
50
50
  outputCostPer1M: 40,
51
51
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 10 },
52
+ fastMultiplier: 2,
52
53
  },
53
54
  ],
54
55
  [
@@ -81,6 +82,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
81
82
  inputCostPer1M: 8,
82
83
  outputCostPer1M: 40,
83
84
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 10 },
85
+ fastMultiplier: 2,
84
86
  },
85
87
  ],
86
88
  [
@@ -121,6 +123,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
121
123
  inputCostPer1M: 5,
122
124
  outputCostPer1M: 25,
123
125
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.5, writeCostPer1M: 6.25 },
126
+ fastMultiplier: 2,
124
127
  },
125
128
  ],
126
129
  [
@@ -129,6 +132,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
129
132
  inputCostPer1M: 5,
130
133
  outputCostPer1M: 25,
131
134
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.5, writeCostPer1M: 6.25 },
135
+ fastMultiplier: 2,
132
136
  },
133
137
  ],
134
138
  [
@@ -137,6 +141,7 @@ export const VENDOR_RATES: readonly VendorRates[] = [
137
141
  inputCostPer1M: 4,
138
142
  outputCostPer1M: 20,
139
143
  cache: { promptIncludesCacheReads: false, readCostPer1M: 0.2, writeCostPer1M: 5 },
144
+ fastMultiplier: 2,
140
145
  },
141
146
  ],
142
147
  [
@@ -19,6 +19,11 @@
19
19
  "inference. Its turns are priced at zero, which is KNOWN-free and therefore",
20
20
  "distinct from unknown. Such a vendor needs no model rows.",
21
21
  "",
22
+ "fastMultiplier (optional) is the vendor's published multiple of every rate for",
23
+ "a request served in fast mode (verified 2026-11-10 against the fast-mode page:",
24
+ "2x on the full context window, stacking with cache pricing). A model without it",
25
+ "has no published fast price, so a fast request on it is reported as unpriced.",
26
+ "",
22
27
  "Matching is EXACT on a normalised id (lowercased, one trailing -YYYYMMDD or",
23
28
  "@YYYYMMDD snapshot suffix removed). Deliberately not prefix or substring:",
24
29
  "within one vendor here, ids one character apart differ in price by 4x, so a",
@@ -66,14 +71,16 @@
66
71
  "inputPer1M": 8,
67
72
  "outputPer1M": 40,
68
73
  "cacheReadPer1M": 0.2,
69
- "cacheWritePer1M": 10
74
+ "cacheWritePer1M": 10,
75
+ "fastMultiplier": 2
70
76
  },
71
77
  {
72
78
  "id": "claude-mythos-5-2",
73
79
  "inputPer1M": 8,
74
80
  "outputPer1M": 40,
75
81
  "cacheReadPer1M": 0.2,
76
- "cacheWritePer1M": 10
82
+ "cacheWritePer1M": 10,
83
+ "fastMultiplier": 2
77
84
  },
78
85
  {
79
86
  "id": "claude-fable-5-1",
@@ -94,7 +101,8 @@
94
101
  "inputPer1M": 4,
95
102
  "outputPer1M": 20,
96
103
  "cacheReadPer1M": 0.2,
97
- "cacheWritePer1M": 5
104
+ "cacheWritePer1M": 5,
105
+ "fastMultiplier": 2
98
106
  },
99
107
  {
100
108
  "id": "claude-sonnet-5-5",
@@ -122,14 +130,16 @@
122
130
  "inputPer1M": 5,
123
131
  "outputPer1M": 25,
124
132
  "cacheReadPer1M": 0.5,
125
- "cacheWritePer1M": 6.25
133
+ "cacheWritePer1M": 6.25,
134
+ "fastMultiplier": 2
126
135
  },
127
136
  {
128
137
  "id": "claude-opus-4-8",
129
138
  "inputPer1M": 5,
130
139
  "outputPer1M": 25,
131
140
  "cacheReadPer1M": 0.5,
132
- "cacheWritePer1M": 6.25
141
+ "cacheWritePer1M": 6.25,
142
+ "fastMultiplier": 2
133
143
  },
134
144
  {
135
145
  "id": "claude-opus-4-7",
@@ -22,10 +22,12 @@
22
22
  */
23
23
 
24
24
  import { GENAI } from '../constants/telemetry/index.js'
25
+ import { DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES } from '../runtime/query/request-rich-content.js'
25
26
  import { classifyProviderError, isAbortError } from '../types/provider/errors.js'
26
27
  import type {
27
28
  ChatCompletionParams,
28
29
  LLMProvider,
30
+ ProviderCapabilities,
29
31
  ReasoningEffort,
30
32
  StreamChunk,
31
33
  ThinkingConfig,
@@ -513,7 +515,7 @@ export function withProviderFallback(
513
515
  return first.provider.name
514
516
  },
515
517
  get capabilities() {
516
- return first.provider.capabilities
518
+ return chainCapabilities(members, first.provider.capabilities)
517
519
  },
518
520
  chatStream,
519
521
  ...(first.provider.listModels
@@ -604,3 +606,33 @@ export function withProviderFallback(
604
606
  : {}),
605
607
  } as LLMProvider
606
608
  }
609
+
610
+ /**
611
+ * The head's declarations, with the request rich-content budget narrowed to
612
+ * what EVERY member can take: a request is built once and may be served by any
613
+ * of them, so a head with a large budget must not send a fallback more than it
614
+ * accepts. A member that declares nothing counts as the kernel default. When
615
+ * no member declares one the head's capabilities are returned untouched.
616
+ */
617
+ function chainCapabilities(
618
+ members: readonly { readonly provider: Pick<LLMProvider, 'capabilities'> }[],
619
+ head: ProviderCapabilities | undefined,
620
+ ): ProviderCapabilities | undefined {
621
+ if (!members.some((m) => m.provider.capabilities?.maxRequestRichContentBytes !== undefined)) {
622
+ return head
623
+ }
624
+ const budget = Math.min(
625
+ ...members.map(
626
+ (m) =>
627
+ m.provider.capabilities?.maxRequestRichContentBytes ??
628
+ DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
629
+ ),
630
+ )
631
+ return {
632
+ supportsTools: true,
633
+ supportsStreaming: true,
634
+ supportsFunctionCalling: true,
635
+ ...head,
636
+ maxRequestRichContentBytes: budget,
637
+ }
638
+ }
@@ -478,7 +478,7 @@ export type {
478
478
  // did nothing.
479
479
  export { SESSION_MEMORY_TAG, createMemoryPromoter } from './turn/index.js'
480
480
  export type { MemoryPromoterOptions, SessionMemoryCategory } from './turn/index.js'
481
- export { createMemoryRecallStep } from './turn/memory-recall.js'
481
+ export { createMemoryRecallStep, recalledMemoryIds } from './turn/memory-recall.js'
482
482
  export type {
483
483
  MemoryRecallOptions,
484
484
  MemorySelection,
@@ -393,6 +393,29 @@ export interface QueryParams {
393
393
  */
394
394
  refusalFallback?: 'auto' | 'off'
395
395
 
396
+ /**
397
+ * Recorded-context keys (see `RecordedContextItem.key`) to leave out of the
398
+ * request when the provider refuses it. On a refusal, before any model
399
+ * fallback, the loop re-runs the step once with every entry filed under one
400
+ * of these keys left out of the request. The history itself is not changed,
401
+ * so nothing is lost for a later turn. The refused request is announced by a
402
+ * `model_refusal` event with `outcome: 'withheld'`, and the steps that
403
+ * follow, in this turn, see the keys in `PrepareStepContext.withheldKeys` so
404
+ * a stage can stop adding what was just set aside. When the entries are
405
+ * recalled memories the event carries their ids. A refusal of the retry
406
+ * falls through to `refusalFallback` unchanged. Nothing happens when the
407
+ * request carried no entry under a listed key. Absent means none.
408
+ */
409
+ withholdOnRefusal?: readonly string[]
410
+
411
+ /**
412
+ * Recorded-context keys to leave out of every request of this turn from the
413
+ * first one, for a host that remembers an earlier `outcome: 'withheld'`
414
+ * refusal and does not want the next turn refused again. History is not
415
+ * changed. Seen by stages as `PrepareStepContext.withheldKeys`.
416
+ */
417
+ withheldKeys?: readonly string[]
418
+
396
419
  /**
397
420
  * Model-visible size cap for a single tool result. Over-budget output is
398
421
  * spilled to the turn directory and replaced with a head+tail preview
@@ -1918,6 +1941,10 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
1918
1941
  ? { maxOutputContinuations: params.maxOutputContinuations }
1919
1942
  : {}),
1920
1943
  ...(params.refusalFallback !== undefined ? { refusalFallback: params.refusalFallback } : {}),
1944
+ ...(params.withholdOnRefusal && params.withholdOnRefusal.length > 0
1945
+ ? { withholdOnRefusal: [...params.withholdOnRefusal] }
1946
+ : {}),
1947
+ withheldKeys: new Set(params.withheldKeys ?? []),
1921
1948
  ...(params.inboundMessages ? { inboundMessages: params.inboundMessages } : {}),
1922
1949
  ...(params.reminders ? { reminders: params.reminders } : {}),
1923
1950
  ...(params.durableInbound ? { durableInbound: params.durableInbound } : {}),
@@ -30,6 +30,7 @@ import {
30
30
  import { getTracer } from '../../../telemetry/runtime-accessors.js'
31
31
  import { STRUCTURED_OUTPUT_TOOL_NAME } from '../../../tools/builtins/structuredOutput.js'
32
32
  import type { ToolManager } from '../../../toolsets/manager.js'
33
+ import { RECALLED_MEMORY_ENTRY_KEY, recalledMemoryIds } from '../../../turn/memory-recall.js'
33
34
  import type { CostInfo, TokenUsage } from '../../../types/common/index.js'
34
35
  import { NamzuError } from '../../../types/errors/index.js'
35
36
  import type { MessageId, SessionId, TurnId } from '../../../types/ids/index.js'
@@ -40,6 +41,8 @@ import {
40
41
  createRuntimeContextMessage,
41
42
  createSystemMessage,
42
43
  createToolMessage,
44
+ turnContextEntries,
45
+ turnContextKey,
43
46
  } from '../../../types/message/index.js'
44
47
  import { classifyProviderError, readOutputOverflow } from '../../../types/provider/errors.js'
45
48
  import type { ChatCompletionResponse, ReasoningEffort } from '../../../types/provider/index.js'
@@ -69,10 +72,10 @@ import {
69
72
  snapshotRequestContext,
70
73
  } from '../request-context.js'
71
74
  import {
72
- DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
73
75
  type RequestImageIdentity,
74
76
  markProviderRejectedImage,
75
77
  projectRequestRichContent,
78
+ resolveMaxRequestRichContentBytes,
76
79
  } from '../request-rich-content.js'
77
80
  import { withoutVoidAssistantTurns } from '../request-void-turns.js'
78
81
  import { formatSteeringNote, isOperatorUserMessage } from '../steering.js'
@@ -255,6 +258,20 @@ export class IterationOrchestrator {
255
258
  }
256
259
 
257
260
  /** Whether a request for `model` may carry images: the driver's catalogue and this run's own evidence agree. */
261
+ /**
262
+ * The request rich-content budget, resolved each time it is read: the
263
+ * caller's `turnConfig` override if one was given, else what the provider
264
+ * serving now declares. Never written back into `turnConfig`, which would
265
+ * turn the first provider's declaration into an override that a resumed
266
+ * turn, a nested agent or a later provider then inherits.
267
+ */
268
+ private richContentBudget(): number {
269
+ return resolveMaxRequestRichContentBytes(
270
+ this.ctx.turnConfig.maxRequestRichContentBytes,
271
+ this.ctx.provider.capabilities?.maxRequestRichContentBytes,
272
+ )
273
+ }
274
+
258
275
  private modelReadsImages(model: string): boolean {
259
276
  const serving = this.servingModel(model)
260
277
  return (
@@ -332,6 +349,8 @@ export class IterationOrchestrator {
332
349
  private refusalFallbackUsed = false
333
350
  /** The request has already been re-run on the same model once this turn. */
334
351
  private refusalRetryUsed = false
352
+ /** The request has already been re-run once without its withheld recorded entries. */
353
+ private refusalWithholdUsed = false
335
354
  /**
336
355
  * `turn_end` (and, for a child, `subagent_stop`) fired when the model
337
356
  * finished and no hook asked for more. `finalizeTurn` then does not fire
@@ -898,7 +917,16 @@ export class IterationOrchestrator {
898
917
  turnId: recorder.turnId,
899
918
  memories: recalled,
900
919
  })
901
- const baseMessages = recorder.messages
920
+ const withheld = this.ctx.withheldKeys
921
+ // A withheld key is left out of the request only: the history keeps
922
+ // its entries, so a later turn or a restore still has them.
923
+ const baseMessages =
924
+ withheld && withheld.size > 0
925
+ ? recorder.messages.filter((message) => {
926
+ const key = turnContextKey(message)
927
+ return key === undefined || !withheld.has(key)
928
+ })
929
+ : recorder.messages
902
930
 
903
931
  // Step guidance is appended to the REQUEST, never pushed onto
904
932
  // the turn's history: it applies to this step only, and pushing
@@ -967,8 +995,7 @@ export class IterationOrchestrator {
967
995
  if (policyNotice) requestHistory.push(stepContextMessage(policyNotice))
968
996
  const projected = projectRequestRichContent(
969
997
  withoutVoidAssistantTurns(this.projectObservations(requestHistory)),
970
- this.ctx.turnConfig.maxRequestRichContentBytes ??
971
- DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
998
+ this.richContentBudget(),
972
999
  { imagesReadable: this.modelReadsImages(stepModel) },
973
1000
  )
974
1001
  // Request-only data (the context contributions, notes about the
@@ -1197,6 +1224,7 @@ export class IterationOrchestrator {
1197
1224
  cacheControl: { type: 'auto' },
1198
1225
  ...(turnConfig.thinking ? { thinking: turnConfig.thinking } : {}),
1199
1226
  ...(stepEffort ? { effort: stepEffort } : {}),
1227
+ ...(turnConfig.speed ? { speed: turnConfig.speed } : {}),
1200
1228
  ...(!forceFinalize && turnConfig.webSearch
1201
1229
  ? { webSearch: turnConfig.webSearch }
1202
1230
  : {}),
@@ -3180,6 +3208,42 @@ export class IterationOrchestrator {
3180
3208
  return 'settled'
3181
3209
  }
3182
3210
 
3211
+ const withholdKeys = this.ctx.withholdOnRefusal
3212
+ if (
3213
+ !input.forceFinalize &&
3214
+ !this.refusalWithholdUsed &&
3215
+ withholdKeys &&
3216
+ this.ctx.withheldKeys
3217
+ ) {
3218
+ const carried = withholdKeys.filter(
3219
+ (key) =>
3220
+ !this.ctx.withheldKeys?.has(key) &&
3221
+ recorder.messages.some((message) => turnContextKey(message) === key),
3222
+ )
3223
+ if (carried.length > 0) {
3224
+ this.refusalWithholdUsed = true
3225
+ for (const key of carried) this.ctx.withheldKeys.add(key)
3226
+ const memoryIds = carried.includes(RECALLED_MEMORY_ENTRY_KEY)
3227
+ ? recalledMemoryIds(turnContextEntries(recorder.messages, RECALLED_MEMORY_ENTRY_KEY))
3228
+ : []
3229
+ await this.ctx.emitEvent({
3230
+ type: 'model_refusal',
3231
+ turnId: recorder.turnId,
3232
+ iteration: iterationNum,
3233
+ model: servedBy.model,
3234
+ providerId: servedBy.providerId,
3235
+ outcome: 'withheld',
3236
+ withheldKeys: carried,
3237
+ ...(memoryIds.length > 0 ? { withheldMemoryIds: memoryIds } : {}),
3238
+ ...(refusal.category ? { category: refusal.category } : {}),
3239
+ ...(refusal.explanation ? { explanation: refusal.explanation } : {}),
3240
+ })
3241
+ await closeIteration()
3242
+ yield* this.ctx.drainPending()
3243
+ return 'retry'
3244
+ }
3245
+ }
3246
+
3183
3247
  if (!input.forceFinalize && !this.refusalFallbackUsed && this.ctx.refusalFallback !== 'off') {
3184
3248
  const next = await this.refusalFallbackModel(input.stepModel, refusal)
3185
3249
  if (next !== undefined) {
@@ -3369,7 +3433,7 @@ export class IterationOrchestrator {
3369
3433
  ]
3370
3434
  const finalMessages = projectRequestRichContent(
3371
3435
  withoutVoidAssistantTurns(this.projectObservations(finalHistory)),
3372
- this.ctx.turnConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
3436
+ this.richContentBudget(),
3373
3437
  { imagesReadable: this.modelReadsImages(model) },
3374
3438
  )
3375
3439
  // The closing directive is the last thing the model reads: after
@@ -3411,6 +3475,7 @@ export class IterationOrchestrator {
3411
3475
  // is exactly the shape a field goes missing from — so it is tested
3412
3476
  // separately rather than assumed to have been kept in step.
3413
3477
  ...(this.ctx.turnConfig.effort ? { effort: this.ctx.turnConfig.effort } : {}),
3478
+ ...(this.ctx.turnConfig.speed ? { speed: this.ctx.turnConfig.speed } : {}),
3414
3479
  // Cancellable too: a Stop during the closing summary must not
3415
3480
  // stream to completion.
3416
3481
  signal: this.ctx.abortController.signal,
@@ -226,6 +226,10 @@ export interface IterationContext {
226
226
  readonly maxOutputContinuations?: number
227
227
  /** See `QueryParams.refusalFallback`. */
228
228
  readonly refusalFallback?: 'auto' | 'off'
229
+ /** See `QueryParams.withholdOnRefusal`. */
230
+ readonly withholdOnRefusal?: readonly string[]
231
+ /** Recorded-context keys left out of the request; grows when a refusal is retried without them. */
232
+ readonly withheldKeys?: Set<string>
229
233
 
230
234
  /**
231
235
  * What the driver said this model's context window is, resolved once.
@@ -162,6 +162,9 @@ export function stepContext(
162
162
  ),
163
163
  },
164
164
  steps: steps(),
165
+ ...(ctx.withheldKeys && ctx.withheldKeys.size > 0
166
+ ? { withheldKeys: [...ctx.withheldKeys] }
167
+ : {}),
165
168
  prepared,
166
169
  }
167
170
  }
@@ -332,8 +332,13 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
332
332
  const promptCache = params.promptCache
333
333
  const taskScheduler = params.taskScheduler
334
334
  const streamIdleTimeoutMs = resolveStreamIdleTimeoutMs(params.turnConfig.streamIdleTimeoutMs)
335
- const maxRequestRichContentBytes = resolveMaxRequestRichContentBytes(
335
+ // Validated here, at the door, but only the caller's own override is kept on
336
+ // the turn: the provider's declaration is read where it is used (see
337
+ // `IterationOrchestrator.richContentBudget`), so it follows the provider
338
+ // that serves the turn rather than being pinned by the first one.
339
+ resolveMaxRequestRichContentBytes(
336
340
  params.turnConfig.maxRequestRichContentBytes,
341
+ params.provider.capabilities?.maxRequestRichContentBytes,
337
342
  )
338
343
  const sandboxTeardownTimeoutMs = resolveSandboxTeardownTimeoutMs(params.sandboxTeardownTimeoutMs)
339
344
  // Persist the EFFECTIVE value, not only an override. A turn replayed after a
@@ -343,7 +348,6 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
343
348
  const turnConfig: TurnConfig = {
344
349
  ...params.turnConfig,
345
350
  streamIdleTimeoutMs,
346
- maxRequestRichContentBytes,
347
351
  }
348
352
 
349
353
  // The turn's one correlated logger, built before anything below needs
@@ -12,8 +12,17 @@ export const DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES = 24 * 1024 * 1024
12
12
 
13
13
  const MAX_REQUEST_RICH_CONTENT_BYTES = Number.MAX_SAFE_INTEGER
14
14
 
15
- export function resolveMaxRequestRichContentBytes(value: number | undefined): number {
16
- const resolved = value ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES
15
+ /**
16
+ * The budget for one turn: the caller's `turnConfig.maxRequestRichContentBytes`
17
+ * when set, else the one the provider declares for its own wire limit
18
+ * (`ProviderCapabilities.maxRequestRichContentBytes`), else the kernel default.
19
+ * The override always wins, so a host can tighten or loosen what a driver declares.
20
+ */
21
+ export function resolveMaxRequestRichContentBytes(
22
+ value: number | undefined,
23
+ providerDeclared?: number,
24
+ ): number {
25
+ const resolved = value ?? providerDeclared ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES
17
26
  if (
18
27
  !Number.isSafeInteger(resolved) ||
19
28
  resolved < 0 ||
package/src/turn/index.ts CHANGED
@@ -23,7 +23,7 @@ export type { LimitCheckerState, LimitCheckResult } from './LimitChecker.js'
23
23
 
24
24
  export { SESSION_MEMORY_TAG, createMemoryPromoter } from './memory-promoter.js'
25
25
  export type { MemoryPromoterOptions, SessionMemoryCategory } from './memory-promoter.js'
26
- export { createMemoryRecallStep } from './memory-recall.js'
26
+ export { createMemoryRecallStep, recalledMemoryIds } from './memory-recall.js'
27
27
  export type {
28
28
  MemoryRecallOptions,
29
29
  MemorySelection,
@@ -80,6 +80,12 @@ export interface MemoryRecallOptions {
80
80
  readonly minCoverage?: number
81
81
  /** Upper bound on the selector, in ms; the signal it is given aborts then. Default 2,500. */
82
82
  readonly selectTimeoutMs?: number
83
+ /**
84
+ * Ids recall must not offer, read afresh on every request. A host keeps a
85
+ * set-aside list here (records a refused request carried) and lifts it when
86
+ * the person restores a record. A throw reads as an empty list.
87
+ */
88
+ readonly excluded?: () => Iterable<string> | Promise<Iterable<string>>
83
89
  }
84
90
 
85
91
  /** What a selector may know about a record: never its body. */
@@ -126,6 +132,27 @@ const OTHERS_PLURAL = (count: number): string =>
126
132
  export const RECALLED_MEMORY_ENTRY_KEY = 'memory'
127
133
 
128
134
  /** The `id@updatedAt` of every record the entries still in the history carry. */
135
+ /**
136
+ * The ids of the records the `memory` entries carry, in the order they were
137
+ * recorded and without repeats. `entries` are bodies as `turnContextEntries`
138
+ * returns them.
139
+ */
140
+ export function recalledMemoryIds(entries: readonly string[]): string[] {
141
+ const ids = new Set<string>()
142
+ for (const entry of entries) {
143
+ for (const line of entry.split('\n')) {
144
+ if (!line.startsWith('{')) continue
145
+ try {
146
+ const parsed = JSON.parse(line) as { id?: unknown }
147
+ if (typeof parsed.id === 'string') ids.add(parsed.id)
148
+ } catch {
149
+ // a line of the entry that is not a record
150
+ }
151
+ }
152
+ }
153
+ return [...ids]
154
+ }
155
+
129
156
  function recordedRecords(entries: readonly string[]): Set<string> {
130
157
  const seen = new Set<string>()
131
158
  for (const entry of entries) {
@@ -377,8 +404,12 @@ export function createMemoryRecallStep(options: MemoryRecallOptions): PrepareSte
377
404
  latestUserMessage,
378
405
  contextBudget,
379
406
  signal,
407
+ withheldKeys,
380
408
  }) => {
381
409
  signal?.throwIfAborted()
410
+ // A refused request was re-run without the memories it carried; adding
411
+ // them again would be refused again.
412
+ if (withheldKeys?.includes(RECALLED_MEMORY_ENTRY_KEY)) return undefined
382
413
  // Leave room for the current request and response. The kernel supplies
383
414
  // an estimate; using one character per remaining token is deliberately
384
415
  // conservative for this optional text, not a billing/tokenizer claim.
@@ -484,6 +515,14 @@ export function createMemoryRecallStep(options: MemoryRecallOptions): PrepareSte
484
515
  }
485
516
  const recall = async (): Promise<Recalled> => {
486
517
  const shown: RecalledMemory[] = []
518
+ let excludedIds: ReadonlySet<string> = new Set()
519
+ if (options.excluded) {
520
+ try {
521
+ excludedIds = new Set(await options.excluded())
522
+ } catch {
523
+ // An unreadable list excludes nothing.
524
+ }
525
+ }
487
526
  const page = await options.store.list({
488
527
  query: terms.join(' '),
489
528
  status: 'active',
@@ -501,6 +540,7 @@ export function createMemoryRecallStep(options: MemoryRecallOptions): PrepareSte
501
540
  let block = lead
502
541
  const fresh = page.entries
503
542
  .filter((entry) => entry.status === 'active')
543
+ .filter((entry) => !excludedIds.has(entry.id))
504
544
  .filter((entry) => !known.has(`${entry.id}@${entry.updatedAt}`))
505
545
  const candidates = fresh.slice(0, shortlist)
506
546
  const picks = await choose(candidates)
@@ -226,6 +226,8 @@ export interface BaseAgentConfig {
226
226
  */
227
227
  thinking?: import('../provider/index.js').ThinkingConfig
228
228
  effort?: import('../provider/index.js').ReasoningEffort
229
+ /** Inference speed; see {@link import('../session/config.js').TurnConfig.speed}. */
230
+ speed?: import('../provider/index.js').InferenceSpeed
229
231
  /** How the turn bounds its reasoning; see {@link import('../session/config.js').TurnConfig.reasoning}. */
230
232
  reasoning?: import('../session/config.js').TurnConfig['reasoning']
231
233
  /** Ask once for a check of edits before a turn ends; see {@link import('../session/config.js').TurnConfig.completionAudit}. */
@@ -100,6 +100,10 @@ export interface QueryAgentConfig extends BaseAgentConfig {
100
100
  maxOutputContinuations?: QueryParams['maxOutputContinuations']
101
101
  /** See {@link QueryParams.refusalFallback}. */
102
102
  refusalFallback?: QueryParams['refusalFallback']
103
+ /** See {@link QueryParams.withholdOnRefusal}. */
104
+ withholdOnRefusal?: QueryParams['withholdOnRefusal']
105
+ /** See {@link QueryParams.withheldKeys}. */
106
+ withheldKeys?: QueryParams['withheldKeys']
103
107
  maxToolOutputChars?: number
104
108
  maxToolOutputCharsPerMessage?: number
105
109
  retainedToolPreviewChars?: QueryParams['retainedToolPreviewChars']
@@ -42,6 +42,14 @@ export interface TokenUsage {
42
42
  * {@link TokenUsage.cacheTtlMs}.
43
43
  */
44
44
  cacheWriteTtlTokens?: { fiveMin?: number; oneHour?: number }
45
+
46
+ /**
47
+ * The speed the provider says served this call, when it says. `'fast'` is a
48
+ * premium-priced mode, so pricing reads it per call: a per-call fact like
49
+ * {@link TokenUsage.cacheTtlMs}, never summed. Absent means standard or
50
+ * unreported.
51
+ */
52
+ speed?: import('../provider/chat.js').InferenceSpeed
45
53
  }
46
54
 
47
55
  export function accumulateTokenUsage(current: TokenUsage, addition: TokenUsage): TokenUsage {
@@ -101,6 +109,9 @@ export function mergeTokenUsage(current: TokenUsage, next: TokenUsage): TokenUsa
101
109
  ...(next.cacheWriteTtlTokens !== undefined || current.cacheWriteTtlTokens !== undefined
102
110
  ? { cacheWriteTtlTokens: next.cacheWriteTtlTokens ?? current.cacheWriteTtlTokens }
103
111
  : {}),
112
+ ...(next.speed !== undefined || current.speed !== undefined
113
+ ? { speed: next.speed ?? current.speed }
114
+ : {}),
104
115
  }
105
116
  }
106
117
 
@@ -106,6 +106,15 @@ export interface ChatCompletionParams {
106
106
  * that narrower set according to their documented provider policy.
107
107
  */
108
108
  effort?: ReasoningEffort
109
+
110
+ /**
111
+ * Inference speed. `'fast'` asks a provider for its premium low-latency
112
+ * mode, billed at a multiple of the standard rate; absent and `'standard'`
113
+ * are the same request. A driver with no such mode, or a model without it,
114
+ * must refuse `'fast'` rather than serve it at standard speed and let the
115
+ * caller believe it was honoured.
116
+ */
117
+ speed?: InferenceSpeed
109
118
  }
110
119
 
111
120
  export interface ThinkingConfig {
@@ -174,6 +183,9 @@ export interface ThinkingConfig {
174
183
  * transport or leave the vendor to refuse an unknown compatible-endpoint
175
184
  * extension explicitly.
176
185
  */
186
+ /** `'fast'` is a premium low-latency mode; `'standard'` is the ordinary request. See {@link ChatCompletionParams.speed}. */
187
+ export type InferenceSpeed = 'standard' | 'fast'
188
+
177
189
  export type ReasoningEffort =
178
190
  | 'none'
179
191
  | 'minimal'
@@ -170,6 +170,14 @@ export interface ProviderCapabilities {
170
170
  * or the lifetime is the provider's own and cannot be chosen or read).
171
171
  */
172
172
  cacheTtl?: readonly ('5m' | '1h')[]
173
+ /**
174
+ * The most base64 characters of inline images and documents this provider
175
+ * will take in one request, when its wire limit differs from the kernel's
176
+ * default (24 MiB). Counts the encoded value, like
177
+ * `turnConfig.maxRequestRichContentBytes`, which still overrides it.
178
+ * Absent means the kernel default.
179
+ */
180
+ maxRequestRichContentBytes?: number
173
181
  }
174
182
 
175
183
  export interface ProviderFactoryResult {
@@ -5,6 +5,7 @@ export type {
5
5
  ChatCompletionParams,
6
6
  ChatCompletionResponse,
7
7
  ProviderRefusal,
8
+ InferenceSpeed,
8
9
  ReasoningEffort,
9
10
  ThinkingConfig,
10
11
  } from './chat.js'