@edukors/ai-cli 0.0.0-stage → 0.1.1
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 +9 -0
- package/README.md +162 -2
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +256 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -0
- package/dist/diagnostics.d.ts +28 -0
- package/dist/diagnostics.js +35 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/engine.d.ts +157 -0
- package/dist/engine.js +395 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +95 -0
- package/dist/events.js +8 -0
- package/dist/events.js.map +1 -0
- package/dist/host/main.d.ts +2 -0
- package/dist/host/main.js +30 -0
- package/dist/host/main.js.map +1 -0
- package/dist/host/stdio.d.ts +34 -0
- package/dist/host/stdio.js +254 -0
- package/dist/host/stdio.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/config.d.ts +19 -0
- package/dist/mcp/config.js +63 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/external.d.ts +55 -0
- package/dist/mcp/external.js +129 -0
- package/dist/mcp/external.js.map +1 -0
- package/dist/mcp/toolServer.d.ts +30 -0
- package/dist/mcp/toolServer.js +102 -0
- package/dist/mcp/toolServer.js.map +1 -0
- package/dist/platform/discovery.d.ts +23 -0
- package/dist/platform/discovery.js +64 -0
- package/dist/platform/discovery.js.map +1 -0
- package/dist/platform/env.d.ts +12 -0
- package/dist/platform/env.js +104 -0
- package/dist/platform/env.js.map +1 -0
- package/dist/platform/login.d.ts +16 -0
- package/dist/platform/login.js +42 -0
- package/dist/platform/login.js.map +1 -0
- package/dist/platform/paths.d.ts +19 -0
- package/dist/platform/paths.js +46 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/process.d.ts +55 -0
- package/dist/platform/process.js +186 -0
- package/dist/platform/process.js.map +1 -0
- package/dist/provider.d.ts +156 -0
- package/dist/provider.js +2 -0
- package/dist/provider.js.map +1 -0
- package/dist/providers/claude/adapter.d.ts +43 -0
- package/dist/providers/claude/adapter.js +398 -0
- package/dist/providers/claude/adapter.js.map +1 -0
- package/dist/providers/claude/events.d.ts +36 -0
- package/dist/providers/claude/events.js +90 -0
- package/dist/providers/claude/events.js.map +1 -0
- package/dist/providers/codex/adapter.d.ts +28 -0
- package/dist/providers/codex/adapter.js +497 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/rpc.d.ts +17 -0
- package/dist/providers/codex/rpc.js +75 -0
- package/dist/providers/codex/rpc.js.map +1 -0
- package/dist/providers.d.ts +9 -0
- package/dist/providers.js +15 -0
- package/dist/providers.js.map +1 -0
- package/dist/redact.d.ts +7 -0
- package/dist/redact.js +48 -0
- package/dist/redact.js.map +1 -0
- package/dist/registry/lock.d.ts +18 -0
- package/dist/registry/lock.js +83 -0
- package/dist/registry/lock.js.map +1 -0
- package/dist/registry/registry.d.ts +38 -0
- package/dist/registry/registry.js +119 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/session.d.ts +126 -0
- package/dist/session.js +333 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +7 -0
- package/dist/settings.js.map +1 -0
- package/dist/skills.d.ts +24 -0
- package/dist/skills.js +91 -0
- package/dist/skills.js.map +1 -0
- package/dist/testing/fakeProvider.d.ts +22 -0
- package/dist/testing/fakeProvider.js +266 -0
- package/dist/testing/fakeProvider.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tools.d.ts +45 -0
- package/dist/tools.js +32 -0
- package/dist/tools.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/fixtures/fake-ai.mjs +230 -0
- package/fixtures/fake-claude.mjs +62 -0
- package/fixtures/fake-codex.mjs +281 -0
- package/fixtures/fake-mcp-server.mjs +14 -0
- package/package.json +74 -4
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProviderAdapter } from "./provider.js";
|
|
2
|
+
/**
|
|
3
|
+
* The adapters the bundled executables (`edukors-ai`, `edukors-ai-host`)
|
|
4
|
+
* offer, in the order a provider menu shows them. `--fake` adds the
|
|
5
|
+
* deterministic test provider.
|
|
6
|
+
*/
|
|
7
|
+
export declare function bundledProviders(options?: {
|
|
8
|
+
fake?: boolean;
|
|
9
|
+
}): ProviderAdapter[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createClaudeProvider } from "./providers/claude/adapter.js";
|
|
2
|
+
import { createCodexProvider } from "./providers/codex/adapter.js";
|
|
3
|
+
import { createFakeProvider } from "./testing/fakeProvider.js";
|
|
4
|
+
/**
|
|
5
|
+
* The adapters the bundled executables (`edukors-ai`, `edukors-ai-host`)
|
|
6
|
+
* offer, in the order a provider menu shows them. `--fake` adds the
|
|
7
|
+
* deterministic test provider.
|
|
8
|
+
*/
|
|
9
|
+
export function bundledProviders(options = {}) {
|
|
10
|
+
const providers = [createClaudeProvider(), createCodexProvider()];
|
|
11
|
+
if (options.fake)
|
|
12
|
+
providers.push(createFakeProvider());
|
|
13
|
+
return providers;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAA8B,EAAE;IAC/D,MAAM,SAAS,GAAsB,CAAC,oBAAoB,EAAE,EAAE,mBAAmB,EAAE,CAAC,CAAC;IACrF,IAAI,OAAO,CAAC,IAAI;QAAE,SAAS,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACvD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface RedactOptions {
|
|
2
|
+
/** Replaced with `~`. Defaults to the current OS user's home. */
|
|
3
|
+
home?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function redactText(text: string, options?: RedactOptions): string;
|
|
6
|
+
/** Deep copy with sensitive keys dropped and every string redacted. */
|
|
7
|
+
export declare function redactValue(value: unknown, options?: RedactOptions, depth?: number): unknown;
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
/** Keys whose values are never written to diagnostics. */
|
|
3
|
+
const SENSITIVE_KEY = /(token|secret|password|passwd|authorization|cookie|api[_-]?key|credential|session[_-]?key|private[_-]?key)/i;
|
|
4
|
+
const PATTERNS = [
|
|
5
|
+
// Bearer and Basic authorization values.
|
|
6
|
+
[/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 [redacted]"],
|
|
7
|
+
// Provider-style API keys (sk-..., sk-ant-..., ghp_..., xox?-...).
|
|
8
|
+
[/\b(sk|rk|pk)-[A-Za-z0-9_-]{16,}/g, "[redacted-key]"],
|
|
9
|
+
[/\b(ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{16,}/g, "[redacted-key]"],
|
|
10
|
+
[/\bxox[abposr]-[A-Za-z0-9-]{10,}/g, "[redacted-key]"],
|
|
11
|
+
// JSON Web Tokens.
|
|
12
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "[redacted-jwt]"],
|
|
13
|
+
// key=value and "key": "value" forms with a sensitive key.
|
|
14
|
+
[
|
|
15
|
+
/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|authorization)["']?\s*[:=]\s*["']?)([^\s"',;}]{4,})/gi,
|
|
16
|
+
"$1[redacted]",
|
|
17
|
+
],
|
|
18
|
+
// Long opaque strings that look like credentials.
|
|
19
|
+
[/\b[A-Za-z0-9+/_-]{64,}={0,2}/g, "[redacted-long]"],
|
|
20
|
+
];
|
|
21
|
+
export function redactText(text, options = {}) {
|
|
22
|
+
let out = text;
|
|
23
|
+
for (const [pattern, replacement] of PATTERNS)
|
|
24
|
+
out = out.replace(pattern, replacement);
|
|
25
|
+
const home = options.home ?? homedir();
|
|
26
|
+
if (home && home.length > 1)
|
|
27
|
+
out = out.split(home).join("~");
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/** Deep copy with sensitive keys dropped and every string redacted. */
|
|
31
|
+
export function redactValue(value, options = {}, depth = 0) {
|
|
32
|
+
if (depth > 12)
|
|
33
|
+
return "[truncated]";
|
|
34
|
+
if (typeof value === "string")
|
|
35
|
+
return redactText(value, options);
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return value.map((v) => redactValue(v, options, depth + 1));
|
|
38
|
+
if (value instanceof Error)
|
|
39
|
+
return { name: value.name, message: redactText(value.message, options) };
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [key, v] of Object.entries(value))
|
|
43
|
+
out[key] = SENSITIVE_KEY.test(key) ? "[redacted]" : redactValue(v, options, depth + 1);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,0DAA0D;AAC1D,MAAM,aAAa,GAAG,6GAA6G,CAAC;AAEpI,MAAM,QAAQ,GAAuB;IACnC,yCAAyC;IACzC,CAAC,6CAA6C,EAAE,eAAe,CAAC;IAChE,mEAAmE;IACnE,CAAC,kCAAkC,EAAE,gBAAgB,CAAC;IACtD,CAAC,mDAAmD,EAAE,gBAAgB,CAAC;IACvE,CAAC,kCAAkC,EAAE,gBAAgB,CAAC;IACtD,mBAAmB;IACnB,CAAC,+DAA+D,EAAE,gBAAgB,CAAC;IACnF,2DAA2D;IAC3D;QACE,gIAAgI;QAChI,cAAc;KACf;IACD,kDAAkD;IAClD,CAAC,+BAA+B,EAAE,iBAAiB,CAAC;CACrD,CAAC;AAOF,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,UAAyB,EAAE;IAClE,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,QAAQ;QAAE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACvF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;IACvC,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,UAAyB,EAAE,EAAE,KAAK,GAAG,CAAC;IAChF,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,aAAa,CAAC;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACtF,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;IACrG,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACrI,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cross-process lock: a directory that exists while it is held. `mkdir` is
|
|
3
|
+
* atomic on every supported filesystem. The holder refreshes its timestamp
|
|
4
|
+
* while it works; a lock whose owner process is gone, or that has not been
|
|
5
|
+
* refreshed within `staleMs`, is taken over.
|
|
6
|
+
*/
|
|
7
|
+
export interface LockOptions {
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
staleMs?: number;
|
|
10
|
+
/** The error thrown when the lock cannot be acquired in time. */
|
|
11
|
+
onTimeout?: () => Error;
|
|
12
|
+
}
|
|
13
|
+
export declare function withLock<T>(dir: string, fn: () => Promise<T>, options?: LockOptions): Promise<T>;
|
|
14
|
+
/** Serializes async work within this process, by key. */
|
|
15
|
+
export declare class KeyedMutex {
|
|
16
|
+
private readonly tails;
|
|
17
|
+
run<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { AiCliError } from "../errors.js";
|
|
5
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6
|
+
function processAlive(pid) {
|
|
7
|
+
try {
|
|
8
|
+
process.kill(pid, 0);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
return e.code === "EPERM";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function isStale(dir, staleMs) {
|
|
16
|
+
try {
|
|
17
|
+
const info = await stat(dir);
|
|
18
|
+
if (Date.now() - info.mtimeMs > staleMs)
|
|
19
|
+
return true;
|
|
20
|
+
const owner = JSON.parse(await readFile(join(dir, "owner.json"), "utf8"));
|
|
21
|
+
return owner.host === hostname() && typeof owner.pid === "number" && !processAlive(owner.pid);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Owner file not written yet: give the holder the benefit of the doubt until staleMs.
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function withLock(dir, fn, options = {}) {
|
|
29
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
30
|
+
const staleMs = options.staleMs ?? 30_000;
|
|
31
|
+
const deadline = Date.now() + timeoutMs;
|
|
32
|
+
for (let attempt = 0;; attempt++) {
|
|
33
|
+
try {
|
|
34
|
+
await mkdir(dir);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
const code = e.code;
|
|
39
|
+
if (code === "ENOENT") {
|
|
40
|
+
await mkdir(join(dir, ".."), { recursive: true });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (code !== "EEXIST")
|
|
44
|
+
throw e;
|
|
45
|
+
if (await isStale(dir, staleMs)) {
|
|
46
|
+
await rm(dir, { recursive: true, force: true });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (Date.now() > deadline)
|
|
50
|
+
throw options.onTimeout?.() ?? new AiCliError("REGISTRY_LOCKED", "the shared account registry is busy", { meta: { lock: dir } });
|
|
51
|
+
await sleep(Math.min(200, 5 * 2 ** Math.min(attempt, 5)) + Math.random() * 10);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const heartbeat = setInterval(() => {
|
|
55
|
+
const now = new Date();
|
|
56
|
+
utimes(dir, now, now).catch(() => undefined);
|
|
57
|
+
}, Math.max(250, staleMs / 3));
|
|
58
|
+
heartbeat.unref();
|
|
59
|
+
try {
|
|
60
|
+
await writeFile(join(dir, "owner.json"), JSON.stringify({ pid: process.pid, host: hostname(), at: new Date().toISOString() }));
|
|
61
|
+
return await fn();
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
clearInterval(heartbeat);
|
|
65
|
+
await rm(dir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Serializes async work within this process, by key. */
|
|
69
|
+
export class KeyedMutex {
|
|
70
|
+
tails = new Map();
|
|
71
|
+
run(key, fn) {
|
|
72
|
+
const previous = this.tails.get(key) ?? Promise.resolve();
|
|
73
|
+
const next = previous.then(fn, fn);
|
|
74
|
+
const tail = next.catch(() => undefined);
|
|
75
|
+
this.tails.set(key, tail);
|
|
76
|
+
void tail.then(() => {
|
|
77
|
+
if (this.tails.get(key) === tail)
|
|
78
|
+
this.tails.delete(key);
|
|
79
|
+
});
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/registry/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The shared account registry: non-secret account metadata, read and written
|
|
4
|
+
* by every EDUKORS application on this computer, possibly running different
|
|
5
|
+
* library versions.
|
|
6
|
+
*
|
|
7
|
+
* Versioning: `schemaVersion` is the format the file was last written in;
|
|
8
|
+
* `minReaderVersion` is the oldest library format that can still read it.
|
|
9
|
+
* A library reads a file whose `minReaderVersion` it meets, and writes only
|
|
10
|
+
* a file whose `schemaVersion` it knows, so an older application never
|
|
11
|
+
* rewrites a newer file. Unknown fields are preserved on write.
|
|
12
|
+
*/
|
|
13
|
+
export declare const REGISTRY_SCHEMA_VERSION = 1;
|
|
14
|
+
export declare const AccountRecordSchema: z.ZodObject<{
|
|
15
|
+
id: z.ZodString;
|
|
16
|
+
providerId: z.ZodString;
|
|
17
|
+
label: z.ZodString;
|
|
18
|
+
home: z.ZodString;
|
|
19
|
+
createdAt: z.ZodString;
|
|
20
|
+
updatedAt: z.ZodString;
|
|
21
|
+
}, z.core.$loose>;
|
|
22
|
+
export type AccountRecord = z.infer<typeof AccountRecordSchema>;
|
|
23
|
+
export declare class AccountRegistry {
|
|
24
|
+
readonly file: string;
|
|
25
|
+
private readonly lockDir;
|
|
26
|
+
private readonly mutex;
|
|
27
|
+
constructor(file: string, lockDir: string);
|
|
28
|
+
list(): Promise<AccountRecord[]>;
|
|
29
|
+
get(id: string): Promise<AccountRecord | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Reads, changes and writes the registry under the cross-process lock. `change`
|
|
32
|
+
* mutates the draft; whatever it returns is returned.
|
|
33
|
+
*/
|
|
34
|
+
update<T>(change: (accounts: AccountRecord[]) => T): Promise<T>;
|
|
35
|
+
private read;
|
|
36
|
+
/** Write to a temporary file, flush, then rename over the original. */
|
|
37
|
+
private write;
|
|
38
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { AiCliError } from "../errors.js";
|
|
6
|
+
import { KeyedMutex, withLock } from "./lock.js";
|
|
7
|
+
/**
|
|
8
|
+
* The shared account registry: non-secret account metadata, read and written
|
|
9
|
+
* by every EDUKORS application on this computer, possibly running different
|
|
10
|
+
* library versions.
|
|
11
|
+
*
|
|
12
|
+
* Versioning: `schemaVersion` is the format the file was last written in;
|
|
13
|
+
* `minReaderVersion` is the oldest library format that can still read it.
|
|
14
|
+
* A library reads a file whose `minReaderVersion` it meets, and writes only
|
|
15
|
+
* a file whose `schemaVersion` it knows, so an older application never
|
|
16
|
+
* rewrites a newer file. Unknown fields are preserved on write.
|
|
17
|
+
*/
|
|
18
|
+
export const REGISTRY_SCHEMA_VERSION = 1;
|
|
19
|
+
export const AccountRecordSchema = z.looseObject({
|
|
20
|
+
/** Immutable; session identity derives from it, never from the label. */
|
|
21
|
+
id: z.string().regex(/^[A-Za-z0-9_-]{8,64}$/),
|
|
22
|
+
providerId: z.string().min(1),
|
|
23
|
+
label: z.string().min(1).max(80),
|
|
24
|
+
/** Application-managed storage, relative to the data directory. */
|
|
25
|
+
home: z.string().min(1),
|
|
26
|
+
createdAt: z.string(),
|
|
27
|
+
updatedAt: z.string(),
|
|
28
|
+
});
|
|
29
|
+
const RegistryFileSchema = z.looseObject({
|
|
30
|
+
schemaVersion: z.number().int().positive(),
|
|
31
|
+
minReaderVersion: z.number().int().positive(),
|
|
32
|
+
accounts: z.array(AccountRecordSchema),
|
|
33
|
+
});
|
|
34
|
+
const empty = () => ({ schemaVersion: REGISTRY_SCHEMA_VERSION, minReaderVersion: 1, accounts: [] });
|
|
35
|
+
export class AccountRegistry {
|
|
36
|
+
file;
|
|
37
|
+
lockDir;
|
|
38
|
+
mutex = new KeyedMutex();
|
|
39
|
+
constructor(file, lockDir) {
|
|
40
|
+
this.file = file;
|
|
41
|
+
this.lockDir = lockDir;
|
|
42
|
+
}
|
|
43
|
+
async list() {
|
|
44
|
+
return (await this.read()).accounts;
|
|
45
|
+
}
|
|
46
|
+
async get(id) {
|
|
47
|
+
return (await this.list()).find((a) => a.id === id);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reads, changes and writes the registry under the cross-process lock. `change`
|
|
51
|
+
* mutates the draft; whatever it returns is returned.
|
|
52
|
+
*/
|
|
53
|
+
update(change) {
|
|
54
|
+
return this.mutex.run("registry", () => withLock(this.lockDir, async () => {
|
|
55
|
+
const current = await this.read();
|
|
56
|
+
if (current.schemaVersion > REGISTRY_SCHEMA_VERSION) {
|
|
57
|
+
throw new AiCliError("REGISTRY_TOO_NEW", "the account registry was written by a newer EDUKORS version", {
|
|
58
|
+
meta: { found: current.schemaVersion, supported: REGISTRY_SCHEMA_VERSION },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const accounts = current.accounts.map((a) => ({ ...a }));
|
|
62
|
+
const result = change(accounts);
|
|
63
|
+
const next = { ...current, schemaVersion: REGISTRY_SCHEMA_VERSION, minReaderVersion: Math.max(1, current.minReaderVersion), accounts };
|
|
64
|
+
for (const account of next.accounts)
|
|
65
|
+
AccountRecordSchema.parse(account);
|
|
66
|
+
await this.write(next);
|
|
67
|
+
return result;
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
async read() {
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = await readFile(this.file, "utf8");
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
if (e.code === "ENOENT")
|
|
77
|
+
return empty();
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
let json;
|
|
81
|
+
try {
|
|
82
|
+
json = JSON.parse(raw);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
throw new AiCliError("REGISTRY_CORRUPT", "the account registry is not valid JSON", { cause: e, meta: { file: this.file } });
|
|
86
|
+
}
|
|
87
|
+
const version = json?.minReaderVersion;
|
|
88
|
+
if (typeof version === "number" && version > REGISTRY_SCHEMA_VERSION) {
|
|
89
|
+
throw new AiCliError("REGISTRY_TOO_NEW", "the account registry was written by a newer EDUKORS version", {
|
|
90
|
+
meta: { requires: version, supported: REGISTRY_SCHEMA_VERSION },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const parsed = RegistryFileSchema.safeParse(json);
|
|
94
|
+
if (!parsed.success)
|
|
95
|
+
throw new AiCliError("REGISTRY_CORRUPT", "the account registry has an invalid structure", { meta: { file: this.file, issue: z.prettifyError(parsed.error) } });
|
|
96
|
+
return parsed.data;
|
|
97
|
+
}
|
|
98
|
+
/** Write to a temporary file, flush, then rename over the original. */
|
|
99
|
+
async write(data) {
|
|
100
|
+
await mkdir(dirname(this.file), { recursive: true });
|
|
101
|
+
const tmp = join(dirname(this.file), `.${basename(this.file)}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
|
|
102
|
+
const handle = await open(tmp, "w", 0o600);
|
|
103
|
+
try {
|
|
104
|
+
await handle.writeFile(JSON.stringify(data, null, 2) + "\n");
|
|
105
|
+
await handle.sync();
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await handle.close();
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await rename(tmp, this.file);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
await rm(tmp, { force: true });
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { type SerializedError } from "./errors.js";
|
|
2
|
+
import type { AiEvent, AiEventListener, EventScope, SessionCloseReason } from "./events.js";
|
|
3
|
+
import type { ScopedLog } from "./diagnostics.js";
|
|
4
|
+
import type { Capabilities, ProviderAdapter, SessionContext, SessionOptions } from "./provider.js";
|
|
5
|
+
import type { ToolDefinition, ToolResult } from "./tools.js";
|
|
6
|
+
/**
|
|
7
|
+
* One conversation: one application, one document, one account.
|
|
8
|
+
*
|
|
9
|
+
* The session owns the identity rules the plan requires. Every request gets
|
|
10
|
+
* its own sink; once the request is interrupted, finished or replaced, that
|
|
11
|
+
* sink drops whatever the provider still sends, so late output never reaches
|
|
12
|
+
* the document. A provider that crashes is restarted on the next request,
|
|
13
|
+
* resuming its conversation when the provider supports it.
|
|
14
|
+
*/
|
|
15
|
+
export type SessionState = "opening" | "idle" | "busy" | "interrupting" | "failed" | "closed";
|
|
16
|
+
export interface TurnResult {
|
|
17
|
+
requestId: string;
|
|
18
|
+
status: "completed" | "cancelled" | "failed";
|
|
19
|
+
error?: SerializedError;
|
|
20
|
+
}
|
|
21
|
+
export interface SendOptions {
|
|
22
|
+
/** Supplied by the application to correlate events with its own records. */
|
|
23
|
+
requestId?: string;
|
|
24
|
+
/** Latest document state, including unsaved manual edits. */
|
|
25
|
+
context?: string;
|
|
26
|
+
}
|
|
27
|
+
/** What the application decides about a call to one of the user's MCP servers. */
|
|
28
|
+
export interface AuthorizationRequest {
|
|
29
|
+
kind: "mcp";
|
|
30
|
+
/** The `callId` of this call's toolStarted/toolCompleted events. */
|
|
31
|
+
callId: string;
|
|
32
|
+
server: string;
|
|
33
|
+
tool: string;
|
|
34
|
+
input: Record<string, unknown>;
|
|
35
|
+
documentId: string;
|
|
36
|
+
requestId: string;
|
|
37
|
+
signal: AbortSignal;
|
|
38
|
+
}
|
|
39
|
+
export type AuthorizationDecision = {
|
|
40
|
+
allow: true;
|
|
41
|
+
} | {
|
|
42
|
+
allow: false;
|
|
43
|
+
message?: string;
|
|
44
|
+
};
|
|
45
|
+
export type Authorizer = (request: AuthorizationRequest) => Promise<AuthorizationDecision> | AuthorizationDecision;
|
|
46
|
+
/** The user's MCP servers as the session reaches them (see mcp/external.ts). */
|
|
47
|
+
export interface ExternalCaller {
|
|
48
|
+
has(server: string, tool: string): boolean;
|
|
49
|
+
call(server: string, tool: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
50
|
+
}
|
|
51
|
+
export interface SessionDeps {
|
|
52
|
+
adapter: ProviderAdapter;
|
|
53
|
+
capabilities: Capabilities;
|
|
54
|
+
scope: Omit<EventScope, "requestId">;
|
|
55
|
+
/** Builds a fresh provider context (account home, work dir, tools). */
|
|
56
|
+
context: () => Promise<SessionContext>;
|
|
57
|
+
tools: ReadonlyMap<string, ToolDefinition>;
|
|
58
|
+
external?: ExternalCaller;
|
|
59
|
+
/** Without one, every call to a user's MCP server is denied. */
|
|
60
|
+
authorize?: Authorizer;
|
|
61
|
+
options: SessionOptions;
|
|
62
|
+
publish: (event: Omit<AiEvent, "seq" | "at">) => AiEvent;
|
|
63
|
+
log: ScopedLog;
|
|
64
|
+
interruptGraceMs: number;
|
|
65
|
+
onClosed: (session: AiSession) => void;
|
|
66
|
+
}
|
|
67
|
+
export declare class AiSession {
|
|
68
|
+
private readonly deps;
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly appId: string;
|
|
71
|
+
readonly documentId: string;
|
|
72
|
+
readonly accountId: string;
|
|
73
|
+
readonly providerId: string;
|
|
74
|
+
private _state;
|
|
75
|
+
private provider;
|
|
76
|
+
/** Identifies the current provider instance; hooks from older ones are ignored. */
|
|
77
|
+
private providerToken;
|
|
78
|
+
private _resumeId;
|
|
79
|
+
private turn;
|
|
80
|
+
private closing;
|
|
81
|
+
private options;
|
|
82
|
+
private readonly listeners;
|
|
83
|
+
private droppedEvents;
|
|
84
|
+
constructor(deps: SessionDeps);
|
|
85
|
+
get state(): SessionState;
|
|
86
|
+
/** The provider's own conversation id, for applications that persist it to resume later. */
|
|
87
|
+
get resumeId(): string | undefined;
|
|
88
|
+
/** Events of this session only. Returns an unsubscribe function. */
|
|
89
|
+
on(listener: AiEventListener): () => void;
|
|
90
|
+
/** Opens (or reopens) the provider session. Called by the library; reopening happens on demand. */
|
|
91
|
+
start(resumeId?: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Sends a request and resolves when it ends. Failures resolve with
|
|
94
|
+
* `status: "failed"` and are also emitted as `error` events; only misuse
|
|
95
|
+
* (a closed or busy session) rejects.
|
|
96
|
+
*/
|
|
97
|
+
send(text: string, options?: SendOptions): Promise<TurnResult>;
|
|
98
|
+
/**
|
|
99
|
+
* Stops the current request. Resolves once it has settled: already
|
|
100
|
+
* committed tool calls stay committed, nothing after this point is applied.
|
|
101
|
+
* A provider that does not stop within the grace period is shut down and
|
|
102
|
+
* restarted (resumed) on the next request.
|
|
103
|
+
*/
|
|
104
|
+
interrupt(): Promise<void>;
|
|
105
|
+
/** Answers a `question` event. */
|
|
106
|
+
answer(questionId: string, answer: string): void;
|
|
107
|
+
updateOptions(options: SessionOptions): Promise<void>;
|
|
108
|
+
close(reason?: SessionCloseReason): Promise<void>;
|
|
109
|
+
/** Counts provider output that arrived for a request no longer current. */
|
|
110
|
+
get lateEventsDropped(): number;
|
|
111
|
+
private newTurn;
|
|
112
|
+
private finish;
|
|
113
|
+
private dropProvider;
|
|
114
|
+
private providerExited;
|
|
115
|
+
private sinkFor;
|
|
116
|
+
private callTool;
|
|
117
|
+
private callExternal;
|
|
118
|
+
/** Reports the user's MCP servers once the session is open. */
|
|
119
|
+
reportServers(statuses: {
|
|
120
|
+
server: string;
|
|
121
|
+
status: "connected" | "failed";
|
|
122
|
+
tools?: number;
|
|
123
|
+
error?: string;
|
|
124
|
+
}[]): void;
|
|
125
|
+
private emit;
|
|
126
|
+
}
|