Files
sausage/extensions/messages/branch.ts
T

40 lines
3.0 KiB
TypeScript

/** Fork a tree root, record its starter, and admit the first message exactly once. */
import { bool, int, obj, opt, str } from '@earendil-works/pi-schema'
import { defineTool, ROOT_CONVERSATION_ID } from '@earendil-works/pi-durable'
import { ModelsApi } from '../models/api.ts'
import { configureChild } from '../subagents/children.ts'
import { LineageApi, Roots } from '../lineage/api.ts'
import { imagesParameter } from './images.ts'
import { send } from './send.ts'
export const branchTool = (host: any) => defineTool({
name: 'branch', replay: 'safe',
description: 'Fork a root for one task in an explicit repository. Its first answer returns to you. Worktree isolation requires workspaces.',
parameters: obj({repo: str({minLength: 1}), prompt: str({minLength: 1}), title: opt(str({minLength: 1, maxLength: 60})), root: opt(int({minimum: 1})), isolated: opt(bool()), images: opt(imagesParameter)}),
async execute(input, api, ctx) {
let id = await api.memo<number>('branch-id', ctx)
if (id === undefined) {
const lineage = host.need(LineageApi)
const workspaces = input.isolated ? host.need({name:'workspaces'}) : undefined
const roots = (await api.snapshot(Roots, ROOT_CONVERSATION_ID, ctx))?.roots ?? []
const root = input.root ?? await lineage.rootOf(api.conversationId, ctx) ?? (roots.length === 1 ? roots[0] : undefined)
if (root === undefined || !roots.includes(root)) throw new Error('Choose a listed root')
const {definitions} = await host.need(ModelsApi).discover(input.repo)
const coordinator = definitions.find((definition: any) => definition.name === 'coordinator')
if (!coordinator) throw new Error('Missing coordinator definition')
const rootAgent = await (await host.harness().conversation(root, ctx))!.agent(ctx)
const branch = await lineage.branch({from: root, repo: input.repo, prompt: input.prompt, title: input.title, model: coordinator.runs.model, deliver: false, by: api.conversationId, receipt: {taskId: api.taskId, callId: api.callId}}, ctx)
const existing = await workspaces?.get(branch.id, ctx)
const cwd = existing ? (await (await host.harness().conversation(branch.id, ctx)).agent(ctx)).cwd : input.repo
const ancestors = workspaces ? (await workspaces.chain(branch.id, ctx)).slice(1) : []
await host.harness().commit(async (tx: any) => {
await configureChild(host, tx, branch.id, {definition: coordinator, parentAgent: rootAgent, cwd})
if (workspaces && !existing) await workspaces.create(tx, branch.id, input.repo, ancestors)
;(await tx.doc(host.metadata, branch.id)).label = 'coordinator'
}, ctx)
id = await api.memo('branch-id', branch.id, ctx)
}
const message = await send(host, api.conversationId, {to: `#${id}`, text: input.prompt, reply: true, images: input.images, imagesFrom: api.conversationId}, ctx, {taskId: api.taskId, callId: api.callId})
return {output: [{type: 'text', text: `Branch #${id} started; message ${message} sent.`}]}
},
})