@workerdeck/core 0.22.0 → 0.23.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 +45 -4
- package/build/index.mjs +149 -37
- package/build/index.mjs.map +1 -1
- package/package.json +3 -3
package/build/index.d.mts
CHANGED
|
@@ -510,7 +510,21 @@ type AiSdkRunnerConfig = Omit<CreateSessionRequest, 'cwd'> & {
|
|
|
510
510
|
timeoutMs?: number;
|
|
511
511
|
memoryLimitBytes?: number;
|
|
512
512
|
}; /** Which backend the executor represents, for `execution_dispatched` events. */
|
|
513
|
-
executionBackend?: ToolExecutionBackend;
|
|
513
|
+
executionBackend?: ToolExecutionBackend;
|
|
514
|
+
/**
|
|
515
|
+
* Decide per call whether the user must approve a tool execution before it
|
|
516
|
+
* dispatches. When the session's permission mode is `'default'` and this
|
|
517
|
+
* callback returns `true`, the runner emits `permission_requested`, parks,
|
|
518
|
+
* and waits for {@link AiSdkRunner.resolvePermission}. Modes
|
|
519
|
+
* `'bypassPermissions'` and `'dontAsk'` skip the check entirely.
|
|
520
|
+
*
|
|
521
|
+
* Unset = every tool dispatches immediately (the pre-§7 behavior).
|
|
522
|
+
*/
|
|
523
|
+
shouldApprove?: (call: {
|
|
524
|
+
toolName: string;
|
|
525
|
+
input: unknown;
|
|
526
|
+
}) => boolean; /** Timeout for permission prompts, in ms. Default 120 000 (2 min). */
|
|
527
|
+
approvalTimeoutMs?: number; /** Swap models mid-session (`set_model`). Unset = setModel() is rejected. */
|
|
514
528
|
resolveModel?: (modelId: string | undefined) => LanguageModel$1;
|
|
515
529
|
/**
|
|
516
530
|
* Live MCP status for this session, when the host wired MCP at all. Unlike
|
|
@@ -656,7 +670,7 @@ declare class AiSdkRunner implements Runner {
|
|
|
656
670
|
resolveToolCall(toolCallId: string, output: ToolCallOutput, options?: {
|
|
657
671
|
isError?: boolean;
|
|
658
672
|
}): boolean;
|
|
659
|
-
resolvePermission(
|
|
673
|
+
resolvePermission(requestId: string, decision: PermissionDecision): boolean;
|
|
660
674
|
/** Emit `file_delivered` — the deliver_file tool's hand-over event (wired by
|
|
661
675
|
* createEngineSession via ToolContextOptions.onFileDelivered). */
|
|
662
676
|
emitFileDelivered(file: {
|
|
@@ -1114,9 +1128,21 @@ type EngineSessionOptions = {
|
|
|
1114
1128
|
* Executor for sandboxed tools. Return the browser bridge when a client is
|
|
1115
1129
|
* attached and the server sandbox otherwise; the seam makes them
|
|
1116
1130
|
* interchangeable, so this is the only place the choice is made.
|
|
1131
|
+
*
|
|
1132
|
+
* A function accepting a {@link ToolExecutionCall} selects **per call**:
|
|
1133
|
+
* `eval_script` may run on the in-process sandbox while a custom tool goes
|
|
1134
|
+
* to the browser, with no coupling between them.
|
|
1117
1135
|
*/
|
|
1118
|
-
selectExecutor: () => ToolExecutor
|
|
1119
|
-
|
|
1136
|
+
selectExecutor: (() => ToolExecutor) | ((call: ToolExecutionCall) => ToolExecutor);
|
|
1137
|
+
/**
|
|
1138
|
+
* Which backend `selectExecutor` returned, for the `execution_dispatched`
|
|
1139
|
+
* events. A function returns the backend per call — pair it with a per-call
|
|
1140
|
+
* `selectExecutor` so the event matches the executor that actually ran.
|
|
1141
|
+
*
|
|
1142
|
+
* When `selectExecutor` is per-call and `backend` is static, every call is
|
|
1143
|
+
* reported under one label. When omitted, falls back to `'server'`.
|
|
1144
|
+
*/
|
|
1145
|
+
backend?: 'server' | 'browser' | 'managed' | 'remote' | ((call: ToolExecutionCall) => 'server' | 'browser' | 'managed' | 'remote'); /** Backends for the granted capabilities. Omitted ones are simply not granted. */
|
|
1120
1146
|
capabilities?: {
|
|
1121
1147
|
search?: ToolContextOptions['search'];
|
|
1122
1148
|
download?: ToolContextOptions['download'];
|
|
@@ -1175,6 +1201,21 @@ type EngineSessionOptions = {
|
|
|
1175
1201
|
timeoutMs?: number;
|
|
1176
1202
|
memoryLimitBytes?: number;
|
|
1177
1203
|
};
|
|
1204
|
+
/**
|
|
1205
|
+
* Gate tool execution behind user approval. When this returns `true` for a
|
|
1206
|
+
* given call and the session's permission mode is `'default'`, the runner
|
|
1207
|
+
* emits `permission_requested` and waits for the user to approve or deny
|
|
1208
|
+
* before dispatching. Bypass modes skip the check entirely.
|
|
1209
|
+
*
|
|
1210
|
+
* By default nothing requires approval — tools dispatch as soon as the model
|
|
1211
|
+
* calls them, which is the right call for trusted pipelines and the pre-§7
|
|
1212
|
+
* behavior.
|
|
1213
|
+
*/
|
|
1214
|
+
shouldApprove?: (call: {
|
|
1215
|
+
toolName: string;
|
|
1216
|
+
input: unknown;
|
|
1217
|
+
}) => boolean; /** Timeout for permission prompts (ms). Default 120 000. */
|
|
1218
|
+
approvalTimeoutMs?: number;
|
|
1178
1219
|
/**
|
|
1179
1220
|
* Initial scratch-filesystem contents for a **new** session, and the safe way
|
|
1180
1221
|
* to seed one: it is ignored outright when `config.restore` is set, because a
|
package/build/index.mjs
CHANGED
|
@@ -1899,6 +1899,8 @@ var AiSdkRunner = class {
|
|
|
1899
1899
|
/** Model alias as requested (not the resolved provider id) — what set_model was
|
|
1900
1900
|
* given, so a rehydrated session can re-resolve the same choice. */
|
|
1901
1901
|
#modelAlias;
|
|
1902
|
+
/** Permission prompts awaiting a client decision. Keyed by request id. */
|
|
1903
|
+
#pendingApprovals = /* @__PURE__ */ new Map();
|
|
1902
1904
|
constructor(config, id = randomUUID()) {
|
|
1903
1905
|
const mode = config.permissionMode ?? "default";
|
|
1904
1906
|
if (!SUPPORTED_PERMISSION_MODES.includes(mode)) throw new Error(`permission mode '${mode}' is not supported by the AI SDK engine`);
|
|
@@ -1957,7 +1959,7 @@ var AiSdkRunner = class {
|
|
|
1957
1959
|
return [...this.#pendingToolCalls.values()];
|
|
1958
1960
|
}
|
|
1959
1961
|
get pendingApprovals() {
|
|
1960
|
-
return [];
|
|
1962
|
+
return [...this.#pendingApprovals.values()].map((a) => a.request);
|
|
1961
1963
|
}
|
|
1962
1964
|
/** The session's scratch filesystem (see Runner.vfs) — the server's file
|
|
1963
1965
|
* routes serve deliverables straight from it. */
|
|
@@ -1971,14 +1973,17 @@ var AiSdkRunner = class {
|
|
|
1971
1973
|
cwd: this.#config.cwd ?? "",
|
|
1972
1974
|
profile: this.#config.profile,
|
|
1973
1975
|
engine: "provider",
|
|
1974
|
-
capabilities:
|
|
1976
|
+
capabilities: this.#config.shouldApprove ? {
|
|
1977
|
+
...ENGINE_CAPABILITIES.provider,
|
|
1978
|
+
interactiveApprovals: true
|
|
1979
|
+
} : ENGINE_CAPABILITIES.provider,
|
|
1975
1980
|
model: this.#modelId(),
|
|
1976
1981
|
permissionMode: this.#permissionMode,
|
|
1977
1982
|
createdAt: this.createdAt,
|
|
1978
1983
|
lastSeq: this.#seq,
|
|
1979
1984
|
activityCount: this.#activityCount,
|
|
1980
1985
|
contextUsage: this.#contextUsage,
|
|
1981
|
-
pendingPermissionCount:
|
|
1986
|
+
pendingPermissionCount: this.#pendingApprovals.size,
|
|
1982
1987
|
meta: this.#config.meta,
|
|
1983
1988
|
scope: this.#config.scope,
|
|
1984
1989
|
title: this.#title(),
|
|
@@ -2163,8 +2168,38 @@ var AiSdkRunner = class {
|
|
|
2163
2168
|
});
|
|
2164
2169
|
return true;
|
|
2165
2170
|
}
|
|
2166
|
-
resolvePermission(
|
|
2167
|
-
|
|
2171
|
+
resolvePermission(requestId, decision) {
|
|
2172
|
+
const approval = this.#pendingApprovals.get(requestId);
|
|
2173
|
+
if (!approval) return false;
|
|
2174
|
+
clearTimeout(approval.timer);
|
|
2175
|
+
this.#pendingApprovals.delete(requestId);
|
|
2176
|
+
const source = "client";
|
|
2177
|
+
if (decision.behavior === "allow") {
|
|
2178
|
+
this.#emit({
|
|
2179
|
+
type: "permission_resolved",
|
|
2180
|
+
requestId,
|
|
2181
|
+
behavior: "allow",
|
|
2182
|
+
resolvedBy: source
|
|
2183
|
+
});
|
|
2184
|
+
this.#dispatchSingle(approval.toolCallId);
|
|
2185
|
+
} else {
|
|
2186
|
+
const message = decision.message ?? "Permission denied by user";
|
|
2187
|
+
this.#emit({
|
|
2188
|
+
type: "permission_resolved",
|
|
2189
|
+
requestId,
|
|
2190
|
+
behavior: "deny",
|
|
2191
|
+
resolvedBy: source,
|
|
2192
|
+
message
|
|
2193
|
+
});
|
|
2194
|
+
this.#applyExecutionResult(approval.toolCallId, {
|
|
2195
|
+
status: "failed",
|
|
2196
|
+
reason: "permission_denied",
|
|
2197
|
+
error: message
|
|
2198
|
+
});
|
|
2199
|
+
if (decision.interrupt) this.interrupt();
|
|
2200
|
+
}
|
|
2201
|
+
if (this.#pendingApprovals.size === 0 && this.#pendingToolCalls.size === 0) {}
|
|
2202
|
+
return true;
|
|
2168
2203
|
}
|
|
2169
2204
|
/** Emit `file_delivered` — the deliver_file tool's hand-over event (wired by
|
|
2170
2205
|
* createEngineSession via ToolContextOptions.onFileDelivered). */
|
|
@@ -2283,6 +2318,8 @@ var AiSdkRunner = class {
|
|
|
2283
2318
|
this.#abort?.abort();
|
|
2284
2319
|
this.#pendingToolCalls.clear();
|
|
2285
2320
|
this.#dispatched.clear();
|
|
2321
|
+
for (const { timer } of this.#pendingApprovals.values()) clearTimeout(timer);
|
|
2322
|
+
this.#pendingApprovals.clear();
|
|
2286
2323
|
this.#emit({
|
|
2287
2324
|
type: "session_closed",
|
|
2288
2325
|
reason
|
|
@@ -2315,50 +2352,111 @@ var AiSdkRunner = class {
|
|
|
2315
2352
|
this.#applyExecutionResult(executionId, result);
|
|
2316
2353
|
return true;
|
|
2317
2354
|
}
|
|
2318
|
-
/** Hand every parked call the executor owns to it
|
|
2355
|
+
/** Hand every parked call the executor owns to it, gating on approval when
|
|
2356
|
+
* the permission mode requires it. */
|
|
2319
2357
|
#dispatchPending() {
|
|
2320
2358
|
const executor = this.#config.executor;
|
|
2321
2359
|
if (!executor) return;
|
|
2322
2360
|
const executable = this.#config.executableTools;
|
|
2361
|
+
const needsApproval = this.#permissionMode === "default" && this.#config.shouldApprove;
|
|
2323
2362
|
const inFlight = [];
|
|
2324
2363
|
let anyDeferred = false;
|
|
2364
|
+
let anyAwaiting = false;
|
|
2325
2365
|
for (const call of Array.from(this.#pendingToolCalls.values())) {
|
|
2326
2366
|
if (executable && !executable.includes(call.toolName)) continue;
|
|
2327
2367
|
if (this.#dispatched.has(call.toolCallId)) continue;
|
|
2328
|
-
|
|
2329
|
-
const toolCall = {
|
|
2330
|
-
executionId: call.toolCallId,
|
|
2331
|
-
sessionId: this.id,
|
|
2332
|
-
tool: call.toolName,
|
|
2333
|
-
input: call.input,
|
|
2334
|
-
vfs: this.#config.vfs,
|
|
2335
|
-
limits: this.#config.executionLimits,
|
|
2336
|
-
signal: this.#abort?.signal
|
|
2337
|
-
};
|
|
2338
|
-
const profile = executor.describe?.(toolCall) ?? {};
|
|
2339
|
-
call.deferred = profile.deferred === true ? true : void 0;
|
|
2340
|
-
call.expiresAt = profile.timeoutMs === void 0 ? void 0 : Date.now() + profile.timeoutMs;
|
|
2341
|
-
anyDeferred ||= call.deferred === true;
|
|
2342
|
-
this.#emit({
|
|
2343
|
-
type: "execution_dispatched",
|
|
2344
|
-
executionId: call.toolCallId,
|
|
2368
|
+
if (needsApproval && needsApproval({
|
|
2345
2369
|
toolName: call.toolName,
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
this.#
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2370
|
+
input: call.input
|
|
2371
|
+
})) {
|
|
2372
|
+
if ([...this.#pendingApprovals.values()].some((a) => a.toolCallId === call.toolCallId)) {
|
|
2373
|
+
anyAwaiting = true;
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
const requestId = randomUUID();
|
|
2377
|
+
const timeoutMs = this.#config.approvalTimeoutMs ?? 12e4;
|
|
2378
|
+
const request = {
|
|
2379
|
+
id: requestId,
|
|
2380
|
+
toolName: call.toolName,
|
|
2381
|
+
input: call.input,
|
|
2382
|
+
toolUseId: call.toolCallId,
|
|
2383
|
+
title: `Agent wants to run ${call.toolName}`,
|
|
2384
|
+
displayName: call.toolName,
|
|
2385
|
+
expiresAt: Date.now() + timeoutMs
|
|
2386
|
+
};
|
|
2387
|
+
const timer = setTimeout(() => {
|
|
2388
|
+
if (!this.#pendingApprovals.has(requestId)) return;
|
|
2389
|
+
this.resolvePermission(requestId, {
|
|
2390
|
+
behavior: "deny",
|
|
2391
|
+
message: "Approval timed out"
|
|
2392
|
+
});
|
|
2393
|
+
}, timeoutMs);
|
|
2394
|
+
this.#pendingApprovals.set(requestId, {
|
|
2395
|
+
request,
|
|
2396
|
+
toolCallId: call.toolCallId,
|
|
2397
|
+
timer
|
|
2357
2398
|
});
|
|
2358
|
-
|
|
2399
|
+
this.#emit({
|
|
2400
|
+
type: "permission_requested",
|
|
2401
|
+
request
|
|
2402
|
+
});
|
|
2403
|
+
anyAwaiting = true;
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
this.#dispatched.add(call.toolCallId);
|
|
2407
|
+
const dispatched = this.#dispatchCall(executor, call);
|
|
2408
|
+
anyDeferred ||= dispatched.deferred;
|
|
2409
|
+
inFlight.push(dispatched.promise);
|
|
2359
2410
|
}
|
|
2411
|
+
if (anyAwaiting) this.#setStatus("awaiting_approval");
|
|
2360
2412
|
if (anyDeferred) Promise.allSettled(inFlight).then(() => this.#announceParked());
|
|
2361
2413
|
}
|
|
2414
|
+
/** Dispatch a single tool call that was held behind an approval gate. */
|
|
2415
|
+
#dispatchSingle(toolCallId) {
|
|
2416
|
+
const executor = this.#config.executor;
|
|
2417
|
+
if (!executor) return;
|
|
2418
|
+
const call = this.#pendingToolCalls.get(toolCallId);
|
|
2419
|
+
if (!call || this.#dispatched.has(toolCallId)) return;
|
|
2420
|
+
this.#dispatched.add(toolCallId);
|
|
2421
|
+
const dispatched = this.#dispatchCall(executor, call);
|
|
2422
|
+
if (dispatched.deferred) dispatched.promise.then(() => this.#announceParked());
|
|
2423
|
+
}
|
|
2424
|
+
/** The actual dispatch + event emission for one tool call. */
|
|
2425
|
+
#dispatchCall(executor, call) {
|
|
2426
|
+
const toolCall = {
|
|
2427
|
+
executionId: call.toolCallId,
|
|
2428
|
+
sessionId: this.id,
|
|
2429
|
+
tool: call.toolName,
|
|
2430
|
+
input: call.input,
|
|
2431
|
+
vfs: this.#config.vfs,
|
|
2432
|
+
limits: this.#config.executionLimits,
|
|
2433
|
+
signal: this.#abort?.signal
|
|
2434
|
+
};
|
|
2435
|
+
const profile = executor.describe?.(toolCall) ?? {};
|
|
2436
|
+
call.deferred = profile.deferred === true ? true : void 0;
|
|
2437
|
+
call.expiresAt = profile.timeoutMs === void 0 ? void 0 : Date.now() + profile.timeoutMs;
|
|
2438
|
+
this.#emit({
|
|
2439
|
+
type: "execution_dispatched",
|
|
2440
|
+
executionId: call.toolCallId,
|
|
2441
|
+
toolName: call.toolName,
|
|
2442
|
+
backend: profile.backend ?? this.#config.executionBackend ?? "server",
|
|
2443
|
+
deferred: call.deferred,
|
|
2444
|
+
expiresAt: call.expiresAt
|
|
2445
|
+
});
|
|
2446
|
+
const promise = executor.dispatch(toolCall).then((dispatch) => {
|
|
2447
|
+
if (dispatch.status === "settled") this.#applyExecutionResult(call.toolCallId, dispatch.result);
|
|
2448
|
+
}).catch((error) => {
|
|
2449
|
+
this.#applyExecutionResult(call.toolCallId, {
|
|
2450
|
+
status: "failed",
|
|
2451
|
+
reason: "dispatch_error",
|
|
2452
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2453
|
+
});
|
|
2454
|
+
});
|
|
2455
|
+
return {
|
|
2456
|
+
deferred: call.deferred === true,
|
|
2457
|
+
promise
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2362
2460
|
/**
|
|
2363
2461
|
* The turn has come to rest on deferred executions: nothing is in flight, and
|
|
2364
2462
|
* only a host-delivered result can move it. `status_changed: 'parked'` is the
|
|
@@ -3579,7 +3677,19 @@ const CAPABILITY_TOOLS = {
|
|
|
3579
3677
|
*/
|
|
3580
3678
|
function createEngineSession(options) {
|
|
3581
3679
|
const vfs = options.config.vfs ?? createVfs(options.config.restore ? options.config.restore.vfs : options.seedVfs);
|
|
3582
|
-
const executor = options.selectExecutor
|
|
3680
|
+
const executor = options.selectExecutor.length > 0 ? {
|
|
3681
|
+
describe(call) {
|
|
3682
|
+
const target = options.selectExecutor(call);
|
|
3683
|
+
const backend = typeof options.backend === "function" ? options.backend(call) : options.backend;
|
|
3684
|
+
return {
|
|
3685
|
+
...target.describe?.(call),
|
|
3686
|
+
...backend ? { backend } : {}
|
|
3687
|
+
};
|
|
3688
|
+
},
|
|
3689
|
+
dispatch(call) {
|
|
3690
|
+
return options.selectExecutor(call).dispatch(call);
|
|
3691
|
+
}
|
|
3692
|
+
} : options.selectExecutor();
|
|
3583
3693
|
const granted = options.config.capabilities ?? options.profile?.session?.capabilities;
|
|
3584
3694
|
const isGranted = (key) => granted === void 0 || granted.includes(CAPABILITY_TOOLS[key]);
|
|
3585
3695
|
let runner;
|
|
@@ -3613,8 +3723,10 @@ function createEngineSession(options) {
|
|
|
3613
3723
|
vfs,
|
|
3614
3724
|
executor,
|
|
3615
3725
|
executableTools: context.sandboxedToolNames,
|
|
3616
|
-
executionBackend: options.backend ?? "server",
|
|
3726
|
+
executionBackend: typeof options.backend === "function" ? void 0 : options.backend ?? "server",
|
|
3617
3727
|
executionLimits: options.executionLimits,
|
|
3728
|
+
shouldApprove: options.shouldApprove,
|
|
3729
|
+
approvalTimeoutMs: options.approvalTimeoutMs,
|
|
3618
3730
|
reportMcpServers: options.mcp ? () => Promise.resolve(declaredServers === void 0 ? options.mcp.servers : options.mcp.servers.filter((s) => declaredServers.includes(s.name))) : void 0
|
|
3619
3731
|
}, options.id);
|
|
3620
3732
|
return runner;
|