@workerdeck/server 2.8.0 → 2.10.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/build/index.d.mts +57 -8
- package/build/index.mjs +295 -24
- package/build/index.mjs.map +1 -1
- package/package.json +5 -5
package/build/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
-
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
2
|
+
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, CostLedgerState, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
3
3
|
import { JobQueue, QueueAdapter } from "@workerdeck/queue";
|
|
4
|
-
import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, ProfileUsage, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
4
|
+
import { ByModel, CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, ProfileSpend, ProfileUsage, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
5
5
|
//#region src/services/bridge.d.ts
|
|
6
6
|
type BridgeHubOptions = {
|
|
7
7
|
timeoutMs?: number;
|
|
@@ -29,21 +29,22 @@ declare class SessionNotifier {
|
|
|
29
29
|
#private;
|
|
30
30
|
constructor(options: SessionNotificationOptions);
|
|
31
31
|
get idle(): boolean;
|
|
32
|
-
watch(runner: Runner, afterSeq?: number): void;
|
|
32
|
+
watch(runner: Runner, afterSeq?: number): () => void;
|
|
33
33
|
}
|
|
34
34
|
//#endregion
|
|
35
35
|
//#region src/services/registry.d.ts
|
|
36
36
|
type SessionRegistryOptions = {
|
|
37
|
-
onRegister?: (runner: Runner) =>
|
|
37
|
+
onRegister?: (runner: Runner) => unknown;
|
|
38
38
|
};
|
|
39
39
|
declare class SessionRegistry {
|
|
40
40
|
#private;
|
|
41
41
|
constructor(options?: SessionRegistryOptions);
|
|
42
|
-
observe(listener: (runner: Runner) =>
|
|
42
|
+
observe(listener: (runner: Runner) => unknown): () => void;
|
|
43
43
|
create(config: SessionRunnerConfig): Runner;
|
|
44
44
|
prepare(config: SessionRunnerConfig): Runner;
|
|
45
45
|
adopt(runner: Runner): Runner;
|
|
46
46
|
register(runner: Runner): Runner;
|
|
47
|
+
retain(id: string, detach: () => void): void;
|
|
47
48
|
get(id: string): Runner | undefined;
|
|
48
49
|
list(): SessionInfo[];
|
|
49
50
|
remove(id: string): boolean;
|
|
@@ -60,6 +61,7 @@ type ParkedSessionRecord = {
|
|
|
60
61
|
config: SessionRunnerConfig;
|
|
61
62
|
snapshot: RunnerSnapshot;
|
|
62
63
|
executions: ParkedExecution[];
|
|
64
|
+
cost?: CostLedgerState;
|
|
63
65
|
parkedAt: number;
|
|
64
66
|
};
|
|
65
67
|
type DormantSessionRecord = {
|
|
@@ -69,9 +71,11 @@ type DormantSessionRecord = {
|
|
|
69
71
|
profile?: string;
|
|
70
72
|
config: SessionRunnerConfig;
|
|
71
73
|
sdkSessionId: string;
|
|
74
|
+
cost?: CostLedgerState;
|
|
72
75
|
savedAt: number;
|
|
73
76
|
};
|
|
74
77
|
type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord;
|
|
78
|
+
declare function isDormant(record: StoredSessionRecord): record is DormantSessionRecord;
|
|
75
79
|
interface SessionStore {
|
|
76
80
|
save(record: StoredSessionRecord): Promise<void>;
|
|
77
81
|
get(id: string): Promise<StoredSessionRecord | null>;
|
|
@@ -119,6 +123,9 @@ declare class SessionParkManager {
|
|
|
119
123
|
touch(runner: Runner): void;
|
|
120
124
|
hydrate(): Promise<void>;
|
|
121
125
|
watch(runner: Runner, afterSeq?: number): () => void;
|
|
126
|
+
release(sessionId: string): SessionRunnerConfig | undefined;
|
|
127
|
+
adopt(runner: Runner, config?: SessionRunnerConfig): void;
|
|
128
|
+
flush(sessionId?: string): Promise<void>;
|
|
122
129
|
onDetach(sessionId: string): void;
|
|
123
130
|
sessionFor(executionId: string): string | undefined;
|
|
124
131
|
get(id: string): Promise<StoredSessionRecord | null>;
|
|
@@ -141,6 +148,33 @@ type ProfileStore = {
|
|
|
141
148
|
declare function createMemoryProfileStore(seed?: ProfileInfo[]): ProfileStore;
|
|
142
149
|
declare function createFileProfileStore(path?: string): ProfileStore;
|
|
143
150
|
//#endregion
|
|
151
|
+
//#region src/services/spend-ledger.d.ts
|
|
152
|
+
type SpendRecord = {
|
|
153
|
+
days: Record<string, Record<string, ByModel>>;
|
|
154
|
+
};
|
|
155
|
+
type SpendStore = {
|
|
156
|
+
load(): Promise<SpendRecord | undefined>;
|
|
157
|
+
save(record: SpendRecord): Promise<void>;
|
|
158
|
+
};
|
|
159
|
+
type SpendLedgerOptions = {
|
|
160
|
+
store?: SpendStore;
|
|
161
|
+
monthlySubscriptionUsd?: (profile: string) => number | undefined;
|
|
162
|
+
retentionDays?: number;
|
|
163
|
+
saveDebounceMs?: number;
|
|
164
|
+
onError?: (error: unknown) => void;
|
|
165
|
+
};
|
|
166
|
+
declare function dayKey(at: number): string;
|
|
167
|
+
declare class SpendLedger {
|
|
168
|
+
#private;
|
|
169
|
+
constructor(options?: SpendLedgerOptions);
|
|
170
|
+
load(): Promise<void>;
|
|
171
|
+
watch(runner: Runner): () => void;
|
|
172
|
+
spend(profile: string, now?: number): ProfileSpend | undefined;
|
|
173
|
+
flush(): Promise<void>;
|
|
174
|
+
close(): void;
|
|
175
|
+
}
|
|
176
|
+
declare function createFileSpendStore(path: string, onError?: (error: unknown) => void): SpendStore;
|
|
177
|
+
//#endregion
|
|
144
178
|
//#region src/options.d.ts
|
|
145
179
|
type SdkSessionLister = (options: {
|
|
146
180
|
dir?: string;
|
|
@@ -198,6 +232,11 @@ type WorkerServerOptions = {
|
|
|
198
232
|
persistLive?: boolean;
|
|
199
233
|
onError?: (error: unknown, context: ParkErrorContext) => void;
|
|
200
234
|
};
|
|
235
|
+
spend?: {
|
|
236
|
+
store?: SpendStore;
|
|
237
|
+
monthlySubscriptionUsd?: Record<string, number>;
|
|
238
|
+
onError?: (error: unknown) => void;
|
|
239
|
+
};
|
|
201
240
|
createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>;
|
|
202
241
|
engines?: Partial<Record<ProfileEngine, EngineAdapter>>;
|
|
203
242
|
};
|
|
@@ -223,6 +262,10 @@ type QueueServerOptions = {
|
|
|
223
262
|
webhookRetryDelayMs?: number;
|
|
224
263
|
onEvent?: (event: JobEvent) => void;
|
|
225
264
|
};
|
|
265
|
+
type CarriedSession = {
|
|
266
|
+
runner: Runner;
|
|
267
|
+
config?: SessionRunnerConfig;
|
|
268
|
+
};
|
|
226
269
|
type DrainReport = {
|
|
227
270
|
working: string[];
|
|
228
271
|
awaitingHuman: string[];
|
|
@@ -243,6 +286,8 @@ type WorkerServer = {
|
|
|
243
286
|
port: number;
|
|
244
287
|
}>;
|
|
245
288
|
drain: (options?: DrainOptions) => Promise<DrainReport>;
|
|
289
|
+
releaseSession: (id: string) => CarriedSession | undefined;
|
|
290
|
+
adoptSession: (carried: CarriedSession) => boolean;
|
|
246
291
|
close: () => Promise<void>;
|
|
247
292
|
};
|
|
248
293
|
//#endregion
|
|
@@ -281,6 +326,10 @@ type ProviderRunnerOptions = {
|
|
|
281
326
|
};
|
|
282
327
|
declare function createProviderRunner(ctx: EngineRunnerContext, options: ProviderRunnerOptions): Promise<Runner>;
|
|
283
328
|
//#endregion
|
|
329
|
+
//#region src/lib/reload-plan.d.ts
|
|
330
|
+
type ReloadPlan = 'carry' | 'persist' | 'drop';
|
|
331
|
+
declare function reloadPlan(runner: Runner): ReloadPlan;
|
|
332
|
+
//#endregion
|
|
284
333
|
//#region src/services/attachments.d.ts
|
|
285
334
|
type AttachmentStoreOptions = {
|
|
286
335
|
maxFileBytes?: number;
|
|
@@ -332,7 +381,7 @@ type ProducedFile = {
|
|
|
332
381
|
};
|
|
333
382
|
declare class ProducedFileStore {
|
|
334
383
|
#private;
|
|
335
|
-
watch(runner: Runner): void;
|
|
384
|
+
watch(runner: Runner): () => void;
|
|
336
385
|
get(sessionId: string, fileId: string): ProducedFile | undefined;
|
|
337
386
|
list(sessionId: string): ProducedFile[];
|
|
338
387
|
drop(sessionId: string): void;
|
|
@@ -341,9 +390,9 @@ declare class ProducedFileStore {
|
|
|
341
390
|
//#region src/services/profile-usage.d.ts
|
|
342
391
|
declare class ProfileUsageTracker {
|
|
343
392
|
#private;
|
|
344
|
-
watch(runner: Runner): void;
|
|
393
|
+
watch(runner: Runner): () => void;
|
|
345
394
|
usage(profile: string, now?: number): ProfileUsage | undefined;
|
|
346
395
|
}
|
|
347
396
|
//#endregion
|
|
348
|
-
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, sandboxedProviderProfile, toDurableRecord };
|
|
397
|
+
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type CarriedSession, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type ReloadPlan, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, SpendLedger, type SpendRecord, type SpendStore, type StoredSessionRecord, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
|
|
349
398
|
//# sourceMappingURL=index.d.mts.map
|
package/build/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createServer } from "node:http";
|
|
|
2
2
|
import { WebSocketServer } from "ws";
|
|
3
3
|
import { BrowserBridgeExecutor, SessionRunner, attachmentKind, checkClaudeAuth, createEngineSession, getEngineAdapter, normalizeMediaType } from "@workerdeck/core";
|
|
4
4
|
import { JobQueue } from "@workerdeck/queue";
|
|
5
|
-
import { ENGINE_CAPABILITIES, PROTOCOL_VERSION, SHELL_COMMAND_MAX, imagePartRef, sessionState, supportsPermissionMode } from "@workerdeck/protocol";
|
|
5
|
+
import { ENGINE_CAPABILITIES, PROTOCOL_VERSION, SHELL_COMMAND_MAX, costOfByModel, imagePartRef, isJobRun, mergeByModel, sessionState, subscriptionComparison, supportsPermissionMode } from "@workerdeck/protocol";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { closeSync, constants, createReadStream, existsSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { constants as constants$1, homedir } from "node:os";
|
|
@@ -211,6 +211,15 @@ function parseSessionRoute(basePath, url) {
|
|
|
211
211
|
return null;
|
|
212
212
|
}
|
|
213
213
|
//#endregion
|
|
214
|
+
//#region src/lib/reload-plan.ts
|
|
215
|
+
function reloadPlan(runner) {
|
|
216
|
+
const info = runner.info();
|
|
217
|
+
if (info.status === "closed" || info.status === "failed") return "drop";
|
|
218
|
+
if (isJobRun(info)) return "drop";
|
|
219
|
+
if (runner.snapshot !== void 0) return "persist";
|
|
220
|
+
return "carry";
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
214
223
|
//#region src/routes/executions.ts
|
|
215
224
|
async function handleExecutionResult(ctx, req, res, pathname, auth) {
|
|
216
225
|
const { auth: authSvc, basePath, parking, registry } = ctx;
|
|
@@ -526,7 +535,7 @@ function subsequenceScore(haystack, needle) {
|
|
|
526
535
|
return score - haystack.length / 100;
|
|
527
536
|
}
|
|
528
537
|
//#endregion
|
|
529
|
-
//#region src/routes/
|
|
538
|
+
//#region src/routes/fs.ts
|
|
530
539
|
function kindRank(type) {
|
|
531
540
|
return type === "dir" ? 0 : 1;
|
|
532
541
|
}
|
|
@@ -1808,6 +1817,7 @@ function attachClient(ctx, ws, runner, req, access) {
|
|
|
1808
1817
|
imageRefs
|
|
1809
1818
|
});
|
|
1810
1819
|
const detachBridge = bridge.attach(runner.id, send);
|
|
1820
|
+
runner.refreshUsage?.().catch(() => {});
|
|
1811
1821
|
ws.on("message", (data) => {
|
|
1812
1822
|
let frame;
|
|
1813
1823
|
try {
|
|
@@ -2209,8 +2219,8 @@ var SessionNotifier = class {
|
|
|
2209
2219
|
return !this.#options.webhook && !this.#options.onNotification;
|
|
2210
2220
|
}
|
|
2211
2221
|
watch(runner, afterSeq = runner.info().lastSeq) {
|
|
2212
|
-
if (this.idle) return;
|
|
2213
|
-
runner.subscribe((event) => {
|
|
2222
|
+
if (this.idle) return () => {};
|
|
2223
|
+
return runner.subscribe((event) => {
|
|
2214
2224
|
switch (event.type) {
|
|
2215
2225
|
case "permission_requested":
|
|
2216
2226
|
this.#emit(runner, event.seq, event.ts, {
|
|
@@ -2301,6 +2311,7 @@ var SessionParkManager = class {
|
|
|
2301
2311
|
#configs = /* @__PURE__ */ new Map();
|
|
2302
2312
|
#remembered = /* @__PURE__ */ new Set();
|
|
2303
2313
|
#storeOps = /* @__PURE__ */ new Map();
|
|
2314
|
+
#watches = /* @__PURE__ */ new Map();
|
|
2304
2315
|
#closed = false;
|
|
2305
2316
|
constructor(options) {
|
|
2306
2317
|
this.#options = options;
|
|
@@ -2320,7 +2331,8 @@ var SessionParkManager = class {
|
|
|
2320
2331
|
}
|
|
2321
2332
|
}
|
|
2322
2333
|
watch(runner, afterSeq = 0) {
|
|
2323
|
-
|
|
2334
|
+
this.#watches.get(runner.id)?.();
|
|
2335
|
+
const unsubscribe = runner.subscribe((event) => {
|
|
2324
2336
|
switch (event.type) {
|
|
2325
2337
|
case "execution_dispatched":
|
|
2326
2338
|
if (!event.deferred) return;
|
|
@@ -2360,6 +2372,35 @@ var SessionParkManager = class {
|
|
|
2360
2372
|
default: return;
|
|
2361
2373
|
}
|
|
2362
2374
|
}, afterSeq);
|
|
2375
|
+
const detach = () => {
|
|
2376
|
+
if (this.#watches.get(runner.id) === detach) this.#watches.delete(runner.id);
|
|
2377
|
+
unsubscribe();
|
|
2378
|
+
};
|
|
2379
|
+
this.#watches.set(runner.id, detach);
|
|
2380
|
+
return detach;
|
|
2381
|
+
}
|
|
2382
|
+
release(sessionId) {
|
|
2383
|
+
this.#watches.get(sessionId)?.();
|
|
2384
|
+
this.#watches.delete(sessionId);
|
|
2385
|
+
clearTimeout(this.#detachTimers.get(sessionId));
|
|
2386
|
+
this.#detachTimers.delete(sessionId);
|
|
2387
|
+
const config = this.#configs.get(sessionId);
|
|
2388
|
+
this.#configs.delete(sessionId);
|
|
2389
|
+
this.#remembered.delete(sessionId);
|
|
2390
|
+
return config;
|
|
2391
|
+
}
|
|
2392
|
+
adopt(runner, config) {
|
|
2393
|
+
this.#options.registry.register(runner);
|
|
2394
|
+
if (config) this.remember(runner.id, config);
|
|
2395
|
+
this.watch(runner, runner.info().lastSeq);
|
|
2396
|
+
this.touch(runner);
|
|
2397
|
+
}
|
|
2398
|
+
async flush(sessionId) {
|
|
2399
|
+
if (sessionId === void 0) {
|
|
2400
|
+
await Promise.all(this.#storeOps.values());
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
await this.#storeOps.get(sessionId);
|
|
2363
2404
|
}
|
|
2364
2405
|
onDetach(sessionId) {
|
|
2365
2406
|
if (this.#closed) return;
|
|
@@ -2411,6 +2452,7 @@ var SessionParkManager = class {
|
|
|
2411
2452
|
};
|
|
2412
2453
|
}
|
|
2413
2454
|
async discard(sessionId) {
|
|
2455
|
+
this.#watches.delete(sessionId);
|
|
2414
2456
|
clearTimeout(this.#detachTimers.get(sessionId));
|
|
2415
2457
|
this.#detachTimers.delete(sessionId);
|
|
2416
2458
|
this.#configs.delete(sessionId);
|
|
@@ -2451,6 +2493,7 @@ var SessionParkManager = class {
|
|
|
2451
2493
|
meta: info.meta
|
|
2452
2494
|
},
|
|
2453
2495
|
sdkSessionId,
|
|
2496
|
+
cost: runner.costState?.(),
|
|
2454
2497
|
savedAt: Date.now()
|
|
2455
2498
|
};
|
|
2456
2499
|
try {
|
|
@@ -2498,6 +2541,7 @@ var SessionParkManager = class {
|
|
|
2498
2541
|
meta: info.meta
|
|
2499
2542
|
},
|
|
2500
2543
|
snapshot,
|
|
2544
|
+
cost: runner.costState?.(),
|
|
2501
2545
|
executions: snapshot.parked,
|
|
2502
2546
|
parkedAt: Date.now()
|
|
2503
2547
|
};
|
|
@@ -2521,6 +2565,7 @@ var SessionParkManager = class {
|
|
|
2521
2565
|
const config = this.#configs.get(id);
|
|
2522
2566
|
if (!config) return;
|
|
2523
2567
|
if (this.#options.onParking && !this.#options.onParking(id, executions[0])) return;
|
|
2568
|
+
const cost = runner.costState?.();
|
|
2524
2569
|
const snapshot = runner.park();
|
|
2525
2570
|
if (!snapshot) return;
|
|
2526
2571
|
const info = {
|
|
@@ -2536,6 +2581,7 @@ var SessionParkManager = class {
|
|
|
2536
2581
|
config,
|
|
2537
2582
|
snapshot,
|
|
2538
2583
|
executions: snapshot.parked,
|
|
2584
|
+
cost,
|
|
2539
2585
|
parkedAt: Date.now()
|
|
2540
2586
|
};
|
|
2541
2587
|
for (const execution of snapshot.parked) this.#track(id, execution);
|
|
@@ -2581,6 +2627,10 @@ var SessionParkManager = class {
|
|
|
2581
2627
|
});
|
|
2582
2628
|
throw error;
|
|
2583
2629
|
}
|
|
2630
|
+
runner.carryCost?.(record.cost ?? {
|
|
2631
|
+
byModel: record.info.usageByModel,
|
|
2632
|
+
reportedCostUsd: record.info.totalCostUsd
|
|
2633
|
+
});
|
|
2584
2634
|
this.#options.registry.register(runner);
|
|
2585
2635
|
this.remember(id, {
|
|
2586
2636
|
...record.config,
|
|
@@ -2639,7 +2689,7 @@ var SessionParkManager = class {
|
|
|
2639
2689
|
var ProducedFileStore = class {
|
|
2640
2690
|
#bySession = /* @__PURE__ */ new Map();
|
|
2641
2691
|
watch(runner) {
|
|
2642
|
-
runner.subscribe((event) => {
|
|
2692
|
+
return runner.subscribe((event) => {
|
|
2643
2693
|
if (event.type !== "file_produced") return;
|
|
2644
2694
|
const held = this.#bySession.get(runner.id) ?? /* @__PURE__ */ new Map();
|
|
2645
2695
|
held.set(event.fileId, {
|
|
@@ -2723,6 +2773,8 @@ var ProfileService = class {
|
|
|
2723
2773
|
}
|
|
2724
2774
|
const usage = decorate.usage(p.name);
|
|
2725
2775
|
if (usage) base.usage = usage;
|
|
2776
|
+
const spend = decorate.spend?.(p.name);
|
|
2777
|
+
if (spend) base.spend = spend;
|
|
2726
2778
|
return base;
|
|
2727
2779
|
}
|
|
2728
2780
|
all() {
|
|
@@ -2790,8 +2842,8 @@ var ProfileUsageTracker = class {
|
|
|
2790
2842
|
#profiles = /* @__PURE__ */ new Map();
|
|
2791
2843
|
watch(runner) {
|
|
2792
2844
|
const profile = runner.info().profile;
|
|
2793
|
-
if (!profile) return;
|
|
2794
|
-
runner.subscribe((event) => {
|
|
2845
|
+
if (!profile) return () => {};
|
|
2846
|
+
return runner.subscribe((event) => {
|
|
2795
2847
|
if (event.type !== "rate_limit") return;
|
|
2796
2848
|
const type = event.info.rateLimitType;
|
|
2797
2849
|
if (!type) return;
|
|
@@ -2830,17 +2882,166 @@ function serveWindow(held, now) {
|
|
|
2830
2882
|
};
|
|
2831
2883
|
}
|
|
2832
2884
|
//#endregion
|
|
2885
|
+
//#region src/services/spend-ledger.ts
|
|
2886
|
+
const DEFAULT_RETENTION_DAYS = 92;
|
|
2887
|
+
const DEFAULT_SAVE_DEBOUNCE_MS = 2e3;
|
|
2888
|
+
function dayKey(at) {
|
|
2889
|
+
const d = new Date(at);
|
|
2890
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
2891
|
+
}
|
|
2892
|
+
function daysBack(now, count) {
|
|
2893
|
+
const out = [];
|
|
2894
|
+
for (let i = 0; i < count; i += 1) out.push(dayKey(now - i * 864e5));
|
|
2895
|
+
return out;
|
|
2896
|
+
}
|
|
2897
|
+
function subtract(current, previous) {
|
|
2898
|
+
const delta = {};
|
|
2899
|
+
for (const [model, usage] of Object.entries(current)) {
|
|
2900
|
+
const was = previous[model];
|
|
2901
|
+
const row = {
|
|
2902
|
+
input: usage.input - (was?.input ?? 0),
|
|
2903
|
+
output: usage.output - (was?.output ?? 0),
|
|
2904
|
+
cacheWrite5m: usage.cacheWrite5m - (was?.cacheWrite5m ?? 0),
|
|
2905
|
+
cacheWrite1h: usage.cacheWrite1h - (was?.cacheWrite1h ?? 0),
|
|
2906
|
+
cacheRead: usage.cacheRead - (was?.cacheRead ?? 0)
|
|
2907
|
+
};
|
|
2908
|
+
if (Object.values(row).some((value) => value < 0)) continue;
|
|
2909
|
+
if (Object.values(row).some((value) => value > 0)) delta[model] = row;
|
|
2910
|
+
}
|
|
2911
|
+
return delta;
|
|
2912
|
+
}
|
|
2913
|
+
var SpendLedger = class {
|
|
2914
|
+
#days = {};
|
|
2915
|
+
#lastBanked = /* @__PURE__ */ new Map();
|
|
2916
|
+
#options;
|
|
2917
|
+
#saveTimer;
|
|
2918
|
+
#loaded;
|
|
2919
|
+
constructor(options = {}) {
|
|
2920
|
+
this.#options = options;
|
|
2921
|
+
}
|
|
2922
|
+
load() {
|
|
2923
|
+
this.#loaded ??= this.#load();
|
|
2924
|
+
return this.#loaded;
|
|
2925
|
+
}
|
|
2926
|
+
async #load() {
|
|
2927
|
+
const store = this.#options.store;
|
|
2928
|
+
if (!store) return;
|
|
2929
|
+
try {
|
|
2930
|
+
const record = await store.load();
|
|
2931
|
+
if (record?.days) this.#days = record.days;
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
this.#options.onError?.(error);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
watch(runner) {
|
|
2937
|
+
const info = runner.info();
|
|
2938
|
+
const profile = info.profile;
|
|
2939
|
+
if (!profile) return () => {};
|
|
2940
|
+
this.#lastBanked.set(runner.id, info.usageByModel ?? {});
|
|
2941
|
+
const unsubscribe = runner.subscribe((event) => {
|
|
2942
|
+
if (event.type !== "turn_result" || !event.usageByModel) return;
|
|
2943
|
+
this.#bank(profile, event.usageByModel, runner.id, event.ts);
|
|
2944
|
+
});
|
|
2945
|
+
return () => {
|
|
2946
|
+
unsubscribe();
|
|
2947
|
+
this.#lastBanked.delete(runner.id);
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
#bank(profile, cumulative, sessionId, at) {
|
|
2951
|
+
const delta = subtract(cumulative, this.#lastBanked.get(sessionId) ?? {});
|
|
2952
|
+
this.#lastBanked.set(sessionId, cumulative);
|
|
2953
|
+
if (Object.keys(delta).length === 0) return;
|
|
2954
|
+
const key = dayKey(at);
|
|
2955
|
+
const day = this.#days[key] ??= {};
|
|
2956
|
+
day[profile] = mergeByModel(day[profile] ?? {}, delta);
|
|
2957
|
+
this.#scheduleSave();
|
|
2958
|
+
}
|
|
2959
|
+
spend(profile, now = Date.now()) {
|
|
2960
|
+
const week = this.#window(profile, daysBack(now, 7));
|
|
2961
|
+
const month = this.#window(profile, daysBack(now, 30));
|
|
2962
|
+
if (Object.keys(month).length === 0) return;
|
|
2963
|
+
const weekCost = costOfByModel(week);
|
|
2964
|
+
const monthlySubscriptionUsd = this.#options.monthlySubscriptionUsd?.(profile);
|
|
2965
|
+
return {
|
|
2966
|
+
weekUsd: weekCost.total,
|
|
2967
|
+
monthUsd: costOfByModel(month).total,
|
|
2968
|
+
weekByModel: week,
|
|
2969
|
+
unpricedShare: weekCost.unpricedShare,
|
|
2970
|
+
monthlySubscriptionUsd,
|
|
2971
|
+
subscription: monthlySubscriptionUsd ? subscriptionComparison(weekCost.total, monthlySubscriptionUsd) : void 0
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
#window(profile, keys) {
|
|
2975
|
+
let out = {};
|
|
2976
|
+
for (const key of keys) {
|
|
2977
|
+
const day = this.#days[key]?.[profile];
|
|
2978
|
+
if (day) out = mergeByModel(out, day);
|
|
2979
|
+
}
|
|
2980
|
+
return out;
|
|
2981
|
+
}
|
|
2982
|
+
#scheduleSave() {
|
|
2983
|
+
if (!this.#options.store || this.#saveTimer) return;
|
|
2984
|
+
this.#saveTimer = setTimeout(() => {
|
|
2985
|
+
this.#saveTimer = void 0;
|
|
2986
|
+
this.flush();
|
|
2987
|
+
}, this.#options.saveDebounceMs ?? DEFAULT_SAVE_DEBOUNCE_MS);
|
|
2988
|
+
this.#saveTimer.unref?.();
|
|
2989
|
+
}
|
|
2990
|
+
async flush() {
|
|
2991
|
+
const store = this.#options.store;
|
|
2992
|
+
if (!store) return;
|
|
2993
|
+
this.#prune();
|
|
2994
|
+
try {
|
|
2995
|
+
await store.save({ days: this.#days });
|
|
2996
|
+
} catch (error) {
|
|
2997
|
+
this.#options.onError?.(error);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
#prune() {
|
|
3001
|
+
const keep = new Set(daysBack(Date.now(), this.#options.retentionDays ?? DEFAULT_RETENTION_DAYS));
|
|
3002
|
+
for (const key of Object.keys(this.#days)) if (!keep.has(key)) delete this.#days[key];
|
|
3003
|
+
}
|
|
3004
|
+
close() {
|
|
3005
|
+
clearTimeout(this.#saveTimer);
|
|
3006
|
+
this.#saveTimer = void 0;
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
function createFileSpendStore(path, onError) {
|
|
3010
|
+
return {
|
|
3011
|
+
async load() {
|
|
3012
|
+
try {
|
|
3013
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
3014
|
+
if (parsed === null || typeof parsed !== "object" || typeof parsed.days !== "object") return;
|
|
3015
|
+
return parsed;
|
|
3016
|
+
} catch {
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
},
|
|
3020
|
+
async save(record) {
|
|
3021
|
+
try {
|
|
3022
|
+
await mkdir(dirname(path), { recursive: true });
|
|
3023
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
3024
|
+
await writeFile(temp, JSON.stringify(record));
|
|
3025
|
+
await rename(temp, path);
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
onError?.(error);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
//#endregion
|
|
2833
3033
|
//#region src/services/registry.ts
|
|
2834
3034
|
var SessionRegistry = class {
|
|
2835
3035
|
#sessions = /* @__PURE__ */ new Map();
|
|
2836
3036
|
#options;
|
|
2837
3037
|
#observers = /* @__PURE__ */ new Set();
|
|
3038
|
+
#detachers = /* @__PURE__ */ new Map();
|
|
2838
3039
|
constructor(options = {}) {
|
|
2839
3040
|
this.#options = options;
|
|
2840
3041
|
}
|
|
2841
3042
|
observe(listener) {
|
|
2842
3043
|
this.#observers.add(listener);
|
|
2843
|
-
for (const runner of this.#sessions.values()) listener(runner);
|
|
3044
|
+
for (const runner of this.#sessions.values()) this.#retain(runner.id, listener(runner));
|
|
2844
3045
|
return () => void this.#observers.delete(listener);
|
|
2845
3046
|
}
|
|
2846
3047
|
create(config) {
|
|
@@ -2858,11 +3059,14 @@ var SessionRegistry = class {
|
|
|
2858
3059
|
const existing = this.#sessions.get(runner.id);
|
|
2859
3060
|
this.#sessions.set(runner.id, runner);
|
|
2860
3061
|
if (existing !== runner) {
|
|
2861
|
-
this.#options.onRegister?.(runner);
|
|
2862
|
-
for (const listener of this.#observers) listener(runner);
|
|
3062
|
+
this.#retain(runner.id, this.#options.onRegister?.(runner));
|
|
3063
|
+
for (const listener of this.#observers) this.#retain(runner.id, listener(runner));
|
|
2863
3064
|
}
|
|
2864
3065
|
return runner;
|
|
2865
3066
|
}
|
|
3067
|
+
retain(id, detach) {
|
|
3068
|
+
this.#retain(id, detach);
|
|
3069
|
+
}
|
|
2866
3070
|
get(id) {
|
|
2867
3071
|
return this.#sessions.get(id);
|
|
2868
3072
|
}
|
|
@@ -2873,14 +3077,30 @@ var SessionRegistry = class {
|
|
|
2873
3077
|
const runner = this.#sessions.get(id);
|
|
2874
3078
|
if (!runner) return false;
|
|
2875
3079
|
runner.close("server");
|
|
3080
|
+
this.#detach(id);
|
|
2876
3081
|
return this.#sessions.delete(id);
|
|
2877
3082
|
}
|
|
2878
3083
|
evict(id) {
|
|
3084
|
+
this.#detach(id);
|
|
2879
3085
|
return this.#sessions.delete(id);
|
|
2880
3086
|
}
|
|
2881
3087
|
closeAll() {
|
|
2882
3088
|
for (const runner of this.#sessions.values()) runner.close("server");
|
|
2883
3089
|
}
|
|
3090
|
+
#retain(id, detach) {
|
|
3091
|
+
if (typeof detach !== "function") return;
|
|
3092
|
+
const run = detach;
|
|
3093
|
+
const existing = this.#detachers.get(id);
|
|
3094
|
+
if (existing) {
|
|
3095
|
+
existing.push(run);
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
this.#detachers.set(id, [run]);
|
|
3099
|
+
}
|
|
3100
|
+
#detach(id) {
|
|
3101
|
+
for (const detach of this.#detachers.get(id) ?? []) detach();
|
|
3102
|
+
this.#detachers.delete(id);
|
|
3103
|
+
}
|
|
2884
3104
|
};
|
|
2885
3105
|
//#endregion
|
|
2886
3106
|
//#region src/services/session-factory.ts
|
|
@@ -3052,7 +3272,7 @@ function createSessionFactory(deps) {
|
|
|
3052
3272
|
};
|
|
3053
3273
|
const watchAuthSource = (runner) => {
|
|
3054
3274
|
let seen = false;
|
|
3055
|
-
runner.subscribe((event) => {
|
|
3275
|
+
return runner.subscribe((event) => {
|
|
3056
3276
|
if (seen || event.type !== "system_init") return;
|
|
3057
3277
|
seen = true;
|
|
3058
3278
|
if (event.apiKeySource !== "oauth") return;
|
|
@@ -3084,6 +3304,8 @@ function createSessionFactory(deps) {
|
|
|
3084
3304
|
//#endregion
|
|
3085
3305
|
//#region src/server.ts
|
|
3086
3306
|
const SOCKET_CLOSE_GRACE_MS = 250;
|
|
3307
|
+
const CLOSE_DEADLINE_MS = 1e3;
|
|
3308
|
+
const USAGE_STALE_MS = 3e5;
|
|
3087
3309
|
function surveyDrain(registry) {
|
|
3088
3310
|
const working = [];
|
|
3089
3311
|
const awaitingHuman = [];
|
|
@@ -3110,6 +3332,25 @@ function createWorkerServer(options = {}) {
|
|
|
3110
3332
|
const adapterFor = (engine) => options.engines?.[engine ?? "claude"] ?? getEngineAdapter(engine);
|
|
3111
3333
|
const profileDefaultModels = /* @__PURE__ */ new Map();
|
|
3112
3334
|
const profileUsage = new ProfileUsageTracker();
|
|
3335
|
+
const spendLedger = new SpendLedger({
|
|
3336
|
+
store: options.spend?.store,
|
|
3337
|
+
monthlySubscriptionUsd: (name) => options.spend?.monthlySubscriptionUsd?.[name] ?? options.spend?.monthlySubscriptionUsd?.["*"],
|
|
3338
|
+
onError: (error) => options.spend?.onError?.(error)
|
|
3339
|
+
});
|
|
3340
|
+
spendLedger.load();
|
|
3341
|
+
const refreshStaleUsage = (name) => {
|
|
3342
|
+
const held = profileUsage.usage(name);
|
|
3343
|
+
const newest = Math.max(0, ...Object.values(held ?? {}).map((window) => window.updatedAt ?? 0));
|
|
3344
|
+
if (Date.now() - newest < USAGE_STALE_MS) return;
|
|
3345
|
+
for (const info of refs.registry?.list() ?? []) {
|
|
3346
|
+
if (info.profile !== name) continue;
|
|
3347
|
+
const runner = refs.registry?.get(info.id);
|
|
3348
|
+
if (runner?.refreshUsage) {
|
|
3349
|
+
runner.refreshUsage().catch(() => {});
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3113
3354
|
const detected = options.profiles ? [] : detectDefaultProfiles();
|
|
3114
3355
|
const profiles = new ProfileService({
|
|
3115
3356
|
declared: options.profiles ?? (options.profileStore ? [] : detected),
|
|
@@ -3122,7 +3363,11 @@ function createWorkerServer(options = {}) {
|
|
|
3122
3363
|
decorate: {
|
|
3123
3364
|
defaultModel: (name) => profileDefaultModels.get(name),
|
|
3124
3365
|
availability: (name) => availability.get(name),
|
|
3125
|
-
usage: (name) =>
|
|
3366
|
+
usage: (name) => {
|
|
3367
|
+
refreshStaleUsage(name);
|
|
3368
|
+
return profileUsage.usage(name);
|
|
3369
|
+
},
|
|
3370
|
+
spend: (name) => spendLedger.spend(name)
|
|
3126
3371
|
}
|
|
3127
3372
|
});
|
|
3128
3373
|
for (const p of options.profiles ?? []) {
|
|
@@ -3155,16 +3400,21 @@ function createWorkerServer(options = {}) {
|
|
|
3155
3400
|
const producedFiles = new ProducedFileStore();
|
|
3156
3401
|
const shell = options.shell?.enabled === true ? createShellService(options.shell) : null;
|
|
3157
3402
|
const registry = new SessionRegistry({ onRegister: (runner) => {
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3403
|
+
const detachers = [
|
|
3404
|
+
notifier.watch(runner),
|
|
3405
|
+
producedFiles.watch(runner),
|
|
3406
|
+
profileUsage.watch(runner),
|
|
3407
|
+
spendLedger.watch(runner),
|
|
3408
|
+
shell?.watch(runner)
|
|
3409
|
+
];
|
|
3162
3410
|
const profile = runner.info().profile;
|
|
3163
|
-
if (
|
|
3164
|
-
runner.subscribe((event) => {
|
|
3411
|
+
if (profile) detachers.push(runner.subscribe((event) => {
|
|
3165
3412
|
if (event.type !== "capabilities" || !event.defaultModel) return;
|
|
3166
3413
|
profileDefaultModels.set(profile, event.defaultModel);
|
|
3167
|
-
});
|
|
3414
|
+
}));
|
|
3415
|
+
return () => {
|
|
3416
|
+
for (const detach of detachers) detach?.();
|
|
3417
|
+
};
|
|
3168
3418
|
} });
|
|
3169
3419
|
const attachmentStore = new AttachmentStore(options.attachments);
|
|
3170
3420
|
const bridge = new BridgeHub({
|
|
@@ -3445,6 +3695,24 @@ function createWorkerServer(options = {}) {
|
|
|
3445
3695
|
});
|
|
3446
3696
|
});
|
|
3447
3697
|
},
|
|
3698
|
+
releaseSession: (id) => {
|
|
3699
|
+
const runner = registry.get(id);
|
|
3700
|
+
if (!runner) return;
|
|
3701
|
+
const plan = reloadPlan(runner);
|
|
3702
|
+
if (plan !== "carry") throw new Error(`session ${id} cannot be carried by identity (${plan})`);
|
|
3703
|
+
const config = parking.release(id);
|
|
3704
|
+
registry.evict(id);
|
|
3705
|
+
return {
|
|
3706
|
+
runner,
|
|
3707
|
+
config
|
|
3708
|
+
};
|
|
3709
|
+
},
|
|
3710
|
+
adoptSession: ({ runner, config }) => {
|
|
3711
|
+
if (reloadPlan(runner) !== "carry") return false;
|
|
3712
|
+
parking.adopt(runner, config);
|
|
3713
|
+
registry.retain(runner.id, factory.watchAuthSource(runner));
|
|
3714
|
+
return true;
|
|
3715
|
+
},
|
|
3448
3716
|
drain: async (drainOptions = {}) => {
|
|
3449
3717
|
const { timeoutMs = 3e4, pollMs = 250, onProgress } = drainOptions;
|
|
3450
3718
|
draining = true;
|
|
@@ -3477,12 +3745,15 @@ function createWorkerServer(options = {}) {
|
|
|
3477
3745
|
for (const ws of wss.clients) ws.terminate();
|
|
3478
3746
|
}, SOCKET_CLOSE_GRACE_MS);
|
|
3479
3747
|
force.unref();
|
|
3480
|
-
|
|
3481
|
-
|
|
3748
|
+
const deadline = setTimeout(() => resolve(), CLOSE_DEADLINE_MS);
|
|
3749
|
+
deadline.unref();
|
|
3750
|
+
const drained = Promise.all([new Promise((done) => wss.close(() => done())), new Promise((done) => server.close(() => done()))]);
|
|
3751
|
+
server.closeAllConnections();
|
|
3752
|
+
drained.then(() => {
|
|
3482
3753
|
clearTimeout(force);
|
|
3754
|
+
clearTimeout(deadline);
|
|
3483
3755
|
resolve();
|
|
3484
3756
|
});
|
|
3485
|
-
server.closeAllConnections();
|
|
3486
3757
|
});
|
|
3487
3758
|
return closing;
|
|
3488
3759
|
}
|
|
@@ -3579,6 +3850,6 @@ function createFileProfileStore(path = join(process.cwd(), ".workerdeck", "profi
|
|
|
3579
3850
|
};
|
|
3580
3851
|
}
|
|
3581
3852
|
//#endregion
|
|
3582
|
-
export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SessionNotifier, SessionParkManager, SessionRegistry, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, sandboxedProviderProfile, toDurableRecord };
|
|
3853
|
+
export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SessionNotifier, SessionParkManager, SessionRegistry, SpendLedger, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
|
|
3583
3854
|
|
|
3584
3855
|
//# sourceMappingURL=index.mjs.map
|