@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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-runtime adapters. Use this sub-path from Cloudflare Workers,
|
|
3
|
+
* Deno, Bun, or any other runtime that speaks WHATWG `Request` /
|
|
4
|
+
* `Response` and exposes native WebSocket upgrade primitives.
|
|
5
|
+
*
|
|
6
|
+
* Pair with `@llui/agent/server/core`'s `createLluiAgentCore` — that
|
|
7
|
+
* builds the runtime-neutral router and registry; the handlers
|
|
8
|
+
* exported here handle the WebSocket upgrade half.
|
|
9
|
+
*/
|
|
10
|
+
export { createWHATWGPairingConnection } from './adapter.js';
|
|
11
|
+
export { handleCloudflareUpgrade, handleDenoUpgrade, extractToken } from './upgrade.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/web/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-runtime adapters. Use this sub-path from Cloudflare Workers,
|
|
3
|
+
* Deno, Bun, or any other runtime that speaks WHATWG `Request` /
|
|
4
|
+
* `Response` and exposes native WebSocket upgrade primitives.
|
|
5
|
+
*
|
|
6
|
+
* Pair with `@llui/agent/server/core`'s `createLluiAgentCore` — that
|
|
7
|
+
* builds the runtime-neutral router and registry; the handlers
|
|
8
|
+
* exported here handle the WebSocket upgrade half.
|
|
9
|
+
*/
|
|
10
|
+
export { createWHATWGPairingConnection } from './adapter.js';
|
|
11
|
+
export { handleCloudflareUpgrade, handleDenoUpgrade, extractToken } from './upgrade.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/web/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA","sourcesContent":["/**\n * Web-runtime adapters. Use this sub-path from Cloudflare Workers,\n * Deno, Bun, or any other runtime that speaks WHATWG `Request` /\n * `Response` and exposes native WebSocket upgrade primitives.\n *\n * Pair with `@llui/agent/server/core`'s `createLluiAgentCore` — that\n * builds the runtime-neutral router and registry; the handlers\n * exported here handle the WebSocket upgrade half.\n */\nexport { createWHATWGPairingConnection } from './adapter.js'\nexport { handleCloudflareUpgrade, handleDenoUpgrade, extractToken } from './upgrade.js'\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AgentCoreHandle } from '../core.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the bearer token from a LAP WebSocket upgrade request.
|
|
4
|
+
* Accepts the token on either `?token=` or `Authorization: Bearer` —
|
|
5
|
+
* query-string is the common pattern because browsers can't set
|
|
6
|
+
* arbitrary headers on WebSocket construction.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractToken(req: Request): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Cloudflare Workers handler. Accepts a WebSocket upgrade using
|
|
11
|
+
* `WebSocketPair`, validates the token via
|
|
12
|
+
* `agent.acceptConnection`, and returns the 101 upgrade Response.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```ts
|
|
16
|
+
* const agent = createLluiAgentCore({ signingKey: env.AGENT_KEY })
|
|
17
|
+
* export default {
|
|
18
|
+
* async fetch(req, env) {
|
|
19
|
+
* const url = new URL(req.url)
|
|
20
|
+
* if (url.pathname === '/agent/ws') return handleCloudflareUpgrade(req, agent)
|
|
21
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
22
|
+
* },
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function handleCloudflareUpgrade(req: Request, agent: AgentCoreHandle): Promise<Response>;
|
|
27
|
+
/**
|
|
28
|
+
* Deno handler. Uses `Deno.upgradeWebSocket(req)` to produce the
|
|
29
|
+
* response + socket pair, then plugs the socket into the registry.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* ```ts
|
|
33
|
+
* Deno.serve(async (req) => {
|
|
34
|
+
* const url = new URL(req.url)
|
|
35
|
+
* if (url.pathname === '/agent/ws') return handleDenoUpgrade(req, agent)
|
|
36
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleDenoUpgrade(req: Request, agent: AgentCoreHandle): Promise<Response>;
|
|
41
|
+
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/server/web/upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGjD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOxD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,QAAQ,CAAC,CAmCnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+B/F"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createWHATWGPairingConnection } from './adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the bearer token from a LAP WebSocket upgrade request.
|
|
4
|
+
* Accepts the token on either `?token=` or `Authorization: Bearer` —
|
|
5
|
+
* query-string is the common pattern because browsers can't set
|
|
6
|
+
* arbitrary headers on WebSocket construction.
|
|
7
|
+
*/
|
|
8
|
+
export function extractToken(req) {
|
|
9
|
+
const url = new URL(req.url);
|
|
10
|
+
const q = url.searchParams.get('token');
|
|
11
|
+
if (q)
|
|
12
|
+
return q;
|
|
13
|
+
const auth = req.headers.get('authorization');
|
|
14
|
+
if (auth?.startsWith('Bearer '))
|
|
15
|
+
return auth.slice('Bearer '.length);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Cloudflare Workers handler. Accepts a WebSocket upgrade using
|
|
20
|
+
* `WebSocketPair`, validates the token via
|
|
21
|
+
* `agent.acceptConnection`, and returns the 101 upgrade Response.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const agent = createLluiAgentCore({ signingKey: env.AGENT_KEY })
|
|
26
|
+
* export default {
|
|
27
|
+
* async fetch(req, env) {
|
|
28
|
+
* const url = new URL(req.url)
|
|
29
|
+
* if (url.pathname === '/agent/ws') return handleCloudflareUpgrade(req, agent)
|
|
30
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
31
|
+
* },
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export async function handleCloudflareUpgrade(req, agent) {
|
|
36
|
+
if (req.headers.get('upgrade') !== 'websocket') {
|
|
37
|
+
return new Response('Expected upgrade: websocket', { status: 426 });
|
|
38
|
+
}
|
|
39
|
+
const token = extractToken(req);
|
|
40
|
+
if (!token)
|
|
41
|
+
return new Response('Unauthorized', { status: 401 });
|
|
42
|
+
// `WebSocketPair` is a Cloudflare Workers global. We reference it
|
|
43
|
+
// through `globalThis` so importing this module in non-CF runtimes
|
|
44
|
+
// (e.g. during type-checking on Node) doesn't crash.
|
|
45
|
+
const Pair = globalThis.WebSocketPair;
|
|
46
|
+
if (!Pair) {
|
|
47
|
+
return new Response('WebSocketPair unavailable in this runtime', { status: 501 });
|
|
48
|
+
}
|
|
49
|
+
const pair = new Pair();
|
|
50
|
+
const client = pair[0];
|
|
51
|
+
const server = pair[1];
|
|
52
|
+
server.accept();
|
|
53
|
+
const conn = createWHATWGPairingConnection(server);
|
|
54
|
+
const result = await agent.acceptConnection(token, conn);
|
|
55
|
+
if (!result.ok) {
|
|
56
|
+
conn.close();
|
|
57
|
+
return new Response(result.code, { status: result.status });
|
|
58
|
+
}
|
|
59
|
+
// `webSocket` on ResponseInit is Cloudflare-specific; cast to satisfy
|
|
60
|
+
// the standard lib types.
|
|
61
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deno handler. Uses `Deno.upgradeWebSocket(req)` to produce the
|
|
65
|
+
* response + socket pair, then plugs the socket into the registry.
|
|
66
|
+
*
|
|
67
|
+
* Usage:
|
|
68
|
+
* ```ts
|
|
69
|
+
* Deno.serve(async (req) => {
|
|
70
|
+
* const url = new URL(req.url)
|
|
71
|
+
* if (url.pathname === '/agent/ws') return handleDenoUpgrade(req, agent)
|
|
72
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
73
|
+
* })
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export async function handleDenoUpgrade(req, agent) {
|
|
77
|
+
const token = extractToken(req);
|
|
78
|
+
if (!token)
|
|
79
|
+
return new Response('Unauthorized', { status: 401 });
|
|
80
|
+
const Deno_ = globalThis.Deno;
|
|
81
|
+
if (!Deno_) {
|
|
82
|
+
return new Response('Deno.upgradeWebSocket unavailable in this runtime', { status: 501 });
|
|
83
|
+
}
|
|
84
|
+
const { socket, response } = Deno_.upgradeWebSocket(req);
|
|
85
|
+
const conn = createWHATWGPairingConnection(socket);
|
|
86
|
+
// Deno opens the socket asynchronously; validate the token first,
|
|
87
|
+
// then register on `open` so frames aren't missed.
|
|
88
|
+
socket.addEventListener('open', () => {
|
|
89
|
+
void agent.acceptConnection(token, conn).then((result) => {
|
|
90
|
+
if (!result.ok)
|
|
91
|
+
conn.close();
|
|
92
|
+
});
|
|
93
|
+
}, { once: true });
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/server/web/upgrade.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentCoreHandle } from '../core.js'\nimport { createWHATWGPairingConnection } from './adapter.js'\n\n/**\n * Extract the bearer token from a LAP WebSocket upgrade request.\n * Accepts the token on either `?token=` or `Authorization: Bearer` —\n * query-string is the common pattern because browsers can't set\n * arbitrary headers on WebSocket construction.\n */\nexport function extractToken(req: Request): string | null {\n const url = new URL(req.url)\n const q = url.searchParams.get('token')\n if (q) return q\n const auth = req.headers.get('authorization')\n if (auth?.startsWith('Bearer ')) return auth.slice('Bearer '.length)\n return null\n}\n\n/**\n * Cloudflare Workers handler. Accepts a WebSocket upgrade using\n * `WebSocketPair`, validates the token via\n * `agent.acceptConnection`, and returns the 101 upgrade Response.\n *\n * Usage:\n * ```ts\n * const agent = createLluiAgentCore({ signingKey: env.AGENT_KEY })\n * export default {\n * async fetch(req, env) {\n * const url = new URL(req.url)\n * if (url.pathname === '/agent/ws') return handleCloudflareUpgrade(req, agent)\n * return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })\n * },\n * }\n * ```\n */\nexport async function handleCloudflareUpgrade(\n req: Request,\n agent: AgentCoreHandle,\n): Promise<Response> {\n if (req.headers.get('upgrade') !== 'websocket') {\n return new Response('Expected upgrade: websocket', { status: 426 })\n }\n const token = extractToken(req)\n if (!token) return new Response('Unauthorized', { status: 401 })\n\n // `WebSocketPair` is a Cloudflare Workers global. We reference it\n // through `globalThis` so importing this module in non-CF runtimes\n // (e.g. during type-checking on Node) doesn't crash.\n const Pair = (\n globalThis as unknown as { WebSocketPair?: new () => { 0: WebSocket; 1: WebSocket } }\n ).WebSocketPair\n if (!Pair) {\n return new Response('WebSocketPair unavailable in this runtime', { status: 501 })\n }\n const pair = new Pair()\n const client = pair[0]\n const server = pair[1]!\n // `accept()` on the server half is Cloudflare-specific — it tells\n // the runtime the Worker will handle the WebSocket itself.\n ;(server as unknown as { accept: () => void }).accept()\n\n const conn = createWHATWGPairingConnection(server)\n const result = await agent.acceptConnection(token, conn)\n if (!result.ok) {\n conn.close()\n return new Response(result.code, { status: result.status })\n }\n\n // `webSocket` on ResponseInit is Cloudflare-specific; cast to satisfy\n // the standard lib types.\n return new Response(null, { status: 101, webSocket: client } as ResponseInit & {\n webSocket: WebSocket\n })\n}\n\n/**\n * Deno handler. Uses `Deno.upgradeWebSocket(req)` to produce the\n * response + socket pair, then plugs the socket into the registry.\n *\n * Usage:\n * ```ts\n * Deno.serve(async (req) => {\n * const url = new URL(req.url)\n * if (url.pathname === '/agent/ws') return handleDenoUpgrade(req, agent)\n * return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })\n * })\n * ```\n */\nexport async function handleDenoUpgrade(req: Request, agent: AgentCoreHandle): Promise<Response> {\n const token = extractToken(req)\n if (!token) return new Response('Unauthorized', { status: 401 })\n\n const Deno_ = (\n globalThis as unknown as {\n Deno?: {\n upgradeWebSocket: (req: Request) => { socket: WebSocket; response: Response }\n }\n }\n ).Deno\n if (!Deno_) {\n return new Response('Deno.upgradeWebSocket unavailable in this runtime', { status: 501 })\n }\n\n const { socket, response } = Deno_.upgradeWebSocket(req)\n const conn = createWHATWGPairingConnection(socket)\n\n // Deno opens the socket asynchronously; validate the token first,\n // then register on `open` so frames aren't missed.\n socket.addEventListener(\n 'open',\n () => {\n void agent.acceptConnection(token, conn).then((result) => {\n if (!result.ok) conn.close()\n })\n },\n { once: true },\n )\n\n return response\n}\n"]}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ClientFrame, ServerFrame, HelloFrame, LogEntry } from '../../protocol.js';
|
|
2
|
+
import { type RpcOptions, type RpcError } from './rpc.js';
|
|
3
|
+
export type { RpcOptions, RpcError };
|
|
2
4
|
/**
|
|
3
|
-
* Thin abstraction over a
|
|
4
|
-
*
|
|
5
|
+
* Thin abstraction over a single paired WebSocket. Consumed by the
|
|
6
|
+
* registry implementations; runtime-specific adapters (`ws`-lib,
|
|
7
|
+
* `WebSocketPair`, `Deno.upgradeWebSocket`, `Bun.serve` upgrade) build
|
|
8
|
+
* one of these and pass it to `registry.register()`.
|
|
5
9
|
*/
|
|
6
10
|
export interface PairingConnection {
|
|
7
11
|
send(frame: ServerFrame): void;
|
|
@@ -9,35 +13,85 @@ export interface PairingConnection {
|
|
|
9
13
|
onClose(handler: () => void): void;
|
|
10
14
|
close(): void;
|
|
11
15
|
}
|
|
12
|
-
export type RpcError = {
|
|
13
|
-
code: 'paused' | 'invalid' | 'timeout' | 'schema-error' | 'internal' | string;
|
|
14
|
-
detail?: string;
|
|
15
|
-
};
|
|
16
|
-
export type RpcOptions = {
|
|
17
|
-
timeoutMs?: number;
|
|
18
|
-
};
|
|
19
16
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* A per-call frame subscriber. Return `true` to remove this
|
|
18
|
+
* subscriber (one-shot), or `false` to keep receiving. The registry
|
|
19
|
+
* dispatches every inbound `ClientFrame` to every active subscriber
|
|
20
|
+
* for the given `tid`; subscribers filter by `frame.t` + identifiers
|
|
21
|
+
* (correlation id, confirm id, state path) to find the one that
|
|
22
|
+
* belongs to their request.
|
|
23
23
|
*/
|
|
24
|
-
export
|
|
24
|
+
export type FrameSubscriber = (frame: ClientFrame) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Registry of live browser pairings. Pure routing + hello cache —
|
|
27
|
+
* request-lifecycle state (in-flight RPC promises, confirm waits,
|
|
28
|
+
* long-polls) lives in the LAP handlers that need it, not here.
|
|
29
|
+
*
|
|
30
|
+
* Two implementations ship today:
|
|
31
|
+
* - `InMemoryPairingRegistry` for long-lived server processes
|
|
32
|
+
* (Node, Bun, Deno, Deno Deploy).
|
|
33
|
+
* - A Cloudflare Durable Object implementation (see
|
|
34
|
+
* `server/cloudflare`) for stateless Worker runtimes.
|
|
35
|
+
*
|
|
36
|
+
* Other runtimes can implement this interface the same way; the
|
|
37
|
+
* contract is intentionally small.
|
|
38
|
+
*/
|
|
39
|
+
export interface PairingRegistry {
|
|
40
|
+
register(tid: string, conn: PairingConnection): void;
|
|
41
|
+
unregister(tid: string): void;
|
|
42
|
+
isPaired(tid: string): boolean;
|
|
43
|
+
getHello(tid: string): HelloFrame | null;
|
|
44
|
+
/** Send a frame. No-op when the pairing is absent or closed. */
|
|
45
|
+
send(tid: string, frame: ServerFrame): void;
|
|
46
|
+
/**
|
|
47
|
+
* Subscribe to frames from the paired browser. Returns an
|
|
48
|
+
* unsubscribe function. A subscriber can remove itself mid-dispatch
|
|
49
|
+
* by returning `true` from its callback — useful for one-shot
|
|
50
|
+
* request/response correlation.
|
|
51
|
+
*/
|
|
52
|
+
subscribe(tid: string, handler: FrameSubscriber): () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Observe the pairing closing (WebSocket drop, `unregister`, etc.).
|
|
55
|
+
* Handlers registered before close fire; handlers registered after
|
|
56
|
+
* close fire synchronously. Returns an unsubscribe function.
|
|
57
|
+
*/
|
|
58
|
+
onClose(tid: string, handler: () => void): () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Send a typed rpc frame and await its matching reply. See
|
|
61
|
+
* `./rpc.ts::rpc` for the full contract.
|
|
62
|
+
*/
|
|
63
|
+
rpc(tid: string, tool: string, args: unknown, opts?: RpcOptions): Promise<unknown>;
|
|
64
|
+
/** See `./rpc.ts::waitForConfirm`. */
|
|
65
|
+
waitForConfirm(tid: string, confirmId: string, timeoutMs: number): Promise<{
|
|
66
|
+
outcome: 'confirmed' | 'user-cancelled';
|
|
67
|
+
stateAfter?: unknown;
|
|
68
|
+
}>;
|
|
69
|
+
/** See `./rpc.ts::waitForChange`. */
|
|
70
|
+
waitForChange(tid: string, path: string | undefined, timeoutMs: number): Promise<{
|
|
71
|
+
status: 'changed' | 'timeout';
|
|
72
|
+
stateAfter: unknown;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Single-process in-memory registry. Correct for Node/Bun/Deno/Deno
|
|
77
|
+
* Deploy — anywhere the server process can hold a long-lived
|
|
78
|
+
* WebSocket. Not suitable for stateless Worker isolates; use the
|
|
79
|
+
* Durable Object registry for Cloudflare.
|
|
80
|
+
*/
|
|
81
|
+
export declare class InMemoryPairingRegistry implements PairingRegistry {
|
|
25
82
|
private pairings;
|
|
26
|
-
private now;
|
|
27
83
|
private onLogAppend;
|
|
28
84
|
constructor(opts?: {
|
|
29
|
-
now?: () => number;
|
|
30
85
|
onLogAppend?: (tid: string, entry: LogEntry) => void;
|
|
31
86
|
});
|
|
32
87
|
register(tid: string, conn: PairingConnection): void;
|
|
33
88
|
unregister(tid: string): void;
|
|
34
89
|
isPaired(tid: string): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Send a ServerFrame to the paired browser connection, if one is live.
|
|
37
|
-
* No-op when unpaired or closed.
|
|
38
|
-
*/
|
|
39
|
-
notify(tid: string, frame: ServerFrame): void;
|
|
40
90
|
getHello(tid: string): HelloFrame | null;
|
|
91
|
+
send(tid: string, frame: ServerFrame): void;
|
|
92
|
+
subscribe(tid: string, handler: FrameSubscriber): () => void;
|
|
93
|
+
onClose(tid: string, handler: () => void): () => void;
|
|
94
|
+
private dispatch;
|
|
41
95
|
rpc(tid: string, tool: string, args: unknown, opts?: RpcOptions): Promise<unknown>;
|
|
42
96
|
waitForConfirm(tid: string, confirmId: string, timeoutMs: number): Promise<{
|
|
43
97
|
outcome: 'confirmed' | 'user-cancelled';
|
|
@@ -47,7 +101,16 @@ export declare class WsPairingRegistry {
|
|
|
47
101
|
status: 'changed' | 'timeout';
|
|
48
102
|
stateAfter: unknown;
|
|
49
103
|
}>;
|
|
50
|
-
|
|
104
|
+
/** @deprecated Use `send(tid, frame)` directly; semantics are identical. */
|
|
105
|
+
notify(tid: string, frame: ServerFrame): void;
|
|
51
106
|
private handleClose;
|
|
52
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Back-compat alias for the prior class name. New code should use
|
|
110
|
+
* `InMemoryPairingRegistry`. Removed in a future major.
|
|
111
|
+
*
|
|
112
|
+
* @deprecated Use `InMemoryPairingRegistry` directly.
|
|
113
|
+
*/
|
|
114
|
+
export declare const WsPairingRegistry: typeof InMemoryPairingRegistry;
|
|
115
|
+
export type WsPairingRegistry = InMemoryPairingRegistry;
|
|
53
116
|
//# sourceMappingURL=pairing-registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,45 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rpc as rpcHelper, waitForConfirm as waitForConfirmHelper, waitForChange as waitForChangeHelper, } from './rpc.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Single-process in-memory registry. Correct for Node/Bun/Deno/Deno
|
|
4
|
+
* Deploy — anywhere the server process can hold a long-lived
|
|
5
|
+
* WebSocket. Not suitable for stateless Worker isolates; use the
|
|
6
|
+
* Durable Object registry for Cloudflare.
|
|
6
7
|
*/
|
|
7
|
-
export class
|
|
8
|
+
export class InMemoryPairingRegistry {
|
|
8
9
|
pairings = new Map();
|
|
9
|
-
now;
|
|
10
10
|
onLogAppend;
|
|
11
11
|
constructor(opts = {}) {
|
|
12
|
-
this.now = opts.now ?? (() => Date.now());
|
|
13
12
|
this.onLogAppend = opts.onLogAppend ?? null;
|
|
14
13
|
}
|
|
15
14
|
register(tid, conn) {
|
|
16
15
|
const p = {
|
|
17
16
|
conn,
|
|
18
17
|
hello: null,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
pendingWait: [],
|
|
18
|
+
subscribers: new Set(),
|
|
19
|
+
closeHandlers: new Set(),
|
|
22
20
|
closed: false,
|
|
23
21
|
};
|
|
24
22
|
this.pairings.set(tid, p);
|
|
25
|
-
conn.onFrame((frame) => this.
|
|
23
|
+
conn.onFrame((frame) => this.dispatch(tid, frame));
|
|
26
24
|
conn.onClose(() => this.handleClose(tid));
|
|
27
25
|
}
|
|
28
26
|
unregister(tid) {
|
|
29
|
-
const p = this.pairings.get(tid);
|
|
30
|
-
if (!p)
|
|
31
|
-
return;
|
|
32
27
|
this.handleClose(tid);
|
|
33
28
|
}
|
|
34
29
|
isPaired(tid) {
|
|
35
30
|
const p = this.pairings.get(tid);
|
|
36
31
|
return !!p && !p.closed;
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
notify(tid, frame) {
|
|
33
|
+
getHello(tid) {
|
|
34
|
+
return this.pairings.get(tid)?.hello ?? null;
|
|
35
|
+
}
|
|
36
|
+
send(tid, frame) {
|
|
43
37
|
const p = this.pairings.get(tid);
|
|
44
38
|
if (!p || p.closed)
|
|
45
39
|
return;
|
|
@@ -47,159 +41,103 @@ export class WsPairingRegistry {
|
|
|
47
41
|
p.conn.send(frame);
|
|
48
42
|
}
|
|
49
43
|
catch {
|
|
50
|
-
//
|
|
44
|
+
// Connection may have dropped between isPaired() and send(); no-op.
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
|
-
|
|
54
|
-
return this.pairings.get(tid)?.hello ?? null;
|
|
55
|
-
}
|
|
56
|
-
async rpc(tid, tool, args, opts = {}) {
|
|
47
|
+
subscribe(tid, handler) {
|
|
57
48
|
const p = this.pairings.get(tid);
|
|
58
|
-
if (!p || p.closed)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return new Promise((resolve, reject) => {
|
|
65
|
-
const entry = {
|
|
66
|
-
resolve,
|
|
67
|
-
reject,
|
|
68
|
-
timer: setTimeout(() => {
|
|
69
|
-
p.pendingRpc.delete(id);
|
|
70
|
-
reject({ code: 'timeout' });
|
|
71
|
-
}, timeoutMs),
|
|
72
|
-
};
|
|
73
|
-
p.pendingRpc.set(id, entry);
|
|
74
|
-
const frame = { t: 'rpc', id, tool, args };
|
|
75
|
-
try {
|
|
76
|
-
p.conn.send(frame);
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
p.pendingRpc.delete(id);
|
|
80
|
-
if (entry.timer)
|
|
81
|
-
clearTimeout(entry.timer);
|
|
82
|
-
reject({ code: 'internal', detail: String(e) });
|
|
83
|
-
}
|
|
84
|
-
});
|
|
49
|
+
if (!p || p.closed)
|
|
50
|
+
return () => { };
|
|
51
|
+
p.subscribers.add(handler);
|
|
52
|
+
return () => {
|
|
53
|
+
p.subscribers.delete(handler);
|
|
54
|
+
};
|
|
85
55
|
}
|
|
86
|
-
|
|
56
|
+
onClose(tid, handler) {
|
|
87
57
|
const p = this.pairings.get(tid);
|
|
88
58
|
if (!p || p.closed) {
|
|
89
|
-
|
|
59
|
+
// Already closed — fire synchronously so callers don't hang
|
|
60
|
+
// waiting for a close that already happened.
|
|
61
|
+
queueMicrotask(handler);
|
|
62
|
+
return () => { };
|
|
90
63
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
p.pendingConfirm.delete(confirmId);
|
|
96
|
-
resolve({ outcome: 'user-cancelled' });
|
|
97
|
-
}, timeoutMs),
|
|
98
|
-
};
|
|
99
|
-
p.pendingConfirm.set(confirmId, entry);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
async waitForChange(tid, path, timeoutMs) {
|
|
103
|
-
const p = this.pairings.get(tid);
|
|
104
|
-
if (!p || p.closed)
|
|
105
|
-
return { status: 'timeout', stateAfter: null };
|
|
106
|
-
return new Promise((resolve) => {
|
|
107
|
-
const entry = {
|
|
108
|
-
path,
|
|
109
|
-
resolve,
|
|
110
|
-
timer: setTimeout(() => {
|
|
111
|
-
const idx = p.pendingWait.indexOf(entry);
|
|
112
|
-
if (idx >= 0)
|
|
113
|
-
p.pendingWait.splice(idx, 1);
|
|
114
|
-
resolve({ status: 'timeout', stateAfter: null });
|
|
115
|
-
}, timeoutMs),
|
|
116
|
-
};
|
|
117
|
-
p.pendingWait.push(entry);
|
|
118
|
-
});
|
|
64
|
+
p.closeHandlers.add(handler);
|
|
65
|
+
return () => {
|
|
66
|
+
p.closeHandlers.delete(handler);
|
|
67
|
+
};
|
|
119
68
|
}
|
|
120
|
-
|
|
69
|
+
dispatch(tid, frame) {
|
|
121
70
|
const p = this.pairings.get(tid);
|
|
122
71
|
if (!p || p.closed)
|
|
123
72
|
return;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!e)
|
|
142
|
-
break;
|
|
143
|
-
p.pendingRpc.delete(frame.id);
|
|
144
|
-
if (e.timer)
|
|
145
|
-
clearTimeout(e.timer);
|
|
146
|
-
e.reject({ code: frame.code, detail: frame.detail });
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
case 'confirm-resolved': {
|
|
150
|
-
const e = p.pendingConfirm.get(frame.confirmId);
|
|
151
|
-
if (!e)
|
|
152
|
-
break;
|
|
153
|
-
p.pendingConfirm.delete(frame.confirmId);
|
|
154
|
-
if (e.timer)
|
|
155
|
-
clearTimeout(e.timer);
|
|
156
|
-
e.resolve({ outcome: frame.outcome, stateAfter: frame.stateAfter });
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
case 'state-update': {
|
|
160
|
-
for (let i = p.pendingWait.length - 1; i >= 0; i--) {
|
|
161
|
-
const w = p.pendingWait[i];
|
|
162
|
-
if (w === undefined)
|
|
163
|
-
continue;
|
|
164
|
-
if (w.path === undefined || w.path === frame.path || frame.path.startsWith(w.path)) {
|
|
165
|
-
p.pendingWait.splice(i, 1);
|
|
166
|
-
if (w.timer)
|
|
167
|
-
clearTimeout(w.timer);
|
|
168
|
-
w.resolve({ status: 'changed', stateAfter: frame.stateAfter });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
73
|
+
// hello and log-append are registry-owned side effects — handled
|
|
74
|
+
// here so no per-call subscriber has to pick them up.
|
|
75
|
+
if (frame.t === 'hello') {
|
|
76
|
+
p.hello = frame;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (frame.t === 'log-append') {
|
|
80
|
+
this.onLogAppend?.(tid, frame.entry);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Iterate over a snapshot because subscribers may self-remove
|
|
84
|
+
// mid-iteration by returning true.
|
|
85
|
+
const snapshot = Array.from(p.subscribers);
|
|
86
|
+
for (const sub of snapshot) {
|
|
87
|
+
try {
|
|
88
|
+
if (sub(frame))
|
|
89
|
+
p.subscribers.delete(sub);
|
|
172
90
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
91
|
+
catch {
|
|
92
|
+
// One bad subscriber shouldn't break the others.
|
|
93
|
+
p.subscribers.delete(sub);
|
|
176
94
|
}
|
|
177
95
|
}
|
|
178
96
|
}
|
|
97
|
+
// ── Convenience wrappers ───────────────────────────────────────
|
|
98
|
+
// The following methods delegate to the free-function helpers in
|
|
99
|
+
// `./rpc.ts`. They're here so the in-memory registry remains a
|
|
100
|
+
// one-stop testing surface (spy on `registry.rpc`, etc.) without
|
|
101
|
+
// couping the `PairingRegistry` interface to request-lifecycle
|
|
102
|
+
// details. External implementations (e.g. the Cloudflare Durable
|
|
103
|
+
// Object registry) are NOT required to provide these; the LAP
|
|
104
|
+
// handlers always go through the free helpers.
|
|
105
|
+
rpc(tid, tool, args, opts = {}) {
|
|
106
|
+
return rpcHelper(this, tid, tool, args, opts);
|
|
107
|
+
}
|
|
108
|
+
waitForConfirm(tid, confirmId, timeoutMs) {
|
|
109
|
+
return waitForConfirmHelper(this, tid, confirmId, timeoutMs);
|
|
110
|
+
}
|
|
111
|
+
waitForChange(tid, path, timeoutMs) {
|
|
112
|
+
return waitForChangeHelper(this, tid, path, timeoutMs);
|
|
113
|
+
}
|
|
114
|
+
/** @deprecated Use `send(tid, frame)` directly; semantics are identical. */
|
|
115
|
+
notify(tid, frame) {
|
|
116
|
+
this.send(tid, frame);
|
|
117
|
+
}
|
|
179
118
|
handleClose(tid) {
|
|
180
119
|
const p = this.pairings.get(tid);
|
|
181
|
-
if (!p)
|
|
120
|
+
if (!p || p.closed)
|
|
182
121
|
return;
|
|
183
122
|
p.closed = true;
|
|
184
|
-
for (const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (e.timer)
|
|
192
|
-
clearTimeout(e.timer);
|
|
193
|
-
e.resolve({ outcome: 'user-cancelled' });
|
|
194
|
-
}
|
|
195
|
-
p.pendingConfirm.clear();
|
|
196
|
-
for (const w of p.pendingWait) {
|
|
197
|
-
if (w.timer)
|
|
198
|
-
clearTimeout(w.timer);
|
|
199
|
-
w.resolve({ status: 'timeout', stateAfter: null });
|
|
123
|
+
for (const h of Array.from(p.closeHandlers)) {
|
|
124
|
+
try {
|
|
125
|
+
h();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Swallow — handlers run best-effort.
|
|
129
|
+
}
|
|
200
130
|
}
|
|
201
|
-
p.
|
|
131
|
+
p.closeHandlers.clear();
|
|
132
|
+
p.subscribers.clear();
|
|
202
133
|
this.pairings.delete(tid);
|
|
203
134
|
}
|
|
204
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Back-compat alias for the prior class name. New code should use
|
|
138
|
+
* `InMemoryPairingRegistry`. Removed in a future major.
|
|
139
|
+
*
|
|
140
|
+
* @deprecated Use `InMemoryPairingRegistry` directly.
|
|
141
|
+
*/
|
|
142
|
+
export const WsPairingRegistry = InMemoryPairingRegistry;
|
|
205
143
|
//# sourceMappingURL=pairing-registry.js.map
|