@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.
- package/CHANGELOG.md +103 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +13 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +16 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/contracts/api.d.ts +8 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/manager/run/persistence.d.ts +11 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +13 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/provider/fallback.d.ts +159 -0
- package/dist/provider/fallback.d.ts.map +1 -0
- package/dist/provider/fallback.js +285 -0
- package/dist/provider/fallback.js.map +1 -0
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +1 -0
- package/dist/provider/index.js.map +1 -1
- package/dist/provider/retry.d.ts +9 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +40 -3
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +16 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/index.d.ts +17 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +76 -3
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +40 -1
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +17 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +25 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/types/provider/stream.d.ts +35 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/run/entity.d.ts +38 -0
- package/dist/types/run/entity.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +29 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/step.d.ts +53 -0
- package/dist/types/run/step.d.ts.map +1 -1
- package/dist/types/run/step.js.map +1 -1
- package/package.json +1 -1
- package/src/bridge/a2a/mapper.ts +16 -0
- package/src/bridge/sse/mapper.ts +17 -0
- package/src/contracts/api.ts +7 -0
- package/src/manager/run/persistence.ts +14 -0
- package/src/provider/fallback.ts +388 -0
- package/src/provider/index.ts +6 -0
- package/src/provider/retry.ts +51 -4
- package/src/public-runtime.ts +7 -1
- package/src/run/reporter.ts +20 -0
- package/src/runtime/query/index.ts +104 -3
- package/src/runtime/query/iteration/index.ts +43 -1
- package/src/runtime/query/iteration/phases/context.ts +17 -0
- package/src/runtime/query/iteration/stream-turn.ts +26 -0
- package/src/types/provider/stream.ts +37 -0
- package/src/types/run/entity.ts +38 -0
- package/src/types/run/events.ts +29 -0
- 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
|
+
}
|
package/src/types/run/entity.ts
CHANGED
|
@@ -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
|
package/src/types/run/events.ts
CHANGED
|
@@ -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
|
package/src/types/run/step.ts
CHANGED
|
@@ -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
|