@nylorun/runtime 0.13.0-beta → 0.14.0-beta
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/CHANGELOG.md +330 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +7825 -0
- package/dist/api/components.js +83 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +130 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +259 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +10 -0
- package/dist/api/http/routes/tenant.js +172 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +138 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25696 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +29 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.js +32 -0
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +107 -113
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +78 -37
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
package/dist/tenant/effects.js
CHANGED
|
@@ -3,34 +3,49 @@ import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowEffect, isFlowTo
|
|
|
3
3
|
import { mayDispatchMore } from "../core/limits.js";
|
|
4
4
|
import { canonical } from "../store/canonical.js";
|
|
5
5
|
import { isOwnershipLost } from "../store/ownership.js";
|
|
6
|
-
import { newStreamIncarnation } from "../streams/types.js";
|
|
7
6
|
import { piModel } from "../model/pi-model.js";
|
|
8
7
|
import { scrub } from "../redact.js";
|
|
9
8
|
import { serversOf } from "../mcp/pool.js";
|
|
10
9
|
import { sandboxCapabilityOf } from "../sandbox/manager.js";
|
|
11
|
-
import { owningSandboxSessionId, sandboxSpecOf } from "../sandbox/share.js";
|
|
10
|
+
import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
11
|
+
import { withSandboxCapability } from "../sandbox/session-sandbox.js";
|
|
12
12
|
import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
|
|
13
13
|
import { fail } from "./http.js";
|
|
14
14
|
import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, turnManifestOf, } from "./session.js";
|
|
15
15
|
import { command } from "./commands.js";
|
|
16
|
-
import {
|
|
16
|
+
import { offerAction } from "./delivery.js";
|
|
17
|
+
import { assistantMessage, contextCompacted, modelFailed, toolCompleted, toolIds, } from "./transcript.js";
|
|
18
|
+
import { classifyThrown } from "../model/classify.js";
|
|
17
19
|
import { abortKind } from "./worker.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Call the model for one effect. A provider failure comes back as a failure outcome
|
|
22
|
+
* (Model Calls §6), whichever provider serves the call; only an abort throws, and the
|
|
23
|
+
* advance decides what the abort means.
|
|
24
|
+
*/
|
|
25
|
+
export async function invokeModel(ctx, request, signal, model) {
|
|
26
|
+
try {
|
|
27
|
+
if (model)
|
|
28
|
+
return await model(request, signal);
|
|
29
|
+
if (!ctx.useVaultModel)
|
|
30
|
+
return await ctx.modelProvider(request, signal);
|
|
31
|
+
const adapter = piModel({
|
|
32
|
+
root: ctx.config.paths.home,
|
|
33
|
+
readHostModel: () => ctx.vault.readHostModel(),
|
|
34
|
+
writeHostCredential: (credential) => ctx.vault.updateHostCredential(credential),
|
|
35
|
+
...(ctx.config.modelCall ? { settings: ctx.config.modelCall } : {}),
|
|
36
|
+
});
|
|
37
|
+
return await adapter(request.input, {
|
|
38
|
+
request: request.context.request,
|
|
39
|
+
invocationId: String(request.context.invocationId),
|
|
40
|
+
signal,
|
|
41
|
+
reportPreparedCall() { },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (signal.aborted)
|
|
46
|
+
throw error;
|
|
47
|
+
return classifyThrown(error);
|
|
48
|
+
}
|
|
34
49
|
}
|
|
35
50
|
/**
|
|
36
51
|
* The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
|
|
@@ -159,8 +174,6 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
|
|
|
159
174
|
context: request.context,
|
|
160
175
|
status: "pending",
|
|
161
176
|
generation: 0,
|
|
162
|
-
claimId: null,
|
|
163
|
-
leaseExpiresAt: null,
|
|
164
177
|
...(request.agent ? { agent: request.agent } : {}),
|
|
165
178
|
};
|
|
166
179
|
const action = request.kind === "hook"
|
|
@@ -189,7 +202,7 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
|
|
|
189
202
|
...(action.kind === "tool" ? toolIds(request.context) : {}),
|
|
190
203
|
input: action.input,
|
|
191
204
|
});
|
|
192
|
-
t
|
|
205
|
+
await offerAction(t, ctx, action);
|
|
193
206
|
return resolved({ status: "pending" });
|
|
194
207
|
});
|
|
195
208
|
if (journaled.kind === "resolved")
|
|
@@ -219,11 +232,23 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
|
|
|
219
232
|
effect.status = "completed";
|
|
220
233
|
effect.outcome = { value };
|
|
221
234
|
await t.put("effects", request.effectId, effect);
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
const failed = invoke === "model" ? modelFailed(request, value) : undefined;
|
|
236
|
+
// A summary call is never an assistant message; the last one publishes context.compacted.
|
|
237
|
+
const summarizing = invoke === "model" && Boolean(request.context.compaction);
|
|
238
|
+
const compacted = summarizing && !failed ? contextCompacted(request, value) : undefined;
|
|
239
|
+
const transcript = failed || summarizing
|
|
240
|
+
? undefined
|
|
241
|
+
: invoke === "model"
|
|
242
|
+
? assistantMessage(request, value)
|
|
243
|
+
: toolCompleted(request, value);
|
|
244
|
+
if (failed)
|
|
245
|
+
await t.event(s.id, request.turnId, "model.failed", failed);
|
|
246
|
+
if (compacted)
|
|
247
|
+
await t.event(s.id, request.turnId, "context.compacted", compacted);
|
|
248
|
+
if (transcript && invoke === "model")
|
|
249
|
+
await t.event(s.id, request.turnId, "message.assistant", transcript);
|
|
250
|
+
else if (transcript)
|
|
251
|
+
await t.event(s.id, request.turnId, "tool.completed", transcript);
|
|
227
252
|
return { status: "completed", outcome: effect.outcome };
|
|
228
253
|
});
|
|
229
254
|
}
|
|
@@ -312,8 +337,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
312
337
|
context: request.context,
|
|
313
338
|
status: "pending",
|
|
314
339
|
generation: 0,
|
|
315
|
-
claimId: null,
|
|
316
|
-
leaseExpiresAt: null,
|
|
317
340
|
kind: request.kind,
|
|
318
341
|
path: request.path,
|
|
319
342
|
key: request.key,
|
|
@@ -329,8 +352,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
329
352
|
context: request.context,
|
|
330
353
|
status: "pending",
|
|
331
354
|
generation: 0,
|
|
332
|
-
claimId: null,
|
|
333
|
-
leaseExpiresAt: null,
|
|
334
355
|
kind: "tool",
|
|
335
356
|
path: request.path,
|
|
336
357
|
key: request.key,
|
|
@@ -353,7 +374,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
353
374
|
key: action.key,
|
|
354
375
|
input: action.input,
|
|
355
376
|
});
|
|
356
|
-
t
|
|
377
|
+
await offerAction(t, ctx, action);
|
|
357
378
|
return resolved({ status: "pending" });
|
|
358
379
|
}
|
|
359
380
|
return { kind: "agent", workflow };
|
|
@@ -388,15 +409,18 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
388
409
|
if (exists)
|
|
389
410
|
return;
|
|
390
411
|
const definition = await leafDefinition(t, workflow, body, request);
|
|
391
|
-
const
|
|
392
|
-
|
|
412
|
+
const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
|
|
413
|
+
const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
|
|
414
|
+
? owningSandboxSessionId(workflow, lookup)
|
|
393
415
|
: undefined;
|
|
416
|
+
// A sandbox chosen when the tree was opened reaches every agent in it, with its tools.
|
|
417
|
+
const inherited = inheritedSandbox(sandboxOwnerId === undefined ? undefined : lookup(sandboxOwnerId) ?? workflow, definition);
|
|
394
418
|
const created = {
|
|
395
419
|
id: agentSessionId,
|
|
396
420
|
agentId: body.agentId,
|
|
397
421
|
ownerUserId: workflow.ownerUserId,
|
|
398
|
-
manifest: definition.manifest,
|
|
399
|
-
manifestHash: definition.manifestHash,
|
|
422
|
+
manifest: inherited?.manifest ?? definition.manifest,
|
|
423
|
+
manifestHash: inherited?.manifestHash ?? definition.manifestHash,
|
|
400
424
|
implementationVersion: definition.implementationVersion,
|
|
401
425
|
status: "idle",
|
|
402
426
|
activeTurnId: null,
|
|
@@ -410,7 +434,9 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
410
434
|
credentialSelections: workflow.credentialSelections,
|
|
411
435
|
pluginRoots: definition.pluginRoots ?? {},
|
|
412
436
|
...(sandboxOwnerId ? { sandboxOwnerId } : {}),
|
|
413
|
-
|
|
437
|
+
...(inherited
|
|
438
|
+
? { sandbox: inherited.spec, sandboxSource: "shared" }
|
|
439
|
+
: {}),
|
|
414
440
|
};
|
|
415
441
|
await t.put("sessions", agentSessionId, created);
|
|
416
442
|
});
|
|
@@ -534,6 +560,21 @@ async function callMcpTool(ctx, request) {
|
|
|
534
560
|
pluginRoots: s.pluginRoots ?? {},
|
|
535
561
|
});
|
|
536
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* The sandbox a linked session inherits from its tree's owner when that sandbox was chosen at
|
|
565
|
+
* open (pinned on the owner). A definition that declares its own sandbox keeps it, as before.
|
|
566
|
+
*/
|
|
567
|
+
function inheritedSandbox(owner, definition) {
|
|
568
|
+
const spec = owner?.sandbox;
|
|
569
|
+
if (!spec || sandboxSpecOf(definition.manifest))
|
|
570
|
+
return undefined;
|
|
571
|
+
if (isWorkflowManifest(definition.manifest))
|
|
572
|
+
return { spec };
|
|
573
|
+
const sandboxed = withSandboxCapability(definition.manifest, spec);
|
|
574
|
+
if (!sandboxed.ok)
|
|
575
|
+
throw new Error(sandboxed.message);
|
|
576
|
+
return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
|
|
577
|
+
}
|
|
537
578
|
async function callSandboxTool(ctx, request, signal) {
|
|
538
579
|
const { s, ownerId } = await ctx.store.tx(async (t) => {
|
|
539
580
|
const s = await sessionOf(t, request.sessionId);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
|
|
3
|
+
* Actions, registered with the application key.
|
|
4
|
+
*/
|
|
5
|
+
import { type Endpoint, type PutEndpointsRequest } from "@nylorun/core/contracts";
|
|
6
|
+
import type { EndpointRow } from "../store/types.js";
|
|
7
|
+
import type { TenantContext } from "./context.js";
|
|
8
|
+
/** An endpoint as the Tenant API answers it. */
|
|
9
|
+
export declare function endpointView(row: EndpointRow): Endpoint;
|
|
10
|
+
/** `GET /v1/endpoints`. */
|
|
11
|
+
export declare function listEndpoints(ctx: TenantContext): Promise<{
|
|
12
|
+
endpoints: {
|
|
13
|
+
agentId: string;
|
|
14
|
+
url: string;
|
|
15
|
+
implementationVersion: string;
|
|
16
|
+
manifestHash?: string | undefined;
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
maxConcurrent: number;
|
|
19
|
+
health: {
|
|
20
|
+
lastDeliveryAt?: string | undefined;
|
|
21
|
+
lastSuccessAt?: string | undefined;
|
|
22
|
+
lastError?: {
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
} | undefined;
|
|
26
|
+
consecutiveFailures: number;
|
|
27
|
+
served?: {
|
|
28
|
+
implementationVersion: string;
|
|
29
|
+
manifestHash?: string | undefined;
|
|
30
|
+
} | undefined;
|
|
31
|
+
};
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
|
|
37
|
+
* waiting for these agents. Answers every registered endpoint of the batch.
|
|
38
|
+
*/
|
|
39
|
+
export declare function putEndpoints(ctx: TenantContext, principalId: string, body: PutEndpointsRequest): Promise<{
|
|
40
|
+
endpoints: {
|
|
41
|
+
agentId: string;
|
|
42
|
+
url: string;
|
|
43
|
+
implementationVersion: string;
|
|
44
|
+
manifestHash?: string | undefined;
|
|
45
|
+
timeoutMs: number;
|
|
46
|
+
maxConcurrent: number;
|
|
47
|
+
health: {
|
|
48
|
+
lastDeliveryAt?: string | undefined;
|
|
49
|
+
lastSuccessAt?: string | undefined;
|
|
50
|
+
lastError?: {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
} | undefined;
|
|
54
|
+
consecutiveFailures: number;
|
|
55
|
+
served?: {
|
|
56
|
+
implementationVersion: string;
|
|
57
|
+
manifestHash?: string | undefined;
|
|
58
|
+
} | undefined;
|
|
59
|
+
};
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
}[];
|
|
62
|
+
}>;
|
|
63
|
+
/** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
|
|
64
|
+
export declare function deleteEndpoint(ctx: TenantContext, agentId: string): Promise<{
|
|
65
|
+
agentId: string;
|
|
66
|
+
deleted: true;
|
|
67
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
|
|
3
|
+
* Actions, registered with the application key.
|
|
4
|
+
*/
|
|
5
|
+
import { ENDPOINT_MAX_CONCURRENT_DEFAULT, ENDPOINT_TIMEOUT_DEFAULT_MS, } from "@nylorun/core/contracts";
|
|
6
|
+
import { fail } from "./http.js";
|
|
7
|
+
/** An endpoint as the Tenant API answers it. */
|
|
8
|
+
export function endpointView(row) {
|
|
9
|
+
const served = row.servedImplementationVersion === undefined
|
|
10
|
+
? undefined
|
|
11
|
+
: {
|
|
12
|
+
implementationVersion: row.servedImplementationVersion,
|
|
13
|
+
...(row.servedManifestHash === undefined
|
|
14
|
+
? {}
|
|
15
|
+
: { manifestHash: row.servedManifestHash }),
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
agentId: row.agentId,
|
|
19
|
+
url: row.url,
|
|
20
|
+
implementationVersion: row.implementationVersion,
|
|
21
|
+
...(row.manifestHash === undefined ? {} : { manifestHash: row.manifestHash }),
|
|
22
|
+
timeoutMs: row.timeoutMs,
|
|
23
|
+
maxConcurrent: row.maxConcurrent,
|
|
24
|
+
health: {
|
|
25
|
+
...(row.lastDeliveryAt === undefined ? {} : { lastDeliveryAt: row.lastDeliveryAt }),
|
|
26
|
+
...(row.lastSuccessAt === undefined ? {} : { lastSuccessAt: row.lastSuccessAt }),
|
|
27
|
+
...(row.lastErrorCode === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: {
|
|
30
|
+
lastError: {
|
|
31
|
+
code: row.lastErrorCode,
|
|
32
|
+
message: row.lastErrorMessage ?? "",
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
consecutiveFailures: row.consecutiveFailures,
|
|
36
|
+
...(served ? { served } : {}),
|
|
37
|
+
},
|
|
38
|
+
updatedAt: row.updatedAt,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** `GET /v1/endpoints`. */
|
|
42
|
+
export async function listEndpoints(ctx) {
|
|
43
|
+
const rows = await ctx.store.tx((t) => t.listEndpoints());
|
|
44
|
+
return { endpoints: rows.map(endpointView) };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
|
|
48
|
+
* waiting for these agents. Answers every registered endpoint of the batch.
|
|
49
|
+
*/
|
|
50
|
+
export async function putEndpoints(ctx, principalId, body) {
|
|
51
|
+
const updatedAt = new Date().toISOString();
|
|
52
|
+
const rows = await ctx.store.tx(async (t) => {
|
|
53
|
+
const saved = [];
|
|
54
|
+
for (const endpoint of body.endpoints) {
|
|
55
|
+
await t.putEndpoint({
|
|
56
|
+
agentId: endpoint.agentId,
|
|
57
|
+
url: endpoint.url,
|
|
58
|
+
implementationVersion: endpoint.implementationVersion,
|
|
59
|
+
...(endpoint.manifestHash === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { manifestHash: endpoint.manifestHash }),
|
|
62
|
+
timeoutMs: endpoint.timeoutMs ?? ENDPOINT_TIMEOUT_DEFAULT_MS,
|
|
63
|
+
maxConcurrent: endpoint.maxConcurrent ?? ENDPOINT_MAX_CONCURRENT_DEFAULT,
|
|
64
|
+
principalId,
|
|
65
|
+
updatedAt,
|
|
66
|
+
});
|
|
67
|
+
saved.push((await t.getEndpoint(endpoint.agentId)));
|
|
68
|
+
}
|
|
69
|
+
return saved;
|
|
70
|
+
});
|
|
71
|
+
// Actions already waiting for these agents go to their endpoint now.
|
|
72
|
+
const waiting = await ctx.store.tx(async (t) => {
|
|
73
|
+
const actions = [];
|
|
74
|
+
for (const endpoint of body.endpoints)
|
|
75
|
+
actions.push(...(await t.pendingActions(endpoint.agentId)));
|
|
76
|
+
return actions;
|
|
77
|
+
});
|
|
78
|
+
for (const action of waiting)
|
|
79
|
+
await ctx.deliver(action.actionId);
|
|
80
|
+
return { endpoints: rows.map(endpointView) };
|
|
81
|
+
}
|
|
82
|
+
/** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
|
|
83
|
+
export async function deleteEndpoint(ctx, agentId) {
|
|
84
|
+
await ctx.store.tx(async (t) => {
|
|
85
|
+
if (!(await t.getEndpoint(agentId)))
|
|
86
|
+
fail(404, "Endpoint not found");
|
|
87
|
+
await t.deleteEndpoint(agentId);
|
|
88
|
+
});
|
|
89
|
+
return { agentId, deleted: true };
|
|
90
|
+
}
|
|
@@ -19,6 +19,10 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
19
19
|
sandboxBackend?: "auto" | "virtual";
|
|
20
20
|
/** When true, close() leaves hostRoot on disk. */
|
|
21
21
|
retainRoot?: boolean;
|
|
22
|
+
/** Allow browser requests (an `Origin` with a publishable key). Default off. */
|
|
23
|
+
browserAccess?: boolean;
|
|
24
|
+
/** Serve the Admin API on its own loopback listener (`adminUrl`). Default off. */
|
|
25
|
+
operatorListener?: boolean;
|
|
22
26
|
logger?: Logger;
|
|
23
27
|
/**
|
|
24
28
|
* A Postgres pool: Tenants become schemas in it (`store-pg.ts`) instead of living in memory.
|
|
@@ -28,6 +32,8 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
28
32
|
}
|
|
29
33
|
export interface EphemeralRuntime {
|
|
30
34
|
url: string;
|
|
35
|
+
/** Where the Admin API answers: `url`, or the operator listener when requested. */
|
|
36
|
+
adminUrl: string;
|
|
31
37
|
tenantId: string;
|
|
32
38
|
applicationKey: string;
|
|
33
39
|
adminKey: string;
|
package/dist/tenant/ephemeral.js
CHANGED
|
@@ -13,7 +13,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
13
13
|
import { randomBytes } from "node:crypto";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import { newTenantId } from "@nylorun/core/compatibility";
|
|
16
|
-
import { hashToken, mintBearerToken } from "../core/
|
|
16
|
+
import { hashToken, mintBearerToken } from "../core/bearer.js";
|
|
17
17
|
import { createHost } from "../host/create-host.js";
|
|
18
18
|
import { createKekFile } from "../vault/kek.js";
|
|
19
19
|
import { MemorySessionStore } from "../store/memory.js";
|
|
@@ -95,6 +95,7 @@ function kept(store) {
|
|
|
95
95
|
tenantId: store.tenantId,
|
|
96
96
|
tx: (fn) => store.tx(fn),
|
|
97
97
|
onCommit: (listener) => store.onCommit(listener),
|
|
98
|
+
record: () => store.record(),
|
|
98
99
|
health: () => store.health(),
|
|
99
100
|
close: async () => { },
|
|
100
101
|
};
|
|
@@ -173,37 +174,65 @@ export async function startEphemeralRuntime(options) {
|
|
|
173
174
|
logger,
|
|
174
175
|
onDeleted: (tenantId) => streams.deleteTenant(tenantId),
|
|
175
176
|
});
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
177
|
+
let host;
|
|
178
|
+
/** Closes what was opened, in reverse order; `close()` and a failed start share it. */
|
|
179
|
+
const release = async () => {
|
|
180
|
+
await host?.close();
|
|
181
|
+
await module.close();
|
|
182
|
+
await memory?.close();
|
|
183
|
+
await streams.close();
|
|
184
|
+
if (!options.retainRoot)
|
|
185
|
+
rmSync(hostRoot, { recursive: true, force: true });
|
|
186
|
+
};
|
|
187
|
+
let tenantId;
|
|
188
|
+
let applicationKey;
|
|
189
|
+
let principalId;
|
|
190
|
+
try {
|
|
191
|
+
await module.start();
|
|
192
|
+
tenantId = options.tenantId ?? newTenantId();
|
|
193
|
+
applicationKey = options.applicationKey ?? mintBearerToken();
|
|
194
|
+
principalId =
|
|
195
|
+
options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
|
|
196
|
+
const credentialHash = hashToken(applicationKey);
|
|
197
|
+
await module.create({
|
|
198
|
+
tenantId,
|
|
199
|
+
name: options.name ?? "ephemeral",
|
|
200
|
+
principalId,
|
|
201
|
+
credentialHash,
|
|
202
|
+
idempotencyKey: `ephemeral-${tenantId}`,
|
|
203
|
+
...(options.studioCredentialHash
|
|
204
|
+
? { studioCredentialHash: options.studioCredentialHash }
|
|
205
|
+
: {}),
|
|
206
|
+
});
|
|
207
|
+
// KEK for first vault write; openTenantRuntime also creates when hooks allow.
|
|
208
|
+
const kekPath = tenantPaths(hostRoot, tenantId).kek;
|
|
209
|
+
if (!existsSync(kekPath))
|
|
210
|
+
createKekFile(kekPath);
|
|
211
|
+
host = createHost({
|
|
212
|
+
hostRoot,
|
|
213
|
+
module,
|
|
214
|
+
config: hostConfig,
|
|
215
|
+
credentials,
|
|
216
|
+
logger,
|
|
217
|
+
coreVersion: coreVersion(),
|
|
218
|
+
browserAccess: options.browserAccess === true,
|
|
219
|
+
...(options.operatorListener
|
|
220
|
+
? { operator: { host: "127.0.0.1", port: 0 } }
|
|
221
|
+
: {}),
|
|
222
|
+
});
|
|
223
|
+
await host.listen();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
await release().catch((cleanup) => logger.warn("ephemeral runtime cleanup failed", {
|
|
227
|
+
message: cleanup instanceof Error ? cleanup.message : String(cleanup),
|
|
228
|
+
}));
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
const started = host;
|
|
204
232
|
let closed = false;
|
|
205
233
|
return {
|
|
206
|
-
url:
|
|
234
|
+
url: started.url,
|
|
235
|
+
adminUrl: started.adminUrl,
|
|
207
236
|
tenantId,
|
|
208
237
|
applicationKey,
|
|
209
238
|
adminKey,
|
|
@@ -213,12 +242,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
213
242
|
if (closed)
|
|
214
243
|
return;
|
|
215
244
|
closed = true;
|
|
216
|
-
await
|
|
217
|
-
await module.close();
|
|
218
|
-
await memory?.close();
|
|
219
|
-
await streams.close();
|
|
220
|
-
if (!options.retainRoot)
|
|
221
|
-
rmSync(hostRoot, { recursive: true, force: true });
|
|
245
|
+
await release();
|
|
222
246
|
},
|
|
223
247
|
};
|
|
224
248
|
}
|
package/dist/tenant/http.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* The Tenant's typed rejections: what a route or the code it calls throws for the client to
|
|
3
|
+
* see (`api/http/respond.ts` answers it), and the opaque 404 for unknown credentials (D5).
|
|
4
4
|
*
|
|
5
5
|
* Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
|
|
6
6
|
*/
|
|
7
|
-
import type { IncomingMessage } from "node:http";
|
|
8
7
|
/** D5 opaque failure for unknown/rejected credentials on Tenant routes. */
|
|
9
8
|
export declare const OPAQUE_NOT_FOUND: {
|
|
10
9
|
status: "rejected";
|
|
@@ -19,9 +18,13 @@ export interface Rejection {
|
|
|
19
18
|
export declare class HttpError extends Error {
|
|
20
19
|
readonly status: number;
|
|
21
20
|
readonly rejection: Rejection;
|
|
22
|
-
|
|
21
|
+
/** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
|
|
22
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
23
|
+
constructor(status: number, message: string, rejection?: Rejection,
|
|
24
|
+
/** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
|
|
25
|
+
headers?: Readonly<Record<string, string>>);
|
|
23
26
|
}
|
|
24
|
-
export declare const fail: (status: number, message: string, rejection?: Rejection) => never;
|
|
27
|
+
export declare const fail: (status: number, message: string, rejection?: Rejection, headers?: Readonly<Record<string, string>>) => never;
|
|
25
28
|
export declare class OpaqueAuthError extends Error {
|
|
26
29
|
readonly status = 404;
|
|
27
30
|
readonly body: {
|
|
@@ -32,7 +35,3 @@ export declare class OpaqueAuthError extends Error {
|
|
|
32
35
|
constructor();
|
|
33
36
|
}
|
|
34
37
|
export declare const failOpaque: () => never;
|
|
35
|
-
/** AbortSignal tied to the HTTP request being closed by the client. */
|
|
36
|
-
export declare function requestAborted(request: IncomingMessage): AbortSignal;
|
|
37
|
-
/** Read a JSON request body, capped at 1 MiB. */
|
|
38
|
-
export declare function readBody(request: IncomingMessage): Promise<unknown>;
|
package/dist/tenant/http.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant's typed rejections: what a route or the code it calls throws for the client to
|
|
3
|
+
* see (`api/http/respond.ts` answers it), and the opaque 404 for unknown credentials (D5).
|
|
4
|
+
*
|
|
5
|
+
* Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
|
|
6
|
+
*/
|
|
1
7
|
/** D5 opaque failure for unknown/rejected credentials on Tenant routes. */
|
|
2
8
|
export const OPAQUE_NOT_FOUND = {
|
|
3
9
|
status: "rejected",
|
|
@@ -7,14 +13,18 @@ export const OPAQUE_NOT_FOUND = {
|
|
|
7
13
|
export class HttpError extends Error {
|
|
8
14
|
status;
|
|
9
15
|
rejection;
|
|
10
|
-
|
|
16
|
+
headers;
|
|
17
|
+
constructor(status, message, rejection = {},
|
|
18
|
+
/** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
|
|
19
|
+
headers = {}) {
|
|
11
20
|
super(message);
|
|
12
21
|
this.status = status;
|
|
13
22
|
this.rejection = rejection;
|
|
23
|
+
this.headers = headers;
|
|
14
24
|
}
|
|
15
25
|
}
|
|
16
|
-
export const fail = (status, message, rejection) => {
|
|
17
|
-
throw new HttpError(status, message, rejection);
|
|
26
|
+
export const fail = (status, message, rejection, headers) => {
|
|
27
|
+
throw new HttpError(status, message, rejection, headers);
|
|
18
28
|
};
|
|
19
29
|
export class OpaqueAuthError extends Error {
|
|
20
30
|
status = 404;
|
|
@@ -27,27 +37,3 @@ export class OpaqueAuthError extends Error {
|
|
|
27
37
|
export const failOpaque = () => {
|
|
28
38
|
throw new OpaqueAuthError();
|
|
29
39
|
};
|
|
30
|
-
/** AbortSignal tied to the HTTP request being closed by the client. */
|
|
31
|
-
export function requestAborted(request) {
|
|
32
|
-
const controller = new AbortController();
|
|
33
|
-
request.on("close", () => {
|
|
34
|
-
if (!request.complete)
|
|
35
|
-
controller.abort();
|
|
36
|
-
});
|
|
37
|
-
return controller.signal;
|
|
38
|
-
}
|
|
39
|
-
/** Read a JSON request body, capped at 1 MiB. */
|
|
40
|
-
export async function readBody(request) {
|
|
41
|
-
let data = "";
|
|
42
|
-
for await (const chunk of request) {
|
|
43
|
-
data += chunk;
|
|
44
|
-
if (Buffer.byteLength(data) > 1024 * 1024)
|
|
45
|
-
fail(413, "Request too large");
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
return JSON.parse(data);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return fail(400, "Invalid JSON");
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const MAX_TOKEN_BYTES = 4096;
|
|
2
|
+
export declare const CLOCK_TOLERANCE_SECONDS = 30;
|
|
3
|
+
/** True when `bearer` has the shape of a JWT; application keys never do. */
|
|
4
|
+
export declare function looksLikeToken(bearer: string): boolean;
|
|
5
|
+
/** The `typ` of a JWT-shaped bearer, or `undefined` when its header cannot be read. */
|
|
6
|
+
export declare function tokenType(raw: string): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Why the header of a token of type `typ` is refused before any key is read, or its key id
|
|
9
|
+
* when it is not: `malformed` (size, shape, undecodable) or `header` (algorithm, type, key id,
|
|
10
|
+
* or a header that could name another key).
|
|
11
|
+
*/
|
|
12
|
+
export declare function tokenKeyId(raw: string, typ: string): {
|
|
13
|
+
kid: string;
|
|
14
|
+
} | {
|
|
15
|
+
refused: "malformed" | "header";
|
|
16
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What subject tokens and delivery tokens share: the Tenant's ES256 JWTs, recognised by shape,
|
|
3
|
+
* told apart by their `typ`, and refused before any key is read when the header is not one the
|
|
4
|
+
* Runtime issues.
|
|
5
|
+
*/
|
|
6
|
+
import { decodeProtectedHeader } from "jose";
|
|
7
|
+
import { SIGNING_KEY_ID_PATTERN } from "@nylorun/core/compatibility";
|
|
8
|
+
export const MAX_TOKEN_BYTES = 4096;
|
|
9
|
+
export const CLOCK_TOLERANCE_SECONDS = 30;
|
|
10
|
+
const JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
11
|
+
const FORBIDDEN_HEADERS = ["jku", "jwk", "x5u", "x5c", "x5t", "crit"];
|
|
12
|
+
/** True when `bearer` has the shape of a JWT; application keys never do. */
|
|
13
|
+
export function looksLikeToken(bearer) {
|
|
14
|
+
return JWT_SHAPE.test(bearer);
|
|
15
|
+
}
|
|
16
|
+
/** The `typ` of a JWT-shaped bearer, or `undefined` when its header cannot be read. */
|
|
17
|
+
export function tokenType(raw) {
|
|
18
|
+
if (Buffer.byteLength(raw) > MAX_TOKEN_BYTES || !JWT_SHAPE.test(raw))
|
|
19
|
+
return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const typ = decodeProtectedHeader(raw).typ;
|
|
22
|
+
return typeof typ === "string" ? typ : undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Why the header of a token of type `typ` is refused before any key is read, or its key id
|
|
30
|
+
* when it is not: `malformed` (size, shape, undecodable) or `header` (algorithm, type, key id,
|
|
31
|
+
* or a header that could name another key).
|
|
32
|
+
*/
|
|
33
|
+
export function tokenKeyId(raw, typ) {
|
|
34
|
+
if (Buffer.byteLength(raw) > MAX_TOKEN_BYTES || !JWT_SHAPE.test(raw))
|
|
35
|
+
return { refused: "malformed" };
|
|
36
|
+
let header;
|
|
37
|
+
try {
|
|
38
|
+
header = decodeProtectedHeader(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { refused: "malformed" };
|
|
42
|
+
}
|
|
43
|
+
if (header.alg !== "ES256" ||
|
|
44
|
+
header.typ !== typ ||
|
|
45
|
+
typeof header.kid !== "string" ||
|
|
46
|
+
!SIGNING_KEY_ID_PATTERN.test(header.kid) ||
|
|
47
|
+
FORBIDDEN_HEADERS.some((name) => name in header))
|
|
48
|
+
return { refused: "header" };
|
|
49
|
+
return { kid: header.kid };
|
|
50
|
+
}
|