@velum-labs/routekit-daemon 0.9.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 +201 -0
- package/README.md +13 -0
- package/dist/account-transaction.d.ts +46 -0
- package/dist/account-transaction.js +223 -0
- package/dist/call-attribution-store.d.ts +16 -0
- package/dist/call-attribution-store.js +116 -0
- package/dist/cliproxy-sidecar.d.ts +17 -0
- package/dist/cliproxy-sidecar.js +148 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +1190 -0
- package/dist/test/account-transaction.test.d.ts +1 -0
- package/dist/test/account-transaction.test.js +184 -0
- package/dist/test/call-attribution-store.test.d.ts +1 -0
- package/dist/test/call-attribution-store.test.js +73 -0
- package/dist/test/daemon.test.d.ts +1 -0
- package/dist/test/daemon.test.js +1022 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { markAccountTransactionCommitted, prepareAccountTransaction, recoverAccountTransactions } from "../account-transaction.js";
|
|
8
|
+
function nativeCredential(kind, prefix) {
|
|
9
|
+
return kind === "claude-code"
|
|
10
|
+
? `${JSON.stringify({
|
|
11
|
+
claudeAiOauth: {
|
|
12
|
+
accessToken: `${prefix}-access`,
|
|
13
|
+
refreshToken: `${prefix}-refresh`
|
|
14
|
+
}
|
|
15
|
+
})}\n`
|
|
16
|
+
: `${JSON.stringify({
|
|
17
|
+
tokens: {
|
|
18
|
+
access_token: `${prefix}-access`,
|
|
19
|
+
refresh_token: `${prefix}-refresh`
|
|
20
|
+
}
|
|
21
|
+
})}\n`;
|
|
22
|
+
}
|
|
23
|
+
function assertPreparedNativeTransactionRestores(kind) {
|
|
24
|
+
const root = mkdtempSync(join(tmpdir(), "routekit-account-transaction-"));
|
|
25
|
+
const home = join(root, "state");
|
|
26
|
+
const account = join(home, "subscriptions", kind, "work.json");
|
|
27
|
+
const config = join(root, "router.yaml");
|
|
28
|
+
const revisions = join(home, "daemon-revisions.json");
|
|
29
|
+
const oldAccount = nativeCredential(kind, "old");
|
|
30
|
+
const oldConfig = "providers:\n openai: {}\n";
|
|
31
|
+
const oldRevisions = '{"config":2,"accounts":3,"daemon":1}\n';
|
|
32
|
+
mkdirSync(join(home, "subscriptions", kind), { recursive: true });
|
|
33
|
+
writeFileSync(account, oldAccount, { mode: 0o600 });
|
|
34
|
+
writeFileSync(config, oldConfig, { mode: 0o600 });
|
|
35
|
+
writeFileSync(revisions, oldRevisions, { mode: 0o600 });
|
|
36
|
+
try {
|
|
37
|
+
const transaction = prepareAccountTransaction({
|
|
38
|
+
home,
|
|
39
|
+
configPath: config,
|
|
40
|
+
accountPaths: [account],
|
|
41
|
+
kind,
|
|
42
|
+
provider: kind,
|
|
43
|
+
labels: ["work"]
|
|
44
|
+
});
|
|
45
|
+
const journal = readFileSync(join(transaction.directory, "transaction.json"), "utf8");
|
|
46
|
+
assert.doesNotMatch(journal, /old-access|old-refresh|accessToken|refreshToken|access_token|refresh_token/);
|
|
47
|
+
writeFileSync(account, nativeCredential(kind, "new"));
|
|
48
|
+
writeFileSync(config, `providers:\n openai: {}\n ${kind}: {}\n`);
|
|
49
|
+
writeFileSync(revisions, '{"config":3,"accounts":4,"daemon":1}\n');
|
|
50
|
+
assert.deepEqual(recoverAccountTransactions(home), {
|
|
51
|
+
recovered: 1,
|
|
52
|
+
cleaned: 0
|
|
53
|
+
});
|
|
54
|
+
assert.equal(readFileSync(account, "utf8"), oldAccount);
|
|
55
|
+
assert.equal(readFileSync(config, "utf8"), oldConfig);
|
|
56
|
+
assert.equal(readFileSync(revisions, "utf8"), oldRevisions);
|
|
57
|
+
assert.equal(statSync(account).mode & 0o777, 0o600);
|
|
58
|
+
assert.equal(existsSync(join(home, "account-transactions")), false);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
rmSync(root, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
test("prepared account transactions restore exact prior files without secrets in the journal", () => {
|
|
65
|
+
assertPreparedNativeTransactionRestores("codex");
|
|
66
|
+
});
|
|
67
|
+
test("prepared Claude transactions restore exact prior files without secrets in the journal", () => {
|
|
68
|
+
assertPreparedNativeTransactionRestores("claude-code");
|
|
69
|
+
});
|
|
70
|
+
test("committed transactions preserve new state and only clean rollback data", () => {
|
|
71
|
+
const root = mkdtempSync(join(tmpdir(), "routekit-account-transaction-"));
|
|
72
|
+
const home = join(root, "state");
|
|
73
|
+
const account = join(home, "cliproxy", "auth", "xai-user.json");
|
|
74
|
+
const config = join(root, "router.yaml");
|
|
75
|
+
mkdirSync(home, { recursive: true });
|
|
76
|
+
writeFileSync(config, "providers: {}\n");
|
|
77
|
+
try {
|
|
78
|
+
const transaction = prepareAccountTransaction({
|
|
79
|
+
home,
|
|
80
|
+
configPath: config,
|
|
81
|
+
accountPaths: [account],
|
|
82
|
+
kind: "grok",
|
|
83
|
+
provider: "cliproxy",
|
|
84
|
+
labels: ["xai-user"]
|
|
85
|
+
});
|
|
86
|
+
mkdirSync(join(home, "cliproxy", "auth"), { recursive: true });
|
|
87
|
+
writeFileSync(account, '{"type":"xai","access_token":"new-access"}\n');
|
|
88
|
+
writeFileSync(config, "providers:\n cliproxy: {}\n");
|
|
89
|
+
markAccountTransactionCommitted(transaction);
|
|
90
|
+
assert.deepEqual(recoverAccountTransactions(home), {
|
|
91
|
+
recovered: 0,
|
|
92
|
+
cleaned: 1
|
|
93
|
+
});
|
|
94
|
+
assert.match(readFileSync(account, "utf8"), /new-access/);
|
|
95
|
+
assert.match(readFileSync(config, "utf8"), /cliproxy/);
|
|
96
|
+
assert.equal(existsSync(join(home, "account-transactions")), false);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
rmSync(root, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
test("orphaned pre-prepare transaction directories are safe cleanup only", () => {
|
|
103
|
+
const root = mkdtempSync(join(tmpdir(), "routekit-account-transaction-"));
|
|
104
|
+
const home = join(root, "state");
|
|
105
|
+
const config = join(root, "router.yaml");
|
|
106
|
+
const orphan = join(home, "account-transactions", "orphan");
|
|
107
|
+
mkdirSync(orphan, { recursive: true });
|
|
108
|
+
writeFileSync(join(orphan, "backup-0.bin"), "opaque");
|
|
109
|
+
writeFileSync(config, "providers: {}\n");
|
|
110
|
+
try {
|
|
111
|
+
assert.deepEqual(recoverAccountTransactions(home), {
|
|
112
|
+
recovered: 0,
|
|
113
|
+
cleaned: 1
|
|
114
|
+
});
|
|
115
|
+
assert.deepEqual(existsSync(join(home, "account-transactions"))
|
|
116
|
+
? readdirSync(join(home, "account-transactions"))
|
|
117
|
+
: [], []);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
rmSync(root, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
async function assertNativeSigkillRecovery(kind) {
|
|
124
|
+
const root = mkdtempSync(join(tmpdir(), "routekit-account-sigkill-"));
|
|
125
|
+
const home = join(root, "state");
|
|
126
|
+
const account = join(home, "subscriptions", kind, "killed.json");
|
|
127
|
+
const config = join(root, "router.yaml");
|
|
128
|
+
const oldConfig = "providers:\n openai: {}\n";
|
|
129
|
+
writeFileSync(config, oldConfig);
|
|
130
|
+
const moduleUrl = new URL("../account-transaction.js", import.meta.url).href;
|
|
131
|
+
const input = JSON.stringify({
|
|
132
|
+
home,
|
|
133
|
+
account,
|
|
134
|
+
config,
|
|
135
|
+
kind,
|
|
136
|
+
credential: nativeCredential(kind, "killed")
|
|
137
|
+
});
|
|
138
|
+
const script = `
|
|
139
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
140
|
+
import { dirname } from "node:path";
|
|
141
|
+
import { prepareAccountTransaction } from ${JSON.stringify(moduleUrl)};
|
|
142
|
+
const input = JSON.parse(process.env.ROUTEKIT_TX_INPUT);
|
|
143
|
+
prepareAccountTransaction({
|
|
144
|
+
home: input.home,
|
|
145
|
+
configPath: input.config,
|
|
146
|
+
accountPaths: [input.account],
|
|
147
|
+
kind: input.kind,
|
|
148
|
+
provider: input.kind,
|
|
149
|
+
labels: ["killed"]
|
|
150
|
+
});
|
|
151
|
+
mkdirSync(dirname(input.account), { recursive: true });
|
|
152
|
+
writeFileSync(input.account, input.credential);
|
|
153
|
+
writeFileSync(input.config, 'providers:\\n openai: {}\\n ' + input.kind + ': {}\\n');
|
|
154
|
+
process.kill(process.pid, "SIGKILL");
|
|
155
|
+
`;
|
|
156
|
+
try {
|
|
157
|
+
const child = spawn(process.execPath, ["--input-type=module", "-e", script], {
|
|
158
|
+
env: { ...process.env, ROUTEKIT_TX_INPUT: input },
|
|
159
|
+
stdio: "ignore"
|
|
160
|
+
});
|
|
161
|
+
const result = await new Promise((resolve, reject) => {
|
|
162
|
+
child.once("error", reject);
|
|
163
|
+
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
164
|
+
});
|
|
165
|
+
assert.equal(result.code, null);
|
|
166
|
+
assert.equal(result.signal, "SIGKILL");
|
|
167
|
+
assert.equal(existsSync(account), true);
|
|
168
|
+
assert.deepEqual(recoverAccountTransactions(home), {
|
|
169
|
+
recovered: 1,
|
|
170
|
+
cleaned: 0
|
|
171
|
+
});
|
|
172
|
+
assert.equal(existsSync(account), false);
|
|
173
|
+
assert.equal(readFileSync(config, "utf8"), oldConfig);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
rmSync(root, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
test("SIGKILL after account/config writes is rolled back from the prepared journal", async () => {
|
|
180
|
+
await assertNativeSigkillRecovery("codex");
|
|
181
|
+
});
|
|
182
|
+
test("SIGKILL after Claude account/config writes is rolled back from the prepared journal", async () => {
|
|
183
|
+
await assertNativeSigkillRecovery("claude-code");
|
|
184
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { CallAttributionStore, callInspection } from "../call-attribution-store.js";
|
|
4
|
+
function modelCall(callId, seat = "seat_0123456789abcdef") {
|
|
5
|
+
return {
|
|
6
|
+
call_id: callId,
|
|
7
|
+
endpoint_id: "codex/gpt-5.3-codex",
|
|
8
|
+
model: "codex/gpt-5.3-codex",
|
|
9
|
+
request_hash: "sha256:request",
|
|
10
|
+
response_hash: "sha256:response",
|
|
11
|
+
messages: [{ role: "user", content: "sha256:message" }],
|
|
12
|
+
status: "succeeded",
|
|
13
|
+
side_effects: "none",
|
|
14
|
+
started_at: "2026-07-22T00:00:00.000Z",
|
|
15
|
+
finished_at: "2026-07-22T00:00:01.000Z",
|
|
16
|
+
latency_ms: 1_000,
|
|
17
|
+
usage: {
|
|
18
|
+
prompt_tokens: 10,
|
|
19
|
+
completion_tokens: 5,
|
|
20
|
+
total_tokens: 15
|
|
21
|
+
},
|
|
22
|
+
metadata: {
|
|
23
|
+
attribution: {
|
|
24
|
+
effective_model: "codex/gpt-5.3-codex",
|
|
25
|
+
native_model: "gpt-5.3-codex",
|
|
26
|
+
provider: "codex",
|
|
27
|
+
billing_mode: "subscription",
|
|
28
|
+
account: { seat },
|
|
29
|
+
attempts: 3,
|
|
30
|
+
retries: 2,
|
|
31
|
+
account_failovers: 1
|
|
32
|
+
},
|
|
33
|
+
unknown_usage: false,
|
|
34
|
+
unknown_cost: false,
|
|
35
|
+
cost_estimate_usd: 0.001,
|
|
36
|
+
credential: "must-not-be-returned",
|
|
37
|
+
source_path: "/secret/account.json"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
test("call inspection exposes attribution while dropping sensitive metadata", () => {
|
|
42
|
+
const inspection = callInspection(modelCall("model_call_safe"));
|
|
43
|
+
assert.ok(inspection);
|
|
44
|
+
assert.equal(inspection.effectiveModel, "codex/gpt-5.3-codex");
|
|
45
|
+
assert.equal(inspection.account?.seat, "seat_0123456789abcdef");
|
|
46
|
+
assert.deepEqual(inspection.retries, {
|
|
47
|
+
attempts: 3,
|
|
48
|
+
total: 2,
|
|
49
|
+
accountFailovers: 1
|
|
50
|
+
});
|
|
51
|
+
assert.equal(inspection.cost.estimateUsd, 0.001);
|
|
52
|
+
assert.doesNotMatch(JSON.stringify(inspection), /must-not-be-returned/);
|
|
53
|
+
assert.doesNotMatch(JSON.stringify(inspection), /secret\/account/);
|
|
54
|
+
assert.equal("messages" in inspection, false);
|
|
55
|
+
});
|
|
56
|
+
test("call attribution store evicts by capacity and expiry", () => {
|
|
57
|
+
let now = 0;
|
|
58
|
+
const store = new CallAttributionStore({
|
|
59
|
+
limit: 2,
|
|
60
|
+
ttlMs: 100,
|
|
61
|
+
now: () => now
|
|
62
|
+
});
|
|
63
|
+
store.onModelCall(modelCall("call_1"));
|
|
64
|
+
now = 10;
|
|
65
|
+
store.onModelCall(modelCall("call_2"));
|
|
66
|
+
now = 20;
|
|
67
|
+
store.onModelCall(modelCall("call_3"));
|
|
68
|
+
assert.equal(store.get("call_1"), undefined);
|
|
69
|
+
assert.ok(store.get("call_2"));
|
|
70
|
+
now = 111;
|
|
71
|
+
assert.equal(store.get("call_2"), undefined);
|
|
72
|
+
assert.ok(store.get("call_3"));
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|