@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,148 @@
|
|
|
1
|
+
import { CLIPROXY_API_KEY_ENV, CLIPROXY_BASE_URL_ENV, CLIPROXY_PINNED_VERSION, cliproxyApiKey, cliproxyBaseUrl, cliproxyBinaryPath, spawnCliproxy } from "@velum-labs/routekit-accounts";
|
|
2
|
+
const RESPAWN_DELAY_MS = 2_000;
|
|
3
|
+
const READY_TIMEOUT_MS = 8_000;
|
|
4
|
+
const READY_POLL_MS = 250;
|
|
5
|
+
const STOP_GRACE_MS = 5_000;
|
|
6
|
+
/** The sidecar is managed here unless an external proxy URL is configured. */
|
|
7
|
+
export function cliproxyManagedLocally(env) {
|
|
8
|
+
return (env[CLIPROXY_BASE_URL_ENV] ?? "").length === 0;
|
|
9
|
+
}
|
|
10
|
+
export function createCliproxySidecar(input) {
|
|
11
|
+
const env = input.env;
|
|
12
|
+
const log = input.log ?? ((line) => void process.stderr.write(`${line}\n`));
|
|
13
|
+
let child;
|
|
14
|
+
let wanted = false;
|
|
15
|
+
let closed = false;
|
|
16
|
+
let stopping = false;
|
|
17
|
+
let respawnTimer;
|
|
18
|
+
const reachable = async (timeoutMs = 1_500) => {
|
|
19
|
+
const key = env[CLIPROXY_API_KEY_ENV] ?? cliproxyApiKey(env) ?? "";
|
|
20
|
+
try {
|
|
21
|
+
// Any HTTP answer (including 401/403) proves the listener is up.
|
|
22
|
+
await fetch(`${cliproxyBaseUrl(env)}/v1/models`, {
|
|
23
|
+
headers: { authorization: `Bearer ${key}` },
|
|
24
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
function scheduleRespawn() {
|
|
33
|
+
if (closed || !wanted || stopping || respawnTimer !== undefined)
|
|
34
|
+
return;
|
|
35
|
+
respawnTimer = setTimeout(() => {
|
|
36
|
+
respawnTimer = undefined;
|
|
37
|
+
spawnOnce();
|
|
38
|
+
}, RESPAWN_DELAY_MS);
|
|
39
|
+
respawnTimer.unref();
|
|
40
|
+
}
|
|
41
|
+
function spawnOnce() {
|
|
42
|
+
if (closed || !wanted || child !== undefined)
|
|
43
|
+
return;
|
|
44
|
+
let spawned;
|
|
45
|
+
try {
|
|
46
|
+
spawned = spawnCliproxy(env, { stdio: "ignore" });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
log(`routekit cliproxy sidecar failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
scheduleRespawn();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
child = spawned;
|
|
54
|
+
spawned.once("error", (error) => {
|
|
55
|
+
if (child === spawned)
|
|
56
|
+
child = undefined;
|
|
57
|
+
log(`routekit cliproxy sidecar error: ${error.message}`);
|
|
58
|
+
scheduleRespawn();
|
|
59
|
+
});
|
|
60
|
+
spawned.once("exit", (code, signal) => {
|
|
61
|
+
if (child === spawned)
|
|
62
|
+
child = undefined;
|
|
63
|
+
if (closed || !wanted || stopping)
|
|
64
|
+
return;
|
|
65
|
+
log(`routekit cliproxy sidecar exited (${signal ?? `code ${code ?? "unknown"}`}); restarting`);
|
|
66
|
+
scheduleRespawn();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const stop = async () => {
|
|
70
|
+
if (respawnTimer !== undefined) {
|
|
71
|
+
clearTimeout(respawnTimer);
|
|
72
|
+
respawnTimer = undefined;
|
|
73
|
+
}
|
|
74
|
+
const current = child;
|
|
75
|
+
if (current === undefined)
|
|
76
|
+
return;
|
|
77
|
+
child = undefined;
|
|
78
|
+
stopping = true;
|
|
79
|
+
try {
|
|
80
|
+
await new Promise((resolve) => {
|
|
81
|
+
const killTimer = setTimeout(() => {
|
|
82
|
+
current.kill("SIGKILL");
|
|
83
|
+
}, STOP_GRACE_MS);
|
|
84
|
+
killTimer.unref();
|
|
85
|
+
current.once("exit", () => {
|
|
86
|
+
clearTimeout(killTimer);
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
89
|
+
if (!current.kill("SIGTERM")) {
|
|
90
|
+
clearTimeout(killTimer);
|
|
91
|
+
resolve();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
stopping = false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const waitUntilReady = async () => {
|
|
100
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
101
|
+
while (Date.now() < deadline) {
|
|
102
|
+
if (await reachable(READY_POLL_MS * 2))
|
|
103
|
+
return;
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, READY_POLL_MS));
|
|
105
|
+
}
|
|
106
|
+
// Force an unhealthy child through the normal crash-recovery path. Spawn
|
|
107
|
+
// failures already leave a retry timer armed.
|
|
108
|
+
child?.kill("SIGKILL");
|
|
109
|
+
throw new Error("routekit cliproxy sidecar did not answer within its readiness window");
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
reconcile: async (next) => {
|
|
113
|
+
if (closed)
|
|
114
|
+
return;
|
|
115
|
+
const installable = cliproxyManagedLocally(env) &&
|
|
116
|
+
cliproxyBinaryPath(CLIPROXY_PINNED_VERSION, env) !== undefined;
|
|
117
|
+
wanted = next && installable;
|
|
118
|
+
if (!wanted) {
|
|
119
|
+
await stop();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (child === undefined)
|
|
123
|
+
spawnOnce();
|
|
124
|
+
// Always wait for readiness — including after a crash respawn left a
|
|
125
|
+
// child handle before the listener was accepting connections.
|
|
126
|
+
await waitUntilReady();
|
|
127
|
+
},
|
|
128
|
+
refresh: async () => {
|
|
129
|
+
if (closed ||
|
|
130
|
+
!wanted ||
|
|
131
|
+
!cliproxyManagedLocally(env) ||
|
|
132
|
+
cliproxyBinaryPath(CLIPROXY_PINNED_VERSION, env) === undefined) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await stop();
|
|
136
|
+
spawnOnce();
|
|
137
|
+
await waitUntilReady();
|
|
138
|
+
},
|
|
139
|
+
running: () => child !== undefined,
|
|
140
|
+
managed: () => cliproxyManagedLocally(env),
|
|
141
|
+
reachable,
|
|
142
|
+
close: async () => {
|
|
143
|
+
closed = true;
|
|
144
|
+
wanted = false;
|
|
145
|
+
await stop();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ServiceRecord } from "@velum-labs/routekit-runtime";
|
|
2
|
+
export declare const ROUTEKIT_DAEMON_KIND = "daemon";
|
|
3
|
+
export declare const ROUTEKIT_PRODUCT = "routekit";
|
|
4
|
+
export type RouteKitDaemonOptions = {
|
|
5
|
+
packageVersion: string;
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
stateHome?: string;
|
|
8
|
+
configPath?: string;
|
|
9
|
+
host?: string;
|
|
10
|
+
port?: number;
|
|
11
|
+
authToken?: string;
|
|
12
|
+
authTokenFile?: string;
|
|
13
|
+
portless?: boolean;
|
|
14
|
+
drainGraceMs?: number;
|
|
15
|
+
onShutdownRequested?: (reason: "stop" | "restart" | "upgrade") => void;
|
|
16
|
+
/** Test seam used by child-process interruption coverage. */
|
|
17
|
+
onAccountTransactionPhase?: (phase: "prepared" | "credentials-written" | "router-swapped" | "committed") => void;
|
|
18
|
+
};
|
|
19
|
+
export type RunningRouteKitDaemon = {
|
|
20
|
+
record: ServiceRecord;
|
|
21
|
+
dataUrl: string;
|
|
22
|
+
controlUrl: string;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
reload(): Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
export declare function startRouteKitDaemon(options: RouteKitDaemonOptions): Promise<RunningRouteKitDaemon>;
|