@effect-uai/core 0.10.0 → 0.11.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 (134) hide show
  1. package/dist/{Image-BZmKfIdq.d.mts → Image-C4vuBmu0.d.mts} +11 -11
  2. package/dist/{Image-BZmKfIdq.d.mts.map → Image-C4vuBmu0.d.mts.map} +1 -1
  3. package/dist/{Items-BnIduEV2.d.mts → Items-BWNu6VGp.d.mts} +116 -90
  4. package/dist/Items-BWNu6VGp.d.mts.map +1 -0
  5. package/dist/{Tool-2lC_XMth.d.mts → Tool-CXFHCQrT.d.mts} +42 -9
  6. package/dist/Tool-CXFHCQrT.d.mts.map +1 -0
  7. package/dist/{ToolEvent-cWZ2BnbS.d.mts → ToolEvent-nVijCrvm.d.mts} +2 -2
  8. package/dist/{ToolEvent-cWZ2BnbS.d.mts.map → ToolEvent-nVijCrvm.d.mts.map} +1 -1
  9. package/dist/{ToolResult-CL4hK_9z.d.mts → ToolResult-CQqKkcDa.d.mts} +2 -2
  10. package/dist/{ToolResult-CL4hK_9z.d.mts.map → ToolResult-CQqKkcDa.d.mts.map} +1 -1
  11. package/dist/{Toolkit-Dr9l_hNy.d.mts → Toolkit-BYPwwu-j.d.mts} +6 -6
  12. package/dist/Toolkit-BYPwwu-j.d.mts.map +1 -0
  13. package/dist/browser/Browser.mjs +4 -4
  14. package/dist/browser/Browser.mjs.map +1 -1
  15. package/dist/browser/BrowserTool.d.mts +34 -33
  16. package/dist/browser/BrowserTool.d.mts.map +1 -1
  17. package/dist/{dist-DWgRbDN3.mjs → dist-C0m0QnUP.mjs} +71 -30
  18. package/dist/dist-C0m0QnUP.mjs.map +1 -0
  19. package/dist/{dist-B04dlFHg.mjs → dist-CcRV3-A5.mjs} +8 -8
  20. package/dist/{dist-B04dlFHg.mjs.map → dist-CcRV3-A5.mjs.map} +1 -1
  21. package/dist/domain/Citation.d.mts +60 -0
  22. package/dist/domain/Citation.d.mts.map +1 -0
  23. package/dist/domain/Citation.mjs +7 -0
  24. package/dist/domain/Citation.mjs.map +1 -0
  25. package/dist/domain/Image.d.mts +1 -1
  26. package/dist/domain/Items.d.mts +1 -1
  27. package/dist/domain/Items.mjs +13 -3
  28. package/dist/domain/Items.mjs.map +1 -1
  29. package/dist/domain/Research.d.mts +37 -0
  30. package/dist/domain/Research.d.mts.map +1 -0
  31. package/dist/domain/Research.mjs +7 -0
  32. package/dist/domain/Research.mjs.map +1 -0
  33. package/dist/domain/Turn.d.mts +91 -9
  34. package/dist/domain/Turn.d.mts.map +1 -1
  35. package/dist/domain/Turn.mjs +8 -1
  36. package/dist/domain/Turn.mjs.map +1 -1
  37. package/dist/domain/Turn.test.mjs +1 -1
  38. package/dist/embedding-model/Embedding.d.mts +1 -1
  39. package/dist/embedding-model/EmbeddingModel.mjs +2 -2
  40. package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
  41. package/dist/embedding-model/EmbeddingModel.test.mjs +1 -1
  42. package/dist/index.d.mts +12 -8
  43. package/dist/index.mjs +5 -1
  44. package/dist/job/Job.d.mts +80 -0
  45. package/dist/job/Job.d.mts.map +1 -0
  46. package/dist/job/Job.mjs +49 -0
  47. package/dist/job/Job.mjs.map +1 -0
  48. package/dist/language-model/LanguageModel.d.mts +2 -2
  49. package/dist/language-model/LanguageModel.mjs +2 -2
  50. package/dist/language-model/LanguageModel.mjs.map +1 -1
  51. package/dist/language-model/LanguageModel.test.mjs +1 -1
  52. package/dist/loop/Loop.test.mjs +1 -1
  53. package/dist/music-generator/MusicGenerator.mjs +4 -4
  54. package/dist/music-generator/MusicGenerator.mjs.map +1 -1
  55. package/dist/music-generator/MusicGenerator.test.mjs +1 -1
  56. package/dist/observability/Metrics.d.mts +1 -1
  57. package/dist/observability/Metrics.test.mjs +2 -2
  58. package/dist/observability/Telemetry.test.mjs +2 -2
  59. package/dist/research/DeepResearch.d.mts +71 -0
  60. package/dist/research/DeepResearch.d.mts.map +1 -0
  61. package/dist/research/DeepResearch.mjs +61 -0
  62. package/dist/research/DeepResearch.mjs.map +1 -0
  63. package/dist/retry/Retry.test.mjs +1 -1
  64. package/dist/sandbox/Sandbox.mjs +18 -18
  65. package/dist/sandbox/Sandbox.mjs.map +1 -1
  66. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +8 -8
  67. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  68. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +1 -1
  69. package/dist/streaming/JSONL.d.mts +3 -1
  70. package/dist/streaming/JSONL.d.mts.map +1 -1
  71. package/dist/streaming/JSONL.mjs +5 -11
  72. package/dist/streaming/JSONL.mjs.map +1 -1
  73. package/dist/streaming/JSONL.test.mjs +10 -1
  74. package/dist/streaming/JSONL.test.mjs.map +1 -1
  75. package/dist/streaming/SSE.d.mts +5 -3
  76. package/dist/streaming/SSE.d.mts.map +1 -1
  77. package/dist/streaming/SSE.mjs +28 -38
  78. package/dist/streaming/SSE.mjs.map +1 -1
  79. package/dist/streaming/SSE.test.mjs +54 -4
  80. package/dist/streaming/SSE.test.mjs.map +1 -1
  81. package/dist/structured-format/StructuredFormat.test.mjs +1 -1
  82. package/dist/testing/MockProvider.d.mts +1 -1
  83. package/dist/tool/Approval.d.mts +3 -3
  84. package/dist/tool/Approval.test.mjs +1 -1
  85. package/dist/tool/HistoryCheck.d.mts +2 -2
  86. package/dist/tool/Tool.d.mts +2 -2
  87. package/dist/tool/Tool.mjs +27 -7
  88. package/dist/tool/Tool.mjs.map +1 -1
  89. package/dist/tool/Tool.test.mjs +127 -3
  90. package/dist/tool/Tool.test.mjs.map +1 -1
  91. package/dist/tool/ToolEvent.d.mts +1 -1
  92. package/dist/tool/ToolResult.d.mts +1 -1
  93. package/dist/tool/Toolkit.d.mts +1 -1
  94. package/dist/tool/Toolkit.mjs.map +1 -1
  95. package/dist/tool/Toolkit.test.mjs +46 -2
  96. package/dist/tool/Toolkit.test.mjs.map +1 -1
  97. package/dist/transcriber/Transcriber.mjs +3 -3
  98. package/dist/transcriber/Transcriber.mjs.map +1 -1
  99. package/dist/transcriber/Transcriber.test.mjs +1 -1
  100. package/dist/web-read/WebRead.mjs +1 -1
  101. package/dist/web-read/WebRead.mjs.map +1 -1
  102. package/dist/web-read/WebReadTool.d.mts +1 -1
  103. package/dist/web-search/WebSearch.mjs +1 -1
  104. package/dist/web-search/WebSearch.mjs.map +1 -1
  105. package/dist/web-search/WebSearchTool.d.mts +1 -1
  106. package/package.json +19 -3
  107. package/src/browser/Browser.ts +4 -4
  108. package/src/domain/Citation.ts +64 -0
  109. package/src/domain/Items.ts +14 -4
  110. package/src/domain/Research.ts +42 -0
  111. package/src/domain/Turn.ts +26 -0
  112. package/src/embedding-model/EmbeddingModel.ts +2 -2
  113. package/src/index.ts +4 -0
  114. package/src/job/Job.ts +125 -0
  115. package/src/language-model/LanguageModel.ts +2 -2
  116. package/src/music-generator/MusicGenerator.ts +4 -4
  117. package/src/research/DeepResearch.ts +137 -0
  118. package/src/sandbox/Sandbox.ts +18 -18
  119. package/src/speech-synthesizer/SpeechSynthesizer.ts +8 -8
  120. package/src/streaming/JSONL.test.ts +19 -0
  121. package/src/streaming/JSONL.ts +6 -38
  122. package/src/streaming/SSE.test.ts +86 -3
  123. package/src/streaming/SSE.ts +36 -80
  124. package/src/tool/Tool.test.ts +151 -1
  125. package/src/tool/Tool.ts +120 -22
  126. package/src/tool/Toolkit.test.ts +60 -0
  127. package/src/tool/Toolkit.ts +2 -2
  128. package/src/transcriber/Transcriber.ts +3 -3
  129. package/src/web-read/WebRead.ts +1 -1
  130. package/src/web-search/WebSearch.ts +1 -1
  131. package/dist/Items-BnIduEV2.d.mts.map +0 -1
  132. package/dist/Tool-2lC_XMth.d.mts.map +0 -1
  133. package/dist/Toolkit-Dr9l_hNy.d.mts.map +0 -1
  134. package/dist/dist-DWgRbDN3.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SSE.mjs","names":[],"sources":["../../src/streaming/SSE.ts"],"sourcesContent":["import { Stream } from \"effect\"\n\n/**\n * One Server-Sent Event. Fields per the WHATWG spec:\n * - `event`: optional event name (default \"message\" on the wire)\n * - `data`: payload, with multiple `data:` lines joined by `\\n`\n * - `id`: optional last-event id\n */\nexport type Event = {\n readonly event?: string\n readonly data: string\n readonly id?: string\n}\n\n// ---------------------------------------------------------------------------\n// Generic stream helpers (kept module-local for now; promote to a shared\n// Stream module once a third caller appears).\n// ---------------------------------------------------------------------------\n\n/** Decode `Uint8Array` chunks as UTF-8, handling multi-byte boundaries. */\nconst decodeText = <E, R>(self: Stream.Stream<Uint8Array, E, R>): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.mapAccum(\n (): TextDecoder => new TextDecoder(\"utf-8\"),\n (decoder, chunk: Uint8Array) => [decoder, [decoder.decode(chunk, { stream: true })]] as const,\n {\n onHalt: (decoder: TextDecoder) => {\n const tail = decoder.decode()\n return tail.length > 0 ? [tail] : []\n },\n },\n ),\n )\n\n/** Split a text stream on a separator, buffering across chunk boundaries. */\nconst splitOn =\n (separator: string) =>\n <E, R>(self: Stream.Stream<string, E, R>): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.mapAccum(\n (): string => \"\",\n (buffer, chunk: string) => {\n const parts = (buffer + chunk).split(separator)\n const tail = parts[parts.length - 1] ?? \"\"\n return [tail, parts.slice(0, -1)] as const\n },\n { onHalt: (tail: string) => (tail.length > 0 ? [tail] : []) },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Parser\n// ---------------------------------------------------------------------------\n\nconst parseField = (line: string): readonly [string, string] => {\n const colon = line.indexOf(\":\")\n if (colon < 0) return [line, \"\"]\n const value = line.slice(colon + 1)\n return [line.slice(0, colon), value.startsWith(\" \") ? value.slice(1) : value]\n}\n\nconst parseBlock = (block: string): Event | null => {\n const lines = block.split(\"\\n\").filter((l) => l.length > 0 && !l.startsWith(\":\"))\n if (lines.length === 0) return null\n\n const fields = lines.map(parseField)\n const dataLines = fields.filter(([f]) => f === \"data\").map(([, v]) => v)\n const event = fields.find(([f]) => f === \"event\")?.[1]\n const id = fields.find(([f]) => f === \"id\")?.[1]\n\n const out: { event?: string; data: string; id?: string } = {\n data: dataLines.join(\"\\n\"),\n }\n if (event !== undefined) out.event = event\n if (id !== undefined) out.id = id\n return out as Event\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a\n * `Stream<SSE.Event>`. Handles partial UTF-8 sequences, CRLF/LF line\n * endings, and events split across chunk boundaries.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Event, E, R> =>\n self.pipe(\n decodeText,\n Stream.map((s) => s.replace(/\\r/g, \"\")), // SSE allows CRLF; normalize to LF\n splitOn(\"\\n\\n\"),\n Stream.map(parseBlock),\n Stream.filter((ev): ev is Event => ev !== null),\n )\n\nconst eventToString = (ev: Event): string => {\n const parts: string[] = []\n if (ev.event !== undefined) parts.push(`event: ${ev.event}`)\n if (ev.id !== undefined) parts.push(`id: ${ev.id}`)\n for (const line of ev.data.split(\"\\n\")) parts.push(`data: ${line}`)\n return parts.join(\"\\n\") + \"\\n\\n\"\n}\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a `Stream<Event>` as `Stream<Uint8Array>` ready to send on an\n * HTTP response with `Content-Type: text/event-stream`.\n */\nexport const toBytes = <E, R>(self: Stream.Stream<Event, E, R>): Stream.Stream<Uint8Array, E, R> =>\n Stream.map(self, (ev) => encoder.encode(eventToString(ev)))\n"],"mappings":";;;;;;;;AAoBA,MAAM,cAAoB,SACxB,KAAK,KACH,OAAO,eACc,IAAI,YAAY,QAAQ,GAC1C,SAAS,UAAsB,CAAC,SAAS,CAAC,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,EACpF,EACE,SAAS,YAAyB;CAChC,MAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAO,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,EAAE;GAEvC,CACF,CACF;;AAGH,MAAM,WACH,eACM,SACL,KAAK,KACH,OAAO,eACS,KACb,QAAQ,UAAkB;CACzB,MAAM,SAAS,SAAS,OAAO,MAAM,UAAU;AAE/C,QAAO,CADM,MAAM,MAAM,SAAS,MAAM,IAC1B,MAAM,MAAM,GAAG,GAAG,CAAC;GAEnC,EAAE,SAAS,SAAkB,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,EAAE,EAAG,CAC9D,CACF;AAML,MAAM,cAAc,SAA4C;CAC9D,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,KAAI,QAAQ,EAAG,QAAO,CAAC,MAAM,GAAG;CAChC,MAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE;AACnC,QAAO,CAAC,KAAK,MAAM,GAAG,MAAM,EAAE,MAAM,WAAW,IAAI,GAAG,MAAM,MAAM,EAAE,GAAG,MAAM;;AAG/E,MAAM,cAAc,UAAgC;CAClD,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,EAAE,WAAW,IAAI,CAAC;AACjF,KAAI,MAAM,WAAW,EAAG,QAAO;CAE/B,MAAM,SAAS,MAAM,IAAI,WAAW;CACpC,MAAM,YAAY,OAAO,QAAQ,CAAC,OAAO,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,EAAE;CACxE,MAAM,QAAQ,OAAO,MAAM,CAAC,OAAO,MAAM,QAAQ,GAAG;CACpD,MAAM,KAAK,OAAO,MAAM,CAAC,OAAO,MAAM,KAAK,GAAG;CAE9C,MAAM,MAAqD,EACzD,MAAM,UAAU,KAAK,KAAK,EAC3B;AACD,KAAI,UAAU,KAAA,EAAW,KAAI,QAAQ;AACrC,KAAI,OAAO,KAAA,EAAW,KAAI,KAAK;AAC/B,QAAO;;;;;;;AAYT,MAAa,aACX,SAEA,KAAK,KACH,YACA,OAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,CAAC,EACvC,QAAQ,OAAO,EACf,OAAO,IAAI,WAAW,EACtB,OAAO,QAAQ,OAAoB,OAAO,KAAK,CAChD;AAEH,MAAM,iBAAiB,OAAsB;CAC3C,MAAM,QAAkB,EAAE;AAC1B,KAAI,GAAG,UAAU,KAAA,EAAW,OAAM,KAAK,UAAU,GAAG,QAAQ;AAC5D,KAAI,GAAG,OAAO,KAAA,EAAW,OAAM,KAAK,OAAO,GAAG,KAAK;AACnD,MAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,KAAK,CAAE,OAAM,KAAK,SAAS,OAAO;AACnE,QAAO,MAAM,KAAK,KAAK,GAAG;;AAG5B,MAAM,UAAU,IAAI,aAAa;;;;;AAMjC,MAAa,WAAiB,SAC5B,OAAO,IAAI,OAAO,OAAO,QAAQ,OAAO,cAAc,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"SSE.mjs","names":[],"sources":["../../src/streaming/SSE.ts"],"sourcesContent":["import { Stream } from \"effect\"\nimport { Sse } from \"effect/unstable/encoding\"\n\n/**\n * One Server-Sent Event. Fields per the WHATWG spec:\n * - `event`: optional event name (absent for the default \"message\")\n * - `data`: payload, with multiple `data:` lines joined by `\\n`\n * - `id`: optional last-event id\n */\nexport type Event = {\n readonly event?: string\n readonly data: string\n readonly id?: string\n}\n\nconst toEvent = (wire: Sse.Event): Event => ({\n data: wire.data,\n ...(wire.event !== \"message\" && { event: wire.event }),\n ...(wire.id !== undefined && { id: wire.id }),\n})\n\ntype ParserState = {\n readonly parser: Sse.Parser\n readonly out: Array<Event>\n}\n\nconst makeParserState = (): ParserState => {\n const out: Array<Event> = []\n const parser = Sse.makeParser((event) => {\n // `retry:` reconnection hints have no representation in `Event`\n if (event._tag === \"Event\") out.push(toEvent(event))\n })\n return { parser, out }\n}\n\n/**\n * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a\n * `Stream<SSE.Event>`, using the spec parser from `effect/unstable/encoding`.\n * An event is dispatched only once its terminating blank line arrives, so a\n * partial event at stream end (clean or failed) is discarded per the spec\n * rather than surfaced as a truncated frame.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Event, E, R> =>\n self.pipe(\n Stream.decodeText,\n Stream.mapAccum(makeParserState, (state, text: string) => {\n state.parser.feed(text)\n return [state, state.out.splice(0)] as const\n }),\n )\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a `Stream<Event>` as `Stream<Uint8Array>` ready to send on an\n * HTTP response with `Content-Type: text/event-stream`.\n */\nexport const toBytes = <E, R>(self: Stream.Stream<Event, E, R>): Stream.Stream<Uint8Array, E, R> =>\n Stream.map(self, (ev) =>\n encoder.encode(\n Sse.encoder.write({\n _tag: \"Event\",\n event: ev.event ?? \"message\",\n id: ev.id,\n data: ev.data,\n }),\n ),\n )\n"],"mappings":";;;;;;;;AAeA,MAAM,WAAW,UAA4B;CAC3C,MAAM,KAAK;CACX,GAAI,KAAK,UAAU,aAAa,EAAE,OAAO,KAAK,OAAO;CACrD,GAAI,KAAK,OAAO,KAAA,KAAa,EAAE,IAAI,KAAK,IAAI;CAC7C;AAOD,MAAM,wBAAqC;CACzC,MAAM,MAAoB,EAAE;AAK5B,QAAO;EAAE,QAJM,IAAI,YAAY,UAAU;AAEvC,OAAI,MAAM,SAAS,QAAS,KAAI,KAAK,QAAQ,MAAM,CAAC;IAEvC;EAAE;EAAK;;;;;;;;;AAUxB,MAAa,aACX,SAEA,KAAK,KACH,OAAO,YACP,OAAO,SAAS,kBAAkB,OAAO,SAAiB;AACxD,OAAM,OAAO,KAAK,KAAK;AACvB,QAAO,CAAC,OAAO,MAAM,IAAI,OAAO,EAAE,CAAC;EACnC,CACH;AAEH,MAAM,UAAU,IAAI,aAAa;;;;;AAMjC,MAAa,WAAiB,SAC5B,OAAO,IAAI,OAAO,OAChB,QAAQ,OACN,IAAI,QAAQ,MAAM;CAChB,MAAM;CACN,OAAO,GAAG,SAAS;CACnB,IAAI,GAAG;CACP,MAAM,GAAG;CACV,CAAC,CACH,CACF"}
@@ -1,10 +1,15 @@
1
1
  import { fromBytes, toBytes } from "./SSE.mjs";
2
- import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
3
- import { Effect, Stream } from "effect";
2
+ import { c as it, n as globalExpect, o as describe } from "../dist-CcRV3-A5.mjs";
3
+ import { Cause, Deferred, Effect, Exit, Fiber, Result, Stream } from "effect";
4
4
  //#region src/streaming/SSE.test.ts
5
5
  const enc = new TextEncoder();
6
6
  const bytesOf = (...chunks) => Stream.fromIterable(chunks.map((c) => enc.encode(c)));
7
7
  const collect = (s) => Effect.runPromise(Stream.runCollect(s));
8
+ const runUntilEnd = (s) => Effect.runPromise(s.pipe(Stream.result, Stream.runCollect, Effect.map((results) => ({
9
+ seen: results.filter(Result.isSuccess).map((r) => r.success),
10
+ failures: results.filter(Result.isFailure).map((r) => r.failure)
11
+ }))));
12
+ const failuresOf = (exit) => Exit.isFailure(exit) ? exit.cause.reasons.filter(Cause.isFailReason).map((r) => r.error) : [];
8
13
  describe("SSE.fromBytes", () => {
9
14
  it("parses a single complete event", async () => {
10
15
  globalExpect(await collect(fromBytes(bytesOf("event: foo\ndata: hello\n\n")))).toEqual([{
@@ -36,8 +41,53 @@ describe("SSE.fromBytes", () => {
36
41
  data: "x"
37
42
  }]);
38
43
  });
39
- it("flushes a trailing event without a closing blank line", async () => {
40
- globalExpect(await collect(fromBytes(bytesOf("data: tail")))).toEqual([{ data: "tail" }]);
44
+ it("parses a data field with no space after the colon", async () => {
45
+ globalExpect(await collect(fromBytes(bytesOf("data:x\n\n")))).toEqual([{ data: "x" }]);
46
+ });
47
+ it("ignores an id containing a NUL character", async () => {
48
+ globalExpect(await collect(fromBytes(bytesOf("id: a\0b\ndata: x\n\n")))).toEqual([{ data: "x" }]);
49
+ });
50
+ it("discards a trailing event without a closing blank line", async () => {
51
+ globalExpect(await collect(fromBytes(bytesOf("data: tail")))).toEqual([]);
52
+ });
53
+ it("discards buffered partial data when the upstream fails mid-stream", async () => {
54
+ const { seen, failures } = await runUntilEnd(fromBytes(Stream.concat(bytesOf("data: good\n\ndata: partial-tr"), Stream.fail("boom"))));
55
+ globalExpect(seen).toEqual([{ data: "good" }]);
56
+ globalExpect(failures).toEqual(["boom"]);
57
+ });
58
+ it("discards a partial multi-byte char in the text decoder when the upstream fails", async () => {
59
+ const partialSquid = enc.encode("data: 🦑\n\n").slice(0, 8);
60
+ const { seen, failures } = await runUntilEnd(fromBytes(Stream.concat(Stream.fromIterable([enc.encode("data: ok\n\n"), partialSquid]), Stream.fail("boom"))));
61
+ globalExpect(seen).toEqual([{ data: "ok" }]);
62
+ globalExpect(failures).toEqual(["boom"]);
63
+ });
64
+ it("discards buffered partial data when the stream is interrupted", async () => {
65
+ const { seen, exit } = await Effect.runPromise(Effect.gen(function* () {
66
+ const seen = [];
67
+ const latch = yield* Deferred.make();
68
+ const hanging = Stream.concat(bytesOf("data: good\n\ndata: partial"), Stream.never);
69
+ const fiber = yield* Stream.runForEach(fromBytes(hanging), (e) => Effect.sync(() => {
70
+ seen.push(e);
71
+ }).pipe(Effect.andThen(Deferred.succeed(latch, void 0)))).pipe(Effect.forkChild);
72
+ yield* Deferred.await(latch);
73
+ yield* Fiber.interrupt(fiber);
74
+ return {
75
+ seen,
76
+ exit: yield* Fiber.await(fiber)
77
+ };
78
+ }));
79
+ globalExpect(seen).toEqual([{ data: "good" }]);
80
+ globalExpect(Exit.isFailure(exit)).toBe(true);
81
+ globalExpect(failuresOf(exit)).toEqual([]);
82
+ });
83
+ it("ignores retry directives", async () => {
84
+ globalExpect(await collect(fromBytes(bytesOf("retry: 3000\n\ndata: a\n\n")))).toEqual([{ data: "a" }]);
85
+ });
86
+ it("treats an explicit 'event: message' as the default event", async () => {
87
+ globalExpect(await collect(fromBytes(bytesOf("event: message\ndata: x\n\n")))).toEqual([{ data: "x" }]);
88
+ });
89
+ it("does not dispatch events with no data", async () => {
90
+ globalExpect(await collect(fromBytes(bytesOf("event: ping\n\ndata: x\n\n")))).toEqual([{ data: "x" }]);
41
91
  });
42
92
  it("ignores empty blocks between events", async () => {
43
93
  globalExpect(await collect(fromBytes(bytesOf("data: a\n\n\n\ndata: b\n\n")))).toEqual([{ data: "a" }, { data: "b" }]);
@@ -1 +1 @@
1
- {"version":3,"file":"SSE.test.mjs","names":["SSE.fromBytes","SSE.toBytes"],"sources":["../../src/streaming/SSE.test.ts"],"sourcesContent":["import { Effect, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as SSE from \"./SSE.js\"\n\nconst enc = new TextEncoder()\nconst bytesOf = (...chunks: ReadonlyArray<string>) =>\n Stream.fromIterable(chunks.map((c) => enc.encode(c)))\n\nconst collect = <A, E>(s: Stream.Stream<A, E>) => Effect.runPromise(Stream.runCollect(s))\n\ndescribe(\"SSE.fromBytes\", () => {\n it(\"parses a single complete event\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: foo\\ndata: hello\\n\\n\")))\n expect(out).toEqual([{ event: \"foo\", data: \"hello\" }])\n })\n\n it(\"joins multiple data lines with \\\\n\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: line1\\ndata: line2\\ndata: line3\\n\\n\")))\n expect(out).toEqual([{ data: \"line1\\nline2\\nline3\" }])\n })\n\n it(\"handles events split across chunk boundaries\", async () => {\n const out = await collect(\n SSE.fromBytes(bytesOf(\"event: split\\nda\", \"ta: hi\\n\", \"\\nevent: next\\ndata: x\\n\\n\")),\n )\n expect(out).toEqual([\n { event: \"split\", data: \"hi\" },\n { event: \"next\", data: \"x\" },\n ])\n })\n\n it(\"handles CRLF line endings\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: a\\r\\ndata: b\\r\\n\\r\\n\")))\n expect(out).toEqual([{ event: \"a\", data: \"b\" }])\n })\n\n it(\"preserves id and skips comment lines\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\": ping\\nid: 42\\ndata: x\\n\\n\")))\n expect(out).toEqual([{ id: \"42\", data: \"x\" }])\n })\n\n it(\"flushes a trailing event without a closing blank line\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: tail\")))\n expect(out).toEqual([{ data: \"tail\" }])\n })\n\n it(\"ignores empty blocks between events\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: a\\n\\n\\n\\ndata: b\\n\\n\")))\n expect(out).toEqual([{ data: \"a\" }, { data: \"b\" }])\n })\n\n it(\"handles a UTF-8 multi-byte char split across chunks\", async () => {\n // \"🦑\" is 0xF0 0x9F 0xA6 0x91. Split between bytes 2 and 3.\n const squidBytes = enc.encode(\"data: 🦑\\n\\n\")\n const a = squidBytes.slice(0, 8) // \"data: \" + first 2 bytes of squid\n const b = squidBytes.slice(8) // remaining squid bytes + \"\\n\\n\"\n const out = await collect(SSE.fromBytes(Stream.fromIterable([a, b])))\n expect(out).toEqual([{ data: \"🦑\" }])\n })\n})\n\ndescribe(\"SSE.toBytes round-trip\", () => {\n it(\"re-parses what it serializes\", async () => {\n const events: ReadonlyArray<SSE.Event> = [\n { event: \"a\", data: \"hello\" },\n { data: \"multi\\nline\" },\n { event: \"b\", id: \"7\", data: \"x\" },\n ]\n const reparsed = await collect(Stream.fromIterable(events).pipe(SSE.toBytes, SSE.fromBytes))\n expect(reparsed).toEqual(events)\n })\n})\n"],"mappings":";;;;AAIA,MAAM,MAAM,IAAI,aAAa;AAC7B,MAAM,WAAW,GAAG,WAClB,OAAO,aAAa,OAAO,KAAK,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AAEvD,MAAM,WAAiB,MAA2B,OAAO,WAAW,OAAO,WAAW,EAAE,CAAC;AAEzF,SAAS,uBAAuB;AAC9B,IAAG,kCAAkC,YAAY;AAE/C,eAAO,MADW,QAAQA,UAAc,QAAQ,8BAA8B,CAAC,CAAC,CACrE,CAAC,QAAQ,CAAC;GAAE,OAAO;GAAO,MAAM;GAAS,CAAC,CAAC;GACtD;AAEF,IAAG,sCAAsC,YAAY;AAEnD,eAAO,MADW,QAAQA,UAAc,QAAQ,4CAA4C,CAAC,CAAC,CACnF,CAAC,QAAQ,CAAC,EAAE,MAAM,uBAAuB,CAAC,CAAC;GACtD;AAEF,IAAG,gDAAgD,YAAY;AAI7D,eAAO,MAHW,QAChBA,UAAc,QAAQ,oBAAoB,YAAY,6BAA6B,CAAC,CACrF,CACU,CAAC,QAAQ,CAClB;GAAE,OAAO;GAAS,MAAM;GAAM,EAC9B;GAAE,OAAO;GAAQ,MAAM;GAAK,CAC7B,CAAC;GACF;AAEF,IAAG,6BAA6B,YAAY;AAE1C,eAAO,MADW,QAAQA,UAAc,QAAQ,8BAA8B,CAAC,CAAC,CACrE,CAAC,QAAQ,CAAC;GAAE,OAAO;GAAK,MAAM;GAAK,CAAC,CAAC;GAChD;AAEF,IAAG,wCAAwC,YAAY;AAErD,eAAO,MADW,QAAQA,UAAc,QAAQ,8BAA8B,CAAC,CAAC,CACrE,CAAC,QAAQ,CAAC;GAAE,IAAI;GAAM,MAAM;GAAK,CAAC,CAAC;GAC9C;AAEF,IAAG,yDAAyD,YAAY;AAEtE,eAAO,MADW,QAAQA,UAAc,QAAQ,aAAa,CAAC,CAAC,CACpD,CAAC,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,CAAC;GACvC;AAEF,IAAG,uCAAuC,YAAY;AAEpD,eAAO,MADW,QAAQA,UAAc,QAAQ,6BAA6B,CAAC,CAAC,CACpE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,EAAE,EAAE,MAAM,KAAK,CAAC,CAAC;GACnD;AAEF,IAAG,uDAAuD,YAAY;EAEpE,MAAM,aAAa,IAAI,OAAO,eAAe;EAC7C,MAAM,IAAI,WAAW,MAAM,GAAG,EAAE;EAChC,MAAM,IAAI,WAAW,MAAM,EAAE;AAE7B,eAAO,MADW,QAAQA,UAAc,OAAO,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAC1D,CAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;GACrC;EACF;AAEF,SAAS,gCAAgC;AACvC,IAAG,gCAAgC,YAAY;EAC7C,MAAM,SAAmC;GACvC;IAAE,OAAO;IAAK,MAAM;IAAS;GAC7B,EAAE,MAAM,eAAe;GACvB;IAAE,OAAO;IAAK,IAAI;IAAK,MAAM;IAAK;GACnC;AAED,eAAO,MADgB,QAAQ,OAAO,aAAa,OAAO,CAAC,KAAKC,SAAaD,UAAc,CAAC,CAC5E,CAAC,QAAQ,OAAO;GAChC;EACF"}
1
+ {"version":3,"file":"SSE.test.mjs","names":["SSE.fromBytes","SSE.toBytes"],"sources":["../../src/streaming/SSE.test.ts"],"sourcesContent":["import { Cause, Deferred, Effect, Exit, Fiber, Result, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as SSE from \"./SSE.js\"\n\nconst enc = new TextEncoder()\nconst bytesOf = (...chunks: ReadonlyArray<string>) =>\n Stream.fromIterable(chunks.map((c) => enc.encode(c)))\n\nconst collect = <A, E>(s: Stream.Stream<A, E>) => Effect.runPromise(Stream.runCollect(s))\n\nconst runUntilEnd = <A, E>(s: Stream.Stream<A, E>) =>\n Effect.runPromise(\n s.pipe(\n Stream.result,\n Stream.runCollect,\n Effect.map((results) => ({\n seen: results.filter(Result.isSuccess).map((r) => r.success),\n failures: results.filter(Result.isFailure).map((r) => r.failure),\n })),\n ),\n )\n\nconst failuresOf = (exit: Exit.Exit<unknown, unknown>): ReadonlyArray<unknown> =>\n Exit.isFailure(exit) ? exit.cause.reasons.filter(Cause.isFailReason).map((r) => r.error) : []\n\ndescribe(\"SSE.fromBytes\", () => {\n it(\"parses a single complete event\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: foo\\ndata: hello\\n\\n\")))\n expect(out).toEqual([{ event: \"foo\", data: \"hello\" }])\n })\n\n it(\"joins multiple data lines with \\\\n\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: line1\\ndata: line2\\ndata: line3\\n\\n\")))\n expect(out).toEqual([{ data: \"line1\\nline2\\nline3\" }])\n })\n\n it(\"handles events split across chunk boundaries\", async () => {\n const out = await collect(\n SSE.fromBytes(bytesOf(\"event: split\\nda\", \"ta: hi\\n\", \"\\nevent: next\\ndata: x\\n\\n\")),\n )\n expect(out).toEqual([\n { event: \"split\", data: \"hi\" },\n { event: \"next\", data: \"x\" },\n ])\n })\n\n it(\"handles CRLF line endings\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: a\\r\\ndata: b\\r\\n\\r\\n\")))\n expect(out).toEqual([{ event: \"a\", data: \"b\" }])\n })\n\n it(\"preserves id and skips comment lines\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\": ping\\nid: 42\\ndata: x\\n\\n\")))\n expect(out).toEqual([{ id: \"42\", data: \"x\" }])\n })\n\n it(\"parses a data field with no space after the colon\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data:x\\n\\n\")))\n expect(out).toEqual([{ data: \"x\" }])\n })\n\n it(\"ignores an id containing a NUL character\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"id: a\\u0000b\\ndata: x\\n\\n\")))\n expect(out).toEqual([{ data: \"x\" }])\n })\n\n it(\"discards a trailing event without a closing blank line\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: tail\")))\n expect(out).toEqual([])\n })\n\n it(\"discards buffered partial data when the upstream fails mid-stream\", async () => {\n const failing = Stream.concat(\n bytesOf(\"data: good\\n\\ndata: partial-tr\"),\n Stream.fail(\"boom\" as const),\n )\n const { seen, failures } = await runUntilEnd(SSE.fromBytes(failing))\n expect(seen).toEqual([{ data: \"good\" }])\n expect(failures).toEqual([\"boom\"])\n })\n\n it(\"discards a partial multi-byte char in the text decoder when the upstream fails\", async () => {\n const partialSquid = enc.encode(\"data: 🦑\\n\\n\").slice(0, 8)\n const failing = Stream.concat(\n Stream.fromIterable([enc.encode(\"data: ok\\n\\n\"), partialSquid]),\n Stream.fail(\"boom\" as const),\n )\n const { seen, failures } = await runUntilEnd(SSE.fromBytes(failing))\n expect(seen).toEqual([{ data: \"ok\" }])\n expect(failures).toEqual([\"boom\"])\n })\n\n it(\"discards buffered partial data when the stream is interrupted\", async () => {\n const { seen, exit } = await Effect.runPromise(\n Effect.gen(function* () {\n const seen: Array<SSE.Event> = []\n const latch = yield* Deferred.make<void>()\n const hanging = Stream.concat(bytesOf(\"data: good\\n\\ndata: partial\"), Stream.never)\n const fiber = yield* Stream.runForEach(SSE.fromBytes(hanging), (e) =>\n Effect.sync(() => {\n seen.push(e)\n }).pipe(Effect.andThen(Deferred.succeed(latch, void 0))),\n ).pipe(Effect.forkChild)\n yield* Deferred.await(latch)\n yield* Fiber.interrupt(fiber)\n const exit = yield* Fiber.await(fiber)\n return { seen, exit }\n }),\n )\n expect(seen).toEqual([{ data: \"good\" }])\n expect(Exit.isFailure(exit)).toBe(true)\n expect(failuresOf(exit)).toEqual([])\n })\n\n it(\"ignores retry directives\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"retry: 3000\\n\\ndata: a\\n\\n\")))\n expect(out).toEqual([{ data: \"a\" }])\n })\n\n it(\"treats an explicit 'event: message' as the default event\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: message\\ndata: x\\n\\n\")))\n expect(out).toEqual([{ data: \"x\" }])\n })\n\n it(\"does not dispatch events with no data\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: ping\\n\\ndata: x\\n\\n\")))\n expect(out).toEqual([{ data: \"x\" }])\n })\n\n it(\"ignores empty blocks between events\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: a\\n\\n\\n\\ndata: b\\n\\n\")))\n expect(out).toEqual([{ data: \"a\" }, { data: \"b\" }])\n })\n\n it(\"handles a UTF-8 multi-byte char split across chunks\", async () => {\n // \"🦑\" is 0xF0 0x9F 0xA6 0x91. Split between bytes 2 and 3.\n const squidBytes = enc.encode(\"data: 🦑\\n\\n\")\n const a = squidBytes.slice(0, 8) // \"data: \" + first 2 bytes of squid\n const b = squidBytes.slice(8) // remaining squid bytes + \"\\n\\n\"\n const out = await collect(SSE.fromBytes(Stream.fromIterable([a, b])))\n expect(out).toEqual([{ data: \"🦑\" }])\n })\n})\n\ndescribe(\"SSE.toBytes round-trip\", () => {\n it(\"re-parses what it serializes\", async () => {\n const events: ReadonlyArray<SSE.Event> = [\n { event: \"a\", data: \"hello\" },\n { data: \"multi\\nline\" },\n { event: \"b\", id: \"7\", data: \"x\" },\n ]\n const reparsed = await collect(Stream.fromIterable(events).pipe(SSE.toBytes, SSE.fromBytes))\n expect(reparsed).toEqual(events)\n })\n})\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { JsonParseError, StructuredDecodeError, decodeJsonLines, decodeJsonLinesRecoverable, fromEffectSchema } from "./StructuredFormat.mjs";
2
- import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
2
+ import { c as it, n as globalExpect, o as describe } from "../dist-CcRV3-A5.mjs";
3
3
  import { Effect, Exit, Filter, Result, Schema, Stream } from "effect";
4
4
  //#region src/structured-format/StructuredFormat.test.ts
5
5
  const itemFormat = fromEffectSchema(Schema.Struct({
@@ -1,4 +1,4 @@
1
- import { o as HistoryItem } from "../Items-BnIduEV2.mjs";
1
+ import { o as HistoryItem } from "../Items-BWNu6VGp.mjs";
2
2
  import { Turn } from "../domain/Turn.mjs";
3
3
  import { LanguageModel, LanguageModelService } from "../language-model/LanguageModel.mjs";
4
4
  import { Duration, Effect, Layer } from "effect";
@@ -1,6 +1,6 @@
1
- import { v as ToolCall } from "../Items-BnIduEV2.mjs";
2
- import { t as ToolResult } from "../ToolResult-CL4hK_9z.mjs";
3
- import { t as ToolEvent } from "../ToolEvent-cWZ2BnbS.mjs";
1
+ import { v as ToolCall } from "../Items-BWNu6VGp.mjs";
2
+ import { t as ToolResult } from "../ToolResult-CQqKkcDa.mjs";
3
+ import { t as ToolEvent } from "../ToolEvent-nVijCrvm.mjs";
4
4
  import { Data, Duration, Effect, Queue, Scope, Stream } from "effect";
5
5
  import * as _$effect_Unify0 from "effect/Unify";
6
6
 
@@ -5,7 +5,7 @@ import { ToolEvent, isApprovalRequested, isOutput, isProgress } from "./ToolEven
5
5
  import { make as make$1, run } from "./Toolkit.mjs";
6
6
  import { fromMap, fromQueue } from "./Approval.mjs";
7
7
  import { cancelAllPending, findUnansweredCalls, isReconciled } from "./HistoryCheck.mjs";
8
- import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
8
+ import { c as it, n as globalExpect, o as describe } from "../dist-CcRV3-A5.mjs";
9
9
  import { Effect, Queue, Schema, Stream } from "effect";
10
10
  //#region src/tool/Approval.test.ts
11
11
  /**
@@ -1,5 +1,5 @@
1
- import { o as HistoryItem, v as ToolCall } from "../Items-BnIduEV2.mjs";
2
- import { t as ToolResult } from "../ToolResult-CL4hK_9z.mjs";
1
+ import { o as HistoryItem, v as ToolCall } from "../Items-BWNu6VGp.mjs";
2
+ import { t as ToolResult } from "../ToolResult-CQqKkcDa.mjs";
3
3
 
4
4
  //#region src/tool/HistoryCheck.d.ts
5
5
  declare namespace HistoryCheck_d_exports {
@@ -1,2 +1,2 @@
1
- import { A as toDescriptors, C as fromEffectSchema, D as noInput, E as make, M as withRun, O as provider, S as fail, T as interaction, a as Emit, b as descriptorsOf, c as SignalTool, d as ToolE, f as ToolError, g as ToolValidationError, h as ToolR, i as DecodeResult, j as withName, k as signal, l as Tool, m as ToolInputSchema, n as AnyTool, o as InteractionTool, p as ToolFailed, r as DecodableTool, s as ProviderTool, t as AnyLocalTool, u as ToolDescriptor, v as decodeArgs, w as fromStandardSchema, x as execute, y as decodeCallInput } from "../Tool-2lC_XMth.mjs";
2
- export { AnyLocalTool, AnyTool, DecodableTool, DecodeResult, Emit, InteractionTool, ProviderTool, SignalTool, Tool, ToolDescriptor, ToolE, ToolError, ToolFailed, ToolInputSchema, ToolR, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, make, noInput, provider, signal, toDescriptors, withName, withRun };
1
+ import { A as providerToolsOf, C as fromEffectSchema, D as make, E as isProviderTool, M as toDescriptors, N as withName, O as noInput, P as withRun, S as fail, T as interaction, a as Emit, b as descriptorsOf, c as SignalTool, d as ToolE, f as ToolError, g as ToolValidationError, h as ToolR, i as DecodeResult, j as signal, k as provider, l as Tool, m as ToolInputSchema, n as AnyTool, o as InteractionTool, p as ToolFailed, r as DecodableTool, s as ProviderTool, t as AnyLocalTool, u as ToolDescriptor, v as decodeArgs, w as fromStandardSchema, x as execute, y as decodeCallInput } from "../Tool-CXFHCQrT.mjs";
2
+ export { AnyLocalTool, AnyTool, DecodableTool, DecodeResult, Emit, InteractionTool, ProviderTool, SignalTool, Tool, ToolDescriptor, ToolE, ToolError, ToolFailed, ToolInputSchema, ToolR, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, isProviderTool, make, noInput, provider, providerToolsOf, signal, toDescriptors, withName, withRun };
@@ -15,9 +15,11 @@ var Tool_exports = /* @__PURE__ */ __exportAll({
15
15
  fromEffectSchema: () => fromEffectSchema,
16
16
  fromStandardSchema: () => fromStandardSchema,
17
17
  interaction: () => interaction,
18
+ isProviderTool: () => isProviderTool,
18
19
  make: () => make,
19
20
  noInput: () => noInput,
20
21
  provider: () => provider,
22
+ providerToolsOf: () => providerToolsOf,
21
23
  signal: () => signal,
22
24
  toDescriptors: () => toDescriptors,
23
25
  withName: () => withName,
@@ -115,10 +117,12 @@ const noInput = { "~standard": {
115
117
  })
116
118
  }
117
119
  } };
120
+ const resolveInputSchema = (schema) => Schema.isSchema(schema) ? fromEffectSchema(schema) : schema;
118
121
  /** A model-visible tool with a local `Effect` executor. */
119
122
  const make = (spec) => ({
120
123
  _tag: "LocalTool",
121
- ...spec
124
+ ...spec,
125
+ inputSchema: resolveInputSchema(spec.inputSchema)
122
126
  });
123
127
  /**
124
128
  * A provider-hosted tool (native web search, code execution, RAG grounding).
@@ -127,7 +131,8 @@ const make = (spec) => ({
127
131
  */
128
132
  const provider = (spec) => ({
129
133
  _tag: "ProviderTool",
130
- ...spec
134
+ ...spec,
135
+ inputSchema: resolveInputSchema(spec.inputSchema)
131
136
  });
132
137
  /**
133
138
  * A typed control-flow signal to the agent loop (escalate, pause, schedule,
@@ -136,7 +141,8 @@ const provider = (spec) => ({
136
141
  */
137
142
  const signal = (spec) => ({
138
143
  _tag: "SignalTool",
139
- ...spec
144
+ ...spec,
145
+ inputSchema: resolveInputSchema(spec.inputSchema)
140
146
  });
141
147
  /**
142
148
  * A typed request for an external actor to respond before the loop resumes
@@ -145,7 +151,8 @@ const signal = (spec) => ({
145
151
  */
146
152
  const interaction = (spec) => ({
147
153
  _tag: "InteractionTool",
148
- ...spec
154
+ ...spec,
155
+ inputSchema: resolveInputSchema(spec.inputSchema)
149
156
  });
150
157
  /**
151
158
  * Return a copy of a tool under a new `name`. Useful for resolving a name
@@ -171,8 +178,21 @@ const withRun = (tool, run) => ({
171
178
  ...tool,
172
179
  run
173
180
  });
174
- /** Render tools to provider-agnostic descriptors. Every kind is model-visible. */
175
- const toDescriptors = (tools) => tools.map((tool) => {
181
+ /** True for provider-hosted tools — the kind a provider renders natively. */
182
+ const isProviderTool = (tool) => tool._tag === "ProviderTool";
183
+ /**
184
+ * The `ProviderTool`s in a toolkit, for a provider adapter to render as its
185
+ * native hosted-tool entries (web search, code execution, grounding). The other
186
+ * kinds render as function declarations via `descriptorsOf`.
187
+ */
188
+ const providerToolsOf = (toolkit) => toolkit === void 0 ? [] : Object.values(toolkit).filter(isProviderTool);
189
+ /**
190
+ * Render tools to provider-agnostic descriptors. `ProviderTool`s are excluded:
191
+ * they are provider-hosted and must never leak into the function-declaration
192
+ * list — each adapter renders them natively via `providerToolsOf`. Every other
193
+ * kind is model-visible as a declaration.
194
+ */
195
+ const toDescriptors = (tools) => tools.filter((tool) => !isProviderTool(tool)).map((tool) => {
176
196
  const inputSchema = tool.inputSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
177
197
  const strict = "strict" in tool ? tool.strict : void 0;
178
198
  return strict !== void 0 ? {
@@ -245,6 +265,6 @@ const decodeArgs = (tool, call) => decodeCallInput(tool, call).pipe(Effect.flatM
245
265
  */
246
266
  const execute = (tool, call) => decodeArgs(tool, call).pipe(Effect.flatMap((input) => tool.run(input, () => Effect.void)), Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))));
247
267
  //#endregion
248
- export { ToolError, ToolFailed, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, make, noInput, provider, signal, Tool_exports as t, toDescriptors, withName, withRun };
268
+ export { ToolError, ToolFailed, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, isProviderTool, make, noInput, provider, providerToolsOf, signal, Tool_exports as t, toDescriptors, withName, withRun };
249
269
 
250
270
  //# sourceMappingURL=Tool.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tool.mjs","names":[],"sources":["../../src/tool/Tool.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Effect, Match, Schema } from \"effect\"\nimport type { ToolCall, ToolCallOutput } from \"../domain/Items.js\"\nimport { toolCallOutput } from \"../domain/Items.js\"\nimport { serializeValue } from \"./ToolResult.js\"\n\nexport class ToolError extends Schema.TaggedErrorClass<ToolError>(\"@betalyra/effect-uai/ToolError\")(\n \"ToolError\",\n {\n call_id: Schema.String,\n tool: Schema.String,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n },\n) {}\n\n/**\n * One Standard Schema validation issue, carried on `ToolValidationError`.\n * Mirrors `StandardSchemaV1.Issue`; `path` is left as `unknown` elements\n * because Standard Schema path segments may be symbols or `{ key }` objects.\n */\nconst ToolValidationIssue = Schema.Struct({\n message: Schema.String,\n path: Schema.optional(Schema.Array(Schema.Unknown)),\n})\n\n/**\n * The model's `arguments` for a call failed the tool's `inputSchema`. Distinct\n * from `ToolError` (malformed JSON, runtime crash) so callers can tell a\n * contract violation apart from a parse or execution failure, and so the\n * structured `issues` survive instead of being flattened into a message string.\n */\nexport class ToolValidationError extends Schema.TaggedErrorClass<ToolValidationError>(\n \"@betalyra/effect-uai/ToolValidationError\",\n)(\"ToolValidationError\", {\n call_id: Schema.String,\n tool: Schema.String,\n issues: Schema.Array(ToolValidationIssue),\n}) {}\n\n/**\n * The deliberate \"speak to the model\" failure sentinel. A tool's `run` that\n * fails with a `ToolFailed` (or a bare `string`, its sugar) is absorbed by\n * `Toolkit.run` into a `ToolResult.Failure` the model reads and adapts to,\n * instead of ending the run. Every other error type propagates typed on the\n * executor's stream. `kind` overrides the synthesized `Failure.kind`\n * (default `\"tool_failed\"`) so a tool can hand the model a distinguishable\n * category (`\"not_found\"`, `\"rate_limited\"`) without a new result variant.\n */\nexport class ToolFailed extends Schema.TaggedErrorClass<ToolFailed>(\n \"@betalyra/effect-uai/ToolFailed\",\n)(\"ToolFailed\", {\n message: Schema.String,\n kind: Schema.optional(Schema.String),\n}) {}\n\n/**\n * Fail a tool's `run` with a model-visible message (optionally a `kind`).\n * `yield* Tool.fail(\"page not found\", { kind: \"not_found\" })` inside a\n * `run`'s `Effect.gen`; the executor absorbs it as `ToolResult.Failure`.\n */\nexport const fail = (\n message: string,\n options?: { readonly kind?: string },\n): Effect.Effect<never, ToolFailed> =>\n Effect.fail(\n new ToolFailed({ message, ...(options?.kind !== undefined ? { kind: options.kind } : {}) }),\n )\n\n/**\n * Schemas accepted on `Tool.inputSchema`. Must implement both Standard\n * Schema validation and JSON Schema conversion (for rendering tool\n * descriptors to provider request bodies).\n *\n * Any Standard-Schema-compliant library that exposes both interfaces\n * works directly: Zod 4+, Valibot, ArkType, Effect Schema (after\n * `fromEffectSchema`), etc.\n */\nexport type ToolInputSchema<Input = unknown> = StandardSchemaV1<unknown, Input> &\n StandardJSONSchemaV1<unknown, Input>\n\n/**\n * Convenience wrapper for Effect Schema users - adds both the\n * `validate` and `jsonSchema` extensions to a plain Effect Schema so it\n * can be used as a `Tool.inputSchema`.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n): S & ToolInputSchema<S[\"Type\"]> =>\n Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema))\n\n/**\n * Use any schema library that implements both Standard Schema (validation)\n * and Standard JSON Schema (JSON Schema generation) as a `Tool.inputSchema`.\n * Covers Zod 4.2+, Valibot 1.2+, and ArkType 2.1.28+ in one helper.\n *\n * Effect Schema doesn't implement Standard JSON Schema natively — use\n * `fromEffectSchema` for those.\n *\n * The intersection constraint catches missing interfaces at compile time:\n * a Zod v3 schema (no Standard JSON Schema) produces a precise type error\n * pointing at the missing interface rather than a runtime surprise. The\n * helper itself is a thin type-narrowing identity — schemas that satisfy\n * both standards already structurally satisfy `ToolInputSchema`; the\n * helper makes the input type inference explicit at the call site.\n */\nexport const fromStandardSchema = <S extends StandardSchemaV1 & StandardJSONSchemaV1>(\n schema: S,\n): S & ToolInputSchema<StandardSchemaV1.InferOutput<S>> =>\n schema as S & ToolInputSchema<StandardSchemaV1.InferOutput<S>>\n\n/**\n * Input schema for a tool that takes no arguments. Renders a clean\n * `{ type: \"object\", properties: {} }`; validation accepts anything and\n * yields `{}`. Use it instead of `fromEffectSchema(Schema.Struct({}))`,\n * which renders a degenerate `anyOf` (object-or-array) that strict\n * providers like Gemini reject.\n */\nexport const noInput: ToolInputSchema<Record<string, never>> = {\n \"~standard\": {\n version: 1,\n vendor: \"effect-uai\",\n validate: () => ({ value: {} }),\n jsonSchema: {\n input: () => ({ type: \"object\", properties: {} }),\n output: () => ({ type: \"object\", properties: {} }),\n },\n },\n}\n\n/**\n * Emit one intermediate `Event` from inside a tool's `run`. Backed by the\n * executor's per-call queue, so it returns an `Effect` (backpressure) and\n * composes with `Stream.runForEach(emit)`.\n */\nexport type Emit<Event> = (event: Event) => Effect.Effect<void>\n\n/**\n * A model-visible tool with a local `Effect` executor. `run` computes the\n * model-facing `Output`; emitting progress is a side channel via `emit`. A\n * plain tool never calls `emit` (its `Event` is irrelevant); a streaming tool\n * emits intermediate `Event`s that flow to the consumer as\n * `ToolEvent.Progress` in real time.\n *\n * This is one of four tool *kinds*, discriminated by `_tag`. The other three\n * (`ProviderTool`, `SignalTool`, `InteractionTool`) are model-visible but have\n * no local executor — see their constructors below. `Tool` keeps its name (and\n * the `Tool.make` constructor) because the local kind is by far the common one.\n */\nexport type Tool<Name extends string, Input, Event, Output, E = unknown, R = never> = {\n readonly _tag: \"LocalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output, E, R>\n /**\n * Bound on this tool's emit queue. Unbounded when omitted. Per-tool because\n * backpressure depends on how a given tool emits, unlike `concurrency`\n * (cross-tool, on `Toolkit.run`'s options).\n */\n readonly emitBufferSize?: number\n /**\n * Whether the provider should render this tool with its strict-mode\n * flag (OpenAI's `strict: true`, etc). Default: true. The framework\n * never rewrites the schema; if the rendered JSON Schema isn't\n * compatible, the provider returns an error.\n */\n readonly strict?: boolean\n}\n\n/**\n * A provider-hosted tool: visible to the model and executed by the *provider*\n * (native web search, code execution, RAG grounding), never by this process.\n * It has no local `run`; `provider`/`config` tell the provider adapter how to\n * render it. Passing it to `Toolkit.run` yields a `non_local_tool` result.\n */\nexport type ProviderTool<Name extends string, Input, Provider extends string, Config> = {\n readonly _tag: \"ProviderTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly provider: Provider\n readonly config: Config\n readonly strict?: boolean\n}\n\n/**\n * A typed request for the agent loop to change control flow (escalate, pause,\n * schedule, hand off). Model-visible and decodable (`decodeArgs`) but never\n * locally executed — the loop intercepts the call in `onTurnComplete` and acts\n * on it, so there is no fake `run`. `Toolkit.run` reports it as `non_local_tool`.\n */\nexport type SignalTool<Name extends string, Input> = {\n readonly _tag: \"SignalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/**\n * A typed request for an external actor (user, channel, frontend) to respond\n * before the loop resumes. Like `SignalTool` it is decode-only, but its\n * lifecycle differs: the loop usually stops/pauses and resumes later by\n * appending a `function_call_output` once the actor replies.\n */\nexport type InteractionTool<Name extends string, Input> = {\n readonly _tag: \"InteractionTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** A local (executable) tool of any shape. Readability alias. */\nexport type AnyLocalTool<E = any, R = any> = Tool<string, any, any, any, E, R>\n\n/** Any of the four model-visible tool kinds. */\nexport type AnyTool<E = any, R = any> =\n | Tool<string, any, any, any, E, R>\n | ProviderTool<string, any, string, any>\n | SignalTool<string, any>\n | InteractionTool<string, any>\n\n/** A model-visible tool that carries a decodable input schema (every kind). */\nexport type DecodableTool<Input> = {\n readonly name: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** Extract the `R` of a local tool; non-local kinds contribute `never`. */\nexport type ToolR<T> = T extends Tool<string, any, any, any, any, infer R> ? R : never\n\n/** Extract the `E` of a local tool; non-local kinds contribute `never`. */\nexport type ToolE<T> = T extends Tool<string, any, any, any, infer E, any> ? E : never\n\n/**\n * Provider-agnostic tool descriptor. Each provider maps `inputSchema`\n * to its own wire field (OpenAI → `parameters`, Anthropic →\n * `input_schema`). Built from a `Tool` by `Tool.toDescriptors`.\n */\nexport type ToolDescriptor = {\n readonly name: string\n readonly description: string\n readonly inputSchema: Record<string, unknown>\n readonly strict?: boolean\n}\n\n/** A model-visible tool with a local `Effect` executor. */\nexport const make = <Name extends string, Input, Event, Output, E = never, R = never>(\n spec: Omit<Tool<Name, Input, Event, Output, E, R>, \"_tag\">,\n): Tool<Name, Input, Event, Output, E, R> => ({ _tag: \"LocalTool\", ...spec })\n\n/**\n * A provider-hosted tool (native web search, code execution, RAG grounding).\n * No local executor — `provider`/`config` drive the provider adapter's\n * rendering. The model sees it; `Toolkit.run` reports it as `non_local_tool`.\n */\nexport const provider = <Name extends string, Input, Provider extends string, Config>(\n spec: Omit<ProviderTool<Name, Input, Provider, Config>, \"_tag\">,\n): ProviderTool<Name, Input, Provider, Config> => ({ _tag: \"ProviderTool\", ...spec })\n\n/**\n * A typed control-flow signal to the agent loop (escalate, pause, schedule,\n * hand off). Decode-only: the loop interprets the call rather than executing a\n * handler, so there is no `run`.\n */\nexport const signal = <Name extends string, Input>(\n spec: Omit<SignalTool<Name, Input>, \"_tag\">,\n): SignalTool<Name, Input> => ({ _tag: \"SignalTool\", ...spec })\n\n/**\n * A typed request for an external actor to respond before the loop resumes\n * (frontend, Telegram, CLI prompt). Decode-only; the loop stops/pauses and\n * resumes by appending the actor's `function_call_output`.\n */\nexport const interaction = <Name extends string, Input>(\n spec: Omit<InteractionTool<Name, Input>, \"_tag\">,\n): InteractionTool<Name, Input> => ({ _tag: \"InteractionTool\", ...spec })\n\n/**\n * Return a copy of a tool under a new `name`. Useful for resolving a name\n * clash before combining tools into one `Toolkit`. Literal-preserving, so the\n * renamed tool keys the toolkit record under the new name statically.\n */\nexport const withName = <N extends string, T extends AnyTool>(\n tool: T,\n name: N,\n): Omit<T, \"name\"> & { readonly name: N } => ({ ...tool, name })\n\n/**\n * Replace a local tool's `run`, keeping its model-facing definition (name,\n * description, schema, `_tag`) identical. The new `run`'s `input` is typed from\n * the original tool, so override/mock need no annotation:\n *\n * const safe = { ...toolkit, send_email: Tool.withRun(toolkit.send_email,\n * ({ to }) => Effect.succeed({ status: \"dry-run\", to })) }\n *\n * Descriptors stay identical to production, so a mocked toolkit can't drift\n * from the contract the model sees.\n */\nexport const withRun = <\n Name extends string,\n Input,\n Event,\n Output,\n E,\n R,\n Output2,\n E2 = never,\n R2 = never,\n>(\n tool: Tool<Name, Input, Event, Output, E, R>,\n run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output2, E2, R2>,\n): Tool<Name, Input, Event, Output2, E2, R2> => ({ ...tool, run })\n\n/** Render tools to provider-agnostic descriptors. Every kind is model-visible. */\nexport const toDescriptors = (tools: ReadonlyArray<AnyTool>): ReadonlyArray<ToolDescriptor> =>\n tools.map((tool) => {\n const inputSchema = tool.inputSchema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n })\n // `strict` only exists on local/provider kinds; signals/interactions omit it.\n const strict = \"strict\" in tool ? tool.strict : undefined\n return strict !== undefined\n ? { name: tool.name, description: tool.description, inputSchema, strict }\n : { name: tool.name, description: tool.description, inputSchema }\n })\n\n/**\n * Render a `Toolkit` (a name-indexed record of tools) to descriptors, treating\n * an absent toolkit as no tools. The normalization point the `LanguageModel`\n * boundary uses so a request can carry the toolkit itself instead of a\n * pre-rendered descriptor array. Lives here (not on `Toolkit`) to keep providers\n * free of a dependency on the toolkit module — it only needs the tool values.\n */\nexport const descriptorsOf = (\n toolkit?: Readonly<Record<string, AnyTool>>,\n): ReadonlyArray<ToolDescriptor> =>\n toolkit === undefined ? [] : toDescriptors(Object.values(toolkit))\n\n/**\n * Outcome of decoding a call's `arguments` against a tool's `inputSchema`,\n * as data so both the typed (`decodeArgs`) and result-synthesizing\n * (`Toolkit.run`) callers can translate it their own way.\n */\nexport type DecodeResult<Input> =\n | { readonly _tag: \"ok\"; readonly input: Input }\n | { readonly _tag: \"parseError\" }\n | { readonly _tag: \"invalid\"; readonly issues: ReadonlyArray<StandardSchemaV1.Issue> }\n\nconst VALIDATION_THREW: ReadonlyArray<StandardSchemaV1.Issue> = [\n { message: \"input schema validation threw\" },\n]\n\n/**\n * Parse and validate a call's `arguments`. Empty arguments normalize to\n * `{}` (some models emit `\"\"` for zero-arg tools); a throwing validator is\n * captured, not propagated. Never fails.\n */\nexport const decodeCallInput = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<DecodeResult<Input>> => {\n const raw = call.arguments.trim() === \"\" ? \"{}\" : call.arguments\n return Effect.try({\n // @effect-diagnostics-next-line effect/preferSchemaOverJson:off\n try: () => JSON.parse(raw) as unknown,\n catch: (): DecodeResult<Input> => ({ _tag: \"parseError\" }),\n }).pipe(\n Effect.flatMap((parsed) =>\n Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: (): DecodeResult<Input> => ({ _tag: \"invalid\", issues: VALIDATION_THREW }),\n }).pipe(\n Effect.map(\n (result): DecodeResult<Input> =>\n result.issues !== undefined\n ? { _tag: \"invalid\", issues: result.issues }\n : { _tag: \"ok\", input: result.value },\n ),\n ),\n ),\n Effect.catch((captured: DecodeResult<Input>) => Effect.succeed(captured)),\n )\n}\n\n/**\n * Decode and validate a function_call's JSON `arguments` against the tool's\n * own `inputSchema`, yielding the typed input. The decode-only half of\n * `execute` - reach for it when you intercept a call to translate it into a\n * control-flow decision rather than running the tool's `run`.\n *\n * Malformed JSON fails with `ToolError`; a parsed body that violates\n * `inputSchema` fails with `ToolValidationError` carrying the issues.\n */\nexport const decodeArgs = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<Input, ToolError | ToolValidationError> =>\n decodeCallInput(tool, call).pipe(\n Effect.flatMap(\n Match.type<DecodeResult<Input>>().pipe(\n Match.tag(\"ok\", ({ input }) => Effect.succeed(input)),\n Match.tag(\"parseError\", () =>\n Effect.fail(\n new ToolError({\n call_id: call.call_id,\n tool: tool.name,\n message: \"Failed to parse JSON arguments\",\n }),\n ),\n ),\n Match.tag(\"invalid\", ({ issues }) =>\n Effect.fail(new ToolValidationError({ call_id: call.call_id, tool: tool.name, issues })),\n ),\n Match.exhaustive,\n ),\n ),\n )\n\n/**\n * Decode and validate the JSON arguments of a function_call against the\n * tool's input schema, run the tool, and serialize the output into a\n * function_call_output item. Single-shot: intermediate events emitted by\n * `run` are discarded (use `Toolkit.run` to stream them).\n */\nexport const execute = <Name extends string, Input, Event, Output, E, R>(\n tool: Tool<Name, Input, Event, Output, E, R>,\n call: ToolCall,\n): Effect.Effect<ToolCallOutput, E | ToolError | ToolValidationError, R> =>\n decodeArgs(tool, call).pipe(\n Effect.flatMap((input) => tool.run(input, () => Effect.void)),\n Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))),\n )\n"],"mappings":"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"}
1
+ {"version":3,"file":"Tool.mjs","names":[],"sources":["../../src/tool/Tool.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Effect, Match, Schema } from \"effect\"\nimport type { ToolCall, ToolCallOutput } from \"../domain/Items.js\"\nimport { toolCallOutput } from \"../domain/Items.js\"\nimport { serializeValue } from \"./ToolResult.js\"\n\nexport class ToolError extends Schema.TaggedErrorClass<ToolError>(\"@betalyra/effect-uai/ToolError\")(\n \"ToolError\",\n {\n call_id: Schema.String,\n tool: Schema.String,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n },\n) {}\n\n/**\n * One Standard Schema validation issue, carried on `ToolValidationError`.\n * Mirrors `StandardSchemaV1.Issue`; `path` is left as `unknown` elements\n * because Standard Schema path segments may be symbols or `{ key }` objects.\n */\nconst ToolValidationIssue = Schema.Struct({\n message: Schema.String,\n path: Schema.optional(Schema.Array(Schema.Unknown)),\n})\n\n/**\n * The model's `arguments` for a call failed the tool's `inputSchema`. Distinct\n * from `ToolError` (malformed JSON, runtime crash) so callers can tell a\n * contract violation apart from a parse or execution failure, and so the\n * structured `issues` survive instead of being flattened into a message string.\n */\nexport class ToolValidationError extends Schema.TaggedErrorClass<ToolValidationError>(\n \"@betalyra/effect-uai/ToolValidationError\",\n)(\"ToolValidationError\", {\n call_id: Schema.String,\n tool: Schema.String,\n issues: Schema.Array(ToolValidationIssue),\n}) {}\n\n/**\n * The deliberate \"speak to the model\" failure sentinel. A tool's `run` that\n * fails with a `ToolFailed` (or a bare `string`, its sugar) is absorbed by\n * `Toolkit.run` into a `ToolResult.Failure` the model reads and adapts to,\n * instead of ending the run. Every other error type propagates typed on the\n * executor's stream. `kind` overrides the synthesized `Failure.kind`\n * (default `\"tool_failed\"`) so a tool can hand the model a distinguishable\n * category (`\"not_found\"`, `\"rate_limited\"`) without a new result variant.\n */\nexport class ToolFailed extends Schema.TaggedErrorClass<ToolFailed>(\n \"@betalyra/effect-uai/ToolFailed\",\n)(\"ToolFailed\", {\n message: Schema.String,\n kind: Schema.optional(Schema.String),\n}) {}\n\n/**\n * Fail a tool's `run` with a model-visible message (optionally a `kind`).\n * `yield* Tool.fail(\"page not found\", { kind: \"not_found\" })` inside a\n * `run`'s `Effect.gen`; the executor absorbs it as `ToolResult.Failure`.\n */\nexport const fail = (\n message: string,\n options?: { readonly kind?: string },\n): Effect.Effect<never, ToolFailed> =>\n Effect.fail(\n new ToolFailed({ message, ...(options?.kind !== undefined ? { kind: options.kind } : {}) }),\n )\n\n/**\n * Schemas accepted on `Tool.inputSchema`. Must implement both Standard\n * Schema validation and JSON Schema conversion (for rendering tool\n * descriptors to provider request bodies).\n *\n * Any Standard-Schema-compliant library that exposes both interfaces\n * works directly: Zod 4+, Valibot, ArkType, Effect Schema (after\n * `fromEffectSchema`), etc.\n */\nexport type ToolInputSchema<Input = unknown> = StandardSchemaV1<unknown, Input> &\n StandardJSONSchemaV1<unknown, Input>\n\n/**\n * Convenience wrapper for Effect Schema users - adds both the\n * `validate` and `jsonSchema` extensions to a plain Effect Schema so it\n * can be used as a `Tool.inputSchema`.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n): S & ToolInputSchema<S[\"Type\"]> =>\n Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema))\n\n/**\n * Use any schema library that implements both Standard Schema (validation)\n * and Standard JSON Schema (JSON Schema generation) as a `Tool.inputSchema`.\n * Covers Zod 4.2+, Valibot 1.2+, and ArkType 2.1.28+ in one helper.\n *\n * Effect Schema doesn't implement Standard JSON Schema natively — use\n * `fromEffectSchema` for those.\n *\n * The intersection constraint catches missing interfaces at compile time:\n * a Zod v3 schema (no Standard JSON Schema) produces a precise type error\n * pointing at the missing interface rather than a runtime surprise. The\n * helper itself is a thin type-narrowing identity — schemas that satisfy\n * both standards already structurally satisfy `ToolInputSchema`; the\n * helper makes the input type inference explicit at the call site.\n */\nexport const fromStandardSchema = <S extends StandardSchemaV1 & StandardJSONSchemaV1>(\n schema: S,\n): S & ToolInputSchema<StandardSchemaV1.InferOutput<S>> =>\n schema as S & ToolInputSchema<StandardSchemaV1.InferOutput<S>>\n\n/**\n * Input schema for a tool that takes no arguments. Renders a clean\n * `{ type: \"object\", properties: {} }`; validation accepts anything and\n * yields `{}`. Use it instead of `fromEffectSchema(Schema.Struct({}))`,\n * which renders a degenerate `anyOf` (object-or-array) that strict\n * providers like Gemini reject.\n */\nexport const noInput: ToolInputSchema<Record<string, never>> = {\n \"~standard\": {\n version: 1,\n vendor: \"effect-uai\",\n validate: () => ({ value: {} }),\n jsonSchema: {\n input: () => ({ type: \"object\", properties: {} }),\n output: () => ({ type: \"object\", properties: {} }),\n },\n },\n}\n\n/**\n * Emit one intermediate `Event` from inside a tool's `run`. Backed by the\n * executor's per-call queue, so it returns an `Effect` (backpressure) and\n * composes with `Stream.runForEach(emit)`.\n */\nexport type Emit<Event> = (event: Event) => Effect.Effect<void>\n\n/**\n * A model-visible tool with a local `Effect` executor. `run` computes the\n * model-facing `Output`; emitting progress is a side channel via `emit`. A\n * plain tool never calls `emit` (its `Event` is irrelevant); a streaming tool\n * emits intermediate `Event`s that flow to the consumer as\n * `ToolEvent.Progress` in real time.\n *\n * This is one of four tool *kinds*, discriminated by `_tag`. The other three\n * (`ProviderTool`, `SignalTool`, `InteractionTool`) are model-visible but have\n * no local executor — see their constructors below. `Tool` keeps its name (and\n * the `Tool.make` constructor) because the local kind is by far the common one.\n */\nexport type Tool<Name extends string, Input, Event, Output, E = unknown, R = never> = {\n readonly _tag: \"LocalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output, E, R>\n /**\n * Bound on this tool's emit queue. Unbounded when omitted. Per-tool because\n * backpressure depends on how a given tool emits, unlike `concurrency`\n * (cross-tool, on `Toolkit.run`'s options).\n */\n readonly emitBufferSize?: number\n /**\n * Whether the provider should render this tool with its strict-mode\n * flag (OpenAI's `strict: true`, etc). Default: true. The framework\n * never rewrites the schema; if the rendered JSON Schema isn't\n * compatible, the provider returns an error.\n */\n readonly strict?: boolean\n}\n\n/**\n * A provider-hosted tool: visible to the model and executed by the *provider*\n * (native web search, code execution, RAG grounding), never by this process.\n * It has no local `run`; `provider`/`config` tell the provider adapter how to\n * render it. Passing it to `Toolkit.run` yields a `non_local_tool` result.\n */\nexport type ProviderTool<Name extends string, Input, Provider extends string, Config> = {\n readonly _tag: \"ProviderTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly provider: Provider\n readonly config: Config\n readonly strict?: boolean\n}\n\n/**\n * A typed request for the agent loop to change control flow (escalate, pause,\n * schedule, hand off). Model-visible and decodable (`decodeArgs`) but never\n * locally executed — the loop intercepts the call in `onTurnComplete` and acts\n * on it, so there is no fake `run`. `Toolkit.run` reports it as `non_local_tool`.\n */\nexport type SignalTool<Name extends string, Input> = {\n readonly _tag: \"SignalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/**\n * A typed request for an external actor (user, channel, frontend) to respond\n * before the loop resumes. Like `SignalTool` it is decode-only, but its\n * lifecycle differs: the loop usually stops/pauses and resumes later by\n * appending a `function_call_output` once the actor replies.\n */\nexport type InteractionTool<Name extends string, Input> = {\n readonly _tag: \"InteractionTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** A local (executable) tool of any shape. Readability alias. */\nexport type AnyLocalTool<E = any, R = any> = Tool<string, any, any, any, E, R>\n\n/** Any of the four model-visible tool kinds. */\nexport type AnyTool<E = any, R = any> =\n | Tool<string, any, any, any, E, R>\n | ProviderTool<string, any, string, any>\n | SignalTool<string, any>\n | InteractionTool<string, any>\n\n/** A model-visible tool that carries a decodable input schema (every kind). */\nexport type DecodableTool<Input> = {\n readonly name: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** Extract the `R` of a local tool; non-local kinds contribute `never`. */\nexport type ToolR<T> = T extends Tool<string, any, any, any, any, infer R> ? R : never\n\n/** Extract the `E` of a local tool; non-local kinds contribute `never`. */\nexport type ToolE<T> = T extends Tool<string, any, any, any, infer E, any> ? E : never\n\n/**\n * Provider-agnostic tool descriptor. Each provider maps `inputSchema`\n * to its own wire field (OpenAI → `parameters`, Anthropic →\n * `input_schema`). Built from a `Tool` by `Tool.toDescriptors`.\n */\nexport type ToolDescriptor = {\n readonly name: string\n readonly description: string\n readonly inputSchema: Record<string, unknown>\n readonly strict?: boolean\n}\n\nconst resolveInputSchema = (\n schema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>,\n): ToolInputSchema<any> =>\n Schema.isSchema(schema)\n ? fromEffectSchema(schema as Schema.Codec<any, any, never, any>)\n : (schema as ToolInputSchema<any>)\n\n/** A model-visible tool with a local `Effect` executor. */\nexport const make: {\n <\n Name extends string,\n S extends Schema.Codec<any, any, never, any>,\n Event,\n Output,\n E = never,\n R = never,\n >(\n spec: Omit<Tool<Name, S[\"Type\"], Event, Output, E, R>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): Tool<Name, S[\"Type\"], Event, Output, E, R>\n <Name extends string, Input, Event, Output, E = never, R = never>(\n spec: Omit<Tool<Name, Input, Event, Output, E, R>, \"_tag\">,\n ): Tool<Name, Input, Event, Output, E, R>\n} = (\n spec: Omit<AnyLocalTool, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): AnyLocalTool => ({\n _tag: \"LocalTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * A provider-hosted tool (native web search, code execution, RAG grounding).\n * No local executor — `provider`/`config` drive the provider adapter's\n * rendering. The model sees it; `Toolkit.run` reports it as `non_local_tool`.\n */\nexport const provider: {\n <\n Name extends string,\n S extends Schema.Codec<any, any, never, any>,\n Provider extends string,\n Config,\n >(\n spec: Omit<ProviderTool<Name, S[\"Type\"], Provider, Config>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): ProviderTool<Name, S[\"Type\"], Provider, Config>\n <Name extends string, Input, Provider extends string, Config>(\n spec: Omit<ProviderTool<Name, Input, Provider, Config>, \"_tag\">,\n ): ProviderTool<Name, Input, Provider, Config>\n} = (\n spec: Omit<ProviderTool<string, any, string, any>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): ProviderTool<string, any, string, any> => ({\n _tag: \"ProviderTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * A typed control-flow signal to the agent loop (escalate, pause, schedule,\n * hand off). Decode-only: the loop interprets the call rather than executing a\n * handler, so there is no `run`.\n */\nexport const signal: {\n <Name extends string, S extends Schema.Codec<any, any, never, any>>(\n spec: Omit<SignalTool<Name, S[\"Type\"]>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): SignalTool<Name, S[\"Type\"]>\n <Name extends string, Input>(spec: Omit<SignalTool<Name, Input>, \"_tag\">): SignalTool<Name, Input>\n} = (\n spec: Omit<SignalTool<string, any>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): SignalTool<string, any> => ({\n _tag: \"SignalTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * A typed request for an external actor to respond before the loop resumes\n * (frontend, Telegram, CLI prompt). Decode-only; the loop stops/pauses and\n * resumes by appending the actor's `function_call_output`.\n */\nexport const interaction: {\n <Name extends string, S extends Schema.Codec<any, any, never, any>>(\n spec: Omit<InteractionTool<Name, S[\"Type\"]>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): InteractionTool<Name, S[\"Type\"]>\n <Name extends string, Input>(\n spec: Omit<InteractionTool<Name, Input>, \"_tag\">,\n ): InteractionTool<Name, Input>\n} = (\n spec: Omit<InteractionTool<string, any>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): InteractionTool<string, any> => ({\n _tag: \"InteractionTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * Return a copy of a tool under a new `name`. Useful for resolving a name\n * clash before combining tools into one `Toolkit`. Literal-preserving, so the\n * renamed tool keys the toolkit record under the new name statically.\n */\nexport const withName = <N extends string, T extends AnyTool>(\n tool: T,\n name: N,\n): Omit<T, \"name\"> & { readonly name: N } => ({ ...tool, name })\n\n/**\n * Replace a local tool's `run`, keeping its model-facing definition (name,\n * description, schema, `_tag`) identical. The new `run`'s `input` is typed from\n * the original tool, so override/mock need no annotation:\n *\n * const safe = { ...toolkit, send_email: Tool.withRun(toolkit.send_email,\n * ({ to }) => Effect.succeed({ status: \"dry-run\", to })) }\n *\n * Descriptors stay identical to production, so a mocked toolkit can't drift\n * from the contract the model sees.\n */\nexport const withRun = <\n Name extends string,\n Input,\n Event,\n Output,\n E,\n R,\n Output2,\n E2 = never,\n R2 = never,\n>(\n tool: Tool<Name, Input, Event, Output, E, R>,\n run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output2, E2, R2>,\n): Tool<Name, Input, Event, Output2, E2, R2> => ({ ...tool, run })\n\n/** True for provider-hosted tools — the kind a provider renders natively. */\nexport const isProviderTool = (tool: AnyTool): tool is ProviderTool<string, any, string, any> =>\n tool._tag === \"ProviderTool\"\n\n/**\n * The `ProviderTool`s in a toolkit, for a provider adapter to render as its\n * native hosted-tool entries (web search, code execution, grounding). The other\n * kinds render as function declarations via `descriptorsOf`.\n */\nexport const providerToolsOf = (\n toolkit?: Readonly<Record<string, AnyTool>>,\n): ReadonlyArray<ProviderTool<string, any, string, any>> =>\n toolkit === undefined ? [] : Object.values(toolkit).filter(isProviderTool)\n\n/**\n * Render tools to provider-agnostic descriptors. `ProviderTool`s are excluded:\n * they are provider-hosted and must never leak into the function-declaration\n * list — each adapter renders them natively via `providerToolsOf`. Every other\n * kind is model-visible as a declaration.\n */\nexport const toDescriptors = (tools: ReadonlyArray<AnyTool>): ReadonlyArray<ToolDescriptor> =>\n tools\n .filter((tool) => !isProviderTool(tool))\n .map((tool) => {\n const inputSchema = tool.inputSchema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n })\n // `strict` only exists on local/provider kinds; signals/interactions omit it.\n const strict = \"strict\" in tool ? tool.strict : undefined\n return strict !== undefined\n ? { name: tool.name, description: tool.description, inputSchema, strict }\n : { name: tool.name, description: tool.description, inputSchema }\n })\n\n/**\n * Render a `Toolkit` (a name-indexed record of tools) to descriptors, treating\n * an absent toolkit as no tools. The normalization point the `LanguageModel`\n * boundary uses so a request can carry the toolkit itself instead of a\n * pre-rendered descriptor array. Lives here (not on `Toolkit`) to keep providers\n * free of a dependency on the toolkit module — it only needs the tool values.\n */\nexport const descriptorsOf = (\n toolkit?: Readonly<Record<string, AnyTool>>,\n): ReadonlyArray<ToolDescriptor> =>\n toolkit === undefined ? [] : toDescriptors(Object.values(toolkit))\n\n/**\n * Outcome of decoding a call's `arguments` against a tool's `inputSchema`,\n * as data so both the typed (`decodeArgs`) and result-synthesizing\n * (`Toolkit.run`) callers can translate it their own way.\n */\nexport type DecodeResult<Input> =\n | { readonly _tag: \"ok\"; readonly input: Input }\n | { readonly _tag: \"parseError\" }\n | { readonly _tag: \"invalid\"; readonly issues: ReadonlyArray<StandardSchemaV1.Issue> }\n\nconst VALIDATION_THREW: ReadonlyArray<StandardSchemaV1.Issue> = [\n { message: \"input schema validation threw\" },\n]\n\n/**\n * Parse and validate a call's `arguments`. Empty arguments normalize to\n * `{}` (some models emit `\"\"` for zero-arg tools); a throwing validator is\n * captured, not propagated. Never fails.\n */\nexport const decodeCallInput = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<DecodeResult<Input>> => {\n const raw = call.arguments.trim() === \"\" ? \"{}\" : call.arguments\n return Effect.try({\n // @effect-diagnostics-next-line effect/preferSchemaOverJson:off\n try: () => JSON.parse(raw) as unknown,\n catch: (): DecodeResult<Input> => ({ _tag: \"parseError\" }),\n }).pipe(\n Effect.flatMap((parsed) =>\n Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: (): DecodeResult<Input> => ({ _tag: \"invalid\", issues: VALIDATION_THREW }),\n }).pipe(\n Effect.map(\n (result): DecodeResult<Input> =>\n result.issues !== undefined\n ? { _tag: \"invalid\", issues: result.issues }\n : { _tag: \"ok\", input: result.value },\n ),\n ),\n ),\n Effect.catch((captured: DecodeResult<Input>) => Effect.succeed(captured)),\n )\n}\n\n/**\n * Decode and validate a function_call's JSON `arguments` against the tool's\n * own `inputSchema`, yielding the typed input. The decode-only half of\n * `execute` - reach for it when you intercept a call to translate it into a\n * control-flow decision rather than running the tool's `run`.\n *\n * Malformed JSON fails with `ToolError`; a parsed body that violates\n * `inputSchema` fails with `ToolValidationError` carrying the issues.\n */\nexport const decodeArgs = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<Input, ToolError | ToolValidationError> =>\n decodeCallInput(tool, call).pipe(\n Effect.flatMap(\n Match.type<DecodeResult<Input>>().pipe(\n Match.tag(\"ok\", ({ input }) => Effect.succeed(input)),\n Match.tag(\"parseError\", () =>\n Effect.fail(\n new ToolError({\n call_id: call.call_id,\n tool: tool.name,\n message: \"Failed to parse JSON arguments\",\n }),\n ),\n ),\n Match.tag(\"invalid\", ({ issues }) =>\n Effect.fail(new ToolValidationError({ call_id: call.call_id, tool: tool.name, issues })),\n ),\n Match.exhaustive,\n ),\n ),\n )\n\n/**\n * Decode and validate the JSON arguments of a function_call against the\n * tool's input schema, run the tool, and serialize the output into a\n * function_call_output item. Single-shot: intermediate events emitted by\n * `run` are discarded (use `Toolkit.run` to stream them).\n */\nexport const execute = <Name extends string, Input, Event, Output, E, R>(\n tool: Tool<Name, Input, Event, Output, E, R>,\n call: ToolCall,\n): Effect.Effect<ToolCallOutput, E | ToolError | ToolValidationError, R> =>\n decodeArgs(tool, call).pipe(\n Effect.flatMap((input) => tool.run(input, () => Effect.void)),\n Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))),\n )\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { decodeArgs, fromStandardSchema, make, toDescriptors, withRun } from "./Tool.mjs";
2
- import { a as describe, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
3
- import { Effect } from "effect";
1
+ import { decodeArgs, descriptorsOf, fromEffectSchema, fromStandardSchema, isProviderTool, make, noInput, provider, providerToolsOf, signal, toDescriptors, withRun } from "./Tool.mjs";
2
+ import { c as it, n as globalExpect, o as describe, t as import_dist } from "../dist-CcRV3-A5.mjs";
3
+ import { Context, Effect, Schema } from "effect";
4
4
  //#region src/tool/Tool.test.ts
5
5
  const emailRecipientSchema = { "~standard": {
6
6
  version: 1,
@@ -98,6 +98,130 @@ describe("Tool.decodeArgs", () => {
98
98
  globalExpect((await Effect.runPromiseExit(decodeArgs(sendEmail, callWith("{\"to\":42}"))))._tag).toBe("Failure");
99
99
  });
100
100
  });
101
+ describe("Effect Schema inputSchema", () => {
102
+ const Recipient = Schema.Struct({ to: Schema.String });
103
+ const sendEmail = make({
104
+ name: "send_email",
105
+ description: "Send an email to a single recipient.",
106
+ inputSchema: Recipient,
107
+ run: ({ to }) => Effect.succeed(`queued: ${to}`)
108
+ });
109
+ const callWith = (args) => ({
110
+ type: "function_call",
111
+ call_id: "call_1",
112
+ name: "send_email",
113
+ arguments: args
114
+ });
115
+ it("runs with the input inferred from the schema's Type", async () => {
116
+ globalExpect(await Effect.runPromise(sendEmail.run({ to: "x@y.z" }, () => Effect.void))).toBe("queued: x@y.z");
117
+ });
118
+ it("type: Input is the schema's Type", () => {
119
+ (0, import_dist.expectTypeOf)().toEqualTypeOf();
120
+ });
121
+ it("type: E and R are inferred from run through the Effect Schema overload", () => {
122
+ class Mailer extends Context.Service()("test/Mailer") {}
123
+ make({
124
+ name: "send_email",
125
+ description: "send",
126
+ inputSchema: Recipient,
127
+ run: ({ to }) => Effect.gen(function* () {
128
+ const mailer = yield* Mailer;
129
+ if (to === "") return yield* Effect.fail("empty");
130
+ return yield* mailer.send(to);
131
+ })
132
+ });
133
+ (0, import_dist.expectTypeOf)().toEqualTypeOf();
134
+ (0, import_dist.expectTypeOf)().toEqualTypeOf();
135
+ });
136
+ it("decodes valid JSON arguments to the typed input", async () => {
137
+ const input = await Effect.runPromise(decodeArgs(sendEmail, callWith("{\"to\":\"x@y.z\"}")));
138
+ globalExpect(input).toEqual({ to: "x@y.z" });
139
+ (0, import_dist.expectTypeOf)(input).toEqualTypeOf();
140
+ });
141
+ it("fails with ToolValidationError when the arguments don't satisfy the schema", async () => {
142
+ globalExpect((await Effect.runPromiseExit(decodeArgs(sendEmail, callWith("{\"to\":42}"))))._tag).toBe("Failure");
143
+ });
144
+ it("renders a JSON Schema descriptor from the wrapped schema", () => {
145
+ const [descriptor] = toDescriptors([sendEmail]);
146
+ globalExpect(descriptor?.name).toBe("send_email");
147
+ globalExpect(descriptor?.inputSchema).toMatchObject({
148
+ type: "object",
149
+ properties: { to: { type: "string" } },
150
+ required: ["to"]
151
+ });
152
+ });
153
+ it("accepts an already-wrapped fromEffectSchema value unchanged", async () => {
154
+ const wrapped = make({
155
+ name: "send_email",
156
+ description: "Send an email to a single recipient.",
157
+ inputSchema: fromEffectSchema(Recipient),
158
+ run: ({ to }) => Effect.succeed(`queued: ${to}`)
159
+ });
160
+ globalExpect(toDescriptors([wrapped])).toEqual(toDescriptors([sendEmail]));
161
+ (0, import_dist.expectTypeOf)(await Effect.runPromise(decodeArgs(wrapped, callWith("{\"to\":\"x@y.z\"}")))).toEqualTypeOf();
162
+ });
163
+ it("signal: decode-only kinds accept an Effect Schema directly", async () => {
164
+ const escalate = signal({
165
+ name: "escalate",
166
+ description: "Escalate to a human.",
167
+ inputSchema: Schema.Struct({ reason: Schema.String })
168
+ });
169
+ const input = await Effect.runPromise(decodeArgs(escalate, {
170
+ type: "function_call",
171
+ call_id: "call_2",
172
+ name: "escalate",
173
+ arguments: "{\"reason\":\"stuck\"}"
174
+ }));
175
+ globalExpect(input).toEqual({ reason: "stuck" });
176
+ (0, import_dist.expectTypeOf)(input).toEqualTypeOf();
177
+ });
178
+ });
179
+ describe("provider-hosted tools", () => {
180
+ const localTool = make({
181
+ name: "send_email",
182
+ description: "Send an email.",
183
+ inputSchema: fromEffectSchema(Schema.Struct({ to: Schema.String })),
184
+ run: ({ to }) => Effect.succeed(`sent: ${to}`)
185
+ });
186
+ const signalTool = signal({
187
+ name: "escalate",
188
+ description: "Escalate to a human.",
189
+ inputSchema: Schema.Struct({ reason: Schema.String })
190
+ });
191
+ const hostedTool = provider({
192
+ name: "web_search",
193
+ description: "Search the web.",
194
+ inputSchema: noInput,
195
+ provider: "gemini",
196
+ config: { kind: "google_search" }
197
+ });
198
+ const toolkit = {
199
+ send_email: localTool,
200
+ escalate: signalTool,
201
+ web_search: hostedTool
202
+ };
203
+ it("descriptorsOf excludes provider-hosted tools", () => {
204
+ globalExpect(descriptorsOf(toolkit).map((d) => d.name)).toEqual(["send_email", "escalate"]);
205
+ });
206
+ it("toDescriptors drops a ProviderTool from a mixed array", () => {
207
+ globalExpect(toDescriptors([localTool, hostedTool]).map((d) => d.name)).toEqual(["send_email"]);
208
+ });
209
+ it("providerToolsOf returns only the provider-hosted tools", () => {
210
+ const provider = providerToolsOf(toolkit);
211
+ globalExpect(provider).toHaveLength(1);
212
+ globalExpect(provider[0]?.name).toBe("web_search");
213
+ globalExpect(provider[0]?.provider).toBe("gemini");
214
+ globalExpect(provider[0]?.config).toEqual({ kind: "google_search" });
215
+ });
216
+ it("providerToolsOf on an absent toolkit is empty", () => {
217
+ globalExpect(providerToolsOf(void 0)).toEqual([]);
218
+ });
219
+ it("isProviderTool narrows the tool kind", () => {
220
+ globalExpect(isProviderTool(hostedTool)).toBe(true);
221
+ globalExpect(isProviderTool(localTool)).toBe(false);
222
+ globalExpect(isProviderTool(signalTool)).toBe(false);
223
+ });
224
+ });
101
225
  //#endregion
102
226
  export {};
103
227
 
@@ -1 +1 @@
1
- {"version":3,"file":"Tool.test.mjs","names":["Tool.fromStandardSchema","Tool.make","Tool.withRun","Tool.toDescriptors","Tool.decodeArgs"],"sources":["../../src/tool/Tool.test.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Effect } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as Items from \"../domain/Items.js\"\nimport * as Tool from \"./Tool.js\"\n\n// ---------------------------------------------------------------------------\n// Minimal dual-standard schema for testing — no need to pull Zod / Valibot /\n// ArkType as devDeps. This is the smallest possible object satisfying both\n// `StandardSchemaV1` and `StandardJSONSchemaV1` per their published specs.\n// ---------------------------------------------------------------------------\n\ntype EmailRecipient = { readonly to: string }\n\nconst emailRecipientSchema: StandardSchemaV1<unknown, EmailRecipient> &\n StandardJSONSchemaV1<unknown, EmailRecipient> = {\n \"~standard\": {\n version: 1,\n vendor: \"test-fixture\",\n validate: (value) => {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"to\" in value &&\n typeof (value as { to: unknown }).to === \"string\"\n ) {\n return { value: value as EmailRecipient }\n }\n return { issues: [{ message: \"expected { to: string }\" }] }\n },\n jsonSchema: {\n input: () => ({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n }),\n output: () => ({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n }),\n },\n },\n}\n\n// A schema that satisfies StandardSchemaV1 only (no JSON Schema). Used to\n// verify the helper's compile-time guard.\nconst standardOnly: StandardSchemaV1<unknown, EmailRecipient> = {\n \"~standard\": {\n version: 1,\n vendor: \"test-fixture\",\n validate: () => ({ value: { to: \"\" } }),\n },\n}\n\ndescribe(\"Tool.fromStandardSchema\", () => {\n it(\"returns the schema (structurally) typed as ToolInputSchema<Output>\", () => {\n const adapted = Tool.fromStandardSchema(emailRecipientSchema)\n\n // Same object — helper is a type-narrowing identity at runtime.\n expect(adapted).toBe(emailRecipientSchema)\n\n // Both interfaces accessible through the same `~standard` key.\n const valid = adapted[\"~standard\"].validate({ to: \"hi@example.com\" })\n expect(valid).toEqual({ value: { to: \"hi@example.com\" } })\n\n const json = adapted[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" })\n expect(json).toEqual({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n })\n })\n\n it(\"composes with Tool.make so Input is inferred from the schema's Output\", async () => {\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: ({ to }) => Effect.succeed(`queued: ${to}`),\n })\n\n // `run`'s parameter is typed as { to: string } via the schema's Output —\n // this property access compiles without annotation.\n const result = await Effect.runPromise(sendEmail.run({ to: \"x@y.z\" }, () => Effect.void))\n expect(result).toBe(\"queued: x@y.z\")\n })\n\n it(\"type: rejects schemas missing the Standard JSON Schema half at compile time\", () => {\n // @ts-expect-error — `standardOnly` lacks `jsonSchema`; helper's\n // intersection constraint refuses it.\n Tool.fromStandardSchema(standardOnly)\n })\n\n it(\"type: Output type flows through fromStandardSchema into Tool.make\", () => {\n const tool = Tool.make({\n name: \"send_email\",\n description: \"send\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: (input) => Effect.succeed(input),\n })\n\n type InputOf<T> = T extends Tool.Tool<string, infer I, any, any, any> ? I : never\n expectTypeOf<InputOf<typeof tool>>().toEqualTypeOf<EmailRecipient>()\n })\n})\n\ndescribe(\"Tool.withRun\", () => {\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: ({ to }) => Effect.succeed(`sent: ${to}`),\n })\n\n it(\"swaps run while keeping the model-facing descriptor identical\", async () => {\n const dryRun = Tool.withRun(sendEmail, ({ to }) => Effect.succeed(`dry-run: ${to}`))\n\n // Definition (name/description/schema) is byte-identical -> same descriptor.\n expect(Tool.toDescriptors([dryRun])).toEqual(Tool.toDescriptors([sendEmail]))\n // input is typed from the original tool, no annotation needed.\n const out = await Effect.runPromise(dryRun.run({ to: \"x@y.z\" }, () => Effect.void))\n expect(out).toBe(\"dry-run: x@y.z\")\n })\n})\n\ndescribe(\"Tool.decodeArgs\", () => {\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: ({ to }) => Effect.succeed(`queued: ${to}`),\n })\n\n const callWith = (args: string): Items.ToolCall => ({\n type: \"function_call\",\n call_id: \"call_1\",\n name: \"send_email\",\n arguments: args,\n })\n\n it(\"decodes valid JSON arguments to the typed input\", async () => {\n const input = await Effect.runPromise(Tool.decodeArgs(sendEmail, callWith('{\"to\":\"x@y.z\"}')))\n expect(input).toEqual({ to: \"x@y.z\" })\n // The return type is the tool's Input, no annotation needed.\n expectTypeOf(input).toEqualTypeOf<EmailRecipient>()\n })\n\n it(\"fails with ToolError when the arguments aren't valid JSON\", async () => {\n const exit = await Effect.runPromiseExit(Tool.decodeArgs(sendEmail, callWith(\"not json\")))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"fails with ToolError when the arguments don't satisfy the schema\", async () => {\n const exit = await Effect.runPromiseExit(Tool.decodeArgs(sendEmail, callWith('{\"to\":42}')))\n expect(exit._tag).toBe(\"Failure\")\n })\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"Tool.test.mjs","names":["Tool.fromStandardSchema","Tool.make","Tool.withRun","Tool.toDescriptors","Tool.decodeArgs","Tool.fromEffectSchema","Tool.signal","Tool.provider","Tool.noInput","Tool.descriptorsOf","Tool.providerToolsOf","Tool.isProviderTool"],"sources":["../../src/tool/Tool.test.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Context, Effect, Schema } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as Items from \"../domain/Items.js\"\nimport * as Tool from \"./Tool.js\"\n\n// ---------------------------------------------------------------------------\n// Minimal dual-standard schema for testing — no need to pull Zod / Valibot /\n// ArkType as devDeps. This is the smallest possible object satisfying both\n// `StandardSchemaV1` and `StandardJSONSchemaV1` per their published specs.\n// ---------------------------------------------------------------------------\n\ntype EmailRecipient = { readonly to: string }\n\nconst emailRecipientSchema: StandardSchemaV1<unknown, EmailRecipient> &\n StandardJSONSchemaV1<unknown, EmailRecipient> = {\n \"~standard\": {\n version: 1,\n vendor: \"test-fixture\",\n validate: (value) => {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"to\" in value &&\n typeof (value as { to: unknown }).to === \"string\"\n ) {\n return { value: value as EmailRecipient }\n }\n return { issues: [{ message: \"expected { to: string }\" }] }\n },\n jsonSchema: {\n input: () => ({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n }),\n output: () => ({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n }),\n },\n },\n}\n\n// A schema that satisfies StandardSchemaV1 only (no JSON Schema). Used to\n// verify the helper's compile-time guard.\nconst standardOnly: StandardSchemaV1<unknown, EmailRecipient> = {\n \"~standard\": {\n version: 1,\n vendor: \"test-fixture\",\n validate: () => ({ value: { to: \"\" } }),\n },\n}\n\ndescribe(\"Tool.fromStandardSchema\", () => {\n it(\"returns the schema (structurally) typed as ToolInputSchema<Output>\", () => {\n const adapted = Tool.fromStandardSchema(emailRecipientSchema)\n\n // Same object — helper is a type-narrowing identity at runtime.\n expect(adapted).toBe(emailRecipientSchema)\n\n // Both interfaces accessible through the same `~standard` key.\n const valid = adapted[\"~standard\"].validate({ to: \"hi@example.com\" })\n expect(valid).toEqual({ value: { to: \"hi@example.com\" } })\n\n const json = adapted[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" })\n expect(json).toEqual({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n })\n })\n\n it(\"composes with Tool.make so Input is inferred from the schema's Output\", async () => {\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: ({ to }) => Effect.succeed(`queued: ${to}`),\n })\n\n // `run`'s parameter is typed as { to: string } via the schema's Output —\n // this property access compiles without annotation.\n const result = await Effect.runPromise(sendEmail.run({ to: \"x@y.z\" }, () => Effect.void))\n expect(result).toBe(\"queued: x@y.z\")\n })\n\n it(\"type: rejects schemas missing the Standard JSON Schema half at compile time\", () => {\n // @ts-expect-error — `standardOnly` lacks `jsonSchema`; helper's\n // intersection constraint refuses it.\n Tool.fromStandardSchema(standardOnly)\n })\n\n it(\"type: Output type flows through fromStandardSchema into Tool.make\", () => {\n const tool = Tool.make({\n name: \"send_email\",\n description: \"send\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: (input) => Effect.succeed(input),\n })\n\n type InputOf<T> = T extends Tool.Tool<string, infer I, any, any, any> ? I : never\n expectTypeOf<InputOf<typeof tool>>().toEqualTypeOf<EmailRecipient>()\n })\n})\n\ndescribe(\"Tool.withRun\", () => {\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: ({ to }) => Effect.succeed(`sent: ${to}`),\n })\n\n it(\"swaps run while keeping the model-facing descriptor identical\", async () => {\n const dryRun = Tool.withRun(sendEmail, ({ to }) => Effect.succeed(`dry-run: ${to}`))\n\n // Definition (name/description/schema) is byte-identical -> same descriptor.\n expect(Tool.toDescriptors([dryRun])).toEqual(Tool.toDescriptors([sendEmail]))\n // input is typed from the original tool, no annotation needed.\n const out = await Effect.runPromise(dryRun.run({ to: \"x@y.z\" }, () => Effect.void))\n expect(out).toBe(\"dry-run: x@y.z\")\n })\n})\n\ndescribe(\"Tool.decodeArgs\", () => {\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromStandardSchema(emailRecipientSchema),\n run: ({ to }) => Effect.succeed(`queued: ${to}`),\n })\n\n const callWith = (args: string): Items.ToolCall => ({\n type: \"function_call\",\n call_id: \"call_1\",\n name: \"send_email\",\n arguments: args,\n })\n\n it(\"decodes valid JSON arguments to the typed input\", async () => {\n const input = await Effect.runPromise(Tool.decodeArgs(sendEmail, callWith('{\"to\":\"x@y.z\"}')))\n expect(input).toEqual({ to: \"x@y.z\" })\n // The return type is the tool's Input, no annotation needed.\n expectTypeOf(input).toEqualTypeOf<EmailRecipient>()\n })\n\n it(\"fails with ToolError when the arguments aren't valid JSON\", async () => {\n const exit = await Effect.runPromiseExit(Tool.decodeArgs(sendEmail, callWith(\"not json\")))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"fails with ToolError when the arguments don't satisfy the schema\", async () => {\n const exit = await Effect.runPromiseExit(Tool.decodeArgs(sendEmail, callWith('{\"to\":42}')))\n expect(exit._tag).toBe(\"Failure\")\n })\n})\n\ndescribe(\"Effect Schema inputSchema\", () => {\n const Recipient = Schema.Struct({ to: Schema.String })\n\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Recipient,\n run: ({ to }) => Effect.succeed(`queued: ${to}`),\n })\n\n const callWith = (args: string): Items.ToolCall => ({\n type: \"function_call\",\n call_id: \"call_1\",\n name: \"send_email\",\n arguments: args,\n })\n\n it(\"runs with the input inferred from the schema's Type\", async () => {\n const result = await Effect.runPromise(sendEmail.run({ to: \"x@y.z\" }, () => Effect.void))\n expect(result).toBe(\"queued: x@y.z\")\n })\n\n it(\"type: Input is the schema's Type\", () => {\n type InputOf<T> = T extends Tool.Tool<string, infer I, any, any, any> ? I : never\n expectTypeOf<InputOf<typeof sendEmail>>().toEqualTypeOf<{ readonly to: string }>()\n })\n\n it(\"type: E and R are inferred from run through the Effect Schema overload\", () => {\n class Mailer extends Context.Service<\n Mailer,\n { readonly send: (to: string) => Effect.Effect<string> }\n >()(\"test/Mailer\") {}\n\n const tool = Tool.make({\n name: \"send_email\",\n description: \"send\",\n inputSchema: Recipient,\n run: ({ to }) =>\n Effect.gen(function* () {\n const mailer = yield* Mailer\n if (to === \"\") return yield* Effect.fail(\"empty\" as const)\n return yield* mailer.send(to)\n }),\n })\n\n expectTypeOf<Tool.ToolE<typeof tool>>().toEqualTypeOf<\"empty\">()\n expectTypeOf<Tool.ToolR<typeof tool>>().toEqualTypeOf<Mailer>()\n })\n\n it(\"decodes valid JSON arguments to the typed input\", async () => {\n const input = await Effect.runPromise(Tool.decodeArgs(sendEmail, callWith('{\"to\":\"x@y.z\"}')))\n expect(input).toEqual({ to: \"x@y.z\" })\n expectTypeOf(input).toEqualTypeOf<{ readonly to: string }>()\n })\n\n it(\"fails with ToolValidationError when the arguments don't satisfy the schema\", async () => {\n const exit = await Effect.runPromiseExit(Tool.decodeArgs(sendEmail, callWith('{\"to\":42}')))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"renders a JSON Schema descriptor from the wrapped schema\", () => {\n const [descriptor] = Tool.toDescriptors([sendEmail])\n expect(descriptor?.name).toBe(\"send_email\")\n expect(descriptor?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { to: { type: \"string\" } },\n required: [\"to\"],\n })\n })\n\n it(\"accepts an already-wrapped fromEffectSchema value unchanged\", async () => {\n const wrapped = Tool.make({\n name: \"send_email\",\n description: \"Send an email to a single recipient.\",\n inputSchema: Tool.fromEffectSchema(Recipient),\n run: ({ to }) => Effect.succeed(`queued: ${to}`),\n })\n expect(Tool.toDescriptors([wrapped])).toEqual(Tool.toDescriptors([sendEmail]))\n const input = await Effect.runPromise(Tool.decodeArgs(wrapped, callWith('{\"to\":\"x@y.z\"}')))\n expectTypeOf(input).toEqualTypeOf<{ readonly to: string }>()\n })\n\n it(\"signal: decode-only kinds accept an Effect Schema directly\", async () => {\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"Escalate to a human.\",\n inputSchema: Schema.Struct({ reason: Schema.String }),\n })\n const input = await Effect.runPromise(\n Tool.decodeArgs(escalate, {\n type: \"function_call\",\n call_id: \"call_2\",\n name: \"escalate\",\n arguments: '{\"reason\":\"stuck\"}',\n }),\n )\n expect(input).toEqual({ reason: \"stuck\" })\n expectTypeOf(input).toEqualTypeOf<{ readonly reason: string }>()\n })\n})\n\ndescribe(\"provider-hosted tools\", () => {\n const localTool = Tool.make({\n name: \"send_email\",\n description: \"Send an email.\",\n inputSchema: Tool.fromEffectSchema(Schema.Struct({ to: Schema.String })),\n run: ({ to }) => Effect.succeed(`sent: ${to}`),\n })\n const signalTool = Tool.signal({\n name: \"escalate\",\n description: \"Escalate to a human.\",\n inputSchema: Schema.Struct({ reason: Schema.String }),\n })\n const hostedTool = Tool.provider({\n name: \"web_search\",\n description: \"Search the web.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"google_search\" },\n })\n const toolkit = { send_email: localTool, escalate: signalTool, web_search: hostedTool }\n\n it(\"descriptorsOf excludes provider-hosted tools\", () => {\n const descriptors = Tool.descriptorsOf(toolkit)\n expect(descriptors.map((d) => d.name)).toEqual([\"send_email\", \"escalate\"])\n })\n\n it(\"toDescriptors drops a ProviderTool from a mixed array\", () => {\n expect(Tool.toDescriptors([localTool, hostedTool]).map((d) => d.name)).toEqual([\"send_email\"])\n })\n\n it(\"providerToolsOf returns only the provider-hosted tools\", () => {\n const provider = Tool.providerToolsOf(toolkit)\n expect(provider).toHaveLength(1)\n expect(provider[0]?.name).toBe(\"web_search\")\n expect(provider[0]?.provider).toBe(\"gemini\")\n expect(provider[0]?.config).toEqual({ kind: \"google_search\" })\n })\n\n it(\"providerToolsOf on an absent toolkit is empty\", () => {\n expect(Tool.providerToolsOf(undefined)).toEqual([])\n })\n\n it(\"isProviderTool narrows the tool kind\", () => {\n expect(Tool.isProviderTool(hostedTool)).toBe(true)\n expect(Tool.isProviderTool(localTool)).toBe(false)\n expect(Tool.isProviderTool(signalTool)).toBe(false)\n })\n})\n"],"mappings":"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"}