Files
sausage/host/js/main.ts
T

76 lines
3.6 KiB
TypeScript

// Durable root conversation and console. SQLite stays outside the world.
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context';
import { createModels } from '@earendil-works/pi-ai/models';
import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';
import { Harness, createRegistry } from '@earendil-works/pi-durable';
import { CodingTools } from '@earendil-works/pi-durable/tools';
import { openStorage } from './sqlite';
import { environment } from './environment';
import { CredentialRequests, startRequests } from './credentials';
const proxy = Deno.core.ops.op_model_proxy();
if (!proxy?.base_url || !proxy?.api_key) throw new Error('The llm-proxy credential is required');
const models = createModels();
const provider = openaiProvider();
const model = {
id: 'gpt-6.1-sol', name: 'gpt-6.1-sol', provider: 'openai', api: 'openai-responses',
baseUrl: `${proxy.base_url}/v1`, reasoning: true, input: ['text'],
thinkingLevelMap: { off: null, minimal: null },
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 4096,
};
models.setProvider({ ...provider, baseUrl: model.baseUrl, getModels: () => [model],
auth: { apiKey: { name: 'Sausage proxy', resolve: async () => ({
auth: { apiKey: proxy.api_key, baseUrl: model.baseUrl,
headers: { 'Shopify-Usage-Tag': JSON.stringify(['pi', 'sausage']) } },
source: 'llm-proxy',
}) } },
});
const registry = createRegistry();
registry.install(CodingTools);
registry.install(CredentialRequests);
const harness = await Harness.open(await openStorage(globalThis.storagePath), {
models, registry, env: (target: any) => environment(target.cwd ?? '/', target.conversationId),
}, context);
const root = await harness.root(context, { agent: { model: { provider: 'openai', modelId: model.id }, thinkingLevel: 'low', cwd: '/', extensions: [CodingTools, CredentialRequests] } });
const watch = await root.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]));
}
watch.start((event: any) => { if (event.kind === 'value') render(event.value); });
const stopRequests = await startRequests(harness, context);
harness.resume();
await harness.waitForIdle(context);
while (true) {
const line = await Deno.core.ops.op_input();
if (line === null) break;
if (!line.trim()) continue;
const submission = await root.submit({ type: 'input', content: line, requestId: crypto.randomUUID() }, context);
const result = await submission.wait(context);
await harness.waitForIdle(context);
const final = await root.watch(context);
render(final.value);
await final.stop();
Deno.core.ops.op_output('\n');
if (result.status !== 'done') throw new Error(`Run ended: ${JSON.stringify(result)}`);
}
await stopRequests();
await watch.stop();
await harness.close(context);