@namzu/sdk 22.1.0 → 25.0.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 (65) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +3 -0
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/compaction/context-window.d.ts +15 -0
  6. package/dist/compaction/context-window.d.ts.map +1 -1
  7. package/dist/compaction/context-window.js +28 -3
  8. package/dist/compaction/context-window.js.map +1 -1
  9. package/dist/provider/retry.d.ts +10 -5
  10. package/dist/provider/retry.d.ts.map +1 -1
  11. package/dist/provider/retry.js +1 -29
  12. package/dist/provider/retry.js.map +1 -1
  13. package/dist/public-runtime.d.ts +1 -0
  14. package/dist/public-runtime.d.ts.map +1 -1
  15. package/dist/public-runtime.js.map +1 -1
  16. package/dist/runtime/query/executor.d.ts +34 -0
  17. package/dist/runtime/query/executor.d.ts.map +1 -1
  18. package/dist/runtime/query/executor.js +61 -0
  19. package/dist/runtime/query/executor.js.map +1 -1
  20. package/dist/runtime/query/index.d.ts +31 -13
  21. package/dist/runtime/query/index.d.ts.map +1 -1
  22. package/dist/runtime/query/index.js +28 -6
  23. package/dist/runtime/query/index.js.map +1 -1
  24. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  25. package/dist/runtime/query/iteration/index.js +178 -20
  26. package/dist/runtime/query/iteration/index.js.map +1 -1
  27. package/dist/runtime/query/iteration/stream-turn.d.ts +14 -1
  28. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  29. package/dist/runtime/query/iteration/stream-turn.js +19 -3
  30. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  31. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  32. package/dist/runtime/query/resume-pending.js +10 -1
  33. package/dist/runtime/query/resume-pending.js.map +1 -1
  34. package/dist/runtime/query/tool-pause.d.ts +35 -0
  35. package/dist/runtime/query/tool-pause.d.ts.map +1 -1
  36. package/dist/runtime/query/tool-pause.js +35 -0
  37. package/dist/runtime/query/tool-pause.js.map +1 -1
  38. package/dist/runtime/query/tooling.d.ts +2 -0
  39. package/dist/runtime/query/tooling.d.ts.map +1 -1
  40. package/dist/runtime/query/tooling.js +3 -0
  41. package/dist/runtime/query/tooling.js.map +1 -1
  42. package/dist/types/agent/reactive.d.ts +1 -0
  43. package/dist/types/agent/reactive.d.ts.map +1 -1
  44. package/dist/types/run/step.d.ts +75 -3
  45. package/dist/types/run/step.d.ts.map +1 -1
  46. package/dist/types/run/step.js.map +1 -1
  47. package/dist/utils/backoff.d.ts +44 -0
  48. package/dist/utils/backoff.d.ts.map +1 -0
  49. package/dist/utils/backoff.js +58 -0
  50. package/dist/utils/backoff.js.map +1 -0
  51. package/package.json +1 -1
  52. package/src/agents/ReactiveAgent.ts +3 -0
  53. package/src/compaction/context-window.ts +28 -3
  54. package/src/provider/retry.ts +10 -35
  55. package/src/public-runtime.ts +5 -0
  56. package/src/runtime/query/executor.ts +74 -0
  57. package/src/runtime/query/index.ts +59 -18
  58. package/src/runtime/query/iteration/index.ts +199 -22
  59. package/src/runtime/query/iteration/stream-turn.ts +24 -3
  60. package/src/runtime/query/resume-pending.ts +10 -1
  61. package/src/runtime/query/tool-pause.ts +37 -0
  62. package/src/runtime/query/tooling.ts +5 -0
  63. package/src/types/agent/reactive.ts +1 -0
  64. package/src/types/run/step.ts +76 -3
  65. package/src/utils/backoff.ts +70 -0
@@ -32,6 +32,43 @@ import type { PendingAnswers, QuestionParkRecorder } from './question-park.js'
32
32
  */
33
33
  export const pauseId = (toolUseId: string, name: string): string => `${toolUseId}:${name}`
34
34
 
35
+ /**
36
+ * Does this pause id belong to `callId`?
37
+ *
38
+ * The other half of the scheme above, and it lives beside the mint rather
39
+ * than at the gate that asks it, so the two cannot drift apart again. They
40
+ * already had: the resume gate compared the whole parked id against a raw
41
+ * tool-use id, which a composite can never equal, so a pause raised
42
+ * through {@link createToolPause} was refused by every cross-process
43
+ * resume — while the built-in question tool, whose `questionId` IS the raw
44
+ * id, passed the same gate and worked.
45
+ *
46
+ * Asked against an id actually present in the turn rather than by
47
+ * splitting the composite on `:`. The left half is the provider's tool-use
48
+ * id and nothing forbids a colon in it, so a split takes `call:9:confirm`
49
+ * apart at the wrong place and then compares against `call` — a string no
50
+ * provider minted. Testing a `<callId>:` prefix asks about candidates that
51
+ * exist instead.
52
+ *
53
+ * It is not exact, and the inexactness is bounded rather than absent: a
54
+ * call whose id is literally `call` matches a pause raised on `call:9`.
55
+ * That costs nothing while the real call is also in the turn, because the
56
+ * pause does belong to a call there; and when it is not, the answer is
57
+ * filed under a key no tool asks for, so the tool asks again. A resume
58
+ * that re-asks, never one that misdelivers — routing below is still an
59
+ * exact key match.
60
+ *
61
+ * Membership only — is there a call in this turn that this pause was
62
+ * raised from. The full composite still ROUTES the answer, by exact key in
63
+ * `PendingAnswers`, which is what keeps a call that pauses twice from
64
+ * delivering its second answer against its first question.
65
+ *
66
+ * Every sentence above that names a condition and an outcome is pinned in
67
+ * `__tests__/tool-pause.test.ts`, under "the id a resume gate matches on".
68
+ */
69
+ export const isPauseForCall = (pause: string, callId: string): boolean =>
70
+ pause === callId || pause.startsWith(`${callId}:`)
71
+
35
72
  interface ToolPauseDeps {
36
73
  readonly runId: RunId
37
74
  readonly toolUseId: string
@@ -6,6 +6,7 @@ import type { PermissionMode } from '../../types/permission/index.js'
6
6
  import type { RunEvent } from '../../types/run/index.js'
7
7
  import type { RequestToolPause, ToolRegistryContract } from '../../types/tool/index.js'
8
8
  import type { RepairToolCall } from '../../types/tool/repair.js'
9
+ import type { BackoffPolicy } from '../../utils/backoff.js'
9
10
  import type { Logger } from '../../utils/logger.js'
10
11
  import { ToolExecutor } from './executor.js'
11
12
 
@@ -22,6 +23,7 @@ export interface ToolingBootstrapConfig {
22
23
  invocationState?: InvocationState
23
24
  pluginManager?: PluginLifecycleManager
24
25
  toolTimeoutMs?: number
26
+ toolRetryBackoff?: Partial<BackoffPolicy>
25
27
  maxToolConcurrency?: number
26
28
  maxToolOutputChars?: number
27
29
  maxToolContentBytes?: number
@@ -50,6 +52,9 @@ export class ToolingBootstrap {
50
52
  invocationState: config.invocationState,
51
53
  pluginManager: config.pluginManager,
52
54
  ...(config.toolTimeoutMs !== undefined ? { toolTimeoutMs: config.toolTimeoutMs } : {}),
55
+ ...(config.toolRetryBackoff !== undefined
56
+ ? { toolRetryBackoff: config.toolRetryBackoff }
57
+ : {}),
53
58
  ...(config.maxToolConcurrency !== undefined
54
59
  ? { maxToolConcurrency: config.maxToolConcurrency }
55
60
  : {}),
@@ -86,6 +86,7 @@ export interface ReactiveAgentConfig extends BaseAgentConfig {
86
86
  retry?: QueryParams['retry']
87
87
  emergencySave?: boolean
88
88
  toolTimeoutMs?: number
89
+ toolRetryBackoff?: QueryParams['toolRetryBackoff']
89
90
  maxToolConcurrency?: number
90
91
  maxToolOutputChars?: number
91
92
  /**
@@ -1,6 +1,7 @@
1
1
  import type { CostInfo, TokenUsage } from '../common/index.js'
2
2
  import type { MessageId } from '../ids/index.js'
3
3
  import type { ToolCall } from '../message/index.js'
4
+ import type { ProviderErrorCode } from '../provider/errors.js'
4
5
 
5
6
  /**
6
7
  * What one iteration of the agent loop did.
@@ -55,13 +56,56 @@ export interface StepResult {
55
56
  * it is handed.
56
57
  */
57
58
  servedBy?: StepProvenance
58
- messageId: MessageId
59
+ /**
60
+ * The assistant message this step produced.
61
+ *
62
+ * Absent only on a step whose iteration ended before the model's message
63
+ * was announced — a compaction failure, a lifecycle hook that threw, a
64
+ * transport error raised before the first chunk. Absence is left meaning
65
+ * "there was no message" rather than filled with an id the event stream
66
+ * never carried: a `messageId` a reader cannot find a `message_started`
67
+ * for is worse evidence than no id at all, because it invites the
68
+ * correlation and then loses it.
69
+ *
70
+ * Present, and correlatable, on every step whose iteration got as far as
71
+ * the provider call — including one that failed mid-stream. The loop
72
+ * mints the id before the call that announces it, and a dying stream
73
+ * emits `message_completed` on its way out, so the events carry both
74
+ * ends of the message a failed step points at.
75
+ */
76
+ messageId?: MessageId
59
77
  /** Assistant text for this step, if any. */
60
78
  content: string | null
61
79
  toolCalls: readonly ToolCall[]
62
- /** Tool outcomes, in the same order as `toolCalls`. */
80
+ /**
81
+ * Tool outcomes, in the same order as `toolCalls`.
82
+ *
83
+ * Shorter than `toolCalls` on a step that ended in failure: only the
84
+ * outcomes that exist are recorded, so a call with no entry here reads
85
+ * as "never came back" rather than as an empty success. Pair by
86
+ * `toolCallId`, not by index.
87
+ */
63
88
  toolResults: readonly StepToolResult[]
64
- finishReason: 'stop' | 'tool_calls' | 'length' | 'content_filter'
89
+ /**
90
+ * How the turn ended.
91
+ *
92
+ * `error` and `cancelled` are not provider verdicts — no provider reports
93
+ * them — but a step exists for a failed iteration too, and it has to say
94
+ * how it ended in the same field a reader already sorts by. `error` comes
95
+ * with {@link failure}; `cancelled` means a Stop tore the turn down and
96
+ * there is no failure to report.
97
+ */
98
+ finishReason: 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'error' | 'cancelled'
99
+ /**
100
+ * What went wrong, on a step with `finishReason: 'error'`.
101
+ *
102
+ * Absent everywhere else. A run whose ledger is complete except on the
103
+ * turns that failed reads as "nothing went wrong" precisely when
104
+ * something did, which is worse than an absent record — so the failed
105
+ * turn gets the same record as every other, and this is what makes it
106
+ * legible as a failure.
107
+ */
108
+ failure?: StepFailure
65
109
  /** Usage for THIS step, not the run's cumulative total. */
66
110
  usage: TokenUsage
67
111
  /** Cost delta attributable to this step. Zero without a pricing table. */
@@ -88,6 +132,35 @@ export interface StepProvenance {
88
132
  readonly chainIndex: number
89
133
  }
90
134
 
135
+ /**
136
+ * Why a step ended in `finishReason: 'error'`.
137
+ *
138
+ * The step-level counterpart of the pair a failed run already carries —
139
+ * {@link import('./entity.js').Run.lastError} and
140
+ * {@link import('./entity.js').Run.lastProviderError} — and shaped from the
141
+ * same classification, so the two agree when the failed step is the one that
142
+ * ended the run. What a run records once, a run of twenty iterations records
143
+ * per iteration, which is the difference between "this run failed" and "this
144
+ * turn failed, and the next four succeeded".
145
+ */
146
+ export interface StepFailure {
147
+ /** The failure's message, as the iteration's span and log recorded it. */
148
+ readonly message: string
149
+ /**
150
+ * Where the classifier placed it.
151
+ *
152
+ * `unknown` for a failure that is not a provider failure at all — a
153
+ * plugin hook that threw, a bug in a tool wrapper. That is the honest
154
+ * reading of the code's own contract ("unclassifiable"), and it is left
155
+ * saying so rather than being given a more specific-looking code.
156
+ */
157
+ readonly code: ProviderErrorCode
158
+ /** HTTP status, when the failure carried one. */
159
+ readonly status?: number
160
+ /** Whether sending the same request again could have worked. */
161
+ readonly retryable: boolean
162
+ }
163
+
91
164
  export interface StepToolResult {
92
165
  toolCallId: string
93
166
  toolName: string
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Exponential backoff with full jitter, and an abortable sleep.
3
+ *
4
+ * Both were private to `provider/retry.ts`, which is where they were needed
5
+ * first and is not where they stop being needed. The tool executor's in-loop
6
+ * retry re-ran a failed call immediately, several times, which is the pattern
7
+ * most likely to prolong the very condition it is retrying against — and the
8
+ * correct implementation was one directory away, already reviewed, already
9
+ * doing the hard part right.
10
+ *
11
+ * So they live here, in one copy, and both retry loops call them. A second
12
+ * implementation of a backoff curve is a second thing to get wrong, and the
13
+ * two would drift on exactly the axis nobody re-derives: jitter.
14
+ */
15
+
16
+ /** The shape a backoff curve needs. */
17
+ export interface BackoffPolicy {
18
+ /** First backoff, doubled each attempt. */
19
+ readonly initialDelayMs: number
20
+ /** Ceiling for a single backoff, before jitter. */
21
+ readonly maxDelayMs: number
22
+ }
23
+
24
+ /**
25
+ * Full jitter (AWS's formulation): sleep a uniform random amount in
26
+ * `[0, backoff]` rather than `backoff` exactly. Equal-jitter and no-jitter
27
+ * both keep a fleet of clients that failed together retrying together;
28
+ * full jitter is what actually spreads a thundering herd.
29
+ *
30
+ * `attempt` is 0-based: attempt 0 draws from `[0, initialDelayMs]`.
31
+ *
32
+ * The concurrency this matters for is not hypothetical on the tool path. A
33
+ * model emits a batch of parallel calls, they hit one rate-limited endpoint
34
+ * together, and they fail together — so a fixed delay would resynchronise
35
+ * them on every attempt, which is a herd this loop creates itself.
36
+ */
37
+ export function backoffWithJitter(
38
+ attempt: number,
39
+ policy: BackoffPolicy,
40
+ random: () => number = Math.random,
41
+ ): number {
42
+ const exponential = Math.min(policy.initialDelayMs * 2 ** attempt, policy.maxDelayMs)
43
+ return Math.round(random() * exponential)
44
+ }
45
+
46
+ /**
47
+ * Sleep, unless the signal says not to.
48
+ *
49
+ * Rejects with the signal's reason when aborted — before sleeping if it is
50
+ * already aborted, and mid-sleep otherwise. A caller that must not throw over
51
+ * an abort catches it; a caller that wants the abort to propagate does not.
52
+ */
53
+ export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
54
+ if (ms <= 0) return Promise.resolve()
55
+ return new Promise<void>((resolve, reject) => {
56
+ if (signal?.aborted) {
57
+ reject(signal.reason)
58
+ return
59
+ }
60
+ const timer = setTimeout(() => {
61
+ signal?.removeEventListener('abort', onAbort)
62
+ resolve()
63
+ }, ms)
64
+ const onAbort = () => {
65
+ clearTimeout(timer)
66
+ reject(signal?.reason)
67
+ }
68
+ signal?.addEventListener('abort', onAbort, { once: true })
69
+ })
70
+ }