mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-13 09:24:53 +00:00
Compare commits
1 Commits
oc-startup
...
task-spec-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d7501a6a3 |
@@ -47,6 +47,7 @@ import { Cause, Effect, Exit, Layer, Option, Scope, ServiceMap } from "effect"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { TaskTool } from "@/tool/task"
|
||||
import { Config } from "@/config/config"
|
||||
import { SessionRunState } from "./run-state"
|
||||
|
||||
// @ts-ignore
|
||||
@@ -88,6 +89,7 @@ export namespace SessionPrompt {
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
const plugin = yield* Plugin.Service
|
||||
const commands = yield* Command.Service
|
||||
const config = yield* Config.Service
|
||||
const permission = yield* Permission.Service
|
||||
const fsys = yield* AppFileSystem.Service
|
||||
const mcp = yield* MCP.Service
|
||||
@@ -140,6 +142,17 @@ export namespace SessionPrompt {
|
||||
return parts
|
||||
})
|
||||
|
||||
let prompt!: Interface["prompt"]
|
||||
|
||||
const taskTool = () =>
|
||||
TaskTool.build({
|
||||
agent: agents,
|
||||
config,
|
||||
cancel: SessionPrompt.cancel,
|
||||
resolvePromptParts: SessionPrompt.resolvePromptParts,
|
||||
prompt: SessionPrompt.prompt,
|
||||
})
|
||||
|
||||
const title = Effect.fn("SessionPrompt.ensureTitle")(function* (input: {
|
||||
session: Session.Info
|
||||
history: MessageV2.WithParts[]
|
||||
@@ -391,6 +404,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
providerID: input.model.providerID,
|
||||
agent: input.agent,
|
||||
})) {
|
||||
const toolDef = item.id === TaskTool.id ? yield* Tool.init(taskTool()) : item
|
||||
const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
|
||||
tools[item.id] = tool({
|
||||
id: item.id as any,
|
||||
@@ -405,7 +419,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
{ tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID },
|
||||
{ args },
|
||||
)
|
||||
const result = yield* Effect.promise(() => item.execute(args, ctx))
|
||||
const result = yield* Effect.promise(() => toolDef.execute(args, ctx))
|
||||
const output = {
|
||||
...result,
|
||||
attachments: result.attachments?.map((attachment) => ({
|
||||
@@ -521,7 +535,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
}) {
|
||||
const { task, model, lastUser, sessionID, session, msgs } = input
|
||||
const ctx = yield* InstanceState.context
|
||||
const { task: taskTool } = yield* registry.named()
|
||||
const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model
|
||||
const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({
|
||||
id: MessageID.ascending(),
|
||||
@@ -578,8 +591,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
}
|
||||
|
||||
let error: Error | undefined
|
||||
const taskDef = yield* Tool.init(taskTool())
|
||||
const result = yield* Effect.promise((signal) =>
|
||||
taskTool
|
||||
taskDef
|
||||
.execute(taskArgs, {
|
||||
agent: task.agent,
|
||||
messageID: assistantMessage.id,
|
||||
@@ -1267,26 +1281,24 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
return { info, parts }
|
||||
}, Effect.scoped)
|
||||
|
||||
const prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.prompt")(
|
||||
function* (input: PromptInput) {
|
||||
const session = yield* sessions.get(input.sessionID)
|
||||
yield* revert.cleanup(session)
|
||||
const message = yield* createUserMessage(input)
|
||||
yield* sessions.touch(input.sessionID)
|
||||
prompt = Effect.fn("SessionPrompt.prompt")(function* (input: PromptInput) {
|
||||
const session = yield* sessions.get(input.sessionID)
|
||||
yield* revert.cleanup(session)
|
||||
const message = yield* createUserMessage(input)
|
||||
yield* sessions.touch(input.sessionID)
|
||||
|
||||
const permissions: Permission.Ruleset = []
|
||||
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
|
||||
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
|
||||
}
|
||||
if (permissions.length > 0) {
|
||||
session.permission = permissions
|
||||
yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
|
||||
}
|
||||
const permissions: Permission.Ruleset = []
|
||||
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
|
||||
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
|
||||
}
|
||||
if (permissions.length > 0) {
|
||||
session.permission = permissions
|
||||
yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
|
||||
}
|
||||
|
||||
if (input.noReply === true) return message
|
||||
return yield* loop({ sessionID: input.sessionID })
|
||||
},
|
||||
)
|
||||
if (input.noReply === true) return message
|
||||
return yield* loop({ sessionID: input.sessionID })
|
||||
})
|
||||
|
||||
const lastAssistant = (sessionID: SessionID) =>
|
||||
Effect.promise(async () => {
|
||||
@@ -1667,28 +1679,30 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
)
|
||||
|
||||
const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(SessionRunState.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(SessionCompaction.defaultLayer),
|
||||
Layer.provide(SessionProcessor.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Permission.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(FileTime.defaultLayer),
|
||||
Layer.provide(ToolRegistry.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Instruction.defaultLayer),
|
||||
Layer.provide(AppFileSystem.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionRevert.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Bus.layer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
),
|
||||
layer
|
||||
.pipe(
|
||||
Layer.provide(SessionRunState.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(SessionCompaction.defaultLayer),
|
||||
Layer.provide(SessionProcessor.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Permission.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(FileTime.defaultLayer),
|
||||
Layer.provide(ToolRegistry.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Instruction.defaultLayer),
|
||||
Layer.provide(AppFileSystem.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionRevert.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Bus.layer),
|
||||
)
|
||||
.pipe(Layer.provide(CrossSpawnSpawner.defaultLayer)),
|
||||
)
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import { AppFileSystem } from "../filesystem"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { Skill } from "../skill"
|
||||
import { Permission } from "@/permission"
|
||||
import type { TaskMetadata } from "./task"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
const log = Log.create({ service: "tool.registry" })
|
||||
@@ -94,7 +95,7 @@ export namespace ToolRegistry {
|
||||
const agents = yield* Agent.Service
|
||||
const skill = yield* Skill.Service
|
||||
|
||||
const task = yield* TaskTool
|
||||
const task: Tool.Info<typeof TaskTool.parameters, TaskMetadata> = yield* TaskTool
|
||||
const read = yield* ReadTool
|
||||
const question = yield* QuestionTool
|
||||
const todo = yield* TodoWriteTool
|
||||
|
||||
@@ -5,10 +5,9 @@ import { Session } from "../session"
|
||||
import { SessionID, MessageID } from "../session/schema"
|
||||
import { MessageV2 } from "../session/message-v2"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { SessionPrompt } from "../session/prompt"
|
||||
import { Config } from "../config/config"
|
||||
import type { SessionPrompt } from "../session/prompt"
|
||||
import { Effect } from "effect"
|
||||
import { Log } from "@/util/log"
|
||||
|
||||
const id = "task"
|
||||
|
||||
@@ -25,153 +24,180 @@ const parameters = z.object({
|
||||
command: z.string().describe("The command that triggered this task").optional(),
|
||||
})
|
||||
|
||||
export const TaskTool = Tool.defineEffect(
|
||||
id,
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* Agent.Service
|
||||
const config = yield* Config.Service
|
||||
type Metadata = {
|
||||
sessionId: SessionID
|
||||
model: {
|
||||
modelID: MessageV2.Assistant["modelID"]
|
||||
providerID: MessageV2.Assistant["providerID"]
|
||||
}
|
||||
}
|
||||
|
||||
const run = Effect.fn("TaskTool.execute")(function* (params: z.infer<typeof parameters>, ctx: Tool.Context) {
|
||||
const cfg = yield* config.get()
|
||||
export type TaskMetadata = Metadata
|
||||
|
||||
if (!ctx.extra?.bypassAgentCheck) {
|
||||
yield* Effect.promise(() =>
|
||||
ctx.ask({
|
||||
permission: id,
|
||||
patterns: [params.subagent_type],
|
||||
always: ["*"],
|
||||
metadata: {
|
||||
description: params.description,
|
||||
subagent_type: params.subagent_type,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
type Runtime = {
|
||||
agent: Agent.Interface
|
||||
config: Config.Interface
|
||||
cancel: (sessionID: SessionID) => Promise<void>
|
||||
resolvePromptParts: (template: string) => Promise<SessionPrompt.PromptInput["parts"]>
|
||||
prompt: (input: SessionPrompt.PromptInput) => Promise<MessageV2.WithParts>
|
||||
}
|
||||
|
||||
const next = yield* agent.get(params.subagent_type)
|
||||
if (!next) {
|
||||
return yield* Effect.fail(new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`))
|
||||
}
|
||||
const unbound: Tool.DefWithoutID<typeof parameters, Metadata> = {
|
||||
description: DESCRIPTION,
|
||||
parameters,
|
||||
async execute() {
|
||||
throw new Error("Task tool execution is only available from the prompt runtime")
|
||||
},
|
||||
}
|
||||
|
||||
const canTask = next.permission.some((rule) => rule.permission === id)
|
||||
const canTodo = next.permission.some((rule) => rule.permission === "todowrite")
|
||||
const build = (runtime: Runtime) => {
|
||||
const run = Effect.fn("TaskTool.execute")(function* (params: z.infer<typeof parameters>, ctx: Tool.Context) {
|
||||
const cfg = yield* runtime.config.get()
|
||||
|
||||
const taskID = params.task_id
|
||||
const session = taskID
|
||||
? yield* Effect.promise(() => {
|
||||
const id = SessionID.make(taskID)
|
||||
return Session.get(id).catch(() => undefined)
|
||||
})
|
||||
: undefined
|
||||
const nextSession =
|
||||
session ??
|
||||
(yield* Effect.promise(() =>
|
||||
Session.create({
|
||||
parentID: ctx.sessionID,
|
||||
title: params.description + ` (@${next.name} subagent)`,
|
||||
permission: [
|
||||
...(canTodo
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: "todowrite" as const,
|
||||
pattern: "*" as const,
|
||||
action: "deny" as const,
|
||||
},
|
||||
]),
|
||||
...(canTask
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: id,
|
||||
pattern: "*" as const,
|
||||
action: "deny" as const,
|
||||
},
|
||||
]),
|
||||
...(cfg.experimental?.primary_tools?.map((item) => ({
|
||||
pattern: "*",
|
||||
action: "allow" as const,
|
||||
permission: item,
|
||||
})) ?? []),
|
||||
],
|
||||
}),
|
||||
))
|
||||
|
||||
const msg = yield* Effect.sync(() => MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID }))
|
||||
if (msg.info.role !== "assistant") return yield* Effect.fail(new Error("Not an assistant message"))
|
||||
|
||||
const model = next.model ?? {
|
||||
modelID: msg.info.modelID,
|
||||
providerID: msg.info.providerID,
|
||||
}
|
||||
|
||||
ctx.metadata({
|
||||
title: params.description,
|
||||
metadata: {
|
||||
sessionId: nextSession.id,
|
||||
model,
|
||||
},
|
||||
})
|
||||
|
||||
const messageID = MessageID.ascending()
|
||||
|
||||
function cancel() {
|
||||
SessionPrompt.cancel(nextSession.id)
|
||||
}
|
||||
|
||||
return yield* Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
ctx.abort.addEventListener("abort", cancel)
|
||||
if (!ctx.extra?.bypassAgentCheck) {
|
||||
yield* Effect.promise(() =>
|
||||
ctx.ask({
|
||||
permission: id,
|
||||
patterns: [params.subagent_type],
|
||||
always: ["*"],
|
||||
metadata: {
|
||||
description: params.description,
|
||||
subagent_type: params.subagent_type,
|
||||
},
|
||||
}),
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const parts = yield* Effect.promise(() => SessionPrompt.resolvePromptParts(params.prompt))
|
||||
const result = yield* Effect.promise(() =>
|
||||
SessionPrompt.prompt({
|
||||
messageID,
|
||||
sessionID: nextSession.id,
|
||||
model: {
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
},
|
||||
agent: next.name,
|
||||
tools: {
|
||||
...(canTodo ? {} : { todowrite: false }),
|
||||
...(canTask ? {} : { task: false }),
|
||||
...Object.fromEntries((cfg.experimental?.primary_tools ?? []).map((item) => [item, false])),
|
||||
},
|
||||
parts,
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
title: params.description,
|
||||
metadata: {
|
||||
sessionId: nextSession.id,
|
||||
model,
|
||||
},
|
||||
output: [
|
||||
`task_id: ${nextSession.id} (for resuming to continue this task if needed)`,
|
||||
"",
|
||||
"<task_result>",
|
||||
result.parts.findLast((item) => item.type === "text")?.text ?? "",
|
||||
"</task_result>",
|
||||
].join("\n"),
|
||||
}
|
||||
}),
|
||||
() =>
|
||||
Effect.sync(() => {
|
||||
ctx.abort.removeEventListener("abort", cancel)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const next = yield* runtime.agent.get(params.subagent_type)
|
||||
if (!next) {
|
||||
return yield* Effect.fail(new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`))
|
||||
}
|
||||
|
||||
const canTask = next.permission.some((rule) => rule.permission === id)
|
||||
const canTodo = next.permission.some((rule) => rule.permission === "todowrite")
|
||||
|
||||
const taskID = params.task_id
|
||||
const session = taskID
|
||||
? yield* Effect.promise(() => {
|
||||
const id = SessionID.make(taskID)
|
||||
return Session.get(id).catch(() => undefined)
|
||||
})
|
||||
: undefined
|
||||
const nextSession =
|
||||
session ??
|
||||
(yield* Effect.promise(() =>
|
||||
Session.create({
|
||||
parentID: ctx.sessionID,
|
||||
title: params.description + ` (@${next.name} subagent)`,
|
||||
permission: [
|
||||
...(canTodo
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: "todowrite" as const,
|
||||
pattern: "*" as const,
|
||||
action: "deny" as const,
|
||||
},
|
||||
]),
|
||||
...(canTask
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: id,
|
||||
pattern: "*" as const,
|
||||
action: "deny" as const,
|
||||
},
|
||||
]),
|
||||
...(cfg.experimental?.primary_tools?.map((item) => ({
|
||||
pattern: "*",
|
||||
action: "allow" as const,
|
||||
permission: item,
|
||||
})) ?? []),
|
||||
],
|
||||
}),
|
||||
))
|
||||
|
||||
const msg = yield* Effect.sync(() => MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID }))
|
||||
if (msg.info.role !== "assistant") return yield* Effect.fail(new Error("Not an assistant message"))
|
||||
|
||||
const model = next.model ?? {
|
||||
modelID: msg.info.modelID,
|
||||
providerID: msg.info.providerID,
|
||||
}
|
||||
|
||||
ctx.metadata({
|
||||
title: params.description,
|
||||
metadata: {
|
||||
sessionId: nextSession.id,
|
||||
model,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
description: DESCRIPTION,
|
||||
parameters,
|
||||
async execute(params: z.infer<typeof parameters>, ctx) {
|
||||
return Effect.runPromise(run(params, ctx))
|
||||
},
|
||||
const messageID = MessageID.ascending()
|
||||
|
||||
function cancel() {
|
||||
return runtime.cancel(nextSession.id)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
return yield* Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
ctx.abort.addEventListener("abort", cancel)
|
||||
}),
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const parts = yield* Effect.promise(() => runtime.resolvePromptParts(params.prompt))
|
||||
const result = yield* Effect.promise(() =>
|
||||
runtime.prompt({
|
||||
messageID,
|
||||
sessionID: nextSession.id,
|
||||
model: {
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
},
|
||||
agent: next.name,
|
||||
tools: {
|
||||
...(canTodo ? {} : { todowrite: false }),
|
||||
...(canTask ? {} : { task: false }),
|
||||
...Object.fromEntries((cfg.experimental?.primary_tools ?? []).map((item) => [item, false])),
|
||||
},
|
||||
parts,
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
title: params.description,
|
||||
metadata: {
|
||||
sessionId: nextSession.id,
|
||||
model,
|
||||
},
|
||||
output: [
|
||||
`task_id: ${nextSession.id} (for resuming to continue this task if needed)`,
|
||||
"",
|
||||
"<task_result>",
|
||||
result.parts.findLast((item) => item.type === "text")?.text ?? "",
|
||||
"</task_result>",
|
||||
].join("\n"),
|
||||
}
|
||||
}),
|
||||
() =>
|
||||
Effect.sync(() => {
|
||||
ctx.abort.removeEventListener("abort", cancel)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
return Tool.define(id, {
|
||||
description: DESCRIPTION,
|
||||
parameters,
|
||||
async execute(params: z.infer<typeof parameters>, ctx) {
|
||||
return Effect.runPromise(run(params, ctx))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const TaskTool = Object.assign(Effect.succeed(Tool.define(id, unbound)), {
|
||||
id,
|
||||
description: DESCRIPTION,
|
||||
parameters,
|
||||
build,
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ import { SessionStatus } from "../../src/session/status"
|
||||
import { Skill } from "../../src/skill"
|
||||
import { Shell } from "../../src/shell/shell"
|
||||
import { Snapshot } from "../../src/snapshot"
|
||||
import { TaskTool } from "../../src/tool/task"
|
||||
import { ToolRegistry } from "../../src/tool/registry"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { Log } from "../../src/util/log"
|
||||
@@ -727,23 +728,31 @@ it.live(
|
||||
Effect.gen(function* () {
|
||||
const ready = defer<void>()
|
||||
const aborted = defer<void>()
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const { task } = yield* registry.named()
|
||||
const original = task.execute
|
||||
task.execute = async (_args, ctx) => {
|
||||
ready.resolve()
|
||||
ctx.abort.addEventListener("abort", () => aborted.resolve(), { once: true })
|
||||
await new Promise<void>(() => {})
|
||||
const original = TaskTool.build
|
||||
TaskTool.build = ((runtime: Parameters<typeof TaskTool.build>[0]) => {
|
||||
const base = original(runtime)
|
||||
return {
|
||||
title: "",
|
||||
metadata: {
|
||||
sessionId: SessionID.make("task"),
|
||||
model: ref,
|
||||
id: base.id,
|
||||
async init() {
|
||||
const next = await base.init()
|
||||
next.execute = async (_args: any, ctx: any) => {
|
||||
ready.resolve()
|
||||
ctx.abort.addEventListener("abort", () => aborted.resolve(), { once: true })
|
||||
await new Promise<void>(() => {})
|
||||
return {
|
||||
title: "",
|
||||
metadata: {
|
||||
sessionId: SessionID.make("task"),
|
||||
model: ref,
|
||||
},
|
||||
output: "",
|
||||
}
|
||||
}
|
||||
return next
|
||||
},
|
||||
output: "",
|
||||
}
|
||||
}
|
||||
yield* Effect.addFinalizer(() => Effect.sync(() => void (task.execute = original)))
|
||||
}) as typeof TaskTool.build
|
||||
yield* Effect.addFinalizer(() => Effect.sync(() => void (TaskTool.build = original)))
|
||||
|
||||
const { prompt, chat } = yield* boot()
|
||||
const msg = yield* user(chat.id, "hello")
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SessionPrompt } from "../../src/session/prompt"
|
||||
import { MessageID, PartID } from "../../src/session/schema"
|
||||
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||
import { TaskTool } from "../../src/tool/task"
|
||||
import { Tool } from "../../src/tool/tool"
|
||||
import { ToolRegistry } from "../../src/tool/registry"
|
||||
import { provideTmpdirInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
@@ -23,6 +24,15 @@ const ref = {
|
||||
modelID: ModelID.make("test-model"),
|
||||
}
|
||||
|
||||
const bindTask = (agent: Agent.Interface, config: Config.Interface) =>
|
||||
TaskTool.build({
|
||||
agent,
|
||||
config,
|
||||
cancel: (sessionID) => SessionPrompt.cancel(sessionID),
|
||||
resolvePromptParts: (template) => SessionPrompt.resolvePromptParts(template),
|
||||
prompt: (input) => SessionPrompt.prompt(input),
|
||||
})
|
||||
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
Agent.defaultLayer,
|
||||
@@ -175,11 +185,12 @@ describe("tool.task", () => {
|
||||
it.live("execute resumes an existing task session from task_id", () =>
|
||||
provideTmpdirInstance(() =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* Agent.Service
|
||||
const config = yield* Config.Service
|
||||
const sessions = yield* Session.Service
|
||||
const { chat, assistant } = yield* seed()
|
||||
const child = yield* sessions.create({ parentID: chat.id, title: "Existing child" })
|
||||
const tool = yield* TaskTool
|
||||
const def = yield* Effect.promise(() => tool.init())
|
||||
const def = yield* Tool.init(bindTask(agent, config))
|
||||
const resolve = SessionPrompt.resolvePromptParts
|
||||
const prompt = SessionPrompt.prompt
|
||||
let seen: Parameters<typeof SessionPrompt.prompt>[0] | undefined
|
||||
@@ -229,9 +240,10 @@ describe("tool.task", () => {
|
||||
it.live("execute asks by default and skips checks when bypassed", () =>
|
||||
provideTmpdirInstance(() =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* Agent.Service
|
||||
const config = yield* Config.Service
|
||||
const { chat, assistant } = yield* seed()
|
||||
const tool = yield* TaskTool
|
||||
const def = yield* Effect.promise(() => tool.init())
|
||||
const def = yield* Tool.init(bindTask(agent, config))
|
||||
const resolve = SessionPrompt.resolvePromptParts
|
||||
const prompt = SessionPrompt.prompt
|
||||
const calls: unknown[] = []
|
||||
@@ -288,10 +300,11 @@ describe("tool.task", () => {
|
||||
it.live("execute creates a child when task_id does not exist", () =>
|
||||
provideTmpdirInstance(() =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* Agent.Service
|
||||
const config = yield* Config.Service
|
||||
const sessions = yield* Session.Service
|
||||
const { chat, assistant } = yield* seed()
|
||||
const tool = yield* TaskTool
|
||||
const def = yield* Effect.promise(() => tool.init())
|
||||
const def = yield* Tool.init(bindTask(agent, config))
|
||||
const resolve = SessionPrompt.resolvePromptParts
|
||||
const prompt = SessionPrompt.prompt
|
||||
let seen: Parameters<typeof SessionPrompt.prompt>[0] | undefined
|
||||
@@ -342,10 +355,11 @@ describe("tool.task", () => {
|
||||
provideTmpdirInstance(
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* Agent.Service
|
||||
const config = yield* Config.Service
|
||||
const sessions = yield* Session.Service
|
||||
const { chat, assistant } = yield* seed()
|
||||
const tool = yield* TaskTool
|
||||
const def = yield* Effect.promise(() => tool.init())
|
||||
const def = yield* Tool.init(bindTask(agent, config))
|
||||
const resolve = SessionPrompt.resolvePromptParts
|
||||
const prompt = SessionPrompt.prompt
|
||||
let seen: Parameters<typeof SessionPrompt.prompt>[0] | undefined
|
||||
|
||||
Reference in New Issue
Block a user