refactor(core): unify v2 tool architecture (#31168)
This commit is contained in:
parent
effd27b239
commit
660a00d317
46 changed files with 2297 additions and 2041 deletions
|
|
@ -1,9 +1,11 @@
|
|||
export * as QuestionTool from "./question"
|
||||
|
||||
import { Tool, ToolFailure, toolText } from "@opencode-ai/llm"
|
||||
import { ToolFailure, toolText } from "@opencode-ai/llm"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { PermissionV2 } from "../permission"
|
||||
import { QuestionV2 } from "../question"
|
||||
import { ToolRegistry } from "./registry"
|
||||
import { Tool } from "./tool"
|
||||
import { Tools } from "./tools"
|
||||
|
||||
export const name = "question"
|
||||
|
||||
|
|
@ -40,42 +42,45 @@ export const toModelOutput = (
|
|||
return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
|
||||
}
|
||||
|
||||
const definition = Tool.make({
|
||||
description,
|
||||
parameters: Parameters,
|
||||
success: Success,
|
||||
toModelOutput: ({ parameters, output }) => [
|
||||
toolText({ type: "text", text: toModelOutput(parameters.questions, output.answers) }),
|
||||
],
|
||||
})
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const tools = yield* Tools.Service
|
||||
const question = yield* QuestionV2.Service
|
||||
const permission = yield* PermissionV2.Service
|
||||
|
||||
yield* registry.contribute((editor) =>
|
||||
editor.set(name, {
|
||||
tool: definition,
|
||||
permission: { action: "question", resource: "*" },
|
||||
authorize: ({ assertPermission }) =>
|
||||
assertPermission({ action: "question", resources: ["*"] }).pipe(
|
||||
Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })),
|
||||
),
|
||||
execute: ({ parameters, sessionID, source }) =>
|
||||
question
|
||||
.ask({
|
||||
sessionID,
|
||||
questions: parameters.questions,
|
||||
// The registry intentionally leaves source absent until it owns the durable assistant message ID.
|
||||
tool: source?.type === "tool" ? { messageID: source.messageID, callID: source.callID } : undefined,
|
||||
})
|
||||
.pipe(
|
||||
Effect.map((answers) => ({ answers })),
|
||||
// V1 treats a dismissed question as an interrupted tool invocation rather than model-facing text.
|
||||
Effect.orDie,
|
||||
),
|
||||
}),
|
||||
)
|
||||
yield* tools
|
||||
.register({
|
||||
[name]: Tool.make({
|
||||
description,
|
||||
input: Parameters,
|
||||
output: Success,
|
||||
toModelOutput: ({ input, output }) => [
|
||||
toolText({ type: "text", text: toModelOutput(input.questions, output.answers) }),
|
||||
],
|
||||
execute: (input, context) =>
|
||||
permission
|
||||
.assert({
|
||||
action: "question",
|
||||
resources: ["*"],
|
||||
sessionID: context.sessionID,
|
||||
agent: context.agent,
|
||||
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
})
|
||||
.pipe(
|
||||
Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })),
|
||||
Effect.andThen(
|
||||
question
|
||||
.ask({
|
||||
sessionID: context.sessionID,
|
||||
questions: input.questions,
|
||||
tool: { messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
})
|
||||
.pipe(Effect.orDie),
|
||||
),
|
||||
Effect.map((answers) => ({ answers })),
|
||||
),
|
||||
}),
|
||||
})
|
||||
.pipe(Effect.orDie)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue