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.
- package/LICENSE +21 -0
- package/NOTICE +11 -0
- package/README.md +206 -2
- package/dist/engine.d.ts +30 -0
- package/dist/engine.js +246 -0
- package/dist/guest/bridge.d.ts +1 -0
- package/dist/guest/bridge.js +14 -0
- package/dist/guest/entry.d.ts +1 -0
- package/dist/guest/entry.js +134 -0
- package/dist/guest/journal.d.ts +6 -0
- package/dist/guest/journal.js +21 -0
- package/dist/guest/platform.d.ts +1 -0
- package/dist/guest/platform.js +64 -0
- package/dist/guest-meta.json +1 -0
- package/dist/guest-source.d.ts +2 -0
- package/dist/guest-source.js +3 -0
- package/dist/guest.js +21929 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +84 -0
- package/dist/licenses/@earendil-works__chord__LICENSE +21 -0
- package/dist/licenses/@earendil-works__pi-ai__LICENSE +21 -0
- package/dist/licenses/@earendil-works__pi-durable__LICENSE +21 -0
- package/dist/licenses/@jitl__quickjs-wasmfile-release-sync__LICENSE +47 -0
- package/dist/licenses/abort-controller__LICENSE +21 -0
- package/dist/licenses/event-target-shim__LICENSE +22 -0
- package/dist/licenses/manifest.json +47 -0
- package/dist/licenses/partial-json__LICENSE +21 -0
- package/dist/licenses/quickjs-emscripten-core__LICENSE +21 -0
- package/dist/licenses/typebox__license +23 -0
- package/dist/providers/openai.d.ts +8 -0
- package/dist/providers/openai.js +41 -0
- package/dist/storage/browser.d.ts +2 -0
- package/dist/storage/browser.js +75 -0
- package/dist/storage/node.d.ts +2 -0
- package/dist/storage/node.js +105 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.js +1 -0
- package/package.json +75 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { open, readFile, mkdir, unlink, truncate } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
export async function openNodeJournal(path) {
|
|
4
|
+
await mkdir(dirname(path), { recursive: true });
|
|
5
|
+
const lockPath = `${path}.lock`;
|
|
6
|
+
let lock;
|
|
7
|
+
try {
|
|
8
|
+
lock = await open(lockPath, 'wx', 0o600);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code !== 'EEXIST')
|
|
12
|
+
throw error;
|
|
13
|
+
// Serialize stale-lock reclamation, then reread the owner under that guard.
|
|
14
|
+
// An abandoned reclaim guard fails closed and requires operator inspection.
|
|
15
|
+
const guardPath = `${lockPath}.reclaim`;
|
|
16
|
+
let guard;
|
|
17
|
+
try {
|
|
18
|
+
guard = await open(guardPath, 'wx', 0o600);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error('Journal owner is being recovered; retry or inspect the reclaim lock');
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const pid = Number(await readFile(lockPath, 'utf8'));
|
|
25
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
26
|
+
throw new Error('Journal lock is incomplete; inspect it before removing');
|
|
27
|
+
try {
|
|
28
|
+
process.kill(pid, 0);
|
|
29
|
+
throw new Error('Journal already has an owner');
|
|
30
|
+
}
|
|
31
|
+
catch (probe) {
|
|
32
|
+
if (probe.code !== 'ESRCH')
|
|
33
|
+
throw probe;
|
|
34
|
+
}
|
|
35
|
+
await unlink(lockPath);
|
|
36
|
+
lock = await open(lockPath, 'wx', 0o600);
|
|
37
|
+
// Publish ownership before releasing the reclaim guard.
|
|
38
|
+
await lock.writeFile(String(process.pid));
|
|
39
|
+
await lock.sync();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await guard.close();
|
|
43
|
+
await unlink(guardPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
await lock.write(String(process.pid), 0);
|
|
47
|
+
await lock.sync();
|
|
48
|
+
let file;
|
|
49
|
+
try {
|
|
50
|
+
file = await open(path, 'a+', 0o600);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
await lock.close();
|
|
54
|
+
await unlink(lockPath);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
let closed = false;
|
|
58
|
+
let tail = Promise.resolve();
|
|
59
|
+
let expected = 1;
|
|
60
|
+
return {
|
|
61
|
+
async load() {
|
|
62
|
+
if (closed)
|
|
63
|
+
throw new Error('Journal is closed');
|
|
64
|
+
const bytes = await readFile(path);
|
|
65
|
+
const end = bytes.lastIndexOf(10) + 1;
|
|
66
|
+
if (end !== bytes.length) {
|
|
67
|
+
await truncate(path, end);
|
|
68
|
+
await file.sync();
|
|
69
|
+
}
|
|
70
|
+
const lines = bytes.subarray(0, end).toString('utf8').split('\n').filter(Boolean);
|
|
71
|
+
const commits = lines.map((line, index) => {
|
|
72
|
+
const commit = JSON.parse(line);
|
|
73
|
+
if (commit.seq !== index + 1 || !Array.isArray(commit.writes))
|
|
74
|
+
throw new Error('Corrupt journal');
|
|
75
|
+
return commit;
|
|
76
|
+
});
|
|
77
|
+
expected = commits.length + 1;
|
|
78
|
+
return commits;
|
|
79
|
+
},
|
|
80
|
+
append(commit) {
|
|
81
|
+
if (closed)
|
|
82
|
+
return Promise.reject(new Error('Journal is closed'));
|
|
83
|
+
const serialized = JSON.stringify(commit) + '\n';
|
|
84
|
+
const seq = commit.seq;
|
|
85
|
+
const write = tail.then(async () => {
|
|
86
|
+
if (seq !== expected)
|
|
87
|
+
throw new Error('Journal sequence conflict');
|
|
88
|
+
await file.writeFile(serialized);
|
|
89
|
+
await file.sync();
|
|
90
|
+
expected++;
|
|
91
|
+
});
|
|
92
|
+
tail = write;
|
|
93
|
+
return write;
|
|
94
|
+
},
|
|
95
|
+
async close() { if (closed)
|
|
96
|
+
return; closed = true; try {
|
|
97
|
+
await tail;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await file.close();
|
|
101
|
+
await lock.close();
|
|
102
|
+
await unlink(lockPath);
|
|
103
|
+
} },
|
|
104
|
+
};
|
|
105
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AssistantMessage, AssistantMessageEvent, Model, TranscriptContext } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { StorageWrite, ConversationView, SettledSubmissionRecord, AgentEvent as PiEvent, AgentState, ToolExecutionResult, HarnessSettings } from '@earendil-works/pi-durable';
|
|
3
|
+
export interface JournalCommit {
|
|
4
|
+
seq: number;
|
|
5
|
+
writes: readonly StorageWrite[];
|
|
6
|
+
}
|
|
7
|
+
/** One exclusive owner. append resolves only after an atomic durable commit. */
|
|
8
|
+
export interface Journal {
|
|
9
|
+
load(): Promise<JournalCommit[]>;
|
|
10
|
+
append(commit: JournalCommit): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface ModelRequest {
|
|
14
|
+
model: Model<any>;
|
|
15
|
+
context: TranscriptContext;
|
|
16
|
+
options?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
export type ModelHandler = (request: ModelRequest, signal: AbortSignal, emit: (event: AssistantMessageEvent) => void) => Promise<AssistantMessage>;
|
|
19
|
+
export interface ToolContext {
|
|
20
|
+
taskId: number;
|
|
21
|
+
conversationId: number;
|
|
22
|
+
signal: AbortSignal;
|
|
23
|
+
callId: string;
|
|
24
|
+
output(text: string): void;
|
|
25
|
+
}
|
|
26
|
+
export interface HostTool {
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
parameters: Record<string, unknown>;
|
|
30
|
+
/** Set only when retrying after interruption is safe. */
|
|
31
|
+
replay?: 'safe';
|
|
32
|
+
execute(args: Record<string, unknown>, context: ToolContext): Promise<string | ToolExecutionResult> | string | ToolExecutionResult;
|
|
33
|
+
}
|
|
34
|
+
export interface AgentOptions {
|
|
35
|
+
journal: Journal;
|
|
36
|
+
model: ModelHandler;
|
|
37
|
+
tools?: HostTool[];
|
|
38
|
+
/** CommonJS sources exporting Pi extensions. Reinstall the same definitions on recovery. */
|
|
39
|
+
extensions?: string[];
|
|
40
|
+
settings?: Omit<HarnessSettings, 'extensions'>;
|
|
41
|
+
/** Open without scheduling persisted work, e.g. to admit a cancellation before recovery. */
|
|
42
|
+
autoResume?: boolean;
|
|
43
|
+
instructions?: string;
|
|
44
|
+
modelId?: string;
|
|
45
|
+
contextWindow?: number;
|
|
46
|
+
memoryLimitBytes?: number;
|
|
47
|
+
cpuSliceMs?: number;
|
|
48
|
+
onEvent?: (event: AgentEvent) => void;
|
|
49
|
+
}
|
|
50
|
+
export type AgentEvent = {
|
|
51
|
+
type: 'commit';
|
|
52
|
+
seq: number;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'error';
|
|
55
|
+
message: string;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'events';
|
|
58
|
+
conversationId: number;
|
|
59
|
+
events: readonly PiEvent[];
|
|
60
|
+
};
|
|
61
|
+
export type AgentView = ConversationView;
|
|
62
|
+
export type SubmissionResult = SettledSubmissionRecord;
|
|
63
|
+
export type AgentConfiguration = Partial<Pick<AgentState, 'model' | 'thinkingLevel' | 'instructions' | 'cwd'>> & {
|
|
64
|
+
extensions?: string[];
|
|
65
|
+
tools?: string[];
|
|
66
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-wasm-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Pi Durable inside a QuickJS WebAssembly sandbox, with browser and Node hosts.",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "node scripts/build.mjs && tsc",
|
|
7
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.demo.json",
|
|
8
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
9
|
+
"dev": "vite --host 127.0.0.1",
|
|
10
|
+
"test:e2e": "playwright test",
|
|
11
|
+
"check": "npm test && npm run typecheck && npm run build:demo && npm run test:e2e",
|
|
12
|
+
"prepack": "npm run build",
|
|
13
|
+
"build:demo": "npm run build && vite build",
|
|
14
|
+
"preview": "vite preview --host 127.0.0.1",
|
|
15
|
+
"test:package": "node scripts/package-smoke.mjs"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"pi",
|
|
19
|
+
"durable",
|
|
20
|
+
"wasm",
|
|
21
|
+
"quickjs",
|
|
22
|
+
"agents",
|
|
23
|
+
"browser"
|
|
24
|
+
],
|
|
25
|
+
"author": "leepokai",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@earendil-works/chord": "1.1.0",
|
|
30
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
31
|
+
"@earendil-works/pi-durable": "1.1.0",
|
|
32
|
+
"@jitl/quickjs-wasmfile-release-sync": "0.32.0",
|
|
33
|
+
"abort-controller": "3.0.0",
|
|
34
|
+
"quickjs-emscripten-core": "0.32.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@playwright/test": "1.64.0",
|
|
38
|
+
"@types/node": "26.6.4",
|
|
39
|
+
"esbuild": "0.28.2",
|
|
40
|
+
"typescript": "7.0.2",
|
|
41
|
+
"vite": "8.3.4"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.19"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"NOTICE"
|
|
50
|
+
],
|
|
51
|
+
"exports": {
|
|
52
|
+
".": {
|
|
53
|
+
"types": "./dist/index.d.ts",
|
|
54
|
+
"import": "./dist/index.js"
|
|
55
|
+
},
|
|
56
|
+
"./node": {
|
|
57
|
+
"types": "./dist/storage/node.d.ts",
|
|
58
|
+
"import": "./dist/storage/node.js"
|
|
59
|
+
},
|
|
60
|
+
"./browser": {
|
|
61
|
+
"types": "./dist/storage/browser.d.ts",
|
|
62
|
+
"import": "./dist/storage/browser.js"
|
|
63
|
+
},
|
|
64
|
+
"./openai": {
|
|
65
|
+
"types": "./dist/providers/openai.d.ts",
|
|
66
|
+
"import": "./dist/providers/openai.js"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/leepokai/pi-wasm-kit.git"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://github.com/leepokai/pi-wasm-kit#readme",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/leepokai/pi-wasm-kit/issues"
|
|
76
|
+
}
|
|
77
|
+
}
|