feat(core): add copilotkit.runTool() for programmatic tool execution#3286
feat(core): add copilotkit.runTool() for programmatic tool execution#3286tylerslaton wants to merge 4 commits intomainfrom
Conversation
Adds a new `runTool()` method to `CopilotKitCore` that allows programmatic execution of registered frontend tools without requiring an LLM turn. This enables UI-driven tool invocations (e.g., button clicks triggering tool handlers) while maintaining proper message history and subscriber notifications. Key changes: - Extract `executeToolHandler` helper from duplicated logic in `executeSpecificTool` to share with `runTool` - Add `runTool()` on `RunHandler` with tool/agent lookup, message creation, handler execution, and optional follow-up support - Add `TOOL_NOT_FOUND` and `AGENT_NOT_FOUND` error codes - Add `CopilotKitCoreRunToolParams` and `CopilotKitCoreRunToolResult` types - Add delegation method on `CopilotKitCore` - Add 16 comprehensive tests covering all runTool behaviors Co-Authored-By: Claude Opus 4.6 <[email protected]>
🦋 Changeset detectedLatest commit: 374696e The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@copilotkit/a2ui-renderer
@copilotkit/react-core
@copilotkit/react-textarea
@copilotkit/react-ui
@copilotkit/runtime
@copilotkit/runtime-client-gql
@copilotkit/sdk-js
@copilotkit/shared
@copilotkitnext/agent
@copilotkitnext/angular
@copilotkitnext/core
@copilotkitnext/react
@copilotkitnext/runtime
@copilotkitnext/shared
@copilotkitnext/sqlite-runner
@copilotkit/voice
@copilotkitnext/web-inspector
commit: |
Co-Authored-By: Claude Opus 4.6 <[email protected]>
| await (this.core as unknown as CopilotKitCoreFriendsAccess).emitError({ | ||
| error: parseError, | ||
| code: CopilotKitCoreErrorCode.TOOL_ARGUMENT_PARSE_FAILED, | ||
| context: { | ||
| agentId, | ||
| toolCallId: toolCall.id, | ||
| toolName: toolCall.function.name, | ||
| rawArguments: handlerArgs, | ||
| toolType, | ||
| ...(messageId ? { messageId } : {}), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| await ( | ||
| this.core as unknown as CopilotKitCoreFriendsAccess | ||
| ).notifySubscribers( | ||
| (subscriber) => | ||
| subscriber.onToolExecutionStart?.({ | ||
| copilotkit: this.core, | ||
| toolCallId: toolCall.id, | ||
| agentId, | ||
| toolName: toolCall.function.name, | ||
| args: parsedArgs, | ||
| }), | ||
| "Subscriber onToolExecutionStart error:", | ||
| ); | ||
|
|
||
| if (!errorMessage) { | ||
| try { | ||
| parsedArgs = JSON.parse(toolCall.function.arguments); | ||
| const result = await tool.handler!(parsedArgs as any, { | ||
| toolCall: toolCall as any, | ||
| agent, | ||
| }); | ||
| if (result === undefined || result === null) { | ||
| toolCallResult = ""; | ||
| } else if (typeof result === "string") { | ||
| toolCallResult = result; | ||
| } else { | ||
| toolCallResult = JSON.stringify(result); |
There was a problem hiding this comment.
this code snippet has so many type casts, can we maybe remove this, llms pick this up constantly and think it's okay to do
There was a problem hiding this comment.
Yep, let me give that a try.
|

Adds a new
runTool()method toCopilotKitCorethat allows programmatic execution of registered frontend tools without requiring an LLM turn. This enables UI-driven tool invocations (e.g., button clicks triggering tool handlers) while maintaining proper message history and subscriber notifications.Key changes:
executeToolHandlerhelper from duplicated logic inexecuteSpecificToolto share withrunToolrunTool()onRunHandlerwith tool/agent lookup, message creation, handler execution, and optional follow-up supportTOOL_NOT_FOUNDandAGENT_NOT_FOUNDerror codesCopilotKitCoreRunToolParamsandCopilotKitCoreRunToolResulttypesCopilotKitCore