/** Read the world config at every run boundary. All real requests retain the host proxy transport. */ import { AgentDoc, defineExtension, GenerationTask, hook } from '@earendil-works/pi-durable' import { CONFIG, definitionRuns, parseConfig, toolsFor } from '../core/agents.ts' import { ModelsApi, Pin, Profile } from './api.ts' export const order = -200 export default function models(host: any) { const read = async (_cwd: string) => { const config = parseConfig(await host.fs.read(CONFIG)) return {...config, definitions:config.definitions.map((definition: any)=>({...definition, resident:definition.name === config.standing || definition.resident, runs:definitionRuns(definition)})), errors:[], replaced:[]} } const register = (ref: any) => { const store = host.models() if (store.getModel(ref.provider, ref.modelId)) return const transport = store.getProvider(ref.provider) const template = store.getModels(ref.provider)[0] if (!transport || !template) throw new Error(`No proxy transport for ${ref.provider}/${ref.modelId}`) const prior = store.getModels(ref.provider) store.setProvider({...transport, getModels:()=>[...prior, {...template, id:ref.modelId, name:ref.modelId, cost:{input:0,output:0,cacheRead:0,cacheWrite:0}}]}) } const apply = async (tx: any, id: number) => { const state = await tx.doc(AgentDoc, id) const profile = await tx.doc(Profile, id) const config = await read(state.cwd ?? '/') if (profile.standing) profile.agent = config.standing const definition = profile.agent ? config.definitions.find(d => d.name === profile.agent) : undefined if (profile.agent && !definition) throw new Error(`Unknown agent ${profile.agent}: ${JSON.stringify(config.errors)}`) const selected = (definition ?? config.definitions.find((d: any)=>d.name === config.standing)).runs const pinned = await host.want(Pin)?.pin(tx, id) const ref = pinned ?? profile.model ?? selected.model register(ref) state.model = ref if (profile.thinking ?? selected.thinking) state.thinkingLevel = profile.thinking ?? selected.thinking else delete state.thinkingLevel state.instructions = [config.prompt, definition ? `You are @${definition.name}.\n\n${definition.instructions}` : ''].filter(Boolean).join('\n\n') if (definition) { if (profile.inheritedTools === undefined) profile.inheritedTools = state.tools ?? null const inherited = profile.inheritedTools if (definition.tools || Array.isArray(inherited)) state.tools = toolsFor(definition,inherited ?? []).filter(name=>!profile.excludeTools?.includes(name)) else state.tools = {remove:[...new Set([...(inherited?.remove ?? []),...(definition.denyTools ?? []),...(profile.excludeTools ?? [])])]} } return ref } host.implement(ModelsApi, { discover: read, configure: async (tx: any, id: number, options: any) => { const profile = await tx.doc(Profile,id) profile.agent = options.agent profile.standing = options.standing ?? false profile.inheritedTools = options.inheritedTools profile.excludeTools = options.excludeTools ?? [] if (options.model) profile.model = options.model else delete profile.model if (options.thinking) profile.thinking = options.thinking else delete profile.thinking return apply(tx,id) }, resolve: apply, start: async ({agent, cwd, ownership = {kind:'ownerless'}}: any, context: any) => { const conversation = await host.harness().createConversation({ownership, agent:{cwd}, init:async (tx: any, id: number)=>{ (await tx.doc(Profile,id)).agent = agent await apply(tx,id) }}, context) return conversation }, }) return defineExtension({name:'models', hooks:[hook(GenerationTask,{beforeInput:async (_input, {tx,conversationId})=>{await apply(tx,conversationId)}})]}) }