tokmon 0.28.1 → 0.28.3
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/README.md +156 -234
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/{bootstrap-ink-L7QSJBMS.js → bootstrap-ink-WG4WG3R2.js} +1579 -1135
- package/dist/chunk-7CH6S7BR.js +447 -0
- package/dist/{chunk-CVKCVHS7.js → chunk-CFKUZCMB.js} +70 -310
- package/dist/chunk-HNWEJ6MS.js +1094 -0
- package/dist/chunk-JZSZEHVD.js +406 -0
- package/dist/{chunk-S33XIUAW.js → chunk-P57DVFNH.js} +277 -49
- package/dist/{chunk-4HU4EI5T.js → chunk-P5JN5DDX.js} +25 -46
- package/dist/chunk-TN6V7XSL.js +512 -0
- package/dist/chunk-USKOQIE3.js +278 -0
- package/dist/chunk-UYPDMVW5.js +21 -0
- package/dist/{cli-command-75LP4IDS.js → cli-command-MD3RJRP4.js} +249 -15
- package/dist/cli.js +6 -6
- package/dist/{config-HXFJTNLM.js → config-3DGZ47F7.js} +23 -3
- package/dist/daemon-EMOA36WB.js +377 -0
- package/dist/daemon-handle-JHE5MKMI.js +11 -0
- package/dist/server-EPSH52DR.js +12 -0
- package/dist/web/assets/{Area-CMXvOw33.js → Area-stRnp8Km.js} +1 -1
- package/dist/web/assets/analytics-moZZt9L_.js +2 -0
- package/dist/web/assets/breakdown-km9w7myU.js +4 -0
- package/dist/web/assets/button-DrX04Cc2.js +1 -0
- package/dist/web/assets/{chart-cZoLgpmG.js → chart-D0CtuL2k.js} +1 -1
- package/dist/web/assets/explore-B11uNJV1.js +22 -0
- package/dist/web/assets/index-RDhXsCNG.js +83 -0
- package/dist/web/assets/index-ZmOS8p9O.css +1 -0
- package/dist/web/assets/models-DYbZnY34.js +2 -0
- package/dist/web/assets/overview-CruICbfJ.js +2 -0
- package/dist/web/assets/panel-xckM3_GC.js +1 -0
- package/dist/web/assets/settings-sheet-G-BTSIF6.js +1 -0
- package/dist/web/assets/share-sheet-weIdd4DW.js +2 -0
- package/dist/web/assets/timeline-BHgniliZ.js +1 -0
- package/dist/web/index.html +30 -4
- package/package.json +13 -4
- package/site/THIRD_PARTY_NOTICES.md +83 -0
- package/site/public/fonts/OFL.txt +93 -0
- package/dist/chunk-3RTWFGGD.js +0 -275
- package/dist/chunk-FMP3P2WV.js +0 -299
- package/dist/chunk-HP5UZCXP.js +0 -436
- package/dist/chunk-SMPY52EV.js +0 -125
- package/dist/chunk-XDA5RJST.js +0 -193
- package/dist/daemon-CL4FJW26.js +0 -219
- package/dist/daemon-handle-QQLJE46Y.js +0 -10
- package/dist/server-EUO7CWYH.js +0 -11
- package/dist/web/assets/analytics-DqdZXOJL.js +0 -2
- package/dist/web/assets/breakdown-kOSSJ3mI.js +0 -4
- package/dist/web/assets/explore-vcsM8K9I.js +0 -22
- package/dist/web/assets/index-BErCXT7f.css +0 -1
- package/dist/web/assets/index-DH3dOnbg.js +0 -83
- package/dist/web/assets/models-B5ikm83_.js +0 -2
- package/dist/web/assets/overview-DD1kP8CP.js +0 -2
- package/dist/web/assets/panel-DTQBwExW.js +0 -1
- package/dist/web/assets/primitives-C36qJlA2.js +0 -1
- package/dist/web/assets/settings-sheet-_i2zTQjf.js +0 -1
- package/dist/web/assets/share-sheet-BLHmrFbm.js +0 -2
- package/dist/web/assets/timeline-B4uG-Emo.js +0 -1
- package/dist/web/assets/use-dialog-trap-bfaXEMz9.js +0 -1
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ConfigUpdateConflictFailure,
|
|
4
|
+
TOKMON_WS_METHODS,
|
|
5
|
+
TOKMON_WS_PATH,
|
|
6
|
+
TokmonRpcGroup
|
|
7
|
+
} from "./chunk-JZSZEHVD.js";
|
|
8
|
+
import {
|
|
9
|
+
normalizeConfig
|
|
10
|
+
} from "./chunk-HNWEJ6MS.js";
|
|
11
|
+
|
|
12
|
+
// src/client/daemon-rpc-client.ts
|
|
13
|
+
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Schema, Stream } from "effect";
|
|
14
|
+
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
15
|
+
import { RpcClientError } from "effect/unstable/rpc/RpcClientError";
|
|
16
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
17
|
+
var DaemonRpcConnectionError = class extends Error {
|
|
18
|
+
constructor(message = "daemon RPC connection closed") {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "DaemonRpcConnectionError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var DaemonRpcRequestTimeoutError = class extends Error {
|
|
24
|
+
constructor(method, timeoutMs) {
|
|
25
|
+
super(`${method} timed out after ${timeoutMs}ms`);
|
|
26
|
+
this.method = method;
|
|
27
|
+
this.timeoutMs = timeoutMs;
|
|
28
|
+
this.name = "DaemonRpcRequestTimeoutError";
|
|
29
|
+
}
|
|
30
|
+
method;
|
|
31
|
+
timeoutMs;
|
|
32
|
+
};
|
|
33
|
+
var TokmonRpcClient = class extends Context.Service()(
|
|
34
|
+
"tokmon/client/DaemonRpcClient/TokmonRpcClient"
|
|
35
|
+
) {
|
|
36
|
+
};
|
|
37
|
+
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
38
|
+
function makeSignal() {
|
|
39
|
+
let settled = false;
|
|
40
|
+
let resolvePromise;
|
|
41
|
+
const promise = new Promise((resolve) => {
|
|
42
|
+
resolvePromise = resolve;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
promise,
|
|
46
|
+
resolve: (value) => {
|
|
47
|
+
if (settled) return;
|
|
48
|
+
settled = true;
|
|
49
|
+
resolvePromise(value);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function toWsUrl(baseUrl) {
|
|
54
|
+
const base = typeof window === "undefined" ? void 0 : window.location.origin;
|
|
55
|
+
const url = new URL(baseUrl, base);
|
|
56
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
57
|
+
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
58
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
59
|
+
throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
|
|
60
|
+
}
|
|
61
|
+
url.hash = "";
|
|
62
|
+
url.searchParams.delete("tokmonToken");
|
|
63
|
+
url.searchParams.delete("wsToken");
|
|
64
|
+
url.pathname = TOKMON_WS_PATH;
|
|
65
|
+
return url.toString();
|
|
66
|
+
}
|
|
67
|
+
function shouldUseNodeTransport(transport) {
|
|
68
|
+
if (transport === "node") return true;
|
|
69
|
+
if (transport === "browser") return false;
|
|
70
|
+
return typeof window === "undefined";
|
|
71
|
+
}
|
|
72
|
+
async function socketLayerFor(url, transport, openTimeoutMs) {
|
|
73
|
+
const socketOptions = { openTimeout: Duration.millis(openTimeoutMs) };
|
|
74
|
+
if (shouldUseNodeTransport(transport)) {
|
|
75
|
+
const NodeSocket = await dynamicImport("@effect/platform-node/NodeSocket");
|
|
76
|
+
return NodeSocket.layerWebSocket(url, socketOptions);
|
|
77
|
+
}
|
|
78
|
+
return Socket.layerWebSocket(url, socketOptions).pipe(
|
|
79
|
+
Layer.provide(Socket.layerWebSocketConstructorGlobal)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function normalizeConfigState(state) {
|
|
83
|
+
return {
|
|
84
|
+
protocol: {
|
|
85
|
+
version: state.protocol.version,
|
|
86
|
+
capabilities: [...state.protocol.capabilities]
|
|
87
|
+
},
|
|
88
|
+
config: normalizeConfig(state.config)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function normalizeSnapshot(snapshot) {
|
|
92
|
+
const copyHeadroom = (headroom) => headroom ? {
|
|
93
|
+
...headroom,
|
|
94
|
+
activeAccountIds: [...headroom.activeAccountIds],
|
|
95
|
+
factors: headroom.factors.map((factor) => ({ ...factor }))
|
|
96
|
+
} : void 0;
|
|
97
|
+
const copyRows = (rows) => rows.map((row) => ({
|
|
98
|
+
...row,
|
|
99
|
+
models: [...row.models],
|
|
100
|
+
breakdown: row.breakdown.map((detail) => ({ ...detail }))
|
|
101
|
+
}));
|
|
102
|
+
return {
|
|
103
|
+
...snapshot,
|
|
104
|
+
providers: snapshot.providers.map((provider) => ({
|
|
105
|
+
...provider,
|
|
106
|
+
headroom: copyHeadroom(provider.headroom)
|
|
107
|
+
})),
|
|
108
|
+
accounts: snapshot.accounts.map((account) => ({
|
|
109
|
+
...account,
|
|
110
|
+
identity: account.identity ? { ...account.identity } : void 0,
|
|
111
|
+
quotas: account.quotas?.map((quota) => ({ ...quota })),
|
|
112
|
+
headroom: copyHeadroom(account.headroom),
|
|
113
|
+
dashboard: account.dashboard ? {
|
|
114
|
+
...account.dashboard,
|
|
115
|
+
today: { ...account.dashboard.today },
|
|
116
|
+
week: { ...account.dashboard.week },
|
|
117
|
+
month: { ...account.dashboard.month },
|
|
118
|
+
series: [...account.dashboard.series]
|
|
119
|
+
} : null,
|
|
120
|
+
table: account.table ? {
|
|
121
|
+
daily: copyRows(account.table.daily),
|
|
122
|
+
weekly: copyRows(account.table.weekly),
|
|
123
|
+
monthly: copyRows(account.table.monthly)
|
|
124
|
+
} : null,
|
|
125
|
+
billing: account.billing ? {
|
|
126
|
+
...account.billing,
|
|
127
|
+
metrics: account.billing.metrics.map((metric) => ({
|
|
128
|
+
...metric,
|
|
129
|
+
format: { ...metric.format }
|
|
130
|
+
})),
|
|
131
|
+
activity: account.billing.activity ? { ...account.billing.activity, series: [...account.billing.activity.series] } : account.billing.activity,
|
|
132
|
+
modelSpend: account.billing.modelSpend == null ? account.billing.modelSpend : account.billing.modelSpend.map((spend) => ({ ...spend }))
|
|
133
|
+
} : null,
|
|
134
|
+
summaryUpdatedAt: account.summaryUpdatedAt ?? null,
|
|
135
|
+
billingUpdatedAt: account.billingUpdatedAt ?? null,
|
|
136
|
+
tableUpdatedAt: account.tableUpdatedAt ?? null
|
|
137
|
+
})),
|
|
138
|
+
peak: snapshot.peak ? { ...snapshot.peak } : null
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function normalizeRequestFailure(error) {
|
|
142
|
+
if (!error || typeof error !== "object" || error.kind !== "conflict") return error;
|
|
143
|
+
const state = error.state;
|
|
144
|
+
if (!state) return error;
|
|
145
|
+
const normalized = normalizeConfigState(state);
|
|
146
|
+
if (error instanceof ConfigUpdateConflictFailure) {
|
|
147
|
+
return new ConfigUpdateConflictFailure({ kind: "conflict", state: normalized });
|
|
148
|
+
}
|
|
149
|
+
return { ...error, state: normalized };
|
|
150
|
+
}
|
|
151
|
+
function isSessionFailure(error) {
|
|
152
|
+
return error instanceof RpcClientError || Schema.isSchemaError(error);
|
|
153
|
+
}
|
|
154
|
+
async function waitForReady(ready, disconnected, timeoutMs) {
|
|
155
|
+
let timer = null;
|
|
156
|
+
try {
|
|
157
|
+
await Promise.race([
|
|
158
|
+
ready.promise,
|
|
159
|
+
disconnected.promise.then((error) => Promise.reject(error)),
|
|
160
|
+
new Promise((_resolve, reject) => {
|
|
161
|
+
timer = setTimeout(() => reject(new DaemonRpcConnectionError(`daemon RPC connection timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
162
|
+
timer.unref?.();
|
|
163
|
+
})
|
|
164
|
+
]);
|
|
165
|
+
} finally {
|
|
166
|
+
if (timer) clearTimeout(timer);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function createDaemonRpcClient(baseUrl, options = {}) {
|
|
170
|
+
const url = toWsUrl(baseUrl);
|
|
171
|
+
const requestTimeoutMs = Math.max(1, options.requestTimeoutMs ?? 3e4);
|
|
172
|
+
const reconnectBaseDelayMs = Math.max(1, options.reconnectBaseDelayMs ?? 250);
|
|
173
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
174
|
+
let session = null;
|
|
175
|
+
let sessionPromise = null;
|
|
176
|
+
let pendingRuntime = null;
|
|
177
|
+
let supervisorPromise = null;
|
|
178
|
+
let reconnectTimer = null;
|
|
179
|
+
let reconnectAttemptsUsed = 0;
|
|
180
|
+
let hasConnected = false;
|
|
181
|
+
let closed = false;
|
|
182
|
+
const reportSubscriberError = (error) => {
|
|
183
|
+
if (options.onSubscriberError) {
|
|
184
|
+
try {
|
|
185
|
+
options.onSubscriberError(error);
|
|
186
|
+
} catch (reportError) {
|
|
187
|
+
console.error("[tokmon] daemon RPC subscriber error reporter failed", reportError);
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
console.error("[tokmon] daemon RPC subscriber failed", error);
|
|
192
|
+
};
|
|
193
|
+
const setConn = (state, error) => {
|
|
194
|
+
try {
|
|
195
|
+
options.onConn?.(state, error);
|
|
196
|
+
} catch (callbackError) {
|
|
197
|
+
reportSubscriberError(callbackError);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const clearReconnectTimer = () => {
|
|
201
|
+
if (!reconnectTimer) return;
|
|
202
|
+
clearTimeout(reconnectTimer);
|
|
203
|
+
reconnectTimer = null;
|
|
204
|
+
};
|
|
205
|
+
const stopSubscriptionFiber = (subscription) => {
|
|
206
|
+
const fiber = subscription.fiber;
|
|
207
|
+
subscription.fiber = null;
|
|
208
|
+
subscription.owner = null;
|
|
209
|
+
return fiber ? Effect.runPromise(Fiber.interrupt(fiber)).catch(() => {
|
|
210
|
+
}) : Promise.resolve();
|
|
211
|
+
};
|
|
212
|
+
const disposeSubscription = (subscription) => {
|
|
213
|
+
subscription.active = false;
|
|
214
|
+
subscriptions.delete(subscription);
|
|
215
|
+
if (subscription.watchdogTimer) {
|
|
216
|
+
clearInterval(subscription.watchdogTimer);
|
|
217
|
+
subscription.watchdogTimer = null;
|
|
218
|
+
}
|
|
219
|
+
return stopSubscriptionFiber(subscription);
|
|
220
|
+
};
|
|
221
|
+
const scheduleReconnect = () => {
|
|
222
|
+
if (closed || subscriptions.size === 0 || reconnectTimer || session || sessionPromise) return;
|
|
223
|
+
if (typeof options.reconnectAttempts === "number" && reconnectAttemptsUsed >= options.reconnectAttempts) return;
|
|
224
|
+
const delayMs = Math.min(2500, reconnectBaseDelayMs * 1.5 ** reconnectAttemptsUsed);
|
|
225
|
+
reconnectTimer = setTimeout(() => {
|
|
226
|
+
reconnectTimer = null;
|
|
227
|
+
reconnectAttemptsUsed++;
|
|
228
|
+
connectSubscriptions();
|
|
229
|
+
}, delayMs);
|
|
230
|
+
reconnectTimer.unref?.();
|
|
231
|
+
};
|
|
232
|
+
const invalidateSession = (active, state, error) => {
|
|
233
|
+
if (active.invalidated) return;
|
|
234
|
+
active.invalidated = true;
|
|
235
|
+
if (session === active) session = null;
|
|
236
|
+
void active.runtime.dispose().catch(() => {
|
|
237
|
+
});
|
|
238
|
+
if (!closed) setConn(state, error);
|
|
239
|
+
scheduleReconnect();
|
|
240
|
+
};
|
|
241
|
+
const makeProtocolLayer = async (ready, disconnected) => {
|
|
242
|
+
const socketLayer = await socketLayerFor(url, options.transport, requestTimeoutMs);
|
|
243
|
+
const connectionHooksLayer = Layer.succeed(
|
|
244
|
+
RpcClient.ConnectionHooks,
|
|
245
|
+
RpcClient.ConnectionHooks.of({
|
|
246
|
+
onConnect: Effect.sync(() => {
|
|
247
|
+
ready.resolve();
|
|
248
|
+
}),
|
|
249
|
+
onDisconnect: Effect.sync(() => {
|
|
250
|
+
disconnected.resolve(new DaemonRpcConnectionError());
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
return Layer.effect(
|
|
255
|
+
RpcClient.Protocol,
|
|
256
|
+
RpcClient.makeProtocolSocket({
|
|
257
|
+
retryPolicy: Schedule.recurs(0),
|
|
258
|
+
retryTransientErrors: false
|
|
259
|
+
})
|
|
260
|
+
).pipe(
|
|
261
|
+
Layer.provide(Layer.mergeAll(socketLayer, RpcSerialization.layerJson, connectionHooksLayer))
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
const ensureSession = async () => {
|
|
265
|
+
if (closed) throw new Error("daemon RPC client is closed");
|
|
266
|
+
if (session) return session;
|
|
267
|
+
if (sessionPromise) return sessionPromise;
|
|
268
|
+
setConn(hasConnected ? "reconnecting" : "connecting");
|
|
269
|
+
const pending = (async () => {
|
|
270
|
+
let runtime = null;
|
|
271
|
+
try {
|
|
272
|
+
const ready = makeSignal();
|
|
273
|
+
const disconnected = makeSignal();
|
|
274
|
+
const protocolLayer = await makeProtocolLayer(ready, disconnected);
|
|
275
|
+
const clientLayer = Layer.effect(
|
|
276
|
+
TokmonRpcClient,
|
|
277
|
+
RpcClient.make(TokmonRpcGroup)
|
|
278
|
+
).pipe(Layer.provide(protocolLayer));
|
|
279
|
+
runtime = ManagedRuntime.make(clientLayer);
|
|
280
|
+
pendingRuntime = runtime;
|
|
281
|
+
await runtime.runPromise(TokmonRpcClient.asEffect());
|
|
282
|
+
await waitForReady(ready, disconnected, requestTimeoutMs);
|
|
283
|
+
if (closed) throw new Error("daemon RPC client is closed");
|
|
284
|
+
const active = {
|
|
285
|
+
runtime,
|
|
286
|
+
disconnected: disconnected.promise,
|
|
287
|
+
invalidated: false
|
|
288
|
+
};
|
|
289
|
+
session = active;
|
|
290
|
+
pendingRuntime = null;
|
|
291
|
+
hasConnected = true;
|
|
292
|
+
reconnectAttemptsUsed = 0;
|
|
293
|
+
setConn("live");
|
|
294
|
+
void active.disconnected.then((error) => {
|
|
295
|
+
invalidateSession(active, "reconnecting", error);
|
|
296
|
+
});
|
|
297
|
+
startAllSubscriptions(active);
|
|
298
|
+
return active;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (pendingRuntime === runtime) pendingRuntime = null;
|
|
301
|
+
await runtime?.dispose().catch(() => {
|
|
302
|
+
});
|
|
303
|
+
if (!closed) setConn("error", error);
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
})();
|
|
307
|
+
sessionPromise = pending;
|
|
308
|
+
try {
|
|
309
|
+
return await pending;
|
|
310
|
+
} finally {
|
|
311
|
+
if (sessionPromise === pending) sessionPromise = null;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const run = async (method, effectFor) => {
|
|
315
|
+
const active = await ensureSession();
|
|
316
|
+
try {
|
|
317
|
+
return await active.runtime.runPromise(
|
|
318
|
+
TokmonRpcClient.use((client) => effectFor(client)).pipe(
|
|
319
|
+
Effect.timeoutOrElse({
|
|
320
|
+
duration: Duration.millis(requestTimeoutMs),
|
|
321
|
+
orElse: () => Effect.fail(new DaemonRpcRequestTimeoutError(method, requestTimeoutMs))
|
|
322
|
+
})
|
|
323
|
+
)
|
|
324
|
+
);
|
|
325
|
+
} catch (rawError) {
|
|
326
|
+
const error = normalizeRequestFailure(rawError);
|
|
327
|
+
if (!closed && isSessionFailure(error)) invalidateSession(active, "error", error);
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const startSubscription = (subscription, active) => {
|
|
332
|
+
if (closed || !subscription.active || subscription.fiber || active.invalidated) return;
|
|
333
|
+
subscription.lastValueAt = Date.now();
|
|
334
|
+
subscription.staleAfterMs = options.snapshotStaleFloorMs ?? 9e4;
|
|
335
|
+
subscription.owner = active;
|
|
336
|
+
const fiber = active.runtime.runFork(
|
|
337
|
+
TokmonRpcClient.use(
|
|
338
|
+
(client) => subscription.streamFor(client).pipe(
|
|
339
|
+
Stream.runForEach(
|
|
340
|
+
(value) => Effect.sync(() => {
|
|
341
|
+
subscription.lastValueAt = Date.now();
|
|
342
|
+
if (subscription.staleAfterFor) subscription.staleAfterMs = subscription.staleAfterFor(value);
|
|
343
|
+
try {
|
|
344
|
+
subscription.onValue(value);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
reportSubscriberError(error);
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
subscription.fiber = fiber;
|
|
354
|
+
fiber.addObserver((exit) => {
|
|
355
|
+
if (subscription.fiber === fiber) subscription.fiber = null;
|
|
356
|
+
if (subscription.owner === active) subscription.owner = null;
|
|
357
|
+
if (closed || !subscription.active || active.invalidated) return;
|
|
358
|
+
if (Exit.isSuccess(exit)) {
|
|
359
|
+
invalidateSession(active, "reconnecting");
|
|
360
|
+
} else {
|
|
361
|
+
invalidateSession(active, "error", Cause.squash(exit.cause));
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
const startAllSubscriptions = (active) => {
|
|
366
|
+
for (const subscription of subscriptions) startSubscription(subscription, active);
|
|
367
|
+
};
|
|
368
|
+
function connectSubscriptions() {
|
|
369
|
+
if (closed || subscriptions.size === 0 || supervisorPromise) return;
|
|
370
|
+
supervisorPromise = ensureSession().then(() => void 0).catch(() => {
|
|
371
|
+
scheduleReconnect();
|
|
372
|
+
}).finally(() => {
|
|
373
|
+
supervisorPromise = null;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const subscribe = (streamFor, onValue, staleAfterFor) => {
|
|
377
|
+
if (closed) return () => {
|
|
378
|
+
};
|
|
379
|
+
const subscription = {
|
|
380
|
+
streamFor,
|
|
381
|
+
onValue,
|
|
382
|
+
staleAfterFor,
|
|
383
|
+
active: true,
|
|
384
|
+
fiber: null,
|
|
385
|
+
owner: null,
|
|
386
|
+
watchdogTimer: null,
|
|
387
|
+
lastValueAt: 0,
|
|
388
|
+
staleAfterMs: Number.POSITIVE_INFINITY
|
|
389
|
+
};
|
|
390
|
+
subscriptions.add(subscription);
|
|
391
|
+
if (staleAfterFor) {
|
|
392
|
+
const checkEveryMs = Math.min(5e3, Math.max(10, (options.snapshotStaleFloorMs ?? 9e4) / 2));
|
|
393
|
+
subscription.watchdogTimer = setInterval(() => {
|
|
394
|
+
if (!subscription.fiber || !subscription.owner || subscription.lastValueAt === 0) return;
|
|
395
|
+
if (Date.now() - subscription.lastValueAt <= subscription.staleAfterMs) return;
|
|
396
|
+
subscription.lastValueAt = Date.now();
|
|
397
|
+
invalidateSession(subscription.owner, "reconnecting");
|
|
398
|
+
}, checkEveryMs);
|
|
399
|
+
subscription.watchdogTimer.unref?.();
|
|
400
|
+
}
|
|
401
|
+
if (session) startSubscription(subscription, session);
|
|
402
|
+
else connectSubscriptions();
|
|
403
|
+
return () => {
|
|
404
|
+
void disposeSubscription(subscription);
|
|
405
|
+
if (subscriptions.size === 0) clearReconnectTimer();
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
return {
|
|
409
|
+
getConfig: () => run(TOKMON_WS_METHODS.getConfig, (client) => client[TOKMON_WS_METHODS.getConfig]({})).then(normalizeConfigState),
|
|
410
|
+
setConfig: (update) => run(TOKMON_WS_METHODS.setConfig, (client) => client[TOKMON_WS_METHODS.setConfig](update)).then(normalizeConfigState),
|
|
411
|
+
refresh: (scope = "all") => run(TOKMON_WS_METHODS.refresh, (client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
|
|
412
|
+
browseFs: (path) => run(TOKMON_WS_METHODS.browseFs, (client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
|
|
413
|
+
subscribeSnapshot: (onSnapshot) => subscribe(
|
|
414
|
+
(client) => client[TOKMON_WS_METHODS.snapshot]({}).pipe(Stream.map(normalizeSnapshot)),
|
|
415
|
+
onSnapshot,
|
|
416
|
+
(snapshot) => Math.max(options.snapshotStaleFloorMs ?? 9e4, snapshot.intervalMs * 3)
|
|
417
|
+
),
|
|
418
|
+
subscribeConfig: (onConfig) => subscribe(
|
|
419
|
+
(client) => client[TOKMON_WS_METHODS.config]({}).pipe(Stream.map(normalizeConfigState)),
|
|
420
|
+
onConfig
|
|
421
|
+
),
|
|
422
|
+
async close() {
|
|
423
|
+
if (closed) return;
|
|
424
|
+
closed = true;
|
|
425
|
+
setConn("closed");
|
|
426
|
+
clearReconnectTimer();
|
|
427
|
+
const cleanup = [...subscriptions].map(disposeSubscription);
|
|
428
|
+
subscriptions.clear();
|
|
429
|
+
const activeSession = session;
|
|
430
|
+
session = null;
|
|
431
|
+
await pendingRuntime?.dispose().catch(() => {
|
|
432
|
+
});
|
|
433
|
+
await Promise.all(cleanup);
|
|
434
|
+
await activeSession?.runtime.dispose().catch(() => {
|
|
435
|
+
});
|
|
436
|
+
await sessionPromise?.catch(() => {
|
|
437
|
+
});
|
|
438
|
+
pendingRuntime = null;
|
|
439
|
+
sessionPromise = null;
|
|
440
|
+
supervisorPromise = null;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export {
|
|
446
|
+
createDaemonRpcClient
|
|
447
|
+
};
|