Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions packages/opencode/src/server/routes/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import { ToolRegistry } from "../../tool/registry"
import { Session } from "../../session"
import { Agent } from "../../agent/agent"
import { Storage } from "../../storage/storage"
import { Tool } from "../../tool/tool"
import { PermissionNext } from "@/permission/next"
import { Worktree } from "../../worktree"
import { Instance } from "../../project/instance"
import { Project } from "../../project/project"
Expand Down Expand Up @@ -86,6 +91,89 @@ export const ExperimentalRoutes = lazy(() =>
)
},
)
.post(
"/tool/execute",
describeRoute({
summary: "Execute tool",
description: "Execute a specific tool with the provided arguments. Returns the tool output.",
operationId: "tool.execute",
responses: {
200: {
description: "Tool execution result",
content: {
"application/json": {
schema: resolver(
z
.object({
title: z.string(),
output: z.string(),
metadata: z.record(z.string(), z.any()).optional(),
})
.meta({ ref: "ToolExecuteResult" }),
),
},
},
},
...errors(400, 404),
},
}),
validator(
"json",
z.object({
sessionID: z.string().meta({ description: "Session ID for context" }),
messageID: z.string().meta({ description: "Message ID for context" }),
providerID: z.string().meta({ description: "Provider ID for tool filtering" }),
toolID: z.string().meta({ description: "Tool ID to execute" }),
args: z.record(z.string(), z.any()).meta({ description: "Tool arguments" }),
agent: z.string().optional().meta({ description: "Agent name (optional)" }),
callID: z.string().optional().meta({ description: "Tool call ID (optional)" }),
}),
),
async (c) => {
const body = c.req.valid("json")
const session = await Session.get(body.sessionID)
const agentName = body.agent ?? (await Agent.defaultAgent())
const agent = await Agent.get(agentName)
if (!agent) {
throw new Storage.NotFoundError({ message: `Agent not found: ${agentName}` })
}

const tools = await ToolRegistry.tools(body.providerID, agent)
const tool = tools.find((t) => t.id === body.toolID)
if (!tool) {
throw new Storage.NotFoundError({ message: `Tool not found: ${body.toolID}` })
}

const abortController = new AbortController()
let currentMetadata: { title?: string; metadata?: Record<string, any> } = {}

const ctx: Tool.Context = {
sessionID: body.sessionID,
messageID: body.messageID,
agent: agentName,
abort: abortController.signal,
callID: body.callID,
metadata: (input) => {
currentMetadata = input
},
ask: async (req) => {
await PermissionNext.ask({
...req,
sessionID: session.id,
tool: body.callID ? { messageID: body.messageID, callID: body.callID } : undefined,
ruleset: PermissionNext.merge(agent.permission, session.permission ?? []),
})
},
}

const result = await tool.execute(body.args, ctx)
return c.json({
title: result.title || currentMetadata.title || "",
output: result.output,
metadata: result.metadata,
})
},
)
.post(
"/worktree",
describeRoute({
Expand Down
19 changes: 19 additions & 0 deletions packages/sdk/js/src/gen/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import type {
ToolListData,
ToolListResponses,
ToolListErrors,
ToolExecuteData,
ToolExecuteErrors,
ToolExecuteResponses,
InstanceDisposeData,
InstanceDisposeResponses,
PathGetData,
Expand Down Expand Up @@ -390,6 +393,22 @@ class Tool extends _HeyApiClient {
...options,
})
}

/**
* Execute tool
*
* Execute a specific tool with the provided arguments. Returns the tool output.
*/
public execute<ThrowOnError extends boolean = false>(options?: Options<ToolExecuteData, ThrowOnError>) {
return (options?.client ?? this._client).post<ToolExecuteResponses, ToolExecuteErrors, ThrowOnError>({
url: "/experimental/tool/execute",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
})
}
}

class Instance extends _HeyApiClient {
Expand Down
70 changes: 70 additions & 0 deletions packages/sdk/js/src/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,14 @@ export type ToolListItem = {

export type ToolList = Array<ToolListItem>

export type ToolExecuteResult = {
title: string
output: string
metadata?: {
[key: string]: unknown
}
}

export type Path = {
state: string
config: string
Expand Down Expand Up @@ -1996,6 +2004,68 @@ export type ToolListResponses = {

export type ToolListResponse = ToolListResponses[keyof ToolListResponses]

export type ToolExecuteData = {
body?: {
/**
* Session ID for context
*/
sessionID: string
/**
* Message ID for context
*/
messageID: string
/**
* Provider ID for tool filtering
*/
providerID: string
/**
* Tool ID to execute
*/
toolID: string
/**
* Tool arguments
*/
args: {
[key: string]: unknown
}
/**
* Agent name (optional)
*/
agent?: string
/**
* Tool call ID (optional)
*/
callID?: string
}
path?: never
query?: {
directory?: string
}
url: "/experimental/tool/execute"
}

export type ToolExecuteErrors = {
/**
* Bad request
*/
400: BadRequestError
/**
* Not found
*/
404: NotFoundError
}

export type ToolExecuteError = ToolExecuteErrors[keyof ToolExecuteErrors]

export type ToolExecuteResponses = {
/**
* Tool execution result
*/
200: ToolExecuteResult
}

export type ToolExecuteResponse = ToolExecuteResponses[keyof ToolExecuteResponses]

export type InstanceDisposeData = {
body?: never
path?: never
Expand Down
51 changes: 51 additions & 0 deletions packages/sdk/js/src/v2/gen/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ import type {
SessionUpdateResponses,
SubtaskPartInput,
TextPartInput,
ToolExecuteErrors,
ToolExecuteResponses,
ToolIdsErrors,
ToolIdsResponses,
ToolListErrors,
Expand Down Expand Up @@ -651,6 +653,55 @@ export class Tool extends HeyApiClient {
...params,
})
}

/**
* Execute tool
*
* Execute a specific tool with the provided arguments. Returns the tool output.
*/
public execute<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
sessionID?: string
messageID?: string
providerID?: string
toolID?: string
args?: {
[key: string]: unknown
}
agent?: string
callID?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "body", key: "sessionID" },
{ in: "body", key: "messageID" },
{ in: "body", key: "providerID" },
{ in: "body", key: "toolID" },
{ in: "body", key: "args" },
{ in: "body", key: "agent" },
{ in: "body", key: "callID" },
],
},
],
)
return (options?.client ?? this.client).post<ToolExecuteResponses, ToolExecuteErrors, ThrowOnError>({
url: "/experimental/tool/execute",
...options,
...params,
headers: {
"Content-Type": "application/json",
...options?.headers,
...params.headers,
},
})
}
}

export class Worktree extends HeyApiClient {
Expand Down
70 changes: 70 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,14 @@ export type ToolListItem = {

export type ToolList = Array<ToolListItem>

export type ToolExecuteResult = {
title: string
output: string
metadata?: {
[key: string]: unknown
}
}

export type Worktree = {
name: string
branch: string
Expand Down Expand Up @@ -2544,6 +2552,68 @@ export type ToolListResponses = {

export type ToolListResponse = ToolListResponses[keyof ToolListResponses]

export type ToolExecuteData = {
body?: {
/**
* Session ID for context
*/
sessionID: string
/**
* Message ID for context
*/
messageID: string
/**
* Provider ID for tool filtering
*/
providerID: string
/**
* Tool ID to execute
*/
toolID: string
/**
* Tool arguments
*/
args: {
[key: string]: unknown
}
/**
* Agent name (optional)
*/
agent?: string
/**
* Tool call ID (optional)
*/
callID?: string
}
path?: never
query?: {
directory?: string
}
url: "/experimental/tool/execute"
}

export type ToolExecuteErrors = {
/**
* Bad request
*/
400: BadRequestError
/**
* Not found
*/
404: NotFoundError
}

export type ToolExecuteError = ToolExecuteErrors[keyof ToolExecuteErrors]

export type ToolExecuteResponses = {
/**
* Tool execution result
*/
200: ToolExecuteResult
}

export type ToolExecuteResponse = ToolExecuteResponses[keyof ToolExecuteResponses]

export type WorktreeListData = {
body?: never
path?: never
Expand Down
Loading