@llui/agent 0.0.30 → 0.0.31
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/dist/server/cloudflare/durable-object.d.ts +58 -0
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -0
- package/dist/server/cloudflare/durable-object.js +33 -0
- package/dist/server/cloudflare/durable-object.js.map +1 -0
- package/dist/server/cloudflare/index.d.ts +49 -0
- package/dist/server/cloudflare/index.d.ts.map +1 -0
- package/dist/server/cloudflare/index.js +49 -0
- package/dist/server/cloudflare/index.js.map +1 -0
- package/dist/server/cloudflare/worker.d.ts +40 -0
- package/dist/server/cloudflare/worker.d.ts.map +1 -0
- package/dist/server/cloudflare/worker.js +55 -0
- package/dist/server/cloudflare/worker.js.map +1 -0
- package/dist/server/core-entry.d.ts +27 -0
- package/dist/server/core-entry.d.ts.map +1 -0
- package/dist/server/core-entry.js +19 -0
- package/dist/server/core-entry.js.map +1 -0
- package/dist/server/core.d.ts +78 -0
- package/dist/server/core.d.ts.map +1 -0
- package/dist/server/core.js +84 -0
- package/dist/server/core.js.map +1 -0
- package/dist/server/factory.d.ts +5 -3
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +18 -58
- package/dist/server/factory.js.map +1 -1
- package/dist/server/http/mint.d.ts.map +1 -1
- package/dist/server/http/mint.js +2 -3
- package/dist/server/http/mint.js.map +1 -1
- package/dist/server/http/resume.js +1 -1
- package/dist/server/http/resume.js.map +1 -1
- package/dist/server/identity.d.ts +5 -1
- package/dist/server/identity.d.ts.map +1 -1
- package/dist/server/identity.js +49 -11
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +16 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +13 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lap/confirm-result.d.ts +2 -2
- package/dist/server/lap/confirm-result.d.ts.map +1 -1
- package/dist/server/lap/confirm-result.js +1 -1
- package/dist/server/lap/confirm-result.js.map +1 -1
- package/dist/server/lap/describe.d.ts +4 -4
- package/dist/server/lap/describe.d.ts.map +1 -1
- package/dist/server/lap/describe.js +4 -4
- package/dist/server/lap/describe.js.map +1 -1
- package/dist/server/lap/forward.d.ts +2 -2
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +1 -1
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/message.d.ts +2 -2
- package/dist/server/lap/message.d.ts.map +1 -1
- package/dist/server/lap/message.js +1 -1
- package/dist/server/lap/message.js.map +1 -1
- package/dist/server/lap/observe.d.ts +2 -2
- package/dist/server/lap/observe.d.ts.map +1 -1
- package/dist/server/lap/observe.js +1 -1
- package/dist/server/lap/observe.js.map +1 -1
- package/dist/server/lap/wait.d.ts +2 -2
- package/dist/server/lap/wait.d.ts.map +1 -1
- package/dist/server/lap/wait.js +1 -1
- package/dist/server/lap/wait.js.map +1 -1
- package/dist/server/options.d.ts +25 -1
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/token.d.ts +7 -3
- package/dist/server/token.d.ts.map +1 -1
- package/dist/server/token.js +66 -26
- package/dist/server/token.js.map +1 -1
- package/dist/server/web/adapter.d.ts +16 -0
- package/dist/server/web/adapter.d.ts.map +1 -0
- package/dist/server/web/adapter.js +45 -0
- package/dist/server/web/adapter.js.map +1 -0
- package/dist/server/web/index.d.ts +12 -0
- package/dist/server/web/index.d.ts.map +1 -0
- package/dist/server/web/index.js +12 -0
- package/dist/server/web/index.js.map +1 -0
- package/dist/server/web/upgrade.d.ts +41 -0
- package/dist/server/web/upgrade.d.ts.map +1 -0
- package/dist/server/web/upgrade.js +96 -0
- package/dist/server/web/upgrade.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +84 -21
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +89 -151
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/dist/server/ws/rpc.d.ts +39 -0
- package/dist/server/ws/rpc.d.ts.map +1 -0
- package/dist/server/ws/rpc.js +126 -0
- package/dist/server/ws/rpc.js.map +1 -0
- package/dist/server/ws/upgrade.d.ts +3 -3
- package/dist/server/ws/upgrade.d.ts.map +1 -1
- package/dist/server/ws/upgrade.js +2 -2
- package/dist/server/ws/upgrade.js.map +1 -1
- package/package.json +13 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../../../src/server/lap/forward.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../../../src/server/lap/forward.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,EAC3C,WAAW,GAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAc,IAElE,KAAK,OAAO,EAAE,MAAM,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CAoClE;AAUD,eAAO,MAAM,cAAc,QA9CN,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAkDhE,CAAA;AAEF,eAAO,MAAM,gBAAgB,QApDR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAoDY,CAAA;AAE9E,eAAO,MAAM,iBAAiB,QAtDT,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA0DhE,CAAA;AAEF,eAAO,MAAM,wBAAwB,QA5DhB,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA4DgC,CAAA;AAElG,eAAO,MAAM,gBAAgB,QA9DR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA8DgB,CAAA"}
|
|
@@ -6,7 +6,7 @@ import { verifyAndReadTid } from './describe.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export function makeForwardHandler(tool, parseArgs, auditDetail = () => ({})) {
|
|
8
8
|
return async (req, deps) => {
|
|
9
|
-
const auth = verifyAndReadTid(req, deps.signingKey);
|
|
9
|
+
const auth = await verifyAndReadTid(req, deps.signingKey);
|
|
10
10
|
if (!auth.ok)
|
|
11
11
|
return json({ error: { code: auth.code } }, auth.status);
|
|
12
12
|
const rec = await deps.tokenStore.findByTid(auth.tid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/server/lap/forward.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAWhD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,SAA2C,EAC3C,cAAsE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;IAEhF,OAAO,KAAK,EAAE,GAAY,EAAE,IAAiB,EAAqB,EAAE;QAClE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/server/lap/forward.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\n\nexport type ForwardDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Generic LAP handler. `parseArgs` is called with the parsed body (may be\n * null for empty bodies); it returns the args object to forward or null\n * to reject as invalid. `tool` is the browser-side tool name.\n */\nexport function makeForwardHandler(\n tool: string,\n parseArgs: (body: unknown) => object | null,\n auditDetail: (tid: string, args: object) => Record<string, unknown> = () => ({}),\n) {\n return async (req: Request, deps: ForwardDeps): Promise<Response> => {\n const auth = await verifyAndReadTid(req, deps.signingKey)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return json({ error: { code: 'paused' } }, 503)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const rawBody = req.method === 'POST' ? await req.json().catch(() => null) : null\n const args = parseArgs(rawBody)\n if (args === null) return json({ error: { code: 'invalid' } }, 400)\n\n try {\n const result = await deps.registry.rpc(auth.tid, tool, args)\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool, ...auditDetail(auth.tid, args) },\n })\n return json(result, 200)\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n const status = code === 'paused' ? 503 : code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n }\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n// Concrete handlers:\nexport const handleLapState = makeForwardHandler('get_state', (body) => {\n const b = (body ?? {}) as { path?: unknown }\n if (b.path !== undefined && typeof b.path !== 'string') return null\n return { path: b.path }\n})\n\nexport const handleLapActions = makeForwardHandler('list_actions', () => ({}))\n\nexport const handleLapQueryDom = makeForwardHandler('query_dom', (body) => {\n const b = (body ?? {}) as { name?: unknown; multiple?: unknown }\n if (typeof b.name !== 'string') return null\n return { name: b.name, multiple: !!b.multiple }\n})\n\nexport const handleLapDescribeVisible = makeForwardHandler('describe_visible_content', () => ({}))\n\nexport const handleLapContext = makeForwardHandler('describe_context', () => ({}))\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TokenStore } from '../token-store.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PairingRegistry } from '../ws/pairing-registry.js';
|
|
3
3
|
import type { AuditSink } from '../audit.js';
|
|
4
4
|
import type { RateLimiter } from '../rate-limit.js';
|
|
5
5
|
export type LapMessageDeps = {
|
|
6
6
|
signingKey: string | Uint8Array;
|
|
7
7
|
tokenStore: TokenStore;
|
|
8
|
-
registry:
|
|
8
|
+
registry: PairingRegistry;
|
|
9
9
|
auditSink: AuditSink;
|
|
10
10
|
rateLimiter: RateLimiter;
|
|
11
11
|
now?: () => number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/server/lap/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/server/lap/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAInD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6G5F"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { verifyAndReadTid } from './describe.js';
|
|
2
2
|
export async function handleLapMessage(req, deps) {
|
|
3
|
-
const auth = verifyAndReadTid(req, deps.signingKey);
|
|
3
|
+
const auth = await verifyAndReadTid(req, deps.signingKey);
|
|
4
4
|
if (!auth.ok)
|
|
5
5
|
return json({ error: { code: auth.code } }, auth.status);
|
|
6
6
|
const rec = await deps.tokenStore.findByTid(auth.tid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/server/lap/message.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAYhD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAY,EAAE,IAAoB;IACvE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/server/lap/message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\nimport type { LapMessageRequest, LapMessageResponse } from '../../protocol.js'\n\nexport type LapMessageDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapMessage(req: Request, deps: LapMessageDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.signingKey)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return json({ error: { code: 'paused' } }, 503)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const body = (await req.json().catch(() => null)) as LapMessageRequest | null\n if (!body || !body.msg || typeof body.msg.type !== 'string') {\n return json({ error: { code: 'invalid' } }, 400)\n }\n\n const timeoutMs = body.timeoutMs ?? 5_000\n\n // The browser-side drain loop caps at `timeoutMs`; give the outer\n // RPC a small buffer so a near-edge drain doesn't race the transport\n // timeout and come back as a false 504.\n const rpcTimeoutMs = timeoutMs + 1_000\n\n let initial: LapMessageResponse\n try {\n initial = (await deps.registry.rpc(auth.tid, 'send_message', body, {\n timeoutMs: rpcTimeoutMs,\n })) as LapMessageResponse\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const status = err.code === 'paused' ? 503 : err.code === 'timeout' ? 504 : 500\n // Build a detail string that surfaces whatever info we have — the rpc-error\n // frame from the browser sometimes lacks `detail` (e.g., when a JS TypeError\n // bubbles out of the handler). Falling back to the code + any Error-like\n // fields gives Claude something actionable instead of an opaque 500.\n const detail =\n err.detail ??\n (e instanceof Error ? `${e.name}: ${e.message}` : undefined) ??\n (err.code ? `rpc rejected with code '${err.code}'` : 'rpc rejected without a code')\n // Mirror to the server console so operators see the real cause even when\n // the client just shows \"internal\".\n console.error(\n `[llui-agent] /lap/v1/message 500 — code=${err.code ?? 'internal'}, detail=${detail}`,\n )\n return json({ error: { code: err.code ?? 'internal', detail } }, status)\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n\n if (\n initial.status === 'dispatched' ||\n initial.status === 'confirmed' ||\n initial.status === 'rejected'\n ) {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: initial.status === 'rejected' ? 'msg-blocked' : 'msg-dispatched',\n detail: { variant: body.msg.type, status: initial.status },\n })\n return json(initial, 200)\n }\n\n if (initial.status === 'pending-confirmation') {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-proposed',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n const resolved = await deps.registry.waitForConfirm(auth.tid, initial.confirmId, timeoutMs)\n const nowMs2 = (deps.now ?? (() => Date.now()))()\n if (resolved.outcome === 'confirmed') {\n await deps.auditSink.write({\n at: nowMs2,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-approved',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n return json(\n { status: 'confirmed', stateAfter: resolved.stateAfter } satisfies LapMessageResponse,\n 200,\n )\n }\n await deps.auditSink.write({\n at: nowMs2,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-rejected',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n return json({ status: 'rejected', reason: 'user-cancelled' } satisfies LapMessageResponse, 200)\n }\n\n return json(\n {\n error: {\n code: 'internal',\n detail: `unexpected browser status: ${String((initial as { status?: unknown }).status ?? 'undefined')}`,\n },\n },\n 500,\n )\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TokenStore } from '../token-store.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PairingRegistry } from '../ws/pairing-registry.js';
|
|
3
3
|
import type { AuditSink } from '../audit.js';
|
|
4
4
|
import type { RateLimiter } from '../rate-limit.js';
|
|
5
5
|
export type LapObserveDeps = {
|
|
6
6
|
signingKey: string | Uint8Array;
|
|
7
7
|
tokenStore: TokenStore;
|
|
8
|
-
registry:
|
|
8
|
+
registry: PairingRegistry;
|
|
9
9
|
auditSink: AuditSink;
|
|
10
10
|
rateLimiter: RateLimiter;
|
|
11
11
|
now?: () => number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAUnD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6D5F"}
|
|
@@ -13,7 +13,7 @@ import { verifyAndReadTid } from './describe.js';
|
|
|
13
13
|
* is one call instead of three.
|
|
14
14
|
*/
|
|
15
15
|
export async function handleLapObserve(req, deps) {
|
|
16
|
-
const auth = verifyAndReadTid(req, deps.signingKey);
|
|
16
|
+
const auth = await verifyAndReadTid(req, deps.signingKey);
|
|
17
17
|
if (!auth.ok)
|
|
18
18
|
return json({ error: { code: auth.code } }, auth.status);
|
|
19
19
|
const rec = await deps.tokenStore.findByTid(auth.tid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAkBhD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAY,EAAE,IAAoB;IACvE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\nimport type {\n AgentContext,\n LapActionsResponse,\n LapDescribeResponse,\n LapObserveResponse,\n MessageSchemaEntry,\n} from '../../protocol.js'\n\nexport type LapObserveDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Unified bootstrap endpoint. One call returns everything the LLM\n * needs to start acting on the app:\n * - state (dynamic, from browser)\n * - actions (dynamic, from browser)\n * - description (static, from cached hello frame)\n * - context (dynamic, from browser — agentContext(state))\n *\n * Replaces the get_state + list_actions + describe_app trio at the\n * MCP layer. Those LAP endpoints remain available for specialized\n * callers, but the common \"what can I see, what can I do\" question\n * is one call instead of three.\n */\nexport async function handleLapObserve(req: Request, deps: LapObserveDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.signingKey)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return json({ error: { code: 'paused' } }, 503)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const hello = deps.registry.getHello(auth.tid)\n if (!hello) return json({ error: { code: 'paused' } }, 503)\n\n let dynamic: {\n state: unknown\n actions: LapActionsResponse['actions']\n context: AgentContext | null\n }\n try {\n dynamic = (await deps.registry.rpc(auth.tid, 'observe', {})) as typeof dynamic\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n const status = code === 'paused' ? 503 : code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n\n const description: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages: hello.msgSchema as Record<string, MessageSchemaEntry>,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const out: LapObserveResponse = {\n state: dynamic.state,\n actions: dynamic.actions,\n description,\n context: dynamic.context,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool: 'observe' },\n })\n return json(out, 200)\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TokenStore } from '../token-store.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PairingRegistry } from '../ws/pairing-registry.js';
|
|
3
3
|
import type { AuditSink } from '../audit.js';
|
|
4
4
|
import type { RateLimiter } from '../rate-limit.js';
|
|
5
5
|
export type LapWaitDeps = {
|
|
6
6
|
signingKey: string | Uint8Array;
|
|
7
7
|
tokenStore: TokenStore;
|
|
8
|
-
registry:
|
|
8
|
+
registry: PairingRegistry;
|
|
9
9
|
auditSink: AuditSink;
|
|
10
10
|
rateLimiter: RateLimiter;
|
|
11
11
|
now?: () => number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAInD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2BtF"}
|
package/dist/server/lap/wait.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { verifyAndReadTid } from './describe.js';
|
|
2
2
|
export async function handleLapWait(req, deps) {
|
|
3
|
-
const auth = verifyAndReadTid(req, deps.signingKey);
|
|
3
|
+
const auth = await verifyAndReadTid(req, deps.signingKey);
|
|
4
4
|
if (!auth.ok)
|
|
5
5
|
return json({ error: { code: auth.code } }, auth.status);
|
|
6
6
|
const rec = await deps.tokenStore.findByTid(auth.tid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAYhD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB;IACjE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAYhD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB;IACjE,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACzD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IACtF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAEtF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC/D,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAmB,CAAA;IAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAA;IAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAChF,MAAM,GAAG,GAAoB,MAAM,CAAA;IAEnC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;IAChD,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QACzB,EAAE,EAAE,KAAK;QACT,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,UAAU;QACjB,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;KACzD,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,IAAI,CAAC,CAAU,EAAE,CAAS;IACjC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;QACrC,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\nimport type { LapWaitRequest, LapWaitResponse } from '../../protocol.js'\n\nexport type LapWaitDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapWait(req: Request, deps: LapWaitDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.signingKey)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return json({ error: { code: 'paused' } }, 503)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const body = ((await req.json().catch(() => null)) ?? {}) as LapWaitRequest\n const timeoutMs = body.timeoutMs ?? 10_000\n const result = await deps.registry.waitForChange(auth.tid, body.path, timeoutMs)\n const out: LapWaitResponse = result\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { path: '/lap/v1/wait', outcome: result.status },\n })\n return json(out, 200)\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
|
package/dist/server/options.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import type { TokenStore } from './token-store.js';
|
|
|
4
4
|
import type { IdentityResolver } from './identity.js';
|
|
5
5
|
import type { AuditSink } from './audit.js';
|
|
6
6
|
import type { RateLimiter } from './rate-limit.js';
|
|
7
|
+
import type { PairingRegistry } from './ws/pairing-registry.js';
|
|
8
|
+
import type { AcceptResult } from './core.js';
|
|
9
|
+
import type { PairingConnection } from './ws/pairing-registry.js';
|
|
7
10
|
/**
|
|
8
11
|
* Options accepted by `createLluiAgentServer`. All values except
|
|
9
12
|
* `signingKey` are optional and fall back to in-memory defaults.
|
|
@@ -36,6 +39,27 @@ export type ServerOptions = {
|
|
|
36
39
|
*/
|
|
37
40
|
export type AgentServerHandle = {
|
|
38
41
|
router: (req: Request) => Promise<Response | null>;
|
|
39
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Handles Node HTTP upgrade events for `/agent/ws`. Returns a Promise
|
|
44
|
+
* because token verification uses WebCrypto (async). Node's
|
|
45
|
+
* `server.on('upgrade', handler)` fires the handler without awaiting,
|
|
46
|
+
* which is fine — the handler writes errors directly to the socket
|
|
47
|
+
* and never throws back to the caller.
|
|
48
|
+
*/
|
|
49
|
+
wsUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<void>;
|
|
50
|
+
/** The pairing registry. Runtime-neutral adapters may access it. */
|
|
51
|
+
registry: PairingRegistry;
|
|
52
|
+
/** The active token store. */
|
|
53
|
+
tokenStore: TokenStore;
|
|
54
|
+
/** The active audit sink. */
|
|
55
|
+
auditSink: AuditSink;
|
|
56
|
+
/**
|
|
57
|
+
* Runtime-neutral WebSocket acceptance primitive. Validates a token
|
|
58
|
+
* and registers a `PairingConnection` with the registry. The Node
|
|
59
|
+
* `wsUpgrade` above calls this internally; web-runtime adapters
|
|
60
|
+
* (`@llui/agent/server/web`) use it after accepting a WebSocket via
|
|
61
|
+
* their native API.
|
|
62
|
+
*/
|
|
63
|
+
acceptConnection: (token: string, conn: PairingConnection) => Promise<AcceptResult>;
|
|
40
64
|
};
|
|
41
65
|
//# sourceMappingURL=options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAEjE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,+EAA+E;IAC/E,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAE/B,wDAAwD;IACxD,UAAU,CAAC,EAAE,UAAU,CAAA;IAEvB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC,kDAAkD;IAClD,SAAS,CAAC,EAAE,SAAS,CAAA;IAErB,qEAAqE;IACrE,WAAW,CAAC,EAAE,WAAW,CAAA;IAEzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,gEAAgE;IAChE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAClD;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChF,oEAAoE;IACpE,QAAQ,EAAE,eAAe,CAAA;IACzB,8BAA8B;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,6BAA6B;IAC7B,SAAS,EAAE,SAAS,CAAA;IACpB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACpF,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"","sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport type { TokenStore } from './token-store.js'\nimport type { IdentityResolver } from './identity.js'\nimport type { AuditSink } from './audit.js'\nimport type { RateLimiter } from './rate-limit.js'\n\n/**\n * Options accepted by `createLluiAgentServer`. All values except\n * `signingKey` are optional and fall back to in-memory defaults.\n * See spec §10.1.\n */\nexport type ServerOptions = {\n /** HMAC key for signing tokens. ≥32 bytes; rotation invalidates all tokens. */\n signingKey: string | Uint8Array\n\n /** Token store. Defaults to an `InMemoryTokenStore`. */\n tokenStore?: TokenStore\n\n /** Identity resolver. Defaults to anonymous (always null). */\n identityResolver?: IdentityResolver\n\n /** Audit sink. Defaults to `consoleAuditSink`. */\n auditSink?: AuditSink\n\n /** Rate limiter. Defaults to `defaultRateLimiter` with 30/minute. */\n rateLimiter?: RateLimiter\n\n /** Base path prefix for LAP endpoints. Defaults to `/agent/lap/v1`. */\n lapBasePath?: string\n\n /** Pairing grace window after a tab closes, in ms. Default 15 min. */\n pairingGraceMs?: number\n\n /** Sliding TTL for active tokens, in ms. Default 1 h. */\n slidingTtlMs?: number\n\n /** Allowed origins for the HTTP surface (CORS). Empty = any. */\n corsOrigins?: readonly string[]\n}\n\n/**\n * Value returned by `createLluiAgentServer`. `router` matches any\n * `/agent/*` request and returns a Response (or null to fall through).\n * `wsUpgrade` handles Node HTTP upgrade events for `/agent/ws`.\n */\nexport type AgentServerHandle = {\n router: (req: Request) => Promise<Response | null>\n wsUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void\n}\n"]}
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"","sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport type { TokenStore } from './token-store.js'\nimport type { IdentityResolver } from './identity.js'\nimport type { AuditSink } from './audit.js'\nimport type { RateLimiter } from './rate-limit.js'\nimport type { PairingRegistry } from './ws/pairing-registry.js'\nimport type { AcceptResult } from './core.js'\nimport type { PairingConnection } from './ws/pairing-registry.js'\n\n/**\n * Options accepted by `createLluiAgentServer`. All values except\n * `signingKey` are optional and fall back to in-memory defaults.\n * See spec §10.1.\n */\nexport type ServerOptions = {\n /** HMAC key for signing tokens. ≥32 bytes; rotation invalidates all tokens. */\n signingKey: string | Uint8Array\n\n /** Token store. Defaults to an `InMemoryTokenStore`. */\n tokenStore?: TokenStore\n\n /** Identity resolver. Defaults to anonymous (always null). */\n identityResolver?: IdentityResolver\n\n /** Audit sink. Defaults to `consoleAuditSink`. */\n auditSink?: AuditSink\n\n /** Rate limiter. Defaults to `defaultRateLimiter` with 30/minute. */\n rateLimiter?: RateLimiter\n\n /** Base path prefix for LAP endpoints. Defaults to `/agent/lap/v1`. */\n lapBasePath?: string\n\n /** Pairing grace window after a tab closes, in ms. Default 15 min. */\n pairingGraceMs?: number\n\n /** Sliding TTL for active tokens, in ms. Default 1 h. */\n slidingTtlMs?: number\n\n /** Allowed origins for the HTTP surface (CORS). Empty = any. */\n corsOrigins?: readonly string[]\n}\n\n/**\n * Value returned by `createLluiAgentServer`. `router` matches any\n * `/agent/*` request and returns a Response (or null to fall through).\n * `wsUpgrade` handles Node HTTP upgrade events for `/agent/ws`.\n */\nexport type AgentServerHandle = {\n router: (req: Request) => Promise<Response | null>\n /**\n * Handles Node HTTP upgrade events for `/agent/ws`. Returns a Promise\n * because token verification uses WebCrypto (async). Node's\n * `server.on('upgrade', handler)` fires the handler without awaiting,\n * which is fine — the handler writes errors directly to the socket\n * and never throws back to the caller.\n */\n wsUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<void>\n /** The pairing registry. Runtime-neutral adapters may access it. */\n registry: PairingRegistry\n /** The active token store. */\n tokenStore: TokenStore\n /** The active audit sink. */\n auditSink: AuditSink\n /**\n * Runtime-neutral WebSocket acceptance primitive. Validates a token\n * and registers a `PairingConnection` with the registry. The Node\n * `wsUpgrade` above calls this internally; web-runtime adapters\n * (`@llui/agent/server/web`) use it after accepting a WebSocket via\n * their native API.\n */\n acceptConnection: (token: string, conn: PairingConnection) => Promise<AcceptResult>\n}\n"]}
|
package/dist/server/token.d.ts
CHANGED
|
@@ -14,11 +14,15 @@ export type VerifyResult = {
|
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.
|
|
17
|
-
* See spec §6.1.
|
|
17
|
+
* See spec §6.1. Async because WebCrypto's HMAC sign/verify is the
|
|
18
|
+
* cross-runtime standard; Node, Cloudflare, Deno, and Bun all expose
|
|
19
|
+
* `crypto.subtle` identically.
|
|
18
20
|
*/
|
|
19
|
-
export declare function signToken(payload: TokenPayload, key: string | Uint8Array): AgentToken
|
|
21
|
+
export declare function signToken(payload: TokenPayload, key: string | Uint8Array): Promise<AgentToken>;
|
|
20
22
|
/**
|
|
21
23
|
* Verify the signature, parse the payload, and check expiry.
|
|
24
|
+
* `crypto.subtle.verify` does the constant-time compare internally,
|
|
25
|
+
* so we don't need a separate `timingSafeEqual`.
|
|
22
26
|
*/
|
|
23
|
-
export declare function verifyToken(token: string, key: string | Uint8Array, nowSec?: number): VerifyResult
|
|
27
|
+
export declare function verifyToken(token: string, key: string | Uint8Array, nowSec?: number): Promise<VerifyResult>;
|
|
24
28
|
//# sourceMappingURL=token.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEhD,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAAA;CAAE,CAAA;AAiE1E;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,MAAM,GAAG,UAAU,GACvB,OAAO,CAAC,UAAU,CAAC,CAOrB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GAAG,UAAU,EACxB,MAAM,GAAE,MAAsC,GAC7C,OAAO,CAAC,YAAY,CAAC,CA2BvB"}
|
package/dist/server/token.js
CHANGED
|
@@ -1,17 +1,54 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
1
|
const PREFIX = 'llui-agent_';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Normalize key + payload to `Uint8Array<ArrayBuffer>`, the shape
|
|
4
|
+
* WebCrypto wants. Newer TS lib types parameterize `Uint8Array` over the
|
|
5
|
+
* underlying buffer, and `TextEncoder.encode()` returns
|
|
6
|
+
* `Uint8Array<ArrayBufferLike>` — which `crypto.subtle.*` won't accept
|
|
7
|
+
* directly. A one-shot copy is cheap (HMAC inputs are bytes-small) and
|
|
8
|
+
* keeps the types honest without `as BufferSource` scattered at call sites.
|
|
9
|
+
*/
|
|
10
|
+
function toBytes(input) {
|
|
11
|
+
const raw = typeof input === 'string' ? new TextEncoder().encode(input) : input;
|
|
12
|
+
const buf = new ArrayBuffer(raw.byteLength);
|
|
13
|
+
const out = new Uint8Array(buf);
|
|
14
|
+
out.set(raw);
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function toKeyBytes(key) {
|
|
18
|
+
if (typeof key === 'string') {
|
|
19
|
+
if (key.length < 32)
|
|
20
|
+
throw new Error('signingKey must be at least 32 bytes');
|
|
21
|
+
}
|
|
22
|
+
else if (key.byteLength < 32) {
|
|
6
23
|
throw new Error('signingKey must be at least 32 bytes');
|
|
7
|
-
|
|
24
|
+
}
|
|
25
|
+
return toBytes(key);
|
|
8
26
|
}
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Import a signing key as a WebCrypto `CryptoKey`. Done per call so the
|
|
29
|
+
* caller doesn't have to pre-import and pass it around; the cost is a
|
|
30
|
+
* microtask per sign/verify, which is negligible for our call volume
|
|
31
|
+
* (tokens verified once per LAP HTTP request).
|
|
32
|
+
*/
|
|
33
|
+
async function importHmacKey(key, usages) {
|
|
34
|
+
return crypto.subtle.importKey('raw', toKeyBytes(key), { name: 'HMAC', hash: 'SHA-256' }, false, usages);
|
|
11
35
|
}
|
|
12
|
-
function
|
|
36
|
+
function toBase64Url(bytes) {
|
|
37
|
+
// btoa needs a binary string; build it manually to avoid ArrayBuffer/Uint8Array quirks.
|
|
38
|
+
let bin = '';
|
|
39
|
+
for (let i = 0; i < bytes.byteLength; i++)
|
|
40
|
+
bin += String.fromCharCode(bytes[i]);
|
|
41
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
42
|
+
}
|
|
43
|
+
function fromBase64Url(s) {
|
|
13
44
|
try {
|
|
14
|
-
|
|
45
|
+
const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4);
|
|
46
|
+
const bin = atob(b64);
|
|
47
|
+
const buf = new ArrayBuffer(bin.length);
|
|
48
|
+
const bytes = new Uint8Array(buf);
|
|
49
|
+
for (let i = 0; i < bin.length; i++)
|
|
50
|
+
bytes[i] = bin.charCodeAt(i);
|
|
51
|
+
return bytes;
|
|
15
52
|
}
|
|
16
53
|
catch {
|
|
17
54
|
return null;
|
|
@@ -19,20 +56,24 @@ function b64urlDecode(s) {
|
|
|
19
56
|
}
|
|
20
57
|
/**
|
|
21
58
|
* Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.
|
|
22
|
-
* See spec §6.1.
|
|
59
|
+
* See spec §6.1. Async because WebCrypto's HMAC sign/verify is the
|
|
60
|
+
* cross-runtime standard; Node, Cloudflare, Deno, and Bun all expose
|
|
61
|
+
* `crypto.subtle` identically.
|
|
23
62
|
*/
|
|
24
|
-
export function signToken(payload, key) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const payloadPart =
|
|
28
|
-
const
|
|
29
|
-
const sigPart =
|
|
63
|
+
export async function signToken(payload, key) {
|
|
64
|
+
const cryptoKey = await importHmacKey(key, ['sign']);
|
|
65
|
+
const jsonBytes = toBytes(JSON.stringify(payload));
|
|
66
|
+
const payloadPart = toBase64Url(jsonBytes);
|
|
67
|
+
const macBuf = await crypto.subtle.sign('HMAC', cryptoKey, toBytes(payloadPart));
|
|
68
|
+
const sigPart = toBase64Url(new Uint8Array(macBuf));
|
|
30
69
|
return (PREFIX + payloadPart + '.' + sigPart);
|
|
31
70
|
}
|
|
32
71
|
/**
|
|
33
72
|
* Verify the signature, parse the payload, and check expiry.
|
|
73
|
+
* `crypto.subtle.verify` does the constant-time compare internally,
|
|
74
|
+
* so we don't need a separate `timingSafeEqual`.
|
|
34
75
|
*/
|
|
35
|
-
export function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000)) {
|
|
76
|
+
export async function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000)) {
|
|
36
77
|
if (!token.startsWith(PREFIX))
|
|
37
78
|
return { kind: 'invalid', reason: 'malformed' };
|
|
38
79
|
const body = token.slice(PREFIX.length);
|
|
@@ -41,20 +82,19 @@ export function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000))
|
|
|
41
82
|
return { kind: 'invalid', reason: 'malformed' };
|
|
42
83
|
const payloadPart = body.slice(0, dot);
|
|
43
84
|
const sigPart = body.slice(dot + 1);
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
85
|
+
const sigBytes = fromBase64Url(sigPart);
|
|
86
|
+
if (!sigBytes)
|
|
46
87
|
return { kind: 'invalid', reason: 'malformed' };
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
88
|
+
const cryptoKey = await importHmacKey(key, ['verify']);
|
|
89
|
+
const ok = await crypto.subtle.verify('HMAC', cryptoKey, sigBytes, toBytes(payloadPart));
|
|
90
|
+
if (!ok)
|
|
50
91
|
return { kind: 'invalid', reason: 'bad-signature' };
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!jsonBuf)
|
|
92
|
+
const jsonBytes = fromBase64Url(payloadPart);
|
|
93
|
+
if (!jsonBytes)
|
|
54
94
|
return { kind: 'invalid', reason: 'malformed' };
|
|
55
95
|
let parsed;
|
|
56
96
|
try {
|
|
57
|
-
parsed = JSON.parse(
|
|
97
|
+
parsed = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
58
98
|
}
|
|
59
99
|
catch {
|
|
60
100
|
return { kind: 'invalid', reason: 'malformed' };
|
package/dist/server/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,GAAG,aAAa,CAAA;AAE5B;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,KAA0B;IACzC,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC/E,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAA;IAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACZ,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,UAAU,CAAC,GAAwB;IAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;IAC9E,CAAC;SAAM,IAAI,GAAG,CAAC,UAAU,GAAG,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACzD,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;AACrB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,aAAa,CAAC,GAAwB,EAAE,MAAkB;IACvE,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAC5B,KAAK,EACL,UAAU,CAAC,GAAG,CAAC,EACf,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,MAAM,CACP,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,KAAiB;IACpC,wFAAwF;IACxF,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAA;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,aAAa,CAAC,CAAS;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAC1F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QACrB,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACvC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAA;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QACjE,OAAO,KAAK,CAAA;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAqB,EACrB,GAAwB;IAExB,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IACpD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IAClD,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;IAChF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;IACnD,OAAO,CAAC,MAAM,GAAG,WAAW,GAAG,GAAG,GAAG,OAAO,CAAe,CAAA;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,GAAwB,EACxB,SAAiB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAE9C,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC7B,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAE5D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;IACnC,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACvC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAE9D,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACtD,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;IACxF,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;IAE5D,MAAM,SAAS,GAAG,aAAa,CAAC,WAAW,CAAC,CAAA;IAC5C,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAC/D,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IACjD,CAAC;IAED,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAC5E,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IACvE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AACxC,CAAC;AAED,SAAS,cAAc,CAAC,CAAU;IAChC,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC7C,MAAM,CAAC,GAAG,CAA4B,CAAA;IACtC,OAAO,CACL,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,CAAC,CAAC,KAAK,KAAK,OAAO,CACpB,CAAA;AACH,CAAC","sourcesContent":["import type { AgentToken } from '../protocol.js'\n\nexport type TokenPayload = {\n tid: string\n iat: number\n exp: number\n scope: 'agent'\n}\n\nexport type VerifyResult =\n | { kind: 'ok'; payload: TokenPayload }\n | { kind: 'invalid'; reason: 'malformed' | 'bad-signature' | 'expired' }\n\nconst PREFIX = 'llui-agent_'\n\n/**\n * Normalize key + payload to `Uint8Array<ArrayBuffer>`, the shape\n * WebCrypto wants. Newer TS lib types parameterize `Uint8Array` over the\n * underlying buffer, and `TextEncoder.encode()` returns\n * `Uint8Array<ArrayBufferLike>` — which `crypto.subtle.*` won't accept\n * directly. A one-shot copy is cheap (HMAC inputs are bytes-small) and\n * keeps the types honest without `as BufferSource` scattered at call sites.\n */\nfunction toBytes(input: string | Uint8Array): Uint8Array<ArrayBuffer> {\n const raw = typeof input === 'string' ? new TextEncoder().encode(input) : input\n const buf = new ArrayBuffer(raw.byteLength)\n const out = new Uint8Array(buf)\n out.set(raw)\n return out\n}\n\nfunction toKeyBytes(key: string | Uint8Array): Uint8Array<ArrayBuffer> {\n if (typeof key === 'string') {\n if (key.length < 32) throw new Error('signingKey must be at least 32 bytes')\n } else if (key.byteLength < 32) {\n throw new Error('signingKey must be at least 32 bytes')\n }\n return toBytes(key)\n}\n\n/**\n * Import a signing key as a WebCrypto `CryptoKey`. Done per call so the\n * caller doesn't have to pre-import and pass it around; the cost is a\n * microtask per sign/verify, which is negligible for our call volume\n * (tokens verified once per LAP HTTP request).\n */\nasync function importHmacKey(key: string | Uint8Array, usages: KeyUsage[]): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n 'raw',\n toKeyBytes(key),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n usages,\n )\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n // btoa needs a binary string; build it manually to avoid ArrayBuffer/Uint8Array quirks.\n let bin = ''\n for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]!)\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(s: string): Uint8Array<ArrayBuffer> | null {\n try {\n const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4)\n const bin = atob(b64)\n const buf = new ArrayBuffer(bin.length)\n const bytes = new Uint8Array(buf)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n return bytes\n } catch {\n return null\n }\n}\n\n/**\n * Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.\n * See spec §6.1. Async because WebCrypto's HMAC sign/verify is the\n * cross-runtime standard; Node, Cloudflare, Deno, and Bun all expose\n * `crypto.subtle` identically.\n */\nexport async function signToken(\n payload: TokenPayload,\n key: string | Uint8Array,\n): Promise<AgentToken> {\n const cryptoKey = await importHmacKey(key, ['sign'])\n const jsonBytes = toBytes(JSON.stringify(payload))\n const payloadPart = toBase64Url(jsonBytes)\n const macBuf = await crypto.subtle.sign('HMAC', cryptoKey, toBytes(payloadPart))\n const sigPart = toBase64Url(new Uint8Array(macBuf))\n return (PREFIX + payloadPart + '.' + sigPart) as AgentToken\n}\n\n/**\n * Verify the signature, parse the payload, and check expiry.\n * `crypto.subtle.verify` does the constant-time compare internally,\n * so we don't need a separate `timingSafeEqual`.\n */\nexport async function verifyToken(\n token: string,\n key: string | Uint8Array,\n nowSec: number = Math.floor(Date.now() / 1000),\n): Promise<VerifyResult> {\n if (!token.startsWith(PREFIX)) return { kind: 'invalid', reason: 'malformed' }\n const body = token.slice(PREFIX.length)\n const dot = body.indexOf('.')\n if (dot < 0) return { kind: 'invalid', reason: 'malformed' }\n\n const payloadPart = body.slice(0, dot)\n const sigPart = body.slice(dot + 1)\n const sigBytes = fromBase64Url(sigPart)\n if (!sigBytes) return { kind: 'invalid', reason: 'malformed' }\n\n const cryptoKey = await importHmacKey(key, ['verify'])\n const ok = await crypto.subtle.verify('HMAC', cryptoKey, sigBytes, toBytes(payloadPart))\n if (!ok) return { kind: 'invalid', reason: 'bad-signature' }\n\n const jsonBytes = fromBase64Url(payloadPart)\n if (!jsonBytes) return { kind: 'invalid', reason: 'malformed' }\n let parsed: unknown\n try {\n parsed = JSON.parse(new TextDecoder().decode(jsonBytes))\n } catch {\n return { kind: 'invalid', reason: 'malformed' }\n }\n\n if (!isTokenPayload(parsed)) return { kind: 'invalid', reason: 'malformed' }\n if (parsed.exp <= nowSec) return { kind: 'invalid', reason: 'expired' }\n return { kind: 'ok', payload: parsed }\n}\n\nfunction isTokenPayload(x: unknown): x is TokenPayload {\n if (!x || typeof x !== 'object') return false\n const o = x as Record<string, unknown>\n return (\n typeof o.tid === 'string' &&\n typeof o.iat === 'number' &&\n typeof o.exp === 'number' &&\n o.scope === 'agent'\n )\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PairingConnection } from '../ws/pairing-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a WHATWG `WebSocket` in a `PairingConnection`. This is the
|
|
4
|
+
* common denominator across Cloudflare Workers (`WebSocketPair`
|
|
5
|
+
* server half), Deno (`Deno.upgradeWebSocket().socket`), Bun's
|
|
6
|
+
* upgraded socket, and any other runtime that exposes a
|
|
7
|
+
* standards-compliant WebSocket object.
|
|
8
|
+
*
|
|
9
|
+
* The input type is intentionally the browser/global `WebSocket`
|
|
10
|
+
* interface — *not* the Node `ws` library's variant, which uses an
|
|
11
|
+
* EventEmitter API (`on('message', ...)`) rather than
|
|
12
|
+
* `addEventListener('message', ...)`. Use `./node/upgrade.ts` for
|
|
13
|
+
* the `ws` library path.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createWHATWGPairingConnection(socket: WebSocket): PairingConnection;
|
|
16
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/server/web/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAGlE;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,GAAG,iBAAiB,CA4BlF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a WHATWG `WebSocket` in a `PairingConnection`. This is the
|
|
3
|
+
* common denominator across Cloudflare Workers (`WebSocketPair`
|
|
4
|
+
* server half), Deno (`Deno.upgradeWebSocket().socket`), Bun's
|
|
5
|
+
* upgraded socket, and any other runtime that exposes a
|
|
6
|
+
* standards-compliant WebSocket object.
|
|
7
|
+
*
|
|
8
|
+
* The input type is intentionally the browser/global `WebSocket`
|
|
9
|
+
* interface — *not* the Node `ws` library's variant, which uses an
|
|
10
|
+
* EventEmitter API (`on('message', ...)`) rather than
|
|
11
|
+
* `addEventListener('message', ...)`. Use `./node/upgrade.ts` for
|
|
12
|
+
* the `ws` library path.
|
|
13
|
+
*/
|
|
14
|
+
export function createWHATWGPairingConnection(socket) {
|
|
15
|
+
return {
|
|
16
|
+
send(frame) {
|
|
17
|
+
socket.send(JSON.stringify(frame));
|
|
18
|
+
},
|
|
19
|
+
onFrame(handler) {
|
|
20
|
+
socket.addEventListener('message', (ev) => {
|
|
21
|
+
const data = ev.data;
|
|
22
|
+
const raw = typeof data === 'string' ? data : new TextDecoder().decode(data);
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
handler(parsed);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ignore malformed frames — one bad frame shouldn't tear down the pairing.
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
onClose(handler) {
|
|
33
|
+
socket.addEventListener('close', () => handler());
|
|
34
|
+
},
|
|
35
|
+
close() {
|
|
36
|
+
try {
|
|
37
|
+
socket.close();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Some runtimes throw if you close twice; swallow.
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/server/web/adapter.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,OAAO;QACL,IAAI,CAAC,KAAkB;YACrB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QACpC,CAAC;QACD,OAAO,CAAC,OAAO;YACb,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;gBACxC,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAA;gBACpB,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAmB,CAAC,CAAA;gBAC3F,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAA;oBAC7C,OAAO,CAAC,MAAM,CAAC,CAAA;gBACjB,CAAC;gBAAC,MAAM,CAAC;oBACP,2EAA2E;gBAC7E,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,CAAC,OAAO;YACb,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QACnD,CAAC;QACD,KAAK;YACH,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,EAAE,CAAA;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { PairingConnection } from '../ws/pairing-registry.js'\nimport type { ClientFrame, ServerFrame } from '../../protocol.js'\n\n/**\n * Wrap a WHATWG `WebSocket` in a `PairingConnection`. This is the\n * common denominator across Cloudflare Workers (`WebSocketPair`\n * server half), Deno (`Deno.upgradeWebSocket().socket`), Bun's\n * upgraded socket, and any other runtime that exposes a\n * standards-compliant WebSocket object.\n *\n * The input type is intentionally the browser/global `WebSocket`\n * interface — *not* the Node `ws` library's variant, which uses an\n * EventEmitter API (`on('message', ...)`) rather than\n * `addEventListener('message', ...)`. Use `./node/upgrade.ts` for\n * the `ws` library path.\n */\nexport function createWHATWGPairingConnection(socket: WebSocket): PairingConnection {\n return {\n send(frame: ServerFrame) {\n socket.send(JSON.stringify(frame))\n },\n onFrame(handler) {\n socket.addEventListener('message', (ev) => {\n const data = ev.data\n const raw = typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)\n try {\n const parsed = JSON.parse(raw) as ClientFrame\n handler(parsed)\n } catch {\n // Ignore malformed frames — one bad frame shouldn't tear down the pairing.\n }\n })\n },\n onClose(handler) {\n socket.addEventListener('close', () => handler())\n },\n close() {\n try {\n socket.close()\n } catch {\n // Some runtimes throw if you close twice; swallow.\n }\n },\n }\n}\n"]}
|