diff --git a/packages/api-client/src/posthog-client.ts b/packages/api-client/src/posthog-client.ts index eed207fd5..ef4db40c1 100644 --- a/packages/api-client/src/posthog-client.ts +++ b/packages/api-client/src/posthog-client.ts @@ -4964,14 +4964,21 @@ export class PostHogAPIClient { ingressBaseUrl: string, message: string, previewToken?: string | null, + supportedClientTools?: readonly string[], ): Promise<{ session_id: string; resumed?: boolean }> { const url = new URL(`${ingressBaseUrl.replace(/\/$/, "")}/run`); + // `supported_client_tools`: the kind:'client' tool ids this client can + // execute this session, so the runner exposes only those to the model. + const body: Record = { message }; + if (supportedClientTools && supportedClientTools.length > 0) { + body.supported_client_tools = supportedClientTools; + } const response = await this.api.fetcher.fetch({ method: "post", url, path: url.pathname, parameters: previewTokenHeader(previewToken), - overrides: { body: JSON.stringify({ message }) }, + overrides: { body: JSON.stringify(body) }, }); return (await response.json()) as { session_id: string; resumed?: boolean }; } diff --git a/packages/core/src/agent-chat/agentChatService.ts b/packages/core/src/agent-chat/agentChatService.ts index 8541084c1..c3a4f018b 100644 --- a/packages/core/src/agent-chat/agentChatService.ts +++ b/packages/core/src/agent-chat/agentChatService.ts @@ -431,6 +431,7 @@ export class AgentChatService { session.ingressBaseUrl, session.buildWireText(text), token, + session.supportedClientTools, ), ); agentChatStore.getState().setSessionId(session.chatId, session_id); diff --git a/packages/core/src/agent-chat/identifiers.ts b/packages/core/src/agent-chat/identifiers.ts index b12805f0b..9c37d9e7a 100644 --- a/packages/core/src/agent-chat/identifiers.ts +++ b/packages/core/src/agent-chat/identifiers.ts @@ -49,6 +49,8 @@ export interface AgentChatSession { ingressBaseUrl: string; /** Non-null targets a specific draft revision (preview token attached per call). */ revisionId: string | null; + /** `kind:'client'` tool ids this client can fulfil; sent to the runner at /run. */ + supportedClientTools?: readonly string[]; createMapper(): AgentChatMapper; /** Resolve a client-tool call; `defer`/null ⇒ the service won't post a result. */ resolveClientTool( diff --git a/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx b/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx index 8cda60b52..9e2a96dd6 100644 --- a/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx +++ b/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx @@ -25,7 +25,10 @@ import { useAgentBuilderStore, } from "./agentBuilderStore"; import { suggestionsForPage } from "./agentBuilderSuggestions"; -import { useAgentBuilderClientTools } from "./useAgentBuilderClientTools"; +import { + AGENT_BUILDER_CLIENT_TOOLS, + useAgentBuilderClientTools, +} from "./useAgentBuilderClientTools"; const CHAT_ID = AGENT_BUILDER_CHAT_ID; @@ -118,6 +121,7 @@ export function AgentBuilderDock() { orgId: currentOrgId, }), clientTools, + supportedClientTools: AGENT_BUILDER_CLIENT_TOOLS, }); const pendingApproval = useAgentChatPendingApproval(CHAT_ID); diff --git a/packages/ui/src/features/agent-applications/agent-builder/useAgentBuilderClientTools.ts b/packages/ui/src/features/agent-applications/agent-builder/useAgentBuilderClientTools.ts index aa90a64dd..e25348f58 100644 --- a/packages/ui/src/features/agent-applications/agent-builder/useAgentBuilderClientTools.ts +++ b/packages/ui/src/features/agent-applications/agent-builder/useAgentBuilderClientTools.ts @@ -3,6 +3,22 @@ import { useCallback, useRef } from "react"; import type { ClientToolHandler } from "../hooks/useAgentChat"; import { useAgentBuilderStore } from "./agentBuilderStore"; +/** + * The `kind:'client'` tool ids the agent-builder dock can fulfil — sent to the + * runner as `supported_client_tools` at /run so it exposes only these to the + * model. Keep in sync with the handler below plus the built-in toast/get_context. + */ +export const AGENT_BUILDER_CLIENT_TOOLS = [ + "set_secret", + "focus_tab", + "focus_file", + "focus_spec_section", + "focus_revision", + "focus_session", + "toast", + "get_context", +] as const; + /** * The agent builder's UI-driving client tools. The agent calls these to steer the * user's screen (`focus_*`, which navigate code's agent routes and report back diff --git a/packages/ui/src/features/agent-applications/hooks/useAgentChat.ts b/packages/ui/src/features/agent-applications/hooks/useAgentChat.ts index 26d9537e0..659a8aead 100644 --- a/packages/ui/src/features/agent-applications/hooks/useAgentChat.ts +++ b/packages/ui/src/features/agent-applications/hooks/useAgentChat.ts @@ -50,6 +50,8 @@ export interface UseAgentChatOptions { contextProvider?: () => unknown; /** AgentBuilder UI-driving tools (focus_*, set_secret); null → built-in handling. */ clientTools?: ClientToolHandler; + /** `kind:'client'` tool ids this client can fulfil; sent to the runner at /run. */ + supportedClientTools?: readonly string[]; } /** @@ -68,6 +70,7 @@ export function useAgentChat({ recordHistory = false, contextProvider, clientTools, + supportedClientTools, }: UseAgentChatOptions) { const client = useAuthenticatedClient(); const service = useService(AGENT_CHAT_SERVICE); @@ -88,6 +91,7 @@ export function useAgentChat({ agentSlug, ingressBaseUrl: ingressBaseUrl ?? "", revisionId, + supportedClientTools, createMapper: createAgentChatMapper, resolveClientTool: (data) => resolveClientTool( @@ -113,7 +117,15 @@ export function useAgentChat({ }) : undefined, }), - [chatId, agentSlug, ingressBaseUrl, revisionId, recordHistory, recordChat], + [ + chatId, + agentSlug, + ingressBaseUrl, + revisionId, + supportedClientTools, + recordHistory, + recordChat, + ], ); const send = useCallback(