feat(tools): add ToolDefinition.timeoutMs declared+validated via defineTool
A tool declares its cooperative timeout budget on its own definition rather than a deployment naming it in a central config map. The field never reaches the model (schemas() whitelists name/description/parameters) and defineTool rejects a non-positive-finite value at authorship.
This commit is contained in:
@@ -138,6 +138,14 @@ export type ToolExecuteReturn = ContentBlock[] | { content: ContentBlock[]; meta
|
||||
/** A registered tool: its schema plus the execution function. */
|
||||
export interface ToolDefinition extends ToolSchema {
|
||||
execute(args: unknown, exec: ToolExecution): Promise<ToolExecuteReturn>
|
||||
/**
|
||||
* Cooperative tool-call timeout budget in milliseconds. Omit for no deadline.
|
||||
* Enforced by `@deepseek-ai/dsh-timeout-policy` (a `tools/execute` wrapper); it
|
||||
* is NEVER sent to the model — `schemas()` whitelists only name/description/
|
||||
* parameters. Declaring it asserts this tool forwards `exec.signal` to a
|
||||
* cooperative implementation that can reach quiescence when the signal aborts.
|
||||
*/
|
||||
timeoutMs?: number
|
||||
/**
|
||||
* Optional: how to present the PENDING state of one call in a UI, derived from
|
||||
* the call's `args` (parsed arguments, `unknown` — the tool validates/narrows
|
||||
|
||||
@@ -295,6 +295,13 @@ export interface DefineToolOptions<S extends SchemaSpec> {
|
||||
* standard JSON Schema at runtime.
|
||||
*/
|
||||
parameters: S
|
||||
/**
|
||||
* Optional cooperative tool-call timeout budget in milliseconds. When given it
|
||||
* must be a positive finite number; it is attached to the produced
|
||||
* {@link ToolDefinition} for `@deepseek-ai/dsh-timeout-policy` to enforce and
|
||||
* is never sent to the model.
|
||||
*/
|
||||
timeoutMs?: number
|
||||
/**
|
||||
* Tool execution function. `args` is typed as {@link InferArgs<S>} — zero
|
||||
* casts needed. Returns either a bare {@link ContentBlock}`[]` (model-facing
|
||||
@@ -362,10 +369,14 @@ export function defineTool<S extends SchemaSpec>(options: DefineToolOptions<S>):
|
||||
const userPresentCall = options.presentCall
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const userPresentResult = options.presentResult
|
||||
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {
|
||||
throw new Error(`defineTool(${options.name}): timeoutMs must be a positive finite number`)
|
||||
}
|
||||
const tool: ToolDefinition = {
|
||||
name: options.name,
|
||||
description: options.description,
|
||||
parameters: schemaSpecToJsonSchema(options.parameters) as unknown as Record<string, unknown>,
|
||||
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
||||
async execute(args: unknown, exec: ToolExecution): Promise<ToolExecuteReturn> {
|
||||
// Validate the model-generated args before the typed body runs. On
|
||||
// mismatch we throw ToolArgsError; the registry turns it into an
|
||||
|
||||
Reference in New Issue
Block a user