feat: implement client-side tools via client_tools spec (#456)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-02 23:35:40 -08:00
committed by GitHub
parent 5ad51d7095
commit 34367de5d7
14 changed files with 181 additions and 1157 deletions

View File

@@ -36,12 +36,14 @@ import { type PermissionMode, permissionMode } from "../permissions/mode";
import { updateProjectSettings } from "../settings";
import { settingsManager } from "../settings-manager";
import { telemetry } from "../telemetry";
import type { ToolExecutionResult } from "../tools/manager";
import {
analyzeToolApproval,
checkToolPermission,
executeTool,
isGeminiModel,
isOpenAIModel,
savePermissionRule,
type ToolExecutionResult,
} from "../tools/manager";
import {
handleMcpAdd,
@@ -393,8 +395,6 @@ export default function App({
agentState?: AgentState | null;
loadingState?:
| "assembling"
| "upserting"
| "updating_tools"
| "importing"
| "initializing"
| "checking"
@@ -1075,11 +1075,14 @@ export default function App({
setCurrentModelId(agentModelHandle || null);
}
// Detect current toolset from attached tools
const { detectToolsetFromAgent } = await import("../tools/toolset");
const detected = await detectToolsetFromAgent(client, agentId);
if (detected) {
setCurrentToolset(detected);
// Derive toolset from agent's model (not persisted, computed on resume)
if (agentModelHandle) {
const derivedToolset = isOpenAIModel(agentModelHandle)
? "codex"
: isGeminiModel(agentModelHandle)
? "gemini"
: "default";
setCurrentToolset(derivedToolset);
}
} catch (error) {
console.error("Error fetching agent config:", error);
@@ -3346,96 +3349,6 @@ export default function App({
return { submitted: true };
}
// Special handling for /link command - attach all Letta Code tools (deprecated)
if (msg.trim() === "/link" || msg.trim().startsWith("/link ")) {
const cmdId = uid("cmd");
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: "Attaching Letta Code tools...",
phase: "running",
});
buffersRef.current.order.push(cmdId);
refreshDerived();
setCommandRunning(true);
try {
const { linkToolsToAgent } = await import("../agent/modify");
const result = await linkToolsToAgent(agentId);
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: result.message,
phase: "finished",
success: result.success,
});
refreshDerived();
} catch (error) {
const errorDetails = formatErrorDetails(error, agentId);
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: `Failed to link tools: ${errorDetails}`,
phase: "finished",
success: false,
});
refreshDerived();
} finally {
setCommandRunning(false);
}
return { submitted: true };
}
// Special handling for /unlink command - remove all Letta Code tools (deprecated)
if (msg.trim() === "/unlink" || msg.trim().startsWith("/unlink ")) {
const cmdId = uid("cmd");
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: "Removing Letta Code tools...",
phase: "running",
});
buffersRef.current.order.push(cmdId);
refreshDerived();
setCommandRunning(true);
try {
const { unlinkToolsFromAgent } = await import("../agent/modify");
const result = await unlinkToolsFromAgent(agentId);
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: result.message,
phase: "finished",
success: result.success,
});
refreshDerived();
} catch (error) {
const errorDetails = formatErrorDetails(error, agentId);
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: `Failed to unlink tools: ${errorDetails}`,
phase: "finished",
success: false,
});
refreshDerived();
} finally {
setCommandRunning(false);
}
return { submitted: true };
}
// Special handling for /bg command - show background shell processes
if (msg.trim() === "/bg") {
const { backgroundProcesses } = await import(

View File

@@ -48,8 +48,6 @@ async function getAuthMethod(): Promise<"url" | "api-key" | "oauth"> {
type LoadingState =
| "assembling"
| "upserting"
| "updating_tools"
| "importing"
| "initializing"
| "checking"
@@ -144,10 +142,6 @@ function getLoadingMessage(
return continueSession ? "Resuming agent..." : "Creating agent...";
case "assembling":
return "Assembling tools...";
case "upserting":
return "Upserting tools...";
case "updating_tools":
return "Updating tools...";
case "importing":
return "Importing agent...";
case "checking":