@effect-uai/core 0.13.0 → 0.15.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 (116) hide show
  1. package/dist/{Audio-60lKKHxS.d.mts → Audio-_nGR4V0X.d.mts} +2 -2
  2. package/dist/{Audio-60lKKHxS.d.mts.map → Audio-_nGR4V0X.d.mts.map} +1 -1
  3. package/dist/Image-Cv6iBLTy.d.mts +99 -0
  4. package/dist/Image-Cv6iBLTy.d.mts.map +1 -0
  5. package/dist/{Items-SfUabNuB.d.mts → Items-C7MyrYPX.d.mts} +35 -4
  6. package/dist/Items-C7MyrYPX.d.mts.map +1 -0
  7. package/dist/Media-BbX7BAaO.d.mts +60 -0
  8. package/dist/Media-BbX7BAaO.d.mts.map +1 -0
  9. package/dist/MessengerError-CeK6VAcu.d.mts +64 -0
  10. package/dist/MessengerError-CeK6VAcu.d.mts.map +1 -0
  11. package/dist/{Tool-DE-rnvkw.d.mts → Tool-BF78QWtn.d.mts} +2 -2
  12. package/dist/{Tool-DE-rnvkw.d.mts.map → Tool-BF78QWtn.d.mts.map} +1 -1
  13. package/dist/{ToolEvent-D99MGQYH.d.mts → ToolEvent-Di4a723B.d.mts} +2 -2
  14. package/dist/{ToolEvent-D99MGQYH.d.mts.map → ToolEvent-Di4a723B.d.mts.map} +1 -1
  15. package/dist/{ToolResult-Cj8ChodL.d.mts → ToolResult-BsYQRjmW.d.mts} +2 -2
  16. package/dist/{ToolResult-Cj8ChodL.d.mts.map → ToolResult-BsYQRjmW.d.mts.map} +1 -1
  17. package/dist/{Toolkit-BEZrNPTI.d.mts → Toolkit-DAjJ9Tvl.d.mts} +5 -5
  18. package/dist/{Toolkit-BEZrNPTI.d.mts.map → Toolkit-DAjJ9Tvl.d.mts.map} +1 -1
  19. package/dist/browser/BrowserTool.d.mts +1 -1
  20. package/dist/capabilities/Capabilities.d.mts +26 -2
  21. package/dist/capabilities/Capabilities.d.mts.map +1 -1
  22. package/dist/capabilities/Capabilities.mjs +32 -1
  23. package/dist/capabilities/Capabilities.mjs.map +1 -1
  24. package/dist/domain/Audio.d.mts +1 -1
  25. package/dist/domain/Image.d.mts +3 -2
  26. package/dist/domain/Image.mjs +29 -3
  27. package/dist/domain/Image.mjs.map +1 -1
  28. package/dist/domain/Items.d.mts +2 -2
  29. package/dist/domain/Items.mjs +19 -1
  30. package/dist/domain/Items.mjs.map +1 -1
  31. package/dist/domain/Media.d.mts +2 -2
  32. package/dist/domain/Media.mjs +51 -2
  33. package/dist/domain/Media.mjs.map +1 -1
  34. package/dist/domain/Music.d.mts +3 -8
  35. package/dist/domain/Music.d.mts.map +1 -1
  36. package/dist/domain/Music.mjs.map +1 -1
  37. package/dist/domain/Research.d.mts +1 -1
  38. package/dist/domain/Turn.d.mts +73 -9
  39. package/dist/domain/Turn.d.mts.map +1 -1
  40. package/dist/domain/Turn.mjs +38 -2
  41. package/dist/domain/Turn.mjs.map +1 -1
  42. package/dist/embedding-model/Embedding.d.mts +1 -1
  43. package/dist/image-generator/ImageGenerator.d.mts +188 -0
  44. package/dist/image-generator/ImageGenerator.d.mts.map +1 -0
  45. package/dist/image-generator/ImageGenerator.mjs +50 -0
  46. package/dist/image-generator/ImageGenerator.mjs.map +1 -0
  47. package/dist/inbox/Inbox.d.mts +16 -0
  48. package/dist/inbox/Inbox.d.mts.map +1 -0
  49. package/dist/inbox/Inbox.mjs +28 -0
  50. package/dist/inbox/Inbox.mjs.map +1 -0
  51. package/dist/inbox/Inbox.test.d.mts +1 -0
  52. package/dist/inbox/Inbox.test.mjs +42 -0
  53. package/dist/inbox/Inbox.test.mjs.map +1 -0
  54. package/dist/index.d.mts +15 -9
  55. package/dist/index.mjs +8 -2
  56. package/dist/language-model/LanguageModel.d.mts +2 -2
  57. package/dist/messenger/Messenger.d.mts +472 -0
  58. package/dist/messenger/Messenger.d.mts.map +1 -0
  59. package/dist/messenger/Messenger.mjs +66 -0
  60. package/dist/messenger/Messenger.mjs.map +1 -0
  61. package/dist/messenger/MessengerAdapter.d.mts +35 -0
  62. package/dist/messenger/MessengerAdapter.d.mts.map +1 -0
  63. package/dist/messenger/MessengerAdapter.mjs +102 -0
  64. package/dist/messenger/MessengerAdapter.mjs.map +1 -0
  65. package/dist/messenger/MessengerAdapter.test.d.mts +1 -0
  66. package/dist/messenger/MessengerAdapter.test.mjs +138 -0
  67. package/dist/messenger/MessengerAdapter.test.mjs.map +1 -0
  68. package/dist/messenger/MessengerError.d.mts +2 -0
  69. package/dist/messenger/MessengerError.mjs +39 -0
  70. package/dist/messenger/MessengerError.mjs.map +1 -0
  71. package/dist/music-generator/MusicGenerator.d.mts +3 -2
  72. package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
  73. package/dist/observability/Metrics.d.mts +1 -1
  74. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
  75. package/dist/streaming/Multipart.d.mts +21 -0
  76. package/dist/streaming/Multipart.d.mts.map +1 -0
  77. package/dist/streaming/Multipart.mjs +26 -0
  78. package/dist/streaming/Multipart.mjs.map +1 -0
  79. package/dist/testing/MockMessenger.d.mts +192 -0
  80. package/dist/testing/MockMessenger.d.mts.map +1 -0
  81. package/dist/testing/MockMessenger.mjs +76 -0
  82. package/dist/testing/MockMessenger.mjs.map +1 -0
  83. package/dist/testing/MockMusicGenerator.d.mts +1 -1
  84. package/dist/testing/MockProvider.d.mts +1 -1
  85. package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
  86. package/dist/tool/Approval.d.mts +3 -3
  87. package/dist/tool/HistoryCheck.d.mts +2 -2
  88. package/dist/tool/Tool.d.mts +1 -1
  89. package/dist/tool/ToolEvent.d.mts +1 -1
  90. package/dist/tool/ToolResult.d.mts +1 -1
  91. package/dist/tool/Toolkit.d.mts +1 -1
  92. package/dist/transcriber/Transcriber.d.mts +1 -1
  93. package/dist/web-read/WebReadTool.d.mts +1 -1
  94. package/dist/web-search/WebSearchTool.d.mts +1 -1
  95. package/package.json +29 -1
  96. package/src/capabilities/Capabilities.ts +38 -0
  97. package/src/domain/Image.ts +68 -2
  98. package/src/domain/Items.ts +18 -1
  99. package/src/domain/Media.ts +66 -0
  100. package/src/domain/Music.ts +4 -3
  101. package/src/domain/Turn.ts +56 -0
  102. package/src/image-generator/ImageGenerator.ts +165 -0
  103. package/src/inbox/Inbox.test.ts +52 -0
  104. package/src/inbox/Inbox.ts +33 -0
  105. package/src/index.ts +6 -0
  106. package/src/messenger/Messenger.ts +293 -0
  107. package/src/messenger/MessengerAdapter.test.ts +179 -0
  108. package/src/messenger/MessengerAdapter.ts +189 -0
  109. package/src/messenger/MessengerError.ts +67 -0
  110. package/src/streaming/Multipart.ts +29 -0
  111. package/src/testing/MockMessenger.ts +147 -0
  112. package/dist/Image-BK8-4H6s.d.mts +0 -59
  113. package/dist/Image-BK8-4H6s.d.mts.map +0 -1
  114. package/dist/Items-SfUabNuB.d.mts.map +0 -1
  115. package/dist/Media-DwgpifPD.d.mts +0 -57
  116. package/dist/Media-DwgpifPD.d.mts.map +0 -1
@@ -0,0 +1,102 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { CurrentConversation, text } from "./Messenger.mjs";
3
+ import { Array, Clock, Data, Duration, Effect, Option, Schedule, Stream } from "effect";
4
+ //#region src/messenger/MessengerAdapter.ts
5
+ /**
6
+ * What an adapter author needs and a recipe author never sees: text
7
+ * splitting at a platform ceiling, and post-then-edit progressive delivery
8
+ * for platforms without a native streaming API.
9
+ */
10
+ var MessengerAdapter_exports = /* @__PURE__ */ __exportAll({
11
+ splitForLimit: () => splitForLimit,
12
+ streamViaEdits: () => streamViaEdits
13
+ });
14
+ const boundaries = [
15
+ "\n\n",
16
+ "\n",
17
+ " "
18
+ ];
19
+ /** Best split at or before `limit`; a hard cut only for an unbroken run. */
20
+ const cutAt = (body, limit) => body.length <= limit ? [body, ""] : Array.findFirst(boundaries, (sep) => Option.map(Option.liftPredicate(body.lastIndexOf(sep, limit - sep.length), (at) => at > 0), (at) => [body.slice(0, at), body.slice(at + sep.length)])).pipe(Option.getOrElse(() => [body.slice(0, limit), body.slice(limit)]));
21
+ /**
22
+ * Chunk `body` into pieces no longer than `limit`, breaking on paragraph,
23
+ * line, then word boundaries. Always yields at least one chunk, so callers
24
+ * have something to send even for empty input.
25
+ */
26
+ const splitForLimit = (body, limit) => {
27
+ const [head, rest] = cutAt(body, limit);
28
+ return rest.length === 0 ? [head] : [head, ...splitForLimit(rest, limit)];
29
+ };
30
+ const isRateLimited = (e) => e._tag === "MessengerRateLimited";
31
+ const honourRetryAfter = (times) => Schedule.recurs(times).pipe(Schedule.setInputType(), Schedule.modifyDelay(({ input }) => Effect.succeed(input._tag === "MessengerRateLimited" ? input.retryAfter : Duration.zero)));
32
+ const Progress = Data.taggedEnum();
33
+ const appended = (s, delta) => ({
34
+ ...s,
35
+ pending: s.pending + delta
36
+ });
37
+ /**
38
+ * Progressive delivery as post-then-edit, for adapters with no native
39
+ * streaming API. Coalesces deltas by time and growth, never resends
40
+ * unchanged text, waits out `MessengerRateLimited`, rolls over to a fresh
41
+ * message past `limits.maxText`, and always flushes the tail.
42
+ *
43
+ * The first chunk goes out as soon as any text arrives, so the message shows
44
+ * up immediately and fills in from there. A stream that produced no text
45
+ * posts nothing and yields `None`.
46
+ */
47
+ const streamViaEdits = (verbs, options) => (deltas, streamOptions = {}) => Effect.gen(function* () {
48
+ const every = Duration.toMillis(options?.every ?? "1 second");
49
+ const minChars = options?.minChars ?? 40;
50
+ const schedule = honourRetryAfter(options?.rateLimitRetries ?? 3);
51
+ const conversation = yield* CurrentConversation;
52
+ const patiently = (effect) => Effect.retry(effect, {
53
+ schedule,
54
+ while: isRateLimited
55
+ });
56
+ const deliver = (s, body, now) => Progress.$match(s, {
57
+ Draft: ({ pending, envelope }) => patiently(verbs.post(text(body, envelope))).pipe(Effect.map((id) => Progress.Sent({
58
+ id,
59
+ sent: body,
60
+ pending,
61
+ lastFlush: now
62
+ }))),
63
+ Sent: (sent) => body === sent.sent ? Effect.succeed(sent) : patiently(verbs.edit({
64
+ conversation,
65
+ id: sent.id
66
+ }, text(body))).pipe(Effect.as(Progress.Sent({
67
+ ...sent,
68
+ sent: body,
69
+ lastFlush: now
70
+ })))
71
+ });
72
+ const rollover = (s, now) => {
73
+ const [head, rest] = cutAt(s.pending, verbs.limits.maxText);
74
+ return rest.length === 0 ? Effect.succeed(s) : deliver(s, head, now).pipe(Effect.flatMap(() => rollover(Progress.Draft({
75
+ pending: rest,
76
+ envelope: {}
77
+ }), now)));
78
+ };
79
+ const due = (s, now) => Progress.$match(s, {
80
+ Draft: ({ pending }) => pending.trim().length > 0,
81
+ Sent: ({ sent, pending, lastFlush }) => now - lastFlush >= every && pending.length - sent.length >= minChars
82
+ });
83
+ const step = (before, delta) => Effect.gen(function* () {
84
+ const now = yield* Clock.currentTimeMillis;
85
+ const s = yield* rollover(appended(before, delta), now);
86
+ if (!due(s, now)) return s;
87
+ return yield* deliver(s, s.pending, now);
88
+ });
89
+ const folded = yield* Stream.runFoldEffect(deltas, () => Progress.Draft({
90
+ pending: "",
91
+ envelope: streamOptions
92
+ }), step);
93
+ const now = yield* Clock.currentTimeMillis;
94
+ const s = yield* rollover(folded, now);
95
+ if (s._tag === "Draft" && s.pending.trim().length === 0) return Option.none();
96
+ const final = yield* deliver(s, s.pending, now);
97
+ return Option.some(final.id);
98
+ });
99
+ //#endregion
100
+ export { splitForLimit, streamViaEdits, MessengerAdapter_exports as t };
101
+
102
+ //# sourceMappingURL=MessengerAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessengerAdapter.mjs","names":["Arr"],"sources":["../../src/messenger/MessengerAdapter.ts"],"sourcesContent":["/**\n * What an adapter author needs and a recipe author never sees: text\n * splitting at a platform ceiling, and post-then-edit progressive delivery\n * for platforms without a native streaming API.\n */\nimport { Array as Arr, Clock, Data, Duration, Effect, Option, Schedule, Stream } from \"effect\"\nimport {\n CurrentConversation,\n type MessageId,\n type MessengerService,\n type StreamOptions,\n text,\n} from \"./Messenger.js\"\nimport type * as MessengerError from \"./MessengerError.js\"\n\n// ---------------------------------------------------------------------------\n// Text splitting\n// ---------------------------------------------------------------------------\n\n// Paragraph break first, then line break, then space.\nconst boundaries = [\"\\n\\n\", \"\\n\", \" \"] as const\n\n/** Best split at or before `limit`; a hard cut only for an unbroken run. */\nconst cutAt = (body: string, limit: number): readonly [head: string, rest: string] =>\n body.length <= limit\n ? [body, \"\"]\n : Arr.findFirst(boundaries, (sep) =>\n Option.map(\n Option.liftPredicate(body.lastIndexOf(sep, limit - sep.length), (at) => at > 0),\n (at) => [body.slice(0, at), body.slice(at + sep.length)] as const,\n ),\n ).pipe(Option.getOrElse(() => [body.slice(0, limit), body.slice(limit)] as const))\n\n/**\n * Chunk `body` into pieces no longer than `limit`, breaking on paragraph,\n * line, then word boundaries. Always yields at least one chunk, so callers\n * have something to send even for empty input.\n */\nexport const splitForLimit = (body: string, limit: number): Arr.NonEmptyReadonlyArray<string> => {\n const [head, rest] = cutAt(body, limit)\n return rest.length === 0 ? [head] : [head, ...splitForLimit(rest, limit)]\n}\n\n// ---------------------------------------------------------------------------\n// streamViaEdits\n// ---------------------------------------------------------------------------\n\n/** The slice of a messenger that {@link streamViaEdits} drives. */\nexport type EditableVerbs = Pick<MessengerService, \"post\" | \"edit\" | \"limits\">\n\nexport type StreamViaEditsOptions = {\n /** Minimum wall time between edits. Default 1 second. */\n readonly every?: Duration.Input\n /** Minimum growth since the last edit before spending another. Default 40. */\n readonly minChars?: number\n /** How often to honour a `MessengerRateLimited` before giving up. Default 3. */\n readonly rateLimitRetries?: number\n}\n\nconst isRateLimited = (e: MessengerError.MessengerError): boolean =>\n e._tag === \"MessengerRateLimited\"\n\n// A retry schedule's input is the error, so the delay is the platform's own\n// `retry_after` rather than a guess. Bounded by `recurs`, so an unlucky call\n// eventually surfaces the rate limit instead of stalling behind it.\nconst honourRetryAfter = (\n times: number,\n): Schedule.Schedule<number, MessengerError.MessengerError> =>\n Schedule.recurs(times).pipe(\n Schedule.setInputType<MessengerError.MessengerError>(),\n Schedule.modifyDelay(({ input }) =>\n Effect.succeed(input._tag === \"MessengerRateLimited\" ? input.retryAfter : Duration.zero),\n ),\n )\n\n/**\n * The message being filled: not on the platform yet, or posted and edited\n * since. Only the first draft carries the reply; rollover drafts do not.\n */\ntype Progress = Data.TaggedEnum<{\n Draft: { readonly pending: string; readonly envelope: StreamOptions }\n Sent: {\n readonly id: MessageId\n /** Text the message shows now. Never resent unchanged. */\n readonly sent: string\n /** Text it should show, deltas included. */\n readonly pending: string\n readonly lastFlush: number\n }\n}>\n\nconst Progress = Data.taggedEnum<Progress>()\n\ntype Sent = Data.TaggedEnum.Value<Progress, \"Sent\">\n\nconst appended = (s: Progress, delta: string): Progress => ({\n ...s,\n pending: s.pending + delta,\n})\n\n/**\n * Progressive delivery as post-then-edit, for adapters with no native\n * streaming API. Coalesces deltas by time and growth, never resends\n * unchanged text, waits out `MessengerRateLimited`, rolls over to a fresh\n * message past `limits.maxText`, and always flushes the tail.\n *\n * The first chunk goes out as soon as any text arrives, so the message shows\n * up immediately and fills in from there. A stream that produced no text\n * posts nothing and yields `None`.\n */\nexport const streamViaEdits =\n (verbs: EditableVerbs, options?: StreamViaEditsOptions): MessengerService[\"stream\"] =>\n (deltas, streamOptions = {}) =>\n Effect.gen(function* () {\n const every = Duration.toMillis(options?.every ?? \"1 second\")\n const minChars = options?.minChars ?? 40\n const schedule = honourRetryAfter(options?.rateLimitRetries ?? 3)\n const conversation = yield* CurrentConversation\n\n const patiently = <A, R2>(\n effect: Effect.Effect<A, MessengerError.MessengerError, R2>,\n ): Effect.Effect<A, MessengerError.MessengerError, R2> =>\n Effect.retry(effect, { schedule, while: isRateLimited })\n\n // Post a draft, edit a sent message, and skip an edit that would resend\n // what the message already shows.\n const deliver = (\n s: Progress,\n body: string,\n now: number,\n ): Effect.Effect<Sent, MessengerError.MessengerError, CurrentConversation> =>\n Progress.$match(s, {\n Draft: ({ pending, envelope }) =>\n patiently(verbs.post(text(body, envelope))).pipe(\n Effect.map((id) => Progress.Sent({ id, sent: body, pending, lastFlush: now })),\n ),\n Sent: (sent) =>\n body === sent.sent\n ? Effect.succeed(sent)\n : patiently(verbs.edit({ conversation, id: sent.id }, text(body))).pipe(\n Effect.as(Progress.Sent({ ...sent, sent: body, lastFlush: now })),\n ),\n })\n\n // Anything past the ceiling becomes the next message: finish the current\n // one at a clean boundary, then start a draft on the remainder.\n const rollover = (\n s: Progress,\n now: number,\n ): Effect.Effect<Progress, MessengerError.MessengerError, CurrentConversation> => {\n const [head, rest] = cutAt(s.pending, verbs.limits.maxText)\n return rest.length === 0\n ? Effect.succeed(s)\n : deliver(s, head, now).pipe(\n Effect.flatMap(() => rollover(Progress.Draft({ pending: rest, envelope: {} }), now)),\n )\n }\n\n // A draft lands on its first real text; a sent message waits for both\n // gates. Blank is not text: a model that opens a tool-calling turn with\n // a newline would otherwise post it, and platforms reject a message\n // that is only whitespace.\n const due = (s: Progress, now: number): boolean =>\n Progress.$match(s, {\n Draft: ({ pending }) => pending.trim().length > 0,\n Sent: ({ sent, pending, lastFlush }) =>\n now - lastFlush >= every && pending.length - sent.length >= minChars,\n })\n\n const step = (before: Progress, delta: string) =>\n Effect.gen(function* () {\n const now = yield* Clock.currentTimeMillis\n const s = yield* rollover(appended(before, delta), now)\n if (!due(s, now)) return s\n return yield* deliver(s, s.pending, now)\n })\n\n const folded = yield* Stream.runFoldEffect(\n deltas,\n () => Progress.Draft({ pending: \"\", envelope: streamOptions }),\n step,\n )\n const now = yield* Clock.currentTimeMillis\n const s = yield* rollover(folded, now)\n // The tail always lands; a draft that never held text is nothing to send.\n if (s._tag === \"Draft\" && s.pending.trim().length === 0) return Option.none()\n const final = yield* deliver(s, s.pending, now)\n return Option.some(final.id)\n })\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,138 @@
1
+ import { ChannelId, MessageId, inConversation } from "./Messenger.mjs";
2
+ import { splitForLimit } from "./MessengerAdapter.mjs";
3
+ import { MessengerRateLimited } from "./MessengerError.mjs";
4
+ import { o as describe } from "../utils-CiH_DjFW.mjs";
5
+ import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
6
+ import { t as it } from "../dist-nohcnkD6.mjs";
7
+ import { make } from "../testing/MockMessenger.mjs";
8
+ import { Array, Duration, Effect, Option, Result, Stream } from "effect";
9
+ import { TestClock } from "effect/testing";
10
+ //#region src/messenger/MessengerAdapter.test.ts
11
+ const here = { channel: ChannelId("c1") };
12
+ const bodyOf = (msg) => msg.body._tag === "Text" ? msg.body.text : "";
13
+ const posts = (calls) => Array.filterMap(calls, (c) => c._tag === "Post" ? Result.succeed(bodyOf(c.message)) : Result.failVoid);
14
+ const edits = (calls) => Array.filterMap(calls, (c) => c._tag === "Edit" ? Result.succeed(bodyOf(c.next)) : Result.failVoid);
15
+ /** Run `deltas` through the mock's `stream` and hand back the call log. */
16
+ const streamed = (deltas, script = {}, streamOptions) => Effect.gen(function* () {
17
+ const { service, recorder } = make(script);
18
+ const id = yield* service.stream(Stream.fromIterable(deltas), streamOptions);
19
+ const { calls } = yield* recorder;
20
+ return {
21
+ id,
22
+ calls
23
+ };
24
+ }).pipe(inConversation(here));
25
+ const options = (o) => ({ streamOptions: o });
26
+ describe("splitForLimit", () => {
27
+ it("prefers a paragraph break, then a line, then a word", () => {
28
+ globalExpect(splitForLimit("aaa\n\nbbb ccc", 8)).toEqual(["aaa", "bbb ccc"]);
29
+ globalExpect(splitForLimit("aaa\nbbbb ccc", 8)).toEqual(["aaa", "bbbb ccc"]);
30
+ globalExpect(splitForLimit("aaaa bbbb ccc", 9)).toEqual(["aaaa", "bbbb ccc"]);
31
+ });
32
+ it("hard-cuts a run with no boundary inside the limit", () => {
33
+ globalExpect(splitForLimit("aaaaaaaaaa", 4)).toEqual([
34
+ "aaaa",
35
+ "aaaa",
36
+ "aa"
37
+ ]);
38
+ });
39
+ });
40
+ describe("streamViaEdits", () => {
41
+ it.effect("coalesces deltas arriving inside the `every` window", () => Effect.gen(function* () {
42
+ const { calls } = yield* streamed([
43
+ "Hel",
44
+ "lo ",
45
+ "the",
46
+ "re!"
47
+ ]);
48
+ globalExpect(posts(calls)).toEqual(["Hel"]);
49
+ globalExpect(edits(calls)).toEqual(["Hello there!"]);
50
+ }));
51
+ it.effect("waits for `minChars` of growth before spending an edit", () => Effect.gen(function* () {
52
+ const { calls } = yield* streamed([
53
+ "aaaa",
54
+ "bbbb",
55
+ "cccc",
56
+ "dddd",
57
+ "eeee",
58
+ "ffff"
59
+ ], options({
60
+ every: 0,
61
+ minChars: 10
62
+ }));
63
+ globalExpect(posts(calls)).toEqual(["aaaa"]);
64
+ globalExpect(edits(calls).map((t) => t.length)).toEqual([16, 24]);
65
+ }));
66
+ it.effect("skips the final flush when the message already shows the text", () => Effect.gen(function* () {
67
+ const { calls } = yield* streamed([
68
+ "aaaaa",
69
+ "bbbbb",
70
+ "ccccc"
71
+ ], options({
72
+ every: 0,
73
+ minChars: 10
74
+ }));
75
+ globalExpect(edits(calls)).toEqual(["aaaaabbbbbccccc"]);
76
+ }));
77
+ it.effect("rolls over to a new message past maxText", () => Effect.gen(function* () {
78
+ const { id, calls } = yield* streamed(["aaaa bbbb cccc dddd"], { limits: {
79
+ maxText: 10,
80
+ maxCaption: 10
81
+ } });
82
+ globalExpect(posts(calls)).toEqual(["aaaa bbbb", "cccc dddd"]);
83
+ globalExpect(edits(calls)).toEqual([]);
84
+ globalExpect(id).toEqual(Option.some(MessageId("m2")));
85
+ }));
86
+ it.effect("quotes the question on the first message only, never on a rollover", () => Effect.gen(function* () {
87
+ const { calls } = yield* streamed(["aaaa bbbb cccc dddd"], { limits: {
88
+ maxText: 10,
89
+ maxCaption: 10
90
+ } }, { replyTo: MessageId("q1") });
91
+ const replies = Array.filterMap(calls, (c) => c._tag === "Post" ? Result.succeed(c.message.replyTo) : Result.failVoid);
92
+ globalExpect(replies).toEqual([MessageId("q1"), void 0]);
93
+ }));
94
+ it.effect("posts nothing for a stream with no text", () => Effect.gen(function* () {
95
+ const { id, calls } = yield* streamed(["", ""]);
96
+ globalExpect(calls).toEqual([]);
97
+ globalExpect(id).toEqual(Option.none());
98
+ }));
99
+ it.effect("treats whitespace-only deltas as no text at all", () => Effect.gen(function* () {
100
+ const { id, calls } = yield* streamed([
101
+ "\n",
102
+ " ",
103
+ "\n\n"
104
+ ]);
105
+ globalExpect(calls).toEqual([]);
106
+ globalExpect(id).toEqual(Option.none());
107
+ }));
108
+ it.live("waits out a rate limit and re-sends the same edit", () => Effect.gen(function* () {
109
+ const limited = new MessengerRateLimited({
110
+ provider: "mock",
111
+ retryAfter: Duration.millis(10)
112
+ });
113
+ const { calls } = yield* streamed(["abc", "defghij"], {
114
+ ...options({
115
+ every: 0,
116
+ minChars: 1
117
+ }),
118
+ failures: [void 0, limited]
119
+ });
120
+ globalExpect(posts(calls)).toEqual(["abc"]);
121
+ globalExpect(edits(calls)).toEqual(["abcdefghij", "abcdefghij"]);
122
+ }));
123
+ it.effect("gives up once the rate-limit budget is spent", () => Effect.gen(function* () {
124
+ const limited = new MessengerRateLimited({
125
+ provider: "mock",
126
+ retryAfter: Duration.zero
127
+ });
128
+ const failed = yield* Effect.flip(streamed(["abc"], {
129
+ ...options({ rateLimitRetries: 1 }),
130
+ failures: [limited, limited]
131
+ }));
132
+ globalExpect(failed._tag).toBe("MessengerRateLimited");
133
+ }).pipe(TestClock.withLive));
134
+ });
135
+ //#endregion
136
+ export {};
137
+
138
+ //# sourceMappingURL=MessengerAdapter.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessengerAdapter.test.mjs","names":["Arr","MockMessenger.make"],"sources":["../../src/messenger/MessengerAdapter.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Option, Result, Stream } from \"effect\"\nimport { TestClock } from \"effect/testing\"\nimport { expect } from \"vitest\"\nimport * as MockMessenger from \"../testing/MockMessenger.js\"\nimport {\n ChannelId,\n type ConversationRef,\n MessageId,\n type Outbound,\n type StreamOptions,\n inConversation,\n} from \"./Messenger.js\"\nimport { type StreamViaEditsOptions, splitForLimit } from \"./MessengerAdapter.js\"\nimport { MessengerRateLimited } from \"./MessengerError.js\"\n\nconst here: ConversationRef = { channel: ChannelId(\"c1\") }\n\nconst bodyOf = (msg: Outbound): string => (msg.body._tag === \"Text\" ? msg.body.text : \"\")\n\nconst posts = (calls: ReadonlyArray<MockMessenger.Call>) =>\n Arr.filterMap(calls, (c) =>\n c._tag === \"Post\" ? Result.succeed(bodyOf(c.message)) : Result.failVoid,\n )\n\nconst edits = (calls: ReadonlyArray<MockMessenger.Call>) =>\n Arr.filterMap(calls, (c) =>\n c._tag === \"Edit\" ? Result.succeed(bodyOf(c.next)) : Result.failVoid,\n )\n\n/** Run `deltas` through the mock's `stream` and hand back the call log. */\nconst streamed = (\n deltas: ReadonlyArray<string>,\n script: MockMessenger.MockMessengerScript = {},\n streamOptions?: StreamOptions,\n) =>\n Effect.gen(function* () {\n const { service, recorder } = MockMessenger.make(script)\n const id = yield* service.stream(Stream.fromIterable(deltas), streamOptions)\n const { calls } = yield* recorder\n return { id, calls }\n }).pipe(inConversation(here))\n\nconst options = (o: StreamViaEditsOptions): MockMessenger.MockMessengerScript => ({\n streamOptions: o,\n})\n\n// ---------------------------------------------------------------------------\n// splitForLimit\n// ---------------------------------------------------------------------------\n\ndescribe(\"splitForLimit\", () => {\n it(\"prefers a paragraph break, then a line, then a word\", () => {\n expect(splitForLimit(\"aaa\\n\\nbbb ccc\", 8)).toEqual([\"aaa\", \"bbb ccc\"])\n expect(splitForLimit(\"aaa\\nbbbb ccc\", 8)).toEqual([\"aaa\", \"bbbb ccc\"])\n expect(splitForLimit(\"aaaa bbbb ccc\", 9)).toEqual([\"aaaa\", \"bbbb ccc\"])\n })\n\n it(\"hard-cuts a run with no boundary inside the limit\", () => {\n expect(splitForLimit(\"aaaaaaaaaa\", 4)).toEqual([\"aaaa\", \"aaaa\", \"aa\"])\n })\n})\n\n// ---------------------------------------------------------------------------\n// streamViaEdits\n// ---------------------------------------------------------------------------\n\ndescribe(\"streamViaEdits\", () => {\n // TestClock keeps wall time frozen, so the `every` gate never opens and\n // every delta after the first coalesces into the final flush.\n it.effect(\"coalesces deltas arriving inside the `every` window\", () =>\n Effect.gen(function* () {\n const { calls } = yield* streamed([\"Hel\", \"lo \", \"the\", \"re!\"])\n\n expect(posts(calls)).toEqual([\"Hel\"])\n expect(edits(calls)).toEqual([\"Hello there!\"])\n }),\n )\n\n it.effect(\"waits for `minChars` of growth before spending an edit\", () =>\n Effect.gen(function* () {\n const { calls } = yield* streamed(\n [\"aaaa\", \"bbbb\", \"cccc\", \"dddd\", \"eeee\", \"ffff\"],\n options({ every: 0, minChars: 10 }),\n )\n\n // Edits land at 16 chars (12 of growth) and again on the final flush.\n expect(posts(calls)).toEqual([\"aaaa\"])\n expect(edits(calls).map((t) => t.length)).toEqual([16, 24])\n }),\n )\n\n it.effect(\"skips the final flush when the message already shows the text\", () =>\n Effect.gen(function* () {\n const { calls } = yield* streamed(\n [\"aaaaa\", \"bbbbb\", \"ccccc\"],\n options({ every: 0, minChars: 10 }),\n )\n\n // The third delta opens the growth gate, so the tail is already sent.\n expect(edits(calls)).toEqual([\"aaaaabbbbbccccc\"])\n }),\n )\n\n it.effect(\"rolls over to a new message past maxText\", () =>\n Effect.gen(function* () {\n const { id, calls } = yield* streamed([\"aaaa bbbb cccc dddd\"], {\n limits: { maxText: 10, maxCaption: 10 },\n })\n\n // Cut on the word boundary, not mid-word, and the id is the last message.\n expect(posts(calls)).toEqual([\"aaaa bbbb\", \"cccc dddd\"])\n expect(edits(calls)).toEqual([])\n expect(id).toEqual(Option.some(MessageId(\"m2\")))\n }),\n )\n\n it.effect(\"quotes the question on the first message only, never on a rollover\", () =>\n Effect.gen(function* () {\n const { calls } = yield* streamed(\n [\"aaaa bbbb cccc dddd\"],\n { limits: { maxText: 10, maxCaption: 10 } },\n { replyTo: MessageId(\"q1\") },\n )\n\n const replies = Arr.filterMap(calls, (c) =>\n c._tag === \"Post\" ? Result.succeed(c.message.replyTo) : Result.failVoid,\n )\n expect(replies).toEqual([MessageId(\"q1\"), undefined])\n }),\n )\n\n it.effect(\"posts nothing for a stream with no text\", () =>\n Effect.gen(function* () {\n const { id, calls } = yield* streamed([\"\", \"\"])\n\n expect(calls).toEqual([])\n expect(id).toEqual(Option.none())\n }),\n )\n\n // A model opening a tool-calling turn with a newline, which every platform\n // rejects as an empty message.\n it.effect(\"treats whitespace-only deltas as no text at all\", () =>\n Effect.gen(function* () {\n const { id, calls } = yield* streamed([\"\\n\", \" \", \"\\n\\n\"])\n\n expect(calls).toEqual([])\n expect(id).toEqual(Option.none())\n }),\n )\n\n it.live(\"waits out a rate limit and re-sends the same edit\", () =>\n Effect.gen(function* () {\n const limited = new MessengerRateLimited({\n provider: \"mock\",\n retryAfter: Duration.millis(10),\n })\n const { calls } = yield* streamed([\"abc\", \"defghij\"], {\n ...options({ every: 0, minChars: 1 }),\n failures: [undefined, limited],\n })\n\n expect(posts(calls)).toEqual([\"abc\"])\n expect(edits(calls)).toEqual([\"abcdefghij\", \"abcdefghij\"])\n }),\n )\n\n it.effect(\"gives up once the rate-limit budget is spent\", () =>\n Effect.gen(function* () {\n const limited = new MessengerRateLimited({ provider: \"mock\", retryAfter: Duration.zero })\n const failed = yield* Effect.flip(\n streamed([\"abc\"], { ...options({ rateLimitRetries: 1 }), failures: [limited, limited] }),\n )\n\n expect(failed._tag).toBe(\"MessengerRateLimited\")\n }).pipe(TestClock.withLive),\n )\n})\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { a as MessengerRateLimited, c as MessengerUnsupported, i as MessengerOperation, l as describe, n as MessengerError, o as MessengerRequestFailed, s as MessengerTransportClosed, t as MessengerConnectFailed } from "../MessengerError-CeK6VAcu.mjs";
2
+ export { MessengerConnectFailed, MessengerError, MessengerOperation, MessengerRateLimited, MessengerRequestFailed, MessengerTransportClosed, MessengerUnsupported, describe };
@@ -0,0 +1,39 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { Data, Match } from "effect";
3
+ //#region src/messenger/MessengerError.ts
4
+ var MessengerError_exports = /* @__PURE__ */ __exportAll({
5
+ MessengerConnectFailed: () => MessengerConnectFailed,
6
+ MessengerRateLimited: () => MessengerRateLimited,
7
+ MessengerRequestFailed: () => MessengerRequestFailed,
8
+ MessengerTransportClosed: () => MessengerTransportClosed,
9
+ MessengerUnsupported: () => MessengerUnsupported,
10
+ describe: () => describe
11
+ });
12
+ /** Transport never came up: bad token, rejected handshake. Raised at layer build. */
13
+ var MessengerConnectFailed = class extends Data.TaggedError("MessengerConnectFailed") {};
14
+ /**
15
+ * Inbound transport ended for good (reconnect budget spent, fatal close).
16
+ * Routine reconnects stay inside the adapter and never surface here.
17
+ */
18
+ var MessengerTransportClosed = class extends Data.TaggedError("MessengerTransportClosed") {};
19
+ /** An outbound verb was rejected: permission, unknown chat, text too long, no-op edit. */
20
+ var MessengerRequestFailed = class extends Data.TaggedError("MessengerRequestFailed") {};
21
+ /**
22
+ * Slow down, for this long. Typed rather than retried blindly so callers -
23
+ * `streamViaEdits` above all - decide whether waiting is still worth it.
24
+ */
25
+ var MessengerRateLimited = class extends Data.TaggedError("MessengerRateLimited") {};
26
+ /** The platform cannot express this at all: off-set reaction emoji, edits on WhatsApp. */
27
+ var MessengerUnsupported = class extends Data.TaggedError("MessengerUnsupported") {};
28
+ /** Human-readable description for logs. Prose, not a contract; branch on `_tag`. */
29
+ const describe = Match.type().pipe(Match.discriminatorsExhaustive("_tag")({
30
+ MessengerConnectFailed: (e) => `the messenger could not connect: ${e.reason}`,
31
+ MessengerTransportClosed: (e) => `the messenger connection closed: ${e.reason}`,
32
+ MessengerRequestFailed: (e) => `the ${e.operation} was rejected: ${e.reason}`,
33
+ MessengerRateLimited: () => "the messenger is rate limited",
34
+ MessengerUnsupported: (e) => `this messenger does not support ${e.capability}: ${e.reason}`
35
+ }));
36
+ //#endregion
37
+ export { MessengerConnectFailed, MessengerRateLimited, MessengerRequestFailed, MessengerTransportClosed, MessengerUnsupported, describe, MessengerError_exports as t };
38
+
39
+ //# sourceMappingURL=MessengerError.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessengerError.mjs","names":[],"sources":["../../src/messenger/MessengerError.ts"],"sourcesContent":["import { Data, type Duration, Match } from \"effect\"\n\n// `reason` is required wherever one exists: the platform always says\n// something, and an adapter raising `Unsupported` knows why it did.\n\n/** Transport never came up: bad token, rejected handshake. Raised at layer build. */\nexport class MessengerConnectFailed extends Data.TaggedError(\"MessengerConnectFailed\")<{\n provider: string\n reason: string\n raw: unknown\n}> {}\n\n/**\n * Inbound transport ended for good (reconnect budget spent, fatal close).\n * Routine reconnects stay inside the adapter and never surface here.\n */\nexport class MessengerTransportClosed extends Data.TaggedError(\"MessengerTransportClosed\")<{\n provider: string\n reason: string\n raw?: unknown\n}> {}\n\n/** Which verb was rejected. */\nexport type MessengerOperation = \"post\" | \"edit\" | \"react\" | \"typing\"\n\n/** An outbound verb was rejected: permission, unknown chat, text too long, no-op edit. */\nexport class MessengerRequestFailed extends Data.TaggedError(\"MessengerRequestFailed\")<{\n provider: string\n operation: MessengerOperation\n reason: string\n raw: unknown\n}> {}\n\n/**\n * Slow down, for this long. Typed rather than retried blindly so callers -\n * `streamViaEdits` above all - decide whether waiting is still worth it.\n */\nexport class MessengerRateLimited extends Data.TaggedError(\"MessengerRateLimited\")<{\n provider: string\n retryAfter: Duration.Duration\n raw?: unknown\n}> {}\n\n/** The platform cannot express this at all: off-set reaction emoji, edits on WhatsApp. */\nexport class MessengerUnsupported extends Data.TaggedError(\"MessengerUnsupported\")<{\n provider: string\n capability: string\n reason: string\n}> {}\n\nexport type MessengerError =\n | MessengerConnectFailed\n | MessengerTransportClosed\n | MessengerRequestFailed\n | MessengerRateLimited\n | MessengerUnsupported\n\n/** Human-readable description for logs. Prose, not a contract; branch on `_tag`. */\nexport const describe: (e: MessengerError) => string = Match.type<MessengerError>().pipe(\n Match.discriminatorsExhaustive(\"_tag\")({\n MessengerConnectFailed: (e) => `the messenger could not connect: ${e.reason}`,\n MessengerTransportClosed: (e) => `the messenger connection closed: ${e.reason}`,\n MessengerRequestFailed: (e) => `the ${e.operation} was rejected: ${e.reason}`,\n MessengerRateLimited: () => \"the messenger is rate limited\",\n MessengerUnsupported: (e) => `this messenger does not support ${e.capability}: ${e.reason}`,\n }),\n)\n"],"mappings":";;;;;;;;;;;;AAMA,IAAa,yBAAb,cAA4C,KAAK,YAAY,wBAAwB,CAAC,CAInF,CAAC;;;;;AAMJ,IAAa,2BAAb,cAA8C,KAAK,YAAY,0BAA0B,CAAC,CAIvF,CAAC;;AAMJ,IAAa,yBAAb,cAA4C,KAAK,YAAY,wBAAwB,CAAC,CAKnF,CAAC;;;;;AAMJ,IAAa,uBAAb,cAA0C,KAAK,YAAY,sBAAsB,CAAC,CAI/E,CAAC;;AAGJ,IAAa,uBAAb,cAA0C,KAAK,YAAY,sBAAsB,CAAC,CAI/E,CAAC;;AAUJ,MAAa,WAA0C,MAAM,KAAqB,CAAC,CAAC,KAClF,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,yBAAyB,MAAM,oCAAoC,EAAE;CACrE,2BAA2B,MAAM,oCAAoC,EAAE;CACvE,yBAAyB,MAAM,OAAO,EAAE,UAAU,iBAAiB,EAAE;CACrE,4BAA4B;CAC5B,uBAAuB,MAAM,mCAAmC,EAAE,WAAW,IAAI,EAAE;AACrF,CAAC,CACH"}
@@ -1,6 +1,7 @@
1
+ import { c as Watermark } from "../Media-BbX7BAaO.mjs";
1
2
  import { t as AiError } from "../AiError-eLV9t1lU.mjs";
2
- import { n as AudioChunk } from "../Audio-60lKKHxS.mjs";
3
- import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicResult, MusicSection, MusicSessionControl, MusicSessionInput, MusicStreamEvent, Watermark, WeightedPrompt } from "../domain/Music.mjs";
3
+ import { n as AudioChunk } from "../Audio-_nGR4V0X.mjs";
4
+ import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicResult, MusicSection, MusicSessionControl, MusicSessionInput, MusicStreamEvent, WeightedPrompt } from "../domain/Music.mjs";
4
5
  import { Context, Effect, Stream } from "effect";
5
6
  declare namespace MusicGenerator_d_exports {
6
7
  export { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicGenerator, MusicGeneratorService, MusicInteractiveSession, MusicResult, MusicSection, MusicSessionControl, MusicSessionInput, MusicStreamEvent, Watermark, WeightedPrompt, generate, streamGeneration, streamGenerationFrom };
@@ -1 +1 @@
1
- {"version":3,"file":"MusicGenerator.d.mts","names":[],"sources":["../../src/music-generator/MusicGenerator.ts"],"mappings":";;;;;;;KAwBY;;;;;;;;;;WAUD,WACP,SAAS,+BACN,OAAO,OAAO,gBAAgB;;;;;;;WAO1B,mBACP,SAAS,qCACN,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;WAuBtB,uBAAuB,GAAG,GACjC,OAAO,OAAO,OAAO,mBAAmB,GAAG,IAC3C,SAAS,qCACN,OAAO,OAAO,kBAAkB,UAAkB,GAAG;;;cAG/C,uBAAuB;;;;;;;;;;;;cAcvB,gCAAgC;;cAKhC,WAAQ,SACV,+BACR,OAAO,OAAO,gBAAgB,SAAiB;;cAIrC,mBAAgB,SAClB,qCACR,OAAO,OAAO,YAAY,SAAiB;;;;;;;;;;;;;;;;;;;;;;;;;cA2BjC;GAET,SAAS,oCACP,GAAG,GACL,OAAO,OAAO,OAAO,mBAAmB,GAAG,OACxC,OAAO,OACV,kBACA,UAAkB,GAClB,IAAI,iBAAiB;GAEtB,GAAG,GACF,OAAO,OAAO,OAAO,mBAAmB,GAAG,IAC3C,SAAS,mCACR,OAAO,OACR,kBACA,UAAkB,GAClB,IAAI,iBAAiB"}
1
+ {"version":3,"file":"MusicGenerator.d.mts","names":[],"sources":["../../src/music-generator/MusicGenerator.ts"],"mappings":";;;;;;;;KAwBY;;;;;;;;;;WAUD,WACP,SAAS,+BACN,OAAO,OAAO,gBAAgB;;;;;;;WAO1B,mBACP,SAAS,qCACN,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;WAuBtB,uBAAuB,GAAG,GACjC,OAAO,OAAO,OAAO,mBAAmB,GAAG,IAC3C,SAAS,qCACN,OAAO,OAAO,kBAAkB,UAAkB,GAAG;;;cAG/C,uBAAuB;;;;;;;;;;;;cAcvB,gCAAgC;;cAKhC,WAAQ,SACV,+BACR,OAAO,OAAO,gBAAgB,SAAiB;;cAIrC,mBAAgB,SAClB,qCACR,OAAO,OAAO,YAAY,SAAiB;;;;;;;;;;;;;;;;;;;;;;;;;cA2BjC;GAET,SAAS,oCACP,GAAG,GACL,OAAO,OAAO,OAAO,mBAAmB,GAAG,OACxC,OAAO,OACV,kBACA,UAAkB,GAClB,IAAI,iBAAiB;GAEtB,GAAG,GACF,OAAO,OAAO,OAAO,mBAAmB,GAAG,IAC3C,SAAS,mCACR,OAAO,OACR,kBACA,UAAkB,GAClB,IAAI,iBAAiB"}
@@ -1,4 +1,4 @@
1
- import { x as Usage } from "../Items-SfUabNuB.mjs";
1
+ import { S as Usage } from "../Items-C7MyrYPX.mjs";
2
2
  import { TurnEvent } from "../domain/Turn.mjs";
3
3
  import { Duration, Effect, Option, Stream } from "effect";
4
4
  declare namespace Metrics_d_exports {
@@ -1,5 +1,5 @@
1
1
  import { t as AiError } from "../AiError-eLV9t1lU.mjs";
2
- import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-60lKKHxS.mjs";
2
+ import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-_nGR4V0X.mjs";
3
3
  import { Context, Effect, Stream } from "effect";
4
4
  declare namespace SpeechSynthesizer_d_exports {
5
5
  export { CommonStreamSynthesizeRequest, CommonSynthesizeDialogueRequest, CommonSynthesizeRequest, CustomPronunciation, DialogueTurn, MultiSpeakerTts, SpeechSynthesizer, SpeechSynthesizerService, TtsIncrementalText, streamSynthesis, streamSynthesisFrom, streamSynthesizeDialogue, synthesize, synthesizeDialogue };
@@ -0,0 +1,21 @@
1
+ import { Effect } from "effect";
2
+ import { HttpClientRequest } from "effect/unstable/http";
3
+ declare namespace Multipart_d_exports {
4
+ export { bodyMultipart };
5
+ }
6
+ /**
7
+ * Multipart body for an `HttpClientRequest`, encoded to bytes.
8
+ *
9
+ * Use this over `HttpClientRequest.bodyFormData`: the Undici client
10
+ * passes the `FormData` object straight to `dispatcher.request`, which
11
+ * cannot serialise it, so the request is never sent and the effect
12
+ * hangs with no error. Encoding here produces the same bytes every
13
+ * other client derives internally, so it works on all of them.
14
+ *
15
+ * The failure is whatever `Response.arrayBuffer` rejects with; map it
16
+ * to your provider's transport error at the call site.
17
+ */
18
+ declare const bodyMultipart: (form: FormData) => Effect.Effect<(request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest, unknown>;
19
+ //#endregion
20
+ export { bodyMultipart, Multipart_d_exports as t };
21
+ //# sourceMappingURL=Multipart.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Multipart.d.mts","names":[],"sources":["../../src/streaming/Multipart.ts"],"mappings":";;;;;;;;;;;;;;;;;cAea,gBAAa,MAClB,aACL,OAAO,QACP,SAAS,kBAAkB,sBAAsB,kBAAkB"}
@@ -0,0 +1,26 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { Effect } from "effect";
3
+ import { HttpClientRequest } from "effect/unstable/http";
4
+ //#region src/streaming/Multipart.ts
5
+ var Multipart_exports = /* @__PURE__ */ __exportAll({ bodyMultipart: () => bodyMultipart });
6
+ /**
7
+ * Multipart body for an `HttpClientRequest`, encoded to bytes.
8
+ *
9
+ * Use this over `HttpClientRequest.bodyFormData`: the Undici client
10
+ * passes the `FormData` object straight to `dispatcher.request`, which
11
+ * cannot serialise it, so the request is never sent and the effect
12
+ * hangs with no error. Encoding here produces the same bytes every
13
+ * other client derives internally, so it works on all of them.
14
+ *
15
+ * The failure is whatever `Response.arrayBuffer` rejects with; map it
16
+ * to your provider's transport error at the call site.
17
+ */
18
+ const bodyMultipart = (form) => Effect.gen(function* () {
19
+ const encoded = new Response(form);
20
+ const buffer = yield* Effect.tryPromise(() => encoded.arrayBuffer());
21
+ return HttpClientRequest.bodyUint8Array(new Uint8Array(buffer), encoded.headers.get("content-type") ?? "multipart/form-data");
22
+ });
23
+ //#endregion
24
+ export { bodyMultipart, Multipart_exports as t };
25
+
26
+ //# sourceMappingURL=Multipart.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Multipart.mjs","names":[],"sources":["../../src/streaming/Multipart.ts"],"sourcesContent":["import { Effect } from \"effect\"\nimport { HttpClientRequest } from \"effect/unstable/http\"\n\n/**\n * Multipart body for an `HttpClientRequest`, encoded to bytes.\n *\n * Use this over `HttpClientRequest.bodyFormData`: the Undici client\n * passes the `FormData` object straight to `dispatcher.request`, which\n * cannot serialise it, so the request is never sent and the effect\n * hangs with no error. Encoding here produces the same bytes every\n * other client derives internally, so it works on all of them.\n *\n * The failure is whatever `Response.arrayBuffer` rejects with; map it\n * to your provider's transport error at the call site.\n */\nexport const bodyMultipart = (\n form: FormData,\n): Effect.Effect<\n (request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest,\n unknown\n> =>\n Effect.gen(function* () {\n const encoded = new Response(form)\n const buffer = yield* Effect.tryPromise(() => encoded.arrayBuffer())\n return HttpClientRequest.bodyUint8Array(\n new Uint8Array(buffer),\n encoded.headers.get(\"content-type\") ?? \"multipart/form-data\",\n )\n })\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAa,iBACX,SAKA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,IAAI,SAAS,IAAI;CACjC,MAAM,SAAS,OAAO,OAAO,iBAAiB,QAAQ,YAAY,CAAC;CACnE,OAAO,kBAAkB,eACvB,IAAI,WAAW,MAAM,GACrB,QAAQ,QAAQ,IAAI,cAAc,KAAK,qBACzC;AACF,CAAC"}
@@ -0,0 +1,192 @@
1
+ import { n as MessengerError } from "../MessengerError-CeK6VAcu.mjs";
2
+ import { ConversationRef, InboundEvent, MessageRef, Messenger, MessengerLimits, MessengerService, Outbound } from "../messenger/Messenger.mjs";
3
+ import { StreamViaEditsOptions } from "../messenger/MessengerAdapter.mjs";
4
+ import { Data, Effect, Layer, Stream } from "effect";
5
+ //#region src/testing/MockMessenger.d.ts
6
+ /**
7
+ * One entry per outbound call. `Post` is logged once even when the text was
8
+ * long enough to split, so a caller that rolled over on its own is
9
+ * distinguishable from one that leaned on `post` to do it.
10
+ */
11
+ type Call = Data.TaggedEnum<{
12
+ Post: {
13
+ readonly conversation: ConversationRef;
14
+ readonly message: Outbound;
15
+ };
16
+ Edit: {
17
+ readonly message: MessageRef;
18
+ readonly next: Outbound;
19
+ };
20
+ React: {
21
+ readonly message: MessageRef;
22
+ readonly emoji: string;
23
+ };
24
+ TypingStart: {
25
+ readonly conversation: ConversationRef;
26
+ };
27
+ TypingStop: {
28
+ readonly conversation: ConversationRef;
29
+ };
30
+ }>;
31
+ declare const Call: {
32
+ readonly $is: <Tag extends "Edit" | "Post" | "React" | "TypingStart" | "TypingStop">(tag: Tag) => (u: unknown) => u is Extract<{
33
+ readonly _tag: "Edit";
34
+ readonly message: MessageRef;
35
+ readonly next: Outbound;
36
+ }, {
37
+ readonly _tag: Tag;
38
+ }> | Extract<{
39
+ readonly _tag: "Post";
40
+ readonly conversation: ConversationRef;
41
+ readonly message: Outbound;
42
+ }, {
43
+ readonly _tag: Tag;
44
+ }> | Extract<{
45
+ readonly _tag: "React";
46
+ readonly message: MessageRef;
47
+ readonly emoji: string;
48
+ }, {
49
+ readonly _tag: Tag;
50
+ }> | Extract<{
51
+ readonly _tag: "TypingStart";
52
+ readonly conversation: ConversationRef;
53
+ }, {
54
+ readonly _tag: Tag;
55
+ }> | Extract<{
56
+ readonly _tag: "TypingStop";
57
+ readonly conversation: ConversationRef;
58
+ }, {
59
+ readonly _tag: Tag;
60
+ }>;
61
+ readonly $match: {
62
+ <Cases extends { readonly [Tag in "Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]: (args: Extract<{
63
+ readonly _tag: "Edit";
64
+ readonly message: MessageRef;
65
+ readonly next: Outbound;
66
+ } | {
67
+ readonly _tag: "Post";
68
+ readonly conversation: ConversationRef;
69
+ readonly message: Outbound;
70
+ } | {
71
+ readonly _tag: "React";
72
+ readonly message: MessageRef;
73
+ readonly emoji: string;
74
+ } | {
75
+ readonly _tag: "TypingStart";
76
+ readonly conversation: ConversationRef;
77
+ } | {
78
+ readonly _tag: "TypingStop";
79
+ readonly conversation: ConversationRef;
80
+ }, {
81
+ readonly _tag: Tag;
82
+ }>) => any; }>(cases: Cases): (value: {
83
+ readonly _tag: "Edit";
84
+ readonly message: MessageRef;
85
+ readonly next: Outbound;
86
+ } | {
87
+ readonly _tag: "Post";
88
+ readonly conversation: ConversationRef;
89
+ readonly message: Outbound;
90
+ } | {
91
+ readonly _tag: "React";
92
+ readonly message: MessageRef;
93
+ readonly emoji: string;
94
+ } | {
95
+ readonly _tag: "TypingStart";
96
+ readonly conversation: ConversationRef;
97
+ } | {
98
+ readonly _tag: "TypingStop";
99
+ readonly conversation: ConversationRef;
100
+ }) => import("effect/Unify").Unify<ReturnType<Cases["Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]>>;
101
+ <Cases extends { readonly [Tag in "Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]: (args: Extract<{
102
+ readonly _tag: "Edit";
103
+ readonly message: MessageRef;
104
+ readonly next: Outbound;
105
+ } | {
106
+ readonly _tag: "Post";
107
+ readonly conversation: ConversationRef;
108
+ readonly message: Outbound;
109
+ } | {
110
+ readonly _tag: "React";
111
+ readonly message: MessageRef;
112
+ readonly emoji: string;
113
+ } | {
114
+ readonly _tag: "TypingStart";
115
+ readonly conversation: ConversationRef;
116
+ } | {
117
+ readonly _tag: "TypingStop";
118
+ readonly conversation: ConversationRef;
119
+ }, {
120
+ readonly _tag: Tag;
121
+ }>) => any; }>(value: {
122
+ readonly _tag: "Edit";
123
+ readonly message: MessageRef;
124
+ readonly next: Outbound;
125
+ } | {
126
+ readonly _tag: "Post";
127
+ readonly conversation: ConversationRef;
128
+ readonly message: Outbound;
129
+ } | {
130
+ readonly _tag: "React";
131
+ readonly message: MessageRef;
132
+ readonly emoji: string;
133
+ } | {
134
+ readonly _tag: "TypingStart";
135
+ readonly conversation: ConversationRef;
136
+ } | {
137
+ readonly _tag: "TypingStop";
138
+ readonly conversation: ConversationRef;
139
+ }, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]>>;
140
+ };
141
+ readonly Edit: Data.TaggedEnum.ConstructorFrom<{
142
+ readonly _tag: "Edit";
143
+ readonly message: MessageRef;
144
+ readonly next: Outbound;
145
+ }, "_tag">;
146
+ readonly Post: Data.TaggedEnum.ConstructorFrom<{
147
+ readonly _tag: "Post";
148
+ readonly conversation: ConversationRef;
149
+ readonly message: Outbound;
150
+ }, "_tag">;
151
+ readonly React: Data.TaggedEnum.ConstructorFrom<{
152
+ readonly _tag: "React";
153
+ readonly message: MessageRef;
154
+ readonly emoji: string;
155
+ }, "_tag">;
156
+ readonly TypingStart: Data.TaggedEnum.ConstructorFrom<{
157
+ readonly _tag: "TypingStart";
158
+ readonly conversation: ConversationRef;
159
+ }, "_tag">;
160
+ readonly TypingStop: Data.TaggedEnum.ConstructorFrom<{
161
+ readonly _tag: "TypingStop";
162
+ readonly conversation: ConversationRef;
163
+ }, "_tag">;
164
+ };
165
+ type MockMessengerRecorder = {
166
+ readonly calls: ReadonlyArray<Call>;
167
+ };
168
+ type MockMessengerScript = {
169
+ /** Inbound events to replay. Empty by default. */
170
+ readonly events?: Stream.Stream<InboundEvent, MessengerError>;
171
+ readonly limits?: MessengerLimits;
172
+ /**
173
+ * Failures handed back one per `post` / `edit` call, in order; `undefined`
174
+ * entries succeed, and everything past the end succeeds.
175
+ */
176
+ readonly failures?: ReadonlyArray<MessengerError | undefined>;
177
+ /** Passed through to `streamViaEdits`, which backs `stream`. */
178
+ readonly streamOptions?: StreamViaEditsOptions;
179
+ };
180
+ /** Layer registering the mock against the `Messenger` tag, plus its recorder. */
181
+ declare const layer: (script?: MockMessengerScript) => {
182
+ layer: Layer.Layer<Messenger, never, never>;
183
+ recorder: Effect.Effect<MockMessengerRecorder, never, never>;
184
+ };
185
+ /** Bare service value + recorder. Use with `Effect.provideService`. */
186
+ declare const make: (script?: MockMessengerScript) => {
187
+ service: MessengerService;
188
+ recorder: Effect.Effect<MockMessengerRecorder, never, never>;
189
+ };
190
+ //#endregion
191
+ export { Call, MockMessengerRecorder, MockMessengerScript, layer, make };
192
+ //# sourceMappingURL=MockMessenger.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockMessenger.d.mts","names":[],"sources":["../../src/testing/MockMessenger.ts"],"mappings":";;;;;;;;;;KA4BY,OAAO,KAAK;EACtB;aAAiB,cAAc;aAA0B,SAAS;;EAClE;aAAiB,SAAS;aAAqB,MAAM;;EACrD;aAAkB,SAAS;aAAqB;;EAChD;aAAwB,cAAc;;EACtC;aAAuB,cAAc;;;cAG1B;;;aANe,SAAA;aAA2B,MAAA;;;;;aADtB,cAAA;aAAmC,SAAA;;;;;aAEvC,SAAA;aAA4B;;;;;aACjB,cAAA;;;;;aACD,cAAA;;;;;;;eAHX,SAAA;eAA2B,MAAA;;;eADtB,cAAA;eAAmC,SAAA;;;eAEvC,SAAA;eAA4B;;;eACjB,cAAA;;;eACD,cAAA;;;;;;;;;;;;;;;;;;;;;;;;eAHX,SAAA;eAA2B,MAAA;;;eADtB,cAAA;eAAmC,SAAA;;;eAEvC,SAAA;eAA4B;;;eACjB,cAAA;;;eACD,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;aAHX,SAAA;aAA2B,MAAA;;;;aADtB,cAAA;aAAmC,SAAA;;;;aAEvC,SAAA;aAA4B;;;;aACjB,cAAA;;;;aACD,cAAA;;;KAK3B;WACD,OAAO,cAAc;;KAOpB;;WAED,SAAS,OAAO,OAAO,cAAc;WACrC,SAAS;;;;;WAKT,WAAW,cAAc;;WAEzB,gBAAgB;;;cAoFd,QAAK,SAAY;;;;;cAMjB,OAAI,SAAY"}