pi-wasm-kit 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +11 -0
  3. package/README.md +206 -2
  4. package/dist/engine.d.ts +30 -0
  5. package/dist/engine.js +246 -0
  6. package/dist/guest/bridge.d.ts +1 -0
  7. package/dist/guest/bridge.js +14 -0
  8. package/dist/guest/entry.d.ts +1 -0
  9. package/dist/guest/entry.js +134 -0
  10. package/dist/guest/journal.d.ts +6 -0
  11. package/dist/guest/journal.js +21 -0
  12. package/dist/guest/platform.d.ts +1 -0
  13. package/dist/guest/platform.js +64 -0
  14. package/dist/guest-meta.json +1 -0
  15. package/dist/guest-source.d.ts +2 -0
  16. package/dist/guest-source.js +3 -0
  17. package/dist/guest.js +21929 -0
  18. package/dist/index.d.ts +104 -0
  19. package/dist/index.js +84 -0
  20. package/dist/licenses/@earendil-works__chord__LICENSE +21 -0
  21. package/dist/licenses/@earendil-works__pi-ai__LICENSE +21 -0
  22. package/dist/licenses/@earendil-works__pi-durable__LICENSE +21 -0
  23. package/dist/licenses/@jitl__quickjs-wasmfile-release-sync__LICENSE +47 -0
  24. package/dist/licenses/abort-controller__LICENSE +21 -0
  25. package/dist/licenses/event-target-shim__LICENSE +22 -0
  26. package/dist/licenses/manifest.json +47 -0
  27. package/dist/licenses/partial-json__LICENSE +21 -0
  28. package/dist/licenses/quickjs-emscripten-core__LICENSE +21 -0
  29. package/dist/licenses/typebox__license +23 -0
  30. package/dist/providers/openai.d.ts +8 -0
  31. package/dist/providers/openai.js +41 -0
  32. package/dist/storage/browser.d.ts +2 -0
  33. package/dist/storage/browser.js +75 -0
  34. package/dist/storage/node.d.ts +2 -0
  35. package/dist/storage/node.js +105 -0
  36. package/dist/types.d.ts +66 -0
  37. package/dist/types.js +1 -0
  38. package/package.json +75 -4
@@ -0,0 +1,134 @@
1
+ import './platform.js';
2
+ import * as contexts from '@earendil-works/chord/context';
3
+ import * as pi from '@earendil-works/pi-durable';
4
+ import { Type } from '@earendil-works/pi-ai';
5
+ import { createModels, createProvider } from '@earendil-works/pi-ai/models';
6
+ import { createAssistantMessageEventStream } from '@earendil-works/pi-ai/utils/event-stream';
7
+ import { JournalStorage } from './journal.js';
8
+ import { hostCall } from './bridge.js';
9
+ const context = contexts.BACKGROUND_CONTEXT;
10
+ let harness;
11
+ let root;
12
+ const registry = pi.createRegistry();
13
+ const watches = new Map();
14
+ const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
15
+ function stream(model, transcript, options) {
16
+ const events = createAssistantMessageEventStream();
17
+ void hostCall('model', { model, context: transcript, options: { ...options, signal: undefined } }, options?.signal, event => events.push(event)).then(message => {
18
+ if (message.stopReason === 'error' || message.stopReason === 'aborted')
19
+ events.push({ type: 'error', reason: message.stopReason, error: message });
20
+ else
21
+ events.push({ type: 'done', reason: message.stopReason, message });
22
+ events.end(message);
23
+ }, error => {
24
+ const message = { role: 'assistant', api: 'host', provider: 'host', model: model.id, content: [], usage, stopReason: options?.signal?.aborted ? 'aborted' : 'error', errorMessage: String(error), timestamp: Date.now() };
25
+ events.push({ type: 'error', reason: message.stopReason, error: message });
26
+ events.end(message);
27
+ });
28
+ return events;
29
+ }
30
+ function install(source) {
31
+ const module = { exports: {} };
32
+ const require = (name) => {
33
+ if (name === '@earendil-works/pi-durable')
34
+ return pi;
35
+ if (name === '@earendil-works/pi-ai')
36
+ return { Type };
37
+ if (name === '@earendil-works/chord/context')
38
+ return contexts;
39
+ throw new Error(`Guest module not available: ${name}`);
40
+ };
41
+ new Function('module', 'exports', 'require', source)(module, module.exports, require);
42
+ const extension = module.exports.default ?? module.exports;
43
+ registry.install(extension);
44
+ return extension.name;
45
+ }
46
+ async function conversation(args) {
47
+ if (args.conversationId === undefined)
48
+ return root;
49
+ const found = await harness.conversation(args.conversationId, context);
50
+ if (!found)
51
+ throw new Error('Conversation not found');
52
+ return found;
53
+ }
54
+ async function watch(id) {
55
+ if (watches.has(id))
56
+ return;
57
+ const stream = await pi.watchEvents(harness, id, context);
58
+ watches.set(id, stream);
59
+ const emit = (events) => __emit(JSON.stringify({ type: 'events', conversationId: id, events }));
60
+ emit([stream.snapshot]);
61
+ stream.start(async (events) => { emit(events); });
62
+ }
63
+ async function open(config) {
64
+ const models = createModels();
65
+ models.setProvider(createProvider({ id: 'host', auth: { apiKey: { name: 'Host', resolve: async () => ({ auth: {} }) } }, models: [{ id: config.modelId ?? 'host-model', name: 'Host model', provider: 'host', baseUrl: 'host://model', api: 'host', reasoning: true, input: ['text', 'image'], cost: usage.cost, contextWindow: config.contextWindow ?? 32768, maxTokens: 4096 }], api: { stream, streamSimple: stream } }));
66
+ registry.install(pi.defineExtension({ name: 'host-tools', tools: config.tools.map((tool) => pi.defineTool({ ...tool, execute: async (args, api, ctx) => {
67
+ const result = await hostCall('tool', { name: tool.name, args, taskId: api.taskId, conversationId: api.conversationId, callId: api.callId }, ctx.abortSignal, update => { if (update.type === 'output')
68
+ api.output(update.text); });
69
+ return typeof result === 'string' ? { content: [{ type: 'text', text: result }] } : result;
70
+ } })) }));
71
+ for (const source of config.extensions ?? [])
72
+ install(source);
73
+ harness = await pi.Harness.open(await JournalStorage.open(), { models, registry, settings: config.settings, onReport: error => __emit(JSON.stringify({ type: 'error', message: String(error) })) }, context);
74
+ root = await harness.root(context, { agent: { model: { provider: 'host', modelId: config.modelId ?? 'host-model' }, instructions: config.instructions ?? 'You are a helpful assistant.' } });
75
+ if (config.events)
76
+ await watch(root.id);
77
+ if (config.autoResume !== false)
78
+ harness.resume();
79
+ return { conversationId: root.id };
80
+ }
81
+ const api = {
82
+ open,
83
+ resume: async () => { harness.resume(); return null; },
84
+ submit: async (args) => ({ id: (await (await conversation(args)).submit(args.submission, context)).id }),
85
+ wait: async (args) => { const submission = await harness.submission(args.id, context); if (!submission)
86
+ throw new Error('Submission not found'); return submission.wait(context); },
87
+ view: async (args) => { const state = await (await conversation(args)).viewState(context); try {
88
+ return state.value;
89
+ }
90
+ finally {
91
+ state.dispose();
92
+ } },
93
+ context: async (args) => (await conversation(args)).context(context, args.at === undefined ? {} : { at: args.at }),
94
+ configure: async (args) => {
95
+ const change = { ...args.change };
96
+ const snapshot = registry.snapshot();
97
+ if (change.extensions)
98
+ change.extensions = change.extensions.map((name) => { const ext = snapshot.extension(name); if (!ext)
99
+ throw new Error(`Unknown extension: ${name}`); return ext; });
100
+ if (change.tools)
101
+ change.tools = change.tools.map((name) => { const tool = snapshot.tools().find(t => t.tool.name === name)?.tool; if (!tool)
102
+ throw new Error(`Unknown tool: ${name}`); return tool; });
103
+ await (await conversation(args)).configure(change, context);
104
+ return null;
105
+ },
106
+ createConversation: async (args) => ({ conversationId: (await harness.createConversation({ ownership: { kind: 'ownerless' }, agent: args.agent }, context)).id }),
107
+ fork: async (args) => ({ conversationId: (await (await conversation(args)).fork(args.at, { ownership: { kind: 'ownerless' } }, context)).id }),
108
+ reset: async (args) => { await (await conversation(args)).reset(args.handoff, context); return null; },
109
+ compact: async (args) => ({ taskId: await (await conversation(args)).compact(args.instructions, context) }),
110
+ waitForTask: async (args) => harness.waitForTask(args.id, context),
111
+ inspect: async () => harness.inspect(context),
112
+ taskGraph: async () => { const state = await harness.taskGraph(context); try {
113
+ return state.value;
114
+ }
115
+ finally {
116
+ state.dispose();
117
+ } },
118
+ abort: async (args) => { await (await conversation(args)).abort(context, { background: args.background }); return null; },
119
+ abortSubmission: async (args) => harness.abortSubmission(args.id, context),
120
+ watch: async (args) => { await watch((await conversation(args)).id); return null; },
121
+ install: async (args) => install(args.source),
122
+ uninstall: async (args) => { registry.uninstall({ name: args.name }); return null; },
123
+ evaluate: async (args) => {
124
+ const fn = new Function('pi', 'Type', 'harness', 'conversation', 'registry', 'context', 'args', `return (async () => { ${args.source}\n })()`);
125
+ return fn(pi, Type, harness, await conversation(args), registry, context, args.args);
126
+ },
127
+ close: async () => { for (const stream of watches.values())
128
+ await stream.stop(); watches.clear(); await harness.close(context); return null; },
129
+ };
130
+ globalThis.__dispatch = async (method, raw) => {
131
+ if (!Object.hasOwn(api, method))
132
+ throw new Error('Unknown guest method');
133
+ return JSON.stringify((await api[method](JSON.parse(raw))) ?? null);
134
+ };
@@ -0,0 +1,6 @@
1
+ import { MemoryStorage, type StorageWrite, type Seq } from '@earendil-works/pi-durable';
2
+ export declare class JournalStorage extends MemoryStorage {
3
+ static open(): Promise<JournalStorage>;
4
+ commit(writes: readonly StorageWrite[]): Promise<Seq>;
5
+ close(): Promise<void>;
6
+ }
@@ -0,0 +1,21 @@
1
+ import { MemoryStorage } from '@earendil-works/pi-durable';
2
+ import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';
3
+ export class JournalStorage extends MemoryStorage {
4
+ static async open() {
5
+ const storage = new JournalStorage();
6
+ const commits = JSON.parse(await __host('load', '{}'));
7
+ let expected = 1;
8
+ for (const commit of commits) {
9
+ if (commit.seq !== expected++)
10
+ throw new Error('Journal sequence is corrupt');
11
+ storage.prepareCommit(commit.writes, commit.seq).apply();
12
+ }
13
+ return storage;
14
+ }
15
+ async commit(writes) {
16
+ const prepared = this.prepareCommit(writes);
17
+ await __host('append', JSON.stringify({ seq: prepared.seq, writes: prepared.writes }));
18
+ return prepared.apply();
19
+ }
20
+ async close() { await super.close(BACKGROUND_CONTEXT); }
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import { AbortController, AbortSignal } from 'abort-controller/dist/abort-controller.mjs';
2
+ const abort = AbortController.prototype.abort;
3
+ AbortController.prototype.abort = function (reason) {
4
+ if (this.signal.aborted)
5
+ return;
6
+ Object.defineProperty(this.signal, 'reason', { value: reason ?? new Error('Operation aborted') });
7
+ abort.call(this);
8
+ };
9
+ AbortSignal.prototype.throwIfAborted = function () { if (this.aborted)
10
+ throw this.reason; };
11
+ AbortSignal.any = (signals) => {
12
+ const controller = new AbortController();
13
+ const handlers = new Map();
14
+ const stop = (signal) => {
15
+ controller.abort(signal.reason);
16
+ for (const [source, handler] of handlers)
17
+ source.removeEventListener('abort', handler);
18
+ };
19
+ for (const signal of signals) {
20
+ if (signal.aborted) {
21
+ stop(signal);
22
+ break;
23
+ }
24
+ const handler = () => stop(signal);
25
+ handlers.set(signal, handler);
26
+ signal.addEventListener('abort', handler, { once: true });
27
+ }
28
+ return controller.signal;
29
+ };
30
+ Object.assign(globalThis, {
31
+ AbortController, AbortSignal,
32
+ URL: class {
33
+ href;
34
+ constructor(input, base) { Object.assign(this, JSON.parse(__hostSync('url', JSON.stringify([String(input), base])))); }
35
+ toString() { return this.href; }
36
+ toJSON() { return this.href; }
37
+ static canParse(input, base) { try {
38
+ __hostSync('url', JSON.stringify([input, base]));
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ } }
44
+ },
45
+ setTimeout: (fn, ms = 0, ...args) => __timer(() => fn(...args), ms),
46
+ clearTimeout: __clearTimer,
47
+ performance: { now: () => Number(__hostSync('now', '')) },
48
+ queueMicrotask: (fn) => Promise.resolve().then(fn),
49
+ structuredClone: (value) => value === undefined ? undefined : JSON.parse(JSON.stringify(value)),
50
+ crypto: {
51
+ randomUUID: () => JSON.parse(__hostSync('uuid', '')),
52
+ getRandomValues: (array) => { array.set(JSON.parse(__hostSync('random', String(array.length)))); return array; },
53
+ },
54
+ TextEncoder: class {
55
+ encode(text = '') { return Uint8Array.from(JSON.parse(__hostSync('encode', text))); }
56
+ },
57
+ TextDecoder: class {
58
+ decode(bytes = new Uint8Array()) { return JSON.parse(__hostSync('decode', JSON.stringify(Array.from(bytes)))); }
59
+ },
60
+ console: { log() { }, warn() { }, error() { }, assert() { } },
61
+ DOMException: class extends Error {
62
+ constructor(message, name = "Error") { super(message); this.name = name; }
63
+ },
64
+ });