@effect-uai/core 0.15.0 → 0.17.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.
Files changed (114) hide show
  1. package/README.md +30 -16
  2. package/dist/{AiError-eLV9t1lU.d.mts → AiError-DvhLIbmd.d.mts} +15 -4
  3. package/dist/{AiError-eLV9t1lU.d.mts.map → AiError-DvhLIbmd.d.mts.map} +1 -1
  4. package/dist/{Loop-B54AB1ME.d.mts → Loop-ChpaO-ya.d.mts} +2 -2
  5. package/dist/{Loop-B54AB1ME.d.mts.map → Loop-ChpaO-ya.d.mts.map} +1 -1
  6. package/dist/{Toolkit-DAjJ9Tvl.d.mts → Toolkit-tlL0F2YR.d.mts} +2 -2
  7. package/dist/{Toolkit-DAjJ9Tvl.d.mts.map → Toolkit-tlL0F2YR.d.mts.map} +1 -1
  8. package/dist/chunker/Chunker.d.mts +1 -1
  9. package/dist/decision-model/Decision.d.mts +155 -0
  10. package/dist/decision-model/Decision.d.mts.map +1 -0
  11. package/dist/decision-model/Decision.mjs +118 -0
  12. package/dist/decision-model/Decision.mjs.map +1 -0
  13. package/dist/decision-model/Decision.test.mjs +164 -0
  14. package/dist/decision-model/Decision.test.mjs.map +1 -0
  15. package/dist/decision-model/DecisionModel.d.mts +45 -0
  16. package/dist/decision-model/DecisionModel.d.mts.map +1 -0
  17. package/dist/decision-model/DecisionModel.mjs +25 -0
  18. package/dist/decision-model/DecisionModel.mjs.map +1 -0
  19. package/dist/domain/AiError.d.mts +2 -2
  20. package/dist/domain/AiError.mjs +9 -2
  21. package/dist/domain/AiError.mjs.map +1 -1
  22. package/dist/domain/Realtime.d.mts +616 -0
  23. package/dist/domain/Realtime.d.mts.map +1 -0
  24. package/dist/domain/Realtime.mjs +21 -0
  25. package/dist/domain/Realtime.mjs.map +1 -0
  26. package/dist/domain/Transcript.d.mts +17 -3
  27. package/dist/domain/Transcript.d.mts.map +1 -1
  28. package/dist/domain/Transcript.mjs +30 -1
  29. package/dist/domain/Transcript.mjs.map +1 -1
  30. package/dist/domain/Transcript.test.d.mts +1 -0
  31. package/dist/domain/Transcript.test.mjs +110 -0
  32. package/dist/domain/Transcript.test.mjs.map +1 -0
  33. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  34. package/dist/image-generator/ImageGenerator.d.mts +1 -1
  35. package/dist/index.d.mts +11 -5
  36. package/dist/index.mjs +9 -3
  37. package/dist/job/Job.d.mts +1 -1
  38. package/dist/language-model/LanguageModel.d.mts +2 -2
  39. package/dist/loop/Loop.d.mts +1 -1
  40. package/dist/math/Probability.d.mts +21 -0
  41. package/dist/math/Probability.d.mts.map +1 -0
  42. package/dist/math/Probability.mjs +31 -0
  43. package/dist/math/Probability.mjs.map +1 -0
  44. package/dist/math/Probability.test.d.mts +1 -0
  45. package/dist/math/Probability.test.mjs +95 -0
  46. package/dist/math/Probability.test.mjs.map +1 -0
  47. package/dist/music-generator/MusicGenerator.d.mts +1 -1
  48. package/dist/realtime/RealtimeSession.d.mts +50 -0
  49. package/dist/realtime/RealtimeSession.d.mts.map +1 -0
  50. package/dist/realtime/RealtimeSession.mjs +45 -0
  51. package/dist/realtime/RealtimeSession.mjs.map +1 -0
  52. package/dist/realtime/RealtimeSession.test.d.mts +1 -0
  53. package/dist/realtime/RealtimeSession.test.mjs +95 -0
  54. package/dist/realtime/RealtimeSession.test.mjs.map +1 -0
  55. package/dist/reranker/Reranker.d.mts +1 -1
  56. package/dist/research/DeepResearch.d.mts +1 -1
  57. package/dist/retry/Retry.d.mts +1 -1
  58. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
  59. package/dist/streaming/Settle.d.mts +45 -0
  60. package/dist/streaming/Settle.d.mts.map +1 -0
  61. package/dist/streaming/Settle.mjs +95 -0
  62. package/dist/streaming/Settle.mjs.map +1 -0
  63. package/dist/streaming/Settle.test.d.mts +1 -0
  64. package/dist/streaming/Settle.test.mjs +252 -0
  65. package/dist/streaming/Settle.test.mjs.map +1 -0
  66. package/dist/streaming/WebSocketSession.d.mts +36 -0
  67. package/dist/streaming/WebSocketSession.d.mts.map +1 -0
  68. package/dist/streaming/WebSocketSession.mjs +89 -0
  69. package/dist/streaming/WebSocketSession.mjs.map +1 -0
  70. package/dist/streaming/WebSocketSession.test.d.mts +1 -0
  71. package/dist/streaming/WebSocketSession.test.mjs +51 -0
  72. package/dist/streaming/WebSocketSession.test.mjs.map +1 -0
  73. package/dist/testing/FakeWebSocket.d.mts +25 -0
  74. package/dist/testing/FakeWebSocket.d.mts.map +1 -0
  75. package/dist/testing/FakeWebSocket.mjs +79 -0
  76. package/dist/testing/FakeWebSocket.mjs.map +1 -0
  77. package/dist/testing/MockRealtimeSession.d.mts +30 -0
  78. package/dist/testing/MockRealtimeSession.d.mts.map +1 -0
  79. package/dist/testing/MockRealtimeSession.mjs +52 -0
  80. package/dist/testing/MockRealtimeSession.mjs.map +1 -0
  81. package/dist/tool/Toolkit.d.mts +1 -1
  82. package/dist/transcriber/Transcriber.d.mts +1 -1
  83. package/dist/web-read/WebRead.d.mts +1 -1
  84. package/dist/web-read/WebReadTool.d.mts +1 -1
  85. package/dist/web-search/WebSearch.d.mts +1 -1
  86. package/dist/web-search/WebSearchTool.d.mts +1 -1
  87. package/package.json +36 -4
  88. package/src/decision-model/Decision.test.ts +146 -0
  89. package/src/decision-model/Decision.ts +273 -0
  90. package/src/decision-model/DecisionModel.ts +53 -0
  91. package/src/domain/AiError.ts +11 -0
  92. package/src/domain/Realtime.ts +101 -0
  93. package/src/domain/Transcript.test.ts +98 -0
  94. package/src/domain/Transcript.ts +53 -1
  95. package/src/index.ts +7 -1
  96. package/src/math/Probability.test.ts +61 -0
  97. package/src/math/Probability.ts +44 -0
  98. package/src/realtime/RealtimeSession.test.ts +100 -0
  99. package/src/realtime/RealtimeSession.ts +77 -0
  100. package/src/streaming/Settle.test.ts +344 -0
  101. package/src/streaming/Settle.ts +196 -0
  102. package/src/streaming/WebSocketSession.test.ts +52 -0
  103. package/src/streaming/WebSocketSession.ts +132 -0
  104. package/src/testing/FakeWebSocket.ts +120 -0
  105. package/src/testing/MockRealtimeSession.ts +92 -0
  106. package/dist/inbox/Inbox.d.mts +0 -16
  107. package/dist/inbox/Inbox.d.mts.map +0 -1
  108. package/dist/inbox/Inbox.mjs +0 -28
  109. package/dist/inbox/Inbox.mjs.map +0 -1
  110. package/dist/inbox/Inbox.test.mjs +0 -42
  111. package/dist/inbox/Inbox.test.mjs.map +0 -1
  112. package/src/inbox/Inbox.test.ts +0 -52
  113. package/src/inbox/Inbox.ts +0 -33
  114. /package/dist/{inbox/Inbox.test.d.mts → decision-model/Decision.test.d.mts} +0 -0
@@ -0,0 +1,36 @@
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
+ import { Cause, Duration, Effect, Queue, Schema, Scope } from "effect";
3
+ import * as Socket from "effect/unstable/socket/Socket";
4
+ declare namespace WebSocketSession_d_exports {
5
+ export { OpenOptions, WebSocketSession, open, redactUrl, toAiError };
6
+ }
7
+ type WebSocketSession<A> = {
8
+ /** Fails `Unavailable` once the socket is closed. */
9
+ readonly send: (frame: string) => Effect.Effect<void, AiError>;
10
+ /** Ends when the socket closes cleanly, fails when it does not. */
11
+ readonly frames: Queue.Dequeue<A, AiError | Cause.Done>;
12
+ };
13
+ type OpenOptions<A, I> = {
14
+ readonly url: string;
15
+ readonly provider: string;
16
+ /** Frames that do not decode are dropped, and logged at debug level. */
17
+ readonly schema: Schema.Codec<A, I>;
18
+ readonly openTimeout?: Duration.Input;
19
+ readonly capacity?: number;
20
+ };
21
+ /**
22
+ * Some wires authenticate with a key in the query string, and some runtimes
23
+ * name the socket URL in their open error (Bun does). Everything taken off a
24
+ * socket error goes through this.
25
+ */
26
+ declare const redactUrl: (text: string) => string;
27
+ declare const toAiError: (provider: string) => (error: Socket.SocketError) => AiError;
28
+ /**
29
+ * Connect for the lifetime of the surrounding `Scope`. The reader runs in a
30
+ * forked fiber; a dirty close reaches the consumer as a failed queue rather
31
+ * than a clean end.
32
+ */
33
+ declare const open: <A, I>(options: OpenOptions<A, I>) => Effect.Effect<WebSocketSession<A>, AiError, Scope.Scope | Socket.WebSocketConstructor>;
34
+ //#endregion
35
+ export { OpenOptions, WebSocketSession, open, redactUrl, WebSocketSession_d_exports as t, toAiError };
36
+ //# sourceMappingURL=WebSocketSession.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketSession.d.mts","names":[],"sources":["../../src/streaming/WebSocketSession.ts"],"mappings":";;;;;;KAuBY,iBAAiB;;WAElB,OAAO,kBAAkB,OAAO,aAAa;;WAE7C,QAAQ,MAAM,QAAQ,GAAG,UAAkB,MAAM;;KAGhD,YAAY,GAAG;WAChB;WACA;;WAEA,QAAQ,OAAO,MAAM,GAAG;WACxB,cAAc,SAAS;WACvB;;;;;;;cAiBE,YAAS;cAeT,YAAS,sBACH,OACT,OAAO,gBAAc;;;;;;cAsBlB,OAAQ,GAAG,GAAC,SACd,YAAY,GAAG,OACvB,OAAO,OAAO,iBAAiB,IAAI,SAAiB,MAAM,QAAQ,OAAO"}
@@ -0,0 +1,89 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { AuthFailed, Timeout, Unavailable } from "../domain/AiError.mjs";
3
+ import { parseSafe } from "./JSONL.mjs";
4
+ import { Cause, Effect, Match, Option, Predicate, Queue, Schema } from "effect";
5
+ import * as Socket from "effect/unstable/socket/Socket";
6
+ //#region src/streaming/WebSocketSession.ts
7
+ /**
8
+ * One JSON-over-WebSocket session: connect, read decoded frames off a queue,
9
+ * write frames back.
10
+ *
11
+ * For duplex protocols where the caller drives the conversation frame by
12
+ * frame. The stream-shaped adapters (realtime STT and TTS) still carry their
13
+ * own copies of this.
14
+ */
15
+ var WebSocketSession_exports = /* @__PURE__ */ __exportAll({
16
+ open: () => open,
17
+ redactUrl: () => redactUrl,
18
+ toAiError: () => toAiError
19
+ });
20
+ /**
21
+ * The cause of a failed open is a DOM `ErrorEvent`, not an `Error`, and its
22
+ * `message` is a prototype getter, so a structural decode does not see it.
23
+ */
24
+ const describeCause = (cause) => Predicate.hasProperty(cause, "message") && Predicate.isString(cause.message) ? cause.message : String(cause);
25
+ /**
26
+ * Some wires authenticate with a key in the query string, and some runtimes
27
+ * name the socket URL in their open error (Bun does). Everything taken off a
28
+ * socket error goes through this.
29
+ */
30
+ const redactUrl = (text) => text.replace(/(\b(?:wss?|https?):\/\/[^\s"']*?)\?[^\s"']*/gi, "$1?<redacted>");
31
+ const detailOf = Match.type().pipe(Match.tag("SocketCloseError", (reason) => reason.message), Match.orElse((reason) => describeCause(reason.cause)));
32
+ /** Never the `SocketError` itself: callers print `raw` with `Cause.pretty`. */
33
+ const rawOf = (error) => redactUrl(`${error.reason._tag}: ${detailOf(error.reason)}`);
34
+ /** A rejected WS upgrade surfaces only as prose, so the status is read from it. */
35
+ const isAuthRejection = (text) => /\b40[13]\b/.test(text);
36
+ const toAiError = (provider) => (error) => {
37
+ const raw = rawOf(error);
38
+ return Match.value(error.reason).pipe(Match.tag("SocketOpenError", (reason) => Match.value(reason.kind).pipe(Match.when("Timeout", () => new Timeout({
39
+ provider,
40
+ raw
41
+ })), Match.orElse(() => isAuthRejection(describeCause(reason.cause)) ? new AuthFailed({
42
+ provider,
43
+ subtype: "auth",
44
+ raw
45
+ }) : new Unavailable({
46
+ provider,
47
+ raw
48
+ })))), Match.orElse(() => new Unavailable({
49
+ provider,
50
+ raw
51
+ })));
52
+ };
53
+ /**
54
+ * Connect for the lifetime of the surrounding `Scope`. The reader runs in a
55
+ * forked fiber; a dirty close reaches the consumer as a failed queue rather
56
+ * than a clean end.
57
+ */
58
+ const open = (options) => Effect.gen(function* () {
59
+ const asAiError = toAiError(options.provider);
60
+ const decode = Schema.decodeUnknownEffect(options.schema);
61
+ const socket = yield* Socket.makeWebSocket(options.url, {
62
+ closeCodeIsError: (code) => code !== 1e3 && code !== 1001 && code !== 1005,
63
+ ...options.openTimeout !== void 0 && { openTimeout: options.openTimeout }
64
+ });
65
+ const frames = yield* Queue.bounded(options.capacity ?? 64);
66
+ const write = yield* socket.writer;
67
+ const onFrame = (raw) => Effect.gen(function* () {
68
+ const json = yield* parseSafe(raw);
69
+ if (json === void 0) return;
70
+ const decoded = yield* decode(json).pipe(Effect.option);
71
+ if (Option.isNone(decoded)) {
72
+ yield* Effect.logDebug(`[${options.provider}] dropped frame`, { frame: json });
73
+ return;
74
+ }
75
+ yield* Queue.offer(frames, decoded.value);
76
+ });
77
+ yield* socket.runString(onFrame).pipe(Effect.mapError(asAiError), Effect.matchCauseEffect({
78
+ onFailure: (cause) => Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),
79
+ onSuccess: () => Queue.end(frames)
80
+ }), Effect.forkScoped);
81
+ return {
82
+ send: (frame) => write(frame).pipe(Effect.mapError(asAiError)),
83
+ frames
84
+ };
85
+ });
86
+ //#endregion
87
+ export { open, redactUrl, WebSocketSession_exports as t, toAiError };
88
+
89
+ //# sourceMappingURL=WebSocketSession.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketSession.mjs","names":["AiError.Timeout","AiError.AuthFailed","AiError.Unavailable","JSONL.parseSafe"],"sources":["../../src/streaming/WebSocketSession.ts"],"sourcesContent":["/**\n * One JSON-over-WebSocket session: connect, read decoded frames off a queue,\n * write frames back.\n *\n * For duplex protocols where the caller drives the conversation frame by\n * frame. The stream-shaped adapters (realtime STT and TTS) still carry their\n * own copies of this.\n */\nimport {\n Cause,\n type Duration,\n Effect,\n Match,\n Option,\n Predicate,\n Queue,\n Schema,\n type Scope,\n} from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as JSONL from \"./JSONL.js\"\n\nexport type WebSocketSession<A> = {\n /** Fails `Unavailable` once the socket is closed. */\n readonly send: (frame: string) => Effect.Effect<void, AiError.AiError>\n /** Ends when the socket closes cleanly, fails when it does not. */\n readonly frames: Queue.Dequeue<A, AiError.AiError | Cause.Done>\n}\n\nexport type OpenOptions<A, I> = {\n readonly url: string\n readonly provider: string\n /** Frames that do not decode are dropped, and logged at debug level. */\n readonly schema: Schema.Codec<A, I>\n readonly openTimeout?: Duration.Input\n readonly capacity?: number\n}\n\n/**\n * The cause of a failed open is a DOM `ErrorEvent`, not an `Error`, and its\n * `message` is a prototype getter, so a structural decode does not see it.\n */\nconst describeCause = (cause: unknown): string =>\n Predicate.hasProperty(cause, \"message\") && Predicate.isString(cause.message)\n ? cause.message\n : String(cause)\n\n/**\n * Some wires authenticate with a key in the query string, and some runtimes\n * name the socket URL in their open error (Bun does). Everything taken off a\n * socket error goes through this.\n */\nexport const redactUrl = (text: string): string =>\n text.replace(/(\\b(?:wss?|https?):\\/\\/[^\\s\"']*?)\\?[^\\s\"']*/gi, \"$1?<redacted>\")\n\nconst detailOf = Match.type<Socket.SocketError[\"reason\"]>().pipe(\n Match.tag(\"SocketCloseError\", (reason) => reason.message),\n Match.orElse((reason) => describeCause(reason.cause)),\n)\n\n/** Never the `SocketError` itself: callers print `raw` with `Cause.pretty`. */\nconst rawOf = (error: Socket.SocketError): string =>\n redactUrl(`${error.reason._tag}: ${detailOf(error.reason)}`)\n\n/** A rejected WS upgrade surfaces only as prose, so the status is read from it. */\nconst isAuthRejection = (text: string): boolean => /\\b40[13]\\b/.test(text)\n\nexport const toAiError =\n (provider: string) =>\n (error: Socket.SocketError): AiError.AiError => {\n const raw = rawOf(error)\n return Match.value(error.reason).pipe(\n Match.tag(\"SocketOpenError\", (reason) =>\n Match.value(reason.kind).pipe(\n Match.when(\"Timeout\", (): AiError.AiError => new AiError.Timeout({ provider, raw })),\n Match.orElse((): AiError.AiError =>\n isAuthRejection(describeCause(reason.cause))\n ? new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n : new AiError.Unavailable({ provider, raw }),\n ),\n ),\n ),\n Match.orElse((): AiError.AiError => new AiError.Unavailable({ provider, raw })),\n )\n }\n\n/**\n * Connect for the lifetime of the surrounding `Scope`. The reader runs in a\n * forked fiber; a dirty close reaches the consumer as a failed queue rather\n * than a clean end.\n */\nexport const open = <A, I>(\n options: OpenOptions<A, I>,\n): Effect.Effect<WebSocketSession<A>, AiError.AiError, Scope.Scope | Socket.WebSocketConstructor> =>\n Effect.gen(function* () {\n const asAiError = toAiError(options.provider)\n const decode = Schema.decodeUnknownEffect(options.schema)\n const socket = yield* Socket.makeWebSocket(options.url, {\n // Effect's Socket treats every close code as an error by default.\n closeCodeIsError: (code) => code !== 1000 && code !== 1001 && code !== 1005,\n ...(options.openTimeout !== undefined && { openTimeout: options.openTimeout }),\n })\n const frames = yield* Queue.bounded<A, AiError.AiError | Cause.Done>(options.capacity ?? 64)\n const write = yield* socket.writer\n\n const onFrame = (raw: string) =>\n Effect.gen(function* () {\n const json = yield* JSONL.parseSafe(raw)\n if (json === undefined) return\n const decoded = yield* decode(json).pipe(Effect.option)\n if (Option.isNone(decoded)) {\n yield* Effect.logDebug(`[${options.provider}] dropped frame`, { frame: json })\n return\n }\n yield* Queue.offer(frames, decoded.value)\n })\n\n yield* socket.runString(onFrame).pipe(\n Effect.mapError(asAiError),\n // `Queue.end` on a clean close so the consumer drains what is queued;\n // a failure must not arrive looking like the end of the conversation.\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),\n onSuccess: () => Queue.end(frames),\n }),\n Effect.forkScoped,\n )\n\n return { send: (frame: string) => write(frame).pipe(Effect.mapError(asAiError)), frames }\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAM,iBAAiB,UACrB,UAAU,YAAY,OAAO,SAAS,KAAK,UAAU,SAAS,MAAM,OAAO,IACvE,MAAM,UACN,OAAO,KAAK;;;;;;AAOlB,MAAa,aAAa,SACxB,KAAK,QAAQ,iDAAiD,eAAe;AAE/E,MAAM,WAAW,MAAM,KAAmC,CAAC,CAAC,KAC1D,MAAM,IAAI,qBAAqB,WAAW,OAAO,OAAO,GACxD,MAAM,QAAQ,WAAW,cAAc,OAAO,KAAK,CAAC,CACtD;;AAGA,MAAM,SAAS,UACb,UAAU,GAAG,MAAM,OAAO,KAAK,IAAI,SAAS,MAAM,MAAM,GAAG;;AAG7D,MAAM,mBAAmB,SAA0B,aAAa,KAAK,IAAI;AAEzE,MAAa,aACV,cACA,UAA+C;CAC9C,MAAM,MAAM,MAAM,KAAK;CACvB,OAAO,MAAM,MAAM,MAAM,MAAM,CAAC,CAAC,KAC/B,MAAM,IAAI,oBAAoB,WAC5B,MAAM,MAAM,OAAO,IAAI,CAAC,CAAC,KACvB,MAAM,KAAK,iBAAkC,IAAIA,QAAgB;EAAE;EAAU;CAAI,CAAC,CAAC,GACnF,MAAM,aACJ,gBAAgB,cAAc,OAAO,KAAK,CAAC,IACvC,IAAIC,WAAmB;EAAE;EAAU,SAAS;EAAQ;CAAI,CAAC,IACzD,IAAIC,YAAoB;EAAE;EAAU;CAAI,CAAC,CAC/C,CACF,CACF,GACA,MAAM,aAA8B,IAAIA,YAAoB;EAAE;EAAU;CAAI,CAAC,CAAC,CAChF;AACF;;;;;;AAOF,MAAa,QACX,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,YAAY,UAAU,QAAQ,QAAQ;CAC5C,MAAM,SAAS,OAAO,oBAAoB,QAAQ,MAAM;CACxD,MAAM,SAAS,OAAO,OAAO,cAAc,QAAQ,KAAK;EAEtD,mBAAmB,SAAS,SAAS,OAAQ,SAAS,QAAQ,SAAS;EACvE,GAAI,QAAQ,gBAAgB,KAAA,KAAa,EAAE,aAAa,QAAQ,YAAY;CAC9E,CAAC;CACD,MAAM,SAAS,OAAO,MAAM,QAAyC,QAAQ,YAAY,EAAE;CAC3F,MAAM,QAAQ,OAAO,OAAO;CAE5B,MAAM,WAAW,QACf,OAAO,IAAI,aAAa;EACtB,MAAM,OAAO,OAAOC,UAAgB,GAAG;EACvC,IAAI,SAAS,KAAA,GAAW;EACxB,MAAM,UAAU,OAAO,OAAO,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM;EACtD,IAAI,OAAO,OAAO,OAAO,GAAG;GAC1B,OAAO,OAAO,SAAS,IAAI,QAAQ,SAAS,kBAAkB,EAAE,OAAO,KAAK,CAAC;GAC7E;EACF;EACA,OAAO,MAAM,MAAM,QAAQ,QAAQ,KAAK;CAC1C,CAAC;CAEH,OAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAC/B,OAAO,SAAS,SAAS,GAGzB,OAAO,iBAAiB;EACtB,YAAY,UACV,MAAM,SAAS,KAAK,IAAI,MAAM,UAAU,QAAQ,KAAK,IAAI,MAAM,IAAI,MAAM;EAC3E,iBAAiB,MAAM,IAAI,MAAM;CACnC,CAAC,GACD,OAAO,UACT;CAEA,OAAO;EAAE,OAAO,UAAkB,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO,SAAS,SAAS,CAAC;EAAG;CAAO;AAC1F,CAAC"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,51 @@
1
+ import { describe } from "../domain/AiError.mjs";
2
+ import { toAiError } from "./WebSocketSession.mjs";
3
+ import { o as describe$1 } from "../utils-CiH_DjFW.mjs";
4
+ import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
5
+ import { t as it } from "../dist-nohcnkD6.mjs";
6
+ import * as Socket from "effect/unstable/socket/Socket";
7
+ //#region src/streaming/WebSocketSession.test.ts
8
+ const KEY = "AIzaSyEXAMPLEKEY";
9
+ const URL = `wss://generativelanguage.googleapis.com/ws/BidiGenerateContent?key=${KEY}`;
10
+ /**
11
+ * What Bun hands the `error` listener when the upgrade is rejected: the whole
12
+ * socket URL, in prose, on a prototype getter. Node and Deno say less, but the
13
+ * adapter cannot know which runtime it is on.
14
+ */
15
+ var BunErrorEvent = class {
16
+ get message() {
17
+ return `WebSocket connection to '${URL}' failed: Connection ended`;
18
+ }
19
+ };
20
+ /** Deno names the status but not the URL, also on a prototype getter. */
21
+ var DenoErrorEvent = class {
22
+ detail;
23
+ constructor(detail) {
24
+ this.detail = detail;
25
+ }
26
+ get message() {
27
+ return `NetworkError: failed to connect to WebSocket: ${this.detail}`;
28
+ }
29
+ };
30
+ const openFailure = (cause) => new Socket.SocketError({ reason: new Socket.SocketOpenError({
31
+ kind: "Unknown",
32
+ cause
33
+ }) });
34
+ describe$1("WebSocketSession errors", () => {
35
+ it("keeps the key out of the error a rejected upgrade produces", () => {
36
+ const error = toAiError("google")(openFailure(new BunErrorEvent()));
37
+ globalExpect(JSON.stringify(error)).not.toContain(KEY);
38
+ globalExpect(describe(error)).not.toContain(KEY);
39
+ globalExpect(JSON.stringify(error)).toContain("generativelanguage.googleapis.com");
40
+ });
41
+ it("reads the rejected status out of the prose, wherever the runtime put it", () => {
42
+ const forbidden = toAiError("google")(openFailure(new DenoErrorEvent("Invalid status code: 403")));
43
+ const ended = toAiError("google")(openFailure(new BunErrorEvent()));
44
+ globalExpect(forbidden._tag).toBe("AuthFailed");
45
+ globalExpect(ended._tag).toBe("Unavailable");
46
+ });
47
+ });
48
+ //#endregion
49
+ export {};
50
+
51
+ //# sourceMappingURL=WebSocketSession.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketSession.test.mjs","names":["describe","AiError.describe"],"sources":["../../src/streaming/WebSocketSession.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport { expect } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport { toAiError } from \"./WebSocketSession.js\"\n\nconst KEY = \"AIzaSyEXAMPLEKEY\"\nconst URL = `wss://generativelanguage.googleapis.com/ws/BidiGenerateContent?key=${KEY}`\n\n/**\n * What Bun hands the `error` listener when the upgrade is rejected: the whole\n * socket URL, in prose, on a prototype getter. Node and Deno say less, but the\n * adapter cannot know which runtime it is on.\n */\nclass BunErrorEvent {\n get message(): string {\n return `WebSocket connection to '${URL}' failed: Connection ended`\n }\n}\n\n/** Deno names the status but not the URL, also on a prototype getter. */\nclass DenoErrorEvent {\n constructor(private readonly detail: string) {}\n get message(): string {\n return `NetworkError: failed to connect to WebSocket: ${this.detail}`\n }\n}\n\nconst openFailure = (cause: unknown) =>\n new Socket.SocketError({ reason: new Socket.SocketOpenError({ kind: \"Unknown\", cause }) })\n\ndescribe(\"WebSocketSession errors\", () => {\n it(\"keeps the key out of the error a rejected upgrade produces\", () => {\n const error = toAiError(\"google\")(openFailure(new BunErrorEvent()))\n\n expect(JSON.stringify(error)).not.toContain(KEY)\n expect(AiError.describe(error)).not.toContain(KEY)\n // The host is still there: an error that says nothing is its own problem.\n expect(JSON.stringify(error)).toContain(\"generativelanguage.googleapis.com\")\n })\n\n it(\"reads the rejected status out of the prose, wherever the runtime put it\", () => {\n // Deno's wording. The status is the only hint a rejected upgrade gives.\n const forbidden = toAiError(\"google\")(\n openFailure(new DenoErrorEvent(\"Invalid status code: 403\")),\n )\n const ended = toAiError(\"google\")(openFailure(new BunErrorEvent()))\n\n expect(forbidden._tag).toBe(\"AuthFailed\")\n expect(ended._tag).toBe(\"Unavailable\")\n })\n})\n"],"mappings":";;;;;;;AAMA,MAAM,MAAM;AACZ,MAAM,MAAM,sEAAsE;;;;;;AAOlF,IAAM,gBAAN,MAAoB;CAClB,IAAI,UAAkB;EACpB,OAAO,4BAA4B,IAAI;CACzC;AACF;;AAGA,IAAM,iBAAN,MAAqB;CACU;CAA7B,YAAY,QAAiC;EAAhB,KAAA,SAAA;CAAiB;CAC9C,IAAI,UAAkB;EACpB,OAAO,iDAAiD,KAAK;CAC/D;AACF;AAEA,MAAM,eAAe,UACnB,IAAI,OAAO,YAAY,EAAE,QAAQ,IAAI,OAAO,gBAAgB;CAAE,MAAM;CAAW;AAAM,CAAC,EAAE,CAAC;AAE3FA,WAAS,iCAAiC;CACxC,GAAG,oEAAoE;EACrE,MAAM,QAAQ,UAAU,QAAQ,CAAC,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;EAElE,aAAO,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG;EAC/C,aAAOC,SAAiB,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG;EAEjD,aAAO,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,UAAU,mCAAmC;CAC7E,CAAC;CAED,GAAG,iFAAiF;EAElF,MAAM,YAAY,UAAU,QAAQ,CAAC,CACnC,YAAY,IAAI,eAAe,0BAA0B,CAAC,CAC5D;EACA,MAAM,QAAQ,UAAU,QAAQ,CAAC,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;EAElE,aAAO,UAAU,IAAI,CAAC,CAAC,KAAK,YAAY;EACxC,aAAO,MAAM,IAAI,CAAC,CAAC,KAAK,aAAa;CACvC,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { Effect, Layer, Scope } from "effect";
2
+ import * as Socket from "effect/unstable/socket/Socket";
3
+ //#region src/testing/FakeWebSocket.d.ts
4
+ type FakeWebSocketServer = {
5
+ /** Every frame the client has sent, JSON-parsed, oldest first. */
6
+ readonly sent: Effect.Effect<ReadonlyArray<unknown>>;
7
+ /** Deliver one frame to the client. */
8
+ readonly push: (frame: unknown) => Effect.Effect<void>;
9
+ /** Close the socket. 1000, 1001 and 1005 are the clean codes. */
10
+ readonly close: (code?: number) => Effect.Effect<void>;
11
+ /** Hand this to an adapter that takes a socket constructor directly. */
12
+ readonly connect: Socket.WebSocketConstructor["Service"];
13
+ /** Provide this in place of the real constructor. */
14
+ readonly layer: Layer.Layer<Socket.WebSocketConstructor>;
15
+ };
16
+ type FakeWebSocketOptions = {
17
+ /** Pushed once the client subscribes, for a protocol whose server speaks first. */
18
+ readonly greeting?: ReadonlyArray<unknown>;
19
+ /** How the server answers each client frame. */
20
+ readonly reply?: (frame: unknown) => Effect.Effect<ReadonlyArray<unknown>>;
21
+ };
22
+ declare const make: (options?: FakeWebSocketOptions) => Effect.Effect<FakeWebSocketServer, never, Scope.Scope>;
23
+ //#endregion
24
+ export { FakeWebSocketOptions, FakeWebSocketServer, make };
25
+ //# sourceMappingURL=FakeWebSocket.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FakeWebSocket.d.mts","names":[],"sources":["../../src/testing/FakeWebSocket.ts"],"mappings":";;;KAeY;;WAED,MAAM,OAAO,OAAO;;WAEpB,OAAO,mBAAmB,OAAO;;WAEjC,QAAQ,kBAAkB,OAAO;;WAEjC,SAAS,OAAO;;WAEhB,OAAO,MAAM,MAAM,OAAO;;KAGzB;;WAED,WAAW;;WAEX,SAAS,mBAAmB,OAAO,OAAO;;cAWxC,OAAI,UACL,yBACT,OAAO,OAAO,4BAA4B,MAAM"}
@@ -0,0 +1,79 @@
1
+ import { Effect, Layer, Queue, Ref, Stream } from "effect";
2
+ import * as Socket from "effect/unstable/socket/Socket";
3
+ //#region src/testing/FakeWebSocket.ts
4
+ /**
5
+ * An in-memory WebSocket for testing socket adapters without a server.
6
+ *
7
+ * Provide `layer` in place of the real `Socket.WebSocketConstructor` and the
8
+ * adapter runs end to end: its own connect, handshake, decode and teardown.
9
+ * The test plays the other end with `reply`, `push`, `close` and `sent`.
10
+ *
11
+ * Client frames land on a queue, so the scripted server is a forked fiber and
12
+ * `reply` is a real Effect. What stays imperative is the listener table the
13
+ * shim registers, since `Socket.makeWebSocket` drives it with plain callbacks.
14
+ * The shim is a plain object, so nothing here touches a platform global.
15
+ */
16
+ const OPEN = 1;
17
+ const CLOSED = 3;
18
+ /** Run after the current call unwinds, so the client is never re-entered. */
19
+ const soon = (run) => void Promise.resolve().then(run);
20
+ const make = (options) => Effect.gen(function* () {
21
+ const inbound = yield* Queue.unbounded();
22
+ const sent = yield* Ref.make([]);
23
+ const wiring = {
24
+ listeners: {},
25
+ readyState: OPEN,
26
+ greeted: false
27
+ };
28
+ const emit = (type, event) => (wiring.listeners[type] ?? []).forEach((listener) => listener(event));
29
+ const deliverSync = (frames) => frames.forEach((f) => emit("message", { data: JSON.stringify(f) }));
30
+ const deliver = (frames) => Effect.sync(() => deliverSync(frames));
31
+ const shutdown = (code) => {
32
+ if (wiring.readyState === CLOSED) return;
33
+ wiring.readyState = CLOSED;
34
+ emit("close", {
35
+ code,
36
+ reason: ""
37
+ });
38
+ };
39
+ yield* Stream.fromQueue(inbound).pipe(Stream.runForEach((frame) => Effect.gen(function* () {
40
+ yield* Ref.update(sent, (frames) => [...frames, frame]);
41
+ const answer = yield* options?.reply?.(frame) ?? Effect.succeed([]);
42
+ yield* deliver(answer);
43
+ })), Effect.forkScoped);
44
+ const socket = {
45
+ get readyState() {
46
+ return wiring.readyState;
47
+ },
48
+ addEventListener: (type, fn) => {
49
+ wiring.listeners = {
50
+ ...wiring.listeners,
51
+ [type]: [...wiring.listeners[type] ?? [], fn]
52
+ };
53
+ if (type === "message" && !wiring.greeted) {
54
+ wiring.greeted = true;
55
+ soon(() => deliverSync(options?.greeting ?? []));
56
+ }
57
+ },
58
+ removeEventListener: (type, fn) => {
59
+ wiring.listeners = {
60
+ ...wiring.listeners,
61
+ [type]: (wiring.listeners[type] ?? []).filter((l) => l !== fn)
62
+ };
63
+ },
64
+ send: (data) => void Queue.offerUnsafe(inbound, JSON.parse(data)),
65
+ close: (code = 1e3) => shutdown(code)
66
+ };
67
+ const connect = () => socket;
68
+ return {
69
+ sent: Ref.get(sent),
70
+ push: (frame) => deliver([frame]),
71
+ close: (code = 1e3) => Effect.sync(() => shutdown(code)),
72
+ connect,
73
+ layer: Layer.succeed(Socket.WebSocketConstructor, connect)
74
+ };
75
+ });
76
+ //#endregion
77
+ export { make };
78
+
79
+ //# sourceMappingURL=FakeWebSocket.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FakeWebSocket.mjs","names":[],"sources":["../../src/testing/FakeWebSocket.ts"],"sourcesContent":["/**\n * An in-memory WebSocket for testing socket adapters without a server.\n *\n * Provide `layer` in place of the real `Socket.WebSocketConstructor` and the\n * adapter runs end to end: its own connect, handshake, decode and teardown.\n * The test plays the other end with `reply`, `push`, `close` and `sent`.\n *\n * Client frames land on a queue, so the scripted server is a forked fiber and\n * `reply` is a real Effect. What stays imperative is the listener table the\n * shim registers, since `Socket.makeWebSocket` drives it with plain callbacks.\n * The shim is a plain object, so nothing here touches a platform global.\n */\nimport { Effect, Layer, Queue, Ref, type Scope, Stream } from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\n\nexport type FakeWebSocketServer = {\n /** Every frame the client has sent, JSON-parsed, oldest first. */\n readonly sent: Effect.Effect<ReadonlyArray<unknown>>\n /** Deliver one frame to the client. */\n readonly push: (frame: unknown) => Effect.Effect<void>\n /** Close the socket. 1000, 1001 and 1005 are the clean codes. */\n readonly close: (code?: number) => Effect.Effect<void>\n /** Hand this to an adapter that takes a socket constructor directly. */\n readonly connect: Socket.WebSocketConstructor[\"Service\"]\n /** Provide this in place of the real constructor. */\n readonly layer: Layer.Layer<Socket.WebSocketConstructor>\n}\n\nexport type FakeWebSocketOptions = {\n /** Pushed once the client subscribes, for a protocol whose server speaks first. */\n readonly greeting?: ReadonlyArray<unknown>\n /** How the server answers each client frame. */\n readonly reply?: (frame: unknown) => Effect.Effect<ReadonlyArray<unknown>>\n}\n\ntype Listener = (event: any) => void\n\nconst OPEN = 1\nconst CLOSED = 3\n\n/** Run after the current call unwinds, so the client is never re-entered. */\nconst soon = (run: () => void): void => void Promise.resolve().then(run)\n\nexport const make = (\n options?: FakeWebSocketOptions,\n): Effect.Effect<FakeWebSocketServer, never, Scope.Scope> =>\n Effect.gen(function* () {\n const inbound = yield* Queue.unbounded<unknown>()\n const sent = yield* Ref.make<ReadonlyArray<unknown>>([])\n\n const wiring = {\n listeners: {} as Record<string, ReadonlyArray<Listener>>,\n readyState: OPEN,\n greeted: false,\n }\n\n const emit = (type: string, event: unknown): void =>\n (wiring.listeners[type] ?? []).forEach((listener) => listener(event))\n\n const deliverSync = (frames: ReadonlyArray<unknown>): void =>\n frames.forEach((f) => emit(\"message\", { data: JSON.stringify(f) }))\n\n const deliver = (frames: ReadonlyArray<unknown>): Effect.Effect<void> =>\n Effect.sync(() => deliverSync(frames))\n\n const shutdown = (code: number): void => {\n if (wiring.readyState === CLOSED) return\n wiring.readyState = CLOSED\n emit(\"close\", { code, reason: \"\" })\n }\n\n // The scripted server. Answers land on their own turn, like a real one.\n yield* Stream.fromQueue(inbound).pipe(\n Stream.runForEach((frame) =>\n Effect.gen(function* () {\n yield* Ref.update(sent, (frames) => [...frames, frame])\n const answer = yield* options?.reply?.(frame) ?? Effect.succeed([])\n yield* deliver(answer)\n }),\n ),\n Effect.forkScoped,\n )\n\n const socket = {\n get readyState() {\n return wiring.readyState\n },\n addEventListener: (type: string, fn: Listener): void => {\n wiring.listeners = {\n ...wiring.listeners,\n [type]: [...(wiring.listeners[type] ?? []), fn],\n }\n // The greeting waits for a subscriber before it lands.\n if (type === \"message\" && !wiring.greeted) {\n wiring.greeted = true\n soon(() => deliverSync(options?.greeting ?? []))\n }\n },\n removeEventListener: (type: string, fn: Listener): void => {\n wiring.listeners = {\n ...wiring.listeners,\n [type]: (wiring.listeners[type] ?? []).filter((l) => l !== fn),\n }\n },\n // Safe from a synchronous callback, and no runtime needed.\n send: (data: string): void => void Queue.offerUnsafe(inbound, JSON.parse(data)),\n close: (code = 1000): void => shutdown(code),\n }\n\n const connect: Socket.WebSocketConstructor[\"Service\"] = () =>\n socket as unknown as globalThis.WebSocket\n\n return {\n sent: Ref.get(sent),\n push: (frame) => deliver([frame]),\n close: (code = 1000) => Effect.sync(() => shutdown(code)),\n connect,\n layer: Layer.succeed(Socket.WebSocketConstructor, connect),\n }\n })\n"],"mappings":";;;;;;;;;;;;;;;AAqCA,MAAM,OAAO;AACb,MAAM,SAAS;;AAGf,MAAM,QAAQ,QAA0B,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAK,GAAG;AAEvE,MAAa,QACX,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,MAAM,UAAmB;CAChD,MAAM,OAAO,OAAO,IAAI,KAA6B,CAAC,CAAC;CAEvD,MAAM,SAAS;EACb,WAAW,CAAC;EACZ,YAAY;EACZ,SAAS;CACX;CAEA,MAAM,QAAQ,MAAc,WACzB,OAAO,UAAU,SAAS,CAAC,EAAA,CAAG,SAAS,aAAa,SAAS,KAAK,CAAC;CAEtE,MAAM,eAAe,WACnB,OAAO,SAAS,MAAM,KAAK,WAAW,EAAE,MAAM,KAAK,UAAU,CAAC,EAAE,CAAC,CAAC;CAEpE,MAAM,WAAW,WACf,OAAO,WAAW,YAAY,MAAM,CAAC;CAEvC,MAAM,YAAY,SAAuB;EACvC,IAAI,OAAO,eAAe,QAAQ;EAClC,OAAO,aAAa;EACpB,KAAK,SAAS;GAAE;GAAM,QAAQ;EAAG,CAAC;CACpC;CAGA,OAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAC/B,OAAO,YAAY,UACjB,OAAO,IAAI,aAAa;EACtB,OAAO,IAAI,OAAO,OAAO,WAAW,CAAC,GAAG,QAAQ,KAAK,CAAC;EACtD,MAAM,SAAS,OAAO,SAAS,QAAQ,KAAK,KAAK,OAAO,QAAQ,CAAC,CAAC;EAClE,OAAO,QAAQ,MAAM;CACvB,CAAC,CACH,GACA,OAAO,UACT;CAEA,MAAM,SAAS;EACb,IAAI,aAAa;GACf,OAAO,OAAO;EAChB;EACA,mBAAmB,MAAc,OAAuB;GACtD,OAAO,YAAY;IACjB,GAAG,OAAO;KACT,OAAO,CAAC,GAAI,OAAO,UAAU,SAAS,CAAC,GAAI,EAAE;GAChD;GAEA,IAAI,SAAS,aAAa,CAAC,OAAO,SAAS;IACzC,OAAO,UAAU;IACjB,WAAW,YAAY,SAAS,YAAY,CAAC,CAAC,CAAC;GACjD;EACF;EACA,sBAAsB,MAAc,OAAuB;GACzD,OAAO,YAAY;IACjB,GAAG,OAAO;KACT,QAAQ,OAAO,UAAU,SAAS,CAAC,EAAA,CAAG,QAAQ,MAAM,MAAM,EAAE;GAC/D;EACF;EAEA,OAAO,SAAuB,KAAK,MAAM,YAAY,SAAS,KAAK,MAAM,IAAI,CAAC;EAC9E,QAAQ,OAAO,QAAe,SAAS,IAAI;CAC7C;CAEA,MAAM,gBACJ;CAEF,OAAO;EACL,MAAM,IAAI,IAAI,IAAI;EAClB,OAAO,UAAU,QAAQ,CAAC,KAAK,CAAC;EAChC,QAAQ,OAAO,QAAS,OAAO,WAAW,SAAS,IAAI,CAAC;EACxD;EACA,OAAO,MAAM,QAAQ,OAAO,sBAAsB,OAAO;CAC3D;AACF,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { CommonSessionRequest, RealtimeEvent, RealtimeInput } from "../domain/Realtime.mjs";
2
+ import { RealtimeSession, RealtimeVideoInput } from "../realtime/RealtimeSession.mjs";
3
+ import { Effect, Layer } from "effect";
4
+ //#region src/testing/MockRealtimeSession.d.ts
5
+ type MockRealtimeSessionRecorder = {
6
+ readonly openCalls: ReadonlyArray<CommonSessionRequest>;
7
+ readonly sent: ReadonlyArray<RealtimeInput>;
8
+ };
9
+ type MockRealtimeSessionScript = {
10
+ /** Emitted as soon as the session opens. */
11
+ readonly initial?: ReadonlyArray<RealtimeEvent>;
12
+ /** The server's answer to each input. */
13
+ readonly onInput?: (input: RealtimeInput) => ReadonlyArray<RealtimeEvent>;
14
+ };
15
+ /** Scripted session that also ships the `RealtimeVideoInput` marker. */
16
+ declare const layer: (script: MockRealtimeSessionScript) => {
17
+ readonly layer: Layer.Layer<RealtimeSession | RealtimeVideoInput>;
18
+ readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>;
19
+ };
20
+ /**
21
+ * The same without the marker, to test that a caller sending video against an
22
+ * audio-only provider fails to compile.
23
+ */
24
+ declare const layerAudioOnly: (script: MockRealtimeSessionScript) => {
25
+ readonly layer: Layer.Layer<RealtimeSession>;
26
+ readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>;
27
+ };
28
+ //#endregion
29
+ export { MockRealtimeSessionRecorder, MockRealtimeSessionScript, layer, layerAudioOnly };
30
+ //# sourceMappingURL=MockRealtimeSession.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockRealtimeSession.d.mts","names":[],"sources":["../../src/testing/MockRealtimeSession.ts"],"mappings":";;;;KASY;WACD,WAAW,cAAc;WACzB,MAAM,cAAc;;KAGnB;;WAED,UAAU,cAAc;;WAExB,WAAW,OAAO,kBAAkB,cAAc;;;cAgDhD,QAAK,QACR;WAEC,OAAO,MAAM,MAAM,kBAAkB;WACrC,UAAU,OAAO,OAAO;;;;;;cAatB,iBAAc,QACjB;WAEC,OAAO,MAAM,MAAM;WACnB,UAAU,OAAO,OAAO"}
@@ -0,0 +1,52 @@
1
+ import { RealtimeSession, RealtimeVideoInput } from "../realtime/RealtimeSession.mjs";
2
+ import { Effect, Layer, Queue, Ref, Stream } from "effect";
3
+ //#region src/testing/MockRealtimeSession.ts
4
+ const makeService = (script, record) => ({ open: (request) => Effect.gen(function* () {
5
+ yield* record.open(request);
6
+ const queue = yield* Queue.unbounded();
7
+ yield* Effect.addFinalizer(() => Queue.end(queue));
8
+ yield* Queue.offerAll(queue, script.initial ?? []);
9
+ return {
10
+ send: (input) => Effect.andThen(record.send(input), Queue.offerAll(queue, script.onInput?.(input) ?? [])).pipe(Effect.asVoid),
11
+ events: Stream.fromQueue(queue)
12
+ };
13
+ }) });
14
+ const build = (script) => {
15
+ const openCalls = Ref.makeUnsafe([]);
16
+ const sent = Ref.makeUnsafe([]);
17
+ return {
18
+ sessionLayer: Layer.succeed(RealtimeSession, makeService(script, {
19
+ open: (request) => Ref.update(openCalls, (xs) => [...xs, request]),
20
+ send: (input) => Ref.update(sent, (xs) => [...xs, input])
21
+ })),
22
+ recorder: Effect.gen(function* () {
23
+ return {
24
+ openCalls: yield* Ref.get(openCalls),
25
+ sent: yield* Ref.get(sent)
26
+ };
27
+ })
28
+ };
29
+ };
30
+ /** Scripted session that also ships the `RealtimeVideoInput` marker. */
31
+ const layer = (script) => {
32
+ const { sessionLayer, recorder } = build(script);
33
+ return {
34
+ layer: Layer.merge(sessionLayer, Layer.succeed(RealtimeVideoInput, void 0)),
35
+ recorder
36
+ };
37
+ };
38
+ /**
39
+ * The same without the marker, to test that a caller sending video against an
40
+ * audio-only provider fails to compile.
41
+ */
42
+ const layerAudioOnly = (script) => {
43
+ const { sessionLayer, recorder } = build(script);
44
+ return {
45
+ layer: sessionLayer,
46
+ recorder
47
+ };
48
+ };
49
+ //#endregion
50
+ export { layer, layerAudioOnly };
51
+
52
+ //# sourceMappingURL=MockRealtimeSession.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockRealtimeSession.mjs","names":[],"sources":["../../src/testing/MockRealtimeSession.ts"],"sourcesContent":["import { Cause, Effect, Layer, Queue, Ref, Stream } from \"effect\"\nimport type { CommonSessionRequest, RealtimeEvent, RealtimeInput } from \"../domain/Realtime.js\"\nimport {\n RealtimeSession,\n RealtimeVideoInput,\n type RealtimeSessionHandle,\n type RealtimeSessionService,\n} from \"../realtime/RealtimeSession.js\"\n\nexport type MockRealtimeSessionRecorder = {\n readonly openCalls: ReadonlyArray<CommonSessionRequest>\n readonly sent: ReadonlyArray<RealtimeInput>\n}\n\nexport type MockRealtimeSessionScript = {\n /** Emitted as soon as the session opens. */\n readonly initial?: ReadonlyArray<RealtimeEvent>\n /** The server's answer to each input. */\n readonly onInput?: (input: RealtimeInput) => ReadonlyArray<RealtimeEvent>\n}\n\ntype Recorders = {\n readonly open: (request: CommonSessionRequest) => Effect.Effect<void>\n readonly send: (input: RealtimeInput) => Effect.Effect<void>\n}\n\nconst makeService = (\n script: MockRealtimeSessionScript,\n record: Recorders,\n): RealtimeSessionService => ({\n open: (request) =>\n Effect.gen(function* () {\n yield* record.open(request)\n const queue = yield* Queue.unbounded<RealtimeEvent, Cause.Done>()\n // Closing the scope ends `events`, leaving anything already queued to drain.\n yield* Effect.addFinalizer(() => Queue.end(queue))\n yield* Queue.offerAll(queue, script.initial ?? [])\n const handle: RealtimeSessionHandle = {\n send: (input) =>\n Effect.andThen(\n record.send(input),\n Queue.offerAll(queue, script.onInput?.(input) ?? []),\n ).pipe(Effect.asVoid),\n events: Stream.fromQueue(queue),\n }\n return handle\n }),\n})\n\nconst build = (script: MockRealtimeSessionScript) => {\n const openCalls = Ref.makeUnsafe<ReadonlyArray<CommonSessionRequest>>([])\n const sent = Ref.makeUnsafe<ReadonlyArray<RealtimeInput>>([])\n const sessionLayer = Layer.succeed(\n RealtimeSession,\n makeService(script, {\n open: (request) => Ref.update(openCalls, (xs) => [...xs, request]),\n send: (input) => Ref.update(sent, (xs) => [...xs, input]),\n }),\n )\n const recorder: Effect.Effect<MockRealtimeSessionRecorder> = Effect.gen(function* () {\n return { openCalls: yield* Ref.get(openCalls), sent: yield* Ref.get(sent) }\n })\n return { sessionLayer, recorder }\n}\n\n/** Scripted session that also ships the `RealtimeVideoInput` marker. */\nexport const layer = (\n script: MockRealtimeSessionScript,\n): {\n readonly layer: Layer.Layer<RealtimeSession | RealtimeVideoInput>\n readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>\n} => {\n const { sessionLayer, recorder } = build(script)\n return {\n layer: Layer.merge(sessionLayer, Layer.succeed(RealtimeVideoInput, undefined)),\n recorder,\n }\n}\n\n/**\n * The same without the marker, to test that a caller sending video against an\n * audio-only provider fails to compile.\n */\nexport const layerAudioOnly = (\n script: MockRealtimeSessionScript,\n): {\n readonly layer: Layer.Layer<RealtimeSession>\n readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>\n} => {\n const { sessionLayer, recorder } = build(script)\n return { layer: sessionLayer, recorder }\n}\n"],"mappings":";;;AA0BA,MAAM,eACJ,QACA,YAC4B,EAC5B,OAAO,YACL,OAAO,IAAI,aAAa;CACtB,OAAO,OAAO,KAAK,OAAO;CAC1B,MAAM,QAAQ,OAAO,MAAM,UAAqC;CAEhE,OAAO,OAAO,mBAAmB,MAAM,IAAI,KAAK,CAAC;CACjD,OAAO,MAAM,SAAS,OAAO,OAAO,WAAW,CAAC,CAAC;CASjD,OAAO;EAPL,OAAO,UACL,OAAO,QACL,OAAO,KAAK,KAAK,GACjB,MAAM,SAAS,OAAO,OAAO,UAAU,KAAK,KAAK,CAAC,CAAC,CACrD,CAAC,CAAC,KAAK,OAAO,MAAM;EACtB,QAAQ,OAAO,UAAU,KAAK;CAEpB;AACd,CAAC,EACL;AAEA,MAAM,SAAS,WAAsC;CACnD,MAAM,YAAY,IAAI,WAAgD,CAAC,CAAC;CACxE,MAAM,OAAO,IAAI,WAAyC,CAAC,CAAC;CAW5D,OAAO;EAAE,cAVY,MAAM,QACzB,iBACA,YAAY,QAAQ;GAClB,OAAO,YAAY,IAAI,OAAO,YAAY,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC;GACjE,OAAO,UAAU,IAAI,OAAO,OAAO,OAAO,CAAC,GAAG,IAAI,KAAK,CAAC;EAC1D,CAAC,CAKiB;EAAG,UAHsC,OAAO,IAAI,aAAa;GACnF,OAAO;IAAE,WAAW,OAAO,IAAI,IAAI,SAAS;IAAG,MAAM,OAAO,IAAI,IAAI,IAAI;GAAE;EAC5E,CAC8B;CAAE;AAClC;;AAGA,MAAa,SACX,WAIG;CACH,MAAM,EAAE,cAAc,aAAa,MAAM,MAAM;CAC/C,OAAO;EACL,OAAO,MAAM,MAAM,cAAc,MAAM,QAAQ,oBAAoB,KAAA,CAAS,CAAC;EAC7E;CACF;AACF;;;;;AAMA,MAAa,kBACX,WAIG;CACH,MAAM,EAAE,cAAc,aAAa,MAAM,MAAM;CAC/C,OAAO;EAAE,OAAO;EAAc;CAAS;AACzC"}
@@ -1,2 +1,2 @@
1
- import { S as wrap, _ as fromArray, a as ToolMap, b as namespace, c as ToolkitR, d as appendToolResults, f as collectResults, g as descriptors, h as describeFailures, i as NoDuplicateNames, m as continueWithResults, n as ExecuteOptions, o as Toolkit, p as compose, r as Middleware, s as ToolkitE, t as DuplicateToolName, u as UniqueTools, v as make, x as run, y as makeNamespaced } from "../Toolkit-DAjJ9Tvl.mjs";
1
+ import { S as wrap, _ as fromArray, a as ToolMap, b as namespace, c as ToolkitR, d as appendToolResults, f as collectResults, g as descriptors, h as describeFailures, i as NoDuplicateNames, m as continueWithResults, n as ExecuteOptions, o as Toolkit, p as compose, r as Middleware, s as ToolkitE, t as DuplicateToolName, u as UniqueTools, v as make, x as run, y as makeNamespaced } from "../Toolkit-tlL0F2YR.mjs";
2
2
  export { DuplicateToolName, ExecuteOptions, Middleware, NoDuplicateNames, ToolMap, Toolkit, ToolkitE, ToolkitR, UniqueTools, appendToolResults, collectResults, compose, continueWithResults, describeFailures, descriptors, fromArray, make, makeNamespaced, namespace, run, wrap };
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { a as AudioSource, r as AudioFormat } from "../Audio-_nGR4V0X.mjs";
3
3
  import { TranscriptEvent, TranscriptResult } from "../domain/Transcript.mjs";
4
4
  import { Context, Effect, Stream } from "effect";
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { Context, Duration, Effect } from "effect";
3
3
  declare namespace WebRead_d_exports {
4
4
  export { CommonReadRequest, ReadFormat, ReadResponse, WebRead, WebReadService, read };
@@ -1,5 +1,5 @@
1
1
  import { l as Tool } from "../Tool-BF78QWtn.mjs";
2
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
2
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
3
3
  import { ReadFormat, ReadResponse, WebRead } from "./WebRead.mjs";
4
4
  import { Schema } from "effect";
5
5
  declare namespace WebReadTool_d_exports {
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { Context, DateTime, Effect, Schema } from "effect";
3
3
  declare namespace WebSearch_d_exports {
4
4
  export { CommonSearchRequest, SearchRecency, SearchResponse, SearchResult, WebSearch, WebSearchService, search };
@@ -1,5 +1,5 @@
1
1
  import { l as Tool } from "../Tool-BF78QWtn.mjs";
2
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
2
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
3
3
  import { SearchResult, WebSearch } from "./WebSearch.mjs";
4
4
  import { Schema } from "effect";
5
5
  declare namespace WebSearchTool_d_exports {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/core",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Low-level primitives (loop, conversation, items, tools, streaming codecs) for building AI agents with Effect.",
5
5
  "keywords": [
6
6
  "agents",
@@ -90,6 +90,14 @@
90
90
  "types": "./dist/domain/Music.d.mts",
91
91
  "import": "./dist/domain/Music.mjs"
92
92
  },
93
+ "./Realtime": {
94
+ "types": "./dist/domain/Realtime.d.mts",
95
+ "import": "./dist/domain/Realtime.mjs"
96
+ },
97
+ "./RealtimeSession": {
98
+ "types": "./dist/realtime/RealtimeSession.d.mts",
99
+ "import": "./dist/realtime/RealtimeSession.mjs"
100
+ },
93
101
  "./Transcript": {
94
102
  "types": "./dist/domain/Transcript.d.mts",
95
103
  "import": "./dist/domain/Transcript.mjs"
@@ -98,6 +106,14 @@
98
106
  "types": "./dist/domain/Turn.d.mts",
99
107
  "import": "./dist/domain/Turn.mjs"
100
108
  },
109
+ "./Decision": {
110
+ "types": "./dist/decision-model/Decision.d.mts",
111
+ "import": "./dist/decision-model/Decision.mjs"
112
+ },
113
+ "./DecisionModel": {
114
+ "types": "./dist/decision-model/DecisionModel.d.mts",
115
+ "import": "./dist/decision-model/DecisionModel.mjs"
116
+ },
101
117
  "./Embedding": {
102
118
  "types": "./dist/embedding-model/Embedding.d.mts",
103
119
  "import": "./dist/embedding-model/Embedding.mjs"
@@ -178,9 +194,13 @@
178
194
  "types": "./dist/web-search/WebSearchTool.d.mts",
179
195
  "import": "./dist/web-search/WebSearchTool.mjs"
180
196
  },
181
- "./Inbox": {
182
- "types": "./dist/inbox/Inbox.d.mts",
183
- "import": "./dist/inbox/Inbox.mjs"
197
+ "./WebSocketSession": {
198
+ "types": "./dist/streaming/WebSocketSession.d.mts",
199
+ "import": "./dist/streaming/WebSocketSession.mjs"
200
+ },
201
+ "./Settle": {
202
+ "types": "./dist/streaming/Settle.d.mts",
203
+ "import": "./dist/streaming/Settle.mjs"
184
204
  },
185
205
  "./Loop": {
186
206
  "types": "./dist/loop/Loop.d.mts",
@@ -190,6 +210,10 @@
190
210
  "types": "./dist/retry/Retry.d.mts",
191
211
  "import": "./dist/retry/Retry.mjs"
192
212
  },
213
+ "./Probability": {
214
+ "types": "./dist/math/Probability.d.mts",
215
+ "import": "./dist/math/Probability.mjs"
216
+ },
193
217
  "./Vector": {
194
218
  "types": "./dist/math/Vector.d.mts",
195
219
  "import": "./dist/math/Vector.mjs"
@@ -270,6 +294,14 @@
270
294
  "types": "./dist/testing/MockSandbox.d.mts",
271
295
  "import": "./dist/testing/MockSandbox.mjs"
272
296
  },
297
+ "./testing/FakeWebSocket": {
298
+ "types": "./dist/testing/FakeWebSocket.d.mts",
299
+ "import": "./dist/testing/FakeWebSocket.mjs"
300
+ },
301
+ "./testing/MockRealtimeSession": {
302
+ "types": "./dist/testing/MockRealtimeSession.d.mts",
303
+ "import": "./dist/testing/MockRealtimeSession.mjs"
304
+ },
273
305
  "./testing/MockTranscriber": {
274
306
  "types": "./dist/testing/MockTranscriber.d.mts",
275
307
  "import": "./dist/testing/MockTranscriber.mjs"