/** * `defineFunctionTool(opts)` — Zod-schema → OpenAI function-tool helper. * * Closes the parity gap identified by the 2026-05-21 ADK comparison * (see `docs/sdk2/research/2026-05-21-google-adk-vs-qodo-ts-sdk.md`, * Opportunity 4 — auto-derive JSON Schema from a Zod schema instead of * hand-authoring it on every `FunctionToolDef`). Google ADK's * `FunctionTool(func)` derives the schema from Python type hints * (`tools/function_tool.py:42-150`); qodo-ts-sdk's pre-S3 surface * required the consumer to ship the full `parameters: { type: 'object', * properties: {…} }` blob inline. * * The helper does three things in one call: * * 1. Converts the supplied Zod schema to JSON Schema via Zod 4's * built-in `z.toJSONSchema()`. (`zod-to-json-schema` was the * pre-2025 path but the package was deprecated in November 2025; * Zod 4 ships the same capability natively, so we use the built-in * and avoid a redundant dependency.) Targets `draft-07` — the * OpenAI-compatible JSON Schema dialect; the produced body drops * directly into the function-tool wire shape with no further * shimming. * 2. Returns a fully-formed `FunctionToolDef` that drops into * `InlineAgentSpec.tools[]` with zero further wiring. * 3. Attaches the runtime `handler` on a symbol-keyed property so * `JSON.stringify` / structural cloning / spread ignore it (wire * serialization stays clean — the handler is SDK-local) and the * SDK can route inbound `tool.request` envelopes for this tool's * `name` to the right callback automatically at * `tasks.startWithAgent` / `tasks.startWithGraph` time. * * The handler's `args` parameter is statically typed against * `z.infer`, so the consumer gets compile-time * checking against the schema shape — mismatched destructures fail at * the call site rather than at runtime when QAR sends the call. The * router also Zod-parses inbound args before invoking the handler, so * `.default()` values are applied and `.transform()` / `.refine()` * chains run as written; argument-validation failures map to per-call * `outcome: 'error'` without the handler running. * * Backward compatibility: pre-S3 hand-authored `FunctionToolDef` * literals continue to work unchanged. The helper is purely additive — * `InlineAgentSpec.tools[]` accepts both shapes, and the SDK's * auto-bind step (see `ToolClient.bindFunctionToolDefs`) is a no-op for * tools that don't carry the handler symbol. */ import { z } from 'zod'; import { type ApprovalConfig } from './approval.js'; import type { JsonObject } from '../../qar/json.js'; import type { FunctionToolDef, McpToolAnnotations } from '../../qar/specs.js'; import type { ToolCall, ToolResponseResult } from '../../qar/payloads.js'; /** * Symbol used to attach the SDK-local handler bag to a * `FunctionToolDef` produced by {@link defineFunctionTool}. Symbol-keyed * so: * * - `JSON.stringify` ignores the property (the wire-serialized spec * stays clean without an explicit strip step at every wire * boundary). * - Object spread / `structuredClone` don't carry the handler across * unless the runtime is opted into symbols (clones drop the handler * — defensive; consumers building wire payloads via `{ ...tool }` * don't accidentally try to ship a function). * - Property-name lookups via dot/bracket access don't collide with a * consumer-authored field. * * Exported (named) so the SDK's own bind step and unit tests can read * it; consumer code rarely needs to touch it directly — * {@link getFunctionToolHandler} is the safer accessor. */ export declare const FUNCTION_TOOL_HANDLER_KEY: unique symbol; /** * Context passed alongside the parsed `args` to a function-tool handler. * Carries the per-call metadata the runtime delivered on the inbound * envelope — useful for logging, tracing, or routing per-call HITL * decisions. */ export interface FunctionToolHandlerContext { /** The `tool_call_id` of the inbound call this handler is responding to. */ readonly toolCallId: string; /** The originating node name (`payload.node_name`). */ readonly nodeName: string; /** The full `ToolCall` from the inbound envelope (HITL block, raw args, etc.). */ readonly call: ToolCall; /** * Approval decision payload from the S4 `requireApproval` flow. * Present only when the tool carried `requireApproval` AND the * resolver returned `decision: 'approve'`. Carries the optional * `input` (`'input'`-kind approvals) and `selection` * (`'choice'`-kind approvals) the resolver attached — handlers * can read these to thread the approver's free-text justification * or choice down into the tool's effect. * * Always `undefined` for tools without `requireApproval` — the * field exists so consumers can read it without an unsafe cast * when the gate IS engaged. */ readonly approval?: import('./approval.js').ApprovalDecision; } /** * Allowed return shape from a function-tool handler: * * - Plain `JsonObject` — wrapped into `{ outcome: 'success', result }`. * - Full `ToolResponseResult` — passed through (lets the consumer * reply `denied` / `error` without throwing). * * `void` / `undefined` is intentionally NOT supported — the SDK auto- * dispatcher needs every matched call to produce a concrete response * item. If the consumer wants "I'll respond later" semantics, they * should NOT use `defineFunctionTool`; the manual * `client.tools.onRequest` surface still applies. */ export type FunctionToolHandlerReturn = JsonObject | ToolResponseResult | Promise; /** * The function-tool handler signature. `args` is the parsed-and-typed * payload derived from the consumer's Zod schema; `ctx` carries the * per-call envelope metadata. * * Throws are caught by the SDK dispatcher and mapped to a per-call * `outcome: 'error'` response (mirrors the * `ToolClient.onRequest` behaviour); `ToolDeniedError` maps to * `outcome: 'denied'`. */ export type FunctionToolHandler = (args: TArgs, ctx: FunctionToolHandlerContext) => FunctionToolHandlerReturn; /** * Internal handler-bag attached to a `FunctionToolDef` via the * {@link FUNCTION_TOOL_HANDLER_KEY} symbol. The `parameters` reference * is kept so unit tests + observability can re-validate / re-print the * schema without round-tripping through JSON. */ export interface FunctionToolHandlerBag { readonly name: string; readonly handler: FunctionToolHandler; readonly parameters: z.ZodType; /** * Normalized approval configuration when the consumer set * `requireApproval` on the tool. Undefined when the tool has no * approval gate — the common case. The router reads this at * dispatch time; presence (not value) drives the gate. */ readonly approval?: ApprovalConfig; } /** Options accepted by {@link defineFunctionTool}. */ export interface DefineFunctionToolOptions { /** * Tool name as the model will call it. Matches the `function.name` on * the produced `FunctionToolDef`. Subject to the same constraints * QAR's tool registry enforces — keep it `[a-zA-Z0-9_-]` and ≤ 64 * chars; the SDK doesn't lint here, the wire validator does on * dispatch. */ readonly name: string; /** * Human-readable description shown to the model. Surfaced verbatim on * the produced `function.description`. Skip — or pass an empty * string — to omit. */ readonly description?: string; /** * Zod schema describing the tool's argument bag. The handler's `args` * parameter is inferred from this schema via `z.infer` so * a destructure typo fails at compile time, not at runtime. * * Use `z.object({...})` at the top level — Zod 4's `toJSONSchema()` * produces the `{ type: 'object', properties: {...}, required: [...] }` * shape OpenAI function tools expect. Non-object top-level schemas * (e.g. `z.string()`) work but produce a schema the LLM may struggle * to invoke correctly; the helper doesn't enforce object-ness so * consumers building thin shim tools (e.g. `z.union(...)`) can still * opt in. */ readonly parameters: TSchema; /** * OpenAI strict-mode flag forwarded onto the produced * `function.strict`. Defaults to `undefined` (lets the server pick its * default — currently `true`). Pass `false` to opt out of strict * argument validation; rarely useful, mostly here for parity with the * pre-S3 hand-authored shape. */ readonly strict?: boolean; /** * Advisory MCP `Tool.annotations` forwarded onto the produced * `function.annotations`. Useful for non-MCP-backed tools that want * to carry the same advisory hints (e.g. `readOnlyHint: true`) the * SDK propagates through the MCP projection layer. */ readonly annotations?: McpToolAnnotations; /** * Runtime callback invoked when QAR sends a `tool.request` envelope * naming this tool. The first arg is the parsed-and-typed payload * derived from `parameters`; the second is the per-call envelope * metadata. */ readonly handler: FunctionToolHandler>; /** * Ergonomic HITL shorthand (S4 — closes the ADK `require_confirmation` * gap; see `docs/sdk2/research/2026-05-21-google-adk-vs-qodo-ts-sdk.md` * § Opp 5). When set, the SDK gates dispatch on a consumer-side * approval flow before invoking `handler`: * * - **`true`** — shortcut for `{ kind: 'approval', sensitivity: 'low' }`. * Synthesizes a yes/no approval prompt at dispatch time. On * approve, the handler runs with the original parsed args; on * deny, the SDK returns `{ outcome: 'denied' }` to QAR without * invoking the handler. * - **`ApprovalConfig`** — richer config: `kind` picks the resolver * UX shape (`'approval'` yes/no, `'choice'` closed-set, * `'input'` free-form); `sensitivity` drives an advisory * annotation overlay on the produced `FunctionToolDef`; `prompt` * overrides the default human-facing prompt; `deadlineMs` is * passed through to the handler. * - **`false` / `undefined`** — no approval gate. Default — the * handler runs immediately on every inbound call (the pre-S4 * behavior). * * Override the resolver via `client.tools.setApprovalHandler(fn)`. * The default handler prompts on stdin when `process.stdin.isTTY` * is true; in non-interactive contexts it auto-denies with a typed * error message pointing at the missing registration. See * `src/client/tools/approval.ts` for the full handler contract. * * Composes with the existing manual `client.tools.onRequest` * surface: the manual primary stack runs first, so a consumer who * wants per-batch policy on top of per-tool `requireApproval` still * has the explicit shape available. See * `docs/sdk2/concepts/03-hitl.md` for the three-layer HITL model * `requireApproval` composes onto. */ readonly requireApproval?: boolean | ApprovalConfig; } /** * `FunctionToolDef` with the SDK-local handler bag attached. Assignable * to `FunctionToolDef[]` (e.g. `InlineAgentSpec.tools`) without * narrowing — the symbol-keyed property is opaque to structural typing * at the call site. */ export type DefinedFunctionToolDef = FunctionToolDef & { readonly [FUNCTION_TOOL_HANDLER_KEY]: FunctionToolHandlerBag; }; /** * Read the SDK-local handler bag off a `FunctionToolDef`. Returns * `undefined` for hand-authored literals that don't carry the symbol — * the SDK's bind step uses this to skip tools that don't auto-route. */ export declare function getFunctionToolHandler(def: FunctionToolDef): FunctionToolHandlerBag | undefined; /** * Derive a `FunctionToolDef` from a Zod schema + handler. * * @example * import { defineFunctionTool } from '@qodo/sdk'; * import { z } from 'zod'; * * const getCurrentTime = defineFunctionTool({ * name: 'get_current_time', * description: 'Returns the current time in ISO 8601 format.', * parameters: z.object({ * timezone: z * .string() * .describe('IANA timezone, defaults to UTC.') * .default('UTC'), * }), * handler: async ({ timezone }) => ({ * time: new Date().toLocaleString('en-US', { timeZone: timezone }), * }), * }); * * const stream = client.tasks.startWithAgent({ * agent: { * apiVersion: 'qar/v1', * instructions: 'Answer with the current time on request.', * model: 'gpt-4o', * tools: [getCurrentTime], * }, * }); */ export declare function defineFunctionTool(opts: DefineFunctionToolOptions): DefinedFunctionToolDef; //# sourceMappingURL=defineFunctionTool.d.ts.map