@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
TypeScript
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>>;