Merge origin/master into parallel-tool-call

This commit is contained in:
Dudu-0223
2026-07-16 14:56:02 +08:00
643 changed files with 28342 additions and 5706 deletions

View File

@@ -108,14 +108,13 @@ function renderValue(value: unknown): string {
/** The run_code result's `meta` payload (JSON-serializable; `presentResult` narrows it back). */
interface RunCodeMeta {
logs: CodeRunResult['logs']
dispatches: number
}
/** Soft-narrow a result `meta` back to {@link RunCodeMeta} (replay may carry older shapes; presentation must not throw). */
function asRunCodeMeta(meta: unknown): RunCodeMeta | undefined {
if (typeof meta !== 'object' || meta === null) return undefined
const m = meta as Record<string, unknown>
if (!Array.isArray(m.logs) || typeof m.dispatches !== 'number') return undefined
if (!Array.isArray(m.logs) || !m.logs.every(log => typeof log === 'string')) return undefined
return m as unknown as RunCodeMeta
}
@@ -251,12 +250,12 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
}
if (result.error) {
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.map(entry => entry.text).join('\n')}` : ''
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : ''
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`)
}
const rendered = renderValue(result.value)
const parts = [result.logs.map(entry => entry.text).join('\n'), rendered].filter(part => part.length > 0)
const meta: RunCodeMeta = { logs: result.logs, dispatches }
const parts = [result.logs.join('\n'), rendered].filter(part => part.length > 0)
const meta: RunCodeMeta = { logs: result.logs }
return {
content: [{ type: 'text', text: parts.length > 0 ? parts.join('\n') : '(run_code completed with no output)' }],
meta,
@@ -278,7 +277,7 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
presentResult: (_args, result) => {
const meta = asRunCodeMeta(result.meta)
if (!meta) return undefined
const output = meta.logs.map(entry => entry.text).join('\n')
const output = meta.logs.join('\n')
return {
card: 'generic',
...output.length > 0 ? { content: [{ type: 'text' as const, text: output }] } : {},

View File

@@ -301,7 +301,7 @@ export interface ToolErrorInfo {
* distinguish it from a tool body's own error.
*/
export class ToolNotFoundError extends HarnessError {
constructor(public readonly toolName: string) {
constructor(toolName: string) {
super(`unknown tool "${toolName}"`, 'UNKNOWN_TOOL')
this.name = 'ToolNotFoundError'
}
@@ -309,7 +309,6 @@ export class ToolNotFoundError extends HarnessError {
/** The outcome of one tool call. */
export interface ToolExecutionResult {
callId: CallId
content: ContentBlock[]
isError: boolean
/**
@@ -789,7 +788,11 @@ export class ToolRegistry extends Service {
* @returns the materialized final result.
*/
async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult> {
const prepared = await this.prepareScheduledExecution(exec)
return this.prepareExecution(exec, prepared => this.completeScheduledExecution(prepared))
}
/** Complete every remaining stage for the public one-call execution path. */
private async completeScheduledExecution(prepared: ScheduledToolPreparation): Promise<ToolExecutionResult> {
switch (prepared.kind) {
case 'dispatch': {
const dispatched = await this.dispatchScheduledExecution(prepared.exec)
@@ -831,7 +834,7 @@ export class ToolRegistry extends Service {
return { kind: 'ready', exec: { ...base, arguments: deepFreeze(detached) } }
} catch (error: unknown) {
const execution: ToolExecution = { ...base, arguments: undefined }
return { kind: 'final-result', exec: execution, result: toolErrorResult(callId, error) }
return { kind: 'final-result', exec: execution, result: toolErrorResult(error) }
}
}
@@ -842,8 +845,16 @@ export class ToolRegistry extends Service {
* @internal
*/
private async prepareScheduledExecution(input: ToolExecutionInput): Promise<ScheduledToolPreparation> {
return this.prepareExecution(input, prepared => prepared)
}
/** Run preparation and hand its outcome directly to the selected continuation. */
private async prepareExecution<T>(
input: ToolExecutionInput,
next: (prepared: ScheduledToolPreparation) => T | PromiseLike<T>,
): Promise<T> {
const created = this.createExecution(input)
if (created.kind !== 'ready') return created
if (created.kind !== 'ready') return next(created)
const exec = created.exec
try {
const carrier = scopeTarget(this, exec.agent)
@@ -856,19 +867,18 @@ export class ToolRegistry extends Service {
? this.guardReason(exec)
: decision.reason
if (denialReason !== undefined) {
return {
return await next({
kind: 'post-result',
exec,
result: {
callId: exec.callId,
content: [{ type: 'text', text: `Error: ${denialReason}` }],
isError: true,
},
}
})
}
return { kind: 'dispatch', exec }
return await next({ kind: 'dispatch', exec })
} catch (error: unknown) {
return { kind: 'final-result', exec, result: toolErrorResult(exec.callId, error) }
return next({ kind: 'final-result', exec, result: toolErrorResult(error) })
}
}
@@ -892,18 +902,15 @@ export class ToolRegistry extends Service {
const returned = await tool.execute(exec.arguments, exec)
const content = Array.isArray(returned) ? returned : returned.content
const meta = Array.isArray(returned) ? undefined : returned.meta
return { callId: exec.callId, content, isError: false, ...meta !== undefined ? { meta } : {} }
return { content, isError: false, ...meta !== undefined ? { meta } : {} }
} catch (error: unknown) {
return toolErrorResult(exec.callId, error)
return toolErrorResult(error)
}
},
)
if (result.callId !== exec.callId) {
throw new TypeError(`tools/execute returned callId "${String(result.callId)}" for authoritative call "${exec.callId}"`)
}
return { kind: 'post-result', result }
} catch (error: unknown) {
return { kind: 'final-result', result: toolErrorResult(exec.callId, error) }
return { kind: 'final-result', result: toolErrorResult(error) }
}
}
@@ -918,7 +925,7 @@ export class ToolRegistry extends Service {
try {
return this.finishScheduledExecution(exec, await this.postExecute(exec, result))
} catch (error: unknown) {
return this.finishScheduledExecution(exec, toolErrorResult(exec.callId, error))
return this.finishScheduledExecution(exec, toolErrorResult(error))
}
}
@@ -934,7 +941,7 @@ export class ToolRegistry extends Service {
try {
finalResult = this.materializeFinalResult(result)
} catch (error: unknown) {
finalResult = this.materializeFinalResult(toolErrorResult(exec.callId, error))
finalResult = this.materializeFinalResult(toolErrorResult(error))
}
this.notifyResult(exec, finalResult)
return finalResult
@@ -942,6 +949,8 @@ export class ToolRegistry extends Service {
/** Notify final-result observers without giving them a mutation/error channel into the outcome. */
private notifyResult(exec: ToolExecution, result: ToolExecutionResult): void {
// Freeze the remaining mutable signal slot before observers receive the
// shared WeakMap-keyable execution object.
Object.freeze(exec)
const callbacks = this.ctx.events.dispatch('emit', [
scopeTarget(this, exec.agent), 'tools/result', exec, result,
@@ -1009,7 +1018,6 @@ export class ToolRegistry extends Service {
const additionalContext = decision.additionalContext
if (decision.kind === 'block') {
return {
callId: result.callId,
content: decision.feedback,
isError: true,
...additionalContext ? { additionalContext } : {},
@@ -1038,10 +1046,9 @@ function createExecutionToken(): ToolExecutionToken {
return Symbol('dsh.tool.execution') as ToolExecutionToken
}
function toolErrorResult(callId: ToolExecution['callId'], error: unknown): ToolExecutionResult {
function toolErrorResult(error: unknown): ToolExecutionResult {
const info = errorInfo(error)
return {
callId,
content: [{ type: 'text', text: `Error: ${errorMessage(error)}` }],
isError: true,
...info ? { error: info } : {},