一键导入
adding-tools
Add new tools to the AI chat system. Use when adding a chat tool, creating tool schemas, wiring backend tool handlers, or building tool UI components.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add new tools to the AI chat system. Use when adding a chat tool, creating tool schemas, wiring backend tool handlers, or building tool UI components.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Adds a new TextMate-based language to the Monaco editor and Shiki highlighter in apps/ui, including precompiling grammars from repos/shiki, wiring codeLanguages and the contribution registry, and keeping artefacts traceable. Use when adding Monaco or Shiki support for a new file extension, creating a custom language pack, or copying the SysML v2 integration pattern.
Audit Cursor IDE context-bar consumption (rules, tools, skills, MCP, AGENTS.md) and recommend cuts. Use when the user complains rules/tools/AGENTS.md are taking too much context, opens the context bar and asks why a layer is large, asks to reclaim context budget, or asks to audit Cursor configuration for token bloat.
Programmatic and empirical UI audit of a deployed Tau environment (taucad.dev staging or tau.new production) covering Core Web Vitals, accessibility (WCAG 2.2 AA), security headers, SEO crawlability, bundle/network profile, and console errors. Use when the user asks for a UI audit, performance audit, accessibility audit, Lighthouse audit, axe-core audit, taucad.dev / tau.new audit, or wants to systematically validate a deployed environment against the vision/UI/accessibility/UX policies.
Create or update policy documents in docs/policy/. Use when writing a new policy, updating an existing policy, reviewing policy structure, or when the user mentions policy docs, coding standards, or architectural decisions that should be documented as policy.
Create or update research documents in docs/research/. Use when investigating a bug root cause, auditing code or configuration, comparing libraries or approaches, designing architecture, evaluating migration paths, or when the user mentions research, investigation, audit, analysis, or deep dive.
Author a workspace script in the Tau monorepo following established conventions for bash and TypeScript scripts (shebang, set -euo pipefail, header comment template, env-var validation, REPO_ROOT pattern, location decision tree, chmod, Nx wiring). Use when creating a new script under scripts/, apps/*/scripts/, packages/*/scripts/, or a skill-bundled script, or when the user asks to add a script, helper, CLI tool, smoke test, release helper, or operator runbook.
| name | adding-tools |
| description | Add new tools to the AI chat system. Use when adding a chat tool, creating tool schemas, wiring backend tool handlers, or building tool UI components. |
This guide documents the complete process for adding new tools that can be used by AI agents in the chat system.
Tools follow a client-server RPC pattern via Socket.IO:
chatRpcService.sendRpcRequest() to execute operations on the client via Socket.IO RPCCreate a new file at libs/chat/src/schemas/tools/<tool-name>.tool.schema.ts:
import { z } from 'zod';
export const myToolInputSchema = z.object({
param1: z.string().describe('Description for the LLM'),
param2: z.number().optional().describe('Optional parameter'),
});
export const myToolOutputSchema = z.object({
result: z.string().describe('The result of the operation'),
success: z.boolean().describe('Whether the operation succeeded'),
});
export type MyToolInput = z.infer<typeof myToolInputSchema>;
export type MyToolOutput = z.infer<typeof myToolOutputSchema>;
Update libs/chat/src/constants/tool.constants.ts:
export const toolName = {
// ... existing tools ...
myTool: 'my_tool',
} as const satisfies Record<string, string>;
Update libs/chat/src/index.ts:
export * from '#schemas/tools/my-tool.tool.schema.js';
Update libs/chat/src/types/tool.types.ts:
import type { MyToolInput, MyToolOutput } from '#schemas/tools/my-tool.tool.schema.js';
export type MyTools = InferUITools<{
// ... existing tools ...
[toolName.myTool]: AiTool<MyToolInput, MyToolOutput>;
}>;
Update libs/chat/src/schemas/message.schema.ts:
import { myToolInputSchema, myToolOutputSchema } from '#schemas/tools/my-tool.tool.schema.js';
const toolPartSchemas = [
// ... existing tools ...
...createToolSchemas(toolName.myTool, myToolInputSchema, myToolOutputSchema),
];
Interrupted streams can persist partial tool inputs that no longer satisfy the per-tool schema. The API preprocess reads libs/chat/src/schemas/tool-input.registry.ts — add:
my_tool: myToolInputSchema,
(Use the exported Zod schema from Step 1; keep catalog keys aligned with toolName string literals.)
Create apps/api/app/api/tools/tools/tool-my-tool.ts:
import type { ToolRuntime } from '@langchain/core/tools';
import { tool } from '@langchain/core/tools';
import { myToolInputSchema } from '@taucad/chat';
import { assertRpcSuccess } from '@taucad/chat/utils';
import type { ChatTool, MyToolInput, MyToolOutput } from '@taucad/chat';
import { rpcName, toolName } from '@taucad/chat/constants';
import type { ChatRpcConfigurable } from '#api/tools/tool.types.js';
export const myToolDefinition = {
name: toolName.myTool,
description: `Detailed description for the LLM explaining when and how to use this tool.`,
schema: myToolInputSchema,
} as const;
export const myTool: ChatTool<typeof myToolInputSchema, MyToolInput, MyToolOutput, typeof toolName.myTool> = tool(
async (args, runtime: ToolRuntime) => {
const { chatRpcService, thread_id: chatId } = runtime.configurable as ChatRpcConfigurable;
const { toolCallId } = runtime;
const result = await chatRpcService.sendRpcRequest({
chatId,
toolCallId,
rpcName: rpcName.myRpc,
args,
});
assertRpcSuccess(result, {
toolName: toolName.myTool,
toolCallId,
clientErrorMessage: 'Failed to execute my tool',
});
return result;
},
myToolDefinition,
);
Update apps/api/app/api/tools/tool.service.ts:
import { myTool } from '#api/tools/tools/tool-my-tool.js';
const toolCategoryToTool = {
// ... existing tools ...
[toolName.myTool]: myTool,
} as const satisfies Partial<Record<ToolName, StructuredTool>>;
const toolNameFromToolCategory = {
// ... existing tools ...
[toolName.myTool]: toolCategoryToTool[toolName.myTool].name,
} as const satisfies Partial<Record<ToolName, string>>;
Update apps/api/app/api/chat/chat.service.ts (cadTools array) so the CAD agent receives the LangChain tool:
const cadTools = [
// ... existing tools ...
tools.my_tool,
].filter((tool) => tool !== undefined);
If the tool uses targetFile (or similar fingerprinted inputs), add it to agent-safeguards.middleware.ts targetFileTools so identical repeated failures get one-shot remediation guidance.
If your tool needs a new RPC (e.g., for a new client-side operation), add the RPC handler:
libs/chat/src/constants/rpc.constants.tslibs/chat/src/schemas/rpc.schema.ts using defineRpc()libs/chat/src/rpc/handlers/handle-my-rpc.tslibs/chat/src/rpc/rpc-dispatcher.tsRpcGraphicsClient / CAD snapshot types, extend libs/chat/src/rpc/rpc-dependencies.ts accordinglyapps/ui/app/hooks/rpc-handlers.tsMost tools reuse existing RPCs (e.g., readFile, createFile, getKernelResult, captureObservations).
Browser adapter split: operations that need the live CAD unit, kernel export/render, or viewer-adjacent work belong on RpcGraphicsClient (see createBrowserGraphicsClient in rpc-handlers.ts). Pure kernel compile/status without graphics should stay on RpcRuntimeClient. ensureGeometryUnit in rpc-handlers.ts is the canonical lazy-bootstrap when the LLM names a targetFile that may not have an open geometry unit yet.
Create apps/ui/app/routes/projects_.$id/chat-message-tool-my-tool.tsx:
import { CheckCircle, XCircle, Loader2 } from 'lucide-react';
import type { MyUIMessage } from '@taucad/chat';
import type { MyToolOutput } from '@taucad/chat';
type Props = {
part: Extract<MyUIMessage['parts'][number], { type: 'tool-my_tool' }>;
};
export function ChatMessageToolMyTool({ part }: Props): React.JSX.Element {
const { state } = part;
const output = part.output as MyToolOutput | undefined;
if (state === 'input-streaming' || state === 'input-available') {
return (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="size-4 animate-spin" />
<span>Processing...</span>
</div>
);
}
if (state === 'output-error') {
return (
<div className="flex items-center gap-2 text-sm text-destructive">
<XCircle className="size-4" />
<span>Error: {part.errorText}</span>
</div>
);
}
return (
<div className="flex items-center gap-2 text-sm">
{output?.success ? (
<CheckCircle className="size-4 text-success" />
) : (
<XCircle className="size-4 text-destructive" />
)}
<span>{output?.result}</span>
</div>
);
}
Update apps/ui/app/routes/projects_.$id/chat-message.tsx:
import { ChatMessageToolMyTool } from './chat-message-tool-my-tool.js';
// In renderPart switch:
case 'tool-my_tool': {
return <ChatMessageToolMyTool key={part.toolCallId} part={part} />;
}
Update apps/api/app/api/chat/prompts/cad-agent.prompt.ts (static/dynamic CAD agent prompt builders) when the workflow or safety copy should mention the tool. Prefer terse references aligned with <tool_usage_policy> / <workflow> — duplicating full tool prose belongs in the LangChain description in Step 6.
Extend apps/ui/app/utils/chat.utils.ts toolSerializers with an entry keyed by toolName.myTool so serializePart/serializeMessage stay exhaustive over MyTools (workspace enforces { [K in keyof MyTools]: ToolSerializer<K> }).
If the tool should affect exploration-phase grouping or counts (e.g. research runs), review apps/ui/app/utils/assistant-message-activity.ts and related activity components.
pnpm nx typecheck chatpnpm nx typecheck apipnpm nx typecheck uiFor tools that need to wait for state machine transitions:
await waitFor(machineRef, (state) => state.matches('ready') || state.matches('error'));
API tools: after chatRpcService.sendRpcRequest(...), validate the discriminated RPC result with assertRpcSuccess from @taucad/chat/utils (see existing tools under apps/api/app/api/tools/tools/). Failures surface as AI SDK output-error tool parts automatically — do not manually push tool outputs unless you are intentionally bypassing that path.
Handle transport / unexpected catch blocks by throwing or wrapping in a ToolRuntime-visible error consistent with LangChain conventions for that tool.
Use the file manager for file operations:
const fileContent = await fileManager.readFile(path);
await fileManager.writeFile(path, content, { source: 'external' });
useFileManager().readFile returns Uint8Array (binary-safe); pair with downloadBlob from @taucad/utils/file for user downloads.
.tau/artifacts + writeArtifactWhen an RPC persists bytes for later UI download (e.g. fetched GLB snapshots, interchange exports):
libs/chat/src/rpc/handlers/write-artifact.ts writeArtifact({ toolCallId, targetFile, extension, bytes }, fileSystem) — canonical path ``.tau/artifacts/${toolCallId}__${slugifyTargetFile(targetFile)}.${ext}`.artifactPath, mimeType, and byteLength (or analogous) in the RPC success payload so the chat card can render size + type without re-reading disk.artifactPath via fileManager.readFile, wraps a Blob, and calls downloadBlob(blob, basename).toolCallId into RPC args alongside LLM-visible fields keeps filenames deterministic across retries (mirror fetch_geometry’s artifactId pattern where applicable).