@namzu/sdk 62.3.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 (138) hide show
  1. package/CHANGELOG.md +29 -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/authorization/top-level-entries.d.ts +38 -0
  10. package/dist/authorization/top-level-entries.d.ts.map +1 -0
  11. package/dist/authorization/top-level-entries.js +200 -0
  12. package/dist/authorization/top-level-entries.js.map +1 -0
  13. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  14. package/dist/bridge/a2a/mapper.js +5 -3
  15. package/dist/bridge/a2a/mapper.js.map +1 -1
  16. package/dist/peers/envelope.d.ts.map +1 -1
  17. package/dist/peers/envelope.js +3 -1
  18. package/dist/peers/envelope.js.map +1 -1
  19. package/dist/peers/protocol.d.ts +9 -0
  20. package/dist/peers/protocol.d.ts.map +1 -1
  21. package/dist/peers/protocol.js +3 -1
  22. package/dist/peers/protocol.js.map +1 -1
  23. package/dist/peers/record.d.ts +2 -0
  24. package/dist/peers/record.d.ts.map +1 -1
  25. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  26. package/dist/pricing/catalogue.generated.js +5 -0
  27. package/dist/pricing/catalogue.generated.js.map +1 -1
  28. package/dist/provider/fallback.d.ts.map +1 -1
  29. package/dist/provider/fallback.js +23 -1
  30. package/dist/provider/fallback.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/executor.d.ts.map +1 -1
  36. package/dist/runtime/query/executor.js +2 -1
  37. package/dist/runtime/query/executor.js.map +1 -1
  38. package/dist/runtime/query/index.d.ts +21 -0
  39. package/dist/runtime/query/index.d.ts.map +1 -1
  40. package/dist/runtime/query/index.js +4 -0
  41. package/dist/runtime/query/index.js.map +1 -1
  42. package/dist/runtime/query/iteration/index.d.ts +10 -0
  43. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  44. package/dist/runtime/query/iteration/index.js +59 -6
  45. package/dist/runtime/query/iteration/index.js.map +1 -1
  46. package/dist/runtime/query/iteration/phases/context.d.ts +4 -0
  47. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  48. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  49. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  50. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  51. package/dist/runtime/query/iteration/step-shaping.d.ts.map +1 -1
  52. package/dist/runtime/query/iteration/step-shaping.js +3 -0
  53. package/dist/runtime/query/iteration/step-shaping.js.map +1 -1
  54. package/dist/runtime/query/path-escalation.d.ts +10 -1
  55. package/dist/runtime/query/path-escalation.d.ts.map +1 -1
  56. package/dist/runtime/query/path-escalation.js +24 -2
  57. package/dist/runtime/query/path-escalation.js.map +1 -1
  58. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  59. package/dist/runtime/query/prepare-turn.js +5 -2
  60. package/dist/runtime/query/prepare-turn.js.map +1 -1
  61. package/dist/runtime/query/request-rich-content.d.ts +7 -1
  62. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  63. package/dist/runtime/query/request-rich-content.js +8 -2
  64. package/dist/runtime/query/request-rich-content.js.map +1 -1
  65. package/dist/runtime/query/review-policy.d.ts +1 -1
  66. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  67. package/dist/runtime/query/review-policy.js +1 -1
  68. package/dist/runtime/query/review-policy.js.map +1 -1
  69. package/dist/turn/index.d.ts +1 -1
  70. package/dist/turn/index.d.ts.map +1 -1
  71. package/dist/turn/index.js +1 -1
  72. package/dist/turn/index.js.map +1 -1
  73. package/dist/turn/memory-recall.d.ts +13 -0
  74. package/dist/turn/memory-recall.d.ts.map +1 -1
  75. package/dist/turn/memory-recall.js +38 -1
  76. package/dist/turn/memory-recall.js.map +1 -1
  77. package/dist/types/agent/base.d.ts +2 -0
  78. package/dist/types/agent/base.d.ts.map +1 -1
  79. package/dist/types/agent/query.d.ts +4 -0
  80. package/dist/types/agent/query.d.ts.map +1 -1
  81. package/dist/types/common/index.d.ts +7 -0
  82. package/dist/types/common/index.d.ts.map +1 -1
  83. package/dist/types/common/index.js +3 -0
  84. package/dist/types/common/index.js.map +1 -1
  85. package/dist/types/hitl/index.d.ts +3 -1
  86. package/dist/types/hitl/index.d.ts.map +1 -1
  87. package/dist/types/hitl/index.js.map +1 -1
  88. package/dist/types/provider/chat.d.ts +10 -0
  89. package/dist/types/provider/chat.d.ts.map +1 -1
  90. package/dist/types/provider/config.d.ts +8 -0
  91. package/dist/types/provider/config.d.ts.map +1 -1
  92. package/dist/types/provider/index.d.ts +1 -1
  93. package/dist/types/provider/index.d.ts.map +1 -1
  94. package/dist/types/session/config.d.ts +10 -0
  95. package/dist/types/session/config.d.ts.map +1 -1
  96. package/dist/types/session/events.d.ts +8 -1
  97. package/dist/types/session/events.d.ts.map +1 -1
  98. package/dist/types/session/events.js.map +1 -1
  99. package/dist/types/session/prepare-step.d.ts +7 -0
  100. package/dist/types/session/prepare-step.d.ts.map +1 -1
  101. package/dist/utils/cost.d.ts +7 -0
  102. package/dist/utils/cost.d.ts.map +1 -1
  103. package/dist/utils/cost.js +15 -0
  104. package/dist/utils/cost.js.map +1 -1
  105. package/package.json +1 -1
  106. package/src/agents/QueryAgent.ts +3 -0
  107. package/src/agents/runAgent.ts +9 -1
  108. package/src/authorization/top-level-entries.ts +203 -0
  109. package/src/bridge/a2a/mapper.ts +5 -3
  110. package/src/peers/envelope.ts +4 -1
  111. package/src/peers/protocol.ts +3 -1
  112. package/src/pricing/catalogue.generated.ts +5 -0
  113. package/src/pricing/rates.source.json +15 -5
  114. package/src/provider/fallback.ts +33 -1
  115. package/src/public-runtime.ts +1 -1
  116. package/src/runtime/query/executor.ts +6 -1
  117. package/src/runtime/query/index.ts +27 -0
  118. package/src/runtime/query/iteration/index.ts +70 -5
  119. package/src/runtime/query/iteration/phases/context.ts +4 -0
  120. package/src/runtime/query/iteration/phases/tool-review.ts +1 -1
  121. package/src/runtime/query/iteration/step-shaping.ts +3 -0
  122. package/src/runtime/query/path-escalation.ts +33 -1
  123. package/src/runtime/query/prepare-turn.ts +6 -2
  124. package/src/runtime/query/request-rich-content.ts +11 -2
  125. package/src/runtime/query/review-policy.ts +1 -1
  126. package/src/turn/index.ts +1 -1
  127. package/src/turn/memory-recall.ts +40 -0
  128. package/src/types/agent/base.ts +2 -0
  129. package/src/types/agent/query.ts +4 -0
  130. package/src/types/common/index.ts +11 -0
  131. package/src/types/hitl/index.ts +3 -1
  132. package/src/types/provider/chat.ts +12 -0
  133. package/src/types/provider/config.ts +8 -0
  134. package/src/types/provider/index.ts +1 -0
  135. package/src/types/session/config.ts +11 -0
  136. package/src/types/session/events.ts +8 -1
  137. package/src/types/session/prepare-step.ts +7 -0
  138. package/src/utils/cost.ts +23 -0
@@ -7,6 +7,9 @@
7
7
  * - does it write a protected path (a git hook, a shell rc file, editor or
8
8
  * tool configuration, the project's `.namzu` directory)?
9
9
  * - does its shell line remove something catastrophic?
10
+ * - does its call create or delete a top-level entry of the home folder or the
11
+ * filesystem root (see `authorization/top-level-entries.ts`)? It is reported
12
+ * as a protected path: the same question, asked the same way.
10
13
  * - does its shell line reach a path outside the turn's roots, through a
11
14
  * command whose operands are paths or through a redirection?
12
15
  *
@@ -22,6 +25,10 @@ import { isProtectedPath } from '../../authorization/protected-paths.js'
22
25
  import { dangerousRemoval } from '../../authorization/removal.js'
23
26
  import type { ShellDialect } from '../../authorization/shell-lexer.js'
24
27
  import { readShellLine, shellPathAccess } from '../../authorization/shell-paths.js'
28
+ import {
29
+ topLevelEntriesOfMovers,
30
+ topLevelEntryChange,
31
+ } from '../../authorization/top-level-entries.js'
25
32
  import { canonicalize, pathOutsideRoots } from '../../tools/paths.js'
26
33
 
27
34
  /** What the escalation records carry. */
@@ -38,6 +45,12 @@ export interface PathEscalationContext {
38
45
  readonly cwd?: string
39
46
  /** Whether to report paths outside the roots (`outsideRootAccess: 'review'`, no sandbox). */
40
47
  readonly reviewOutside: boolean
48
+ /**
49
+ * Whether to report a top-level entry of the home folder or the filesystem
50
+ * root the line would create or delete. Like `reviewOutside`, off in a sandbox,
51
+ * where the paths are the guest's.
52
+ */
53
+ readonly topLevel?: boolean
41
54
  }
42
55
 
43
56
  /**
@@ -137,11 +150,13 @@ function beforeGlob(path: string): string {
137
150
  export async function declaredWriteEscalation(
138
151
  roots: readonly string[],
139
152
  value: string,
153
+ topLevel = false,
140
154
  ): Promise<string | undefined> {
141
155
  const [first] = roots
142
156
  if (first === undefined || value === '') return undefined
143
157
  const absolute = resolve(resolve(first), value)
144
- return (await isProtectedWrite(value, absolute, roots)) ? absolute : undefined
158
+ if (await isProtectedWrite(value, absolute, roots)) return absolute
159
+ return topLevel ? topLevelEntryChange(absolute, 'write', homedir()) : undefined
145
160
  }
146
161
 
147
162
  /** What a shell line reaches. */
@@ -190,6 +205,15 @@ export async function shellEscalations(
190
205
  for (const candidate of absolutes) {
191
206
  if (await isProtectedWrite(spelled, candidate, context.roots))
192
207
  protectedPaths.add(candidate)
208
+ else if (
209
+ context.topLevel === true &&
210
+ 'path' in settled &&
211
+ (!use.unresolved || /^\$(?:\{HOME\}|HOME)\//.test(spelled)) &&
212
+ use.via !== 'mv'
213
+ ) {
214
+ const entry = topLevelEntryChange(candidate, use.via, home)
215
+ if (entry !== undefined) protectedPaths.add(entry)
216
+ }
193
217
  }
194
218
  }
195
219
  if (!context.reviewOutside) continue
@@ -224,6 +248,14 @@ export async function shellEscalations(
224
248
  }
225
249
  }
226
250
  }
251
+ if (context.topLevel === true) {
252
+ for (const entry of topLevelEntriesOfMovers(reading, {
253
+ home,
254
+ cwd,
255
+ changesDirectory: access.changesDirectory,
256
+ }))
257
+ protectedPaths.add(entry)
258
+ }
227
259
  const removalAt = (directory: string | undefined) =>
228
260
  directory === undefined
229
261
  ? undefined
@@ -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 ||
@@ -293,7 +293,7 @@ export const UNKNOWN_PROGRAM_UNATTENDED_REFUSAL =
293
293
  * existed (a mode, a rule, "approve all") is not an answer about it.
294
294
  */
295
295
  export const PROTECTED_PATH_UNATTENDED_REFUSAL =
296
- "Refused: this call writes a protected path (a git hook or config, a shell startup file, editor or tool configuration, or the project's .namzu directory), which needs a person to approve it each time, and nobody can be asked in this session. This call did not run; the other calls in the batch are not affected. Leave those files alone, or tell the user what change you need so they can make it."
296
+ "Refused: this call writes a protected path (a git hook or config, a shell startup file, editor or tool configuration, or the project's .namzu directory) or creates or deletes a top-level entry directly in the home folder or the filesystem root, which needs a person to approve it each time, and nobody can be asked in this session. This call did not run; the other calls in the batch are not affected. Work inside a project folder instead (create it first, or use an existing one), or tell the user what change you need so they can make it."
297
297
 
298
298
  /**
299
299
  * What the model is told when a batch removes something catastrophic (the
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
 
@@ -389,7 +389,9 @@ export interface ToolCallEscalation {
389
389
  * asked; no rule, mode or remembered approval covers it. Set for the file
390
390
  * tools' declared path and for the visible write targets of a shell line:
391
391
  * output redirections, and the operands of `cp`, `mv`, `tee`, `touch`,
392
- * `mkdir`, `rm`, `rmdir` and `sed -i`.
392
+ * `mkdir`, `rm`, `rmdir` and `sed -i`. A top-level entry of the home folder or
393
+ * the filesystem root that the call would create or delete is listed here
394
+ * too (when the turn reviews outside paths and runs without a sandbox).
393
395
  */
394
396
  protectedPaths?: readonly string[]
395
397
  /**
@@ -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'
@@ -69,6 +69,17 @@ export interface TurnConfig {
69
69
  */
70
70
  effort?: import('../provider/index.js').ReasoningEffort
71
71
 
72
+ /**
73
+ * Inference speed for every model call in the turn: `'fast'` asks for the
74
+ * provider's premium low-latency mode (billed at a multiple of the standard
75
+ * rate), absent and `'standard'` are the ordinary request. Turn-level for
76
+ * the same reason as {@link TurnConfig.effort}: speeds do not share a
77
+ * prompt-cache prefix, so a value that moves between steps pays for a cache
78
+ * miss each time. A driver or model without the mode REFUSES `'fast'`
79
+ * rather than serving it slowly.
80
+ */
81
+ speed?: import('../provider/index.js').InferenceSpeed
82
+
72
83
  /**
73
84
  * How the turn bounds its reasoning. On by default.
74
85
  *
@@ -519,7 +519,14 @@ type CoreSessionEvent =
519
519
  /** The model that declined. */
520
520
  model: string
521
521
  providerId: string
522
- outcome: 'fallback' | 'retry' | 'declined'
522
+ outcome: 'fallback' | 'retry' | 'withheld' | 'declined'
523
+ /**
524
+ * Set only with `outcome: 'withheld'`: the recorded-context keys the
525
+ * re-run leaves out of the request (see `QueryParams.withholdOnRefusal`).
526
+ */
527
+ withheldKeys?: string[]
528
+ /** With `'withheld'`: the ids of the recalled memories those entries carried. */
529
+ withheldMemoryIds?: string[]
523
530
  /** The model the request moves to. Set only with `outcome: 'fallback'`. */
524
531
  fallbackModel?: string
525
532
  /** The provider's own classification, when it gave one. */
@@ -98,6 +98,13 @@ export interface PrepareStepContext {
98
98
  }
99
99
  /** Every completed step, in order. */
100
100
  readonly steps: readonly StepResult[]
101
+ /**
102
+ * Recorded-context keys the request leaves out: set aside by a refused
103
+ * request (`QueryParams.withholdOnRefusal`) or by the host
104
+ * (`QueryParams.withheldKeys`). A stage that records text under one of
105
+ * these keys should add nothing now; whatever it records is also left out.
106
+ */
107
+ readonly withheldKeys?: readonly string[]
101
108
 
102
109
  /**
103
110
  * What the stages before this one decided, when several were supplied.
package/src/utils/cost.ts CHANGED
@@ -41,6 +41,13 @@ export interface ModelPricing {
41
41
  * part. The catalogue always supplies this.
42
42
  */
43
43
  cache?: CacheRates
44
+ /**
45
+ * The vendor's published multiple of every rate on this card for a request
46
+ * served in fast mode (`TokenUsage.speed === 'fast'`). Absent means there is
47
+ * no published fast price: fast usage is then counted as unpriced, never
48
+ * charged at the standard rate, which would under-report it.
49
+ */
50
+ fastMultiplier?: number
44
51
  }
45
52
 
46
53
  export { ZERO_COST }
@@ -54,6 +61,22 @@ interface Priced {
54
61
  }
55
62
 
56
63
  function priceUsage(usage: TokenUsage, pricing: ModelPricing): Priced {
64
+ if (usage.speed === 'fast') {
65
+ const multiplier = pricing.fastMultiplier
66
+ if (multiplier === undefined) {
67
+ return { cost: 0, cacheDiscount: 0, unpriced: usage.totalTokens }
68
+ }
69
+ const standard = priceStandard({ ...usage, speed: undefined }, pricing)
70
+ return {
71
+ cost: standard.cost * multiplier,
72
+ cacheDiscount: standard.cacheDiscount * multiplier,
73
+ unpriced: standard.unpriced,
74
+ }
75
+ }
76
+ return priceStandard(usage, pricing)
77
+ }
78
+
79
+ function priceStandard(usage: TokenUsage, pricing: ModelPricing): Priced {
57
80
  const perMillion = (tokens: number, rate: number) => (tokens / 1_000_000) * rate
58
81
 
59
82
  const cache = pricing.cache