@workerdeck/server 0.13.0 → 0.16.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/README.md +26 -0
- package/build/index.d.mts +422 -91
- package/build/index.mjs +2863 -2146
- package/build/index.mjs.map +1 -1
- package/package.json +5 -5
package/build/index.d.mts
CHANGED
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
-
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, EngineAdapter, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionResult } from "@workerdeck/core";
|
|
2
|
+
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
3
3
|
import { JobQueue, QueueAdapter } from "@workerdeck/queue";
|
|
4
|
-
import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, SdkSessionSummary, ServerFrame, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
4
|
+
import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, ProfileUsage, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
7
|
-
type
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
*/
|
|
13
|
-
onRegister?: (runner: Runner) => void;
|
|
6
|
+
//#region src/services/bridge.d.ts
|
|
7
|
+
type BridgeHubOptions = {
|
|
8
|
+
/** How long a bridged call may stay unanswered before it fails. Default 60000. */timeoutMs?: number;
|
|
9
|
+
/** Called when a bridged execution reaches a terminal result — the host feeds
|
|
10
|
+
* it back into the runner's loop. */
|
|
11
|
+
onResult?: (sessionId: string, executionId: string, result: ToolExecutionResult) => void;
|
|
14
12
|
};
|
|
15
|
-
/**
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Routes tool executions between a session and the browser tabs attached to it.
|
|
15
|
+
*
|
|
16
|
+
* A session may have several clients attached (dashboard plus embedded panel);
|
|
17
|
+
* the bridge asks the **first attached** one, which is the closest thing to "the
|
|
18
|
+
* client driving this session". If none is attached, dispatch fails fast rather
|
|
19
|
+
* than hanging — an autonomous job simply never bridges, it uses the server
|
|
20
|
+
* executor instead.
|
|
21
|
+
*/
|
|
22
|
+
declare class BridgeHub {
|
|
17
23
|
#private;
|
|
18
|
-
constructor(options?:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
closeAll(): void;
|
|
24
|
+
constructor(options?: BridgeHubOptions);
|
|
25
|
+
/** The executor to hand a runner for this session. Created on first use and
|
|
26
|
+
* reused, so results routed back always reach the same pending table. */
|
|
27
|
+
executorFor(sessionId: string): BrowserBridgeExecutor;
|
|
28
|
+
/** How many clients are watching this session. Parking consults it: a session
|
|
29
|
+
* someone is watching stays live. */
|
|
30
|
+
attachedCount(sessionId: string): number;
|
|
31
|
+
/** Register an attached client. Returns a detach function. */
|
|
32
|
+
attach(sessionId: string, send: (frame: ServerFrame) => void): () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Deliver a client's answer to a bridged call. Returns false when the id is
|
|
35
|
+
* unknown or already settled — late and duplicate answers are ignored.
|
|
36
|
+
*/
|
|
37
|
+
resolve(sessionId: string, executionId: string, answer: BridgeAnswer): boolean;
|
|
38
|
+
/** Drop a session's bridge, failing anything still in flight. */
|
|
39
|
+
remove(sessionId: string): void;
|
|
35
40
|
}
|
|
36
41
|
//#endregion
|
|
37
|
-
//#region src/notifications.d.ts
|
|
42
|
+
//#region src/services/notifications.d.ts
|
|
38
43
|
type SessionNotificationOptions = {
|
|
39
44
|
/** POST target for every notification. */webhook?: SessionWebhookConfig;
|
|
40
45
|
/** Local observer, invoked for every notification whether or not a webhook is
|
|
@@ -73,43 +78,38 @@ declare class SessionNotifier {
|
|
|
73
78
|
watch(runner: Runner, afterSeq?: number): void;
|
|
74
79
|
}
|
|
75
80
|
//#endregion
|
|
76
|
-
//#region src/
|
|
77
|
-
type
|
|
78
|
-
/** How long a bridged call may stay unanswered before it fails. Default 60000. */timeoutMs?: number;
|
|
79
|
-
/** Called when a bridged execution reaches a terminal result — the host feeds
|
|
80
|
-
* it back into the runner's loop. */
|
|
81
|
-
onResult?: (sessionId: string, executionId: string, result: ToolExecutionResult) => void;
|
|
82
|
-
};
|
|
83
|
-
/**
|
|
84
|
-
* Routes tool executions between a session and the browser tabs attached to it.
|
|
85
|
-
*
|
|
86
|
-
* A session may have several clients attached (dashboard plus embedded panel);
|
|
87
|
-
* the bridge asks the **first attached** one, which is the closest thing to "the
|
|
88
|
-
* client driving this session". If none is attached, dispatch fails fast rather
|
|
89
|
-
* than hanging — an autonomous job simply never bridges, it uses the server
|
|
90
|
-
* executor instead.
|
|
91
|
-
*/
|
|
92
|
-
declare class BridgeHub {
|
|
93
|
-
#private;
|
|
94
|
-
constructor(options?: BridgeHubOptions);
|
|
95
|
-
/** The executor to hand a runner for this session. Created on first use and
|
|
96
|
-
* reused, so results routed back always reach the same pending table. */
|
|
97
|
-
executorFor(sessionId: string): BrowserBridgeExecutor;
|
|
98
|
-
/** How many clients are watching this session. Parking consults it: a session
|
|
99
|
-
* someone is watching stays live. */
|
|
100
|
-
attachedCount(sessionId: string): number;
|
|
101
|
-
/** Register an attached client. Returns a detach function. */
|
|
102
|
-
attach(sessionId: string, send: (frame: ServerFrame) => void): () => void;
|
|
81
|
+
//#region src/services/registry.d.ts
|
|
82
|
+
type SessionRegistryOptions = {
|
|
103
83
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
84
|
+
* Called once per runner as it enters the table, before it starts — the one
|
|
85
|
+
* seam every path goes through (create, prepare, adopt, and the rebuild of a
|
|
86
|
+
* parked session), which is what a watcher that must not miss a session needs.
|
|
106
87
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
onRegister?: (runner: Runner) => void;
|
|
89
|
+
};
|
|
90
|
+
/** In-memory session table. Terminal sessions stay listed until removed or the process exits. */
|
|
91
|
+
declare class SessionRegistry {
|
|
92
|
+
#private;
|
|
93
|
+
constructor(options?: SessionRegistryOptions);
|
|
94
|
+
create(config: SessionRunnerConfig): Runner;
|
|
95
|
+
/** Build and list a Claude-engine runner without starting it, so watchers can
|
|
96
|
+
* subscribe first. Call `start()` once they have. */
|
|
97
|
+
prepare(config: SessionRunnerConfig): Runner;
|
|
98
|
+
/** Register an already-built runner (a non-Claude engine) and start it. */
|
|
99
|
+
adopt(runner: Runner): Runner;
|
|
100
|
+
/** List a runner without starting it — for a rehydrated session, whose watchers
|
|
101
|
+
* must be subscribed before it comes back up. */
|
|
102
|
+
register(runner: Runner): Runner;
|
|
103
|
+
get(id: string): Runner | undefined;
|
|
104
|
+
list(): SessionInfo[];
|
|
105
|
+
remove(id: string): boolean;
|
|
106
|
+
/** Drop a runner WITHOUT closing it: the session isn't ending, it parked and
|
|
107
|
+
* lives on in its snapshot. Closing here would tell every client it was over. */
|
|
108
|
+
evict(id: string): boolean;
|
|
109
|
+
closeAll(): void;
|
|
110
110
|
}
|
|
111
111
|
//#endregion
|
|
112
|
-
//#region src/session-store.d.ts
|
|
112
|
+
//#region src/services/session-store.d.ts
|
|
113
113
|
/**
|
|
114
114
|
* A session with its live runner torn down, waiting on deferred executions.
|
|
115
115
|
*
|
|
@@ -118,6 +118,7 @@ declare class BridgeHub {
|
|
|
118
118
|
* snapshot, and what it is waiting for.
|
|
119
119
|
*/
|
|
120
120
|
type ParkedSessionRecord = {
|
|
121
|
+
/** Absent on records written before dormant sessions existed — those are all parked. */kind?: 'parked';
|
|
121
122
|
id: string; /** Session info as of the park, with `status: 'parked'`. */
|
|
122
123
|
info: SessionInfo;
|
|
123
124
|
profile?: string; /** The config the session was created with (profile defaults already applied). */
|
|
@@ -126,6 +127,46 @@ type ParkedSessionRecord = {
|
|
|
126
127
|
executions: ParkedExecution[];
|
|
127
128
|
parkedAt: number;
|
|
128
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* A live session, remembered so it can be brought back after a gateway restart.
|
|
132
|
+
*
|
|
133
|
+
* The counterpart to a park, and deliberately not the same mechanism. A park
|
|
134
|
+
* preserves *mid-task* state, which means a `RunnerSnapshot` — and only the
|
|
135
|
+
* provider engine can produce one, because the claude and codex engines run
|
|
136
|
+
* behind a binary that owns its own process state. What those two have instead
|
|
137
|
+
* is a session store of their own: the transcript is already on disk under an
|
|
138
|
+
* engine session id, and `CreateSessionRequest.resume` is how you get it back.
|
|
139
|
+
*
|
|
140
|
+
* So this record holds no transcript at all. It holds the id (every client keys
|
|
141
|
+
* its watermarks and routes on it), the engine session id to resume from, and
|
|
142
|
+
* the config to rebuild with — and rehydration is an ordinary create with
|
|
143
|
+
* `resume` set, done **lazily on first attach**, because eagerly respawning
|
|
144
|
+
* every session at boot is a fork bomb wearing a feature's clothes.
|
|
145
|
+
*
|
|
146
|
+
* Written only for engines whose capability record says `resume`, and only once
|
|
147
|
+
* an `sdkSessionId` exists: a record that would come back with an empty
|
|
148
|
+
* transcript is worse than no record.
|
|
149
|
+
*/
|
|
150
|
+
type DormantSessionRecord = {
|
|
151
|
+
kind: 'dormant';
|
|
152
|
+
id: string;
|
|
153
|
+
/** Session info as of the last save, with `status: 'idle'` — whatever it was
|
|
154
|
+
* doing, it is not doing it now. */
|
|
155
|
+
info: SessionInfo;
|
|
156
|
+
profile?: string;
|
|
157
|
+
/**
|
|
158
|
+
* The config the session was built from, minus the ephemeral keys. Fed back
|
|
159
|
+
* through the server's `buildRunnerConfig` on wake rather than used as-is, so
|
|
160
|
+
* the profile's env pin and the host hook's injections are **re-derived**
|
|
161
|
+
* instead of persisted (see {@link EPHEMERAL_CONFIG_KEYS}).
|
|
162
|
+
*/
|
|
163
|
+
config: SessionRunnerConfig; /** Where the transcript actually lives. Without one there is nothing to resume. */
|
|
164
|
+
sdkSessionId: string;
|
|
165
|
+
savedAt: number;
|
|
166
|
+
};
|
|
167
|
+
/** What a {@link SessionStore} holds: a session waiting on deferred work, or one
|
|
168
|
+
* waiting to be asked for again. */
|
|
169
|
+
type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord;
|
|
129
170
|
/**
|
|
130
171
|
* Where parked sessions live. Two implementations ship: {@link MemorySessionStore}
|
|
131
172
|
* (a park survives a disconnect, not a restart) and {@link createFileSessionStore}
|
|
@@ -138,22 +179,22 @@ type ParkedSessionRecord = {
|
|
|
138
179
|
* {@link toDurableRecord} is the filter the bundled file store applies — reuse it.
|
|
139
180
|
*/
|
|
140
181
|
interface SessionStore {
|
|
141
|
-
save(record:
|
|
142
|
-
get(id: string): Promise<
|
|
143
|
-
list(): Promise<
|
|
182
|
+
save(record: StoredSessionRecord): Promise<void>;
|
|
183
|
+
get(id: string): Promise<StoredSessionRecord | null>;
|
|
184
|
+
list(): Promise<StoredSessionRecord[]>;
|
|
144
185
|
delete(id: string): Promise<boolean>;
|
|
145
186
|
}
|
|
146
187
|
/** Single-process, no persistence: parks survive a client disconnect, not a restart. */
|
|
147
188
|
declare class MemorySessionStore implements SessionStore {
|
|
148
189
|
#private;
|
|
149
|
-
save(record:
|
|
150
|
-
get(id: string): Promise<
|
|
151
|
-
list(): Promise<
|
|
190
|
+
save(record: StoredSessionRecord): Promise<void>;
|
|
191
|
+
get(id: string): Promise<StoredSessionRecord | null>;
|
|
192
|
+
list(): Promise<StoredSessionRecord[]>;
|
|
152
193
|
delete(id: string): Promise<boolean>;
|
|
153
194
|
}
|
|
154
195
|
/** The record as it may be persisted: same session, config narrowed to what is
|
|
155
196
|
* safe and meaningful to keep (see {@link EPHEMERAL_CONFIG_KEYS}). */
|
|
156
|
-
declare function toDurableRecord(record:
|
|
197
|
+
declare function toDurableRecord<T extends StoredSessionRecord>(record: T): T;
|
|
157
198
|
type FileSessionStoreOptions = {
|
|
158
199
|
/** Directory holding one JSON file per parked session.
|
|
159
200
|
* Default `<cwd>/.workerdeck/parked`. */
|
|
@@ -187,13 +228,18 @@ type FileSessionStoreOptions = {
|
|
|
187
228
|
*/
|
|
188
229
|
declare function createFileSessionStore(options?: FileSessionStoreOptions): SessionStore;
|
|
189
230
|
//#endregion
|
|
190
|
-
//#region src/parking.d.ts
|
|
231
|
+
//#region src/services/parking.d.ts
|
|
191
232
|
type SessionParkOptions = {
|
|
192
233
|
registry: SessionRegistry;
|
|
193
234
|
store: SessionStore;
|
|
194
|
-
/**
|
|
195
|
-
*
|
|
196
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Rebuild a stored session's runner. For a park the snapshot rides in on
|
|
237
|
+
* `config.restore`, so the engine adopts the id, event log, and history; for a
|
|
238
|
+
* dormant record there is no snapshot and the engine is asked to resume its
|
|
239
|
+
* own session under the stored id. Either way the runner it returns must carry
|
|
240
|
+
* `record.id` — {@link SessionParkManager} refuses one that does not.
|
|
241
|
+
*/
|
|
242
|
+
rebuild: (record: StoredSessionRecord) => Promise<Runner>;
|
|
197
243
|
/** How many clients are attached to this session. A watched session stays live:
|
|
198
244
|
* parking would pull the runner out from under the socket. */
|
|
199
245
|
attachedCount: (sessionId: string) => number;
|
|
@@ -212,17 +258,25 @@ type SessionParkOptions = {
|
|
|
212
258
|
/** The session is live again under a NEW runner object — anything holding the
|
|
213
259
|
* old reference must rebind. */
|
|
214
260
|
onResumed?: (sessionId: string, runner: Runner) => void;
|
|
215
|
-
/** Park/resume failures. These are not session errors — the session
|
|
216
|
-
* the host's storage or engine assembly isn't. */
|
|
261
|
+
/** Park/remember/resume failures. These are not session errors — the session
|
|
262
|
+
* is intact, the host's storage or engine assembly isn't. */
|
|
217
263
|
onError?: (error: unknown, context: {
|
|
218
264
|
sessionId: string;
|
|
219
|
-
phase: 'park' | 'resume';
|
|
265
|
+
phase: 'park' | 'remember' | 'resume';
|
|
220
266
|
}) => void;
|
|
221
267
|
};
|
|
222
268
|
/**
|
|
223
|
-
*
|
|
269
|
+
* Two ways a session outlives its runner, behind one door.
|
|
270
|
+
*
|
|
271
|
+
* **Parking** is deferred execution's other half: a session waiting on work no
|
|
224
272
|
* process in this server is doing.
|
|
225
273
|
*
|
|
274
|
+
* **Dormancy** is the restart story for the engines that cannot park. Every live
|
|
275
|
+
* claude or codex session leaves a small record naming its engine session id, so
|
|
276
|
+
* a gateway that comes back up lists them and resumes one the first time someone
|
|
277
|
+
* attaches. Both kinds live in the same store and come back through the same
|
|
278
|
+
* `ensureLive`, which is why there is one class here and not two.
|
|
279
|
+
*
|
|
226
280
|
* The runner announces the moment with `status_changed: 'parked'` — emitted only
|
|
227
281
|
* once every dispatch of the batch has been handed over, so the snapshot can never
|
|
228
282
|
* miss a call that was still being dispatched. From there this class snapshots,
|
|
@@ -236,9 +290,27 @@ declare class SessionParkManager {
|
|
|
236
290
|
/** Record the config a session was created with. Only sessions the host
|
|
237
291
|
* remembers can be parked — there is no way to rebuild the others. */
|
|
238
292
|
remember(sessionId: string, config: SessionRunnerConfig): void;
|
|
239
|
-
/**
|
|
293
|
+
/**
|
|
294
|
+
* Re-save a live session's dormant record because something outside the event
|
|
295
|
+
* stream changed it.
|
|
296
|
+
*
|
|
297
|
+
* `#rememberDormant` is otherwise driven by `status_changed` and `system_init`
|
|
298
|
+
* alone, and a rename (`PATCH /sessions/:id`) fires neither — so without this
|
|
299
|
+
* a renamed session that is never touched again keeps its old title on disk
|
|
300
|
+
* and comes back under it. Safe to call for anything: every gate in
|
|
301
|
+
* `#rememberDormant` still applies, so a session that cannot be resumed, has
|
|
302
|
+
* no engine session yet, or is no longer the registry's writes nothing.
|
|
303
|
+
*/
|
|
304
|
+
touch(runner: Runner): void;
|
|
305
|
+
/**
|
|
306
|
+
* Adopt the store's contents (a durable store after a restart): re-index the
|
|
240
307
|
* executions and re-arm their watchdogs, no deadline sooner than the grace
|
|
241
|
-
* window — nothing could have been delivered while the process was down.
|
|
308
|
+
* window — nothing could have been delivered while the process was down.
|
|
309
|
+
*
|
|
310
|
+
* Dormant records need nothing here, which is the point of them. They list
|
|
311
|
+
* from the store (`listInfo`) and come back on first attach (`ensureLive`), so
|
|
312
|
+
* a boot with fifty remembered sessions spawns nothing at all.
|
|
313
|
+
*/
|
|
242
314
|
hydrate(): Promise<void>;
|
|
243
315
|
/**
|
|
244
316
|
* Follow a session's lifecycle: index its deferred executions, park it when the
|
|
@@ -251,9 +323,9 @@ declare class SessionParkManager {
|
|
|
251
323
|
onDetach(sessionId: string): void;
|
|
252
324
|
/** Which session this execution belongs to — still waiting, or already settled. */
|
|
253
325
|
sessionFor(executionId: string): string | undefined;
|
|
254
|
-
/** The
|
|
255
|
-
get(id: string): Promise<
|
|
256
|
-
/** Every
|
|
326
|
+
/** The stored session's record, for the read paths (GET, list, attach). */
|
|
327
|
+
get(id: string): Promise<StoredSessionRecord | null>;
|
|
328
|
+
/** Every stored session's info, to merge into `GET {basePath}/sessions`. */
|
|
257
329
|
listInfo(): Promise<SessionInfo[]>;
|
|
258
330
|
/** The live runner for a session, rehydrating a parked one on demand. Undefined
|
|
259
331
|
* when the session is neither live nor parked. */
|
|
@@ -275,7 +347,7 @@ declare class SessionParkManager {
|
|
|
275
347
|
close(): void;
|
|
276
348
|
}
|
|
277
349
|
//#endregion
|
|
278
|
-
//#region src/profile-store.d.ts
|
|
350
|
+
//#region src/services/profile-store.d.ts
|
|
279
351
|
/**
|
|
280
352
|
* Where dashboard-managed profiles live. The seam exists for the same reason
|
|
281
353
|
* `QueueAdapter` does: a single-host deployment wants the bundled file store and
|
|
@@ -307,7 +379,7 @@ declare function createMemoryProfileStore(seed?: ProfileInfo[]): ProfileStore;
|
|
|
307
379
|
*/
|
|
308
380
|
declare function createFileProfileStore(path?: string): ProfileStore;
|
|
309
381
|
//#endregion
|
|
310
|
-
//#region src/
|
|
382
|
+
//#region src/options.d.ts
|
|
311
383
|
type SdkSessionLister = (options: {
|
|
312
384
|
dir?: string;
|
|
313
385
|
limit?: number;
|
|
@@ -321,11 +393,64 @@ type SdkSessionLister = (options: {
|
|
|
321
393
|
* without it the caller may use every declared profile. It may also carry
|
|
322
394
|
* `canManageProfiles: true` to allow creating/editing/deleting managed profiles
|
|
323
395
|
* (requires the `profileStore` option); anything else means no.
|
|
396
|
+
*
|
|
397
|
+
* It may also carry `scope: Record<string, string>` — the opaque tags deciding
|
|
398
|
+
* which *sessions* this caller may see at all (see
|
|
399
|
+
* {@link WorkerServerOptions.authorizeSession}). A principal carrying a scope is
|
|
400
|
+
* an embedded end user rather than the operator, and is refused the
|
|
401
|
+
* operator-privileged surfaces outright: `/fs/*`, `/sdk-sessions`, `/queue` and
|
|
402
|
+
* `/queue/ws`.
|
|
403
|
+
*
|
|
404
|
+
* **This is the place to be expensive.** It is already async and already runs
|
|
405
|
+
* once per request, so a lookup (which spaces is this user in?) belongs here,
|
|
406
|
+
* landing its answer on the principal. The visibility check itself is
|
|
407
|
+
* synchronous by design: it runs on every route and every row of every list.
|
|
324
408
|
*/
|
|
325
409
|
type Authenticator = (req: IncomingMessage) => unknown | Promise<unknown>;
|
|
326
410
|
type WorkerServerOptions = {
|
|
327
411
|
/** Required unless `allowUnauthenticated: true` — the worker must never be exposed bare. */authenticate?: Authenticator; /** Explicit opt-in to run without auth (local dev only). */
|
|
328
|
-
allowUnauthenticated?: boolean;
|
|
412
|
+
allowUnauthenticated?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Whether a principal may see one session — the policy half of
|
|
415
|
+
* {@link CreateSessionRequest.scope}. WorkerDeck stores the opaque tags and
|
|
416
|
+
* enforces the answer at every door; what the tags *mean* is the host's, and
|
|
417
|
+
* has to be, because "space" and "user" are one app's vocabulary and the next
|
|
418
|
+
* embedder has tenants or projects or nothing.
|
|
419
|
+
*
|
|
420
|
+
* **Synchronous, deliberately.** It runs per route and per row of every list,
|
|
421
|
+
* so resolving it against a database per request is the failure mode this
|
|
422
|
+
* signature designs out: do the lookup in {@link Authenticator} and put the
|
|
423
|
+
* answer on the principal.
|
|
424
|
+
*
|
|
425
|
+
* Unset, the default rule applies: every key the principal's `scope` pins must
|
|
426
|
+
* equal the session's, and a principal with no scope (`undefined` or `{}`) is
|
|
427
|
+
* unrestricted — the same "unset means all" rule `allowedProfiles` uses, so an
|
|
428
|
+
* operator's dashboard is unaffected. A consequence worth stating: a session
|
|
429
|
+
* carrying *no* scope is invisible to a scoped principal, which is the right
|
|
430
|
+
* fail direction — sessions predating this feature never leak into an
|
|
431
|
+
* end user's list.
|
|
432
|
+
*
|
|
433
|
+
* **False means the session does not exist**: every refusal answers 404, never
|
|
434
|
+
* 403, matching `host-files.ts`' uniform-disclosure discipline. A predicate
|
|
435
|
+
* that *throws* has not said yes — it is caught and read as false, so one
|
|
436
|
+
* surprising row cannot turn a hundred-row list into a page-wide error.
|
|
437
|
+
*
|
|
438
|
+
* **Declaring this withdraws the unscoped-means-operator default.** The
|
|
439
|
+
* gateway-wide surfaces (`/fs/*`, `/sdk-sessions`, `/queue`, `/queue/ws`) key
|
|
440
|
+
* on {@link Authenticator}'s principal carrying no `scope` — but a host may
|
|
441
|
+
* well write this predicate over its own principal shape and never set one,
|
|
442
|
+
* and reading that as "everyone is the operator" would serve the host
|
|
443
|
+
* filesystem to every end user whose sessions this correctly walls off. So
|
|
444
|
+
* with a policy declared, operator principals must say `operator: true`.
|
|
445
|
+
*
|
|
446
|
+
* **True means full control, not read access.** An attach can send
|
|
447
|
+
* `user_message`, `permission_decision`, `interrupt` and `close`, and a
|
|
448
|
+
* bridged client can settle a tool call — so this is one boolean over "may
|
|
449
|
+
* drive this session", not a visibility level. A read-only-for-my-team policy
|
|
450
|
+
* is not expressible here yet; do not approximate it with `readOnly`, which is
|
|
451
|
+
* affordance removal in a client and not an authorization boundary.
|
|
452
|
+
*/
|
|
453
|
+
authorizeSession?: (principal: unknown, session: SessionInfo) => boolean; /** If set, session cwd must resolve inside one of these roots. Strongly recommended. */
|
|
329
454
|
allowedCwdRoots?: string[];
|
|
330
455
|
/**
|
|
331
456
|
* The host filesystem routes (`{basePath}/fs/*`) — browse and read the
|
|
@@ -489,6 +614,24 @@ type WorkerServerOptions = {
|
|
|
489
614
|
probe?: ClaudeAuthProbe;
|
|
490
615
|
timeoutMs?: number;
|
|
491
616
|
};
|
|
617
|
+
/**
|
|
618
|
+
* Refuse to create a session or submit a job on a profile the credential
|
|
619
|
+
* probe has reported **unavailable** — 503 with the probe's own reason —
|
|
620
|
+
* rather than letting the run start and die mid-turn on a raw provider error.
|
|
621
|
+
*
|
|
622
|
+
* Off by default, and that default is right for an operator's own gateway:
|
|
623
|
+
* the verdict can be stale in both directions, the operator may be three
|
|
624
|
+
* seconds from finishing a login, and turning a probe bug into an outage is
|
|
625
|
+
* worse than one confusing failure. It is wrong in front of an **end user**,
|
|
626
|
+
* who cannot read a provider stack trace and did not choose the deployment's
|
|
627
|
+
* credentials — which is why every embedder otherwise grows its own
|
|
628
|
+
* `available` flag in front of the create button.
|
|
629
|
+
*
|
|
630
|
+
* Requires `checkCredentials`; without probes nothing is ever unavailable.
|
|
631
|
+
* A profile whose verdict is 'unknown' (never probed, probe couldn't run) is
|
|
632
|
+
* always allowed through — "couldn't check" is not "not available".
|
|
633
|
+
*/
|
|
634
|
+
requireAvailableProfile?: boolean;
|
|
492
635
|
/** Injectable lister for GET /sdk-sessions (tests) — honored for the CLAUDE
|
|
493
636
|
* engine only, like the injectable claude auth probe (it predates the adapter
|
|
494
637
|
* layer). Defaults to the claude adapter's lister (the SDK's on-disk session
|
|
@@ -532,10 +675,13 @@ type WorkerServerOptions = {
|
|
|
532
675
|
parkDelayMs?: number;
|
|
533
676
|
/** Grace given on boot to an execution whose deadline passed while the server
|
|
534
677
|
* was down (durable stores only — nothing else survives a restart). Default 60000. */
|
|
535
|
-
expiredGraceMs?: number;
|
|
678
|
+
expiredGraceMs?: number;
|
|
679
|
+
/** Park/remember/resume failures — storage or engine-assembly problems, not
|
|
680
|
+
* session errors. 'remember' is the write that lets a live session survive a
|
|
681
|
+
* restart; losing one costs that session its way back and nothing else. */
|
|
536
682
|
onError?: (error: unknown, context: {
|
|
537
683
|
sessionId: string;
|
|
538
|
-
phase: 'park' | 'resume';
|
|
684
|
+
phase: 'park' | 'remember' | 'resume';
|
|
539
685
|
}) => void;
|
|
540
686
|
};
|
|
541
687
|
/**
|
|
@@ -576,6 +722,13 @@ type EngineRunnerContext = {
|
|
|
576
722
|
* numbering, history, and scratch filesystem instead of starting fresh.
|
|
577
723
|
*/
|
|
578
724
|
restore?: RunnerSnapshot;
|
|
725
|
+
/**
|
|
726
|
+
* Set when rehydrating a session across a gateway restart: build the runner
|
|
727
|
+
* under exactly this id rather than a fresh one. Never set together with
|
|
728
|
+
* `restore` (a snapshot carries its own id). Ignoring it strands every
|
|
729
|
+
* client's watermarks and routes, and the rebuild is refused.
|
|
730
|
+
*/
|
|
731
|
+
id?: string;
|
|
579
732
|
};
|
|
580
733
|
type QueueServerOptions = {
|
|
581
734
|
/** Concurrent job sessions. Default 1. */maxConcurrency?: number; /** Token cap per job session (input+output+cache tokens); exceeding it kills the run. */
|
|
@@ -612,9 +765,138 @@ type WorkerServer = {
|
|
|
612
765
|
}>;
|
|
613
766
|
close: () => Promise<void>;
|
|
614
767
|
};
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/server.d.ts
|
|
615
770
|
declare function createWorkerServer(options?: WorkerServerOptions): WorkerServer;
|
|
616
771
|
//#endregion
|
|
617
|
-
//#region src/
|
|
772
|
+
//#region src/lib/sandboxed-profile.d.ts
|
|
773
|
+
/**
|
|
774
|
+
* A `provider` profile that grants a session nothing but the sandbox: the
|
|
775
|
+
* QuickJS guest, the in-memory VFS, and the model.
|
|
776
|
+
*
|
|
777
|
+
* This adds no mechanism. `capabilities: []` and `mcpServers: []` already mean
|
|
778
|
+
* what they mean, and `createToolContext` already withholds a tool whose backend
|
|
779
|
+
* the host did not inject. What the helper buys is that the locked-down profile
|
|
780
|
+
* is one call rather than three fields an operator has to get right together —
|
|
781
|
+
* the failure mode being a profile that *looks* sandboxed and still grants
|
|
782
|
+
* `deliver_file` because nobody wrote the empty array.
|
|
783
|
+
*
|
|
784
|
+
* What a session under it can do:
|
|
785
|
+
* - run untrusted JavaScript in the WASM guest, under the interpreter's own
|
|
786
|
+
* timeout and memory limits (`eval_script`),
|
|
787
|
+
* - read and write the session's in-memory VFS, which is a map and not a
|
|
788
|
+
* filesystem — no host path is reachable from it.
|
|
789
|
+
*
|
|
790
|
+
* What it cannot do: read or write a host path, spawn a process, reach the
|
|
791
|
+
* network (`web_fetch`/`download`/`web_search` are capabilities, and none is
|
|
792
|
+
* granted), deliver a file, or use an MCP server.
|
|
793
|
+
*
|
|
794
|
+
* Two things this helper does **not** do, because they are not a profile's to
|
|
795
|
+
* decide. It does not authorize anyone — visibility is
|
|
796
|
+
* `CreateSessionRequest.scope` plus the gateway's `authorizeSession`. And it
|
|
797
|
+
* does not make the model's *input* trustworthy: content the loop reads is
|
|
798
|
+
* attacker-influenced by default, and a sandbox bounds what a tool can reach,
|
|
799
|
+
* not what a prompt can talk the model into asking for.
|
|
800
|
+
*
|
|
801
|
+
* @param name Profile name clients name in `CreateSessionRequest.profile`.
|
|
802
|
+
* @param provider Which model to run (credentials stay in the operator's
|
|
803
|
+
* environment and are resolved by the host's `createEngineRunner` — never
|
|
804
|
+
* here, and never on the wire).
|
|
805
|
+
*/
|
|
806
|
+
declare function sandboxedProviderProfile(name: string, provider: ProviderConfig, options?: {
|
|
807
|
+
description?: string; /** Prepended to the session's system prompt. */
|
|
808
|
+
instructions?: string;
|
|
809
|
+
/** Profile-level run defaults (model, permission mode) — see
|
|
810
|
+
* {@link ProfileInfo.defaults}. */
|
|
811
|
+
defaults?: ProfileInfo['defaults'];
|
|
812
|
+
/**
|
|
813
|
+
* Capabilities to grant on top of the floor. Default `[]` — the floor is
|
|
814
|
+
* nothing, and every entry here is a deliberate widening you are writing
|
|
815
|
+
* down: `web_fetch` gives the loop egress (SSRF-guarded, but egress),
|
|
816
|
+
* `download` and `web_search` reach whatever backends you injected, and
|
|
817
|
+
* `deliver_file` lets it hand a file to the client.
|
|
818
|
+
*/
|
|
819
|
+
capabilities?: SessionCapability[];
|
|
820
|
+
/**
|
|
821
|
+
* MCP servers, **by name**, whose tools sessions may use. Default `[]`.
|
|
822
|
+
* MCP tools are authoritative — they run with the host's credentials and
|
|
823
|
+
* are never bridged — so naming one here is a larger grant than any
|
|
824
|
+
* capability above it.
|
|
825
|
+
*/
|
|
826
|
+
mcpServers?: string[];
|
|
827
|
+
}): ProfileInfo;
|
|
828
|
+
//#endregion
|
|
829
|
+
//#region src/lib/provider-runner.d.ts
|
|
830
|
+
type ProviderRunnerOptions = {
|
|
831
|
+
/**
|
|
832
|
+
* The model to run. A function is called per turn with the session's
|
|
833
|
+
* requested model id (undefined = the profile's default), which is what makes
|
|
834
|
+
* the in-session model switcher work; a bare instance pins one model.
|
|
835
|
+
*/
|
|
836
|
+
model: LanguageModel | ((modelId: string | undefined) => LanguageModel);
|
|
837
|
+
/**
|
|
838
|
+
* Where sandboxed tools (`eval_script` and any `sandboxed` entry in `tools`)
|
|
839
|
+
* execute. This is a real architectural choice, not a default worth guessing
|
|
840
|
+
* at, so it is required:
|
|
841
|
+
*
|
|
842
|
+
* - a {@link ToolExecutor} — an in-process guest (`new QuickJsExecutor(...)`
|
|
843
|
+
* from `@workerdeck/core`), which is right when the data the loop reasons
|
|
844
|
+
* over lives in this process. It is also the only option that works when no
|
|
845
|
+
* client is attached, which is every unattended job.
|
|
846
|
+
* - `'browser'` — the attached tab, resolved per call from the bridge. Right
|
|
847
|
+
* when the data is *there* (a document the user is editing) and it should
|
|
848
|
+
* not travel to the gateway at all. Note the trade: it hands an executor to
|
|
849
|
+
* the party being sandboxed against, so its results are untrusted input.
|
|
850
|
+
*/
|
|
851
|
+
executor: ToolExecutor | 'browser';
|
|
852
|
+
/** Capability backends — the same shape {@link createEngineSession} takes.
|
|
853
|
+
* Wiring one only offers it; the profile and request decide the grant. */
|
|
854
|
+
capabilities?: EngineSessionOptions['capabilities']; /** Host tools at explicit trust levels (`@workerdeck/core`'s `withHostTools`). */
|
|
855
|
+
tools?: Record<string, HostToolDefinition>;
|
|
856
|
+
/** A live MCP connection from `connectMcpTools`. Prefer this over `mcpTools`:
|
|
857
|
+
* it is what lets a profile's unhonoured `mcpServers` refuse the build, and
|
|
858
|
+
* what makes `GET /sessions/:id/mcp` answer for this session. */
|
|
859
|
+
mcp?: McpConnection; /** A bare MCP tool set, for a host assembling one itself. */
|
|
860
|
+
mcpTools?: ToolSet; /** System-prompt addition, unless the profile declares its own. */
|
|
861
|
+
instructions?: string; /** Sandbox limits per execution. */
|
|
862
|
+
executionLimits?: {
|
|
863
|
+
timeoutMs?: number;
|
|
864
|
+
memoryLimitBytes?: number;
|
|
865
|
+
};
|
|
866
|
+
/** Scratch-filesystem seed for a new session. Ignored on a rehydration, so a
|
|
867
|
+
* parked turn's files are never overwritten. */
|
|
868
|
+
seedVfs?: Record<string, string>;
|
|
869
|
+
/** Release per-session resources: the MCP connection, an issued token, a
|
|
870
|
+
* watcher. Runs on close **and on park** — parking releases the same things. */
|
|
871
|
+
onClose?: () => void | Promise<void>;
|
|
872
|
+
};
|
|
873
|
+
/**
|
|
874
|
+
* Build a provider-engine runner from the server's `createEngineRunner` context.
|
|
875
|
+
*
|
|
876
|
+
* `createEngineRunner` is a blank sheet: it hands you a context and wants a
|
|
877
|
+
* `Runner`, and four of the five things a correct one must do are invisible in
|
|
878
|
+
* the types — forward `restore`, adopt `id`, seed the VFS only when *not*
|
|
879
|
+
* restoring, and dispose per-session resources. Each is a runtime-only failure
|
|
880
|
+
* (a woken session that starts empty, a refused rebuild, an overwritten
|
|
881
|
+
* filesystem, a connection leaked per session), and each is handled here.
|
|
882
|
+
*
|
|
883
|
+
* ```ts
|
|
884
|
+
* createEngineRunner: (ctx) =>
|
|
885
|
+
* createProviderRunner(ctx, {
|
|
886
|
+
* model: (id) => openai(id ?? 'gpt-5.6-luna'),
|
|
887
|
+
* executor: quickjs,
|
|
888
|
+
* capabilities: { webFetch: {} },
|
|
889
|
+
* mcp,
|
|
890
|
+
* onClose: () => mcp.close(),
|
|
891
|
+
* }),
|
|
892
|
+
* ```
|
|
893
|
+
*
|
|
894
|
+
* The hook itself stays open for anything this does not cover — this is the
|
|
895
|
+
* 80% case, not a replacement for it.
|
|
896
|
+
*/
|
|
897
|
+
declare function createProviderRunner(ctx: EngineRunnerContext, options: ProviderRunnerOptions): Promise<Runner>;
|
|
898
|
+
//#endregion
|
|
899
|
+
//#region src/services/attachments.d.ts
|
|
618
900
|
type AttachmentStoreOptions = {
|
|
619
901
|
/** Largest single upload. Default 10 MiB. */maxFileBytes?: number; /** Ceiling on everything one session is holding. Default 64 MiB. */
|
|
620
902
|
maxSessionBytes?: number;
|
|
@@ -678,7 +960,7 @@ declare class AttachmentStore {
|
|
|
678
960
|
drop(sessionId: string): void;
|
|
679
961
|
}
|
|
680
962
|
//#endregion
|
|
681
|
-
//#region src/produced-files.d.ts
|
|
963
|
+
//#region src/services/produced-files.d.ts
|
|
682
964
|
/** One host file an engine reported writing, as the store holds it. */
|
|
683
965
|
type ProducedFile = {
|
|
684
966
|
fileId: string; /** Absolute host path, exactly as the runner reported it. */
|
|
@@ -728,5 +1010,54 @@ declare class ProducedFileStore {
|
|
|
728
1010
|
drop(sessionId: string): void;
|
|
729
1011
|
}
|
|
730
1012
|
//#endregion
|
|
731
|
-
|
|
1013
|
+
//#region src/services/profile-usage.d.ts
|
|
1014
|
+
/**
|
|
1015
|
+
* The gateway's single plan-usage state per profile, fed from every session's
|
|
1016
|
+
* `rate_limit` events and served on `GET /profiles` (`ProfileInfo.usage`).
|
|
1017
|
+
*
|
|
1018
|
+
* Why this exists at all: usage had only ever lived in session transcripts, so
|
|
1019
|
+
* a client attaching to a session that idled since yesterday replayed
|
|
1020
|
+
* yesterday's reading as if current — and a session opened today knew nothing
|
|
1021
|
+
* of what a sibling session on the same account spent an hour ago. The profile
|
|
1022
|
+
* is the account boundary (one config dir / codex home / provider key = one
|
|
1023
|
+
* plan), so the newest reading across all of a profile's sessions is the one
|
|
1024
|
+
* usage state that is ever worth showing. No history: last-write-wins per
|
|
1025
|
+
* window, exactly the reducer's rule on the client side.
|
|
1026
|
+
*
|
|
1027
|
+
* Last-write-wins goes by the **event's own clock**, not arrival order:
|
|
1028
|
+
* `watch()` subscribes from seq 0 (a replayed log is how a rebuilt session's
|
|
1029
|
+
* readings arrive at all), and a replayed yesterday-reading must not clobber
|
|
1030
|
+
* the fresher one another session on the same profile reported live. All
|
|
1031
|
+
* events are stamped by this gateway's clock at emit time, so the comparison
|
|
1032
|
+
* is sound across sessions.
|
|
1033
|
+
*
|
|
1034
|
+
* In-memory on purpose, like the learned default models and the availability
|
|
1035
|
+
* cache: display-only state may start empty after a restart (absent = unknown,
|
|
1036
|
+
* never 0%), and the first session to report refills it.
|
|
1037
|
+
*/
|
|
1038
|
+
declare class ProfileUsageTracker {
|
|
1039
|
+
#private;
|
|
1040
|
+
/** Follow a runner's `rate_limit` events for its lifetime. Sessions without a
|
|
1041
|
+
* profile have no account to attribute usage to and are skipped. */
|
|
1042
|
+
watch(runner: Runner): void;
|
|
1043
|
+
/**
|
|
1044
|
+
* The profile's windows as they should be served *now*. Undefined until any
|
|
1045
|
+
* session on the profile has reported (unknown, never 0%).
|
|
1046
|
+
*
|
|
1047
|
+
* The 0%-after-reset inference lives here — at serve time — and nowhere
|
|
1048
|
+
* else, because it is a function of the wall clock: a window whose own
|
|
1049
|
+
* `resetsAt` has passed with no newer reading has provably rolled, so the
|
|
1050
|
+
* pre-reset utilization is no longer merely stale but *wrong*. It cannot be
|
|
1051
|
+
* a producer's job (the producers only relay what the engine said, and the
|
|
1052
|
+
* whole problem is the engine's silence; a fabricated 0% event would be
|
|
1053
|
+
* replayed from transcripts forever as if reported) and must not be every
|
|
1054
|
+
* renderer's (N clients would each reimplement the clock math). The held
|
|
1055
|
+
* reading stays untouched, so a late fresh report still lands by ts, and the
|
|
1056
|
+
* served zero is labeled `inferredReset` — it is a floor, not a report: the
|
|
1057
|
+
* account may have been used outside this gateway since the reset.
|
|
1058
|
+
*/
|
|
1059
|
+
usage(profile: string, now?: number): ProfileUsage | undefined;
|
|
1060
|
+
}
|
|
1061
|
+
//#endregion
|
|
1062
|
+
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 };
|
|
732
1063
|
//# sourceMappingURL=index.d.mts.map
|