@namzu/sdk 31.0.0 → 32.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 +67 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +4 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +4 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/runAgent.d.ts +4 -0
- package/dist/agents/runAgent.d.ts.map +1 -1
- package/dist/agents/runAgent.js +4 -0
- package/dist/agents/runAgent.js.map +1 -1
- package/dist/directory/derive-supervisor.d.ts.map +1 -1
- package/dist/directory/derive-supervisor.js +5 -0
- package/dist/directory/derive-supervisor.js.map +1 -1
- package/dist/directory/derive.d.ts.map +1 -1
- package/dist/directory/derive.js +5 -0
- package/dist/directory/derive.js.map +1 -1
- package/dist/directory/load.d.ts.map +1 -1
- package/dist/directory/load.js +1 -0
- package/dist/directory/load.js.map +1 -1
- package/dist/directory/types.d.ts +2 -0
- package/dist/directory/types.d.ts.map +1 -1
- package/dist/execution/code-runtime/types.d.ts +11 -1
- 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.d.ts.map +1 -1
- package/dist/execution/code-runtime/worker.js +67 -9
- package/dist/execution/code-runtime/worker.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +1 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/provider/errors.d.ts +1 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +39 -1
- package/dist/provider/errors.js.map +1 -1
- package/dist/public-runtime.d.ts +5 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +7 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +6 -1
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +214 -29
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +5 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +1 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +1 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +34 -14
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.js +2 -2
- package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts +13 -0
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -0
- package/dist/runtime/query/iteration/provider-rejected-image.js +91 -0
- package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +4 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +7 -3
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/request-rich-content.d.ts +13 -0
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +153 -17
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +1 -0
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/store/attachment/index.d.ts +24 -2
- package/dist/store/attachment/index.d.ts.map +1 -1
- package/dist/store/attachment/index.js +52 -5
- package/dist/store/attachment/index.js.map +1 -1
- package/dist/tools/builtins/lsp.d.ts +1 -1
- package/dist/tools/builtins/run-code.d.ts.map +1 -1
- package/dist/tools/builtins/run-code.js +15 -5
- package/dist/tools/builtins/run-code.js.map +1 -1
- package/dist/types/agent/base.d.ts +4 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/factory.d.ts +1 -0
- package/dist/types/agent/factory.d.ts.map +1 -1
- package/dist/types/message/index.d.ts +35 -4
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +42 -0
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/provider/error.d.ts +4 -1
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/index.d.ts +1 -1
- package/dist/types/provider/index.d.ts.map +1 -1
- package/dist/types/provider/model.d.ts +12 -0
- package/dist/types/provider/model.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +10 -2
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/tool/index.d.ts +52 -6
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +4 -0
- package/src/agents/SupervisorAgent.ts +4 -0
- package/src/agents/runAgent.ts +8 -0
- package/src/directory/derive-supervisor.ts +5 -0
- package/src/directory/derive.ts +5 -0
- package/src/directory/load.ts +1 -0
- package/src/directory/types.ts +2 -0
- package/src/execution/code-runtime/types.ts +15 -1
- package/src/execution/code-runtime/worker.ts +72 -9
- package/src/manager/agent/lifecycle.ts +1 -0
- package/src/provider/errors.ts +40 -1
- package/src/public-runtime.ts +16 -1
- package/src/public-types.ts +11 -0
- package/src/run/reporter.ts +7 -1
- package/src/runtime/query/executor.ts +235 -26
- package/src/runtime/query/index.ts +6 -0
- package/src/runtime/query/iteration/index.ts +69 -29
- package/src/runtime/query/iteration/phases/advisory.ts +2 -2
- package/src/runtime/query/iteration/provider-rejected-image.ts +106 -0
- package/src/runtime/query/iteration/stream-turn.ts +12 -2
- package/src/runtime/query/request-rich-content.ts +188 -15
- package/src/runtime/query/result.ts +1 -0
- package/src/store/attachment/index.ts +100 -15
- package/src/tools/builtins/run-code.ts +15 -5
- package/src/types/agent/base.ts +4 -0
- package/src/types/agent/factory.ts +1 -0
- package/src/types/message/index.ts +81 -4
- package/src/types/provider/error.ts +4 -1
- package/src/types/provider/index.ts +1 -1
- package/src/types/provider/model.ts +13 -0
- package/src/types/run/events.ts +18 -4
- package/src/types/tool/index.ts +74 -9
|
@@ -29,6 +29,7 @@ import type {
|
|
|
29
29
|
RequestToolPause,
|
|
30
30
|
SkillRegistryRef,
|
|
31
31
|
ToolContext,
|
|
32
|
+
ToolDispatchOptions,
|
|
32
33
|
ToolRegistryContract,
|
|
33
34
|
ToolResult,
|
|
34
35
|
} from '../../types/tool/index.js'
|
|
@@ -71,6 +72,103 @@ export const DEFAULT_TOOL_TIMEOUT_MS = 120_000
|
|
|
71
72
|
*/
|
|
72
73
|
export const DEFAULT_TOOL_CONCURRENCY = 8
|
|
73
74
|
|
|
75
|
+
/** Maximum UTF-8 size of one ephemeral tool-progress update. */
|
|
76
|
+
const MAX_TOOL_PROGRESS_BYTES = 8 * 1024
|
|
77
|
+
|
|
78
|
+
/** A visible marker: this event is a display projection, not durable output. */
|
|
79
|
+
const TOOL_PROGRESS_OMISSION = '… '
|
|
80
|
+
|
|
81
|
+
function boundedToolProgress(message: string): string {
|
|
82
|
+
if (Buffer.byteLength(message, 'utf8') <= MAX_TOOL_PROGRESS_BYTES) return message
|
|
83
|
+
|
|
84
|
+
const tailBudget = MAX_TOOL_PROGRESS_BYTES - Buffer.byteLength(TOOL_PROGRESS_OMISSION, 'utf8')
|
|
85
|
+
let low = 0
|
|
86
|
+
let high = message.length
|
|
87
|
+
while (low < high) {
|
|
88
|
+
const middle = Math.floor((low + high) / 2)
|
|
89
|
+
if (Buffer.byteLength(message.slice(middle), 'utf8') > tailBudget) low = middle + 1
|
|
90
|
+
else high = middle
|
|
91
|
+
}
|
|
92
|
+
// Do not turn the low half of a retained surrogate pair into U+FFFD.
|
|
93
|
+
if (
|
|
94
|
+
low > 0 &&
|
|
95
|
+
low < message.length &&
|
|
96
|
+
message.charCodeAt(low) >= 0xdc00 &&
|
|
97
|
+
message.charCodeAt(low) <= 0xdfff &&
|
|
98
|
+
message.charCodeAt(low - 1) >= 0xd800 &&
|
|
99
|
+
message.charCodeAt(low - 1) <= 0xdbff
|
|
100
|
+
) {
|
|
101
|
+
low += 1
|
|
102
|
+
}
|
|
103
|
+
return `${TOOL_PROGRESS_OMISSION}${message.slice(low)}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Latest-state publisher for one tool call.
|
|
108
|
+
*
|
|
109
|
+
* `ToolContext.report()` is synchronous by contract, while host event
|
|
110
|
+
* listeners may be arbitrarily slow. Starting one promise per report makes a
|
|
111
|
+
* chatty tool an unbounded allocation source. Progress is state rather than a
|
|
112
|
+
* transcript, so one in-flight update plus the latest pending update is the
|
|
113
|
+
* complete useful working set; intermediate states may be replaced.
|
|
114
|
+
*/
|
|
115
|
+
class ToolProgressPublisher {
|
|
116
|
+
private pending: { readonly message: string; readonly fraction?: number } | undefined
|
|
117
|
+
private draining: Promise<void> | null = null
|
|
118
|
+
private accepting = true
|
|
119
|
+
|
|
120
|
+
constructor(
|
|
121
|
+
private readonly emitEvent: EmitEvent,
|
|
122
|
+
private readonly base: Omit<
|
|
123
|
+
Extract<RunEvent, { type: 'tool_progress' }>,
|
|
124
|
+
'message' | 'fraction'
|
|
125
|
+
>,
|
|
126
|
+
) {}
|
|
127
|
+
|
|
128
|
+
report(message: string, fraction?: number): void {
|
|
129
|
+
if (!this.accepting) return
|
|
130
|
+
this.pending = {
|
|
131
|
+
message,
|
|
132
|
+
...(fraction !== undefined ? { fraction: Math.min(1, Math.max(0, fraction)) } : {}),
|
|
133
|
+
}
|
|
134
|
+
this.startDrain()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async close(): Promise<void> {
|
|
138
|
+
this.accepting = false
|
|
139
|
+
this.startDrain()
|
|
140
|
+
while (this.draining) await this.draining
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private startDrain(): void {
|
|
144
|
+
if (this.draining || !this.pending) return
|
|
145
|
+
const current = this.drain()
|
|
146
|
+
this.draining = current
|
|
147
|
+
void current.finally(() => {
|
|
148
|
+
if (this.draining !== current) return
|
|
149
|
+
this.draining = null
|
|
150
|
+
// A report can land after the final loop check but before this
|
|
151
|
+
// settlement callback. It is still an accepted update and must be
|
|
152
|
+
// drained even when close() has since stopped new reports.
|
|
153
|
+
this.startDrain()
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async drain(): Promise<void> {
|
|
158
|
+
while (this.pending) {
|
|
159
|
+
const update = this.pending
|
|
160
|
+
this.pending = undefined
|
|
161
|
+
// A progress observer is diagnostic. Its failure cannot become a tool
|
|
162
|
+
// failure, and report() never hands a rejection back to the tool.
|
|
163
|
+
await this.emitEvent({
|
|
164
|
+
...this.base,
|
|
165
|
+
message: boundedToolProgress(update.message),
|
|
166
|
+
...(update.fraction !== undefined ? { fraction: update.fraction } : {}),
|
|
167
|
+
}).catch(() => {})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
74
172
|
/**
|
|
75
173
|
* Re-runs granted to a `post_tool_use` hook that returns `{action:'retry'}`
|
|
76
174
|
* on a tool which did not opt into {@link ToolDefinition.maxRetries}.
|
|
@@ -548,33 +646,35 @@ export class ToolExecutor {
|
|
|
548
646
|
// Per-call, because the event has to name which call it is about:
|
|
549
647
|
// a batch can run several tools at once and a host rendering them
|
|
550
648
|
// side by side needs to know whose progress this is.
|
|
649
|
+
const progress = new ToolProgressPublisher(this.emitEvent, {
|
|
650
|
+
type: 'tool_progress',
|
|
651
|
+
runId: this.config.runId,
|
|
652
|
+
toolUseId: toolCall.id as ToolUseId,
|
|
653
|
+
toolName: toolCall.function.name,
|
|
654
|
+
})
|
|
551
655
|
const ctx: ToolContext = {
|
|
552
656
|
...baseContext,
|
|
553
657
|
toolUseId: toolCall.id,
|
|
658
|
+
source: { kind: 'direct' },
|
|
554
659
|
// Overridden per call, so a nested dispatch can name the call
|
|
555
660
|
// that made it. The base context has no `toolUseId`, and a
|
|
556
661
|
// closure built there would report every nested call as
|
|
557
662
|
// parentless.
|
|
558
|
-
dispatchTool: (name, input) =>
|
|
663
|
+
dispatchTool: (name, input, options) =>
|
|
664
|
+
this.dispatchNested(name, input, ctx, recordObservation, toolCall.function.name, options),
|
|
559
665
|
// Per-call for the same reason: a pause has to be routed back
|
|
560
666
|
// to the call that raised it, and a batch can raise several.
|
|
561
667
|
...(this.config.toolPause ? { requestPause: this.config.toolPause(toolCall.id) } : {}),
|
|
562
|
-
report: (message: string, fraction?: number) =>
|
|
563
|
-
// Fire-and-forget: a tool reporting progress must never be
|
|
564
|
-
// able to fail because the host's listener threw, and must
|
|
565
|
-
// never have to await the emit mid-work.
|
|
566
|
-
void this.emitEvent({
|
|
567
|
-
type: 'tool_progress',
|
|
568
|
-
runId: this.config.runId,
|
|
569
|
-
toolUseId: toolCall.id as ToolUseId,
|
|
570
|
-
toolName: toolCall.function.name,
|
|
571
|
-
message,
|
|
572
|
-
...(fraction !== undefined ? { fraction: Math.min(1, Math.max(0, fraction)) } : {}),
|
|
573
|
-
}).catch(() => {})
|
|
574
|
-
},
|
|
668
|
+
report: (message: string, fraction?: number) => progress.report(message, fraction),
|
|
575
669
|
}
|
|
576
670
|
const run = async () => {
|
|
577
|
-
|
|
671
|
+
try {
|
|
672
|
+
results[i] = await this.executeSingle(toolCall, ctx, recordObservation, () =>
|
|
673
|
+
progress.close(),
|
|
674
|
+
)
|
|
675
|
+
} finally {
|
|
676
|
+
await progress.close()
|
|
677
|
+
}
|
|
578
678
|
}
|
|
579
679
|
const gated = async () => {
|
|
580
680
|
await gate.acquire()
|
|
@@ -651,15 +751,64 @@ export class ToolExecutor {
|
|
|
651
751
|
input: unknown,
|
|
652
752
|
context: ToolContext,
|
|
653
753
|
recordObservation: (observation: ToolResultObservation) => void,
|
|
754
|
+
parentToolName?: string,
|
|
755
|
+
options?: ToolDispatchOptions,
|
|
654
756
|
): Promise<ToolResult> {
|
|
655
757
|
const parent = context.toolUseId
|
|
656
|
-
const via =
|
|
758
|
+
const via =
|
|
759
|
+
parent && parentToolName
|
|
760
|
+
? {
|
|
761
|
+
tool: parentToolName,
|
|
762
|
+
toolUseId: parent as ToolUseId,
|
|
763
|
+
...(options?.runtimeToolCallId ? { runtimeToolCallId: options.runtimeToolCallId } : {}),
|
|
764
|
+
}
|
|
765
|
+
: undefined
|
|
657
766
|
// Its own id, minted here. Reusing the parent's would make two
|
|
658
767
|
// different calls indistinguishable in any log keyed by it, which is
|
|
659
768
|
// exactly how a nested write gets attributed to the program that ran
|
|
660
769
|
// it rather than to itself.
|
|
661
770
|
const nestedId = generateToolCallId() as unknown as ToolUseId
|
|
662
771
|
const startedAt = Date.now()
|
|
772
|
+
const signal = options?.signal
|
|
773
|
+
? AbortSignal.any([context.abortSignal, options.signal])
|
|
774
|
+
: context.abortSignal
|
|
775
|
+
const source = parent
|
|
776
|
+
? options?.runtimeToolCallId
|
|
777
|
+
? {
|
|
778
|
+
kind: 'code' as const,
|
|
779
|
+
parentToolUseId: parent,
|
|
780
|
+
runtimeToolCallId: options.runtimeToolCallId,
|
|
781
|
+
}
|
|
782
|
+
: { kind: 'nested' as const, parentToolUseId: parent }
|
|
783
|
+
: { kind: 'direct' as const }
|
|
784
|
+
const progress = new ToolProgressPublisher(this.emitEvent, {
|
|
785
|
+
type: 'tool_progress',
|
|
786
|
+
runId: this.config.runId,
|
|
787
|
+
toolUseId: nestedId,
|
|
788
|
+
toolName: name,
|
|
789
|
+
})
|
|
790
|
+
const childContext: ToolContext = {
|
|
791
|
+
...context,
|
|
792
|
+
abortSignal: signal,
|
|
793
|
+
toolUseId: nestedId,
|
|
794
|
+
source,
|
|
795
|
+
dispatchTool: (childName, childInput, childOptions) =>
|
|
796
|
+
this.dispatchNested(
|
|
797
|
+
childName,
|
|
798
|
+
childInput,
|
|
799
|
+
childContext,
|
|
800
|
+
recordObservation,
|
|
801
|
+
name,
|
|
802
|
+
childOptions,
|
|
803
|
+
),
|
|
804
|
+
// A nested execution has its own event/progress identity, but its
|
|
805
|
+
// durable pause belongs to the nearest model-issued ancestor. The
|
|
806
|
+
// checkpoint transcript contains that ancestor call and not this
|
|
807
|
+
// ephemeral child id; minting a pause route for `nestedId` makes the
|
|
808
|
+
// answer impossible to match after process restart. `...context`
|
|
809
|
+
// intentionally preserves the ancestor route here.
|
|
810
|
+
report: (message: string, fraction?: number) => progress.report(message, fraction),
|
|
811
|
+
}
|
|
663
812
|
|
|
664
813
|
await this.emitEvent({
|
|
665
814
|
type: 'tool_executing',
|
|
@@ -670,16 +819,54 @@ export class ToolExecutor {
|
|
|
670
819
|
...(via ? { via } : {}),
|
|
671
820
|
})
|
|
672
821
|
|
|
673
|
-
|
|
822
|
+
let result: ToolResult
|
|
823
|
+
try {
|
|
824
|
+
// Use the same deadline layer as a model-issued call. Calling the
|
|
825
|
+
// registry directly made nested tools the only tools whose own
|
|
826
|
+
// `timeoutMs` declaration was ignored.
|
|
827
|
+
result = await this.runOnce(name, input, childContext)
|
|
828
|
+
} finally {
|
|
829
|
+
await progress.close()
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const rawOutput = result.success
|
|
833
|
+
? result.output
|
|
834
|
+
: formatFailedToolOutput(result.output, result.error)
|
|
835
|
+
const budgeted = applyToolOutputBudget({
|
|
836
|
+
toolName: name,
|
|
837
|
+
toolUseId: nestedId,
|
|
838
|
+
output: rawOutput,
|
|
839
|
+
maxChars: this.config.maxToolOutputChars ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS,
|
|
840
|
+
spillDir: this.config.toolOutputDir,
|
|
841
|
+
onError: (message) =>
|
|
842
|
+
this.log.warn('Failed to spill oversized nested tool output', {
|
|
843
|
+
[NAMZU.RUN_ID]: this.config.runId,
|
|
844
|
+
[GENAI.TOOL_NAME]: name,
|
|
845
|
+
'exception.message': message,
|
|
846
|
+
}),
|
|
847
|
+
})
|
|
848
|
+
const visibleResult: ToolResult = result.success
|
|
849
|
+
? { ...result, output: budgeted.output }
|
|
850
|
+
: {
|
|
851
|
+
...result,
|
|
852
|
+
output: '',
|
|
853
|
+
// `run_code` sends `error`, not `output`, across the worker
|
|
854
|
+
// bridge on a failed host call. Leaving the raw error here would
|
|
855
|
+
// make the success path bounded and the failure path unbounded.
|
|
856
|
+
error: budgeted.output,
|
|
857
|
+
}
|
|
674
858
|
|
|
675
859
|
await this.emitEvent({
|
|
676
860
|
type: 'tool_completed',
|
|
677
861
|
runId: this.config.runId,
|
|
678
862
|
toolUseId: nestedId,
|
|
679
863
|
toolName: name,
|
|
680
|
-
result:
|
|
864
|
+
result: budgeted.output,
|
|
681
865
|
isError: !result.success,
|
|
682
866
|
durationMs: Date.now() - startedAt,
|
|
867
|
+
outputLength: budgeted.originalLength,
|
|
868
|
+
...(budgeted.truncated ? { outputTruncated: true } : {}),
|
|
869
|
+
...(budgeted.spillPath ? { outputSpillPath: budgeted.spillPath } : {}),
|
|
683
870
|
...(via ? { via } : {}),
|
|
684
871
|
})
|
|
685
872
|
recordObservation({
|
|
@@ -691,7 +878,7 @@ export class ToolExecutor {
|
|
|
691
878
|
...(parent ? { parentToolUseId: parent } : {}),
|
|
692
879
|
})
|
|
693
880
|
|
|
694
|
-
return
|
|
881
|
+
return visibleResult
|
|
695
882
|
}
|
|
696
883
|
|
|
697
884
|
private buildToolContext(
|
|
@@ -729,7 +916,8 @@ export class ToolExecutor {
|
|
|
729
916
|
// dispatching outside a batch has no parent call to name. The
|
|
730
917
|
// per-call context below overrides it with one that does; see
|
|
731
918
|
// `dispatchNested`.
|
|
732
|
-
dispatchTool: (name, input) =>
|
|
919
|
+
dispatchTool: (name, input, options) =>
|
|
920
|
+
this.dispatchNested(name, input, context, recordObservation, undefined, options),
|
|
733
921
|
sandbox: this.config.sandbox,
|
|
734
922
|
fileReadTracker: this.fileReadTracker,
|
|
735
923
|
// Bound to this run, once. Binding here rather than passing the
|
|
@@ -759,6 +947,7 @@ export class ToolExecutor {
|
|
|
759
947
|
toolCall: ToolCall,
|
|
760
948
|
toolContext: ToolContext,
|
|
761
949
|
recordObservation: (observation: ToolResultObservation) => void,
|
|
950
|
+
settleProgress: () => Promise<void>,
|
|
762
951
|
): Promise<ToolCallOutcome> {
|
|
763
952
|
let toolName = toolCall.function.name
|
|
764
953
|
|
|
@@ -1095,6 +1284,11 @@ export class ToolExecutor {
|
|
|
1095
1284
|
}
|
|
1096
1285
|
}
|
|
1097
1286
|
|
|
1287
|
+
// The terminal event closes the live row. Every accepted progress update
|
|
1288
|
+
// must settle before it, otherwise a slow host can receive an update for a
|
|
1289
|
+
// call it has already removed. Detached work reporting after a timeout is
|
|
1290
|
+
// ignored because close() also revokes the publisher.
|
|
1291
|
+
await settleProgress()
|
|
1098
1292
|
await this.emitEvent({
|
|
1099
1293
|
type: 'tool_completed',
|
|
1100
1294
|
runId: this.config.runId,
|
|
@@ -1180,10 +1374,10 @@ export class ToolExecutor {
|
|
|
1180
1374
|
}
|
|
1181
1375
|
|
|
1182
1376
|
const controller = new AbortController()
|
|
1183
|
-
const
|
|
1184
|
-
const
|
|
1185
|
-
if (
|
|
1186
|
-
else
|
|
1377
|
+
const parentSignal = toolContext.abortSignal
|
|
1378
|
+
const onParentAbort = () => controller.abort(parentSignal.reason)
|
|
1379
|
+
if (parentSignal.aborted) controller.abort(parentSignal.reason)
|
|
1380
|
+
else parentSignal.addEventListener('abort', onParentAbort, { once: true })
|
|
1187
1381
|
|
|
1188
1382
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
1189
1383
|
let timedOut = false
|
|
@@ -1211,9 +1405,21 @@ export class ToolExecutor {
|
|
|
1211
1405
|
)
|
|
1212
1406
|
})
|
|
1213
1407
|
|
|
1408
|
+
const inheritedDispatch = toolContext.dispatchTool
|
|
1214
1409
|
const execution = this.config.tools.execute(toolName, input, {
|
|
1215
1410
|
...toolContext,
|
|
1216
1411
|
abortSignal: controller.signal,
|
|
1412
|
+
...(inheritedDispatch
|
|
1413
|
+
? {
|
|
1414
|
+
dispatchTool: (name: string, nestedInput: unknown, options?: ToolDispatchOptions) =>
|
|
1415
|
+
inheritedDispatch(name, nestedInput, {
|
|
1416
|
+
...options,
|
|
1417
|
+
signal: options?.signal
|
|
1418
|
+
? AbortSignal.any([controller.signal, options.signal])
|
|
1419
|
+
: controller.signal,
|
|
1420
|
+
}),
|
|
1421
|
+
}
|
|
1422
|
+
: {}),
|
|
1217
1423
|
})
|
|
1218
1424
|
// The loser of the race may still reject later; neutralize it so
|
|
1219
1425
|
// it is never an unhandled rejection.
|
|
@@ -1254,7 +1460,7 @@ export class ToolExecutor {
|
|
|
1254
1460
|
return outcome
|
|
1255
1461
|
} finally {
|
|
1256
1462
|
if (timer !== undefined) clearTimeout(timer)
|
|
1257
|
-
|
|
1463
|
+
parentSignal.removeEventListener('abort', onParentAbort)
|
|
1258
1464
|
}
|
|
1259
1465
|
}
|
|
1260
1466
|
|
|
@@ -1457,7 +1663,10 @@ export class ToolExecutor {
|
|
|
1457
1663
|
reason: failure.reason,
|
|
1458
1664
|
message: failure.message,
|
|
1459
1665
|
...(tool
|
|
1460
|
-
? {
|
|
1666
|
+
? {
|
|
1667
|
+
tool,
|
|
1668
|
+
jsonSchema: tool.modelInputSchema ?? renderToolSchema(tool.inputSchema),
|
|
1669
|
+
}
|
|
1461
1670
|
: {}),
|
|
1462
1671
|
availableTools: this.config.tools.listNames(),
|
|
1463
1672
|
})
|
|
@@ -302,6 +302,11 @@ export interface QueryParams {
|
|
|
302
302
|
* store REFUSES rather than dropping the attachment.
|
|
303
303
|
*/
|
|
304
304
|
attachmentStore?: import('../../store/attachment/index.js').AttachmentStore
|
|
305
|
+
/**
|
|
306
|
+
* Maximum wall-clock time for resolving the run's stored attachments.
|
|
307
|
+
* Defaults to one minute; `0` retains the prior unbounded wait.
|
|
308
|
+
*/
|
|
309
|
+
attachmentResolveTimeoutMs?: number
|
|
305
310
|
|
|
306
311
|
/**
|
|
307
312
|
* How this run reaches the web.
|
|
@@ -1192,6 +1197,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
1192
1197
|
resolvedInitialMessages = [
|
|
1193
1198
|
...(await resolveAttachments(seeded, params.attachmentStore, {
|
|
1194
1199
|
signal: params.signal,
|
|
1200
|
+
timeoutMs: params.attachmentResolveTimeoutMs,
|
|
1195
1201
|
})),
|
|
1196
1202
|
]
|
|
1197
1203
|
params.signal?.throwIfAborted()
|
|
@@ -22,8 +22,8 @@ import type { CostInfo, TokenUsage } from '../../../types/common/index.js'
|
|
|
22
22
|
import type { MessageId } from '../../../types/ids/index.js'
|
|
23
23
|
import {
|
|
24
24
|
createAssistantMessage,
|
|
25
|
+
createRuntimeContextMessage,
|
|
25
26
|
createSystemMessage,
|
|
26
|
-
createUserMessage,
|
|
27
27
|
} from '../../../types/message/index.js'
|
|
28
28
|
import type { ToolChoice } from '../../../types/provider/chat.js'
|
|
29
29
|
import { classifyProviderError } from '../../../types/provider/errors.js'
|
|
@@ -47,6 +47,8 @@ import type { ToolCallOutcome } from '../executor.js'
|
|
|
47
47
|
import { applyLifecycleHookResults } from '../plugin-hooks.js'
|
|
48
48
|
import {
|
|
49
49
|
DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
50
|
+
type RequestImageIdentity,
|
|
51
|
+
markProviderRejectedImage,
|
|
50
52
|
projectRequestRichContent,
|
|
51
53
|
} from '../request-rich-content.js'
|
|
52
54
|
import { formatSteeringNote } from '../steering.js'
|
|
@@ -57,6 +59,7 @@ import type { IterationContext } from './phases/index.js'
|
|
|
57
59
|
import { runPlanGate } from './phases/plan.js'
|
|
58
60
|
import { runToolReview } from './phases/tool-review.js'
|
|
59
61
|
import { refreshWorkingMemory } from './phases/working-memory.js'
|
|
62
|
+
import { streamWithProviderRejectedImageRecovery } from './provider-rejected-image.js'
|
|
60
63
|
import { streamProviderTurn } from './stream-turn.js'
|
|
61
64
|
|
|
62
65
|
export type { IterationContext } from './phases/index.js'
|
|
@@ -394,8 +397,9 @@ export class IterationOrchestrator {
|
|
|
394
397
|
const baseMessages = forceFinalize
|
|
395
398
|
? [
|
|
396
399
|
...runMgr.messages,
|
|
397
|
-
|
|
400
|
+
createRuntimeContextMessage(
|
|
398
401
|
'[SYSTEM] You are approaching your resource limits. Provide your final, comprehensive response now based on everything you have gathered so far. Do not request any more tool calls.',
|
|
402
|
+
'limit-finalization',
|
|
399
403
|
),
|
|
400
404
|
]
|
|
401
405
|
: runMgr.messages
|
|
@@ -589,6 +593,9 @@ export class IterationOrchestrator {
|
|
|
589
593
|
this.ctx.log,
|
|
590
594
|
iterSpan,
|
|
591
595
|
stepMessageId,
|
|
596
|
+
{
|
|
597
|
+
onAccepted: (identity) => this.acceptProviderRejectedImage(identity),
|
|
598
|
+
},
|
|
592
599
|
)
|
|
593
600
|
stepResponse = response
|
|
594
601
|
|
|
@@ -880,7 +887,9 @@ export class IterationOrchestrator {
|
|
|
880
887
|
[NAMZU.ITERATION]: iterationNum,
|
|
881
888
|
[GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
|
|
882
889
|
})
|
|
883
|
-
runMgr.pushMessage(
|
|
890
|
+
runMgr.pushMessage(
|
|
891
|
+
createRuntimeContextMessage(AUTO_CONTINUATION_USER_MESSAGE, 'auto-continuation'),
|
|
892
|
+
)
|
|
884
893
|
await this.ctx.emitEvent({
|
|
885
894
|
type: 'iteration_completed',
|
|
886
895
|
runId: runMgr.id,
|
|
@@ -912,7 +921,9 @@ export class IterationOrchestrator {
|
|
|
912
921
|
'namzu.retry.attempt': attempt,
|
|
913
922
|
'namzu.runtime.limit': limit,
|
|
914
923
|
})
|
|
915
|
-
runMgr.pushMessage(
|
|
924
|
+
runMgr.pushMessage(
|
|
925
|
+
createRuntimeContextMessage(STRUCTURED_OUTPUT_REPROMPT, 'structured-output'),
|
|
926
|
+
)
|
|
916
927
|
await this.ctx.emitEvent({
|
|
917
928
|
type: 'iteration_completed',
|
|
918
929
|
runId: runMgr.id,
|
|
@@ -955,7 +966,7 @@ export class IterationOrchestrator {
|
|
|
955
966
|
'namzu.retry.attempt': attempt,
|
|
956
967
|
'namzu.runtime.limit': limit,
|
|
957
968
|
})
|
|
958
|
-
runMgr.pushMessage(
|
|
969
|
+
runMgr.pushMessage(createRuntimeContextMessage(review.feedback, 'answer-review'))
|
|
959
970
|
await this.ctx.emitEvent({
|
|
960
971
|
type: 'iteration_completed',
|
|
961
972
|
runId: runMgr.id,
|
|
@@ -1188,7 +1199,9 @@ export class IterationOrchestrator {
|
|
|
1188
1199
|
[NAMZU.ITERATION]: iterationNum,
|
|
1189
1200
|
'namzu.runtime.tasks': unheard.map((h) => h.taskId),
|
|
1190
1201
|
})
|
|
1191
|
-
runMgr.pushMessage(
|
|
1202
|
+
runMgr.pushMessage(
|
|
1203
|
+
createRuntimeContextMessage(formatCompletionNotification(unheard), 'task-completion'),
|
|
1204
|
+
)
|
|
1192
1205
|
}
|
|
1193
1206
|
|
|
1194
1207
|
// The same seam, for the two channels that could accept text
|
|
@@ -1388,7 +1401,9 @@ export class IterationOrchestrator {
|
|
|
1388
1401
|
const arrived = this.ctx.completionInbox.drain()
|
|
1389
1402
|
if (arrived.length === 0) return false
|
|
1390
1403
|
|
|
1391
|
-
this.ctx.runMgr.pushMessage(
|
|
1404
|
+
this.ctx.runMgr.pushMessage(
|
|
1405
|
+
createRuntimeContextMessage(formatCompletionNotification(arrived), 'task-completion'),
|
|
1406
|
+
)
|
|
1392
1407
|
await this.ctx.emitEvent({
|
|
1393
1408
|
type: 'iteration_completed',
|
|
1394
1409
|
runId: this.ctx.runMgr.id,
|
|
@@ -1467,7 +1482,9 @@ export class IterationOrchestrator {
|
|
|
1467
1482
|
[NAMZU.RUN_ID]: this.ctx.runMgr.id,
|
|
1468
1483
|
'namzu.runtime.tasks': unheard.map((h) => h.taskId),
|
|
1469
1484
|
})
|
|
1470
|
-
this.ctx.runMgr.pushMessage(
|
|
1485
|
+
this.ctx.runMgr.pushMessage(
|
|
1486
|
+
createRuntimeContextMessage(formatCompletionNotification(unheard), 'task-completion'),
|
|
1487
|
+
)
|
|
1471
1488
|
}
|
|
1472
1489
|
|
|
1473
1490
|
/**
|
|
@@ -1633,7 +1650,9 @@ export class IterationOrchestrator {
|
|
|
1633
1650
|
// guidance from a turn that had no result to attach it to.
|
|
1634
1651
|
const stranded = this.ctx.steering?.drain()
|
|
1635
1652
|
if (stranded) {
|
|
1636
|
-
this.ctx.runMgr.pushMessage(
|
|
1653
|
+
this.ctx.runMgr.pushMessage(
|
|
1654
|
+
createRuntimeContextMessage(formatSteeringNote(stranded), 'steering'),
|
|
1655
|
+
)
|
|
1637
1656
|
}
|
|
1638
1657
|
|
|
1639
1658
|
return queued.length + (stranded ? 1 : 0)
|
|
@@ -1922,6 +1941,23 @@ export class IterationOrchestrator {
|
|
|
1922
1941
|
.some((t) => t.state !== 'completed' && t.state !== 'failed' && t.state !== 'canceled')
|
|
1923
1942
|
}
|
|
1924
1943
|
|
|
1944
|
+
private async acceptProviderRejectedImage(identity: RequestImageIdentity): Promise<void> {
|
|
1945
|
+
const repaired = markProviderRejectedImage(this.ctx.runMgr.messages, identity)
|
|
1946
|
+
if (repaired.count === 0) {
|
|
1947
|
+
throw new Error('Provider-rejected image recovery could not find its durable source image')
|
|
1948
|
+
}
|
|
1949
|
+
this.ctx.runMgr.replaceMessages(repaired.messages)
|
|
1950
|
+
await this.ctx.emitEvent?.({
|
|
1951
|
+
type: 'message_history_repaired',
|
|
1952
|
+
runId: this.ctx.runMgr.id,
|
|
1953
|
+
source: 'provider-rejected-image',
|
|
1954
|
+
duplicateToolResultsRemoved: 0,
|
|
1955
|
+
orphanedToolResultsRemoved: 0,
|
|
1956
|
+
syntheticToolResultsInserted: 0,
|
|
1957
|
+
providerRejectedImagesSuppressed: repaired.count,
|
|
1958
|
+
})
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1925
1961
|
private async requestFinalResponse(model: string, reason: StopReason): Promise<void> {
|
|
1926
1962
|
const lastAssistant = [...this.ctx.runMgr.messages]
|
|
1927
1963
|
.reverse()
|
|
@@ -1941,8 +1977,9 @@ export class IterationOrchestrator {
|
|
|
1941
1977
|
try {
|
|
1942
1978
|
const finalHistory = [
|
|
1943
1979
|
...this.ctx.runMgr.messages,
|
|
1944
|
-
|
|
1980
|
+
createRuntimeContextMessage(
|
|
1945
1981
|
`[SYSTEM] Run is ending due to ${reason}. You MUST provide a final response now summarizing all your findings and work so far. Do not use any tools.`,
|
|
1982
|
+
'limit-finalization',
|
|
1946
1983
|
),
|
|
1947
1984
|
]
|
|
1948
1985
|
const finalMessages = projectRequestRichContent(
|
|
@@ -1964,26 +2001,29 @@ export class IterationOrchestrator {
|
|
|
1964
2001
|
model: requestedMember.model ?? model,
|
|
1965
2002
|
chainIndex: requestedMember.index,
|
|
1966
2003
|
}
|
|
2004
|
+
const finalParams = {
|
|
2005
|
+
model,
|
|
2006
|
+
providerRoute: requestedRoute,
|
|
2007
|
+
messages: finalMessages,
|
|
2008
|
+
tools: finalTools.length > 0 ? finalTools : undefined,
|
|
2009
|
+
...(finalEnforced ? { enforceToolInputSchema: finalEnforced } : {}),
|
|
2010
|
+
toolChoice: finalTools.length > 0 ? 'none' : undefined,
|
|
2011
|
+
temperature: this.ctx.runConfig.temperature,
|
|
2012
|
+
maxTokens: this.ctx.runConfig.maxResponseTokens,
|
|
2013
|
+
cacheControl: { type: 'auto' },
|
|
2014
|
+
...(this.ctx.runConfig.thinking ? { thinking: this.ctx.runConfig.thinking } : {}),
|
|
2015
|
+
// This turn is a hand-maintained duplicate of the one above, which
|
|
2016
|
+
// is exactly the shape a field goes missing from — so it is tested
|
|
2017
|
+
// separately rather than assumed to have been kept in step.
|
|
2018
|
+
...(this.ctx.runConfig.effort ? { effort: this.ctx.runConfig.effort } : {}),
|
|
2019
|
+
// Cancellable too: a Stop during the closing summary must not
|
|
2020
|
+
// stream to completion.
|
|
2021
|
+
signal: this.ctx.abortController.signal,
|
|
2022
|
+
} satisfies import('../../../types/provider/index.js').ChatCompletionParams
|
|
1967
2023
|
const response = await collectChatCompletion(
|
|
1968
|
-
this.ctx.provider
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
messages: finalMessages,
|
|
1972
|
-
tools: finalTools.length > 0 ? finalTools : undefined,
|
|
1973
|
-
...(finalEnforced ? { enforceToolInputSchema: finalEnforced } : {}),
|
|
1974
|
-
toolChoice: finalTools.length > 0 ? 'none' : undefined,
|
|
1975
|
-
temperature: this.ctx.runConfig.temperature,
|
|
1976
|
-
maxTokens: this.ctx.runConfig.maxResponseTokens,
|
|
1977
|
-
cacheControl: { type: 'auto' },
|
|
1978
|
-
...(this.ctx.runConfig.thinking ? { thinking: this.ctx.runConfig.thinking } : {}),
|
|
1979
|
-
// This turn is a hand-maintained duplicate of the one above, which
|
|
1980
|
-
// is exactly the shape a field goes missing from — so it is tested
|
|
1981
|
-
// separately rather than assumed to have been kept in step.
|
|
1982
|
-
...(this.ctx.runConfig.effort ? { effort: this.ctx.runConfig.effort } : {}),
|
|
1983
|
-
// Cancellable too: a Stop during the closing summary must not
|
|
1984
|
-
// stream to completion.
|
|
1985
|
-
signal: this.ctx.abortController.signal,
|
|
1986
|
-
}),
|
|
2024
|
+
streamWithProviderRejectedImageRecovery(this.ctx.provider, finalParams, (identity) =>
|
|
2025
|
+
this.acceptProviderRejectedImage(identity),
|
|
2026
|
+
),
|
|
1987
2027
|
)
|
|
1988
2028
|
|
|
1989
2029
|
const servingMember = this.ctx.servingMember?.() ?? requestedMember
|
|
@@ -2,7 +2,7 @@ import { serializeState } from '../../../../compaction/serializer.js'
|
|
|
2
2
|
import { NAMZU } from '../../../../constants/telemetry/index.js'
|
|
3
3
|
import type { AdvisoryRequest, TriggerEvaluationState } from '../../../../types/advisory/index.js'
|
|
4
4
|
import { toolResultToText } from '../../../../types/message/content.js'
|
|
5
|
-
import {
|
|
5
|
+
import { createRuntimeContextMessage } from '../../../../types/message/index.js'
|
|
6
6
|
import type { ChatCompletionResponse } from '../../../../types/provider/index.js'
|
|
7
7
|
import { toErrorMessage } from '../../../../utils/error.js'
|
|
8
8
|
import type { IterationContext } from './context.js'
|
|
@@ -169,7 +169,7 @@ export async function runAdvisoryPhase(
|
|
|
169
169
|
|
|
170
170
|
sections.push('</advisory-result>')
|
|
171
171
|
|
|
172
|
-
ctx.runMgr.pushMessage(
|
|
172
|
+
ctx.runMgr.pushMessage(createRuntimeContextMessage(sections.join('\n'), 'advisory'))
|
|
173
173
|
|
|
174
174
|
ctx.log.info('Advisory phase completed', {
|
|
175
175
|
[NAMZU.RUN_ID]: ctx.runMgr.id,
|