@namzu/sdk 50.1.0 → 51.1.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 +28 -0
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +1 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/execution/code-runtime/types.d.ts +35 -0
- package/dist/execution/code-runtime/types.d.ts.map +1 -1
- package/dist/execution/code-runtime/types.js.map +1 -1
- package/dist/execution/code-runtime/worker-program.d.ts +1 -1
- package/dist/execution/code-runtime/worker-program.d.ts.map +1 -1
- package/dist/execution/code-runtime/worker-program.js +72 -13
- package/dist/execution/code-runtime/worker-program.js.map +1 -1
- package/dist/execution/code-runtime/worker.d.ts.map +1 -1
- package/dist/execution/code-runtime/worker.js +35 -1
- package/dist/execution/code-runtime/worker.js.map +1 -1
- package/dist/execution/workflow/errors.d.ts +5 -0
- package/dist/execution/workflow/errors.d.ts.map +1 -0
- package/dist/execution/workflow/errors.js +8 -0
- package/dist/execution/workflow/errors.js.map +1 -0
- package/dist/execution/workflow/index.d.ts +9 -0
- package/dist/execution/workflow/index.d.ts.map +1 -0
- package/dist/execution/workflow/index.js +6 -0
- package/dist/execution/workflow/index.js.map +1 -0
- package/dist/execution/workflow/journal.d.ts +18 -0
- package/dist/execution/workflow/journal.d.ts.map +1 -0
- package/dist/execution/workflow/journal.js +43 -0
- package/dist/execution/workflow/journal.js.map +1 -0
- package/dist/execution/workflow/meta.d.ts +14 -0
- package/dist/execution/workflow/meta.d.ts.map +1 -0
- package/dist/execution/workflow/meta.js +314 -0
- package/dist/execution/workflow/meta.js.map +1 -0
- package/dist/execution/workflow/phases.d.ts +32 -0
- package/dist/execution/workflow/phases.d.ts.map +1 -0
- package/dist/execution/workflow/phases.js +57 -0
- package/dist/execution/workflow/phases.js.map +1 -0
- package/dist/execution/workflow/prelude.d.ts +15 -0
- package/dist/execution/workflow/prelude.d.ts.map +1 -0
- package/dist/execution/workflow/prelude.js +128 -0
- package/dist/execution/workflow/prelude.js.map +1 -0
- package/dist/execution/workflow/runtime.d.ts +31 -0
- package/dist/execution/workflow/runtime.d.ts.map +1 -0
- package/dist/execution/workflow/runtime.js +682 -0
- package/dist/execution/workflow/runtime.js.map +1 -0
- package/dist/execution/workflow/schema.d.ts +10 -0
- package/dist/execution/workflow/schema.d.ts.map +1 -0
- package/dist/execution/workflow/schema.js +145 -0
- package/dist/execution/workflow/schema.js.map +1 -0
- package/dist/execution/workflow/types.d.ts +267 -0
- package/dist/execution/workflow/types.d.ts.map +1 -0
- package/dist/execution/workflow/types.js +7 -0
- package/dist/execution/workflow/types.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +2 -1
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/process/kill-tree.d.ts +7 -0
- package/dist/process/kill-tree.d.ts.map +1 -1
- package/dist/process/kill-tree.js +41 -0
- package/dist/process/kill-tree.js.map +1 -1
- package/dist/provider/errors.d.ts +10 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +16 -0
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/retry.d.ts +27 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +19 -3
- package/dist/provider/retry.js.map +1 -1
- package/dist/provider/token-budget.d.ts.map +1 -1
- package/dist/provider/token-budget.js +31 -2
- package/dist/provider/token-budget.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -0
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -0
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -1
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +1 -1
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +3 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/jobs/job-id.d.ts +21 -0
- package/dist/runtime/jobs/job-id.d.ts.map +1 -0
- package/dist/runtime/jobs/job-id.js +66 -0
- package/dist/runtime/jobs/job-id.js.map +1 -0
- package/dist/runtime/jobs/registry.d.ts +0 -1
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +2 -3
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +24 -1
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prompt.d.ts.map +1 -1
- package/dist/runtime/query/prompt.js +5 -1
- package/dist/runtime/query/prompt.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +8 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +20 -3
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/job.d.ts.map +1 -1
- package/dist/tools/builtins/job.js +4 -0
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/wait-for-job.d.ts.map +1 -1
- package/dist/tools/builtins/wait-for-job.js +4 -0
- package/dist/tools/builtins/wait-for-job.js.map +1 -1
- package/dist/tools/command-shell.d.ts +55 -0
- package/dist/tools/command-shell.d.ts.map +1 -1
- package/dist/tools/command-shell.js +174 -13
- package/dist/tools/command-shell.js.map +1 -1
- package/dist/types/provider/error.d.ts +6 -0
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +15 -1
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +5 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/utils/backoff.d.ts +12 -0
- package/dist/utils/backoff.d.ts.map +1 -1
- package/dist/utils/backoff.js +15 -0
- package/dist/utils/backoff.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/AGENTS.md +134 -46
- package/src/bridge/sse/mapper.ts +1 -0
- package/src/execution/code-runtime/types.ts +37 -1
- package/src/execution/code-runtime/worker-program.ts +72 -13
- package/src/execution/code-runtime/worker.ts +40 -1
- package/src/execution/workflow/errors.ts +7 -0
- package/src/execution/workflow/index.ts +8 -0
- package/src/execution/workflow/journal.ts +46 -0
- package/src/execution/workflow/meta.ts +316 -0
- package/src/execution/workflow/phases.ts +91 -0
- package/src/execution/workflow/prelude.ts +135 -0
- package/src/execution/workflow/runtime.ts +786 -0
- package/src/execution/workflow/schema.ts +137 -0
- package/src/execution/workflow/types.ts +300 -0
- package/src/index.ts +4 -0
- package/src/plugin/shell-hook.ts +2 -1
- package/src/process/kill-tree.ts +43 -0
- package/src/provider/errors.ts +18 -0
- package/src/provider/retry.ts +53 -3
- package/src/provider/token-budget.ts +31 -1
- package/src/public-runtime.ts +11 -0
- package/src/public-tools.ts +6 -0
- package/src/public-types.ts +3 -0
- package/src/runtime/jobs/job-id.ts +66 -0
- package/src/runtime/jobs/registry.ts +2 -3
- package/src/runtime/query/iteration/stream-turn.ts +24 -1
- package/src/runtime/query/prompt.ts +5 -1
- package/src/tools/builtins/bash.ts +33 -3
- package/src/tools/builtins/job.ts +4 -0
- package/src/tools/builtins/wait-for-job.ts +4 -0
- package/src/tools/command-shell.ts +194 -11
- package/src/types/provider/error.ts +6 -0
- package/src/types/provider/errors.ts +16 -1
- package/src/types/provider/stream.ts +6 -0
- package/src/types/session/events.ts +5 -0
- package/src/utils/backoff.ts +21 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small JSON Schema validator for `agent(..., { schema })`.
|
|
3
|
+
*
|
|
4
|
+
* Supported keywords: `type` (string or list), `enum`, `const`, `properties`,
|
|
5
|
+
* `required`, `additionalProperties` (boolean or schema), `items` (one
|
|
6
|
+
* schema), `minItems`, `maxItems`, `minLength`, `maxLength`, `minimum`,
|
|
7
|
+
* `maximum`, `anyOf`, `oneOf`, `allOf`. Any other keyword is ignored, and the
|
|
8
|
+
* schema is also handed to the host so a model-side structured-output
|
|
9
|
+
* mechanism can enforce more. `pattern` is deliberately not supported: the
|
|
10
|
+
* schema is model-authored and a regular expression is a CPU risk.
|
|
11
|
+
*/
|
|
12
|
+
const MAX_DEPTH = 32
|
|
13
|
+
|
|
14
|
+
function typeOf(value: unknown): string {
|
|
15
|
+
if (value === null) return 'null'
|
|
16
|
+
if (Array.isArray(value)) return 'array'
|
|
17
|
+
if (typeof value === 'number') return Number.isInteger(value) ? 'integer' : 'number'
|
|
18
|
+
return typeof value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function matchesType(value: unknown, type: string): boolean {
|
|
22
|
+
const actual = typeOf(value)
|
|
23
|
+
return actual === type || (type === 'number' && actual === 'integer')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function same(a: unknown, b: unknown): boolean {
|
|
27
|
+
return JSON.stringify(a) === JSON.stringify(b)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSchema(value: unknown): value is Record<string, unknown> {
|
|
31
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function check(value: unknown, schema: unknown, path: string, depth: number): string | undefined {
|
|
35
|
+
if (schema === true || schema === undefined) return undefined
|
|
36
|
+
if (schema === false) return `${path}: nothing is allowed here`
|
|
37
|
+
if (!isSchema(schema)) return undefined
|
|
38
|
+
if (depth > MAX_DEPTH) return `${path}: schema is nested too deeply`
|
|
39
|
+
const here = path || 'the result'
|
|
40
|
+
|
|
41
|
+
const type = schema.type
|
|
42
|
+
if (typeof type === 'string' && !matchesType(value, type)) {
|
|
43
|
+
return `${here} must be ${type}, got ${typeOf(value)}`
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(type) && !type.some((t) => typeof t === 'string' && matchesType(value, t))) {
|
|
46
|
+
return `${here} must be one of ${type.join(', ')}, got ${typeOf(value)}`
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((e) => same(e, value))) {
|
|
49
|
+
return `${here} must be one of ${JSON.stringify(schema.enum)}`
|
|
50
|
+
}
|
|
51
|
+
if ('const' in schema && !same(schema.const, value)) {
|
|
52
|
+
return `${here} must equal ${JSON.stringify(schema.const)}`
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
if (typeof schema.minLength === 'number' && value.length < schema.minLength) {
|
|
56
|
+
return `${here} is shorter than ${schema.minLength} characters`
|
|
57
|
+
}
|
|
58
|
+
if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {
|
|
59
|
+
return `${here} is longer than ${schema.maxLength} characters`
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'number') {
|
|
63
|
+
if (typeof schema.minimum === 'number' && value < schema.minimum) {
|
|
64
|
+
return `${here} is below ${schema.minimum}`
|
|
65
|
+
}
|
|
66
|
+
if (typeof schema.maximum === 'number' && value > schema.maximum) {
|
|
67
|
+
return `${here} is above ${schema.maximum}`
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
if (typeof schema.minItems === 'number' && value.length < schema.minItems) {
|
|
72
|
+
return `${here} has fewer than ${schema.minItems} items`
|
|
73
|
+
}
|
|
74
|
+
if (typeof schema.maxItems === 'number' && value.length > schema.maxItems) {
|
|
75
|
+
return `${here} has more than ${schema.maxItems} items`
|
|
76
|
+
}
|
|
77
|
+
if (schema.items !== undefined) {
|
|
78
|
+
for (const [i, item] of value.entries()) {
|
|
79
|
+
const problem = check(item, schema.items, `${path}[${i}]`, depth + 1)
|
|
80
|
+
if (problem) return problem
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
85
|
+
const object = value as Record<string, unknown>
|
|
86
|
+
if (Array.isArray(schema.required)) {
|
|
87
|
+
for (const key of schema.required) {
|
|
88
|
+
if (typeof key === 'string' && !Object.hasOwn(object, key)) {
|
|
89
|
+
return `${here} is missing "${key}"`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const properties = isSchema(schema.properties) ? schema.properties : {}
|
|
94
|
+
for (const [key, child] of Object.entries(object)) {
|
|
95
|
+
const childPath = path ? `${path}.${key}` : key
|
|
96
|
+
if (Object.hasOwn(properties, key)) {
|
|
97
|
+
const problem = check(child, properties[key], childPath, depth + 1)
|
|
98
|
+
if (problem) return problem
|
|
99
|
+
} else if (schema.additionalProperties === false) {
|
|
100
|
+
return `${here} has an unexpected property "${key}"`
|
|
101
|
+
} else if (isSchema(schema.additionalProperties)) {
|
|
102
|
+
const problem = check(child, schema.additionalProperties, childPath, depth + 1)
|
|
103
|
+
if (problem) return problem
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(schema.allOf)) {
|
|
108
|
+
for (const part of schema.allOf) {
|
|
109
|
+
const problem = check(value, part, path, depth + 1)
|
|
110
|
+
if (problem) return problem
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(schema.anyOf) && !schema.anyOf.some((p) => !check(value, p, path, depth + 1))) {
|
|
114
|
+
return `${here} matches none of the allowed shapes`
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(schema.oneOf)) {
|
|
117
|
+
const matching = schema.oneOf.filter((p) => !check(value, p, path, depth + 1)).length
|
|
118
|
+
if (matching !== 1) return `${here} must match exactly one allowed shape, matches ${matching}`
|
|
119
|
+
}
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** `undefined` when `value` satisfies `schema`, otherwise a plain sentence naming the first problem. */
|
|
124
|
+
export function validateAgainstSchema(value: unknown, schema: unknown): string | undefined {
|
|
125
|
+
return check(value, schema, '', 0)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Parse a model's text as JSON, tolerating one surrounding code fence. */
|
|
129
|
+
export function parseJsonText(text: string): { ok: true; value: unknown } | { ok: false } {
|
|
130
|
+
const trimmed = text.trim()
|
|
131
|
+
const fenced = /^```(?:json)?\s*\n([\s\S]*?)\n```$/.exec(trimmed)
|
|
132
|
+
try {
|
|
133
|
+
return { ok: true, value: JSON.parse(fenced ? (fenced[1] as string) : trimmed) }
|
|
134
|
+
} catch {
|
|
135
|
+
return { ok: false }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types of the workflow runtime: a script the model wrote once decides how
|
|
3
|
+
* many subagents run, with which prompts and under which phase titles; the
|
|
4
|
+
* runtime schedules them. See `docs/sdk/workflows.md`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** One phase declared in `meta.phases`. */
|
|
8
|
+
export interface WorkflowPhaseMeta {
|
|
9
|
+
readonly title: string
|
|
10
|
+
readonly detail?: string
|
|
11
|
+
readonly model?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The pure literal a script exports as `meta`. */
|
|
15
|
+
export interface WorkflowMeta {
|
|
16
|
+
readonly name: string
|
|
17
|
+
readonly description: string
|
|
18
|
+
readonly whenToUse?: string
|
|
19
|
+
readonly phases: readonly WorkflowPhaseMeta[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** What the host is asked to run for one `agent()` call. */
|
|
23
|
+
export interface WorkflowAgentRequest {
|
|
24
|
+
/** 1-based, in the order the script asked. */
|
|
25
|
+
readonly index: number
|
|
26
|
+
readonly label: string
|
|
27
|
+
readonly prompt: string
|
|
28
|
+
readonly phaseIndex?: number
|
|
29
|
+
readonly phaseTitle?: string
|
|
30
|
+
/** JSON Schema the result must satisfy; the runtime validates `data`. */
|
|
31
|
+
readonly schema?: Readonly<Record<string, unknown>>
|
|
32
|
+
readonly agentType?: string
|
|
33
|
+
readonly model?: string
|
|
34
|
+
readonly effort?: string
|
|
35
|
+
/** Report live progress; the runtime turns it into `workflow_agent` events. */
|
|
36
|
+
readonly onProgress: (progress: WorkflowAgentProgress) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WorkflowAgentProgress {
|
|
40
|
+
readonly tokens?: number
|
|
41
|
+
readonly toolCalls?: number
|
|
42
|
+
readonly lastToolName?: string
|
|
43
|
+
readonly lastToolSummary?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What the host's agent run came to. */
|
|
47
|
+
export interface WorkflowAgentOutcome {
|
|
48
|
+
readonly state: 'done' | 'error' | 'skipped'
|
|
49
|
+
/** The agent's final text. */
|
|
50
|
+
readonly text?: string
|
|
51
|
+
/** A structured result, when the host produced one for the schema. */
|
|
52
|
+
readonly data?: unknown
|
|
53
|
+
readonly tokens: number
|
|
54
|
+
readonly toolCalls: number
|
|
55
|
+
readonly error?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Starts one subagent through the host's own delegation path (the same
|
|
60
|
+
* policy, sandbox, accounting and cancellation as the `Agent` tool).
|
|
61
|
+
* Must honour `signal`.
|
|
62
|
+
*/
|
|
63
|
+
export type WorkflowSpawnAgent = (
|
|
64
|
+
request: WorkflowAgentRequest,
|
|
65
|
+
signal: AbortSignal,
|
|
66
|
+
) => Promise<WorkflowAgentOutcome>
|
|
67
|
+
|
|
68
|
+
export type WorkflowAgentState = 'start' | 'progress' | 'done' | 'error'
|
|
69
|
+
|
|
70
|
+
export interface WorkflowStartedEvent {
|
|
71
|
+
readonly type: 'workflow_started'
|
|
72
|
+
readonly runId: string
|
|
73
|
+
readonly name: string
|
|
74
|
+
readonly description: string
|
|
75
|
+
readonly phases: readonly WorkflowPhaseMeta[]
|
|
76
|
+
readonly at: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface WorkflowPhaseEvent {
|
|
80
|
+
readonly type: 'workflow_phase'
|
|
81
|
+
readonly runId: string
|
|
82
|
+
/** 1-based first-seen order; declared titles come first. */
|
|
83
|
+
readonly index: number
|
|
84
|
+
readonly title: string
|
|
85
|
+
readonly declared: boolean
|
|
86
|
+
readonly at: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One agent changed state. `start` without `startedAt` means queued;
|
|
91
|
+
* `start` with `startedAt` means running. A `done` agent that was skipped
|
|
92
|
+
* (by the host, the operator or the token budget) carries `skipped: true`.
|
|
93
|
+
*/
|
|
94
|
+
export interface WorkflowAgentEvent {
|
|
95
|
+
readonly type: 'workflow_agent'
|
|
96
|
+
readonly runId: string
|
|
97
|
+
readonly state: WorkflowAgentState
|
|
98
|
+
readonly index: number
|
|
99
|
+
readonly label: string
|
|
100
|
+
readonly phaseIndex?: number
|
|
101
|
+
readonly phaseTitle?: string
|
|
102
|
+
readonly agentType?: string
|
|
103
|
+
readonly model?: string
|
|
104
|
+
readonly queuedAt: number
|
|
105
|
+
readonly startedAt?: number
|
|
106
|
+
readonly finishedAt?: number
|
|
107
|
+
readonly lastProgressAt: number
|
|
108
|
+
readonly tokens: number
|
|
109
|
+
readonly toolCalls: number
|
|
110
|
+
readonly lastToolName?: string
|
|
111
|
+
readonly lastToolSummary?: string
|
|
112
|
+
readonly promptPreview: string
|
|
113
|
+
readonly resultPreview?: string
|
|
114
|
+
readonly error?: string
|
|
115
|
+
readonly skipped?: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface WorkflowLogEvent {
|
|
119
|
+
readonly type: 'workflow_log'
|
|
120
|
+
readonly runId: string
|
|
121
|
+
readonly message: string
|
|
122
|
+
readonly at: number
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type WorkflowFinishStatus = 'completed' | 'failed' | 'stopped'
|
|
126
|
+
|
|
127
|
+
export interface WorkflowUsage {
|
|
128
|
+
/** Agents the script asked for and the runtime accepted. */
|
|
129
|
+
readonly agents: number
|
|
130
|
+
readonly agentsDone: number
|
|
131
|
+
readonly agentsFailed: number
|
|
132
|
+
readonly agentsSkipped: number
|
|
133
|
+
readonly tokens: number
|
|
134
|
+
readonly toolCalls: number
|
|
135
|
+
readonly elapsedMs: number
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface WorkflowFinishedEvent {
|
|
139
|
+
readonly type: 'workflow_finished'
|
|
140
|
+
readonly runId: string
|
|
141
|
+
readonly status: WorkflowFinishStatus
|
|
142
|
+
readonly result?: unknown
|
|
143
|
+
readonly error?: string
|
|
144
|
+
readonly usage: WorkflowUsage
|
|
145
|
+
readonly at: number
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type WorkflowEvent =
|
|
149
|
+
| WorkflowStartedEvent
|
|
150
|
+
| WorkflowPhaseEvent
|
|
151
|
+
| WorkflowAgentEvent
|
|
152
|
+
| WorkflowLogEvent
|
|
153
|
+
| WorkflowFinishedEvent
|
|
154
|
+
|
|
155
|
+
export type WorkflowPhaseStatus = 'not-started' | 'running' | 'done' | 'failed'
|
|
156
|
+
|
|
157
|
+
export interface WorkflowPhaseSummary {
|
|
158
|
+
/** 0 for the derived "Agents" group, which has no phase index. */
|
|
159
|
+
readonly index: number
|
|
160
|
+
readonly title: string
|
|
161
|
+
readonly detail?: string
|
|
162
|
+
readonly status: WorkflowPhaseStatus
|
|
163
|
+
readonly agents: number
|
|
164
|
+
readonly done: number
|
|
165
|
+
readonly failed: number
|
|
166
|
+
readonly tokens: number
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Append-only audit records, one JSON object per line. */
|
|
170
|
+
export type WorkflowJournalRecord =
|
|
171
|
+
| {
|
|
172
|
+
readonly type: 'run_started'
|
|
173
|
+
readonly at: number
|
|
174
|
+
readonly runId: string
|
|
175
|
+
readonly scriptSha256: string
|
|
176
|
+
readonly scriptBytes: number
|
|
177
|
+
readonly meta: WorkflowMeta
|
|
178
|
+
readonly args: unknown
|
|
179
|
+
readonly limits: WorkflowResolvedLimits
|
|
180
|
+
}
|
|
181
|
+
| {
|
|
182
|
+
readonly type: 'phase'
|
|
183
|
+
readonly at: number
|
|
184
|
+
readonly index: number
|
|
185
|
+
readonly title: string
|
|
186
|
+
readonly declared: boolean
|
|
187
|
+
}
|
|
188
|
+
| {
|
|
189
|
+
readonly type: 'agent_request'
|
|
190
|
+
readonly at: number
|
|
191
|
+
readonly index: number
|
|
192
|
+
readonly label: string
|
|
193
|
+
readonly phaseIndex?: number
|
|
194
|
+
readonly phaseTitle?: string
|
|
195
|
+
readonly promptPreview: string
|
|
196
|
+
readonly promptSha256: string
|
|
197
|
+
readonly options: {
|
|
198
|
+
readonly agentType?: string
|
|
199
|
+
readonly model?: string
|
|
200
|
+
readonly effort?: string
|
|
201
|
+
readonly schema: boolean
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
| {
|
|
205
|
+
readonly type: 'agent_outcome'
|
|
206
|
+
readonly at: number
|
|
207
|
+
readonly index: number
|
|
208
|
+
readonly state: 'done' | 'error' | 'skipped'
|
|
209
|
+
readonly tokens: number
|
|
210
|
+
readonly toolCalls: number
|
|
211
|
+
readonly queuedAt: number
|
|
212
|
+
readonly startedAt?: number
|
|
213
|
+
readonly finishedAt: number
|
|
214
|
+
readonly resultPreview?: string
|
|
215
|
+
readonly error?: string
|
|
216
|
+
}
|
|
217
|
+
| { readonly type: 'log'; readonly at: number; readonly message: string }
|
|
218
|
+
| {
|
|
219
|
+
readonly type: 'run_finished'
|
|
220
|
+
readonly at: number
|
|
221
|
+
readonly status: WorkflowFinishStatus
|
|
222
|
+
readonly result?: unknown
|
|
223
|
+
readonly error?: string
|
|
224
|
+
readonly usage: WorkflowUsage
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Where a run's audit trail goes. Injected so the SDK stays host-agnostic. */
|
|
228
|
+
export interface WorkflowJournal {
|
|
229
|
+
/** Called once, before the first record, with the exact script text. */
|
|
230
|
+
writeScript?(source: string, sha256: string): Promise<void> | void
|
|
231
|
+
append(record: WorkflowJournalRecord): Promise<void> | void
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface WorkflowLimits {
|
|
235
|
+
/** Agents running at once. Default `min(8, cpus - 2)`, at least 1; at most 16. */
|
|
236
|
+
readonly maxConcurrency?: number
|
|
237
|
+
/** Agents one run may start. Default 200; at most 1000. */
|
|
238
|
+
readonly maxAgents?: number
|
|
239
|
+
/** UTF-8 script size. Default 256 KiB. */
|
|
240
|
+
readonly maxSourceBytes?: number
|
|
241
|
+
/** Interpreter memory. Default 64 MiB. */
|
|
242
|
+
readonly memoryLimitBytes?: number
|
|
243
|
+
/** One value crossing the bridge, either way. Default 1 MiB. */
|
|
244
|
+
readonly maxValueBytes?: number
|
|
245
|
+
/** Stop admitting new agents once this many tokens are spent. Default: none. */
|
|
246
|
+
readonly tokenBudget?: number
|
|
247
|
+
/** Wall clock for the whole run. Default 6 hours. */
|
|
248
|
+
readonly maxDurationMs?: number
|
|
249
|
+
/** One synchronous stretch of script execution. Default 2 seconds. */
|
|
250
|
+
readonly cpuSliceMs?: number
|
|
251
|
+
/** All synchronous script execution together. Default 60 seconds. */
|
|
252
|
+
readonly cpuTotalMs?: number
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface WorkflowResolvedLimits {
|
|
256
|
+
readonly maxConcurrency: number
|
|
257
|
+
readonly maxAgents: number
|
|
258
|
+
readonly maxSourceBytes: number
|
|
259
|
+
readonly memoryLimitBytes: number
|
|
260
|
+
readonly maxValueBytes: number
|
|
261
|
+
readonly tokenBudget?: number
|
|
262
|
+
readonly maxDurationMs: number
|
|
263
|
+
readonly cpuSliceMs: number
|
|
264
|
+
readonly cpuTotalMs: number
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Lets the host act on one running agent. Handed to `onControl`. */
|
|
268
|
+
export interface WorkflowControl {
|
|
269
|
+
/** Skip an agent: queued ones never start, running ones are aborted. Its `agent()` resolves `null`. */
|
|
270
|
+
skipAgent(index: number): boolean
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface WorkflowRunOptions {
|
|
274
|
+
/** The script, including its `export const meta` literal. Untrusted. */
|
|
275
|
+
readonly script: string
|
|
276
|
+
/** JSON handed to the script as `args`. */
|
|
277
|
+
readonly args?: unknown
|
|
278
|
+
readonly limits?: WorkflowLimits
|
|
279
|
+
readonly spawnAgent: WorkflowSpawnAgent
|
|
280
|
+
readonly journal?: WorkflowJournal
|
|
281
|
+
readonly signal?: AbortSignal
|
|
282
|
+
readonly onEvent?: (event: WorkflowEvent) => void
|
|
283
|
+
readonly onControl?: (control: WorkflowControl) => void
|
|
284
|
+
/** Default: a fresh `w` + 8 base36 id. */
|
|
285
|
+
readonly runId?: string
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface WorkflowRunResult {
|
|
289
|
+
readonly runId: string
|
|
290
|
+
readonly status: WorkflowFinishStatus
|
|
291
|
+
/** What the script returned (`completed` only). */
|
|
292
|
+
readonly result?: unknown
|
|
293
|
+
readonly error?: string
|
|
294
|
+
readonly usage: WorkflowUsage
|
|
295
|
+
readonly phases: readonly WorkflowPhaseSummary[]
|
|
296
|
+
/** True when the script logged more than the output cap and later lines were dropped. */
|
|
297
|
+
readonly logsTruncated: boolean
|
|
298
|
+
/** Set when the journal could not be written; the run itself was not affected. */
|
|
299
|
+
readonly journalError?: string
|
|
300
|
+
}
|
package/src/index.ts
CHANGED
package/src/plugin/shell-hook.ts
CHANGED
|
@@ -231,7 +231,8 @@ export function runShellHook(
|
|
|
231
231
|
// its own — the hook's deadline becomes the grandchild's.
|
|
232
232
|
detached: process.platform !== 'win32',
|
|
233
233
|
// A hook has always run under `sh`, whatever shell the `bash` tool
|
|
234
|
-
// chose. Windows has no `sh`, so there the host's own shell runs it
|
|
234
|
+
// chose. Windows has no `sh`, so there the host's own shell runs it: Git Bash
|
|
235
|
+
// when installed, cmd.exe when not.
|
|
235
236
|
...(process.platform === 'win32'
|
|
236
237
|
? {}
|
|
237
238
|
: { shell: { path: 'sh', dialect: 'sh', source: 'sh' } as const }),
|
package/src/process/kill-tree.ts
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process'
|
|
2
2
|
import type { spawn } from 'node:child_process'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Git Bash children that the Windows process tree cannot reach.
|
|
6
|
+
*
|
|
7
|
+
* Git for Windows' msys runtime starts a command that is not the last of its
|
|
8
|
+
* line by `fork`, and the process that forked is gone by the time the command
|
|
9
|
+
* runs: the command's Windows parent no longer exists, so `taskkill /T` walks
|
|
10
|
+
* from the shell to its child and never finds the grandchild (`sleep 60; echo`,
|
|
11
|
+
* `cd app && npm start & wait`). Each such shell is started with a unique tag
|
|
12
|
+
* in its environment, which every descendant inherits however it was started;
|
|
13
|
+
* the tag is how the tree is found again.
|
|
14
|
+
*/
|
|
15
|
+
const MSYS_SHELLS = new WeakMap<object, { readonly bash: string; readonly tag: string }>()
|
|
16
|
+
|
|
17
|
+
/** Remember how to find every process a Git Bash child started. Called by the spawn. */
|
|
18
|
+
export function registerMsysShell(child: object, shell: { bash: string; tag: string }): void {
|
|
19
|
+
MSYS_SHELLS.set(child, shell)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The environment variable that carries the tag. */
|
|
23
|
+
export const MSYS_TAG_VARIABLE = 'NAMZU_SHELL_TAG'
|
|
24
|
+
|
|
25
|
+
/** Kill every process whose environment carries the tag, from inside the msys runtime. */
|
|
26
|
+
function killMsysTagged(shell: { readonly bash: string; readonly tag: string }): void {
|
|
27
|
+
// `$$` is skipped so the killer cannot end itself. Parameter expansion keeps
|
|
28
|
+
// the script to the two programs (grep, kill) the launcher puts on PATH.
|
|
29
|
+
const script = [
|
|
30
|
+
`for f in $(grep -laF "${MSYS_TAG_VARIABLE}=${shell.tag}" /proc/[0-9]*/environ 2>/dev/null); do`,
|
|
31
|
+
'p=${f#/proc/}; p=${p%/environ}; [ "$p" != "$$" ] && kill -9 $p;',
|
|
32
|
+
'done',
|
|
33
|
+
].join(' ')
|
|
34
|
+
try {
|
|
35
|
+
execFileSync(shell.bash, ['-c', script], {
|
|
36
|
+
stdio: 'ignore',
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
timeout: 5000,
|
|
39
|
+
})
|
|
40
|
+
} catch {
|
|
41
|
+
// Nothing tagged was left, or the runtime could not start: taskkill still runs.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
4
45
|
/** The system's own taskkill, so a PATH entry cannot stand in for it. */
|
|
5
46
|
function taskkillPath(): string {
|
|
6
47
|
const root = process.env.SystemRoot ?? process.env.windir
|
|
@@ -46,6 +87,8 @@ export function killTree(child: ReturnType<typeof spawn>, signal: NodeJS.Signals
|
|
|
46
87
|
if (!child.pid) return
|
|
47
88
|
try {
|
|
48
89
|
if (process.platform === 'win32') {
|
|
90
|
+
const msys = MSYS_SHELLS.get(child)
|
|
91
|
+
if (msys !== undefined) killMsysTagged(msys)
|
|
49
92
|
execFileSync(taskkillPath(), ['/pid', String(child.pid), '/t', '/f'], {
|
|
50
93
|
stdio: 'ignore',
|
|
51
94
|
windowsHide: true,
|
package/src/provider/errors.ts
CHANGED
|
@@ -76,6 +76,8 @@ export class ProviderRequestError extends Error {
|
|
|
76
76
|
* See {@link vendorDetail} for what is kept and what is scrubbed.
|
|
77
77
|
*/
|
|
78
78
|
public readonly detail?: string
|
|
79
|
+
/** The provider's `x-should-retry` verdict, when it sent one; see {@link ProviderRequestErrorInit.shouldRetry}. */
|
|
80
|
+
public readonly shouldRetry?: boolean
|
|
79
81
|
|
|
80
82
|
constructor(init: ProviderRequestErrorInit) {
|
|
81
83
|
super(buildProviderErrorMessage(init))
|
|
@@ -87,6 +89,7 @@ export class ProviderRequestError extends Error {
|
|
|
87
89
|
if (init.status !== undefined) this.status = init.status
|
|
88
90
|
if (init.retryAfterMs !== undefined) this.retryAfterMs = init.retryAfterMs
|
|
89
91
|
if (init.detail !== undefined) this.detail = init.detail
|
|
92
|
+
if (init.shouldRetry !== undefined) this.shouldRetry = init.shouldRetry
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -273,6 +276,15 @@ export function bodySaysContextOverflow(body: string | undefined | null): boolea
|
|
|
273
276
|
* a negative delay is worse than none, because a caller would treat it as
|
|
274
277
|
* "retry immediately" against a provider that just asked it to wait.
|
|
275
278
|
*/
|
|
279
|
+
/**
|
|
280
|
+
* `x-should-retry` is only read when it says `false`: that is the provider
|
|
281
|
+
* refusing a resend, and it outranks the status code. `true` changes nothing,
|
|
282
|
+
* because a retryable status is already retried.
|
|
283
|
+
*/
|
|
284
|
+
export function parseShouldRetry(value: string | null | undefined): false | undefined {
|
|
285
|
+
return value?.trim().toLowerCase() === 'false' ? false : undefined
|
|
286
|
+
}
|
|
287
|
+
|
|
276
288
|
export function parseRetryAfterMs(
|
|
277
289
|
headerValue: string | null | undefined,
|
|
278
290
|
now: number = Date.now(),
|
|
@@ -525,6 +537,7 @@ export function providerVendorError(input: {
|
|
|
525
537
|
input.retryAfter ?? vendorRetryAfter(error),
|
|
526
538
|
input.now ?? Date.now(),
|
|
527
539
|
)
|
|
540
|
+
const shouldRetry = parseShouldRetry(vendorHeader(error, ['x-should-retry']))
|
|
528
541
|
const fields = error as { type?: unknown; param?: unknown; requestID?: unknown } | null
|
|
529
542
|
// A message that already carries its body (Anthropic's) has the fields; one that is only the
|
|
530
543
|
// parsed sentence (OpenAI's) gets them from the SDK error's own properties.
|
|
@@ -544,6 +557,7 @@ export function providerVendorError(input: {
|
|
|
544
557
|
...(status !== undefined ? { status } : {}),
|
|
545
558
|
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
546
559
|
...(detail !== undefined ? { detail } : {}),
|
|
560
|
+
...(shouldRetry !== undefined ? { shouldRetry } : {}),
|
|
547
561
|
})
|
|
548
562
|
}
|
|
549
563
|
|
|
@@ -605,6 +619,8 @@ export function providerHttpError(input: {
|
|
|
605
619
|
readonly retryAfter?: string | null
|
|
606
620
|
/** The response's request ID header, when the driver read one. */
|
|
607
621
|
readonly requestId?: string | null
|
|
622
|
+
/** The response's `x-should-retry` header, when the driver read one. */
|
|
623
|
+
readonly shouldRetry?: string | null
|
|
608
624
|
readonly now?: number
|
|
609
625
|
}): ProviderRequestError {
|
|
610
626
|
const kind = classifyProviderHttpStatus(input.status, input.body)
|
|
@@ -621,6 +637,7 @@ export function providerHttpError(input: {
|
|
|
621
637
|
})
|
|
622
638
|
: vendorDetail(input.body)
|
|
623
639
|
const exactProviderCode = providerCode(input.body)
|
|
640
|
+
const shouldRetry = parseShouldRetry(input.shouldRetry)
|
|
624
641
|
return new ProviderRequestError({
|
|
625
642
|
kind,
|
|
626
643
|
providerId: input.providerId,
|
|
@@ -628,5 +645,6 @@ export function providerHttpError(input: {
|
|
|
628
645
|
status: input.status,
|
|
629
646
|
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
630
647
|
...(detail !== undefined ? { detail } : {}),
|
|
648
|
+
...(shouldRetry !== undefined ? { shouldRetry } : {}),
|
|
631
649
|
})
|
|
632
650
|
}
|
package/src/provider/retry.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { GENAI } from '../constants/telemetry/index.js'
|
|
2
2
|
import { classifyProviderError, isAbortError } from '../types/provider/errors.js'
|
|
3
3
|
import type { ChatCompletionParams, LLMProvider, StreamChunk } from '../types/provider/index.js'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type BackoffPolicy,
|
|
6
|
+
backoffProportional,
|
|
7
|
+
backoffWithJitter,
|
|
8
|
+
sleep,
|
|
9
|
+
} from '../utils/backoff.js'
|
|
5
10
|
import type { Logger } from '../utils/logger.js'
|
|
6
11
|
import { isProviderRequestError } from './errors.js'
|
|
7
12
|
|
|
@@ -31,6 +36,33 @@ export interface ProviderRetryConfig extends BackoffPolicy {
|
|
|
31
36
|
* still slept exactly as instructed.
|
|
32
37
|
*/
|
|
33
38
|
readonly maxRetryAfterMs: number
|
|
39
|
+
/**
|
|
40
|
+
* Shape of the random part of a backoff.
|
|
41
|
+
*
|
|
42
|
+
* `'full'` (the default) sleeps a uniform amount in `[0, backoff]`.
|
|
43
|
+
* `'proportional'` sleeps the backoff plus up to a quarter of it, so a step
|
|
44
|
+
* is never shorter than its exponential value.
|
|
45
|
+
*/
|
|
46
|
+
readonly jitter?: 'full' | 'proportional'
|
|
47
|
+
/**
|
|
48
|
+
* What a server-named wait means.
|
|
49
|
+
*
|
|
50
|
+
* `'exact'` (the default) sleeps exactly what `Retry-After` said.
|
|
51
|
+
* `'floor'` sleeps the longer of that and the backoff for this attempt, so
|
|
52
|
+
* a server that says "1 second" on the seventh failure is still given the
|
|
53
|
+
* backoff's thirty. The ceiling above applies either way.
|
|
54
|
+
*/
|
|
55
|
+
readonly retryAfter?: 'exact' | 'floor'
|
|
56
|
+
/**
|
|
57
|
+
* Also retry a stream that failed AFTER it produced text or reasoning.
|
|
58
|
+
*
|
|
59
|
+
* Off by default, because the decorator cannot un-emit: the consumer has
|
|
60
|
+
* already seen those deltas. When on, the retry notice carries
|
|
61
|
+
* `restart: true` and the consumer must discard what the failed attempt
|
|
62
|
+
* produced; the runtime does this itself. A stream that has begun a tool
|
|
63
|
+
* call is never restarted, because the call may already be underway.
|
|
64
|
+
*/
|
|
65
|
+
readonly retryMidStream?: boolean
|
|
34
66
|
}
|
|
35
67
|
|
|
36
68
|
export const DEFAULT_PROVIDER_RETRY: ProviderRetryConfig = {
|
|
@@ -97,6 +129,9 @@ export function withProviderRetry(
|
|
|
97
129
|
async function* chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk> {
|
|
98
130
|
for (let attempt = 0; ; attempt++) {
|
|
99
131
|
let produced = false
|
|
132
|
+
// A tool call or hosted tool began: past this point a restart could
|
|
133
|
+
// repeat work that is already underway, so it is never offered.
|
|
134
|
+
let acted = false
|
|
100
135
|
try {
|
|
101
136
|
for await (const chunk of provider.chatStream(params)) {
|
|
102
137
|
// An error-only chunk is a failure report, not output: it
|
|
@@ -106,6 +141,9 @@ export function withProviderRetry(
|
|
|
106
141
|
chunk.error !== undefined && !chunk.delta.content && !chunk.delta.toolCalls?.length
|
|
107
142
|
|
|
108
143
|
if (!isErrorOnly) produced = true
|
|
144
|
+
if (chunk.delta.toolCalls?.length || chunk.delta.toolCallEnd || chunk.delta.hostedTool) {
|
|
145
|
+
acted = true
|
|
146
|
+
}
|
|
109
147
|
|
|
110
148
|
if (isErrorOnly && !produced && attempt < config.maxRetries) {
|
|
111
149
|
throw new Error(chunk.error)
|
|
@@ -132,7 +170,8 @@ export function withProviderRetry(
|
|
|
132
170
|
const classified = classifyProviderError(err, provider.id)
|
|
133
171
|
const exhausted = attempt >= config.maxRetries
|
|
134
172
|
|
|
135
|
-
|
|
173
|
+
const restartable = produced && config.retryMidStream === true && !acted
|
|
174
|
+
if ((produced && !restartable) || !classified.retryable || exhausted) {
|
|
136
175
|
log?.warn('Provider call failed', {
|
|
137
176
|
[GENAI.SYSTEM]: provider.id,
|
|
138
177
|
'namzu.provider.code': classified.code,
|
|
@@ -200,7 +239,16 @@ export function withProviderRetry(
|
|
|
200
239
|
throw isProviderRequestError(err) ? err : classified
|
|
201
240
|
}
|
|
202
241
|
|
|
203
|
-
const
|
|
242
|
+
const backoff =
|
|
243
|
+
config.jitter === 'proportional'
|
|
244
|
+
? backoffProportional(attempt, config, random)
|
|
245
|
+
: backoffWithJitter(attempt, config, random)
|
|
246
|
+
const delay =
|
|
247
|
+
serverDirected === undefined
|
|
248
|
+
? backoff
|
|
249
|
+
: config.retryAfter === 'floor'
|
|
250
|
+
? Math.max(serverDirected, backoff)
|
|
251
|
+
: serverDirected
|
|
204
252
|
|
|
205
253
|
log?.warn('Provider call failed — retrying', {
|
|
206
254
|
[GENAI.SYSTEM]: provider.id,
|
|
@@ -210,6 +258,7 @@ export function withProviderRetry(
|
|
|
210
258
|
'namzu.provider.max_retries': config.maxRetries,
|
|
211
259
|
'namzu.provider.delay_ms': delay,
|
|
212
260
|
'namzu.provider.server_directed': serverDirected !== undefined,
|
|
261
|
+
'namzu.provider.restart': restartable,
|
|
213
262
|
})
|
|
214
263
|
|
|
215
264
|
// Tell the consumer BEFORE sleeping. With the default policy a
|
|
@@ -231,6 +280,7 @@ export function withProviderRetry(
|
|
|
231
280
|
code: classified.code,
|
|
232
281
|
...(classified.status !== undefined ? { status: classified.status } : {}),
|
|
233
282
|
serverDirected: serverDirected !== undefined,
|
|
283
|
+
...(restartable ? { restart: true } : {}),
|
|
234
284
|
},
|
|
235
285
|
}
|
|
236
286
|
|