66 lines
3.3 KiB
TypeScript
66 lines
3.3 KiB
TypeScript
// The standing conversation serves the console and chat. SQLite stays outside the world.
|
|
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context';
|
|
import { proxyModels } from './proxy-models';
|
|
import { Harness, createRegistry } from '@earendil-works/pi-durable';
|
|
import { CodingTools } from './environment';
|
|
import { openStorage } from './sqlite';
|
|
import { environment, conversationChains } from './environment';
|
|
import { CredentialRequests, startRequests } from './credentials';
|
|
import { serve } from './input';
|
|
import { harnessApi } from './harness-api';
|
|
import { loadExtensions } from './extensions';
|
|
const proxy = await Deno.core.ops.op_model_proxy();
|
|
const models = await proxyModels(proxy, () => Deno.core.ops.op_model_proxy());
|
|
const registry = createRegistry();
|
|
registry.install(CodingTools);
|
|
registry.install(CredentialRequests);
|
|
export const storage = await openStorage(globalThis.storagePath);
|
|
const harness = await Harness.open(storage, {
|
|
models, registry, settings: { followUpMode: 'all', get extensions() { return registry.snapshot().installed(); } },
|
|
env: async (target: any) => environment(target.cwd ?? '/', await chain(target.conversationId)),
|
|
}, context);
|
|
const chain = conversationChains(harness, context);
|
|
await harness.root(context, {agent:{cwd:'/src/sausage'}});
|
|
const stopRequests = await startRequests(harness, context);
|
|
const extensions = await loadExtensions(harness, registry, storage, models, context, [CodingTools, CredentialRequests]);
|
|
const standing = await extensions.standing();
|
|
if (!standing) throw new Error('The world needs the models and subagents extensions for its standing agent');
|
|
const conversation = (await harness.conversation(standing.conversationId, context))!;
|
|
const watch = await conversation.watch(context);
|
|
const emitted = new Map<number, string>();
|
|
let partial = '';
|
|
const text = (message: any) => message?.content?.filter((b: any) => b.type === 'text').map((b: any) => b.text).join('') ?? '';
|
|
const render = (view: any) => {
|
|
for (const entry of view.entries) {
|
|
if (entry.kind !== 'pi.assistant') continue;
|
|
const value = text(entry.model?.[0]);
|
|
if (emitted.has(entry.id)) continue;
|
|
if (value.startsWith(partial)) Deno.core.ops.op_output(value.slice(partial.length));
|
|
else Deno.core.ops.op_output(value);
|
|
partial = '';
|
|
emitted.set(entry.id, value);
|
|
}
|
|
const live = text(view.docs['pi.live']?.generation?.message);
|
|
if (live.startsWith(partial)) Deno.core.ops.op_output(live.slice(partial.length));
|
|
partial = live;
|
|
};
|
|
// Do not replay the historical transcript to the console.
|
|
for (const entry of watch.value.entries) {
|
|
if (entry.kind === 'pi.assistant') emitted.set(entry.id, text(entry.model?.[0]));
|
|
}
|
|
if (globalThis.consoleEnabled) watch.start((event: any) => { if (event.kind === 'value') render(event.value); });
|
|
const api = harnessApi(harness, models, extensions, context);
|
|
harness.resume();
|
|
await serve(harness, context, globalThis.consoleEnabled ? async () => {
|
|
await harness.waitForIdle(context);
|
|
const final = await conversation.watch(context);
|
|
render(final.value);
|
|
await final.stop();
|
|
Deno.core.ops.op_output('\n');
|
|
} : undefined, api, async () => (await extensions.standing())!.conversationId);
|
|
await api.close();
|
|
await extensions.close();
|
|
await stopRequests();
|
|
await watch.stop();
|
|
await harness.close(context);
|