@namzu/sdk 16.0.0 → 18.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 (73) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +13 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +16 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/contracts/api.d.ts +8 -1
  9. package/dist/contracts/api.d.ts.map +1 -1
  10. package/dist/manager/run/persistence.d.ts +11 -0
  11. package/dist/manager/run/persistence.d.ts.map +1 -1
  12. package/dist/manager/run/persistence.js +13 -0
  13. package/dist/manager/run/persistence.js.map +1 -1
  14. package/dist/provider/fallback.d.ts +159 -0
  15. package/dist/provider/fallback.d.ts.map +1 -0
  16. package/dist/provider/fallback.js +285 -0
  17. package/dist/provider/fallback.js.map +1 -0
  18. package/dist/provider/index.d.ts +2 -0
  19. package/dist/provider/index.d.ts.map +1 -1
  20. package/dist/provider/index.js +1 -0
  21. package/dist/provider/index.js.map +1 -1
  22. package/dist/provider/retry.d.ts +9 -0
  23. package/dist/provider/retry.d.ts.map +1 -1
  24. package/dist/provider/retry.js +40 -3
  25. package/dist/provider/retry.js.map +1 -1
  26. package/dist/public-runtime.d.ts +2 -2
  27. package/dist/public-runtime.d.ts.map +1 -1
  28. package/dist/public-runtime.js +1 -1
  29. package/dist/public-runtime.js.map +1 -1
  30. package/dist/run/reporter.d.ts.map +1 -1
  31. package/dist/run/reporter.js +16 -0
  32. package/dist/run/reporter.js.map +1 -1
  33. package/dist/runtime/query/index.d.ts +17 -0
  34. package/dist/runtime/query/index.d.ts.map +1 -1
  35. package/dist/runtime/query/index.js +76 -3
  36. package/dist/runtime/query/index.js.map +1 -1
  37. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  38. package/dist/runtime/query/iteration/index.js +40 -1
  39. package/dist/runtime/query/iteration/index.js.map +1 -1
  40. package/dist/runtime/query/iteration/phases/context.d.ts +17 -0
  41. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  42. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  43. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  44. package/dist/runtime/query/iteration/stream-turn.js +25 -0
  45. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  46. package/dist/types/provider/stream.d.ts +35 -0
  47. package/dist/types/provider/stream.d.ts.map +1 -1
  48. package/dist/types/run/entity.d.ts +38 -0
  49. package/dist/types/run/entity.d.ts.map +1 -1
  50. package/dist/types/run/events.d.ts +29 -0
  51. package/dist/types/run/events.d.ts.map +1 -1
  52. package/dist/types/run/events.js.map +1 -1
  53. package/dist/types/run/step.d.ts +53 -0
  54. package/dist/types/run/step.d.ts.map +1 -1
  55. package/dist/types/run/step.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/bridge/a2a/mapper.ts +16 -0
  58. package/src/bridge/sse/mapper.ts +17 -0
  59. package/src/contracts/api.ts +7 -0
  60. package/src/manager/run/persistence.ts +14 -0
  61. package/src/provider/fallback.ts +388 -0
  62. package/src/provider/index.ts +6 -0
  63. package/src/provider/retry.ts +51 -4
  64. package/src/public-runtime.ts +7 -1
  65. package/src/run/reporter.ts +20 -0
  66. package/src/runtime/query/index.ts +104 -3
  67. package/src/runtime/query/iteration/index.ts +43 -1
  68. package/src/runtime/query/iteration/phases/context.ts +17 -0
  69. package/src/runtime/query/iteration/stream-turn.ts +26 -0
  70. package/src/types/provider/stream.ts +37 -0
  71. package/src/types/run/entity.ts +38 -0
  72. package/src/types/run/events.ts +29 -0
  73. package/src/types/run/step.ts +54 -0
@@ -32,6 +32,7 @@ import type { AnswerReview } from '../../../types/run/answer-review.js'
32
32
  import type {
33
33
  PrepareStepResult,
34
34
  RunEvent,
35
+ StepProvenance,
35
36
  StepResult,
36
37
  StopReason,
37
38
  } from '../../../types/run/index.js'
@@ -425,6 +426,40 @@ export class IterationOrchestrator {
425
426
  iterSpan,
426
427
  )
427
428
 
429
+ // Who answered THIS turn.
430
+ //
431
+ // The read is exact at this point and stays exact: a chain that
432
+ // has produced output cannot fall over again inside the same
433
+ // request, so the member at the cursor when the stream ends is
434
+ // the one whose bytes are in `response`.
435
+ //
436
+ // It is taken here rather than at `recordStep` several hundred
437
+ // lines below, and the honest account of that is defence in
438
+ // depth, not a defect it currently prevents. Moving it down
439
+ // fails no test, because nothing between the two asks this
440
+ // provider for anything: compaction and working memory run
441
+ // BEFORE the turn, the advisory phase runs after the step is
442
+ // already recorded, and the only thing in between is tool
443
+ // execution. That is a fact about today's phase order, which a
444
+ // later phase inserted here would change silently — and the
445
+ // symptom would be a step attributed to a member that first
446
+ // served the turn after it, which is the class of wrongness
447
+ // this whole field exists to end.
448
+ const servedBy: StepProvenance = ((): StepProvenance => {
449
+ const member = this.ctx.servingMember?.() ?? {
450
+ index: 0,
451
+ providerId: this.ctx.provider.id,
452
+ }
453
+ return {
454
+ providerId: member.providerId,
455
+ // A member declared without a model asked for the model the
456
+ // step named — which is what the decorator does with the
457
+ // request, so this is a reading of it and not a guess.
458
+ model: member.model ?? stepModel,
459
+ chainIndex: member.index,
460
+ }
461
+ })()
462
+
428
463
  // Main-loop turn: also records the prompt size compaction reads.
429
464
  runMgr.recordTurnUsage(response.usage)
430
465
 
@@ -746,7 +781,12 @@ export class IterationOrchestrator {
746
781
  // and a caller reconstructing cost per step must see it.
747
782
  this.recordStep({
748
783
  stepNumber: iterationNum,
749
- model,
784
+ // The model this step ASKED for. It used to be `model`, the
785
+ // run's own — so a `prepareStep` that routed one step to a
786
+ // cheaper model was recorded as the expensive one, with no
787
+ // provider chain involved.
788
+ model: stepModel,
789
+ servedBy,
750
790
  messageId,
751
791
  response,
752
792
  toolResults: reviewOutcome.results,
@@ -1201,6 +1241,7 @@ export class IterationOrchestrator {
1201
1241
  private recordStep(input: {
1202
1242
  stepNumber: number
1203
1243
  model: string
1244
+ servedBy: StepProvenance
1204
1245
  messageId: MessageId
1205
1246
  response: ChatCompletionResponse
1206
1247
  toolResults: readonly ToolCallOutcome[]
@@ -1216,6 +1257,7 @@ export class IterationOrchestrator {
1216
1257
  const step: StepResult = {
1217
1258
  stepNumber: input.stepNumber,
1218
1259
  model: input.model,
1260
+ servedBy: input.servedBy,
1219
1261
  messageId: input.messageId,
1220
1262
  content: input.response.message.content,
1221
1263
  toolCalls,
@@ -6,6 +6,7 @@ import type { CompactionConfig } from '../../../../config/runtime.js'
6
6
  import type { CompletionInbox } from '../../../../gateway/completion-inbox.js'
7
7
  import type { PlanManager } from '../../../../manager/plan/lifecycle.js'
8
8
  import type { RunPersistence } from '../../../../manager/run/persistence.js'
9
+ import type { ServingMember } from '../../../../provider/fallback.js'
9
10
  import type { ActivityStore } from '../../../../store/activity/memory.js'
10
11
  import type { TaskGateway } from '../../../../types/agent/gateway.js'
11
12
  import type { WorkingMemoryProvider } from '../../../../types/agent/working-memory.js'
@@ -37,6 +38,22 @@ import type { ToolGrantSet } from '../../tool-grants.js'
37
38
 
38
39
  export interface IterationContext {
39
40
  readonly provider: LLMProvider
41
+ /**
42
+ * Which chain member `provider` will route the NEXT request to.
43
+ *
44
+ * `provider` cannot answer this itself: `withProviderFallback` keeps its
45
+ * `id` transparently equal to the head's, deliberately, because that is
46
+ * what capability negotiation and the run's `gen_ai.system` attribute are
47
+ * about. Asking the wrapper who it is gets the declaration; this gets the
48
+ * observation.
49
+ *
50
+ * Optional because a host may build an `IterationContext` without a chain
51
+ * at all. Absent, the loop attributes each step to `provider.id` and the
52
+ * model it requested, which is exactly right when nothing can fall over —
53
+ * and exactly wrong when something can, so the wiring from `query()` is
54
+ * covered end-to-end rather than by a unit test on this accessor.
55
+ */
56
+ readonly servingMember?: () => ServingMember
40
57
  /**
41
58
  * The run's `invoke_agent` span, so each iteration can parent itself to
42
59
  * it. Explicit rather than ambient because this loop is an async
@@ -258,6 +258,32 @@ export async function* streamProviderTurn(
258
258
  continue
259
259
  }
260
260
 
261
+ // A chain swap, not output, and handled beside the retry notice
262
+ // because it is the same kind of thing: a fact about HOW the answer
263
+ // is being produced, arriving on the only channel open while the
264
+ // consumer is blocked inside the provider's iterator. It carries no
265
+ // delta, so nothing below applies to it either.
266
+ if (chunk.fallback) {
267
+ await emitEvent({
268
+ type: 'provider_fallback',
269
+ runId,
270
+ iteration,
271
+ fromIndex: chunk.fallback.fromIndex,
272
+ fromProviderId: chunk.fallback.fromProviderId,
273
+ ...(chunk.fallback.fromModel !== undefined
274
+ ? { fromModel: chunk.fallback.fromModel }
275
+ : {}),
276
+ toIndex: chunk.fallback.toIndex,
277
+ toProviderId: chunk.fallback.toProviderId,
278
+ ...(chunk.fallback.toModel !== undefined ? { toModel: chunk.fallback.toModel } : {}),
279
+ code: chunk.fallback.code,
280
+ ...(chunk.fallback.status !== undefined ? { status: chunk.fallback.status } : {}),
281
+ reason: chunk.fallback.reason,
282
+ })
283
+ yield* drainPending()
284
+ continue
285
+ }
286
+
261
287
  if (chunk.error) {
262
288
  streamError = chunk.error
263
289
  break
@@ -81,6 +81,18 @@ export interface StreamChunk {
81
81
  * output.
82
82
  */
83
83
  retry?: ProviderRetryNotice
84
+
85
+ /**
86
+ * The call failed and a different member of the provider chain is taking
87
+ * over from here.
88
+ *
89
+ * Emitted by the fallback decorator, never by a driver, and it rides the
90
+ * stream for the reason {@link retry} does. Like a retry notice it carries
91
+ * no delta and must not be treated as output — and that distinction is
92
+ * load-bearing twice over, because the fallback decorator reads these
93
+ * chunks too when deciding whether output has already gone out.
94
+ */
95
+ fallback?: ProviderFallbackNotice
84
96
  }
85
97
 
86
98
  /** See {@link StreamChunk.retry}. */
@@ -96,3 +108,28 @@ export interface ProviderRetryNotice {
96
108
  /** The delay came from the server's own `Retry-After`, not backoff. */
97
109
  readonly serverDirected: boolean
98
110
  }
111
+
112
+ /**
113
+ * See {@link StreamChunk.fallback}.
114
+ *
115
+ * Both members are named, not just the new one. Naming only the replacement
116
+ * does not tell an operator which of their declared members went down, and on a
117
+ * chain of four that is the only fact they can act on.
118
+ *
119
+ * The positions are 0-based indices into the chain as the host declared it, so
120
+ * a surface can name a member the way its own configuration does rather than
121
+ * inventing a second numbering.
122
+ */
123
+ export interface ProviderFallbackNotice {
124
+ readonly fromIndex: number
125
+ readonly fromProviderId: string
126
+ readonly fromModel?: string
127
+ readonly toIndex: number
128
+ readonly toProviderId: string
129
+ readonly toModel?: string
130
+ /** Classified failure code, as `classifyProviderError` reports it. */
131
+ readonly code: string
132
+ readonly status?: number
133
+ /** The classified failure's own sentence, already redacted at its source. */
134
+ readonly reason: string
135
+ }
@@ -11,7 +11,45 @@ export interface RunStateMetadata {
11
11
  agentId: string
12
12
  agentName: string
13
13
  config: AgentRunConfig
14
+ /**
15
+ * The provider the run was CONFIGURED with — the head of the chain, and
16
+ * the counterpart of `config.model` beside it.
17
+ *
18
+ * It is a declaration, not an observation, and it stays one. After a
19
+ * provider chain falls over it still names the head, which is correct for
20
+ * a field that answers "what was asked for"; it was only ever misleading
21
+ * because nothing else answered "what served". See {@link servingProvider}
22
+ * for the run-level answer and `steps[].servedBy` for the per-step one.
23
+ */
14
24
  provider: string
25
+ /**
26
+ * The chain member the run was routed to at the end, when that is not the
27
+ * one it was configured with.
28
+ *
29
+ * Absent means the declared provider served every call. That reading holds
30
+ * for stored records too, with one bounded exception: the sdk major that
31
+ * shipped the chain could fall over without recording it, so a run from
32
+ * that release reads as "no swap" whether or not there was one. Its
33
+ * transcript still carries the `provider_fallback` events. The exception
34
+ * is one release wide and it is stated rather than migrated away, because
35
+ * a migration would have to invent the answer for exactly the records that
36
+ * do not have it.
37
+ *
38
+ * **This is the durable half.** `RunDiskStore.writeRunMeta` writes
39
+ * `metadata` and not `steps`, so on the built-in store this field is the
40
+ * whole of what survives the process. `steps[].servedBy` is the finer
41
+ * record — the one to read for "which member answered turn 4" — and it
42
+ * reaches a host only on the returned `Run`.
43
+ *
44
+ * It also covers the case no step ledger can: a run that falls over and
45
+ * then dies before a single step is recorded still has to be able to say
46
+ * whose failure ended it. That is why the wording is "routed to" and not
47
+ * "served by" — this member was asked, and on that path it answered with
48
+ * an error. It is never a member that was merely selected: the chain
49
+ * announces a replacement when it issues its request, not when it picks
50
+ * it, so a run cancelled at the swap notice does not name one.
51
+ */
52
+ servingProvider?: string
15
53
  }
16
54
 
17
55
  export type SessionMetadata = RunStateMetadata
@@ -186,6 +186,35 @@ type CoreRunEvent =
186
186
  /** The delay came from the server's own `Retry-After`. */
187
187
  serverDirected: boolean
188
188
  }
189
+ /**
190
+ * A member of the provider chain could not serve, and a later member has
191
+ * taken over. The run continues from where it stopped.
192
+ *
193
+ * This event is the feature's honesty. A chain that swapped silently would
194
+ * produce a run that succeeded while quietly not doing what the operator
195
+ * asked — served by a provider they did not choose, at a price and a
196
+ * quality they did not agree to, with nothing in the transcript saying so.
197
+ * A host is expected to SHOW this, not log it.
198
+ *
199
+ * Emitted at the moment of the swap, before the replacement request runs.
200
+ */
201
+ | {
202
+ type: 'provider_fallback'
203
+ runId: RunId
204
+ iteration: number
205
+ /** 0-based position in the chain, as the host declared it. */
206
+ fromIndex: number
207
+ fromProviderId: string
208
+ fromModel?: string
209
+ toIndex: number
210
+ toProviderId: string
211
+ toModel?: string
212
+ /** Classified failure code, as the boundary classifier reports it. */
213
+ code: string
214
+ status?: number
215
+ /** The classified failure's own sentence. */
216
+ reason: string
217
+ }
189
218
  | {
190
219
  type: 'tool_completed'
191
220
  runId: RunId
@@ -15,7 +15,46 @@ import type { ToolCall } from '../message/index.js'
15
15
  export interface StepResult {
16
16
  /** 1-based, matching `iteration` on the run events. */
17
17
  stepNumber: number
18
+ /**
19
+ * The model this step ASKED for: the run's configured model, or the
20
+ * override a `prepareStep` hook returned for this step.
21
+ *
22
+ * It used to be the run's model unconditionally — the loop passed its own
23
+ * `model` here while building the request from `step.model ?? model` a few
24
+ * lines above — so a host that routed one step to a cheaper model read the
25
+ * expensive one back out of the ledger. No chain was needed to see it.
26
+ *
27
+ * What was asked for and what answered are two facts, and after a provider
28
+ * chain falls over they differ. This is the first; {@link servedBy} is the
29
+ * second.
30
+ */
18
31
  model: string
32
+ /**
33
+ * Who actually answered, and with which model.
34
+ *
35
+ * Equal to {@link model} and to `run.metadata.provider` on every run
36
+ * without a chain, which is most of them; it diverges exactly when
37
+ * `withProviderFallback` advanced. Recorded even when it agrees, because a
38
+ * ledger that carries the fact only when it is surprising cannot be read as
39
+ * evidence — a reader could not tell "the head served" from "nobody wrote
40
+ * it down".
41
+ *
42
+ * Optional only for records that predate the field. Absence means "not
43
+ * recorded", and it is left meaning that rather than backfilled: the sdk
44
+ * shipped a chain that could fall over one release before it recorded
45
+ * which member did, so filling those in from the declared head would state
46
+ * as fact the exact thing that release got wrong, on exactly the runs
47
+ * where it was wrong. Every step this build produces has it.
48
+ *
49
+ * **Reaches a host through the returned `Run`, not through `run.json`.**
50
+ * `RunDiskStore.writeRunMeta` persists the metadata and the counters and
51
+ * does not write `steps` at all, so the built-in store carries the
52
+ * run-level {@link
53
+ * import('./entity.js').RunStateMetadata.servingProvider} and none of
54
+ * this. A host that wants per-step provenance on disk persists the `Run`
55
+ * it is handed.
56
+ */
57
+ servedBy?: StepProvenance
19
58
  messageId: MessageId
20
59
  /** Assistant text for this step, if any. */
21
60
  content: string | null
@@ -34,6 +73,21 @@ export interface StepResult {
34
73
  toolExecutionMs: number
35
74
  }
36
75
 
76
+ /**
77
+ * The chain member that served one step.
78
+ *
79
+ * `chainIndex` is a position in the chain the host declared, and it is here
80
+ * rather than derived from `providerId` because a chain may legitimately name
81
+ * the same provider twice — two models, or two credentials, on one driver.
82
+ * `providerId` alone could not tell those apart.
83
+ */
84
+ export interface StepProvenance {
85
+ readonly providerId: string
86
+ readonly model: string
87
+ /** 0 is the head, i.e. the provider the run was configured with. */
88
+ readonly chainIndex: number
89
+ }
90
+
37
91
  export interface StepToolResult {
38
92
  toolCallId: string
39
93
  toolName: string