@caupulican/pi-agent-core 0.81.26 → 0.81.27

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.
@@ -1 +1 @@
1
- {"version":3,"file":"watchdogs.js","sourceRoot":"","sources":["../../src/reliability/watchdogs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Silence/idle watchdogs for the reliability kernel.\n *\n * A silence watchdog bounds \"running but mute\" — it never bounds total runtime,\n * so long tasks that produce output are never killed (autonomy constraint).\n */\n\nexport interface SilenceWatchdog {\n\t/** Report activity (output chunk / stream event); resets the countdown.\n\t * Pass silenceMs to also change the bound for this and subsequent countdowns. */\n\ttouch(silenceMs?: number): void;\n\t/** Stop permanently (normal completion). Idempotent. */\n\tdisarm(): void;\n}\n\nexport interface SilenceWatchdogOptions {\n\tsilenceMs: number;\n\t/** Fired at most once, after silenceMs with no touch(). The watchdog self-disarms. */\n\tonSilence: () => void;\n}\n\nexport function createSilenceWatchdog(opts: SilenceWatchdogOptions): SilenceWatchdog {\n\tlet timer: NodeJS.Timeout | undefined;\n\tlet disarmed = false;\n\tlet currentSilenceMs = opts.silenceMs;\n\n\tconst arm = () => {\n\t\ttimer = setTimeout(() => {\n\t\t\tdisarmed = true;\n\t\t\ttimer = undefined;\n\t\t\topts.onSilence();\n\t\t}, currentSilenceMs);\n\t\t// Never keep the host process alive just for a watchdog.\n\t\ttimer.unref?.();\n\t};\n\n\tarm();\n\n\treturn {\n\t\ttouch(silenceMs?: number): void {\n\t\t\tif (disarmed) return;\n\t\t\tif (silenceMs !== undefined) currentSilenceMs = silenceMs;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tarm();\n\t\t},\n\t\tdisarm(): void {\n\t\t\tdisarmed = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t},\n\t};\n}\n\n// --- Stream-idle watchdog (wraps a StreamFn) -------------------------------\n\nimport {\n\ttype Api,\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\tcreateAssistantMessageEventStream,\n\ttype Model,\n\ttype ProviderResponse,\n} from \"@caupulican/pi-ai\";\nimport type { StreamFn } from \"../types.ts\";\n\nexport type StallPhase = \"connect\" | \"quiet\" | \"active\";\n\nexport interface StreamIdleOptions {\n\t/** Max ms to wait for the FIRST event (connection/first-token allowance). */\n\tconnectMs: number;\n\t/** Max ms between events while content is flowing — the latest content block is\n\t * text or toolCall. A flowing stream that goes silent this long is presumed dead. */\n\tactiveIdleMs: number;\n\t/** Max ms between events while the model is quietly working — no content blocks\n\t * yet (provider queue / prompt prefill / unstreamed reasoning) or the latest block\n\t * is thinking. Deep-thinking models and huge compaction prompts legitimately sit\n\t * here for minutes, so this bound is deliberately generous. */\n\tquietIdleMs: number;\n\t/** Fired once when a stall is detected, before the inner request is aborted. */\n\tonStall?: (info: { phase: StallPhase; elapsedMs: number }) => void;\n}\n\n/** User-locked defaults: connect 120s / active 180s / quiet 600s. The quiet bound must stay\n * below the HTTP dispatcher idle timeout (see coding-agent http-dispatcher.ts, 660s) or the\n * HTTP layer would kill quiet-but-healthy streams before this watchdog ever sees the gap. */\nexport const DEFAULT_STREAM_IDLE: StreamIdleOptions = {\n\tconnectMs: 120_000,\n\tactiveIdleMs: 180_000,\n\tquietIdleMs: 600_000,\n};\n\n/** Re-resolved at the start of every request, so hosts can wire live-tunable settings. */\nexport type StreamIdleOptionsResolver = (...args: Parameters<StreamFn>) => Partial<StreamIdleOptions>;\n\n/** Extracts the current AssistantMessage snapshot carried by any stream event variant. */\nfunction partialFromEvent(event: AssistantMessageEvent): AssistantMessage {\n\tif (event.type === \"done\") return event.message;\n\tif (event.type === \"error\") return event.error;\n\treturn event.partial;\n}\n\n/**\n * Wrap a StreamFn so a silently dead connection cannot wedge a turn forever.\n *\n * Phase-aware: `connectMs` bounds the wait for the first event; after that the\n * inter-event bound adapts to what the stream is doing — `quietIdleMs` while the\n * model is quietly working (no content blocks yet, or the latest block is thinking:\n * prefill, provider queues, unstreamed reasoning) and `activeIdleMs` once content is\n * flowing (latest block is text/toolCall). This keeps detection fast where silence is\n * anomalous without killing healthy deep-thinking or compaction-sized requests.\n * No bound ever limits total runtime (autonomy constraint).\n *\n * On stall, the inner request is aborted and the returned stream resolves immediately\n * with a synthetic `AssistantMessage` (`stopReason: \"error\"`, `errorMessage: \"stream\n * stalled: no events for <n>ms (<phase> phase)\"`) — the `stream stalled` phrasing is\n * what `classifyFailure` maps to a retryable `stream_stall`, so the host's\n * retry/failover path takes it from there.\n *\n * Options may be a resolver function; it is re-invoked at the start of every request,\n * so settings changes apply without rewrapping.\n *\n * A caller-initiated abort (via the options `signal`) is never treated as a stall: it\n * is chained into the wrapper's own controller. The inner stream's abort result is\n * forwarded untouched when it provides one; if it just ends, the wrapper synthesizes\n * an aborted terminal event so the returned stream always settles.\n */\nexport function withStreamIdleWatchdog(\n\tstreamFn: StreamFn,\n\toptions?: Partial<StreamIdleOptions> | StreamIdleOptionsResolver,\n): StreamFn {\n\treturn async (model, context, streamOptions) => {\n\t\tconst resolved = typeof options === \"function\" ? options(model, context, streamOptions) : options;\n\t\tconst cleaned: Partial<StreamIdleOptions> = {};\n\t\tif (resolved) {\n\t\t\tfor (const [key, val] of Object.entries(resolved)) {\n\t\t\t\tif (val !== undefined) {\n\t\t\t\t\t(cleaned as any)[key] = val;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst opts = { ...DEFAULT_STREAM_IDLE, ...cleaned };\n\n\t\tconst controller = new AbortController();\n\t\tconst callerSignal = streamOptions?.signal;\n\t\tlet callerAborted = callerSignal?.aborted ?? false;\n\t\tconst onCallerAbort = () => {\n\t\t\tcallerAborted = true;\n\t\t\tcontroller.abort(callerSignal?.reason);\n\t\t};\n\t\tif (callerAborted) controller.abort(callerSignal?.reason);\n\t\telse callerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n\n\t\tconst outer = createAssistantMessageEventStream();\n\n\t\t// Seeded so a connect-phase stall (no event ever arrived) still has a base message\n\t\t// to report on; overwritten with the latest real snapshot once events start flowing.\n\t\tlet latest: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tlet stalled = false;\n\t\tlet firstEventSeen = false;\n\t\tlet transportConfirmed = false;\n\n\t\t// The idle bound adapts per event: quiet while nothing/thinking, active while\n\t\t// text/toolCall content is flowing. Mutable so the onSilence closure always\n\t\t// reports the phase/bound that actually elapsed.\n\t\tlet currentPhase: StallPhase = \"connect\";\n\t\tlet currentBoundMs = opts.connectMs;\n\t\tconst idleBoundFor = (message: AssistantMessage): { phase: StallPhase; ms: number } => {\n\t\t\tconst lastBlock = message.content[message.content.length - 1];\n\t\t\treturn !lastBlock || lastBlock.type === \"thinking\"\n\t\t\t\t? { phase: \"quiet\", ms: opts.quietIdleMs }\n\t\t\t\t: { phase: \"active\", ms: opts.activeIdleMs };\n\t\t};\n\n\t\t// Emits the stall result directly (rather than after the inner loop finishes) so a\n\t\t// connection that never resolves at all still yields a result promptly — providers\n\t\t// are contractually expected to end their stream after abort, but the watchdog does\n\t\t// not depend on that to report the stall itself.\n\t\tconst stall = (phase: StallPhase, elapsedMs: number) => {\n\t\t\tif (callerAborted || stalled) return;\n\t\t\tstalled = true;\n\t\t\topts.onStall?.({ phase, elapsedMs });\n\t\t\tconst description = `stream stalled: no events for ${elapsedMs}ms (${phase} phase)`;\n\t\t\tcontroller.abort(new Error(description));\n\t\t\tconst message: AssistantMessage = {\n\t\t\t\t...latest,\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: description,\n\t\t\t};\n\t\t\touter.push({ type: \"error\", reason: \"error\", error: message });\n\t\t};\n\n\t\tlet watchdog = createSilenceWatchdog({\n\t\t\tsilenceMs: opts.connectMs,\n\t\t\tonSilence: () => stall(currentPhase, currentBoundMs),\n\t\t});\n\t\tconst markTransportConfirmed = () => {\n\t\t\tif (callerAborted || stalled || firstEventSeen || transportConfirmed) return;\n\t\t\ttransportConfirmed = true;\n\t\t\tcurrentPhase = \"quiet\";\n\t\t\tcurrentBoundMs = opts.quietIdleMs;\n\t\t\twatchdog.touch(opts.quietIdleMs);\n\t\t};\n\t\tconst originalOnResponse = streamOptions?.onResponse;\n\t\tconst inner = await streamFn(model, context, {\n\t\t\t...streamOptions,\n\t\t\tsignal: controller.signal,\n\t\t\tonResponse: async (response: ProviderResponse, responseModel: Model<Api>) => {\n\t\t\t\tmarkTransportConfirmed();\n\t\t\t\tawait originalOnResponse?.(response, responseModel);\n\t\t\t},\n\t\t});\n\t\tlet terminalPushed = false;\n\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tfor await (const event of inner) {\n\t\t\t\t\tif (stalled) break;\n\t\t\t\t\tlatest = partialFromEvent(event);\n\t\t\t\t\tconst bound = idleBoundFor(latest);\n\t\t\t\t\tcurrentPhase = bound.phase;\n\t\t\t\t\tcurrentBoundMs = bound.ms;\n\t\t\t\t\tif (!firstEventSeen) {\n\t\t\t\t\t\tfirstEventSeen = true;\n\t\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\t\twatchdog = createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs: bound.ms,\n\t\t\t\t\t\t\tonSilence: () => stall(currentPhase, currentBoundMs),\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\twatchdog.touch(bound.ms);\n\t\t\t\t\t}\n\t\t\t\t\t// A terminal event ends the turn: disarm synchronously, in the same tick as\n\t\t\t\t\t// the push below, so no watchdog can fire after the consumer's `result()`\n\t\t\t\t\t// promise resolves — a disarm that only happened once the loop later notices\n\t\t\t\t\t// `inner` is done would race with that resolution (it runs a tick or more\n\t\t\t\t\t// later) and could fire a spurious stall on an already-finished stream.\n\t\t\t\t\tconst terminal = event.type === \"done\" || event.type === \"error\";\n\t\t\t\t\tif (terminal) {\n\t\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\t}\n\t\t\t\t\touter.push(event);\n\t\t\t\t\tif (terminal) return;\n\t\t\t\t}\n\t\t\t\tif (!terminalPushed && !stalled) {\n\t\t\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\t\t\tconst description = callerAborted\n\t\t\t\t\t\t? \"stream aborted before terminal event\"\n\t\t\t\t\t\t: \"stream ended before terminal event\";\n\t\t\t\t\touter.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\treason: stopReason,\n\t\t\t\t\t\terror: { ...latest, stopReason, errorMessage: description },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\twatchdog.disarm();\n\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t}\n\t\t})();\n\n\t\treturn outer;\n\t};\n}\n"]}
1
+ {"version":3,"file":"watchdogs.js","sourceRoot":"","sources":["../../src/reliability/watchdogs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Silence/idle watchdogs for the reliability kernel.\n *\n * A silence watchdog bounds \"running but mute\" — it never bounds total runtime,\n * so long tasks that produce output are never killed (autonomy constraint).\n */\n\nexport interface SilenceWatchdog {\n\t/** Report activity (output chunk / stream event); resets the countdown.\n\t * Pass silenceMs to also change the bound for this and subsequent countdowns. */\n\ttouch(silenceMs?: number): void;\n\t/** Stop permanently (normal completion). Idempotent. */\n\tdisarm(): void;\n}\n\nexport interface SilenceWatchdogOptions {\n\tsilenceMs: number;\n\t/** Fired at most once, after silenceMs with no touch(). The watchdog self-disarms. */\n\tonSilence: () => void;\n}\n\nexport function createSilenceWatchdog(opts: SilenceWatchdogOptions): SilenceWatchdog {\n\tlet timer: NodeJS.Timeout | undefined;\n\tlet disarmed = false;\n\tlet currentSilenceMs = opts.silenceMs;\n\n\tconst arm = () => {\n\t\ttimer = setTimeout(() => {\n\t\t\tdisarmed = true;\n\t\t\ttimer = undefined;\n\t\t\topts.onSilence();\n\t\t}, currentSilenceMs);\n\t\t// Never keep the host process alive just for a watchdog.\n\t\ttimer.unref?.();\n\t};\n\n\tarm();\n\n\treturn {\n\t\ttouch(silenceMs?: number): void {\n\t\t\tif (disarmed) return;\n\t\t\tif (silenceMs !== undefined) currentSilenceMs = silenceMs;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tarm();\n\t\t},\n\t\tdisarm(): void {\n\t\t\tdisarmed = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t},\n\t};\n}\n\n// --- Stream-idle watchdog (wraps a StreamFn) -------------------------------\n\nimport {\n\ttype Api,\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\tcreateAssistantMessageEventStream,\n\ttype Model,\n\ttype ProviderResponse,\n} from \"@caupulican/pi-ai\";\nimport type { StreamFn } from \"../types.ts\";\n\nexport type StallPhase = \"connect\" | \"quiet\" | \"active\";\n\nexport interface StreamIdleOptions {\n\t/** Max ms to wait for the FIRST event (connection/first-token allowance). */\n\tconnectMs: number;\n\t/** Max ms between events while content is flowing — the latest content block is\n\t * text or toolCall. A flowing stream that goes silent this long is presumed dead. */\n\tactiveIdleMs: number;\n\t/** Max ms between events while the model is quietly working — no content blocks\n\t * yet (provider queue / prompt prefill / unstreamed reasoning) or the latest block\n\t * is thinking. Deep-thinking models and huge compaction prompts legitimately sit\n\t * here for minutes, so this bound is deliberately generous. */\n\tquietIdleMs: number;\n\t/** Fired once when a stall is detected, before the inner request is aborted. */\n\tonStall?: (info: { phase: StallPhase; elapsedMs: number }) => void;\n}\n\n/** User-locked defaults: connect 120s / active 180s / quiet 600s. The quiet bound must stay\n * below the HTTP dispatcher idle timeout (see coding-agent http-dispatcher.ts, 660s) or the\n * HTTP layer would kill quiet-but-healthy streams before this watchdog ever sees the gap. */\nexport const DEFAULT_STREAM_IDLE: StreamIdleOptions = {\n\tconnectMs: 120_000,\n\tactiveIdleMs: 180_000,\n\tquietIdleMs: 600_000,\n};\n\n/** Re-resolved at the start of every request, so hosts can wire live-tunable settings. */\nexport type StreamIdleOptionsResolver = (...args: Parameters<StreamFn>) => Partial<StreamIdleOptions>;\n\n/** Extracts the current AssistantMessage snapshot carried by any stream event variant. */\nfunction partialFromEvent(event: AssistantMessageEvent): AssistantMessage {\n\tif (event.type === \"done\") return event.message;\n\tif (event.type === \"error\") return event.error;\n\treturn event.partial;\n}\n\n/**\n * Wrap a StreamFn so a silently dead connection cannot wedge a turn forever.\n *\n * Phase-aware: `connectMs` bounds the wait for the first event; after that the\n * inter-event bound adapts to what the stream is doing — `quietIdleMs` while the\n * model is quietly working (no content blocks yet, or the latest block is thinking:\n * prefill, provider queues, unstreamed reasoning) and `activeIdleMs` once content is\n * flowing (latest block is text/toolCall). This keeps detection fast where silence is\n * anomalous without killing healthy deep-thinking or compaction-sized requests.\n * No bound ever limits total runtime (autonomy constraint).\n *\n * On stall, the inner request is aborted and the returned stream resolves immediately\n * with a synthetic `AssistantMessage` (`stopReason: \"error\"`, `errorMessage: \"stream\n * stalled: no events for <n>ms (<phase> phase)\"`) — the `stream stalled` phrasing is\n * what `classifyFailure` maps to a retryable `stream_stall`, so the host's\n * retry/failover path takes it from there.\n *\n * Options may be a resolver function; it is re-invoked at the start of every request,\n * so settings changes apply without rewrapping.\n *\n * A caller-initiated abort (via the options `signal`) is never treated as a stall: it\n * is chained into the wrapper's own controller. The inner stream's abort result is\n * forwarded untouched when it provides one; if it just ends, the wrapper synthesizes\n * an aborted terminal event so the returned stream always settles.\n */\nexport function withStreamIdleWatchdog(\n\tstreamFn: StreamFn,\n\toptions?: Partial<StreamIdleOptions> | StreamIdleOptionsResolver,\n): StreamFn {\n\treturn (model, context, streamOptions) => {\n\t\tconst resolved = typeof options === \"function\" ? options(model, context, streamOptions) : options;\n\t\tconst cleaned: Partial<StreamIdleOptions> = {};\n\t\tif (resolved) {\n\t\t\tfor (const [key, val] of Object.entries(resolved)) {\n\t\t\t\tif (val !== undefined) {\n\t\t\t\t\t(cleaned as any)[key] = val;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst opts = { ...DEFAULT_STREAM_IDLE, ...cleaned };\n\n\t\tconst controller = new AbortController();\n\t\tconst callerSignal = streamOptions?.signal;\n\t\tlet callerAborted = callerSignal?.aborted ?? false;\n\t\tconst outer = createAssistantMessageEventStream();\n\t\tlet readySettled = false;\n\t\tlet resolveReady: (stream: typeof outer) => void = () => {};\n\t\tconst ready = new Promise<typeof outer>((resolve) => {\n\t\t\tresolveReady = resolve;\n\t\t});\n\t\tconst settleReady = () => {\n\t\t\tif (readySettled) return;\n\t\t\treadySettled = true;\n\t\t\tresolveReady(outer);\n\t\t};\n\n\t\t// Seeded so a connect-phase stall (no event ever arrived) still has a base message\n\t\t// to report on; overwritten with the latest real snapshot once events start flowing.\n\t\tlet latest: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tlet stalled = false;\n\t\tlet firstEventSeen = false;\n\t\tlet transportConfirmed = false;\n\t\tlet streamSetupSettled = false;\n\t\tlet terminalPushed = false;\n\n\t\t// The idle bound adapts per event: quiet while nothing/thinking, active while\n\t\t// text/toolCall content is flowing. Mutable so the onSilence closure always\n\t\t// reports the phase/bound that actually elapsed.\n\t\tlet currentPhase: StallPhase = \"connect\";\n\t\tlet currentBoundMs = opts.connectMs;\n\t\tconst idleBoundFor = (message: AssistantMessage): { phase: StallPhase; ms: number } => {\n\t\t\tconst lastBlock = message.content[message.content.length - 1];\n\t\t\treturn !lastBlock || lastBlock.type === \"thinking\"\n\t\t\t\t? { phase: \"quiet\", ms: opts.quietIdleMs }\n\t\t\t\t: { phase: \"active\", ms: opts.activeIdleMs };\n\t\t};\n\n\t\t// Emits the stall result directly (rather than after the inner loop finishes) so a\n\t\t// connection that never resolves at all still yields a result promptly — providers\n\t\t// are contractually expected to end their stream after abort, but the watchdog does\n\t\t// not depend on that to report the stall itself.\n\t\tconst stall = (phase: StallPhase, elapsedMs: number) => {\n\t\t\tif (callerAborted || stalled || terminalPushed) return;\n\t\t\tstalled = true;\n\t\t\topts.onStall?.({ phase, elapsedMs });\n\t\t\tconst description = `stream stalled: no events for ${elapsedMs}ms (${phase} phase)`;\n\t\t\tcontroller.abort(new Error(description));\n\t\t\tconst message: AssistantMessage = {\n\t\t\t\t...latest,\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: description,\n\t\t\t};\n\t\t\tterminalPushed = true;\n\t\t\touter.push({ type: \"error\", reason: \"error\", error: message });\n\t\t\tsettleReady();\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t};\n\n\t\tlet watchdog = createSilenceWatchdog({\n\t\t\tsilenceMs: opts.connectMs,\n\t\t\tonSilence: () => stall(currentPhase, currentBoundMs),\n\t\t});\n\t\tconst markTransportConfirmed = () => {\n\t\t\tif (callerAborted || stalled || firstEventSeen || transportConfirmed) return;\n\t\t\ttransportConfirmed = true;\n\t\t\tcurrentPhase = \"quiet\";\n\t\t\tcurrentBoundMs = opts.quietIdleMs;\n\t\t\twatchdog.touch(opts.quietIdleMs);\n\t\t};\n\t\tconst originalOnResponse = streamOptions?.onResponse;\n\t\tconst pushSetupAbort = () => {\n\t\t\tif (streamSetupSettled || terminalPushed || stalled) return;\n\t\t\tterminalPushed = true;\n\t\t\twatchdog.disarm();\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\touter.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"aborted\",\n\t\t\t\terror: {\n\t\t\t\t\t...latest,\n\t\t\t\t\tstopReason: \"aborted\",\n\t\t\t\t\terrorMessage: \"stream aborted before terminal event during stream setup\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tsettleReady();\n\t\t};\n\t\tconst onCallerAbort = () => {\n\t\t\tcallerAborted = true;\n\t\t\tcontroller.abort(callerSignal?.reason);\n\t\t\tpushSetupAbort();\n\t\t};\n\t\tif (callerAborted) {\n\t\t\tonCallerAbort();\n\t\t\treturn ready;\n\t\t}\n\t\tcallerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tlet inner: Awaited<ReturnType<StreamFn>>;\n\t\t\t\ttry {\n\t\t\t\t\tinner = await streamFn(model, context, {\n\t\t\t\t\t\t...streamOptions,\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\tonResponse: async (response: ProviderResponse, responseModel: Model<Api>) => {\n\t\t\t\t\t\t\tmarkTransportConfirmed();\n\t\t\t\t\t\t\tawait originalOnResponse?.(response, responseModel);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tstreamSetupSettled = true;\n\t\t\t\t\tsettleReady();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tstreamSetupSettled = true;\n\t\t\t\t\tif (terminalPushed || stalled) return;\n\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\t\t\tconst detail = error instanceof Error ? `: ${error.message}` : `: ${String(error)}`;\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\touter.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\treason: stopReason,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t...latest,\n\t\t\t\t\t\t\tstopReason,\n\t\t\t\t\t\t\terrorMessage: callerAborted\n\t\t\t\t\t\t\t\t? `stream aborted before terminal event${detail}`\n\t\t\t\t\t\t\t\t: `stream ended without terminal event${detail}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tsettleReady();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A setup promise may resolve after a connect stall or caller abort. The outer\n\t\t\t\t// stream is already terminal in that case, so never attach a late event pump.\n\t\t\t\tif (terminalPushed || stalled || callerAborted) return;\n\t\t\t\tfor await (const event of inner) {\n\t\t\t\t\tif (stalled) break;\n\t\t\t\t\tlatest = partialFromEvent(event);\n\t\t\t\t\tconst bound = idleBoundFor(latest);\n\t\t\t\t\tcurrentPhase = bound.phase;\n\t\t\t\t\tcurrentBoundMs = bound.ms;\n\t\t\t\t\tif (!firstEventSeen) {\n\t\t\t\t\t\tfirstEventSeen = true;\n\t\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\t\twatchdog = createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs: bound.ms,\n\t\t\t\t\t\t\tonSilence: () => stall(currentPhase, currentBoundMs),\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\twatchdog.touch(bound.ms);\n\t\t\t\t\t}\n\t\t\t\t\t// A terminal event ends the turn: disarm synchronously, in the same tick as\n\t\t\t\t\t// the push below, so no watchdog can fire after the consumer's `result()`\n\t\t\t\t\t// promise resolves — a disarm that only happened once the loop later notices\n\t\t\t\t\t// `inner` is done would race with that resolution (it runs a tick or more\n\t\t\t\t\t// later) and could fire a spurious stall on an already-finished stream.\n\t\t\t\t\tconst terminal = event.type === \"done\" || event.type === \"error\";\n\t\t\t\t\tif (terminal) {\n\t\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\t}\n\t\t\t\t\touter.push(event);\n\t\t\t\t\tif (terminal) return;\n\t\t\t\t}\n\t\t\t\tif (!terminalPushed && !stalled) {\n\t\t\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\t\t\tconst description = callerAborted\n\t\t\t\t\t\t? \"stream aborted before terminal event\"\n\t\t\t\t\t\t: \"stream ended without terminal event\";\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\touter.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\treason: stopReason,\n\t\t\t\t\t\terror: { ...latest, stopReason, errorMessage: description },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!terminalPushed && !stalled) {\n\t\t\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\t\t\tconst detail = error instanceof Error ? `: ${error.message}` : `: ${String(error)}`;\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\touter.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\treason: stopReason,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t...latest,\n\t\t\t\t\t\t\tstopReason,\n\t\t\t\t\t\t\terrorMessage: callerAborted\n\t\t\t\t\t\t\t\t? `stream aborted before terminal event${detail}`\n\t\t\t\t\t\t\t\t: `stream ended without terminal event${detail}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tsettleReady();\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\twatchdog.disarm();\n\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t}\n\t\t})();\n\n\t\treturn ready;\n\t};\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEvent, ImageContent, Message, Model, SimpleStreamOptions, streamSimple, TextContent, Tool, ToolArgumentValidationTelemetryEvent, ToolResultMessage } from "@caupulican/pi-ai";
1
+ import type { Api, AssistantMessage, AssistantMessageEvent, Context, ImageContent, Message, Model, SimpleStreamOptions, streamSimple, TextContent, Tool, ToolArgumentValidationTelemetryEvent, ToolResultMessage } from "@caupulican/pi-ai";
2
2
  import type { Static, TSchema } from "typebox";
3
3
  /**
4
4
  * Stream function used by the agent loop.
@@ -173,6 +173,15 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
173
173
  * ```
174
174
  */
175
175
  transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
176
+ /**
177
+ * Resolve the reasoning effort after context transformation and immediately before the provider
178
+ * request. This supports request-local policy decisions that must not mutate persisted agent state.
179
+ */
180
+ resolveRequestReasoning?: (reasoning: SimpleStreamOptions["reasoning"], request: {
181
+ model: Model<Api>;
182
+ context: Context;
183
+ maxTokens?: number;
184
+ }) => SimpleStreamOptions["reasoning"];
176
185
  /**
177
186
  * Resolves an API key dynamically for each LLM call.
178
187
  *
@@ -291,10 +300,12 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
291
300
  }
292
301
  /**
293
302
  * Thinking/reasoning level for models that support it.
294
- * Note: "xhigh" is only supported by selected model families. Use model thinking-level metadata
295
- * from @caupulican/pi-ai to detect support for a concrete model.
303
+ * Note: "xhigh", "max", and "ultra" are only supported by selected model families. "ultra" maps
304
+ * to the model's maximum provider effort and reinforces proactive orchestration in capable hosts;
305
+ * delegation can remain available at lower levels. Use model thinking-level metadata from
306
+ * @caupulican/pi-ai to detect support for a concrete model.
296
307
  */
297
- export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
308
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
298
309
  /**
299
310
  * Extensible interface for custom app messages.
300
311
  * Apps can extend via declaration merging:
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamSimple,\n\tTextContent,\n\tTool,\n\tToolArgumentValidationTelemetryEvent,\n\tToolResultMessage,\n} from \"@caupulican/pi-ai\";\nimport type { Static, TSchema } from \"typebox\";\n\n/**\n * Stream function used by the agent loop.\n *\n * Contract:\n * - Must not throw or return a rejected promise for request/model/runtime failures.\n * - Must return an AssistantMessageEventStream.\n * - Failures must be encoded in the returned stream via protocol events and a\n * final AssistantMessage with stopReason \"error\" or \"aborted\" and errorMessage.\n */\nexport type StreamFn = (\n\t...args: Parameters<typeof streamSimple>\n) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;\n\n/**\n * Configuration for how tool calls from a single assistant message are executed.\n *\n * - \"sequential\": each tool call is prepared, executed, and finalized before the next one starts.\n * - \"parallel\": tool calls are prepared sequentially, then allowed tools execute concurrently.\n * `tool_execution_end` is emitted in tool completion order after each tool is finalized,\n * while tool-result message artifacts are emitted later in assistant source order.\n */\nexport type ToolExecutionMode = \"sequential\" | \"parallel\";\n\n/**\n * Controls how many queued user messages are injected when the agent loop reaches a queue drain point.\n *\n * - \"all\": drain and inject every queued message at that point.\n * - \"one-at-a-time\": drain and inject only the oldest queued message, leaving the rest queued for later drain points.\n */\nexport type QueueMode = \"all\" | \"one-at-a-time\";\n\n/** A single tool call content block emitted by an assistant message. */\nexport type AgentToolCall = Extract<AssistantMessage[\"content\"][number], { type: \"toolCall\" }>;\n\n/**\n * Result returned from `beforeToolCall`.\n *\n * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.\n * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.\n */\nexport interface BeforeToolCallResult {\n\tblock?: boolean;\n\treason?: string;\n}\n\n/**\n * Partial override returned from `afterToolCall`.\n *\n * Merge semantics are field-by-field:\n * - `content`: if provided, replaces the tool result content array in full\n * - `details`: if provided, replaces the tool result details value in full\n * - `isError`: if provided, replaces the tool result error flag\n * - `terminate`: if provided, replaces the early-termination hint\n *\n * Omitted fields keep the original executed tool result values.\n * There is no deep merge for `content` or `details`.\n */\nexport interface AfterToolCallResult {\n\tcontent?: (TextContent | ImageContent)[];\n\tdetails?: unknown;\n\tisError?: boolean;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Context passed to `beforeToolCall`. */\nexport interface BeforeToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** Current agent context at the time the tool call is prepared. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `afterToolCall`. */\nexport interface AfterToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** The executed tool result before any `afterToolCall` overrides are applied. */\n\tresult: AgentToolResult<any>;\n\t/** Whether the executed tool result is currently treated as an error. */\n\tisError: boolean;\n\t/** Current agent context at the time the tool call is finalized. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `shouldStopAfterTurn`. */\nexport interface ShouldStopAfterTurnContext {\n\t/** The assistant message that completed the turn. */\n\tmessage: AssistantMessage;\n\t/** Tool result messages passed to the preceding `turn_end` event. */\n\ttoolResults: ToolResultMessage[];\n\t/** Current agent context after the turn's assistant message and tool results have been appended. */\n\tcontext: AgentContext;\n\t/** Messages that this loop invocation will return if it exits at this point. Prompt runs include the initial prompt messages; continuation runs do not include pre-existing context messages. */\n\tnewMessages: AgentMessage[];\n}\n\n/** Replacement runtime state used by the agent loop before starting another provider request. */\nexport interface AgentLoopTurnUpdate {\n\t/** Context for the next provider request. */\n\tcontext?: AgentContext;\n\t/** Model for the next provider request. */\n\tmodel?: Model<any>;\n\t/** Thinking level for the next provider request. */\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport interface ToolValidationEscalationEvent {\n\ttool: string;\n\tsignature: string;\n\trepeats: number;\n\tmodel: string;\n\tprovider: string;\n}\n\nexport interface PrepareNextTurnContext extends ShouldStopAfterTurnContext {}\n\n/**\n * Default runaway-loop backstop: a single identical tool-call signature recurring this many times\n * within a sliding window (2×) stops the loop. Generous enough that legitimate long/varied work never\n * trips it, but bounds the cost of a model wedged repeating one failing call forever.\n */\nexport const DEFAULT_MAX_STALL_TURNS = 12;\n\nexport interface AgentLoopConfig extends SimpleStreamOptions {\n\tmodel: Model<any>;\n\n\t/**\n\t * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.\n\t *\n\t * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage\n\t * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,\n\t * status messages) should be filtered out.\n\t *\n\t * Contract: must not throw or reject. Return a safe fallback value instead.\n\t * Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t *\n\t * @example\n\t * ```typescript\n\t * convertToLlm: (messages) => messages.flatMap(m => {\n\t * if (m.role === \"custom\") {\n\t * // Convert custom message to user message\n\t * return [{ role: \"user\", content: m.content, timestamp: m.timestamp }];\n\t * }\n\t * if (m.role === \"notification\") {\n\t * // Filter out UI-only messages\n\t * return [];\n\t * }\n\t * // Pass through standard LLM messages\n\t * return [m];\n\t * })\n\t * ```\n\t */\n\tconvertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;\n\n\t/**\n\t * Optional transform applied to the context before `convertToLlm`.\n\t *\n\t * Use this for operations that work at the AgentMessage level:\n\t * - Context window management (pruning old messages)\n\t * - Injecting context from external sources\n\t *\n\t * Contract: must not throw or reject. Return the original messages or another\n\t * safe fallback value instead.\n\t *\n\t * @example\n\t * ```typescript\n\t * transformContext: async (messages) => {\n\t * if (estimateTokens(messages) > MAX_TOKENS) {\n\t * return pruneOldMessages(messages);\n\t * }\n\t * return messages;\n\t * }\n\t * ```\n\t */\n\ttransformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;\n\n\t/**\n\t * Resolves an API key dynamically for each LLM call.\n\t *\n\t * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire\n\t * during long-running tool execution phases.\n\t *\n\t * Contract: must not throw or reject. Return undefined when no key is available.\n\t */\n\tgetApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;\n\n\t/**\n\t * Called after each turn fully completes and `turn_end` has been emitted.\n\t *\n\t * If it returns true, the loop emits `agent_end` and exits before polling steering or follow-up queues,\n\t * without starting another LLM call. The current assistant response and any tool executions finish normally.\n\t *\n\t * Use this to request a graceful stop after the current turn, e.g. before context gets too full.\n\t *\n\t * Contract: must not throw or reject. Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t */\n\tshouldStopAfterTurn?: (context: ShouldStopAfterTurnContext) => boolean | Promise<boolean>;\n\n\t/**\n\t * Runaway-loop backstop. A model stuck repeating the SAME tool call (identical name + arguments) —\n\t * because a tool keeps erroring, or it is confused/oscillating — makes no progress yet keeps\n\t * consuming tokens indefinitely (history grows every turn). This bounds that cost: if one tool-call\n\t * signature recurs at least this many times within a sliding window (2×), the loop stops gracefully\n\t * (emits `agent_end`). It counts ONLY turns that issued tool calls and keys on exact name+arguments,\n\t * so legitimate long or varied agentic work never trips it. `0` disables the backstop.\n\t * Default: {@link DEFAULT_MAX_STALL_TURNS}.\n\t */\n\tmaxStallTurns?: number;\n\n\t/**\n\t * Observability hook fired once if the {@link maxStallTurns} runaway backstop trips, just before the\n\t * loop stops. Lets the host surface/log why the run ended. Must not throw.\n\t */\n\tonRunawayStop?: (info: { signature: string; repeats: number }) => void;\n\n\t/**\n\t * Called after `turn_end` and before the loop decides whether another provider request should start.\n\t * Return replacement context/model/thinking state to affect the next turn in this run.\n\t * Return undefined to keep using the current context/config.\n\t */\n\tprepareNextTurn?: (\n\t\tcontext: PrepareNextTurnContext,\n\t) => AgentLoopTurnUpdate | undefined | Promise<AgentLoopTurnUpdate | undefined>;\n\n\t/**\n\t * Returns steering messages to inject into the conversation mid-run.\n\t *\n\t * Called after the current assistant turn finishes executing its tool calls, unless `shouldStopAfterTurn` exits first.\n\t * If messages are returned, they are added to the context before the next LLM call.\n\t * Tool calls from the current assistant message are not skipped.\n\t *\n\t * Use this for \"steering\" the agent while it's working.\n\t *\n\t * Contract: must not throw or reject. Return [] when no steering messages are available.\n\t */\n\tgetSteeringMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Returns follow-up messages to process after the agent would otherwise stop.\n\t *\n\t * Called when the agent has no more tool calls and no steering messages.\n\t * If messages are returned, they're added to the context and the agent\n\t * continues with another turn.\n\t *\n\t * Use this for follow-up messages that should wait until the agent finishes.\n\t *\n\t * Contract: must not throw or reject. Return [] when no follow-up messages are available.\n\t */\n\tgetFollowUpMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Tool execution mode.\n\t * - \"sequential\": execute tool calls one by one\n\t * - \"parallel\": preflight tool calls sequentially, then execute allowed tools concurrently;\n\t * emit `tool_execution_end` in tool completion order after each tool is finalized,\n\t * then emit tool-result message artifacts later in assistant source order\n\t *\n\t * Default: \"parallel\"\n\t */\n\ttoolExecution?: ToolExecutionMode;\n\n\t/** Disable in-band tool repair teaching notes. Default: enabled. */\n\ttoolArgumentTeachEnabled?: boolean;\n\n\t/**\n\t * Observe tool argument validation outcomes. Events contain only shape metadata\n\t * (outcome, model/provider/tool, failure modes, repairs) and never argument values.\n\t */\n\tonToolArgumentValidation?: (event: ToolArgumentValidationTelemetryEvent) => void;\n\n\t/**\n\t * Number of consecutive identical validation bounces before adding full schema/example feedback\n\t * and notifying the host. Set to 0 to disable. Default: 3.\n\t */\n\ttoolValidationEscalationThreshold?: number;\n\n\t/**\n\t * Fired when a repeated identical tool validation failure reaches the escalation threshold.\n\t * Hosts with model routers can use this signal to move the next turn off a cheap route.\n\t */\n\tonToolValidationEscalation?: (event: ToolValidationEscalationEvent) => void;\n\n\t/**\n\t * Called before a tool is executed, after arguments have been validated.\n\t *\n\t * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tbeforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;\n\n\t/**\n\t * Called after a tool finishes executing, before `tool_execution_end` and tool-result message events are emitted.\n\t *\n\t * Return an `AfterToolCallResult` to override parts of the executed tool result:\n\t * - `content` replaces the full content array\n\t * - `details` replaces the full details payload\n\t * - `isError` replaces the error flag\n\t * - `terminate` replaces the early-termination hint\n\t *\n\t * Any omitted fields keep their original values. No deep merge is performed.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tafterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;\n}\n\n/**\n * Thinking/reasoning level for models that support it.\n * Note: \"xhigh\" is only supported by selected model families. Use model thinking-level metadata\n * from @caupulican/pi-ai to detect support for a concrete model.\n */\nexport type ThinkingLevel = \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/**\n * Extensible interface for custom app messages.\n * Apps can extend via declaration merging:\n *\n * @example\n * ```typescript\n * declare module \"@mariozechner/agent\" {\n * interface CustomAgentMessages {\n * artifact: ArtifactMessage;\n * notification: NotificationMessage;\n * }\n * }\n * ```\n */\nexport interface CustomAgentMessages {\n\t// Empty by default - apps extend via declaration merging\n}\n\n/**\n * AgentMessage: Union of LLM messages + custom messages.\n * This abstraction allows apps to add custom message types while maintaining\n * type safety and compatibility with the base LLM messages.\n */\nexport type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];\n\n/**\n * Public agent state.\n *\n * `tools` and `messages` use accessor properties so implementations can copy\n * assigned arrays before storing them.\n */\nexport interface AgentState {\n\t/** System prompt sent with each model request. */\n\tsystemPrompt: string;\n\t/** Active model used for future turns. */\n\tmodel: Model<any>;\n\t/** Requested reasoning level for future turns. */\n\tthinkingLevel: ThinkingLevel;\n\t/** Available tools. Assigning a new array copies the top-level array. */\n\tset tools(tools: AgentTool<any>[]);\n\tget tools(): AgentTool<any>[];\n\t/** Conversation transcript. Assigning a new array copies the top-level array. */\n\tset messages(messages: AgentMessage[]);\n\tget messages(): AgentMessage[];\n\t/**\n\t * True while the agent is processing a prompt or continuation.\n\t *\n\t * This remains true until awaited `agent_end` listeners settle.\n\t */\n\treadonly isStreaming: boolean;\n\t/** Partial assistant message for the current streamed response, if any. */\n\treadonly streamingMessage?: AgentMessage;\n\t/** Tool call ids currently executing. */\n\treadonly pendingToolCalls: ReadonlySet<string>;\n\t/** Error message from the most recent failed or aborted assistant turn, if any. */\n\treadonly errorMessage?: string;\n}\n\n/** Final or partial result produced by a tool. */\nexport interface AgentToolResult<T> {\n\t/** Text or image content returned to the model. */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Arbitrary structured details for logs or UI rendering. */\n\tdetails: T;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Callback used by tools to stream partial execution updates. */\nexport type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;\n\n/** Tool definition used by the agent runtime. */\nexport interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {\n\t/** Human-readable label for UI display. */\n\tlabel: string;\n\t/**\n\t * Optional compatibility shim for raw tool-call arguments before schema validation.\n\t * Must return an object that matches `TParameters`.\n\t */\n\tprepareArguments?: (args: unknown) => Static<TParameters>;\n\t/** Execute the tool call. Throw on failure instead of encoding errors in `content`. */\n\texecute: (\n\t\ttoolCallId: string,\n\t\tparams: Static<TParameters>,\n\t\tsignal?: AbortSignal,\n\t\tonUpdate?: AgentToolUpdateCallback<TDetails>,\n\t) => Promise<AgentToolResult<TDetails>>;\n\t/**\n\t * Per-tool execution mode override.\n\t * - \"sequential\": this tool must execute one at a time with other tool calls.\n\t * - \"parallel\": this tool can execute concurrently with other tool calls.\n\t *\n\t * If omitted, the default execution mode applies.\n\t */\n\texecutionMode?: ToolExecutionMode;\n}\n\n/** Context snapshot passed into the low-level agent loop. */\nexport interface AgentContext {\n\t/** System prompt included with the request. */\n\tsystemPrompt: string;\n\t/** Transcript visible to the model. */\n\tmessages: AgentMessage[];\n\t/** Tools available for this run. */\n\ttools?: AgentTool<any>[];\n}\n\n/**\n * Events emitted by the Agent for UI updates.\n *\n * `agent_end` is the last event emitted for a run, but awaited `Agent.subscribe()`\n * listeners for that event are still part of run settlement. The agent becomes\n * idle only after those listeners finish.\n */\nexport interface ToolCallRepairInfo {\n\trepaired: true;\n\trawArguments?: Record<string, unknown>;\n\tnotes?: string[];\n}\n\nexport type AgentEvent =\n\t// Agent lifecycle\n\t| { type: \"agent_start\" }\n\t| { type: \"agent_end\"; messages: AgentMessage[] }\n\t// Turn lifecycle - a turn is one assistant response + any tool calls/results\n\t| { type: \"turn_start\" }\n\t| { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n\t// Message lifecycle - emitted for user, assistant, and toolResult messages\n\t| { type: \"message_start\"; message: AgentMessage }\n\t// Only emitted for assistant messages during streaming\n\t| { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n\t| { type: \"message_end\"; message: AgentMessage }\n\t// Tool execution lifecycle\n\t| { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any; repair?: ToolCallRepairInfo }\n\t| {\n\t\t\ttype: \"tool_execution_update\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\targs: any;\n\t\t\tpartialResult: any;\n\t\t\trepair?: ToolCallRepairInfo;\n\t }\n\t| {\n\t\t\ttype: \"tool_execution_end\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\tresult: any;\n\t\t\tisError: boolean;\n\t\t\trepair?: ToolCallRepairInfo;\n\t };\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tApi,\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tContext,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamSimple,\n\tTextContent,\n\tTool,\n\tToolArgumentValidationTelemetryEvent,\n\tToolResultMessage,\n} from \"@caupulican/pi-ai\";\nimport type { Static, TSchema } from \"typebox\";\n\n/**\n * Stream function used by the agent loop.\n *\n * Contract:\n * - Must not throw or return a rejected promise for request/model/runtime failures.\n * - Must return an AssistantMessageEventStream.\n * - Failures must be encoded in the returned stream via protocol events and a\n * final AssistantMessage with stopReason \"error\" or \"aborted\" and errorMessage.\n */\nexport type StreamFn = (\n\t...args: Parameters<typeof streamSimple>\n) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;\n\n/**\n * Configuration for how tool calls from a single assistant message are executed.\n *\n * - \"sequential\": each tool call is prepared, executed, and finalized before the next one starts.\n * - \"parallel\": tool calls are prepared sequentially, then allowed tools execute concurrently.\n * `tool_execution_end` is emitted in tool completion order after each tool is finalized,\n * while tool-result message artifacts are emitted later in assistant source order.\n */\nexport type ToolExecutionMode = \"sequential\" | \"parallel\";\n\n/**\n * Controls how many queued user messages are injected when the agent loop reaches a queue drain point.\n *\n * - \"all\": drain and inject every queued message at that point.\n * - \"one-at-a-time\": drain and inject only the oldest queued message, leaving the rest queued for later drain points.\n */\nexport type QueueMode = \"all\" | \"one-at-a-time\";\n\n/** A single tool call content block emitted by an assistant message. */\nexport type AgentToolCall = Extract<AssistantMessage[\"content\"][number], { type: \"toolCall\" }>;\n\n/**\n * Result returned from `beforeToolCall`.\n *\n * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.\n * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.\n */\nexport interface BeforeToolCallResult {\n\tblock?: boolean;\n\treason?: string;\n}\n\n/**\n * Partial override returned from `afterToolCall`.\n *\n * Merge semantics are field-by-field:\n * - `content`: if provided, replaces the tool result content array in full\n * - `details`: if provided, replaces the tool result details value in full\n * - `isError`: if provided, replaces the tool result error flag\n * - `terminate`: if provided, replaces the early-termination hint\n *\n * Omitted fields keep the original executed tool result values.\n * There is no deep merge for `content` or `details`.\n */\nexport interface AfterToolCallResult {\n\tcontent?: (TextContent | ImageContent)[];\n\tdetails?: unknown;\n\tisError?: boolean;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Context passed to `beforeToolCall`. */\nexport interface BeforeToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** Current agent context at the time the tool call is prepared. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `afterToolCall`. */\nexport interface AfterToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** The executed tool result before any `afterToolCall` overrides are applied. */\n\tresult: AgentToolResult<any>;\n\t/** Whether the executed tool result is currently treated as an error. */\n\tisError: boolean;\n\t/** Current agent context at the time the tool call is finalized. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `shouldStopAfterTurn`. */\nexport interface ShouldStopAfterTurnContext {\n\t/** The assistant message that completed the turn. */\n\tmessage: AssistantMessage;\n\t/** Tool result messages passed to the preceding `turn_end` event. */\n\ttoolResults: ToolResultMessage[];\n\t/** Current agent context after the turn's assistant message and tool results have been appended. */\n\tcontext: AgentContext;\n\t/** Messages that this loop invocation will return if it exits at this point. Prompt runs include the initial prompt messages; continuation runs do not include pre-existing context messages. */\n\tnewMessages: AgentMessage[];\n}\n\n/** Replacement runtime state used by the agent loop before starting another provider request. */\nexport interface AgentLoopTurnUpdate {\n\t/** Context for the next provider request. */\n\tcontext?: AgentContext;\n\t/** Model for the next provider request. */\n\tmodel?: Model<any>;\n\t/** Thinking level for the next provider request. */\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport interface ToolValidationEscalationEvent {\n\ttool: string;\n\tsignature: string;\n\trepeats: number;\n\tmodel: string;\n\tprovider: string;\n}\n\nexport interface PrepareNextTurnContext extends ShouldStopAfterTurnContext {}\n\n/**\n * Default runaway-loop backstop: a single identical tool-call signature recurring this many times\n * within a sliding window (2×) stops the loop. Generous enough that legitimate long/varied work never\n * trips it, but bounds the cost of a model wedged repeating one failing call forever.\n */\nexport const DEFAULT_MAX_STALL_TURNS = 12;\n\nexport interface AgentLoopConfig extends SimpleStreamOptions {\n\tmodel: Model<any>;\n\n\t/**\n\t * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.\n\t *\n\t * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage\n\t * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,\n\t * status messages) should be filtered out.\n\t *\n\t * Contract: must not throw or reject. Return a safe fallback value instead.\n\t * Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t *\n\t * @example\n\t * ```typescript\n\t * convertToLlm: (messages) => messages.flatMap(m => {\n\t * if (m.role === \"custom\") {\n\t * // Convert custom message to user message\n\t * return [{ role: \"user\", content: m.content, timestamp: m.timestamp }];\n\t * }\n\t * if (m.role === \"notification\") {\n\t * // Filter out UI-only messages\n\t * return [];\n\t * }\n\t * // Pass through standard LLM messages\n\t * return [m];\n\t * })\n\t * ```\n\t */\n\tconvertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;\n\n\t/**\n\t * Optional transform applied to the context before `convertToLlm`.\n\t *\n\t * Use this for operations that work at the AgentMessage level:\n\t * - Context window management (pruning old messages)\n\t * - Injecting context from external sources\n\t *\n\t * Contract: must not throw or reject. Return the original messages or another\n\t * safe fallback value instead.\n\t *\n\t * @example\n\t * ```typescript\n\t * transformContext: async (messages) => {\n\t * if (estimateTokens(messages) > MAX_TOKENS) {\n\t * return pruneOldMessages(messages);\n\t * }\n\t * return messages;\n\t * }\n\t * ```\n\t */\n\ttransformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;\n\n\t/**\n\t * Resolve the reasoning effort after context transformation and immediately before the provider\n\t * request. This supports request-local policy decisions that must not mutate persisted agent state.\n\t */\n\tresolveRequestReasoning?: (\n\t\treasoning: SimpleStreamOptions[\"reasoning\"],\n\t\trequest: { model: Model<Api>; context: Context; maxTokens?: number },\n\t) => SimpleStreamOptions[\"reasoning\"];\n\n\t/**\n\t * Resolves an API key dynamically for each LLM call.\n\t *\n\t * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire\n\t * during long-running tool execution phases.\n\t *\n\t * Contract: must not throw or reject. Return undefined when no key is available.\n\t */\n\tgetApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;\n\n\t/**\n\t * Called after each turn fully completes and `turn_end` has been emitted.\n\t *\n\t * If it returns true, the loop emits `agent_end` and exits before polling steering or follow-up queues,\n\t * without starting another LLM call. The current assistant response and any tool executions finish normally.\n\t *\n\t * Use this to request a graceful stop after the current turn, e.g. before context gets too full.\n\t *\n\t * Contract: must not throw or reject. Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t */\n\tshouldStopAfterTurn?: (context: ShouldStopAfterTurnContext) => boolean | Promise<boolean>;\n\n\t/**\n\t * Runaway-loop backstop. A model stuck repeating the SAME tool call (identical name + arguments) —\n\t * because a tool keeps erroring, or it is confused/oscillating — makes no progress yet keeps\n\t * consuming tokens indefinitely (history grows every turn). This bounds that cost: if one tool-call\n\t * signature recurs at least this many times within a sliding window (2×), the loop stops gracefully\n\t * (emits `agent_end`). It counts ONLY turns that issued tool calls and keys on exact name+arguments,\n\t * so legitimate long or varied agentic work never trips it. `0` disables the backstop.\n\t * Default: {@link DEFAULT_MAX_STALL_TURNS}.\n\t */\n\tmaxStallTurns?: number;\n\n\t/**\n\t * Observability hook fired once if the {@link maxStallTurns} runaway backstop trips, just before the\n\t * loop stops. Lets the host surface/log why the run ended. Must not throw.\n\t */\n\tonRunawayStop?: (info: { signature: string; repeats: number }) => void;\n\n\t/**\n\t * Called after `turn_end` and before the loop decides whether another provider request should start.\n\t * Return replacement context/model/thinking state to affect the next turn in this run.\n\t * Return undefined to keep using the current context/config.\n\t */\n\tprepareNextTurn?: (\n\t\tcontext: PrepareNextTurnContext,\n\t) => AgentLoopTurnUpdate | undefined | Promise<AgentLoopTurnUpdate | undefined>;\n\n\t/**\n\t * Returns steering messages to inject into the conversation mid-run.\n\t *\n\t * Called after the current assistant turn finishes executing its tool calls, unless `shouldStopAfterTurn` exits first.\n\t * If messages are returned, they are added to the context before the next LLM call.\n\t * Tool calls from the current assistant message are not skipped.\n\t *\n\t * Use this for \"steering\" the agent while it's working.\n\t *\n\t * Contract: must not throw or reject. Return [] when no steering messages are available.\n\t */\n\tgetSteeringMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Returns follow-up messages to process after the agent would otherwise stop.\n\t *\n\t * Called when the agent has no more tool calls and no steering messages.\n\t * If messages are returned, they're added to the context and the agent\n\t * continues with another turn.\n\t *\n\t * Use this for follow-up messages that should wait until the agent finishes.\n\t *\n\t * Contract: must not throw or reject. Return [] when no follow-up messages are available.\n\t */\n\tgetFollowUpMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Tool execution mode.\n\t * - \"sequential\": execute tool calls one by one\n\t * - \"parallel\": preflight tool calls sequentially, then execute allowed tools concurrently;\n\t * emit `tool_execution_end` in tool completion order after each tool is finalized,\n\t * then emit tool-result message artifacts later in assistant source order\n\t *\n\t * Default: \"parallel\"\n\t */\n\ttoolExecution?: ToolExecutionMode;\n\n\t/** Disable in-band tool repair teaching notes. Default: enabled. */\n\ttoolArgumentTeachEnabled?: boolean;\n\n\t/**\n\t * Observe tool argument validation outcomes. Events contain only shape metadata\n\t * (outcome, model/provider/tool, failure modes, repairs) and never argument values.\n\t */\n\tonToolArgumentValidation?: (event: ToolArgumentValidationTelemetryEvent) => void;\n\n\t/**\n\t * Number of consecutive identical validation bounces before adding full schema/example feedback\n\t * and notifying the host. Set to 0 to disable. Default: 3.\n\t */\n\ttoolValidationEscalationThreshold?: number;\n\n\t/**\n\t * Fired when a repeated identical tool validation failure reaches the escalation threshold.\n\t * Hosts with model routers can use this signal to move the next turn off a cheap route.\n\t */\n\tonToolValidationEscalation?: (event: ToolValidationEscalationEvent) => void;\n\n\t/**\n\t * Called before a tool is executed, after arguments have been validated.\n\t *\n\t * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tbeforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;\n\n\t/**\n\t * Called after a tool finishes executing, before `tool_execution_end` and tool-result message events are emitted.\n\t *\n\t * Return an `AfterToolCallResult` to override parts of the executed tool result:\n\t * - `content` replaces the full content array\n\t * - `details` replaces the full details payload\n\t * - `isError` replaces the error flag\n\t * - `terminate` replaces the early-termination hint\n\t *\n\t * Any omitted fields keep their original values. No deep merge is performed.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tafterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;\n}\n\n/**\n * Thinking/reasoning level for models that support it.\n * Note: \"xhigh\", \"max\", and \"ultra\" are only supported by selected model families. \"ultra\" maps\n * to the model's maximum provider effort and reinforces proactive orchestration in capable hosts;\n * delegation can remain available at lower levels. Use model thinking-level metadata from\n * @caupulican/pi-ai to detect support for a concrete model.\n */\nexport type ThinkingLevel = \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\" | \"ultra\";\n\n/**\n * Extensible interface for custom app messages.\n * Apps can extend via declaration merging:\n *\n * @example\n * ```typescript\n * declare module \"@mariozechner/agent\" {\n * interface CustomAgentMessages {\n * artifact: ArtifactMessage;\n * notification: NotificationMessage;\n * }\n * }\n * ```\n */\nexport interface CustomAgentMessages {\n\t// Empty by default - apps extend via declaration merging\n}\n\n/**\n * AgentMessage: Union of LLM messages + custom messages.\n * This abstraction allows apps to add custom message types while maintaining\n * type safety and compatibility with the base LLM messages.\n */\nexport type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];\n\n/**\n * Public agent state.\n *\n * `tools` and `messages` use accessor properties so implementations can copy\n * assigned arrays before storing them.\n */\nexport interface AgentState {\n\t/** System prompt sent with each model request. */\n\tsystemPrompt: string;\n\t/** Active model used for future turns. */\n\tmodel: Model<any>;\n\t/** Requested reasoning level for future turns. */\n\tthinkingLevel: ThinkingLevel;\n\t/** Available tools. Assigning a new array copies the top-level array. */\n\tset tools(tools: AgentTool<any>[]);\n\tget tools(): AgentTool<any>[];\n\t/** Conversation transcript. Assigning a new array copies the top-level array. */\n\tset messages(messages: AgentMessage[]);\n\tget messages(): AgentMessage[];\n\t/**\n\t * True while the agent is processing a prompt or continuation.\n\t *\n\t * This remains true until awaited `agent_end` listeners settle.\n\t */\n\treadonly isStreaming: boolean;\n\t/** Partial assistant message for the current streamed response, if any. */\n\treadonly streamingMessage?: AgentMessage;\n\t/** Tool call ids currently executing. */\n\treadonly pendingToolCalls: ReadonlySet<string>;\n\t/** Error message from the most recent failed or aborted assistant turn, if any. */\n\treadonly errorMessage?: string;\n}\n\n/** Final or partial result produced by a tool. */\nexport interface AgentToolResult<T> {\n\t/** Text or image content returned to the model. */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Arbitrary structured details for logs or UI rendering. */\n\tdetails: T;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Callback used by tools to stream partial execution updates. */\nexport type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;\n\n/** Tool definition used by the agent runtime. */\nexport interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {\n\t/** Human-readable label for UI display. */\n\tlabel: string;\n\t/**\n\t * Optional compatibility shim for raw tool-call arguments before schema validation.\n\t * Must return an object that matches `TParameters`.\n\t */\n\tprepareArguments?: (args: unknown) => Static<TParameters>;\n\t/** Execute the tool call. Throw on failure instead of encoding errors in `content`. */\n\texecute: (\n\t\ttoolCallId: string,\n\t\tparams: Static<TParameters>,\n\t\tsignal?: AbortSignal,\n\t\tonUpdate?: AgentToolUpdateCallback<TDetails>,\n\t) => Promise<AgentToolResult<TDetails>>;\n\t/**\n\t * Per-tool execution mode override.\n\t * - \"sequential\": this tool must execute one at a time with other tool calls.\n\t * - \"parallel\": this tool can execute concurrently with other tool calls.\n\t *\n\t * If omitted, the default execution mode applies.\n\t */\n\texecutionMode?: ToolExecutionMode;\n}\n\n/** Context snapshot passed into the low-level agent loop. */\nexport interface AgentContext {\n\t/** System prompt included with the request. */\n\tsystemPrompt: string;\n\t/** Transcript visible to the model. */\n\tmessages: AgentMessage[];\n\t/** Tools available for this run. */\n\ttools?: AgentTool<any>[];\n}\n\n/**\n * Events emitted by the Agent for UI updates.\n *\n * `agent_end` is the last event emitted for a run, but awaited `Agent.subscribe()`\n * listeners for that event are still part of run settlement. The agent becomes\n * idle only after those listeners finish.\n */\nexport interface ToolCallRepairInfo {\n\trepaired: true;\n\trawArguments?: Record<string, unknown>;\n\tnotes?: string[];\n}\n\nexport type AgentEvent =\n\t// Agent lifecycle\n\t| { type: \"agent_start\" }\n\t| { type: \"agent_end\"; messages: AgentMessage[] }\n\t// Turn lifecycle - a turn is one assistant response + any tool calls/results\n\t| { type: \"turn_start\" }\n\t| { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n\t// Message lifecycle - emitted for user, assistant, and toolResult messages\n\t| { type: \"message_start\"; message: AgentMessage }\n\t// Only emitted for assistant messages during streaming\n\t| { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n\t| { type: \"message_end\"; message: AgentMessage }\n\t// Tool execution lifecycle\n\t| { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any; repair?: ToolCallRepairInfo }\n\t| {\n\t\t\ttype: \"tool_execution_update\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\targs: any;\n\t\t\tpartialResult: any;\n\t\t\trepair?: ToolCallRepairInfo;\n\t }\n\t| {\n\t\t\ttype: \"tool_execution_end\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\tresult: any;\n\t\t\tisError: boolean;\n\t\t\trepair?: ToolCallRepairInfo;\n\t };\n"]}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA+IA;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC","sourcesContent":["import type {\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamSimple,\n\tTextContent,\n\tTool,\n\tToolArgumentValidationTelemetryEvent,\n\tToolResultMessage,\n} from \"@caupulican/pi-ai\";\nimport type { Static, TSchema } from \"typebox\";\n\n/**\n * Stream function used by the agent loop.\n *\n * Contract:\n * - Must not throw or return a rejected promise for request/model/runtime failures.\n * - Must return an AssistantMessageEventStream.\n * - Failures must be encoded in the returned stream via protocol events and a\n * final AssistantMessage with stopReason \"error\" or \"aborted\" and errorMessage.\n */\nexport type StreamFn = (\n\t...args: Parameters<typeof streamSimple>\n) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;\n\n/**\n * Configuration for how tool calls from a single assistant message are executed.\n *\n * - \"sequential\": each tool call is prepared, executed, and finalized before the next one starts.\n * - \"parallel\": tool calls are prepared sequentially, then allowed tools execute concurrently.\n * `tool_execution_end` is emitted in tool completion order after each tool is finalized,\n * while tool-result message artifacts are emitted later in assistant source order.\n */\nexport type ToolExecutionMode = \"sequential\" | \"parallel\";\n\n/**\n * Controls how many queued user messages are injected when the agent loop reaches a queue drain point.\n *\n * - \"all\": drain and inject every queued message at that point.\n * - \"one-at-a-time\": drain and inject only the oldest queued message, leaving the rest queued for later drain points.\n */\nexport type QueueMode = \"all\" | \"one-at-a-time\";\n\n/** A single tool call content block emitted by an assistant message. */\nexport type AgentToolCall = Extract<AssistantMessage[\"content\"][number], { type: \"toolCall\" }>;\n\n/**\n * Result returned from `beforeToolCall`.\n *\n * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.\n * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.\n */\nexport interface BeforeToolCallResult {\n\tblock?: boolean;\n\treason?: string;\n}\n\n/**\n * Partial override returned from `afterToolCall`.\n *\n * Merge semantics are field-by-field:\n * - `content`: if provided, replaces the tool result content array in full\n * - `details`: if provided, replaces the tool result details value in full\n * - `isError`: if provided, replaces the tool result error flag\n * - `terminate`: if provided, replaces the early-termination hint\n *\n * Omitted fields keep the original executed tool result values.\n * There is no deep merge for `content` or `details`.\n */\nexport interface AfterToolCallResult {\n\tcontent?: (TextContent | ImageContent)[];\n\tdetails?: unknown;\n\tisError?: boolean;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Context passed to `beforeToolCall`. */\nexport interface BeforeToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** Current agent context at the time the tool call is prepared. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `afterToolCall`. */\nexport interface AfterToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** The executed tool result before any `afterToolCall` overrides are applied. */\n\tresult: AgentToolResult<any>;\n\t/** Whether the executed tool result is currently treated as an error. */\n\tisError: boolean;\n\t/** Current agent context at the time the tool call is finalized. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `shouldStopAfterTurn`. */\nexport interface ShouldStopAfterTurnContext {\n\t/** The assistant message that completed the turn. */\n\tmessage: AssistantMessage;\n\t/** Tool result messages passed to the preceding `turn_end` event. */\n\ttoolResults: ToolResultMessage[];\n\t/** Current agent context after the turn's assistant message and tool results have been appended. */\n\tcontext: AgentContext;\n\t/** Messages that this loop invocation will return if it exits at this point. Prompt runs include the initial prompt messages; continuation runs do not include pre-existing context messages. */\n\tnewMessages: AgentMessage[];\n}\n\n/** Replacement runtime state used by the agent loop before starting another provider request. */\nexport interface AgentLoopTurnUpdate {\n\t/** Context for the next provider request. */\n\tcontext?: AgentContext;\n\t/** Model for the next provider request. */\n\tmodel?: Model<any>;\n\t/** Thinking level for the next provider request. */\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport interface ToolValidationEscalationEvent {\n\ttool: string;\n\tsignature: string;\n\trepeats: number;\n\tmodel: string;\n\tprovider: string;\n}\n\nexport interface PrepareNextTurnContext extends ShouldStopAfterTurnContext {}\n\n/**\n * Default runaway-loop backstop: a single identical tool-call signature recurring this many times\n * within a sliding window (2×) stops the loop. Generous enough that legitimate long/varied work never\n * trips it, but bounds the cost of a model wedged repeating one failing call forever.\n */\nexport const DEFAULT_MAX_STALL_TURNS = 12;\n\nexport interface AgentLoopConfig extends SimpleStreamOptions {\n\tmodel: Model<any>;\n\n\t/**\n\t * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.\n\t *\n\t * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage\n\t * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,\n\t * status messages) should be filtered out.\n\t *\n\t * Contract: must not throw or reject. Return a safe fallback value instead.\n\t * Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t *\n\t * @example\n\t * ```typescript\n\t * convertToLlm: (messages) => messages.flatMap(m => {\n\t * if (m.role === \"custom\") {\n\t * // Convert custom message to user message\n\t * return [{ role: \"user\", content: m.content, timestamp: m.timestamp }];\n\t * }\n\t * if (m.role === \"notification\") {\n\t * // Filter out UI-only messages\n\t * return [];\n\t * }\n\t * // Pass through standard LLM messages\n\t * return [m];\n\t * })\n\t * ```\n\t */\n\tconvertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;\n\n\t/**\n\t * Optional transform applied to the context before `convertToLlm`.\n\t *\n\t * Use this for operations that work at the AgentMessage level:\n\t * - Context window management (pruning old messages)\n\t * - Injecting context from external sources\n\t *\n\t * Contract: must not throw or reject. Return the original messages or another\n\t * safe fallback value instead.\n\t *\n\t * @example\n\t * ```typescript\n\t * transformContext: async (messages) => {\n\t * if (estimateTokens(messages) > MAX_TOKENS) {\n\t * return pruneOldMessages(messages);\n\t * }\n\t * return messages;\n\t * }\n\t * ```\n\t */\n\ttransformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;\n\n\t/**\n\t * Resolves an API key dynamically for each LLM call.\n\t *\n\t * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire\n\t * during long-running tool execution phases.\n\t *\n\t * Contract: must not throw or reject. Return undefined when no key is available.\n\t */\n\tgetApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;\n\n\t/**\n\t * Called after each turn fully completes and `turn_end` has been emitted.\n\t *\n\t * If it returns true, the loop emits `agent_end` and exits before polling steering or follow-up queues,\n\t * without starting another LLM call. The current assistant response and any tool executions finish normally.\n\t *\n\t * Use this to request a graceful stop after the current turn, e.g. before context gets too full.\n\t *\n\t * Contract: must not throw or reject. Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t */\n\tshouldStopAfterTurn?: (context: ShouldStopAfterTurnContext) => boolean | Promise<boolean>;\n\n\t/**\n\t * Runaway-loop backstop. A model stuck repeating the SAME tool call (identical name + arguments) —\n\t * because a tool keeps erroring, or it is confused/oscillating — makes no progress yet keeps\n\t * consuming tokens indefinitely (history grows every turn). This bounds that cost: if one tool-call\n\t * signature recurs at least this many times within a sliding window (2×), the loop stops gracefully\n\t * (emits `agent_end`). It counts ONLY turns that issued tool calls and keys on exact name+arguments,\n\t * so legitimate long or varied agentic work never trips it. `0` disables the backstop.\n\t * Default: {@link DEFAULT_MAX_STALL_TURNS}.\n\t */\n\tmaxStallTurns?: number;\n\n\t/**\n\t * Observability hook fired once if the {@link maxStallTurns} runaway backstop trips, just before the\n\t * loop stops. Lets the host surface/log why the run ended. Must not throw.\n\t */\n\tonRunawayStop?: (info: { signature: string; repeats: number }) => void;\n\n\t/**\n\t * Called after `turn_end` and before the loop decides whether another provider request should start.\n\t * Return replacement context/model/thinking state to affect the next turn in this run.\n\t * Return undefined to keep using the current context/config.\n\t */\n\tprepareNextTurn?: (\n\t\tcontext: PrepareNextTurnContext,\n\t) => AgentLoopTurnUpdate | undefined | Promise<AgentLoopTurnUpdate | undefined>;\n\n\t/**\n\t * Returns steering messages to inject into the conversation mid-run.\n\t *\n\t * Called after the current assistant turn finishes executing its tool calls, unless `shouldStopAfterTurn` exits first.\n\t * If messages are returned, they are added to the context before the next LLM call.\n\t * Tool calls from the current assistant message are not skipped.\n\t *\n\t * Use this for \"steering\" the agent while it's working.\n\t *\n\t * Contract: must not throw or reject. Return [] when no steering messages are available.\n\t */\n\tgetSteeringMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Returns follow-up messages to process after the agent would otherwise stop.\n\t *\n\t * Called when the agent has no more tool calls and no steering messages.\n\t * If messages are returned, they're added to the context and the agent\n\t * continues with another turn.\n\t *\n\t * Use this for follow-up messages that should wait until the agent finishes.\n\t *\n\t * Contract: must not throw or reject. Return [] when no follow-up messages are available.\n\t */\n\tgetFollowUpMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Tool execution mode.\n\t * - \"sequential\": execute tool calls one by one\n\t * - \"parallel\": preflight tool calls sequentially, then execute allowed tools concurrently;\n\t * emit `tool_execution_end` in tool completion order after each tool is finalized,\n\t * then emit tool-result message artifacts later in assistant source order\n\t *\n\t * Default: \"parallel\"\n\t */\n\ttoolExecution?: ToolExecutionMode;\n\n\t/** Disable in-band tool repair teaching notes. Default: enabled. */\n\ttoolArgumentTeachEnabled?: boolean;\n\n\t/**\n\t * Observe tool argument validation outcomes. Events contain only shape metadata\n\t * (outcome, model/provider/tool, failure modes, repairs) and never argument values.\n\t */\n\tonToolArgumentValidation?: (event: ToolArgumentValidationTelemetryEvent) => void;\n\n\t/**\n\t * Number of consecutive identical validation bounces before adding full schema/example feedback\n\t * and notifying the host. Set to 0 to disable. Default: 3.\n\t */\n\ttoolValidationEscalationThreshold?: number;\n\n\t/**\n\t * Fired when a repeated identical tool validation failure reaches the escalation threshold.\n\t * Hosts with model routers can use this signal to move the next turn off a cheap route.\n\t */\n\tonToolValidationEscalation?: (event: ToolValidationEscalationEvent) => void;\n\n\t/**\n\t * Called before a tool is executed, after arguments have been validated.\n\t *\n\t * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tbeforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;\n\n\t/**\n\t * Called after a tool finishes executing, before `tool_execution_end` and tool-result message events are emitted.\n\t *\n\t * Return an `AfterToolCallResult` to override parts of the executed tool result:\n\t * - `content` replaces the full content array\n\t * - `details` replaces the full details payload\n\t * - `isError` replaces the error flag\n\t * - `terminate` replaces the early-termination hint\n\t *\n\t * Any omitted fields keep their original values. No deep merge is performed.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tafterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;\n}\n\n/**\n * Thinking/reasoning level for models that support it.\n * Note: \"xhigh\" is only supported by selected model families. Use model thinking-level metadata\n * from @caupulican/pi-ai to detect support for a concrete model.\n */\nexport type ThinkingLevel = \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/**\n * Extensible interface for custom app messages.\n * Apps can extend via declaration merging:\n *\n * @example\n * ```typescript\n * declare module \"@mariozechner/agent\" {\n * interface CustomAgentMessages {\n * artifact: ArtifactMessage;\n * notification: NotificationMessage;\n * }\n * }\n * ```\n */\nexport interface CustomAgentMessages {\n\t// Empty by default - apps extend via declaration merging\n}\n\n/**\n * AgentMessage: Union of LLM messages + custom messages.\n * This abstraction allows apps to add custom message types while maintaining\n * type safety and compatibility with the base LLM messages.\n */\nexport type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];\n\n/**\n * Public agent state.\n *\n * `tools` and `messages` use accessor properties so implementations can copy\n * assigned arrays before storing them.\n */\nexport interface AgentState {\n\t/** System prompt sent with each model request. */\n\tsystemPrompt: string;\n\t/** Active model used for future turns. */\n\tmodel: Model<any>;\n\t/** Requested reasoning level for future turns. */\n\tthinkingLevel: ThinkingLevel;\n\t/** Available tools. Assigning a new array copies the top-level array. */\n\tset tools(tools: AgentTool<any>[]);\n\tget tools(): AgentTool<any>[];\n\t/** Conversation transcript. Assigning a new array copies the top-level array. */\n\tset messages(messages: AgentMessage[]);\n\tget messages(): AgentMessage[];\n\t/**\n\t * True while the agent is processing a prompt or continuation.\n\t *\n\t * This remains true until awaited `agent_end` listeners settle.\n\t */\n\treadonly isStreaming: boolean;\n\t/** Partial assistant message for the current streamed response, if any. */\n\treadonly streamingMessage?: AgentMessage;\n\t/** Tool call ids currently executing. */\n\treadonly pendingToolCalls: ReadonlySet<string>;\n\t/** Error message from the most recent failed or aborted assistant turn, if any. */\n\treadonly errorMessage?: string;\n}\n\n/** Final or partial result produced by a tool. */\nexport interface AgentToolResult<T> {\n\t/** Text or image content returned to the model. */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Arbitrary structured details for logs or UI rendering. */\n\tdetails: T;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Callback used by tools to stream partial execution updates. */\nexport type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;\n\n/** Tool definition used by the agent runtime. */\nexport interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {\n\t/** Human-readable label for UI display. */\n\tlabel: string;\n\t/**\n\t * Optional compatibility shim for raw tool-call arguments before schema validation.\n\t * Must return an object that matches `TParameters`.\n\t */\n\tprepareArguments?: (args: unknown) => Static<TParameters>;\n\t/** Execute the tool call. Throw on failure instead of encoding errors in `content`. */\n\texecute: (\n\t\ttoolCallId: string,\n\t\tparams: Static<TParameters>,\n\t\tsignal?: AbortSignal,\n\t\tonUpdate?: AgentToolUpdateCallback<TDetails>,\n\t) => Promise<AgentToolResult<TDetails>>;\n\t/**\n\t * Per-tool execution mode override.\n\t * - \"sequential\": this tool must execute one at a time with other tool calls.\n\t * - \"parallel\": this tool can execute concurrently with other tool calls.\n\t *\n\t * If omitted, the default execution mode applies.\n\t */\n\texecutionMode?: ToolExecutionMode;\n}\n\n/** Context snapshot passed into the low-level agent loop. */\nexport interface AgentContext {\n\t/** System prompt included with the request. */\n\tsystemPrompt: string;\n\t/** Transcript visible to the model. */\n\tmessages: AgentMessage[];\n\t/** Tools available for this run. */\n\ttools?: AgentTool<any>[];\n}\n\n/**\n * Events emitted by the Agent for UI updates.\n *\n * `agent_end` is the last event emitted for a run, but awaited `Agent.subscribe()`\n * listeners for that event are still part of run settlement. The agent becomes\n * idle only after those listeners finish.\n */\nexport interface ToolCallRepairInfo {\n\trepaired: true;\n\trawArguments?: Record<string, unknown>;\n\tnotes?: string[];\n}\n\nexport type AgentEvent =\n\t// Agent lifecycle\n\t| { type: \"agent_start\" }\n\t| { type: \"agent_end\"; messages: AgentMessage[] }\n\t// Turn lifecycle - a turn is one assistant response + any tool calls/results\n\t| { type: \"turn_start\" }\n\t| { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n\t// Message lifecycle - emitted for user, assistant, and toolResult messages\n\t| { type: \"message_start\"; message: AgentMessage }\n\t// Only emitted for assistant messages during streaming\n\t| { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n\t| { type: \"message_end\"; message: AgentMessage }\n\t// Tool execution lifecycle\n\t| { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any; repair?: ToolCallRepairInfo }\n\t| {\n\t\t\ttype: \"tool_execution_update\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\targs: any;\n\t\t\tpartialResult: any;\n\t\t\trepair?: ToolCallRepairInfo;\n\t }\n\t| {\n\t\t\ttype: \"tool_execution_end\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\tresult: any;\n\t\t\tisError: boolean;\n\t\t\trepair?: ToolCallRepairInfo;\n\t };\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAiJA;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC","sourcesContent":["import type {\n\tApi,\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tContext,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamSimple,\n\tTextContent,\n\tTool,\n\tToolArgumentValidationTelemetryEvent,\n\tToolResultMessage,\n} from \"@caupulican/pi-ai\";\nimport type { Static, TSchema } from \"typebox\";\n\n/**\n * Stream function used by the agent loop.\n *\n * Contract:\n * - Must not throw or return a rejected promise for request/model/runtime failures.\n * - Must return an AssistantMessageEventStream.\n * - Failures must be encoded in the returned stream via protocol events and a\n * final AssistantMessage with stopReason \"error\" or \"aborted\" and errorMessage.\n */\nexport type StreamFn = (\n\t...args: Parameters<typeof streamSimple>\n) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;\n\n/**\n * Configuration for how tool calls from a single assistant message are executed.\n *\n * - \"sequential\": each tool call is prepared, executed, and finalized before the next one starts.\n * - \"parallel\": tool calls are prepared sequentially, then allowed tools execute concurrently.\n * `tool_execution_end` is emitted in tool completion order after each tool is finalized,\n * while tool-result message artifacts are emitted later in assistant source order.\n */\nexport type ToolExecutionMode = \"sequential\" | \"parallel\";\n\n/**\n * Controls how many queued user messages are injected when the agent loop reaches a queue drain point.\n *\n * - \"all\": drain and inject every queued message at that point.\n * - \"one-at-a-time\": drain and inject only the oldest queued message, leaving the rest queued for later drain points.\n */\nexport type QueueMode = \"all\" | \"one-at-a-time\";\n\n/** A single tool call content block emitted by an assistant message. */\nexport type AgentToolCall = Extract<AssistantMessage[\"content\"][number], { type: \"toolCall\" }>;\n\n/**\n * Result returned from `beforeToolCall`.\n *\n * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.\n * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.\n */\nexport interface BeforeToolCallResult {\n\tblock?: boolean;\n\treason?: string;\n}\n\n/**\n * Partial override returned from `afterToolCall`.\n *\n * Merge semantics are field-by-field:\n * - `content`: if provided, replaces the tool result content array in full\n * - `details`: if provided, replaces the tool result details value in full\n * - `isError`: if provided, replaces the tool result error flag\n * - `terminate`: if provided, replaces the early-termination hint\n *\n * Omitted fields keep the original executed tool result values.\n * There is no deep merge for `content` or `details`.\n */\nexport interface AfterToolCallResult {\n\tcontent?: (TextContent | ImageContent)[];\n\tdetails?: unknown;\n\tisError?: boolean;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Context passed to `beforeToolCall`. */\nexport interface BeforeToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** Current agent context at the time the tool call is prepared. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `afterToolCall`. */\nexport interface AfterToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** The executed tool result before any `afterToolCall` overrides are applied. */\n\tresult: AgentToolResult<any>;\n\t/** Whether the executed tool result is currently treated as an error. */\n\tisError: boolean;\n\t/** Current agent context at the time the tool call is finalized. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `shouldStopAfterTurn`. */\nexport interface ShouldStopAfterTurnContext {\n\t/** The assistant message that completed the turn. */\n\tmessage: AssistantMessage;\n\t/** Tool result messages passed to the preceding `turn_end` event. */\n\ttoolResults: ToolResultMessage[];\n\t/** Current agent context after the turn's assistant message and tool results have been appended. */\n\tcontext: AgentContext;\n\t/** Messages that this loop invocation will return if it exits at this point. Prompt runs include the initial prompt messages; continuation runs do not include pre-existing context messages. */\n\tnewMessages: AgentMessage[];\n}\n\n/** Replacement runtime state used by the agent loop before starting another provider request. */\nexport interface AgentLoopTurnUpdate {\n\t/** Context for the next provider request. */\n\tcontext?: AgentContext;\n\t/** Model for the next provider request. */\n\tmodel?: Model<any>;\n\t/** Thinking level for the next provider request. */\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport interface ToolValidationEscalationEvent {\n\ttool: string;\n\tsignature: string;\n\trepeats: number;\n\tmodel: string;\n\tprovider: string;\n}\n\nexport interface PrepareNextTurnContext extends ShouldStopAfterTurnContext {}\n\n/**\n * Default runaway-loop backstop: a single identical tool-call signature recurring this many times\n * within a sliding window (2×) stops the loop. Generous enough that legitimate long/varied work never\n * trips it, but bounds the cost of a model wedged repeating one failing call forever.\n */\nexport const DEFAULT_MAX_STALL_TURNS = 12;\n\nexport interface AgentLoopConfig extends SimpleStreamOptions {\n\tmodel: Model<any>;\n\n\t/**\n\t * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.\n\t *\n\t * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage\n\t * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,\n\t * status messages) should be filtered out.\n\t *\n\t * Contract: must not throw or reject. Return a safe fallback value instead.\n\t * Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t *\n\t * @example\n\t * ```typescript\n\t * convertToLlm: (messages) => messages.flatMap(m => {\n\t * if (m.role === \"custom\") {\n\t * // Convert custom message to user message\n\t * return [{ role: \"user\", content: m.content, timestamp: m.timestamp }];\n\t * }\n\t * if (m.role === \"notification\") {\n\t * // Filter out UI-only messages\n\t * return [];\n\t * }\n\t * // Pass through standard LLM messages\n\t * return [m];\n\t * })\n\t * ```\n\t */\n\tconvertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;\n\n\t/**\n\t * Optional transform applied to the context before `convertToLlm`.\n\t *\n\t * Use this for operations that work at the AgentMessage level:\n\t * - Context window management (pruning old messages)\n\t * - Injecting context from external sources\n\t *\n\t * Contract: must not throw or reject. Return the original messages or another\n\t * safe fallback value instead.\n\t *\n\t * @example\n\t * ```typescript\n\t * transformContext: async (messages) => {\n\t * if (estimateTokens(messages) > MAX_TOKENS) {\n\t * return pruneOldMessages(messages);\n\t * }\n\t * return messages;\n\t * }\n\t * ```\n\t */\n\ttransformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;\n\n\t/**\n\t * Resolve the reasoning effort after context transformation and immediately before the provider\n\t * request. This supports request-local policy decisions that must not mutate persisted agent state.\n\t */\n\tresolveRequestReasoning?: (\n\t\treasoning: SimpleStreamOptions[\"reasoning\"],\n\t\trequest: { model: Model<Api>; context: Context; maxTokens?: number },\n\t) => SimpleStreamOptions[\"reasoning\"];\n\n\t/**\n\t * Resolves an API key dynamically for each LLM call.\n\t *\n\t * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire\n\t * during long-running tool execution phases.\n\t *\n\t * Contract: must not throw or reject. Return undefined when no key is available.\n\t */\n\tgetApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;\n\n\t/**\n\t * Called after each turn fully completes and `turn_end` has been emitted.\n\t *\n\t * If it returns true, the loop emits `agent_end` and exits before polling steering or follow-up queues,\n\t * without starting another LLM call. The current assistant response and any tool executions finish normally.\n\t *\n\t * Use this to request a graceful stop after the current turn, e.g. before context gets too full.\n\t *\n\t * Contract: must not throw or reject. Throwing interrupts the low-level agent loop without producing a normal event sequence.\n\t */\n\tshouldStopAfterTurn?: (context: ShouldStopAfterTurnContext) => boolean | Promise<boolean>;\n\n\t/**\n\t * Runaway-loop backstop. A model stuck repeating the SAME tool call (identical name + arguments) —\n\t * because a tool keeps erroring, or it is confused/oscillating — makes no progress yet keeps\n\t * consuming tokens indefinitely (history grows every turn). This bounds that cost: if one tool-call\n\t * signature recurs at least this many times within a sliding window (2×), the loop stops gracefully\n\t * (emits `agent_end`). It counts ONLY turns that issued tool calls and keys on exact name+arguments,\n\t * so legitimate long or varied agentic work never trips it. `0` disables the backstop.\n\t * Default: {@link DEFAULT_MAX_STALL_TURNS}.\n\t */\n\tmaxStallTurns?: number;\n\n\t/**\n\t * Observability hook fired once if the {@link maxStallTurns} runaway backstop trips, just before the\n\t * loop stops. Lets the host surface/log why the run ended. Must not throw.\n\t */\n\tonRunawayStop?: (info: { signature: string; repeats: number }) => void;\n\n\t/**\n\t * Called after `turn_end` and before the loop decides whether another provider request should start.\n\t * Return replacement context/model/thinking state to affect the next turn in this run.\n\t * Return undefined to keep using the current context/config.\n\t */\n\tprepareNextTurn?: (\n\t\tcontext: PrepareNextTurnContext,\n\t) => AgentLoopTurnUpdate | undefined | Promise<AgentLoopTurnUpdate | undefined>;\n\n\t/**\n\t * Returns steering messages to inject into the conversation mid-run.\n\t *\n\t * Called after the current assistant turn finishes executing its tool calls, unless `shouldStopAfterTurn` exits first.\n\t * If messages are returned, they are added to the context before the next LLM call.\n\t * Tool calls from the current assistant message are not skipped.\n\t *\n\t * Use this for \"steering\" the agent while it's working.\n\t *\n\t * Contract: must not throw or reject. Return [] when no steering messages are available.\n\t */\n\tgetSteeringMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Returns follow-up messages to process after the agent would otherwise stop.\n\t *\n\t * Called when the agent has no more tool calls and no steering messages.\n\t * If messages are returned, they're added to the context and the agent\n\t * continues with another turn.\n\t *\n\t * Use this for follow-up messages that should wait until the agent finishes.\n\t *\n\t * Contract: must not throw or reject. Return [] when no follow-up messages are available.\n\t */\n\tgetFollowUpMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Tool execution mode.\n\t * - \"sequential\": execute tool calls one by one\n\t * - \"parallel\": preflight tool calls sequentially, then execute allowed tools concurrently;\n\t * emit `tool_execution_end` in tool completion order after each tool is finalized,\n\t * then emit tool-result message artifacts later in assistant source order\n\t *\n\t * Default: \"parallel\"\n\t */\n\ttoolExecution?: ToolExecutionMode;\n\n\t/** Disable in-band tool repair teaching notes. Default: enabled. */\n\ttoolArgumentTeachEnabled?: boolean;\n\n\t/**\n\t * Observe tool argument validation outcomes. Events contain only shape metadata\n\t * (outcome, model/provider/tool, failure modes, repairs) and never argument values.\n\t */\n\tonToolArgumentValidation?: (event: ToolArgumentValidationTelemetryEvent) => void;\n\n\t/**\n\t * Number of consecutive identical validation bounces before adding full schema/example feedback\n\t * and notifying the host. Set to 0 to disable. Default: 3.\n\t */\n\ttoolValidationEscalationThreshold?: number;\n\n\t/**\n\t * Fired when a repeated identical tool validation failure reaches the escalation threshold.\n\t * Hosts with model routers can use this signal to move the next turn off a cheap route.\n\t */\n\tonToolValidationEscalation?: (event: ToolValidationEscalationEvent) => void;\n\n\t/**\n\t * Called before a tool is executed, after arguments have been validated.\n\t *\n\t * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tbeforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;\n\n\t/**\n\t * Called after a tool finishes executing, before `tool_execution_end` and tool-result message events are emitted.\n\t *\n\t * Return an `AfterToolCallResult` to override parts of the executed tool result:\n\t * - `content` replaces the full content array\n\t * - `details` replaces the full details payload\n\t * - `isError` replaces the error flag\n\t * - `terminate` replaces the early-termination hint\n\t *\n\t * Any omitted fields keep their original values. No deep merge is performed.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tafterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;\n}\n\n/**\n * Thinking/reasoning level for models that support it.\n * Note: \"xhigh\", \"max\", and \"ultra\" are only supported by selected model families. \"ultra\" maps\n * to the model's maximum provider effort and reinforces proactive orchestration in capable hosts;\n * delegation can remain available at lower levels. Use model thinking-level metadata from\n * @caupulican/pi-ai to detect support for a concrete model.\n */\nexport type ThinkingLevel = \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\" | \"ultra\";\n\n/**\n * Extensible interface for custom app messages.\n * Apps can extend via declaration merging:\n *\n * @example\n * ```typescript\n * declare module \"@mariozechner/agent\" {\n * interface CustomAgentMessages {\n * artifact: ArtifactMessage;\n * notification: NotificationMessage;\n * }\n * }\n * ```\n */\nexport interface CustomAgentMessages {\n\t// Empty by default - apps extend via declaration merging\n}\n\n/**\n * AgentMessage: Union of LLM messages + custom messages.\n * This abstraction allows apps to add custom message types while maintaining\n * type safety and compatibility with the base LLM messages.\n */\nexport type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];\n\n/**\n * Public agent state.\n *\n * `tools` and `messages` use accessor properties so implementations can copy\n * assigned arrays before storing them.\n */\nexport interface AgentState {\n\t/** System prompt sent with each model request. */\n\tsystemPrompt: string;\n\t/** Active model used for future turns. */\n\tmodel: Model<any>;\n\t/** Requested reasoning level for future turns. */\n\tthinkingLevel: ThinkingLevel;\n\t/** Available tools. Assigning a new array copies the top-level array. */\n\tset tools(tools: AgentTool<any>[]);\n\tget tools(): AgentTool<any>[];\n\t/** Conversation transcript. Assigning a new array copies the top-level array. */\n\tset messages(messages: AgentMessage[]);\n\tget messages(): AgentMessage[];\n\t/**\n\t * True while the agent is processing a prompt or continuation.\n\t *\n\t * This remains true until awaited `agent_end` listeners settle.\n\t */\n\treadonly isStreaming: boolean;\n\t/** Partial assistant message for the current streamed response, if any. */\n\treadonly streamingMessage?: AgentMessage;\n\t/** Tool call ids currently executing. */\n\treadonly pendingToolCalls: ReadonlySet<string>;\n\t/** Error message from the most recent failed or aborted assistant turn, if any. */\n\treadonly errorMessage?: string;\n}\n\n/** Final or partial result produced by a tool. */\nexport interface AgentToolResult<T> {\n\t/** Text or image content returned to the model. */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Arbitrary structured details for logs or UI rendering. */\n\tdetails: T;\n\t/**\n\t * Hint that the agent should stop after the current tool batch.\n\t * Early termination only happens when every finalized tool result in the batch sets this to true.\n\t */\n\tterminate?: boolean;\n}\n\n/** Callback used by tools to stream partial execution updates. */\nexport type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;\n\n/** Tool definition used by the agent runtime. */\nexport interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {\n\t/** Human-readable label for UI display. */\n\tlabel: string;\n\t/**\n\t * Optional compatibility shim for raw tool-call arguments before schema validation.\n\t * Must return an object that matches `TParameters`.\n\t */\n\tprepareArguments?: (args: unknown) => Static<TParameters>;\n\t/** Execute the tool call. Throw on failure instead of encoding errors in `content`. */\n\texecute: (\n\t\ttoolCallId: string,\n\t\tparams: Static<TParameters>,\n\t\tsignal?: AbortSignal,\n\t\tonUpdate?: AgentToolUpdateCallback<TDetails>,\n\t) => Promise<AgentToolResult<TDetails>>;\n\t/**\n\t * Per-tool execution mode override.\n\t * - \"sequential\": this tool must execute one at a time with other tool calls.\n\t * - \"parallel\": this tool can execute concurrently with other tool calls.\n\t *\n\t * If omitted, the default execution mode applies.\n\t */\n\texecutionMode?: ToolExecutionMode;\n}\n\n/** Context snapshot passed into the low-level agent loop. */\nexport interface AgentContext {\n\t/** System prompt included with the request. */\n\tsystemPrompt: string;\n\t/** Transcript visible to the model. */\n\tmessages: AgentMessage[];\n\t/** Tools available for this run. */\n\ttools?: AgentTool<any>[];\n}\n\n/**\n * Events emitted by the Agent for UI updates.\n *\n * `agent_end` is the last event emitted for a run, but awaited `Agent.subscribe()`\n * listeners for that event are still part of run settlement. The agent becomes\n * idle only after those listeners finish.\n */\nexport interface ToolCallRepairInfo {\n\trepaired: true;\n\trawArguments?: Record<string, unknown>;\n\tnotes?: string[];\n}\n\nexport type AgentEvent =\n\t// Agent lifecycle\n\t| { type: \"agent_start\" }\n\t| { type: \"agent_end\"; messages: AgentMessage[] }\n\t// Turn lifecycle - a turn is one assistant response + any tool calls/results\n\t| { type: \"turn_start\" }\n\t| { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n\t// Message lifecycle - emitted for user, assistant, and toolResult messages\n\t| { type: \"message_start\"; message: AgentMessage }\n\t// Only emitted for assistant messages during streaming\n\t| { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n\t| { type: \"message_end\"; message: AgentMessage }\n\t// Tool execution lifecycle\n\t| { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any; repair?: ToolCallRepairInfo }\n\t| {\n\t\t\ttype: \"tool_execution_update\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\targs: any;\n\t\t\tpartialResult: any;\n\t\t\trepair?: ToolCallRepairInfo;\n\t }\n\t| {\n\t\t\ttype: \"tool_execution_end\";\n\t\t\ttoolCallId: string;\n\t\t\ttoolName: string;\n\t\t\tresult: any;\n\t\t\tisError: boolean;\n\t\t\trepair?: ToolCallRepairInfo;\n\t };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caupulican/pi-agent-core",
3
- "version": "0.81.26",
3
+ "version": "0.81.27",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "prepublishOnly": "npm run clean && npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@caupulican/pi-ai": "^0.81.26",
36
+ "@caupulican/pi-ai": "^0.81.27",
37
37
  "ignore": "7.0.5",
38
38
  "typebox": "1.1.38",
39
39
  "yaml": "2.9.0"