fix(agent): merge abort signals instead of overriding caller's signal
When both timeoutMs and a caller-provided abortSignal were set, the timeout signal silently replaced the caller's signal. Now they are combined via mergeAbortSignals() so either source can cancel the run. Also removes dead array-handling branch in text-tool-extractor.ts (extractJSONObjects only returns objects, never arrays).
This commit is contained in:
parent
bc31008f4e
commit
a4a1add8ca
|
|
@ -50,6 +50,19 @@ import {
|
||||||
|
|
||||||
const ZERO_USAGE: TokenUsage = { input_tokens: 0, output_tokens: 0 }
|
const ZERO_USAGE: TokenUsage = { input_tokens: 0, output_tokens: 0 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two {@link AbortSignal}s so that aborting either one cancels the
|
||||||
|
* returned signal. Works on Node 18+ (no `AbortSignal.any` required).
|
||||||
|
*/
|
||||||
|
function mergeAbortSignals(a: AbortSignal, b: AbortSignal): AbortSignal {
|
||||||
|
const controller = new AbortController()
|
||||||
|
if (a.aborted || b.aborted) { controller.abort(); return controller.signal }
|
||||||
|
const abort = () => controller.abort()
|
||||||
|
a.addEventListener('abort', abort, { once: true })
|
||||||
|
b.addEventListener('abort', abort, { once: true })
|
||||||
|
return controller.signal
|
||||||
|
}
|
||||||
|
|
||||||
function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage {
|
function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage {
|
||||||
return {
|
return {
|
||||||
input_tokens: a.input_tokens + b.input_tokens,
|
input_tokens: a.input_tokens + b.input_tokens,
|
||||||
|
|
@ -298,11 +311,17 @@ export class Agent {
|
||||||
const timeoutSignal = this.config.timeoutMs !== undefined && this.config.timeoutMs > 0
|
const timeoutSignal = this.config.timeoutMs !== undefined && this.config.timeoutMs > 0
|
||||||
? AbortSignal.timeout(this.config.timeoutMs)
|
? AbortSignal.timeout(this.config.timeoutMs)
|
||||||
: undefined
|
: undefined
|
||||||
|
// Merge caller-provided abortSignal with the timeout signal so that
|
||||||
|
// either cancellation source is respected.
|
||||||
|
const callerAbort = callerOptions?.abortSignal
|
||||||
|
const effectiveAbort = timeoutSignal && callerAbort
|
||||||
|
? mergeAbortSignals(timeoutSignal, callerAbort)
|
||||||
|
: timeoutSignal ?? callerAbort
|
||||||
const runOptions: RunOptions = {
|
const runOptions: RunOptions = {
|
||||||
...callerOptions,
|
...callerOptions,
|
||||||
onMessage: internalOnMessage,
|
onMessage: internalOnMessage,
|
||||||
...(needsRunId ? { runId: generateRunId() } : undefined),
|
...(needsRunId ? { runId: generateRunId() } : undefined),
|
||||||
...(timeoutSignal ? { abortSignal: timeoutSignal } : undefined),
|
...(effectiveAbort ? { abortSignal: effectiveAbort } : undefined),
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await runner.run(messages, runOptions)
|
const result = await runner.run(messages, runOptions)
|
||||||
|
|
|
||||||
|
|
@ -211,15 +211,6 @@ export function extractToolCallsFromText(
|
||||||
|
|
||||||
const results: ToolUseBlock[] = []
|
const results: ToolUseBlock[] = []
|
||||||
for (const obj of jsonObjects) {
|
for (const obj of jsonObjects) {
|
||||||
// Handle array of tool calls
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
for (const item of obj) {
|
|
||||||
const block = parseToolCallJSON(item, nameSet)
|
|
||||||
if (block !== null) results.push(block)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const block = parseToolCallJSON(obj, nameSet)
|
const block = parseToolCallJSON(obj, nameSet)
|
||||||
if (block !== null) results.push(block)
|
if (block !== null) results.push(block)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue