feat(core): add simulated tool execution seam
This commit is contained in:
parent
75bc611ef1
commit
0918b7d805
5 changed files with 202 additions and 38 deletions
|
|
@ -3,7 +3,8 @@ export * as ExecuteTool from "./execute"
|
|||
import { CodeMode, Tool, toolError } from "@opencode-ai/codemode"
|
||||
import { ToolOutput } from "@opencode-ai/llm"
|
||||
import { Effect, Ref, Schema } from "effect"
|
||||
import { definition, make, settle, type AnyTool } from "./tool"
|
||||
import { definition, make, type AnyTool } from "./tool"
|
||||
import { ToolExecutionSimulation } from "./execution-simulation"
|
||||
|
||||
const ExecuteFile = Schema.Struct({
|
||||
data: Schema.String,
|
||||
|
|
@ -111,7 +112,7 @@ export const create = (registrations: ReadonlyMap<string, Registration>) => {
|
|||
(name, registration, input) =>
|
||||
Effect.gen(function* () {
|
||||
const index = yield* Ref.getAndUpdate(callIndex, (index) => index + 1)
|
||||
const output = yield* settle(
|
||||
const output = yield* ToolExecutionSimulation.settle(
|
||||
registration.tool,
|
||||
{ type: "tool-call", id: context.callID, name, input },
|
||||
{
|
||||
|
|
|
|||
43
packages/core/src/tool/execution-simulation.ts
Normal file
43
packages/core/src/tool/execution-simulation.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
export * as ToolExecutionSimulation from "./execution-simulation"
|
||||
|
||||
import type { ToolCall } from "@opencode-ai/llm"
|
||||
import { Effect } from "effect"
|
||||
import { Tool } from "./tool"
|
||||
|
||||
export interface Invocation {
|
||||
readonly tool: string
|
||||
readonly input: unknown
|
||||
readonly context: Tool.Context
|
||||
}
|
||||
|
||||
export type Handler = (
|
||||
invocation: Invocation,
|
||||
passthrough: Effect.Effect<Tool.SettledOutput, Tool.Failure>,
|
||||
) => Effect.Effect<Tool.SettledOutput, Tool.Failure>
|
||||
|
||||
const handlers = new Map<string, ReadonlyArray<{ readonly token: object; readonly handler: Handler }>>()
|
||||
|
||||
export const intercept = (tool: string, handler: Handler) =>
|
||||
Effect.acquireRelease(
|
||||
Effect.sync(() => {
|
||||
const token = {}
|
||||
handlers.set(tool, [...(handlers.get(tool) ?? []), { token, handler }])
|
||||
return token
|
||||
}),
|
||||
(token) =>
|
||||
Effect.sync(() => {
|
||||
const remaining = handlers.get(tool)?.filter((entry) => entry.token !== token) ?? []
|
||||
if (remaining.length > 0) handlers.set(tool, remaining)
|
||||
else handlers.delete(tool)
|
||||
}),
|
||||
).pipe(Effect.asVoid)
|
||||
|
||||
export const settle = (tool: Tool.AnyTool, call: ToolCall, context: Tool.Context) => {
|
||||
const handler = handlers.get(call.name)?.at(-1)?.handler
|
||||
return Tool.settle(
|
||||
tool,
|
||||
call,
|
||||
context,
|
||||
handler ? (input, passthrough) => handler({ tool: call.name, input, context }, passthrough) : undefined,
|
||||
)
|
||||
}
|
||||
|
|
@ -9,9 +9,11 @@ import { SessionSchema } from "../session/schema"
|
|||
import { ToolOutputStore } from "../tool-output-store"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
import { ExecuteTool } from "./execute"
|
||||
import { definition, permission, registrationEntries, RegistrationError, settle, type AnyTool } from "./tool"
|
||||
import { Tool } from "./tool"
|
||||
import { definition, permission, registrationEntries, RegistrationError, type AnyTool } from "./tool"
|
||||
import { Tools } from "./tools"
|
||||
import { ToolHooks } from "./hooks"
|
||||
import { ToolExecutionSimulation } from "./execution-simulation"
|
||||
import { makeLocationNode } from "../effect/app-node"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { toSessionError } from "../session/to-session-error"
|
||||
|
|
@ -76,29 +78,30 @@ const registryLayer = Layer.effect(
|
|||
input: input.call.input,
|
||||
}
|
||||
yield* toolHooks.runBefore(beforeEvent)
|
||||
const pending = yield* settle(
|
||||
const context: Tool.Context = {
|
||||
sessionID: input.sessionID,
|
||||
agent: input.agent,
|
||||
messageID: input.messageID,
|
||||
callID: input.call.id,
|
||||
progress: (update) =>
|
||||
input.progress?.({
|
||||
structured: update.structured,
|
||||
content: (update.content ?? []).map((part) =>
|
||||
part.type === "text"
|
||||
? { type: "text" as const, text: part.text }
|
||||
: {
|
||||
type: "file" as const,
|
||||
uri: `data:${part.mime};base64,${part.data}`,
|
||||
mime: part.mime,
|
||||
name: part.name,
|
||||
},
|
||||
),
|
||||
}) ?? Effect.void,
|
||||
}
|
||||
const pending = yield* ToolExecutionSimulation.settle(
|
||||
tool,
|
||||
{ ...input.call, input: beforeEvent.input },
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
agent: input.agent,
|
||||
messageID: input.messageID,
|
||||
callID: input.call.id,
|
||||
progress: (update) =>
|
||||
input.progress?.({
|
||||
structured: update.structured,
|
||||
content: (update.content ?? []).map((part) =>
|
||||
part.type === "text"
|
||||
? { type: "text" as const, text: part.text }
|
||||
: {
|
||||
type: "file" as const,
|
||||
uri: `data:${part.mime};base64,${part.data}`,
|
||||
mime: part.mime,
|
||||
name: part.name,
|
||||
},
|
||||
),
|
||||
}) ?? Effect.void,
|
||||
},
|
||||
context,
|
||||
).pipe(
|
||||
Effect.map((output) => ({ output })),
|
||||
Effect.catchTag("LLM.ToolFailure", (failure) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue