agents 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/{agent-tool-types-CzGGB-20.d.ts → agent-routing-DE5zmCQ8.d.ts} +1844 -1336
- package/dist/agent-routing.d.ts +14 -0
- package/dist/agent-routing.js +187 -0
- package/dist/agent-routing.js.map +1 -0
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-zR2d5uij.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/agent-tools.js +2 -1
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.js +6 -2
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js +42 -0
- package/dist/capability-BjSKYpzg.js.map +1 -0
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2328 -2015
- package/dist/chat/index.js +891 -521
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +14 -1
- package/dist/chat/react.js +82 -52
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.js +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
- package/dist/client-jagG8a9_.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
- package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/current-agent-Da_C9a3b.d.ts +266 -0
- package/dist/current-agent-DhoDkSnH.js +51 -0
- package/dist/current-agent-DhoDkSnH.js.map +1 -0
- package/dist/diagnostics-BzvaX2UT.js +45 -0
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
- package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
- package/dist/email-send.d.ts +15 -0
- package/dist/email-send.js +32 -0
- package/dist/email-send.js.map +1 -0
- package/dist/email.d.ts +14 -10
- package/dist/email.js.map +1 -1
- package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
- package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
- package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-BRnybD6X.d.ts → index-YSKgfgg9.d.ts} +21 -31
- package/dist/index.d.ts +113 -101
- package/dist/index.js +11 -7234
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context-BlxFEWfn.d.ts +19 -0
- package/dist/internal_context.d.ts +10 -4
- package/dist/internal_context.js +1 -10
- package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
- package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
- package/dist/invoker-CHMnoxIA.js.map +1 -0
- package/dist/lifecycle/index.d.ts +66 -0
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
- package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
- package/dist/mcp/client/index.d.ts +42 -0
- package/dist/mcp/{client.js → client/index.js} +1 -1
- package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
- package/dist/mcp/{x402.js → client/x402.js} +2 -2
- package/dist/mcp/client/x402.js.map +1 -0
- package/dist/mcp/index.d.ts +36 -36
- package/dist/mcp/index.js +14 -16
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
- package/dist/mcp/{server.js → server/index.js} +1 -1
- package/dist/observability/ai/index.js +50 -35
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.js +3 -50
- package/dist/observability/index.js.map +1 -1
- package/dist/{protocol-Dqc2MQxo.js → protocol-B0nh6KNf.js} +19 -21
- package/dist/protocol-B0nh6KNf.js.map +1 -0
- package/dist/react.d.ts +4 -4
- package/dist/react.js +1 -1
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/schedule.d.ts +25 -94
- package/dist/schedule.js +1 -98
- package/dist/schedule.js.map +1 -1
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/scheduler-Dwh85ZGl.d.ts +223 -0
- package/dist/schedules/index.d.ts +22 -0
- package/dist/schedules/index.js +2 -0
- package/dist/schedules/parser.d.ts +79 -0
- package/dist/schedules/parser.js +103 -0
- package/dist/schedules/parser.js.map +1 -0
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sql-error-CPY-GXyI.d.ts +12 -0
- package/dist/sql-error.d.ts +2 -0
- package/dist/sql-error.js +16 -0
- package/dist/sql-error.js.map +1 -0
- package/dist/src-DlSHshb2.js +6963 -0
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +22 -22
- package/dist/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/dist/{ws-chat-transport-CIoOBbO7.js → ws-chat-transport-rWwta645.js} +152 -15
- package/dist/ws-chat-transport-rWwta645.js.map +1 -0
- package/docs/agent-class.md +29 -87
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +19 -25
- package/docs/context.md +131 -0
- package/docs/durable-execution.md +1 -1
- package/docs/http-websockets.md +1 -11
- package/docs/human-in-the-loop.md +1 -1
- package/docs/index.md +16 -12
- package/docs/lifecycle.md +370 -0
- package/docs/long-running-agents.md +4 -6
- package/docs/mcp-client.md +55 -0
- package/docs/mcp-servers.md +5 -1
- package/docs/observability.md +11 -11
- package/docs/resumable-streaming.md +2 -2
- package/docs/routing.md +105 -0
- package/docs/scheduling.md +175 -15
- package/docs/server-driven-messages.md +1 -1
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +185 -125
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +144 -33
- package/dist/cli/index.js +0 -26
- package/dist/cli/index.js.map +0 -1
- package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
- package/dist/client-zqKcsyFa.js.map +0 -1
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/experimental/memory/session/index.d.ts +0 -670
- package/dist/experimental/memory/session/index.js +0 -2374
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/handler-stateless-CIkKPETH.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
- package/dist/internal_context.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -42
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/mcp/x402.js.map +0 -1
- package/dist/protocol-Dqc2MQxo.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
- package/dist/ws-chat-transport-CIoOBbO7.js.map +0 -1
- /package/dist/{cli/index.d.ts → index-BVVgDSdq.d.ts} +0 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
|
|
2
|
+
import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
|
+
import { t as isChannelMessageSurface } from "../surface-bZZJqBka.js";
|
|
4
|
+
import { c as unsupported, t as compositeDestinations } from "../internal-CYlgHl1l.js";
|
|
5
|
+
import { n as collectText, r as consumeChunks, t as matchesPath } from "../ingress-BfetZbMO.js";
|
|
6
|
+
//#region src/channels/fallback.ts
|
|
7
|
+
/**
|
|
8
|
+
* Build an inert fallback destination for a `ChannelHost` to resolve.
|
|
9
|
+
*
|
|
10
|
+
* The Host skips unavailable destinations, advances after confirmed failures,
|
|
11
|
+
* and stops after a delivered or uncertain result to avoid duplicates.
|
|
12
|
+
*/
|
|
13
|
+
function fallback(surfaces) {
|
|
14
|
+
return {
|
|
15
|
+
channelKey: "fallback",
|
|
16
|
+
version: 1,
|
|
17
|
+
address: { surfaces },
|
|
18
|
+
label: surfaces.map((surface) => surface.label).join(", then ")
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Advance only when a failed destination has not started reading the answer.
|
|
23
|
+
*
|
|
24
|
+
* Replaying an arbitrarily large consumed prefix requires an arbitrarily large
|
|
25
|
+
* buffer. Instead, each attempt gets a cancellation-shielded view of the same
|
|
26
|
+
* source. Once an attempt asks for its first chunk, its result is terminal and
|
|
27
|
+
* the source is never handed to another destination.
|
|
28
|
+
*/
|
|
29
|
+
async function streamWithFallbackBeforeRead(resolve, destinations, chunks, options) {
|
|
30
|
+
const reader = chunks.getReader();
|
|
31
|
+
let drained = false;
|
|
32
|
+
function attempt() {
|
|
33
|
+
let startedReading = false;
|
|
34
|
+
return {
|
|
35
|
+
chunks: new ReadableStream({
|
|
36
|
+
async pull(controller) {
|
|
37
|
+
startedReading = true;
|
|
38
|
+
const result = await reader.read();
|
|
39
|
+
if (result.done) {
|
|
40
|
+
drained = true;
|
|
41
|
+
controller.close();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
controller.enqueue(result.value);
|
|
45
|
+
},
|
|
46
|
+
cancel() {}
|
|
47
|
+
}, { highWaterMark: 0 }),
|
|
48
|
+
startedReading: () => startedReading
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
for (let index = 0; index < destinations.length - 1; index += 1) {
|
|
53
|
+
const destination = destinations[index];
|
|
54
|
+
if (!await resolve.isAvailable(destination)) continue;
|
|
55
|
+
const current = attempt();
|
|
56
|
+
const result = await resolve.stream(destination, current.chunks, options);
|
|
57
|
+
if (result.status !== "failed" || current.startedReading()) return result;
|
|
58
|
+
}
|
|
59
|
+
const final = attempt();
|
|
60
|
+
return await resolve.stream(destinations.at(-1), final.chunks, options);
|
|
61
|
+
} finally {
|
|
62
|
+
if (!drained) await reader.cancel().catch(() => {});
|
|
63
|
+
reader.releaseLock();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Build the ordinary Channel installed under the reserved fallback key. */
|
|
67
|
+
function fallbackChannel(resolve) {
|
|
68
|
+
async function run(surface, operation) {
|
|
69
|
+
const destinations = compositeDestinations(surface);
|
|
70
|
+
if (!destinations) return unsupported("FALLBACK_SURFACE_INVALID", "Fallback surface must contain at least one valid destination");
|
|
71
|
+
for (let index = 0; index < destinations.length - 1; index += 1) {
|
|
72
|
+
const destination = destinations[index];
|
|
73
|
+
if (await resolve.isAvailable(destination)) {
|
|
74
|
+
const result = await operation(destination);
|
|
75
|
+
if (result.status !== "failed") return result;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return operation(destinations.at(-1));
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
deliver(surface, message, options) {
|
|
82
|
+
return run(surface, (destination) => resolve.deliver(destination, message, options));
|
|
83
|
+
},
|
|
84
|
+
async stream(surface, chunks, options) {
|
|
85
|
+
const destinations = compositeDestinations(surface);
|
|
86
|
+
if (!destinations) {
|
|
87
|
+
await chunks.cancel().catch(() => {});
|
|
88
|
+
return unsupported("FALLBACK_SURFACE_INVALID", "Fallback surface must contain at least one valid destination");
|
|
89
|
+
}
|
|
90
|
+
return streamWithFallbackBeforeRead(resolve, destinations, chunks, options);
|
|
91
|
+
},
|
|
92
|
+
requestApproval(surface, options) {
|
|
93
|
+
return run(surface, (destination) => resolve.requestApproval(destination, options));
|
|
94
|
+
},
|
|
95
|
+
async isAvailable(surface) {
|
|
96
|
+
const destinations = compositeDestinations(surface);
|
|
97
|
+
if (!destinations) return true;
|
|
98
|
+
for (const destination of destinations) if (await resolve.isAvailable(destination)) return true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/channels/fanout.ts
|
|
105
|
+
function teeAll(source, count) {
|
|
106
|
+
const branches = [];
|
|
107
|
+
let rest = source;
|
|
108
|
+
for (let index = 0; index < count - 1; index += 1) {
|
|
109
|
+
const [branch, remainder] = rest.tee();
|
|
110
|
+
branches.push(branch);
|
|
111
|
+
rest = remainder;
|
|
112
|
+
}
|
|
113
|
+
branches.push(rest);
|
|
114
|
+
return branches;
|
|
115
|
+
}
|
|
116
|
+
/** Build an inert fanout destination for a `ChannelHost` to resolve. */
|
|
117
|
+
function fanout(surfaces) {
|
|
118
|
+
return {
|
|
119
|
+
channelKey: "fanout",
|
|
120
|
+
version: 1,
|
|
121
|
+
address: { surfaces },
|
|
122
|
+
label: surfaces.map((surface) => surface.label).join(" and ")
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Build the ordinary Channel installed under the reserved fanout key. */
|
|
126
|
+
function fanoutChannel(resolve) {
|
|
127
|
+
async function run(surface, operation) {
|
|
128
|
+
const destinations = compositeDestinations(surface);
|
|
129
|
+
if (!destinations) return unsupported("FANOUT_SURFACE_INVALID", "Fanout surface must contain at least one valid destination");
|
|
130
|
+
return combine(await Promise.all(destinations.map(operation)));
|
|
131
|
+
}
|
|
132
|
+
function combine(results) {
|
|
133
|
+
if (results.every((result) => result.status === "delivered")) return { status: "delivered" };
|
|
134
|
+
if (results.every((result) => result.status === "failed")) return {
|
|
135
|
+
status: "failed",
|
|
136
|
+
retryable: results.every((result) => result.status === "failed" && result.retryable),
|
|
137
|
+
error: {
|
|
138
|
+
code: "FANOUT_DELIVERY_FAILED",
|
|
139
|
+
message: "Every fanout destination rejected the delivery"
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
status: "uncertain",
|
|
144
|
+
error: {
|
|
145
|
+
code: "FANOUT_DELIVERY_UNCERTAIN",
|
|
146
|
+
message: "Fanout delivery was partial or had an uncertain destination outcome"
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
deliver(surface, message, options) {
|
|
152
|
+
return run(surface, (destination) => resolve.deliver(destination, message, options));
|
|
153
|
+
},
|
|
154
|
+
async stream(surface, chunks, options) {
|
|
155
|
+
const destinations = compositeDestinations(surface);
|
|
156
|
+
if (!destinations) {
|
|
157
|
+
await chunks.cancel().catch(() => {});
|
|
158
|
+
return unsupported("FANOUT_SURFACE_INVALID", "Fanout surface must contain at least one valid destination");
|
|
159
|
+
}
|
|
160
|
+
const branches = teeAll(chunks, destinations.length);
|
|
161
|
+
return combine(await Promise.all(destinations.map(async (destination, index) => {
|
|
162
|
+
const branch = branches[index];
|
|
163
|
+
try {
|
|
164
|
+
return await resolve.stream(destination, branch, options);
|
|
165
|
+
} finally {
|
|
166
|
+
branch.cancel().catch(() => {});
|
|
167
|
+
}
|
|
168
|
+
})));
|
|
169
|
+
},
|
|
170
|
+
requestApproval(surface, options) {
|
|
171
|
+
return run(surface, (destination) => resolve.requestApproval(destination, options));
|
|
172
|
+
},
|
|
173
|
+
async isAvailable(surface) {
|
|
174
|
+
const destinations = compositeDestinations(surface);
|
|
175
|
+
if (!destinations) return true;
|
|
176
|
+
return (await Promise.all(destinations.map((destination) => resolve.isAvailable(destination)))).every(Boolean);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/channels/host/index.ts
|
|
182
|
+
const POLICY_KEYS = /* @__PURE__ */ new Set(["fallback", "fanout"]);
|
|
183
|
+
var _channels = /* @__PURE__ */ new WeakMap();
|
|
184
|
+
var _defaultRoute = /* @__PURE__ */ new WeakMap();
|
|
185
|
+
var _findUser = /* @__PURE__ */ new WeakMap();
|
|
186
|
+
var _onRoute = /* @__PURE__ */ new WeakMap();
|
|
187
|
+
var _onMessage = /* @__PURE__ */ new WeakMap();
|
|
188
|
+
var _onApprovalResponse = /* @__PURE__ */ new WeakMap();
|
|
189
|
+
var _ChannelHost_brand = /* @__PURE__ */ new WeakSet();
|
|
190
|
+
/**
|
|
191
|
+
* Authenticates and normalizes ingress through configured Channel adapters,
|
|
192
|
+
* resolves outbound surfaces, and awaits the application's durable handoff.
|
|
193
|
+
*/
|
|
194
|
+
var ChannelHost = class {
|
|
195
|
+
constructor(options) {
|
|
196
|
+
_classPrivateMethodInitSpec(this, _ChannelHost_brand);
|
|
197
|
+
_classPrivateFieldInitSpec(this, _channels, void 0);
|
|
198
|
+
_classPrivateFieldInitSpec(this, _defaultRoute, void 0);
|
|
199
|
+
_classPrivateFieldInitSpec(this, _findUser, void 0);
|
|
200
|
+
_classPrivateFieldInitSpec(this, _onRoute, void 0);
|
|
201
|
+
_classPrivateFieldInitSpec(this, _onMessage, void 0);
|
|
202
|
+
_classPrivateFieldInitSpec(this, _onApprovalResponse, void 0);
|
|
203
|
+
for (const channelKey of Object.keys(options.channels)) if (POLICY_KEYS.has(channelKey)) throw new Error(`Channel key "${channelKey}" is reserved for a delivery policy`);
|
|
204
|
+
const channels = { ...options.channels };
|
|
205
|
+
_classPrivateFieldSet2(_channels, this, channels);
|
|
206
|
+
channels.fallback = fallbackChannel(this);
|
|
207
|
+
channels.fanout = fanoutChannel(this);
|
|
208
|
+
_classPrivateFieldSet2(_defaultRoute, this, options.defaultRoute);
|
|
209
|
+
_classPrivateFieldSet2(_findUser, this, options.findUser);
|
|
210
|
+
_classPrivateFieldSet2(_onRoute, this, options.onRoute);
|
|
211
|
+
_classPrivateFieldSet2(_onMessage, this, options.onMessage);
|
|
212
|
+
_classPrivateFieldSet2(_onApprovalResponse, this, options.onApprovalResponse);
|
|
213
|
+
}
|
|
214
|
+
async handleRequest(request) {
|
|
215
|
+
for (const [channelKey, channel] of Object.entries(_classPrivateFieldGet2(_channels, this))) {
|
|
216
|
+
const ingress = channel.ingress;
|
|
217
|
+
if (!ingress) continue;
|
|
218
|
+
try {
|
|
219
|
+
const result = await ingress.receive(request);
|
|
220
|
+
if (!result) continue;
|
|
221
|
+
for (const envelope of result.events) await _assertClassBrand(_ChannelHost_brand, this, _dispatch).call(this, channelKey, channel, envelope);
|
|
222
|
+
return result.response;
|
|
223
|
+
} catch {
|
|
224
|
+
return new Response("Failed to handle Channel event", { status: 500 });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async handleEmail(email) {
|
|
229
|
+
for (const [channelKey, channel] of Object.entries(_classPrivateFieldGet2(_channels, this))) {
|
|
230
|
+
const ingress = channel.emailIngress;
|
|
231
|
+
if (!ingress) continue;
|
|
232
|
+
const result = await ingress.receive(email);
|
|
233
|
+
if (!result) continue;
|
|
234
|
+
for (const envelope of result.events) await _assertClassBrand(_ChannelHost_brand, this, _dispatch).call(this, channelKey, channel, envelope);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
/** Deliver through the configured Channel or composite named by the surface. */
|
|
240
|
+
deliver(surface, message, options) {
|
|
241
|
+
return _assertClassBrand(_ChannelHost_brand, this, _outbound).call(this, surface, (channel, destination) => {
|
|
242
|
+
if (!channel.deliver) return Promise.resolve(unsupported("CHANNEL_DELIVERY_UNSUPPORTED", `Channel "${destination.channelKey}" does not support delivery`));
|
|
243
|
+
return channel.deliver(destination, message, options);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Deliver a progressively generated answer to the Channel or composite
|
|
248
|
+
* named by the surface.
|
|
249
|
+
*
|
|
250
|
+
* A Channel that can stream consumes the stream itself. A Channel that
|
|
251
|
+
* cannot never learns it was a stream, because the Host collects the answer
|
|
252
|
+
* and calls `deliver` once.
|
|
253
|
+
*/
|
|
254
|
+
async stream(surface, chunks, options = {}) {
|
|
255
|
+
if (!isChannelMessageSurface(surface)) {
|
|
256
|
+
await chunks.cancel().catch(() => {});
|
|
257
|
+
return invalidSurface();
|
|
258
|
+
}
|
|
259
|
+
const channel = _assertClassBrand(_ChannelHost_brand, this, _configuredChannel).call(this, surface.channelKey);
|
|
260
|
+
if (!channel.stream && !channel.deliver) {
|
|
261
|
+
await chunks.cancel().catch(() => {});
|
|
262
|
+
return unsupported("CHANNEL_DELIVERY_UNSUPPORTED", `Channel "${surface.channelKey}" does not support delivery`);
|
|
263
|
+
}
|
|
264
|
+
if (channel.stream) return channel.stream(surface, chunks, options);
|
|
265
|
+
return collectAndDeliver(channel, surface, chunks, options);
|
|
266
|
+
}
|
|
267
|
+
/** Request approval through the Channel or composite named by the surface. */
|
|
268
|
+
requestApproval(surface, options) {
|
|
269
|
+
return _assertClassBrand(_ChannelHost_brand, this, _outbound).call(this, surface, (channel, destination) => {
|
|
270
|
+
if (!channel.requestApproval) return Promise.resolve(unsupported("CHANNEL_APPROVAL_UNSUPPORTED", `Channel "${destination.channelKey}" does not support approval requests`));
|
|
271
|
+
return channel.requestApproval(destination, options);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/** Return the identity's configured Channel destination, when supported. */
|
|
275
|
+
contactSurface(identity) {
|
|
276
|
+
const surface = (Object.prototype.hasOwnProperty.call(_classPrivateFieldGet2(_channels, this), identity.channelKey) ? _classPrivateFieldGet2(_channels, this)[identity.channelKey] : void 0)?.contactSurface?.(identity);
|
|
277
|
+
return surface ? stampSurface(identity.channelKey, surface) : null;
|
|
278
|
+
}
|
|
279
|
+
/** Resolve whether a surface can currently be selected without delivery. */
|
|
280
|
+
async isAvailable(surface) {
|
|
281
|
+
if (!isChannelMessageSurface(surface)) return false;
|
|
282
|
+
return _assertClassBrand(_ChannelHost_brand, this, _configuredChannel).call(this, surface.channelKey).isAvailable?.(surface) ?? true;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
async function _outbound(surface, operation) {
|
|
286
|
+
if (!isChannelMessageSurface(surface)) return invalidSurface();
|
|
287
|
+
return operation(_assertClassBrand(_ChannelHost_brand, this, _configuredChannel).call(this, surface.channelKey), surface);
|
|
288
|
+
}
|
|
289
|
+
function _configuredChannel(channelKey) {
|
|
290
|
+
const channel = Object.prototype.hasOwnProperty.call(_classPrivateFieldGet2(_channels, this), channelKey) ? _classPrivateFieldGet2(_channels, this)[channelKey] : void 0;
|
|
291
|
+
if (!channel) throw new Error(`Channel message surface names unknown configured Channel key "${channelKey}"`);
|
|
292
|
+
return channel;
|
|
293
|
+
}
|
|
294
|
+
async function _dispatch(channelKey, channel, envelope) {
|
|
295
|
+
const rawEvent = envelope.event;
|
|
296
|
+
const event = stampEvent(channelKey, rawEvent);
|
|
297
|
+
const route = await _assertClassBrand(_ChannelHost_brand, this, _route).call(this, channelKey, channel, event, envelope.raw);
|
|
298
|
+
const dispatchId = await createDispatchId(channelKey, event.eventId);
|
|
299
|
+
await _classPrivateFieldGet2(_onRoute, this)?.call(this, {
|
|
300
|
+
channelKey,
|
|
301
|
+
event,
|
|
302
|
+
route,
|
|
303
|
+
dispatchId
|
|
304
|
+
});
|
|
305
|
+
if (route === null) return;
|
|
306
|
+
if (event.type === "message") {
|
|
307
|
+
if (!_classPrivateFieldGet2(_onMessage, this)) throw new Error(`Channel "${channelKey}" received a message without an onMessage callback`);
|
|
308
|
+
await _classPrivateFieldGet2(_onMessage, this).call(this, {
|
|
309
|
+
channelKey,
|
|
310
|
+
route,
|
|
311
|
+
dispatchId,
|
|
312
|
+
message: event
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!_classPrivateFieldGet2(_onApprovalResponse, this)) throw new Error(`Channel "${channelKey}" received an approval response without an onApprovalResponse callback`);
|
|
317
|
+
await _classPrivateFieldGet2(_onApprovalResponse, this).call(this, {
|
|
318
|
+
channelKey,
|
|
319
|
+
route,
|
|
320
|
+
dispatchId,
|
|
321
|
+
response: event
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async function _route(channelKey, channel, event, raw) {
|
|
325
|
+
const context = _assertClassBrand(_ChannelHost_brand, this, _routeContext).call(this, event);
|
|
326
|
+
const route = channel.route ? await channel.route(event, raw, context) : _classPrivateFieldGet2(_defaultRoute, this) ? await _classPrivateFieldGet2(_defaultRoute, this).call(this, event, raw, context) : event.thread.id;
|
|
327
|
+
if (route === void 0) throw new Error(`Channel route for "${channelKey}" returned undefined; return null to ignore an event`);
|
|
328
|
+
if (route !== null && typeof route !== "string") throw new Error(`Channel route for "${channelKey}" must return a string or null`);
|
|
329
|
+
return route;
|
|
330
|
+
}
|
|
331
|
+
function _routeContext(event) {
|
|
332
|
+
let linkedUser;
|
|
333
|
+
return { findUser: () => {
|
|
334
|
+
if (!linkedUser) {
|
|
335
|
+
const identity = event.actor?.identity;
|
|
336
|
+
const findUser = _classPrivateFieldGet2(_findUser, this);
|
|
337
|
+
linkedUser = identity && findUser ? Promise.resolve().then(() => findUser(identity)) : Promise.resolve(null);
|
|
338
|
+
}
|
|
339
|
+
return linkedUser;
|
|
340
|
+
} };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Serve a Channel that cannot stream by collecting the answer first.
|
|
344
|
+
*
|
|
345
|
+
* A generation that failed part-way still delivers what it produced, because
|
|
346
|
+
* losing the partial answer helps nobody, but the result is downgraded to
|
|
347
|
+
* `uncertain` since the reader received an incomplete answer.
|
|
348
|
+
*/
|
|
349
|
+
function invalidSurface() {
|
|
350
|
+
return unsupported("CHANNEL_SURFACE_INVALID", "Cannot resolve an invalid Channel message surface");
|
|
351
|
+
}
|
|
352
|
+
async function collectAndDeliver(channel, surface, stream, options) {
|
|
353
|
+
const collected = await collectText(stream);
|
|
354
|
+
if (collected.interrupted && collected.text.length === 0) return {
|
|
355
|
+
status: "failed",
|
|
356
|
+
retryable: false,
|
|
357
|
+
error: {
|
|
358
|
+
code: "CHANNEL_STREAM_INTERRUPTED",
|
|
359
|
+
message: "The stream ended before producing any content to deliver"
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const result = await channel.deliver(surface, {
|
|
363
|
+
...options.title !== void 0 && { title: options.title },
|
|
364
|
+
markdown: collected.text
|
|
365
|
+
}, options.delivery ? { delivery: options.delivery } : void 0);
|
|
366
|
+
if (!collected.interrupted || result.status !== "delivered") return result;
|
|
367
|
+
return {
|
|
368
|
+
status: "uncertain",
|
|
369
|
+
...result.reference !== void 0 && { reference: result.reference },
|
|
370
|
+
error: {
|
|
371
|
+
code: "CHANNEL_STREAM_INTERRUPTED",
|
|
372
|
+
message: "An incomplete answer was delivered because the stream ended early"
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function stampSurface(channelKey, surface) {
|
|
377
|
+
return {
|
|
378
|
+
...surface,
|
|
379
|
+
channelKey
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function stampIdentity(channelKey, identity) {
|
|
383
|
+
return {
|
|
384
|
+
...identity,
|
|
385
|
+
channelKey
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function stampEvent(channelKey, event) {
|
|
389
|
+
return {
|
|
390
|
+
...event,
|
|
391
|
+
...event.replySurface && { replySurface: stampSurface(channelKey, event.replySurface) },
|
|
392
|
+
...event.actor && { actor: {
|
|
393
|
+
...event.actor,
|
|
394
|
+
...event.actor.identity && { identity: stampIdentity(channelKey, event.actor.identity) }
|
|
395
|
+
} }
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/** Hash an unambiguous tuple so dispatch identities remain safe to carry. */
|
|
399
|
+
async function createDispatchId(channelKey, eventId) {
|
|
400
|
+
const identity = new TextEncoder().encode(JSON.stringify([channelKey, eventId]));
|
|
401
|
+
const digest = await crypto.subtle.digest("SHA-256", identity);
|
|
402
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/channels/identity.ts
|
|
406
|
+
const DEFAULT_IDENTITY_SCOPE = "default";
|
|
407
|
+
function normalizeIdentity(identity) {
|
|
408
|
+
return {
|
|
409
|
+
channelKey: identity.channelKey,
|
|
410
|
+
scope: identity.scope ?? DEFAULT_IDENTITY_SCOPE,
|
|
411
|
+
subject: identity.subject
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/** Build a stable key for comparing or indexing a Channel identity. */
|
|
415
|
+
function identityKey(identity) {
|
|
416
|
+
const normalized = normalizeIdentity(identity);
|
|
417
|
+
return JSON.stringify([
|
|
418
|
+
normalized.channelKey,
|
|
419
|
+
normalized.scope,
|
|
420
|
+
normalized.subject
|
|
421
|
+
]);
|
|
422
|
+
}
|
|
423
|
+
/** Raised when an operation would join identities already assigned to users. */
|
|
424
|
+
var UserIdentityConflictError = class extends Error {
|
|
425
|
+
constructor(conflicts, attemptedUserId) {
|
|
426
|
+
super(attemptedUserId ? `A channel identity is already linked to a different user than ${attemptedUserId}` : "Both channel identities are already linked to users");
|
|
427
|
+
this.conflicts = conflicts;
|
|
428
|
+
this.attemptedUserId = attemptedUserId;
|
|
429
|
+
this.code = "USER_IDENTITY_CONFLICT";
|
|
430
|
+
this.name = "UserIdentityConflictError";
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const USERS_TABLE = "cf_channels_users_v1";
|
|
434
|
+
const LINKS_TABLE = "cf_channels_user_identity_links_v1";
|
|
435
|
+
function requireNonEmpty(label, value) {
|
|
436
|
+
if (value.length === 0) throw new TypeError(`${label} must be a non-empty string`);
|
|
437
|
+
}
|
|
438
|
+
function validatedIdentity(identity) {
|
|
439
|
+
const normalized = normalizeIdentity(identity);
|
|
440
|
+
requireNonEmpty("identity.channelKey", normalized.channelKey);
|
|
441
|
+
requireNonEmpty("identity.scope", normalized.scope);
|
|
442
|
+
requireNonEmpty("identity.subject", normalized.subject);
|
|
443
|
+
return normalized;
|
|
444
|
+
}
|
|
445
|
+
function sameIdentity(first, second) {
|
|
446
|
+
return identityKey(first) === identityKey(second);
|
|
447
|
+
}
|
|
448
|
+
function compareIdentities(first, second) {
|
|
449
|
+
return first.channelKey.localeCompare(second.channelKey) || first.scope.localeCompare(second.scope) || first.subject.localeCompare(second.subject);
|
|
450
|
+
}
|
|
451
|
+
/** Build the off-the-shelf user identity store over application-owned SQLite. */
|
|
452
|
+
function createUserIdentityStore(storage, options = {}) {
|
|
453
|
+
const createUserId = options.createUserId ?? (() => crypto.randomUUID());
|
|
454
|
+
storage.sql.exec(`
|
|
455
|
+
CREATE TABLE IF NOT EXISTS ${USERS_TABLE} (
|
|
456
|
+
user_id TEXT PRIMARY KEY
|
|
457
|
+
) WITHOUT ROWID
|
|
458
|
+
`);
|
|
459
|
+
storage.sql.exec(`
|
|
460
|
+
CREATE TABLE IF NOT EXISTS ${LINKS_TABLE} (
|
|
461
|
+
channel_key TEXT NOT NULL,
|
|
462
|
+
scope TEXT NOT NULL,
|
|
463
|
+
subject TEXT NOT NULL,
|
|
464
|
+
user_id TEXT NOT NULL,
|
|
465
|
+
PRIMARY KEY (channel_key, scope, subject)
|
|
466
|
+
) WITHOUT ROWID
|
|
467
|
+
`);
|
|
468
|
+
storage.sql.exec(`
|
|
469
|
+
CREATE INDEX IF NOT EXISTS ${LINKS_TABLE}_user
|
|
470
|
+
ON ${LINKS_TABLE} (user_id, channel_key, scope, subject)
|
|
471
|
+
`);
|
|
472
|
+
function linkedUserId(identity) {
|
|
473
|
+
return storage.sql.exec(`SELECT user_id FROM ${LINKS_TABLE}
|
|
474
|
+
WHERE channel_key = ? AND scope = ? AND subject = ?`, identity.channelKey, identity.scope, identity.subject).toArray()[0]?.user_id ?? null;
|
|
475
|
+
}
|
|
476
|
+
function readUser(userId) {
|
|
477
|
+
const user = storage.sql.exec(`SELECT user_id FROM ${USERS_TABLE} WHERE user_id = ?`, userId).toArray()[0];
|
|
478
|
+
if (!user) return null;
|
|
479
|
+
const channelIdentities = storage.sql.exec(`SELECT channel_key AS channelKey, scope, subject FROM ${LINKS_TABLE}
|
|
480
|
+
WHERE user_id = ?
|
|
481
|
+
ORDER BY channel_key, scope, subject`, userId).toArray().sort(compareIdentities);
|
|
482
|
+
return {
|
|
483
|
+
id: user.user_id,
|
|
484
|
+
channelIdentities
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function insertUser(userId) {
|
|
488
|
+
return storage.sql.exec(`INSERT INTO ${USERS_TABLE} (user_id) VALUES (?)
|
|
489
|
+
ON CONFLICT (user_id) DO NOTHING
|
|
490
|
+
RETURNING user_id`, userId).toArray()[0] !== void 0;
|
|
491
|
+
}
|
|
492
|
+
function createUser() {
|
|
493
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
494
|
+
const userId = createUserId();
|
|
495
|
+
requireNonEmpty("createUserId() result", userId);
|
|
496
|
+
if (insertUser(userId)) return userId;
|
|
497
|
+
}
|
|
498
|
+
throw new Error("createUserId() repeatedly returned existing user IDs");
|
|
499
|
+
}
|
|
500
|
+
function insertLink(userId, identity) {
|
|
501
|
+
storage.sql.exec(`INSERT INTO ${LINKS_TABLE} (channel_key, scope, subject, user_id)
|
|
502
|
+
VALUES (?, ?, ?, ?)`, identity.channelKey, identity.scope, identity.subject, userId);
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
async link(userId, identity) {
|
|
506
|
+
requireNonEmpty("userId", userId);
|
|
507
|
+
const normalized = validatedIdentity(identity);
|
|
508
|
+
return storage.transactionSync(() => {
|
|
509
|
+
const existingUserId = linkedUserId(normalized);
|
|
510
|
+
if (existingUserId && existingUserId !== userId) throw new UserIdentityConflictError([{
|
|
511
|
+
channelIdentity: normalized,
|
|
512
|
+
userId: existingUserId
|
|
513
|
+
}], userId);
|
|
514
|
+
insertUser(userId);
|
|
515
|
+
if (!existingUserId) insertLink(userId, normalized);
|
|
516
|
+
const user = readUser(userId);
|
|
517
|
+
if (!user) throw new Error("Linked user identity could not be read");
|
|
518
|
+
return user;
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
async findUser(identity) {
|
|
522
|
+
const userId = linkedUserId(validatedIdentity(identity));
|
|
523
|
+
return userId ? readUser(userId) : null;
|
|
524
|
+
},
|
|
525
|
+
async getUser(userId) {
|
|
526
|
+
requireNonEmpty("userId", userId);
|
|
527
|
+
return readUser(userId);
|
|
528
|
+
},
|
|
529
|
+
async listUsers() {
|
|
530
|
+
return storage.sql.exec(`SELECT user_id FROM ${USERS_TABLE} ORDER BY user_id`).toArray().map(({ user_id }) => readUser(user_id)).filter((user) => user !== null);
|
|
531
|
+
},
|
|
532
|
+
async linkChannelIdentities(first, second) {
|
|
533
|
+
const normalizedFirst = validatedIdentity(first);
|
|
534
|
+
const normalizedSecond = validatedIdentity(second);
|
|
535
|
+
if (sameIdentity(normalizedFirst, normalizedSecond)) throw new TypeError("Channel identities must be distinct");
|
|
536
|
+
return storage.transactionSync(() => {
|
|
537
|
+
const firstUserId = linkedUserId(normalizedFirst);
|
|
538
|
+
const secondUserId = linkedUserId(normalizedSecond);
|
|
539
|
+
if (firstUserId && secondUserId) throw new UserIdentityConflictError([{
|
|
540
|
+
channelIdentity: normalizedFirst,
|
|
541
|
+
userId: firstUserId
|
|
542
|
+
}, {
|
|
543
|
+
channelIdentity: normalizedSecond,
|
|
544
|
+
userId: secondUserId
|
|
545
|
+
}]);
|
|
546
|
+
const userId = firstUserId ?? secondUserId ?? createUser();
|
|
547
|
+
if (!firstUserId) insertLink(userId, normalizedFirst);
|
|
548
|
+
if (!secondUserId) insertLink(userId, normalizedSecond);
|
|
549
|
+
const user = readUser(userId);
|
|
550
|
+
if (!user) throw new Error("Linked user identity could not be read");
|
|
551
|
+
return user;
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
/** Join two Channel identities through a `UserIdentityStore`. */
|
|
557
|
+
function linkChannelIdentities(store, first, second) {
|
|
558
|
+
return store.linkChannelIdentities(first, second);
|
|
559
|
+
}
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/channels/routes.ts
|
|
562
|
+
/**
|
|
563
|
+
* Common deterministic routing policies for normalized Channel events.
|
|
564
|
+
*
|
|
565
|
+
* Each one maps an event to a namespaced application route. Deciding whether
|
|
566
|
+
* an event is relevant at all is application policy: write that in your own
|
|
567
|
+
* `route` function and return `null` to ignore the event.
|
|
568
|
+
*/
|
|
569
|
+
const routes = {
|
|
570
|
+
/**
|
|
571
|
+
* Give every event its own application route. Useful to kick off a
|
|
572
|
+
* new conversation for each ingress, and where you don't want subsequent
|
|
573
|
+
* messages routed back to that same conversation.
|
|
574
|
+
**/
|
|
575
|
+
perEvent(event) {
|
|
576
|
+
return `event:${event.eventId}`;
|
|
577
|
+
},
|
|
578
|
+
/** Send every event in the same thread to the same conversation */
|
|
579
|
+
perThread(event) {
|
|
580
|
+
return `thread:${event.thread.id}`;
|
|
581
|
+
},
|
|
582
|
+
/**
|
|
583
|
+
* Prefer the sender's own channel identity, then delegate to another route
|
|
584
|
+
* policy. Without a fallback, an event carrying no identity is ignored.
|
|
585
|
+
*
|
|
586
|
+
* This groups events that carry the *same* identity. It never infers that
|
|
587
|
+
* two different identities belong to one person: that is an explicit
|
|
588
|
+
* application decision, exposed to routing through `byUser`.
|
|
589
|
+
*/
|
|
590
|
+
byIdentity(fallback) {
|
|
591
|
+
return (event, raw, context) => {
|
|
592
|
+
const identity = event.actor?.identity;
|
|
593
|
+
if (identity) return `identity:${identityKey(identity)}`;
|
|
594
|
+
return fallback ? fallback(event, raw, context) : null;
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
/** Prefer an explicitly linked user, then delegate to another route policy. */
|
|
598
|
+
byUser(fallback) {
|
|
599
|
+
return async (event, raw, context) => {
|
|
600
|
+
const user = await context.findUser();
|
|
601
|
+
return user ? `user:${user.id}` : fallback(event, raw, context);
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
//#endregion
|
|
606
|
+
export { ChannelHost, UserIdentityConflictError, consumeChunks, createUserIdentityStore, fallback, fallbackChannel, fanout, fanoutChannel, identityKey, isChannelMessageSurface, linkChannelIdentities, matchesPath, routes };
|
|
607
|
+
|
|
608
|
+
//# sourceMappingURL=index.js.map
|