import type OpenAI from "openai"; /** * Run a batch of tool calls concurrently with per-tool-name pooling. Calls * to different tools fan out fully; calls to the *same* tool are capped at * `PER_TOOL_POOL_SIZE` so a batch of many calls to one downstream service does * not overwhelm it. Results are * returned in the same order as `calls`, each as a `PromiseSettledResult` * so the caller can synthesize error tool messages for any rejections * (every `tool_call_id` in the assistant message still needs a response). * * Generic over the per-call outcome: the pooling and ordering policy is * harness-owned, while the shape a host derives from a completed call (which * plugin activated, whether the run should compact, a suspend directive, …) * stays with the host. * * **`signal` bounds the batch, and bounding it is the point.** A batch of many * calls to one tool runs five at a time, so the rest sit queued — and without a * signal the pool claims every one of them however long ago the run's deadline * fired or the user pressed stop. Each queued call is a side effect nobody * wants any more: a write, an email, a third-party POST. Once the signal * aborts, workers stop claiming new work. * * Calls that never ran still come back — as `rejected` with an * {@link AbortedToolCallError} — because every `tool_call_id` in the assistant * message needs a response either way, and a caller that received a shorter * array than it passed would silently mispair the rest. A call already in * flight is left alone: this cannot reach inside a host's tool, and cancelling * one mid-write is the host's problem to solve with the same signal. */ /** * The message a refused call carries into the transcript, and therefore **into * the model's next turn**. Shared by both refusal sites — the pool and the * loop's serial phase — because to the only reader they are the same fact, and * two literals for one fact drift. * * Three things it deliberately does *not* do. It does not name the batch, the * worker, or the claim: those are this module's internals, the model cannot act * on them, and they appear nowhere else in its context. It does not interpolate * the call id, which the `tool` message's own `tool_call_id` already carries — * restating it spends the sentence's most-attended position on something the * reader has. And it does not lead with the cause: the actionable fact is that * *nothing happened*, and a model skimming a batch of results reads the first * clause. */ export declare const ABORTED_TOOL_CALL_MESSAGE: string; /** * A call the batch refused because it was aborted before any worker claimed it. * * Carries the id as a **field** rather than in the message, so a host's * rejection observer and operator log can filter on it (`CLAUDE.md`: identifying * context is a field, not string interpolation). */ export declare class AbortedToolCallError extends Error { readonly toolCallId: string; readonly name = "AbortedToolCallError"; constructor(toolCallId: string); } export interface ToolBatchOptions { /** * Stop claiming queued calls once this aborts. Combine a run deadline with a * cancellation signal (`AbortSignal.any`) before passing it — the pool does * not care which one fired, only that no further work should start. */ readonly signal?: AbortSignal | undefined; } export declare function runToolCallsPooledByTool(calls: OpenAI.ChatCompletionMessageToolCall[], run: (tc: OpenAI.ChatCompletionMessageToolCall) => Promise, options?: ToolBatchOptions): Promise[]>;