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
|
|
@ -4,7 +4,7 @@ import { Agent } from "@opencode-ai/schema/agent"
|
|||
import type { LLM } from "@opencode-ai/schema/llm"
|
||||
import { Session } from "@opencode-ai/schema/session"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { Effect, JsonSchema, Schema, type Scope } from "effect"
|
||||
import { Effect, JsonSchema, Schema } from "effect"
|
||||
import type { Hooks, Transform } from "./registration.js"
|
||||
|
||||
export interface Context {
|
||||
|
|
@ -40,11 +40,16 @@ type ToolResultValue =
|
|||
| { readonly type: "error"; readonly value: unknown }
|
||||
| { readonly type: "content"; readonly value: ReadonlyArray<LLM.ToolContent> }
|
||||
|
||||
type ToolOutput = {
|
||||
export type SettledOutput = {
|
||||
readonly structured: unknown
|
||||
readonly content: ReadonlyArray<LLM.ToolContent>
|
||||
}
|
||||
|
||||
export type SettlementInterceptor = (
|
||||
input: unknown,
|
||||
passthrough: Effect.Effect<SettledOutput, Failure>,
|
||||
) => Effect.Effect<SettledOutput, Failure>
|
||||
|
||||
declare const TypeId: unique symbol
|
||||
|
||||
export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
|
||||
|
|
@ -113,7 +118,11 @@ type DynamicConfig = {
|
|||
type Runtime = {
|
||||
readonly permission?: string
|
||||
readonly definition: (name: string) => ToolDefinition
|
||||
readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, Failure>
|
||||
readonly settle: (
|
||||
call: ToolCall,
|
||||
context: Context,
|
||||
interceptor?: SettlementInterceptor,
|
||||
) => Effect.Effect<SettledOutput, Failure>
|
||||
}
|
||||
|
||||
const runtimes = new WeakMap<AnyTool, Runtime>()
|
||||
|
|
@ -149,11 +158,11 @@ function makeTyped<
|
|||
definitions.set(name, definition)
|
||||
return definition
|
||||
},
|
||||
settle: (call, context) =>
|
||||
settle: (call, context, interceptor) =>
|
||||
Schema.decodeUnknownEffect(config.input)(call.input).pipe(
|
||||
Effect.mapError((error) => new Failure({ message: `Invalid tool input: ${error.message}` })),
|
||||
Effect.flatMap((input) =>
|
||||
config.execute(input, context).pipe(
|
||||
Effect.flatMap((input) => {
|
||||
const passthrough = Effect.suspend(() => config.execute(input, context)).pipe(
|
||||
Effect.flatMap((output) =>
|
||||
Schema.encodeEffect(config.output)(output).pipe(
|
||||
Effect.flatMap((output) => {
|
||||
|
|
@ -177,8 +186,9 @@ function makeTyped<
|
|||
config.toModelOutput?.({ input, output }).map(toModelContent) ??
|
||||
(typeof output === "string" ? [{ type: "text" as const, text: output }] : []),
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
return interceptor?.(input, passthrough) ?? passthrough
|
||||
}),
|
||||
),
|
||||
})
|
||||
return tool
|
||||
|
|
@ -200,10 +210,11 @@ function makeDynamic(config: DynamicConfig): AnyTool {
|
|||
definitions.set(name, definition)
|
||||
return definition
|
||||
},
|
||||
settle: (call, context) =>
|
||||
config
|
||||
.execute(call.input, context)
|
||||
.pipe(Effect.map((output) => ({ structured: output.structured, content: output.content.map(toModelContent) }))),
|
||||
settle: (call, context, interceptor) => {
|
||||
const passthrough = Effect.suspend(() => config.execute(call.input, context))
|
||||
.pipe(Effect.map((output) => ({ structured: output.structured, content: output.content.map(toModelContent) })))
|
||||
return interceptor?.(call.input, passthrough) ?? passthrough
|
||||
},
|
||||
})
|
||||
return tool
|
||||
}
|
||||
|
|
@ -241,7 +252,8 @@ export const withPermission = <Input extends SchemaType<any>, Output extends Sch
|
|||
|
||||
export const permission = (tool: AnyTool, name: string) => runtimeOf(tool).permission ?? name
|
||||
export const definition = (name: string, tool: AnyTool) => runtimeOf(tool).definition(name)
|
||||
export const settle = (tool: AnyTool, call: ToolCall, context: Context) => runtimeOf(tool).settle(call, context)
|
||||
export const settle = (tool: AnyTool, call: ToolCall, context: Context, interceptor?: SettlementInterceptor) =>
|
||||
runtimeOf(tool).settle(call, context, interceptor)
|
||||
|
||||
function runtimeOf(tool: AnyTool) {
|
||||
const runtime = runtimes.get(tool)
|
||||
|
|
@ -272,7 +284,7 @@ export interface ToolExecuteAfterEvent {
|
|||
readonly callID: string
|
||||
readonly input: unknown
|
||||
result: ToolResultValue
|
||||
output?: ToolOutput
|
||||
output?: SettledOutput
|
||||
outputPaths?: ReadonlyArray<string>
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue