UNPKG

@tanstack/ai-code-mode

Version:

Secure TypeScript Code Mode for TanStack AI agents to execute sandboxed tool orchestration programs.

114 lines (113 loc) • 5.96 kB
import { AnyTool } from '@tanstack/ai'; import { PermissionCallDecision, PermissionMode } from '@tanstack/ai-harness'; import { CodeModeTool, CodeModeToolConfig } from './types.js'; export interface CodeModePluginOptions extends Omit<CodeModeToolConfig, 'tools' | 'aroundCalls'> { /** * Which tools move into code mode. Default: every server tool that does * not need approval, that has no permission checks (`PermissionResources`), * and that `permissions()` allows in plan mode (so no edits, no commands, * nothing that asks first). Without `permissions()`, the rules of tool * plugins decide. A safe tool with `metadata.codeMode: true`, for example * a tool of an `mcp()` server with `codeMode: true`, moves even when * `include` does not pick it. * * `include` can also pick a tool with permission checks or an `ask` rule, * for example the coding tools of `workspaceTools()`. Each call of such a * tool in a program gets the same decision as a direct tool call: it * runs, it throws, or the program waits for the user to approve it. * `include` cannot move a tool with `needsApproval`, or a tool that the * rules deny: such a tool stays a normal tool call, and code mode warns * once to the host logger for each one that `include` picks. The rules * decide in the permission mode of the session at each turn, so in plan * mode a tool that edits stays a normal tool call. */ include?: (tool: CodeModeTool) => boolean; /** * Make each tool that moves into code mode lazy. The prompt then lists only * the tool names, and `lazyToolsConfig.includeDescription` adds part of * each description. The model calls `discover_tools` for the signatures of * the tools it needs, then calls them in `execute_typescript`. This saves * tokens on each turn when there are many tools. Default `false`. */ lazy?: boolean; /** * What happens to the tools that move into code mode. `'remove'` * (default): they leave the model's tool list, and the model calls them * only in programs. `'keep'`: the model can also call them directly. The * session setting `directCalls` changes it while the session runs, for * example `session.setPluginSetting({ key: 'directCalls', value: 'keep' })`. * A change applies at the next turn. */ directCalls?: 'remove' | 'keep'; /** * The timeout of a program in milliseconds. Default: 30000, or 600000 * (10 minutes) when a tool with permission checks moved, so that a * program can wait for the user to approve a call. */ timeout?: number; } /** * A harness plugin that gives the model an `execute_typescript` tool. The * model writes one TypeScript program that calls several tools, and the * program runs in the isolate of `driver` (any `@tanstack/ai-isolate-*` * driver). The tools it calls leave the model's tool list, including tools * that plugins find at run time, such as MCP tools after `/connect`. * * By default, only tools that are safe to run without a question move into * code mode. The other tools stay normal tool calls. A tool with * `metadata.codeMode: true` moves even when `include` does not pick it, but * only when it is safe in the same way. * * `include` can pick more tools, also tools with permission checks, such as * the coding tools of `workspaceTools()`. Each call of such a tool in a * program gets the same decision as a direct tool call from `permissions()`: * it runs, it throws an error with the reason, or the program waits for the * user to approve it (a question that `session.answer` answers). The log * keeps the result of each such call, so after a stop the next host runs * the program again without running a finished call twice. `include` never * moves a tool with `needsApproval`, or a tool that the rules deny. * * With `lazy: true`, the model gets only the names of the moved tools and * asks `discover_tools` for the signatures. With `directCalls: 'keep'`, the * model can still call the moved tools directly. * * @example * ```ts * import { codeMode } from '@tanstack/ai-code-mode/harness' * import { createQuickJSIsolateDriver } from '@tanstack/ai-isolate-quickjs' * * defineHarness({ * name: 'acme/agent', * adapter, * plugins: () => [codeMode({ driver: createQuickJSIsolateDriver() })], * }) * ``` */ export declare function codeMode(options: CodeModePluginOptions): import('@tanstack/ai-harness').HarnessPlugin<NoInfer<{ readonly name: "tanstack/code-mode"; readonly optionalRequires: readonly [import('@tanstack/ai').Capability<(tool: string, mode: PermissionMode, resources?: import('@tanstack/ai-harness').CallResources) => import('@tanstack/ai-harness').PermissionDecision, "tanstack/permission-decision">, import('@tanstack/ai').Capability<(call: { tool: string; args: unknown; }) => Promise<PermissionCallDecision>, "tanstack/permission-call">]; readonly setup: (ctx: import('@tanstack/ai-harness').PluginSetupContext<Record<string, unknown>>) => { config: { directCalls: { type: "select"; options: ReadonlyArray<string>; default: string; description?: string; category?: string; }; }; prompts: { id: string; text: () => string; }[]; contribute: import('@tanstack/ai-harness').ExtensionItem<import('@tanstack/ai-harness').PermissionRule>[]; prepareTools: ({ tools }: { tools: ReadonlyArray<AnyTool>; model: string; run: import('@tanstack/ai-harness').RunContext; }) => readonly AnyTool[] | (import('@tanstack/ai').ServerTool<import('@tanstack/ai').SchemaInput, import('@tanstack/ai').SchemaInput, string, unknown, false, undefined> | AnyTool)[]; }; }>, Record<string, unknown>>;