@effect-uai/core 0.12.0 → 0.13.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 (301) hide show
  1. package/dist/{AiError-Bf1pq4BB.d.mts → AiError-eLV9t1lU.d.mts} +12 -16
  2. package/dist/AiError-eLV9t1lU.d.mts.map +1 -0
  3. package/dist/{Audio-CR7CSljJ.d.mts → Audio-60lKKHxS.d.mts} +4 -5
  4. package/dist/Audio-60lKKHxS.d.mts.map +1 -0
  5. package/dist/{Image-C4vuBmu0.d.mts → Image-BK8-4H6s.d.mts} +12 -14
  6. package/dist/Image-BK8-4H6s.d.mts.map +1 -0
  7. package/dist/{Items-BKQgqJ8A.d.mts → Items-SfUabNuB.d.mts} +74 -76
  8. package/dist/Items-SfUabNuB.d.mts.map +1 -0
  9. package/dist/{Loop-D8nWpowc.d.mts → Loop-B54AB1ME.d.mts} +19 -21
  10. package/dist/Loop-B54AB1ME.d.mts.map +1 -0
  11. package/dist/{Media-D_CpcM1Z.d.mts → Media-DwgpifPD.d.mts} +1 -1
  12. package/dist/Media-DwgpifPD.d.mts.map +1 -0
  13. package/dist/{SandboxError-CIrVDOMV.d.mts → SandboxError-BN8b28Bh.d.mts} +11 -15
  14. package/dist/SandboxError-BN8b28Bh.d.mts.map +1 -0
  15. package/dist/{StructuredFormat-DHaUJBdd.d.mts → StructuredFormat-BlZhyOsi.d.mts} +3 -7
  16. package/dist/StructuredFormat-BlZhyOsi.d.mts.map +1 -0
  17. package/dist/{Tool-u5bag8SO.d.mts → Tool-DE-rnvkw.d.mts} +5 -8
  18. package/dist/Tool-DE-rnvkw.d.mts.map +1 -0
  19. package/dist/{ToolEvent-BBfOdhF_.d.mts → ToolEvent-D99MGQYH.d.mts} +56 -63
  20. package/dist/ToolEvent-D99MGQYH.d.mts.map +1 -0
  21. package/dist/{ToolResult-CRpmkBXR.d.mts → ToolResult-Cj8ChodL.d.mts} +49 -54
  22. package/dist/ToolResult-Cj8ChodL.d.mts.map +1 -0
  23. package/dist/{Toolkit-D4mQvhjq.d.mts → Toolkit-BEZrNPTI.d.mts} +13 -16
  24. package/dist/Toolkit-BEZrNPTI.d.mts.map +1 -0
  25. package/dist/browser/Browser.d.mts +11 -7
  26. package/dist/browser/Browser.d.mts.map +1 -1
  27. package/dist/browser/Browser.mjs +1 -1
  28. package/dist/browser/Browser.mjs.map +1 -1
  29. package/dist/browser/BrowserError.d.mts +9 -13
  30. package/dist/browser/BrowserError.d.mts.map +1 -1
  31. package/dist/browser/BrowserError.mjs +1 -1
  32. package/dist/browser/BrowserError.mjs.map +1 -1
  33. package/dist/browser/BrowserTool.d.mts +33 -36
  34. package/dist/browser/BrowserTool.d.mts.map +1 -1
  35. package/dist/browser/BrowserTool.mjs +1 -1
  36. package/dist/browser/BrowserTool.mjs.map +1 -1
  37. package/dist/capabilities/Capabilities.d.mts +8 -6
  38. package/dist/capabilities/Capabilities.d.mts.map +1 -1
  39. package/dist/capabilities/Capabilities.mjs +1 -1
  40. package/dist/capabilities/Capabilities.mjs.map +1 -1
  41. package/dist/chunker/Chunker.d.mts +32 -0
  42. package/dist/chunker/Chunker.d.mts.map +1 -0
  43. package/dist/chunker/Chunker.mjs +14 -0
  44. package/dist/chunker/Chunker.mjs.map +1 -0
  45. package/dist/dist-BaenoU1A.mjs +145 -0
  46. package/dist/dist-BaenoU1A.mjs.map +1 -0
  47. package/dist/{dist-C0m0QnUP.mjs → dist-nohcnkD6.mjs} +16 -15
  48. package/dist/dist-nohcnkD6.mjs.map +1 -0
  49. package/dist/domain/AiError.d.mts +1 -1
  50. package/dist/domain/AiError.mjs +1 -1
  51. package/dist/domain/AiError.mjs.map +1 -1
  52. package/dist/domain/Audio.d.mts +1 -1
  53. package/dist/domain/Audio.mjs +1 -1
  54. package/dist/domain/Citation.d.mts +0 -2
  55. package/dist/domain/Citation.d.mts.map +1 -1
  56. package/dist/domain/Citation.mjs +1 -1
  57. package/dist/domain/Image.d.mts +1 -1
  58. package/dist/domain/Image.mjs +1 -1
  59. package/dist/domain/Image.mjs.map +1 -1
  60. package/dist/domain/Items.d.mts +1 -1
  61. package/dist/domain/Items.mjs +1 -1
  62. package/dist/domain/Items.mjs.map +1 -1
  63. package/dist/domain/Media.d.mts +1 -1
  64. package/dist/domain/Media.mjs +1 -1
  65. package/dist/domain/Music.d.mts +18 -11
  66. package/dist/domain/Music.d.mts.map +1 -1
  67. package/dist/domain/Music.mjs +1 -1
  68. package/dist/domain/Music.mjs.map +1 -1
  69. package/dist/domain/Research.d.mts +3 -4
  70. package/dist/domain/Research.d.mts.map +1 -1
  71. package/dist/domain/Research.mjs +1 -1
  72. package/dist/domain/Transcript.d.mts +2 -3
  73. package/dist/domain/Transcript.d.mts.map +1 -1
  74. package/dist/domain/Transcript.mjs +1 -1
  75. package/dist/domain/Turn.d.mts +143 -164
  76. package/dist/domain/Turn.d.mts.map +1 -1
  77. package/dist/domain/Turn.mjs +1 -1
  78. package/dist/domain/Turn.mjs.map +1 -1
  79. package/dist/domain/Turn.test.d.mts +1 -1
  80. package/dist/domain/Turn.test.mjs +28 -18
  81. package/dist/domain/Turn.test.mjs.map +1 -1
  82. package/dist/embedding-model/Embedding.d.mts +1 -3
  83. package/dist/embedding-model/Embedding.d.mts.map +1 -1
  84. package/dist/embedding-model/Embedding.mjs +1 -1
  85. package/dist/embedding-model/EmbeddingModel.d.mts +1 -3
  86. package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
  87. package/dist/embedding-model/EmbeddingModel.mjs +1 -1
  88. package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
  89. package/dist/embedding-model/EmbeddingModel.test.d.mts +1 -1
  90. package/dist/embedding-model/EmbeddingModel.test.mjs +14 -9
  91. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  92. package/dist/index.d.mts +16 -13
  93. package/dist/index.mjs +4 -1
  94. package/dist/job/Job.d.mts +4 -4
  95. package/dist/job/Job.d.mts.map +1 -1
  96. package/dist/job/Job.mjs +1 -1
  97. package/dist/job/Job.mjs.map +1 -1
  98. package/dist/language-model/LanguageModel.d.mts +4 -6
  99. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  100. package/dist/language-model/LanguageModel.mjs +1 -1
  101. package/dist/language-model/LanguageModel.mjs.map +1 -1
  102. package/dist/language-model/LanguageModel.test.d.mts +1 -1
  103. package/dist/language-model/LanguageModel.test.mjs +6 -3
  104. package/dist/language-model/LanguageModel.test.mjs.map +1 -1
  105. package/dist/loop/Loop.d.mts +1 -1
  106. package/dist/loop/Loop.mjs +1 -1
  107. package/dist/loop/Loop.mjs.map +1 -1
  108. package/dist/loop/Loop.test.d.mts +1 -1
  109. package/dist/loop/Loop.test.mjs +60 -34
  110. package/dist/loop/Loop.test.mjs.map +1 -1
  111. package/dist/{magic-string.es-Dk-MMA0F.mjs → magic-string.es-CB2hOw9v.mjs} +14 -13
  112. package/dist/{magic-string.es-Dk-MMA0F.mjs.map → magic-string.es-CB2hOw9v.mjs.map} +1 -1
  113. package/dist/math/Vector.d.mts +0 -2
  114. package/dist/math/Vector.d.mts.map +1 -1
  115. package/dist/math/Vector.mjs +1 -1
  116. package/dist/math/Vector.mjs.map +1 -1
  117. package/dist/music-generator/MusicGenerator.d.mts +2 -4
  118. package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
  119. package/dist/music-generator/MusicGenerator.mjs +1 -1
  120. package/dist/music-generator/MusicGenerator.mjs.map +1 -1
  121. package/dist/music-generator/MusicGenerator.test.d.mts +1 -1
  122. package/dist/music-generator/MusicGenerator.test.mjs +19 -10
  123. package/dist/music-generator/MusicGenerator.test.mjs.map +1 -1
  124. package/dist/observability/Metrics.d.mts +10 -7
  125. package/dist/observability/Metrics.d.mts.map +1 -1
  126. package/dist/observability/Metrics.mjs +1 -1
  127. package/dist/observability/Metrics.mjs.map +1 -1
  128. package/dist/observability/Metrics.test.d.mts +1 -1
  129. package/dist/observability/Metrics.test.mjs +50 -28
  130. package/dist/observability/Metrics.test.mjs.map +1 -1
  131. package/dist/observability/Telemetry.d.mts +2 -3
  132. package/dist/observability/Telemetry.d.mts.map +1 -1
  133. package/dist/observability/Telemetry.mjs +1 -1
  134. package/dist/observability/Telemetry.mjs.map +1 -1
  135. package/dist/observability/Telemetry.test.d.mts +1 -1
  136. package/dist/observability/Telemetry.test.mjs +15 -7
  137. package/dist/observability/Telemetry.test.mjs.map +1 -1
  138. package/dist/reranker/Reranker.d.mts +46 -0
  139. package/dist/reranker/Reranker.d.mts.map +1 -0
  140. package/dist/reranker/Reranker.mjs +14 -0
  141. package/dist/reranker/Reranker.mjs.map +1 -0
  142. package/dist/research/DeepResearch.d.mts +1 -3
  143. package/dist/research/DeepResearch.d.mts.map +1 -1
  144. package/dist/research/DeepResearch.mjs +1 -1
  145. package/dist/research/DeepResearch.mjs.map +1 -1
  146. package/dist/retry/Retry.d.mts +2 -6
  147. package/dist/retry/Retry.d.mts.map +1 -1
  148. package/dist/retry/Retry.mjs +1 -1
  149. package/dist/retry/Retry.mjs.map +1 -1
  150. package/dist/retry/Retry.test.d.mts +1 -1
  151. package/dist/retry/Retry.test.mjs +6 -3
  152. package/dist/retry/Retry.test.mjs.map +1 -1
  153. package/dist/sandbox/Memory.d.mts +0 -2
  154. package/dist/sandbox/Memory.d.mts.map +1 -1
  155. package/dist/sandbox/Memory.mjs +4 -4
  156. package/dist/sandbox/Memory.mjs.map +1 -1
  157. package/dist/sandbox/Sandbox.d.mts +144 -156
  158. package/dist/sandbox/Sandbox.d.mts.map +1 -1
  159. package/dist/sandbox/Sandbox.mjs +1 -1
  160. package/dist/sandbox/Sandbox.mjs.map +1 -1
  161. package/dist/sandbox/SandboxError.d.mts +1 -1
  162. package/dist/sandbox/SandboxError.mjs +1 -1
  163. package/dist/sandbox/SandboxError.mjs.map +1 -1
  164. package/dist/sandbox/SandboxImage.d.mts +0 -2
  165. package/dist/sandbox/SandboxImage.d.mts.map +1 -1
  166. package/dist/sandbox/SandboxImage.mjs +1 -1
  167. package/dist/sandbox/SandboxImage.mjs.map +1 -1
  168. package/dist/sandbox/SandboxNetwork.d.mts +0 -2
  169. package/dist/sandbox/SandboxNetwork.d.mts.map +1 -1
  170. package/dist/sandbox/SandboxNetwork.mjs +1 -1
  171. package/dist/sandbox/SandboxNetwork.mjs.map +1 -1
  172. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +8 -7
  173. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
  174. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +1 -1
  175. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  176. package/dist/speech-synthesizer/SpeechSynthesizer.test.d.mts +1 -1
  177. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +31 -16
  178. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
  179. package/dist/streaming/JSONL.d.mts +1 -5
  180. package/dist/streaming/JSONL.d.mts.map +1 -1
  181. package/dist/streaming/JSONL.mjs +1 -1
  182. package/dist/streaming/JSONL.mjs.map +1 -1
  183. package/dist/streaming/JSONL.test.d.mts +1 -1
  184. package/dist/streaming/JSONL.test.mjs +16 -8
  185. package/dist/streaming/JSONL.test.mjs.map +1 -1
  186. package/dist/streaming/Lines.d.mts +0 -2
  187. package/dist/streaming/Lines.d.mts.map +1 -1
  188. package/dist/streaming/Lines.mjs +1 -1
  189. package/dist/streaming/Lines.mjs.map +1 -1
  190. package/dist/streaming/SSE.d.mts +0 -2
  191. package/dist/streaming/SSE.d.mts.map +1 -1
  192. package/dist/streaming/SSE.mjs +1 -1
  193. package/dist/streaming/SSE.mjs.map +1 -1
  194. package/dist/streaming/SSE.test.d.mts +1 -1
  195. package/dist/streaming/SSE.test.mjs +34 -17
  196. package/dist/streaming/SSE.test.mjs.map +1 -1
  197. package/dist/structured-format/StructuredFormat.d.mts +1 -1
  198. package/dist/structured-format/StructuredFormat.mjs +1 -1
  199. package/dist/structured-format/StructuredFormat.mjs.map +1 -1
  200. package/dist/structured-format/StructuredFormat.test.d.mts +1 -1
  201. package/dist/structured-format/StructuredFormat.test.mjs +7 -4
  202. package/dist/structured-format/StructuredFormat.test.mjs.map +1 -1
  203. package/dist/{dist-CcRV3-A5.mjs → test.DNmyFkvJ-YPzDR0Co.mjs} +1644 -5172
  204. package/dist/test.DNmyFkvJ-YPzDR0Co.mjs.map +1 -0
  205. package/dist/testing/MockMusicGenerator.d.mts +4 -3
  206. package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
  207. package/dist/testing/MockMusicGenerator.mjs.map +1 -1
  208. package/dist/testing/MockProvider.d.mts +1 -2
  209. package/dist/testing/MockProvider.d.mts.map +1 -1
  210. package/dist/testing/MockProvider.mjs.map +1 -1
  211. package/dist/testing/MockSandbox.d.mts +256 -290
  212. package/dist/testing/MockSandbox.d.mts.map +1 -1
  213. package/dist/testing/MockSandbox.mjs.map +1 -1
  214. package/dist/testing/MockSpeechSynthesizer.d.mts +10 -6
  215. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
  216. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
  217. package/dist/testing/MockTranscriber.d.mts +3 -2
  218. package/dist/testing/MockTranscriber.d.mts.map +1 -1
  219. package/dist/testing/MockTranscriber.mjs.map +1 -1
  220. package/dist/tokenizer/Tokenizer.d.mts +22 -0
  221. package/dist/tokenizer/Tokenizer.d.mts.map +1 -0
  222. package/dist/tokenizer/Tokenizer.mjs +9 -0
  223. package/dist/tokenizer/Tokenizer.mjs.map +1 -0
  224. package/dist/tool/Approval.d.mts +31 -36
  225. package/dist/tool/Approval.d.mts.map +1 -1
  226. package/dist/tool/Approval.mjs +1 -1
  227. package/dist/tool/Approval.mjs.map +1 -1
  228. package/dist/tool/Approval.test.d.mts +1 -1
  229. package/dist/tool/Approval.test.mjs +37 -24
  230. package/dist/tool/Approval.test.mjs.map +1 -1
  231. package/dist/tool/HistoryCheck.d.mts +2 -4
  232. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  233. package/dist/tool/HistoryCheck.mjs +1 -1
  234. package/dist/tool/HistoryCheck.mjs.map +1 -1
  235. package/dist/tool/Tool.d.mts +1 -1
  236. package/dist/tool/Tool.mjs +4 -4
  237. package/dist/tool/Tool.mjs.map +1 -1
  238. package/dist/tool/Tool.test.d.mts +1 -1
  239. package/dist/tool/Tool.test.mjs +23 -11
  240. package/dist/tool/Tool.test.mjs.map +1 -1
  241. package/dist/tool/ToolEvent.d.mts +1 -1
  242. package/dist/tool/ToolEvent.mjs +1 -1
  243. package/dist/tool/ToolEvent.mjs.map +1 -1
  244. package/dist/tool/ToolResult.d.mts +1 -1
  245. package/dist/tool/ToolResult.mjs +1 -1
  246. package/dist/tool/ToolResult.mjs.map +1 -1
  247. package/dist/tool/Toolkit.d.mts +1 -1
  248. package/dist/tool/Toolkit.mjs +4 -3
  249. package/dist/tool/Toolkit.mjs.map +1 -1
  250. package/dist/tool/Toolkit.test.d.mts +1 -1
  251. package/dist/tool/Toolkit.test.mjs +40 -21
  252. package/dist/tool/Toolkit.test.mjs.map +1 -1
  253. package/dist/transcriber/Transcriber.d.mts +6 -6
  254. package/dist/transcriber/Transcriber.d.mts.map +1 -1
  255. package/dist/transcriber/Transcriber.mjs +1 -1
  256. package/dist/transcriber/Transcriber.mjs.map +1 -1
  257. package/dist/transcriber/Transcriber.test.d.mts +1 -1
  258. package/dist/transcriber/Transcriber.test.mjs +20 -10
  259. package/dist/transcriber/Transcriber.test.mjs.map +1 -1
  260. package/dist/utils-CiH_DjFW.mjs +3918 -0
  261. package/dist/utils-CiH_DjFW.mjs.map +1 -0
  262. package/dist/web-read/WebRead.d.mts +15 -9
  263. package/dist/web-read/WebRead.d.mts.map +1 -1
  264. package/dist/web-read/WebRead.mjs +1 -1
  265. package/dist/web-read/WebRead.mjs.map +1 -1
  266. package/dist/web-read/WebReadTool.d.mts +4 -5
  267. package/dist/web-read/WebReadTool.d.mts.map +1 -1
  268. package/dist/web-read/WebReadTool.mjs +1 -1
  269. package/dist/web-read/WebReadTool.mjs.map +1 -1
  270. package/dist/web-search/WebSearch.d.mts +22 -13
  271. package/dist/web-search/WebSearch.d.mts.map +1 -1
  272. package/dist/web-search/WebSearch.mjs +1 -1
  273. package/dist/web-search/WebSearch.mjs.map +1 -1
  274. package/dist/web-search/WebSearchTool.d.mts +6 -6
  275. package/dist/web-search/WebSearchTool.d.mts.map +1 -1
  276. package/dist/web-search/WebSearchTool.mjs +1 -1
  277. package/dist/web-search/WebSearchTool.mjs.map +1 -1
  278. package/package.json +16 -4
  279. package/src/chunker/Chunker.ts +34 -0
  280. package/src/index.ts +3 -0
  281. package/src/loop/Loop.test.ts +13 -14
  282. package/src/loop/Loop.ts +74 -76
  283. package/src/reranker/Reranker.ts +51 -0
  284. package/src/testing/MockProvider.ts +8 -9
  285. package/src/tokenizer/Tokenizer.ts +19 -0
  286. package/src/tool/Tool.ts +13 -13
  287. package/dist/AiError-Bf1pq4BB.d.mts.map +0 -1
  288. package/dist/Audio-CR7CSljJ.d.mts.map +0 -1
  289. package/dist/Image-C4vuBmu0.d.mts.map +0 -1
  290. package/dist/Items-BKQgqJ8A.d.mts.map +0 -1
  291. package/dist/Loop-D8nWpowc.d.mts.map +0 -1
  292. package/dist/Media-D_CpcM1Z.d.mts.map +0 -1
  293. package/dist/SandboxError-CIrVDOMV.d.mts.map +0 -1
  294. package/dist/StructuredFormat-DHaUJBdd.d.mts.map +0 -1
  295. package/dist/Tool-u5bag8SO.d.mts.map +0 -1
  296. package/dist/ToolEvent-BBfOdhF_.d.mts.map +0 -1
  297. package/dist/ToolResult-CRpmkBXR.d.mts.map +0 -1
  298. package/dist/Toolkit-D4mQvhjq.d.mts.map +0 -1
  299. package/dist/dist-C0m0QnUP.mjs.map +0 -1
  300. package/dist/dist-CcRV3-A5.mjs.map +0 -1
  301. /package/dist/{chunk-uyGKjUfl.mjs → rolldown-runtime-EAsCxrDo.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"MusicGenerator.test.mjs","names":["MockMusicGenerator.layer","MusicGenerator.generate","MusicGenerator.streamGeneration","MusicGenerator.streamGenerationFrom","MockMusicGenerator.layerWithoutInteractive"],"sources":["../../src/music-generator/MusicGenerator.test.ts"],"sourcesContent":["import { Duration, Effect, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type { AudioChunk, AudioFormat } from \"../domain/Audio.js\"\nimport {\n audioEvent,\n controlInput,\n type GenerateResult,\n type MusicResult,\n type MusicStreamEvent,\n promptsInput,\n singleVariant,\n} from \"../domain/Music.js\"\nimport * as MockMusicGenerator from \"../testing/MockMusicGenerator.js\"\nimport * as MusicGenerator from \"./MusicGenerator.js\"\n\nconst mp3Format: AudioFormat = {\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n channels: 2,\n}\n\nconst oneResult: MusicResult = {\n audio: {\n format: mp3Format,\n bytes: new Uint8Array([0xff, 0xfb, 0x90, 0x00]),\n duration: Duration.seconds(30),\n },\n provider: \"mock-music\",\n lyrics: \"[Verse]\\nhello\\n\",\n watermark: \"synthid\",\n}\n\nconst oneVariant: GenerateResult = singleVariant(oneResult)\n\nconst chunk = (n: number): AudioChunk => ({ bytes: new Uint8Array([n]) })\n\ndescribe(\"MusicGenerator.generate\", () => {\n it(\"returns the scripted GenerateResult\", async () => {\n const mock = MockMusicGenerator.layer({ results: [oneVariant] })\n const program = MusicGenerator.generate({\n model: \"mock-music\",\n prompt: \"upbeat indie pop\",\n })\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.primary.audio.bytes).toEqual(oneResult.audio.bytes)\n expect(out.primary.audio.duration).toEqual(Duration.seconds(30))\n expect(out.primary.watermark).toBe(\"synthid\")\n expect(out.primary.lyrics).toContain(\"[Verse]\")\n expect(out.variants).toHaveLength(1)\n expect(out.variants[0]).toBe(out.primary)\n })\n\n it(\"exposes multiple variants when the provider returned more than one\", async () => {\n const secondTrack: MusicResult = {\n ...oneResult,\n audio: { ...oneResult.audio, bytes: new Uint8Array([0xff, 0xfb, 0xa0, 0x00]) },\n }\n const twoVariants: GenerateResult = {\n primary: oneResult,\n variants: [oneResult, secondTrack],\n }\n const mock = MockMusicGenerator.layer({ results: [twoVariants] })\n const program = MusicGenerator.generate({ model: \"m\", prompt: \"house\" })\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.variants).toHaveLength(2)\n expect(out.variants[1]!.audio.bytes).toEqual(secondTrack.audio.bytes)\n })\n\n it(\"records the request shape on the recorder\", async () => {\n const mock = MockMusicGenerator.layer({ results: [oneVariant, oneVariant] })\n const program = Effect.gen(function* () {\n yield* MusicGenerator.generate({ model: \"m\", prompt: \"techno\" })\n yield* MusicGenerator.generate({\n model: \"m\",\n prompt: \"synthwave\",\n duration: Duration.seconds(45),\n seed: 42,\n })\n return yield* mock.recorder\n })\n const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(rec.generateCalls.length).toBe(2)\n expect(rec.generateCalls[1]!.duration).toEqual(Duration.seconds(45))\n expect(rec.generateCalls[1]!.seed).toBe(42)\n })\n})\n\ndescribe(\"MusicGenerator.streamGeneration\", () => {\n it(\"emits scripted chunks\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationChunks: [[chunk(1), chunk(2), chunk(3)]],\n })\n const program = Stream.runCollect(\n MusicGenerator.streamGeneration({ model: \"m\", prompt: \"ambient\" }),\n )\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.map((c) => Array.from(c.bytes))).toEqual([[1], [2], [3]])\n })\n})\n\ndescribe(\"MusicGenerator capability marker (compile-time)\", () => {\n const sgfReq: MusicGenerator.CommonStreamGenerateMusicRequest = {\n model: \"m\",\n prompt: \"\",\n }\n\n it(\"requires `MusicInteractiveSession` on the R channel of streamGenerationFrom\", () => {\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n expectTypeOf(events).toEqualTypeOf<\n Stream.Stream<\n MusicStreamEvent,\n AiError.AiError,\n MusicGenerator.MusicGenerator | MusicGenerator.MusicInteractiveSession\n >\n >()\n })\n\n it(\"does NOT require `MusicInteractiveSession` for sync `generate`\", () => {\n const eff = MusicGenerator.generate({ model: \"m\", prompt: \"ambient\" })\n expectTypeOf(eff).toEqualTypeOf<\n Effect.Effect<GenerateResult, AiError.AiError, MusicGenerator.MusicGenerator>\n >()\n })\n\n it(\"does NOT require `MusicInteractiveSession` for `streamGeneration`\", () => {\n const audio = MusicGenerator.streamGeneration({ model: \"m\", prompt: \"ambient\" })\n expectTypeOf(audio).toEqualTypeOf<\n Stream.Stream<AudioChunk, AiError.AiError, MusicGenerator.MusicGenerator>\n >()\n })\n\n it(\"a layer without the marker leaves `MusicInteractiveSession` unsatisfied in R\", () => {\n const noMarker = MockMusicGenerator.layerWithoutInteractive({})\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const program = Stream.runDrain(events).pipe(Effect.provide(noMarker.layer))\n expectTypeOf(program).toEqualTypeOf<\n Effect.Effect<void, AiError.AiError, MusicGenerator.MusicInteractiveSession>\n >()\n })\n\n it(\"a full layer (with marker) clears R to never\", () => {\n const fullMock = MockMusicGenerator.layer({\n streamGenerationFromEvents: [[]],\n })\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const program = Stream.runDrain(events).pipe(Effect.provide(fullMock.layer))\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n})\n\ndescribe(\"MusicGenerator.streamGenerationFrom\", () => {\n const sgfReq: MusicGenerator.CommonStreamGenerateMusicRequest = {\n model: \"lyria-realtime-exp\",\n prompt: \"\",\n }\n\n it(\"drains a session-input stream and emits scripted events\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationFromEvents: [[audioEvent(chunk(10)), audioEvent(chunk(20))]],\n })\n const inputs = Stream.fromIterable([\n promptsInput([{ text: \"minimal techno\", weight: 1.0 }]),\n controlInput(\"play\"),\n promptsInput([\n { text: \"minimal techno\", weight: 1.0 },\n { text: \"1980s synthwave\", weight: 0.3 },\n ]),\n ])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const out = await Effect.runPromise(Stream.runCollect(events).pipe(Effect.provide(mock.layer)))\n const audioChunks = out.flatMap((e) => (e._tag === \"audio\" ? [e.chunk] : []))\n expect(audioChunks.map((c) => Array.from(c.bytes))).toEqual([[10], [20]])\n })\n\n it(\"records the request on the streamGenerationFrom call channel\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationFromEvents: [[audioEvent(chunk(42))]],\n })\n const program = Effect.gen(function* () {\n const inputs = Stream.fromIterable([promptsInput([{ text: \"x\" }])])\n yield* Stream.runDrain(inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq)))\n return yield* mock.recorder\n })\n const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(rec.streamGenerationFromCalls.length).toBe(1)\n expect(rec.streamGenerationFromCalls[0]!.model).toBe(\"lyria-realtime-exp\")\n expect(rec.generateCalls.length).toBe(0)\n expect(rec.streamGenerationCalls.length).toBe(0)\n })\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"MusicGenerator.test.mjs","names":["MockMusicGenerator.layer","MusicGenerator.generate","MusicGenerator.streamGeneration","MusicGenerator.streamGenerationFrom","MockMusicGenerator.layerWithoutInteractive"],"sources":["../../src/music-generator/MusicGenerator.test.ts"],"sourcesContent":["import { Duration, Effect, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type { AudioChunk, AudioFormat } from \"../domain/Audio.js\"\nimport {\n audioEvent,\n controlInput,\n type GenerateResult,\n type MusicResult,\n type MusicStreamEvent,\n promptsInput,\n singleVariant,\n} from \"../domain/Music.js\"\nimport * as MockMusicGenerator from \"../testing/MockMusicGenerator.js\"\nimport * as MusicGenerator from \"./MusicGenerator.js\"\n\nconst mp3Format: AudioFormat = {\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n channels: 2,\n}\n\nconst oneResult: MusicResult = {\n audio: {\n format: mp3Format,\n bytes: new Uint8Array([0xff, 0xfb, 0x90, 0x00]),\n duration: Duration.seconds(30),\n },\n provider: \"mock-music\",\n lyrics: \"[Verse]\\nhello\\n\",\n watermark: \"synthid\",\n}\n\nconst oneVariant: GenerateResult = singleVariant(oneResult)\n\nconst chunk = (n: number): AudioChunk => ({ bytes: new Uint8Array([n]) })\n\ndescribe(\"MusicGenerator.generate\", () => {\n it(\"returns the scripted GenerateResult\", async () => {\n const mock = MockMusicGenerator.layer({ results: [oneVariant] })\n const program = MusicGenerator.generate({\n model: \"mock-music\",\n prompt: \"upbeat indie pop\",\n })\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.primary.audio.bytes).toEqual(oneResult.audio.bytes)\n expect(out.primary.audio.duration).toEqual(Duration.seconds(30))\n expect(out.primary.watermark).toBe(\"synthid\")\n expect(out.primary.lyrics).toContain(\"[Verse]\")\n expect(out.variants).toHaveLength(1)\n expect(out.variants[0]).toBe(out.primary)\n })\n\n it(\"exposes multiple variants when the provider returned more than one\", async () => {\n const secondTrack: MusicResult = {\n ...oneResult,\n audio: { ...oneResult.audio, bytes: new Uint8Array([0xff, 0xfb, 0xa0, 0x00]) },\n }\n const twoVariants: GenerateResult = {\n primary: oneResult,\n variants: [oneResult, secondTrack],\n }\n const mock = MockMusicGenerator.layer({ results: [twoVariants] })\n const program = MusicGenerator.generate({ model: \"m\", prompt: \"house\" })\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.variants).toHaveLength(2)\n expect(out.variants[1]!.audio.bytes).toEqual(secondTrack.audio.bytes)\n })\n\n it(\"records the request shape on the recorder\", async () => {\n const mock = MockMusicGenerator.layer({ results: [oneVariant, oneVariant] })\n const program = Effect.gen(function* () {\n yield* MusicGenerator.generate({ model: \"m\", prompt: \"techno\" })\n yield* MusicGenerator.generate({\n model: \"m\",\n prompt: \"synthwave\",\n duration: Duration.seconds(45),\n seed: 42,\n })\n return yield* mock.recorder\n })\n const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(rec.generateCalls.length).toBe(2)\n expect(rec.generateCalls[1]!.duration).toEqual(Duration.seconds(45))\n expect(rec.generateCalls[1]!.seed).toBe(42)\n })\n})\n\ndescribe(\"MusicGenerator.streamGeneration\", () => {\n it(\"emits scripted chunks\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationChunks: [[chunk(1), chunk(2), chunk(3)]],\n })\n const program = Stream.runCollect(\n MusicGenerator.streamGeneration({ model: \"m\", prompt: \"ambient\" }),\n )\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.map((c) => Array.from(c.bytes))).toEqual([[1], [2], [3]])\n })\n})\n\ndescribe(\"MusicGenerator capability marker (compile-time)\", () => {\n const sgfReq: MusicGenerator.CommonStreamGenerateMusicRequest = {\n model: \"m\",\n prompt: \"\",\n }\n\n it(\"requires `MusicInteractiveSession` on the R channel of streamGenerationFrom\", () => {\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n expectTypeOf(events).toEqualTypeOf<\n Stream.Stream<\n MusicStreamEvent,\n AiError.AiError,\n MusicGenerator.MusicGenerator | MusicGenerator.MusicInteractiveSession\n >\n >()\n })\n\n it(\"does NOT require `MusicInteractiveSession` for sync `generate`\", () => {\n const eff = MusicGenerator.generate({ model: \"m\", prompt: \"ambient\" })\n expectTypeOf(eff).toEqualTypeOf<\n Effect.Effect<GenerateResult, AiError.AiError, MusicGenerator.MusicGenerator>\n >()\n })\n\n it(\"does NOT require `MusicInteractiveSession` for `streamGeneration`\", () => {\n const audio = MusicGenerator.streamGeneration({ model: \"m\", prompt: \"ambient\" })\n expectTypeOf(audio).toEqualTypeOf<\n Stream.Stream<AudioChunk, AiError.AiError, MusicGenerator.MusicGenerator>\n >()\n })\n\n it(\"a layer without the marker leaves `MusicInteractiveSession` unsatisfied in R\", () => {\n const noMarker = MockMusicGenerator.layerWithoutInteractive({})\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const program = Stream.runDrain(events).pipe(Effect.provide(noMarker.layer))\n expectTypeOf(program).toEqualTypeOf<\n Effect.Effect<void, AiError.AiError, MusicGenerator.MusicInteractiveSession>\n >()\n })\n\n it(\"a full layer (with marker) clears R to never\", () => {\n const fullMock = MockMusicGenerator.layer({\n streamGenerationFromEvents: [[]],\n })\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const program = Stream.runDrain(events).pipe(Effect.provide(fullMock.layer))\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n})\n\ndescribe(\"MusicGenerator.streamGenerationFrom\", () => {\n const sgfReq: MusicGenerator.CommonStreamGenerateMusicRequest = {\n model: \"lyria-realtime-exp\",\n prompt: \"\",\n }\n\n it(\"drains a session-input stream and emits scripted events\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationFromEvents: [[audioEvent(chunk(10)), audioEvent(chunk(20))]],\n })\n const inputs = Stream.fromIterable([\n promptsInput([{ text: \"minimal techno\", weight: 1.0 }]),\n controlInput(\"play\"),\n promptsInput([\n { text: \"minimal techno\", weight: 1.0 },\n { text: \"1980s synthwave\", weight: 0.3 },\n ]),\n ])\n const events = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const out = await Effect.runPromise(Stream.runCollect(events).pipe(Effect.provide(mock.layer)))\n const audioChunks = out.flatMap((e) => (e._tag === \"audio\" ? [e.chunk] : []))\n expect(audioChunks.map((c) => Array.from(c.bytes))).toEqual([[10], [20]])\n })\n\n it(\"records the request on the streamGenerationFrom call channel\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationFromEvents: [[audioEvent(chunk(42))]],\n })\n const program = Effect.gen(function* () {\n const inputs = Stream.fromIterable([promptsInput([{ text: \"x\" }])])\n yield* Stream.runDrain(inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq)))\n return yield* mock.recorder\n })\n const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(rec.streamGenerationFromCalls.length).toBe(1)\n expect(rec.streamGenerationFromCalls[0]!.model).toBe(\"lyria-realtime-exp\")\n expect(rec.generateCalls.length).toBe(0)\n expect(rec.streamGenerationCalls.length).toBe(0)\n })\n})\n"],"mappings":"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"}
@@ -1,8 +1,6 @@
1
- import { x as Usage } from "../Items-BKQgqJ8A.mjs";
1
+ import { x as Usage } from "../Items-SfUabNuB.mjs";
2
2
  import { TurnEvent } from "../domain/Turn.mjs";
3
3
  import { Duration, Effect, Option, Stream } from "effect";
4
-
5
- //#region src/observability/Metrics.d.ts
6
4
  declare namespace Metrics_d_exports {
7
5
  export { AllMetricsOptions, Measurement, MetricEvent, MetricEventTypeId, OutputDelta, RateState, Throughput, ThroughputOptions, TimeToCompletion, TimeToFirstToken, TimeToFirstTokenOptions, TokenTotals, allMetrics, computeThroughputTick, isMetricEvent, makeEvent, metricEvents, throughput, timeToCompletion, timeToFirstToken, tokenTotals };
8
6
  }
@@ -32,7 +30,8 @@ declare const MetricEventTypeId: unique symbol;
32
30
  */
33
31
  type MetricEvent = {
34
32
  readonly [MetricEventTypeId]: typeof MetricEventTypeId;
35
- readonly _tag: string; /** Which turn within the piped stream this sample belongs to (0-based). */
33
+ readonly _tag: string;
34
+ /** Which turn within the piped stream this sample belongs to (0-based). */
36
35
  readonly turnIndex: number;
37
36
  readonly measurements: ReadonlyArray<Measurement>;
38
37
  };
@@ -94,14 +93,18 @@ declare const timeToFirstToken: (options?: TimeToFirstTokenOptions) => <A, E, R>
94
93
  declare const timeToCompletion: <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A | MetricEvent, E, R>;
95
94
  declare const tokenTotals: <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A | MetricEvent, E, R>;
96
95
  type ThroughputOptions = {
97
- /** Tick interval. Default `"1 second"`. */readonly every?: Duration.Input; /** What a "unit" is. Default `"char"` (exact and chunking-independent). */
96
+ /** Tick interval. Default `"1 second"`. */
97
+ readonly every?: Duration.Input;
98
+ /** What a "unit" is. Default `"char"` (exact and chunking-independent). */
98
99
  readonly unit?: "char" | "token" | "event";
99
100
  /**
100
101
  * Token counter for `unit: "token"`. Called only for deltas that carry
101
102
  * generated output. Absent => approximate 1 per such delta.
102
103
  */
103
- readonly tokenizer?: (event: OutputDelta) => Effect.Effect<number>; /** Rate definition. Default `"windowed"` (current speed, no cold-start bias). */
104
- readonly mode?: "windowed" | "cumulative"; /** EWMA smoothing. Omitted => off; `"default"` => a sensible factor. */
104
+ readonly tokenizer?: (event: OutputDelta) => Effect.Effect<number>;
105
+ /** Rate definition. Default `"windowed"` (current speed, no cold-start bias). */
106
+ readonly mode?: "windowed" | "cumulative";
107
+ /** EWMA smoothing. Omitted => off; `"default"` => a sensible factor. */
105
108
  readonly smooth?: "default" | number;
106
109
  };
107
110
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Metrics.d.mts","names":[],"sources":["../../src/observability/Metrics.ts"],"mappings":"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"}
1
+ {"version":3,"file":"Metrics.d.mts","names":[],"sources":["../../src/observability/Metrics.ts"],"mappings":";;;;;;;;;;;;KA0BY;WACD;WACA;WACA,gBAAgB,SAAS;WACzB,aAAa,SAAS;;;;;;;;cAapB;;;;;;KASD;YACA,2BAA2B;WAC5B;;WAEA;WACA,cAAc,cAAc;;;KAI3B,mBAAmB;WACpB;WACA,SAAS,SAAS;WAClB;;;KAIC,aAAa;WACd;WACA;WACA;WACA,QAAQ,SAAS;;;KAIhB,cAAc;WACf;WACA,OAAO;WACP,YAAY;;;KAIX,mBAAmB;WACpB;WACA,UAAU,SAAS;WACnB,YAAY,SAAS;;;;;;;;cASnB,YAAa,eAAe,KAAK,oBAAoB,oBAAkB,QAC1E,WACP;YAAqB,2BAA2B;;;cAMtC,gBAAa,eAAiB,KAAK;;;;;cAOnC,eAAgB,GAAG,GAAG,GAAC,MAC5B,OAAO,OAAO,GAAG,GAAG,OACzB,OAAO,OAAO,aAAa,GAAG;;KAsCrB,cAAc,QACxB;WACW;;KA6ED;;;;;;;WAOD;;cAUE,mBAAgB,UAChB,6BACV,GAAG,GAAG,GAAC,MAAQ,OAAO,OAAO,GAAG,GAAG,OAAK,OAAO,OAAO,IAAI,aAAa,GAAG;cAwDhE,mBAAoB,GAAG,GAAG,GAAC,MAChC,OAAO,OAAO,GAAG,GAAG,OACzB,OAAO,OAAO,IAAI,aAAa,GAAG;cAqDxB,cAAe,GAAG,GAAG,GAAC,MAC3B,OAAO,OAAO,GAAG,GAAG,OACzB,OAAO,OAAO,IAAI,aAAa,GAAG;KAyBzB;;WAED,QAAQ,SAAS;;WAEjB;;;;;WAKA,aAAa,OAAO,gBAAgB,OAAO;;WAE3C;;WAEA;;;;;;;;KASC;WACD;WACA;WACA;WACA,aAAa,OAAO;WACpB,UAAU,OAAO;WACjB;;;;;;;;;cAsBE,wBAAqB,OACzB,WAAS,aACL,iCACoB,+BAE9B,OAAO;WACC;WACA,QAAQ,SAAS;WACjB,MAAM;;cAiDJ,aAAU,UACV,uBACV,GAAG,GAAG,GAAC,MAAQ,OAAO,OAAO,GAAG,GAAG,OAAK,OAAO,OAAO,IAAI,aAAa,GAAG;KA4DjE;WACD,mBAAmB;WACnB,aAAa;;cAGX,aAAU,UACV,uBACV,GAAG,GAAG,GAAC,MAAQ,OAAO,OAAO,GAAG,GAAG,OAAK,OAAO,OAAO,IAAI,aAAa,GAAG"}
@@ -1,4 +1,4 @@
1
- import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { TurnEvent } from "../domain/Turn.mjs";
3
3
  import { Array, Clock, Duration, Effect, Match, Option, Predicate, Ref, Result, Schedule, Stream } from "effect";
4
4
  //#region src/observability/Metrics.ts
@@ -1 +1 @@
1
- {"version":3,"file":"Metrics.mjs","names":["Arr"],"sources":["../../src/observability/Metrics.ts"],"sourcesContent":["import {\n Array as Arr,\n Clock,\n Duration,\n Effect,\n Match,\n Option,\n Predicate,\n Ref,\n Result,\n Schedule,\n Stream,\n} from \"effect\"\nimport type { Usage } from \"../domain/Items.js\"\nimport { type Turn, TurnEvent } from \"../domain/Turn.js\"\n\n// ---------------------------------------------------------------------------\n// Self-describing measurements (the exporter-facing payload)\n// ---------------------------------------------------------------------------\n\n/**\n * The instrument-agnostic description of a single recorded value. The\n * `Telemetry` recorder reads only this: it builds (or reuses) a `Metric`\n * instrument from `kind` + `name` and updates it with `value`. A new metric,\n * built-in or user-defined, never requires a change to the recorder.\n */\nexport type Measurement = {\n readonly name: string\n readonly kind: \"counter\" | \"histogram\" | \"timer\" | \"gauge\"\n readonly value: number | Duration.Duration\n readonly attributes?: Readonly<Record<string, string>> | undefined\n}\n\n// ---------------------------------------------------------------------------\n// MetricEvent: an open, brand-marked union (built-ins + user metrics)\n// ---------------------------------------------------------------------------\n\n/**\n * Brand stamped on every metric event. `isMetricEvent` checks this symbol,\n * not an enumerated `_tag`, so events are distinguished from `TurnEvent`s,\n * tool outputs, and arbitrary custom loop values regardless of their tag.\n * This is the same TypeId-symbol pattern Effect uses for `isStream` etc.\n */\nexport const MetricEventTypeId: unique symbol = Symbol.for(\n \"@effect-uai/core/observability/MetricEvent\",\n)\n\n/**\n * The structural contract every metric event satisfies, ours and custom.\n * The `_tag` + extra typed fields serve a frontend; the brand + `measurements`\n * serve the exporter.\n */\nexport type MetricEvent = {\n readonly [MetricEventTypeId]: typeof MetricEventTypeId\n readonly _tag: string\n /** Which turn within the piped stream this sample belongs to (0-based). */\n readonly turnIndex: number\n readonly measurements: ReadonlyArray<Measurement>\n}\n\n/** Request/turn start to the first content delta. */\nexport type TimeToFirstToken = MetricEvent & {\n readonly _tag: \"TimeToFirstToken\"\n readonly elapsed: Duration.Duration\n readonly kind: \"text\" | \"reasoning\"\n}\n\n/** A live output rate, emitted on the metronome cadence. */\nexport type Throughput = MetricEvent & {\n readonly _tag: \"Throughput\"\n readonly ratePerSecond: number\n readonly unit: \"char\" | \"token\" | \"event\"\n readonly window: Duration.Duration\n}\n\n/** Provider-reported usage at `TurnComplete`, this turn and cumulative. */\nexport type TokenTotals = MetricEvent & {\n readonly _tag: \"TokenTotals\"\n readonly usage: Usage\n readonly cumulative: Usage\n}\n\n/** Per-turn wall times at `TurnComplete`. */\nexport type TimeToCompletion = MetricEvent & {\n readonly _tag: \"TimeToCompletion\"\n readonly duration: Duration.Duration\n readonly generation: Duration.Duration\n}\n\n/**\n * Mint a custom metric event. Stamp it with the brand and provide a\n * well-formed `measurements` array; `Telemetry.record` will export it with no\n * changes to the recorder. The only constraint is that each measurement's\n * `kind` is one the recorder maps.\n */\nexport const makeEvent = <Fields extends Omit<MetricEvent, typeof MetricEventTypeId>>(\n fields: Fields,\n): Fields & { readonly [MetricEventTypeId]: typeof MetricEventTypeId } => ({\n [MetricEventTypeId]: MetricEventTypeId,\n ...fields,\n})\n\n/** Structural (brand-based) guard for any metric event, built-in or custom. */\nexport const isMetricEvent = (u: unknown): u is MetricEvent =>\n Predicate.hasProperty(u, MetricEventTypeId)\n\n/**\n * Project a stream of mixed events onto just its metric samples, for a side\n * channel or a frontend feed.\n */\nexport const metricEvents = <A, E, R>(\n self: Stream.Stream<A, E, R>,\n): Stream.Stream<MetricEvent, E, R> =>\n self.pipe(Stream.filterMap((ev) => (isMetricEvent(ev) ? Result.succeed(ev) : Result.failVoid)))\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst MetricName = {\n ttft: \"effect_uai_turn_ttft\",\n duration: \"effect_uai_turn_duration\",\n generation: \"effect_uai_turn_generation\",\n inputTokens: \"effect_uai_input_tokens\",\n outputTokens: \"effect_uai_output_tokens\",\n totalTokens: \"effect_uai_total_tokens\",\n reasoningTokens: \"effect_uai_reasoning_tokens\",\n cachedInputTokens: \"effect_uai_cached_input_tokens\",\n cacheWriteTokens: \"effect_uai_cache_write_tokens\",\n} as const\n\n/** Read a structural `_tag` off any value without narrowing to a closed union. */\nconst tagOf = (u: unknown): string | undefined =>\n Predicate.hasProperty(u, \"_tag\") && Predicate.isString(u._tag) ? u._tag : undefined\n\nconst contentDeltaKind = (ev: unknown): Option.Option<\"text\" | \"reasoning\"> => {\n const t = tagOf(ev)\n return t === \"TextDelta\"\n ? Option.some(\"text\")\n : t === \"ReasoningDelta\"\n ? Option.some(\"reasoning\")\n : Option.none()\n}\n\nconst isTextDelta = TurnEvent.$is(\"TextDelta\")\nconst isReasoningDelta = TurnEvent.$is(\"ReasoningDelta\")\nconst isRefusalDelta = TurnEvent.$is(\"RefusalDelta\")\nconst isToolCallArgsDelta = TurnEvent.$is(\"ToolCallArgsDelta\")\n\n/** A delta carrying generated output, whatever the model was producing. */\nexport type OutputDelta = Extract<\n TurnEvent,\n { readonly _tag: \"TextDelta\" | \"ReasoningDelta\" | \"RefusalDelta\" | \"ToolCallArgsDelta\" }\n>\n\n/**\n * Narrow to a delta that contributes to the model's output stream. Tool-call\n * arguments count: an agent turn can be almost entirely `ToolCallArgsDelta`,\n * and measuring only prose reports a near-zero rate for it.\n *\n * The single place the output-delta tag list lives — the throughput\n * accumulator and `unitCount` both read it. Distinct from `contentDeltaKind`,\n * which answers a narrower question for first-token latency.\n */\nconst outputDelta = (ev: unknown): Option.Option<OutputDelta> =>\n isTextDelta(ev) || isReasoningDelta(ev) || isRefusalDelta(ev) || isToolCallArgsDelta(ev)\n ? Option.some(ev)\n : Option.none()\n\nconst outputTextOf = (delta: OutputDelta): string =>\n isToolCallArgsDelta(delta) ? delta.delta : delta.text\n\nconst isTurnCompleteEvent = (ev: unknown): boolean => tagOf(ev) === \"TurnComplete\"\n\nconst turnOf = (ev: unknown): Turn =>\n (ev as Extract<TurnEvent, { readonly _tag: \"TurnComplete\" }>).turn\n\nconst sumOptional = (a: number | undefined, b: number | undefined): number | undefined =>\n a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0)\n\nconst addUsage = (a: Usage, b: Usage): Usage => {\n const cached = sumOptional(\n a.input_tokens_details?.cached_tokens,\n b.input_tokens_details?.cached_tokens,\n )\n const cacheWrite = sumOptional(\n a.input_tokens_details?.cache_write_tokens,\n b.input_tokens_details?.cache_write_tokens,\n )\n const reasoning = sumOptional(\n a.output_tokens_details?.reasoning_tokens,\n b.output_tokens_details?.reasoning_tokens,\n )\n return {\n input_tokens: sumOptional(a.input_tokens, b.input_tokens),\n output_tokens: sumOptional(a.output_tokens, b.output_tokens),\n total_tokens: sumOptional(a.total_tokens, b.total_tokens),\n input_tokens_details:\n cached === undefined && cacheWrite === undefined\n ? undefined\n : {\n ...(cached !== undefined && { cached_tokens: cached }),\n ...(cacheWrite !== undefined && { cache_write_tokens: cacheWrite }),\n },\n output_tokens_details: reasoning === undefined ? undefined : { reasoning_tokens: reasoning },\n }\n}\n\n/** Per-turn token counts as incremental counter measurements (present fields only). */\nconst usageMeasurements = (usage: Usage): ReadonlyArray<Measurement> => {\n const pairs: ReadonlyArray<readonly [string, number | undefined]> = [\n [MetricName.inputTokens, usage.input_tokens],\n [MetricName.outputTokens, usage.output_tokens],\n [MetricName.totalTokens, usage.total_tokens],\n [MetricName.reasoningTokens, usage.output_tokens_details?.reasoning_tokens],\n [MetricName.cachedInputTokens, usage.input_tokens_details?.cached_tokens],\n [MetricName.cacheWriteTokens, usage.input_tokens_details?.cache_write_tokens],\n ]\n return Arr.filterMap(pairs, ([name, value]) =>\n value === undefined\n ? Result.failVoid\n : Result.succeed<Measurement>({ name, kind: \"counter\", value }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// timeToFirstToken\n// ---------------------------------------------------------------------------\n\nexport type TimeToFirstTokenOptions = {\n /**\n * Where the clock starts. `\"request\"` (default) anchors at stream\n * initialization, i.e. when the provider request fires, so the measurement\n * includes connection + prefill. `\"first-event\"` anchors at the first\n * emitted event, isolating decode latency.\n */\n readonly from?: \"request\" | \"first-event\"\n}\n\ntype TtftState = {\n readonly turnIndex: number\n readonly turnStart: number\n readonly started: boolean\n readonly seen: boolean\n}\n\nexport const timeToFirstToken =\n (options?: TimeToFirstTokenOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A | MetricEvent, E, R> => {\n const fromRequest = (options?.from ?? \"request\") === \"request\"\n return Stream.unwrap(\n Effect.map(Clock.currentTimeMillis, (init) =>\n self.pipe(\n Stream.mapAccumEffect(\n (): TtftState => ({ turnIndex: 0, turnStart: init, started: fromRequest, seen: false }),\n (state, ev) =>\n Effect.map(Clock.currentTimeMillis, (now) => {\n const started = state.started ? state : { ...state, turnStart: now, started: true }\n const kind = contentDeltaKind(ev)\n const emitTtft = !started.seen && Option.isSome(kind)\n const out: ReadonlyArray<A | MetricEvent> = emitTtft\n ? [\n ev,\n makeEvent<Omit<TimeToFirstToken, typeof MetricEventTypeId>>({\n _tag: \"TimeToFirstToken\",\n turnIndex: started.turnIndex,\n elapsed: Duration.millis(now - started.turnStart),\n kind: (kind as Option.Some<\"text\" | \"reasoning\">).value,\n measurements: [\n {\n name: MetricName.ttft,\n kind: \"timer\",\n value: Duration.millis(now - started.turnStart),\n },\n ],\n }),\n ]\n : [ev]\n const next: TtftState = isTurnCompleteEvent(ev)\n ? {\n turnIndex: started.turnIndex + 1,\n turnStart: now,\n started: fromRequest,\n seen: false,\n }\n : { ...started, seen: started.seen || emitTtft }\n return [next, out] as const\n }),\n ),\n ),\n ),\n )\n }\n\n// ---------------------------------------------------------------------------\n// timeToCompletion\n// ---------------------------------------------------------------------------\n\ntype CompletionState = {\n readonly turnIndex: number\n readonly turnStart: number\n readonly firstTokenAt: Option.Option<number>\n}\n\nexport const timeToCompletion = <A, E, R>(\n self: Stream.Stream<A, E, R>,\n): Stream.Stream<A | MetricEvent, E, R> =>\n Stream.unwrap(\n Effect.map(Clock.currentTimeMillis, (init) =>\n self.pipe(\n Stream.mapAccumEffect(\n (): CompletionState => ({ turnIndex: 0, turnStart: init, firstTokenAt: Option.none() }),\n (state, ev) =>\n Effect.map(Clock.currentTimeMillis, (now) => {\n const firstTokenAt =\n Option.isNone(state.firstTokenAt) && Option.isSome(contentDeltaKind(ev))\n ? Option.some(now)\n : state.firstTokenAt\n if (!isTurnCompleteEvent(ev)) {\n return [{ ...state, firstTokenAt }, [ev]] as const\n }\n const duration = Duration.millis(now - state.turnStart)\n const generation = Option.match(firstTokenAt, {\n onNone: () => Duration.zero,\n onSome: (ft) => Duration.millis(now - ft),\n })\n const event = makeEvent<Omit<TimeToCompletion, typeof MetricEventTypeId>>({\n _tag: \"TimeToCompletion\",\n turnIndex: state.turnIndex,\n duration,\n generation,\n measurements: [\n { name: MetricName.duration, kind: \"timer\", value: duration },\n { name: MetricName.generation, kind: \"timer\", value: generation },\n ],\n })\n const next: CompletionState = {\n turnIndex: state.turnIndex + 1,\n turnStart: now,\n firstTokenAt: Option.none(),\n }\n return [next, [ev, event]] as const\n }),\n ),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// tokenTotals\n// ---------------------------------------------------------------------------\n\nconst emptyUsage: Usage = {}\n\ntype TotalsState = {\n readonly turnIndex: number\n readonly cumulative: Usage\n}\n\nexport const tokenTotals = <A, E, R>(\n self: Stream.Stream<A, E, R>,\n): Stream.Stream<A | MetricEvent, E, R> =>\n self.pipe(\n Stream.mapAccumEffect(\n (): TotalsState => ({ turnIndex: 0, cumulative: emptyUsage }),\n (state, ev) =>\n Effect.sync(() => {\n if (!isTurnCompleteEvent(ev)) return [state, [ev]] as const\n const usage = turnOf(ev).usage\n const cumulative = addUsage(state.cumulative, usage)\n const event = makeEvent<Omit<TokenTotals, typeof MetricEventTypeId>>({\n _tag: \"TokenTotals\",\n turnIndex: state.turnIndex,\n usage,\n cumulative,\n measurements: usageMeasurements(usage),\n })\n return [{ turnIndex: state.turnIndex + 1, cumulative }, [ev, event]] as const\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// throughput\n// ---------------------------------------------------------------------------\n\nexport type ThroughputOptions = {\n /** Tick interval. Default `\"1 second\"`. */\n readonly every?: Duration.Input\n /** What a \"unit\" is. Default `\"char\"` (exact and chunking-independent). */\n readonly unit?: \"char\" | \"token\" | \"event\"\n /**\n * Token counter for `unit: \"token\"`. Called only for deltas that carry\n * generated output. Absent => approximate 1 per such delta.\n */\n readonly tokenizer?: (event: OutputDelta) => Effect.Effect<number>\n /** Rate definition. Default `\"windowed\"` (current speed, no cold-start bias). */\n readonly mode?: \"windowed\" | \"cumulative\"\n /** EWMA smoothing. Omitted => off; `\"default\"` => a sensible factor. */\n readonly smooth?: \"default\" | number\n}\n\n/**\n * The throughput accumulator. Mutated by the source per content delta and read\n * by the metronome per tick.\n *\n * @internal\n */\nexport type RateState = {\n readonly units: number\n readonly lastUnits: number\n readonly lastMillis: number\n readonly firstMillis: Option.Option<number>\n readonly smoothed: Option.Option<number>\n readonly turnIndex: number\n}\n\nconst initRateState: RateState = {\n units: 0,\n lastUnits: 0,\n lastMillis: 0,\n firstMillis: Option.none(),\n smoothed: Option.none(),\n turnIndex: 0,\n}\n\nconst resolveSmooth = (smooth: ThroughputOptions[\"smooth\"]): number | undefined =>\n smooth === undefined ? undefined : smooth === \"default\" ? 0.3 : smooth\n\n/**\n * Pure per-tick rate computation. Returns `None` before the first token, else\n * the rate, the window it was measured over, and the next accumulator state.\n * Separated from the metronome so the rate math is unit-testable without time.\n *\n * @internal\n */\nexport const computeThroughputTick = (\n state: RateState,\n now: number,\n mode: \"windowed\" | \"cumulative\",\n smooth: number | undefined,\n): Option.Option<{\n readonly rate: number\n readonly window: Duration.Duration\n readonly next: RateState\n}> =>\n Option.map(state.firstMillis, (first) => {\n const windowMs = mode === \"windowed\" ? now - state.lastMillis : now - first\n const deltaUnits = mode === \"windowed\" ? state.units - state.lastUnits : state.units\n const instant = windowMs > 0 ? (deltaUnits / windowMs) * 1000 : 0\n const rate =\n smooth === undefined\n ? instant\n : Option.match(state.smoothed, {\n onNone: () => instant,\n onSome: (prev) => smooth * instant + (1 - smooth) * prev,\n })\n return {\n rate,\n window: Duration.millis(windowMs),\n next: { ...state, lastUnits: state.units, lastMillis: now, smoothed: Option.some(rate) },\n }\n })\n\nconst addUnits = (state: RateState, n: number, now: number): RateState =>\n Option.isNone(state.firstMillis)\n ? { ...state, units: n, lastUnits: 0, lastMillis: now, firstMillis: Option.some(now) }\n : { ...state, units: state.units + n }\n\nconst resetTurn = (state: RateState): RateState => ({\n ...initRateState,\n turnIndex: state.turnIndex + 1,\n})\n\nconst unitCount = (\n ev: unknown,\n unit: \"char\" | \"token\" | \"event\",\n tokenizer: ThroughputOptions[\"tokenizer\"],\n): Effect.Effect<number> => {\n const output = outputDelta(ev)\n if (Option.isNone(output)) return Effect.succeed(0)\n const delta = output.value\n return Match.value(unit).pipe(\n Match.when(\"char\", () => Effect.succeed([...outputTextOf(delta)].length)),\n Match.when(\"event\", () => Effect.succeed(1)),\n Match.when(\"token\", () => (tokenizer === undefined ? Effect.succeed(1) : tokenizer(delta))),\n Match.exhaustive,\n )\n}\n\nconst throughputName = (unit: \"char\" | \"token\" | \"event\"): string =>\n `effect_uai_output_${unit}s_per_second`\n\nexport const throughput =\n (options?: ThroughputOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A | MetricEvent, E, R> => {\n const unit = options?.unit ?? \"char\"\n const mode = options?.mode ?? \"windowed\"\n const every = options?.every ?? Duration.seconds(1)\n const smooth = resolveSmooth(options?.smooth)\n const tokenizer = options?.tokenizer\n const name = throughputName(unit)\n\n return Stream.unwrap(\n Effect.map(Ref.make(initRateState), (ref) => {\n const source = self.pipe(\n Stream.tap((ev) =>\n isTurnCompleteEvent(ev)\n ? Ref.update(ref, resetTurn)\n : Option.isSome(outputDelta(ev))\n ? Effect.flatMap(Clock.currentTimeMillis, (now) =>\n Effect.flatMap(unitCount(ev, unit, tokenizer), (n) =>\n Ref.update(ref, (state) => addUnits(state, n, now)),\n ),\n )\n : Effect.void,\n ),\n )\n const metronome: Stream.Stream<MetricEvent> = Stream.fromSchedule(\n Schedule.spaced(every),\n ).pipe(\n Stream.mapEffect(() =>\n Effect.flatMap(Clock.currentTimeMillis, (now) =>\n Ref.modify(ref, (state) =>\n Option.match(computeThroughputTick(state, now, mode, smooth), {\n onNone: () => [Option.none<MetricEvent>(), state] as const,\n onSome: ({ next, rate, window }) =>\n [\n Option.some<MetricEvent>(\n makeEvent<Omit<Throughput, typeof MetricEventTypeId>>({\n _tag: \"Throughput\",\n turnIndex: state.turnIndex,\n ratePerSecond: rate,\n unit,\n window,\n measurements: [{ name, kind: \"histogram\", value: rate }],\n }),\n ),\n next,\n ] as const,\n }),\n ),\n ),\n ),\n Stream.filterMap((o) => (Option.isSome(o) ? Result.succeed(o.value) : Result.failVoid)),\n )\n return source.pipe(Stream.merge(metronome, { haltStrategy: \"left\" }))\n }),\n )\n }\n\n// ---------------------------------------------------------------------------\n// allMetrics\n// ---------------------------------------------------------------------------\n\nexport type AllMetricsOptions = {\n readonly timeToFirstToken?: TimeToFirstTokenOptions\n readonly throughput?: ThroughputOptions\n}\n\nexport const allMetrics =\n (options?: AllMetricsOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A | MetricEvent, E, R> =>\n self.pipe(\n timeToFirstToken(options?.timeToFirstToken),\n throughput(options?.throughput),\n tokenTotals,\n timeToCompletion,\n )\n"],"mappings":"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"}
1
+ {"version":3,"file":"Metrics.mjs","names":["Arr"],"sources":["../../src/observability/Metrics.ts"],"sourcesContent":["import {\n Array as Arr,\n Clock,\n Duration,\n Effect,\n Match,\n Option,\n Predicate,\n Ref,\n Result,\n Schedule,\n Stream,\n} from \"effect\"\nimport type { Usage } from \"../domain/Items.js\"\nimport { type Turn, TurnEvent } from \"../domain/Turn.js\"\n\n// ---------------------------------------------------------------------------\n// Self-describing measurements (the exporter-facing payload)\n// ---------------------------------------------------------------------------\n\n/**\n * The instrument-agnostic description of a single recorded value. The\n * `Telemetry` recorder reads only this: it builds (or reuses) a `Metric`\n * instrument from `kind` + `name` and updates it with `value`. A new metric,\n * built-in or user-defined, never requires a change to the recorder.\n */\nexport type Measurement = {\n readonly name: string\n readonly kind: \"counter\" | \"histogram\" | \"timer\" | \"gauge\"\n readonly value: number | Duration.Duration\n readonly attributes?: Readonly<Record<string, string>> | undefined\n}\n\n// ---------------------------------------------------------------------------\n// MetricEvent: an open, brand-marked union (built-ins + user metrics)\n// ---------------------------------------------------------------------------\n\n/**\n * Brand stamped on every metric event. `isMetricEvent` checks this symbol,\n * not an enumerated `_tag`, so events are distinguished from `TurnEvent`s,\n * tool outputs, and arbitrary custom loop values regardless of their tag.\n * This is the same TypeId-symbol pattern Effect uses for `isStream` etc.\n */\nexport const MetricEventTypeId: unique symbol = Symbol.for(\n \"@effect-uai/core/observability/MetricEvent\",\n)\n\n/**\n * The structural contract every metric event satisfies, ours and custom.\n * The `_tag` + extra typed fields serve a frontend; the brand + `measurements`\n * serve the exporter.\n */\nexport type MetricEvent = {\n readonly [MetricEventTypeId]: typeof MetricEventTypeId\n readonly _tag: string\n /** Which turn within the piped stream this sample belongs to (0-based). */\n readonly turnIndex: number\n readonly measurements: ReadonlyArray<Measurement>\n}\n\n/** Request/turn start to the first content delta. */\nexport type TimeToFirstToken = MetricEvent & {\n readonly _tag: \"TimeToFirstToken\"\n readonly elapsed: Duration.Duration\n readonly kind: \"text\" | \"reasoning\"\n}\n\n/** A live output rate, emitted on the metronome cadence. */\nexport type Throughput = MetricEvent & {\n readonly _tag: \"Throughput\"\n readonly ratePerSecond: number\n readonly unit: \"char\" | \"token\" | \"event\"\n readonly window: Duration.Duration\n}\n\n/** Provider-reported usage at `TurnComplete`, this turn and cumulative. */\nexport type TokenTotals = MetricEvent & {\n readonly _tag: \"TokenTotals\"\n readonly usage: Usage\n readonly cumulative: Usage\n}\n\n/** Per-turn wall times at `TurnComplete`. */\nexport type TimeToCompletion = MetricEvent & {\n readonly _tag: \"TimeToCompletion\"\n readonly duration: Duration.Duration\n readonly generation: Duration.Duration\n}\n\n/**\n * Mint a custom metric event. Stamp it with the brand and provide a\n * well-formed `measurements` array; `Telemetry.record` will export it with no\n * changes to the recorder. The only constraint is that each measurement's\n * `kind` is one the recorder maps.\n */\nexport const makeEvent = <Fields extends Omit<MetricEvent, typeof MetricEventTypeId>>(\n fields: Fields,\n): Fields & { readonly [MetricEventTypeId]: typeof MetricEventTypeId } => ({\n [MetricEventTypeId]: MetricEventTypeId,\n ...fields,\n})\n\n/** Structural (brand-based) guard for any metric event, built-in or custom. */\nexport const isMetricEvent = (u: unknown): u is MetricEvent =>\n Predicate.hasProperty(u, MetricEventTypeId)\n\n/**\n * Project a stream of mixed events onto just its metric samples, for a side\n * channel or a frontend feed.\n */\nexport const metricEvents = <A, E, R>(\n self: Stream.Stream<A, E, R>,\n): Stream.Stream<MetricEvent, E, R> =>\n self.pipe(Stream.filterMap((ev) => (isMetricEvent(ev) ? Result.succeed(ev) : Result.failVoid)))\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst MetricName = {\n ttft: \"effect_uai_turn_ttft\",\n duration: \"effect_uai_turn_duration\",\n generation: \"effect_uai_turn_generation\",\n inputTokens: \"effect_uai_input_tokens\",\n outputTokens: \"effect_uai_output_tokens\",\n totalTokens: \"effect_uai_total_tokens\",\n reasoningTokens: \"effect_uai_reasoning_tokens\",\n cachedInputTokens: \"effect_uai_cached_input_tokens\",\n cacheWriteTokens: \"effect_uai_cache_write_tokens\",\n} as const\n\n/** Read a structural `_tag` off any value without narrowing to a closed union. */\nconst tagOf = (u: unknown): string | undefined =>\n Predicate.hasProperty(u, \"_tag\") && Predicate.isString(u._tag) ? u._tag : undefined\n\nconst contentDeltaKind = (ev: unknown): Option.Option<\"text\" | \"reasoning\"> => {\n const t = tagOf(ev)\n return t === \"TextDelta\"\n ? Option.some(\"text\")\n : t === \"ReasoningDelta\"\n ? Option.some(\"reasoning\")\n : Option.none()\n}\n\nconst isTextDelta = TurnEvent.$is(\"TextDelta\")\nconst isReasoningDelta = TurnEvent.$is(\"ReasoningDelta\")\nconst isRefusalDelta = TurnEvent.$is(\"RefusalDelta\")\nconst isToolCallArgsDelta = TurnEvent.$is(\"ToolCallArgsDelta\")\n\n/** A delta carrying generated output, whatever the model was producing. */\nexport type OutputDelta = Extract<\n TurnEvent,\n { readonly _tag: \"TextDelta\" | \"ReasoningDelta\" | \"RefusalDelta\" | \"ToolCallArgsDelta\" }\n>\n\n/**\n * Narrow to a delta that contributes to the model's output stream. Tool-call\n * arguments count: an agent turn can be almost entirely `ToolCallArgsDelta`,\n * and measuring only prose reports a near-zero rate for it.\n *\n * The single place the output-delta tag list lives — the throughput\n * accumulator and `unitCount` both read it. Distinct from `contentDeltaKind`,\n * which answers a narrower question for first-token latency.\n */\nconst outputDelta = (ev: unknown): Option.Option<OutputDelta> =>\n isTextDelta(ev) || isReasoningDelta(ev) || isRefusalDelta(ev) || isToolCallArgsDelta(ev)\n ? Option.some(ev)\n : Option.none()\n\nconst outputTextOf = (delta: OutputDelta): string =>\n isToolCallArgsDelta(delta) ? delta.delta : delta.text\n\nconst isTurnCompleteEvent = (ev: unknown): boolean => tagOf(ev) === \"TurnComplete\"\n\nconst turnOf = (ev: unknown): Turn =>\n (ev as Extract<TurnEvent, { readonly _tag: \"TurnComplete\" }>).turn\n\nconst sumOptional = (a: number | undefined, b: number | undefined): number | undefined =>\n a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0)\n\nconst addUsage = (a: Usage, b: Usage): Usage => {\n const cached = sumOptional(\n a.input_tokens_details?.cached_tokens,\n b.input_tokens_details?.cached_tokens,\n )\n const cacheWrite = sumOptional(\n a.input_tokens_details?.cache_write_tokens,\n b.input_tokens_details?.cache_write_tokens,\n )\n const reasoning = sumOptional(\n a.output_tokens_details?.reasoning_tokens,\n b.output_tokens_details?.reasoning_tokens,\n )\n return {\n input_tokens: sumOptional(a.input_tokens, b.input_tokens),\n output_tokens: sumOptional(a.output_tokens, b.output_tokens),\n total_tokens: sumOptional(a.total_tokens, b.total_tokens),\n input_tokens_details:\n cached === undefined && cacheWrite === undefined\n ? undefined\n : {\n ...(cached !== undefined && { cached_tokens: cached }),\n ...(cacheWrite !== undefined && { cache_write_tokens: cacheWrite }),\n },\n output_tokens_details: reasoning === undefined ? undefined : { reasoning_tokens: reasoning },\n }\n}\n\n/** Per-turn token counts as incremental counter measurements (present fields only). */\nconst usageMeasurements = (usage: Usage): ReadonlyArray<Measurement> => {\n const pairs: ReadonlyArray<readonly [string, number | undefined]> = [\n [MetricName.inputTokens, usage.input_tokens],\n [MetricName.outputTokens, usage.output_tokens],\n [MetricName.totalTokens, usage.total_tokens],\n [MetricName.reasoningTokens, usage.output_tokens_details?.reasoning_tokens],\n [MetricName.cachedInputTokens, usage.input_tokens_details?.cached_tokens],\n [MetricName.cacheWriteTokens, usage.input_tokens_details?.cache_write_tokens],\n ]\n return Arr.filterMap(pairs, ([name, value]) =>\n value === undefined\n ? Result.failVoid\n : Result.succeed<Measurement>({ name, kind: \"counter\", value }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// timeToFirstToken\n// ---------------------------------------------------------------------------\n\nexport type TimeToFirstTokenOptions = {\n /**\n * Where the clock starts. `\"request\"` (default) anchors at stream\n * initialization, i.e. when the provider request fires, so the measurement\n * includes connection + prefill. `\"first-event\"` anchors at the first\n * emitted event, isolating decode latency.\n */\n readonly from?: \"request\" | \"first-event\"\n}\n\ntype TtftState = {\n readonly turnIndex: number\n readonly turnStart: number\n readonly started: boolean\n readonly seen: boolean\n}\n\nexport const timeToFirstToken =\n (options?: TimeToFirstTokenOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A | MetricEvent, E, R> => {\n const fromRequest = (options?.from ?? \"request\") === \"request\"\n return Stream.unwrap(\n Effect.map(Clock.currentTimeMillis, (init) =>\n self.pipe(\n Stream.mapAccumEffect(\n (): TtftState => ({ turnIndex: 0, turnStart: init, started: fromRequest, seen: false }),\n (state, ev) =>\n Effect.map(Clock.currentTimeMillis, (now) => {\n const started = state.started ? state : { ...state, turnStart: now, started: true }\n const kind = contentDeltaKind(ev)\n const emitTtft = !started.seen && Option.isSome(kind)\n const out: ReadonlyArray<A | MetricEvent> = emitTtft\n ? [\n ev,\n makeEvent<Omit<TimeToFirstToken, typeof MetricEventTypeId>>({\n _tag: \"TimeToFirstToken\",\n turnIndex: started.turnIndex,\n elapsed: Duration.millis(now - started.turnStart),\n kind: (kind as Option.Some<\"text\" | \"reasoning\">).value,\n measurements: [\n {\n name: MetricName.ttft,\n kind: \"timer\",\n value: Duration.millis(now - started.turnStart),\n },\n ],\n }),\n ]\n : [ev]\n const next: TtftState = isTurnCompleteEvent(ev)\n ? {\n turnIndex: started.turnIndex + 1,\n turnStart: now,\n started: fromRequest,\n seen: false,\n }\n : { ...started, seen: started.seen || emitTtft }\n return [next, out] as const\n }),\n ),\n ),\n ),\n )\n }\n\n// ---------------------------------------------------------------------------\n// timeToCompletion\n// ---------------------------------------------------------------------------\n\ntype CompletionState = {\n readonly turnIndex: number\n readonly turnStart: number\n readonly firstTokenAt: Option.Option<number>\n}\n\nexport const timeToCompletion = <A, E, R>(\n self: Stream.Stream<A, E, R>,\n): Stream.Stream<A | MetricEvent, E, R> =>\n Stream.unwrap(\n Effect.map(Clock.currentTimeMillis, (init) =>\n self.pipe(\n Stream.mapAccumEffect(\n (): CompletionState => ({ turnIndex: 0, turnStart: init, firstTokenAt: Option.none() }),\n (state, ev) =>\n Effect.map(Clock.currentTimeMillis, (now) => {\n const firstTokenAt =\n Option.isNone(state.firstTokenAt) && Option.isSome(contentDeltaKind(ev))\n ? Option.some(now)\n : state.firstTokenAt\n if (!isTurnCompleteEvent(ev)) {\n return [{ ...state, firstTokenAt }, [ev]] as const\n }\n const duration = Duration.millis(now - state.turnStart)\n const generation = Option.match(firstTokenAt, {\n onNone: () => Duration.zero,\n onSome: (ft) => Duration.millis(now - ft),\n })\n const event = makeEvent<Omit<TimeToCompletion, typeof MetricEventTypeId>>({\n _tag: \"TimeToCompletion\",\n turnIndex: state.turnIndex,\n duration,\n generation,\n measurements: [\n { name: MetricName.duration, kind: \"timer\", value: duration },\n { name: MetricName.generation, kind: \"timer\", value: generation },\n ],\n })\n const next: CompletionState = {\n turnIndex: state.turnIndex + 1,\n turnStart: now,\n firstTokenAt: Option.none(),\n }\n return [next, [ev, event]] as const\n }),\n ),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// tokenTotals\n// ---------------------------------------------------------------------------\n\nconst emptyUsage: Usage = {}\n\ntype TotalsState = {\n readonly turnIndex: number\n readonly cumulative: Usage\n}\n\nexport const tokenTotals = <A, E, R>(\n self: Stream.Stream<A, E, R>,\n): Stream.Stream<A | MetricEvent, E, R> =>\n self.pipe(\n Stream.mapAccumEffect(\n (): TotalsState => ({ turnIndex: 0, cumulative: emptyUsage }),\n (state, ev) =>\n Effect.sync(() => {\n if (!isTurnCompleteEvent(ev)) return [state, [ev]] as const\n const usage = turnOf(ev).usage\n const cumulative = addUsage(state.cumulative, usage)\n const event = makeEvent<Omit<TokenTotals, typeof MetricEventTypeId>>({\n _tag: \"TokenTotals\",\n turnIndex: state.turnIndex,\n usage,\n cumulative,\n measurements: usageMeasurements(usage),\n })\n return [{ turnIndex: state.turnIndex + 1, cumulative }, [ev, event]] as const\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// throughput\n// ---------------------------------------------------------------------------\n\nexport type ThroughputOptions = {\n /** Tick interval. Default `\"1 second\"`. */\n readonly every?: Duration.Input\n /** What a \"unit\" is. Default `\"char\"` (exact and chunking-independent). */\n readonly unit?: \"char\" | \"token\" | \"event\"\n /**\n * Token counter for `unit: \"token\"`. Called only for deltas that carry\n * generated output. Absent => approximate 1 per such delta.\n */\n readonly tokenizer?: (event: OutputDelta) => Effect.Effect<number>\n /** Rate definition. Default `\"windowed\"` (current speed, no cold-start bias). */\n readonly mode?: \"windowed\" | \"cumulative\"\n /** EWMA smoothing. Omitted => off; `\"default\"` => a sensible factor. */\n readonly smooth?: \"default\" | number\n}\n\n/**\n * The throughput accumulator. Mutated by the source per content delta and read\n * by the metronome per tick.\n *\n * @internal\n */\nexport type RateState = {\n readonly units: number\n readonly lastUnits: number\n readonly lastMillis: number\n readonly firstMillis: Option.Option<number>\n readonly smoothed: Option.Option<number>\n readonly turnIndex: number\n}\n\nconst initRateState: RateState = {\n units: 0,\n lastUnits: 0,\n lastMillis: 0,\n firstMillis: Option.none(),\n smoothed: Option.none(),\n turnIndex: 0,\n}\n\nconst resolveSmooth = (smooth: ThroughputOptions[\"smooth\"]): number | undefined =>\n smooth === undefined ? undefined : smooth === \"default\" ? 0.3 : smooth\n\n/**\n * Pure per-tick rate computation. Returns `None` before the first token, else\n * the rate, the window it was measured over, and the next accumulator state.\n * Separated from the metronome so the rate math is unit-testable without time.\n *\n * @internal\n */\nexport const computeThroughputTick = (\n state: RateState,\n now: number,\n mode: \"windowed\" | \"cumulative\",\n smooth: number | undefined,\n): Option.Option<{\n readonly rate: number\n readonly window: Duration.Duration\n readonly next: RateState\n}> =>\n Option.map(state.firstMillis, (first) => {\n const windowMs = mode === \"windowed\" ? now - state.lastMillis : now - first\n const deltaUnits = mode === \"windowed\" ? state.units - state.lastUnits : state.units\n const instant = windowMs > 0 ? (deltaUnits / windowMs) * 1000 : 0\n const rate =\n smooth === undefined\n ? instant\n : Option.match(state.smoothed, {\n onNone: () => instant,\n onSome: (prev) => smooth * instant + (1 - smooth) * prev,\n })\n return {\n rate,\n window: Duration.millis(windowMs),\n next: { ...state, lastUnits: state.units, lastMillis: now, smoothed: Option.some(rate) },\n }\n })\n\nconst addUnits = (state: RateState, n: number, now: number): RateState =>\n Option.isNone(state.firstMillis)\n ? { ...state, units: n, lastUnits: 0, lastMillis: now, firstMillis: Option.some(now) }\n : { ...state, units: state.units + n }\n\nconst resetTurn = (state: RateState): RateState => ({\n ...initRateState,\n turnIndex: state.turnIndex + 1,\n})\n\nconst unitCount = (\n ev: unknown,\n unit: \"char\" | \"token\" | \"event\",\n tokenizer: ThroughputOptions[\"tokenizer\"],\n): Effect.Effect<number> => {\n const output = outputDelta(ev)\n if (Option.isNone(output)) return Effect.succeed(0)\n const delta = output.value\n return Match.value(unit).pipe(\n Match.when(\"char\", () => Effect.succeed([...outputTextOf(delta)].length)),\n Match.when(\"event\", () => Effect.succeed(1)),\n Match.when(\"token\", () => (tokenizer === undefined ? Effect.succeed(1) : tokenizer(delta))),\n Match.exhaustive,\n )\n}\n\nconst throughputName = (unit: \"char\" | \"token\" | \"event\"): string =>\n `effect_uai_output_${unit}s_per_second`\n\nexport const throughput =\n (options?: ThroughputOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A | MetricEvent, E, R> => {\n const unit = options?.unit ?? \"char\"\n const mode = options?.mode ?? \"windowed\"\n const every = options?.every ?? Duration.seconds(1)\n const smooth = resolveSmooth(options?.smooth)\n const tokenizer = options?.tokenizer\n const name = throughputName(unit)\n\n return Stream.unwrap(\n Effect.map(Ref.make(initRateState), (ref) => {\n const source = self.pipe(\n Stream.tap((ev) =>\n isTurnCompleteEvent(ev)\n ? Ref.update(ref, resetTurn)\n : Option.isSome(outputDelta(ev))\n ? Effect.flatMap(Clock.currentTimeMillis, (now) =>\n Effect.flatMap(unitCount(ev, unit, tokenizer), (n) =>\n Ref.update(ref, (state) => addUnits(state, n, now)),\n ),\n )\n : Effect.void,\n ),\n )\n const metronome: Stream.Stream<MetricEvent> = Stream.fromSchedule(\n Schedule.spaced(every),\n ).pipe(\n Stream.mapEffect(() =>\n Effect.flatMap(Clock.currentTimeMillis, (now) =>\n Ref.modify(ref, (state) =>\n Option.match(computeThroughputTick(state, now, mode, smooth), {\n onNone: () => [Option.none<MetricEvent>(), state] as const,\n onSome: ({ next, rate, window }) =>\n [\n Option.some<MetricEvent>(\n makeEvent<Omit<Throughput, typeof MetricEventTypeId>>({\n _tag: \"Throughput\",\n turnIndex: state.turnIndex,\n ratePerSecond: rate,\n unit,\n window,\n measurements: [{ name, kind: \"histogram\", value: rate }],\n }),\n ),\n next,\n ] as const,\n }),\n ),\n ),\n ),\n Stream.filterMap((o) => (Option.isSome(o) ? Result.succeed(o.value) : Result.failVoid)),\n )\n return source.pipe(Stream.merge(metronome, { haltStrategy: \"left\" }))\n }),\n )\n }\n\n// ---------------------------------------------------------------------------\n// allMetrics\n// ---------------------------------------------------------------------------\n\nexport type AllMetricsOptions = {\n readonly timeToFirstToken?: TimeToFirstTokenOptions\n readonly throughput?: ThroughputOptions\n}\n\nexport const allMetrics =\n (options?: AllMetricsOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A | MetricEvent, E, R> =>\n self.pipe(\n timeToFirstToken(options?.timeToFirstToken),\n throughput(options?.throughput),\n tokenTotals,\n timeToCompletion,\n )\n"],"mappings":"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"}
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,7 +1,8 @@
1
1
  import { TurnEvent } from "../domain/Turn.mjs";
2
2
  import { computeThroughputTick, isMetricEvent, makeEvent, metricEvents, throughput, timeToCompletion, timeToFirstToken, tokenTotals } from "./Metrics.mjs";
3
- import { n as globalExpect, o as describe } from "../dist-CcRV3-A5.mjs";
4
- import { t as it } from "../dist-C0m0QnUP.mjs";
3
+ import { o as describe } from "../utils-CiH_DjFW.mjs";
4
+ import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
5
+ import { t as it } from "../dist-nohcnkD6.mjs";
5
6
  import { Array, Duration, Effect, Fiber, Option, Stream } from "effect";
6
7
  import * as TestClock from "effect/testing/TestClock";
7
8
  //#region src/observability/Metrics.test.ts
@@ -31,7 +32,8 @@ describe("tokenTotals", () => {
31
32
  output_tokens: 20,
32
33
  total_tokens: 120
33
34
  }));
34
- const totals = tagged(yield* Stream.runCollect(stream.pipe(tokenTotals)), "TokenTotals");
35
+ const out = yield* Stream.runCollect(stream.pipe(tokenTotals));
36
+ const totals = tagged(out, "TokenTotals");
35
37
  globalExpect(totals).toHaveLength(1);
36
38
  globalExpect(head(totals).turnIndex).toBe(0);
37
39
  globalExpect(head(totals).usage.output_tokens).toBe(20);
@@ -45,7 +47,8 @@ describe("tokenTotals", () => {
45
47
  input_tokens: 1300,
46
48
  output_tokens: 410
47
49
  }));
48
- const totals = tagged(yield* Stream.runCollect(stream.pipe(tokenTotals)), "TokenTotals");
50
+ const out = yield* Stream.runCollect(stream.pipe(tokenTotals));
51
+ const totals = tagged(out, "TokenTotals");
49
52
  globalExpect(totals.map((t) => t.turnIndex)).toEqual([0, 1]);
50
53
  globalExpect(totals.map((t) => t.usage.output_tokens)).toEqual([120, 410]);
51
54
  globalExpect(totals.map((t) => t.cumulative.input_tokens)).toEqual([1e3, 2300]);
@@ -53,7 +56,9 @@ describe("tokenTotals", () => {
53
56
  }));
54
57
  it.effect("emits an incremental output_tokens counter measurement per turn", () => Effect.gen(function* () {
55
58
  const stream = Stream.make(turnComplete({ output_tokens: 7 }));
56
- globalExpect(head(samples(yield* Stream.runCollect(stream.pipe(tokenTotals)))).measurements.find((m) => m.name === "effect_uai_output_tokens")).toEqual({
59
+ const out = yield* Stream.runCollect(stream.pipe(tokenTotals));
60
+ const m = head(samples(out)).measurements.find((m) => m.name === "effect_uai_output_tokens");
61
+ globalExpect(m).toEqual({
57
62
  name: "effect_uai_output_tokens",
58
63
  kind: "counter",
59
64
  value: 7
@@ -70,10 +75,12 @@ describe("tokenTotals", () => {
70
75
  input_tokens: 6,
71
76
  input_tokens_details: { cached_tokens: 32e3 }
72
77
  }));
73
- const totals = tagged(yield* Stream.runCollect(stream.pipe(tokenTotals)), "TokenTotals");
78
+ const out = yield* Stream.runCollect(stream.pipe(tokenTotals));
79
+ const totals = tagged(out, "TokenTotals");
74
80
  globalExpect(totals.map((t) => t.cumulative.input_tokens_details?.cache_write_tokens)).toEqual([604, 604]);
75
81
  globalExpect(totals.map((t) => t.cumulative.input_tokens_details?.cached_tokens)).toEqual([31851, 63851]);
76
- globalExpect(head(totals).measurements.find((m) => m.name === "effect_uai_cache_write_tokens")).toEqual({
82
+ const write = head(totals).measurements.find((m) => m.name === "effect_uai_cache_write_tokens");
83
+ globalExpect(write).toEqual({
77
84
  name: "effect_uai_cache_write_tokens",
78
85
  kind: "counter",
79
86
  value: 604
@@ -82,47 +89,55 @@ describe("tokenTotals", () => {
82
89
  });
83
90
  describe("timeToFirstToken", () => {
84
91
  it.effect("fires on the first content delta, measured from request (stream init)", () => Effect.gen(function* () {
85
- const ttft = tagged(yield* runTimed(timed([["300 millis", text("hello")], ["700 millis", turnComplete({})]]).pipe(timeToFirstToken()), ["300 millis", "700 millis"]), "TimeToFirstToken");
92
+ const stream = timed([["300 millis", text("hello")], ["700 millis", turnComplete({})]]).pipe(timeToFirstToken());
93
+ const out = yield* runTimed(stream, ["300 millis", "700 millis"]);
94
+ const ttft = tagged(out, "TimeToFirstToken");
86
95
  globalExpect(ttft).toHaveLength(1);
87
96
  globalExpect(Duration.toMillis(head(ttft).elapsed)).toBe(300);
88
97
  globalExpect(head(ttft).kind).toBe("text");
89
98
  }));
90
99
  it.effect("reports kind reasoning when reasoning arrives first", () => Effect.gen(function* () {
91
- const ttft = tagged(yield* runTimed(timed([
100
+ const stream = timed([
92
101
  ["120 millis", TurnEvent.ReasoningDelta({
93
102
  text: "...",
94
103
  kind: "trace"
95
104
  })],
96
105
  ["80 millis", text("answer")],
97
106
  ["50 millis", turnComplete({})]
98
- ]).pipe(timeToFirstToken()), [
107
+ ]).pipe(timeToFirstToken());
108
+ const out = yield* runTimed(stream, [
99
109
  "120 millis",
100
110
  "80 millis",
101
111
  "50 millis"
102
- ]), "TimeToFirstToken");
112
+ ]);
113
+ const ttft = tagged(out, "TimeToFirstToken");
103
114
  globalExpect(ttft).toHaveLength(1);
104
115
  globalExpect(head(ttft).kind).toBe("reasoning");
105
116
  globalExpect(Duration.toMillis(head(ttft).elapsed)).toBe(120);
106
117
  }));
107
118
  it.effect("loop level: one TTFT per turn, turnIndex increments", () => Effect.gen(function* () {
108
- const ttft = tagged(yield* runTimed(timed([
119
+ const stream = timed([
109
120
  ["200 millis", text("a")],
110
121
  ["100 millis", turnComplete({})],
111
122
  ["300 millis", text("b")],
112
123
  ["100 millis", turnComplete({})]
113
- ]).pipe(timeToFirstToken()), [
124
+ ]).pipe(timeToFirstToken());
125
+ const out = yield* runTimed(stream, [
114
126
  "200 millis",
115
127
  "100 millis",
116
128
  "300 millis",
117
129
  "100 millis"
118
- ]), "TimeToFirstToken");
130
+ ]);
131
+ const ttft = tagged(out, "TimeToFirstToken");
119
132
  globalExpect(ttft.map((t) => t.turnIndex)).toEqual([0, 1]);
120
133
  globalExpect(ttft.map((t) => Duration.toMillis(t.elapsed))).toEqual([200, 300]);
121
134
  }));
122
135
  });
123
136
  describe("timeToCompletion", () => {
124
137
  it.effect("reports duration (request->complete) and generation (first token->complete)", () => Effect.gen(function* () {
125
- const done = tagged(yield* runTimed(timed([["300 millis", text("hi")], ["700 millis", turnComplete({})]]).pipe(timeToCompletion), ["300 millis", "700 millis"]), "TimeToCompletion");
138
+ const stream = timed([["300 millis", text("hi")], ["700 millis", turnComplete({})]]).pipe(timeToCompletion);
139
+ const out = yield* runTimed(stream, ["300 millis", "700 millis"]);
140
+ const done = tagged(out, "TimeToCompletion");
126
141
  globalExpect(done).toHaveLength(1);
127
142
  globalExpect(Duration.toMillis(head(done).duration)).toBe(1e3);
128
143
  globalExpect(Duration.toMillis(head(done).generation)).toBe(700);
@@ -141,53 +156,58 @@ describe("computeThroughputTick", () => {
141
156
  globalExpect(Option.isNone(computeThroughputTick(base, 500, "windowed", void 0))).toBe(true);
142
157
  });
143
158
  it("windowed: rate is units in the last interval over that interval", () => {
144
- const r = computeThroughputTick({
159
+ const state = {
145
160
  ...base,
146
161
  units: 30,
147
162
  lastUnits: 10,
148
163
  lastMillis: 500,
149
164
  firstMillis: Option.some(100)
150
- }, 1e3, "windowed", void 0);
165
+ };
166
+ const r = computeThroughputTick(state, 1e3, "windowed", void 0);
151
167
  globalExpect(Option.getOrThrow(r).rate).toBe(40);
152
168
  });
153
169
  it("cumulative: rate is total units over time since first token", () => {
154
- const r = computeThroughputTick({
170
+ const state = {
155
171
  ...base,
156
172
  units: 30,
157
173
  lastUnits: 10,
158
174
  lastMillis: 500,
159
175
  firstMillis: Option.some(100)
160
- }, 1e3, "cumulative", void 0);
176
+ };
177
+ const r = computeThroughputTick(state, 1e3, "cumulative", void 0);
161
178
  globalExpect(Option.getOrThrow(r).rate).toBeCloseTo(33.333, 2);
162
179
  });
163
180
  it("smooth: blends the instantaneous rate with the carried average", () => {
164
- const r = computeThroughputTick({
181
+ const state = {
165
182
  ...base,
166
183
  units: 20,
167
184
  lastUnits: 0,
168
185
  lastMillis: 0,
169
186
  firstMillis: Option.some(0),
170
187
  smoothed: Option.some(100)
171
- }, 1e3, "windowed", .5);
188
+ };
189
+ const r = computeThroughputTick(state, 1e3, "windowed", .5);
172
190
  globalExpect(Option.getOrThrow(r).rate).toBe(60);
173
191
  });
174
192
  });
175
193
  describe("throughput (metronome)", () => {
176
194
  it.effect("emits a live char rate on the tick, over the elapsed window", () => Effect.gen(function* () {
177
- const rates = (yield* runTimed(Stream.make(text("aaaaa"), text("aaaaa")).pipe(Stream.concat(timed([["10 seconds", turnComplete({})]]))).pipe(throughput({
195
+ const stream = Stream.make(text("aaaaa"), text("aaaaa")).pipe(Stream.concat(timed([["10 seconds", turnComplete({})]]))).pipe(throughput({
178
196
  every: "1 second",
179
197
  unit: "char"
180
- }), metricEvents, Stream.take(1)), ["1 second"])).filter((e) => e._tag === "Throughput");
198
+ }), metricEvents, Stream.take(1));
199
+ const rates = (yield* runTimed(stream, ["1 second"])).filter((e) => e._tag === "Throughput");
181
200
  globalExpect(rates).toHaveLength(1);
182
201
  globalExpect(head(rates).unit).toBe("char");
183
202
  globalExpect(head(rates).turnIndex).toBe(0);
184
203
  globalExpect(head(rates).ratePerSecond).toBeGreaterThan(0);
185
204
  }));
186
205
  it.effect("counts tool-call arguments: a turn with no prose still reports a rate", () => Effect.gen(function* () {
187
- const rates = (yield* runTimed(Stream.make(toolArgs("{\"city\":"), toolArgs("\"Lisbon\"}")).pipe(Stream.concat(timed([["10 seconds", turnComplete({})]]))).pipe(throughput({
206
+ const stream = Stream.make(toolArgs("{\"city\":"), toolArgs("\"Lisbon\"}")).pipe(Stream.concat(timed([["10 seconds", turnComplete({})]]))).pipe(throughput({
188
207
  every: "1 second",
189
208
  unit: "char"
190
- }), metricEvents, Stream.take(1)), ["1 second"])).filter((e) => e._tag === "Throughput");
209
+ }), metricEvents, Stream.take(1));
210
+ const rates = (yield* runTimed(stream, ["1 second"])).filter((e) => e._tag === "Throughput");
191
211
  globalExpect(rates).toHaveLength(1);
192
212
  globalExpect(head(rates).ratePerSecond).toBeGreaterThan(0);
193
213
  }));
@@ -199,12 +219,13 @@ describe("throughput (metronome)", () => {
199
219
  every: "10 millis",
200
220
  unit: "char"
201
221
  }), metricEvents);
202
- globalExpect((yield* Stream.runCollect(stream)).filter((e) => e._tag === "Throughput")).toHaveLength(0);
222
+ const out = yield* Stream.runCollect(stream);
223
+ globalExpect(out.filter((e) => e._tag === "Throughput")).toHaveLength(0);
203
224
  }));
204
225
  });
205
226
  describe("custom events and helpers", () => {
206
227
  it("makeEvent stamps the brand so isMetricEvent and the recorder see it", () => {
207
- globalExpect(isMetricEvent(makeEvent({
228
+ const ev = makeEvent({
208
229
  _tag: "ToolLatency",
209
230
  turnIndex: 0,
210
231
  measurements: [{
@@ -212,7 +233,8 @@ describe("custom events and helpers", () => {
212
233
  kind: "timer",
213
234
  value: Duration.millis(42)
214
235
  }]
215
- }))).toBe(true);
236
+ });
237
+ globalExpect(isMetricEvent(ev)).toBe(true);
216
238
  globalExpect(isMetricEvent(text("x"))).toBe(false);
217
239
  globalExpect(isMetricEvent(turnComplete({}))).toBe(false);
218
240
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Metrics.test.mjs","names":["Arr"],"sources":["../../src/observability/Metrics.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Fiber, Option, Stream } from \"effect\"\nimport * as TestClock from \"effect/testing/TestClock\"\nimport { expect } from \"vitest\"\nimport type { Usage } from \"../domain/Items.js\"\nimport { TurnEvent } from \"../domain/Turn.js\"\nimport {\n type MetricEvent,\n type Throughput,\n type TimeToCompletion,\n type TimeToFirstToken,\n type TokenTotals,\n computeThroughputTick,\n isMetricEvent,\n makeEvent,\n metricEvents,\n throughput,\n timeToCompletion,\n timeToFirstToken,\n tokenTotals,\n} from \"./Metrics.js\"\n\nconst turnComplete = (usage: Usage): TurnEvent =>\n TurnEvent.TurnComplete({ turn: { items: [], usage, stop_reason: \"stop\" } })\n\nconst text = (s: string): TurnEvent => TurnEvent.TextDelta({ text: s })\n\nconst toolArgs = (s: string): TurnEvent =>\n TurnEvent.ToolCallArgsDelta({ call_id: \"call_1\", delta: s })\n\nconst samples = (events: ReadonlyArray<unknown>): ReadonlyArray<MetricEvent> =>\n events.filter(isMetricEvent)\n\nconst head = <A>(xs: ReadonlyArray<A>): A => Option.getOrThrow(Arr.head(xs))\n\nconst tagged = <T extends MetricEvent[\"_tag\"]>(\n events: ReadonlyArray<unknown>,\n tag: T,\n): ReadonlyArray<Extract<MetricEvent, { readonly _tag: T }>> =>\n samples(events).filter((e): e is Extract<MetricEvent, { readonly _tag: T }> => e._tag === tag)\n\n// A stream that emits each event after the given delay since the previous one.\nconst timed = <A>(steps: ReadonlyArray<readonly [Duration.Input, A]>): Stream.Stream<A> =>\n Stream.fromIterable(steps).pipe(\n Stream.mapEffect(([delay, ev]) => Effect.as(Effect.sleep(delay), ev)),\n )\n\n// Collect a metered stream while advancing the test clock by each step, so\n// every per-event clock read lands at a known virtual time.\nconst runTimed = <A>(\n metered: Stream.Stream<A>,\n steps: ReadonlyArray<Duration.Input>,\n): Effect.Effect<ReadonlyArray<A>> =>\n Effect.gen(function* () {\n const fiber = yield* Effect.forkChild(Stream.runCollect(metered))\n yield* Effect.forEach(steps, (step) => TestClock.adjust(step), { discard: true })\n return yield* Fiber.join(fiber)\n })\n\ndescribe(\"tokenTotals\", () => {\n it.effect(\"turn level: a single TurnComplete reports that generation's usage\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(\n text(\"hi\"),\n turnComplete({ input_tokens: 100, output_tokens: 20, total_tokens: 120 }),\n )\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const totals = tagged(out, \"TokenTotals\") as ReadonlyArray<TokenTotals>\n expect(totals).toHaveLength(1)\n expect(head(totals).turnIndex).toBe(0)\n expect(head(totals).usage.output_tokens).toBe(20)\n expect(head(totals).cumulative.output_tokens).toBe(20)\n }),\n )\n\n it.effect(\"loop level: cumulative sums across every turn, usage stays per-turn\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(\n text(\"a\"),\n turnComplete({ input_tokens: 1000, output_tokens: 120 }),\n turnComplete({ input_tokens: 1300, output_tokens: 410 }),\n )\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const totals = tagged(out, \"TokenTotals\") as ReadonlyArray<TokenTotals>\n expect(totals.map((t) => t.turnIndex)).toEqual([0, 1])\n expect(totals.map((t) => t.usage.output_tokens)).toEqual([120, 410])\n expect(totals.map((t) => t.cumulative.input_tokens)).toEqual([1000, 2300])\n expect(totals.map((t) => t.cumulative.output_tokens)).toEqual([120, 530])\n }),\n )\n\n it.effect(\"emits an incremental output_tokens counter measurement per turn\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(turnComplete({ output_tokens: 7 }))\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const m = head(samples(out)).measurements.find((m) => m.name === \"effect_uai_output_tokens\")\n expect(m).toEqual({ name: \"effect_uai_output_tokens\", kind: \"counter\", value: 7 })\n }),\n )\n\n it.effect(\"carries cache read and write tokens through usage and measurements\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(\n turnComplete({\n input_tokens: 94,\n input_tokens_details: { cached_tokens: 31851, cache_write_tokens: 604 },\n }),\n turnComplete({ input_tokens: 6, input_tokens_details: { cached_tokens: 32000 } }),\n )\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const totals = tagged(out, \"TokenTotals\") as ReadonlyArray<TokenTotals>\n expect(totals.map((t) => t.cumulative.input_tokens_details?.cache_write_tokens)).toEqual([\n 604, 604,\n ])\n expect(totals.map((t) => t.cumulative.input_tokens_details?.cached_tokens)).toEqual([\n 31851, 63851,\n ])\n const write = head(totals).measurements.find(\n (m) => m.name === \"effect_uai_cache_write_tokens\",\n )\n expect(write).toEqual({ name: \"effect_uai_cache_write_tokens\", kind: \"counter\", value: 604 })\n }),\n )\n})\n\ndescribe(\"timeToFirstToken\", () => {\n it.effect(\"fires on the first content delta, measured from request (stream init)\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"300 millis\", text(\"hello\")],\n [\"700 millis\", turnComplete({})],\n ]).pipe(timeToFirstToken())\n const out = yield* runTimed(stream, [\"300 millis\", \"700 millis\"])\n const ttft = tagged(out, \"TimeToFirstToken\") as ReadonlyArray<TimeToFirstToken>\n expect(ttft).toHaveLength(1)\n expect(Duration.toMillis(head(ttft).elapsed)).toBe(300)\n expect(head(ttft).kind).toBe(\"text\")\n }),\n )\n\n it.effect(\"reports kind reasoning when reasoning arrives first\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"120 millis\", TurnEvent.ReasoningDelta({ text: \"...\", kind: \"trace\" })],\n [\"80 millis\", text(\"answer\")],\n [\"50 millis\", turnComplete({})],\n ]).pipe(timeToFirstToken())\n const out = yield* runTimed(stream, [\"120 millis\", \"80 millis\", \"50 millis\"])\n const ttft = tagged(out, \"TimeToFirstToken\") as ReadonlyArray<TimeToFirstToken>\n expect(ttft).toHaveLength(1)\n expect(head(ttft).kind).toBe(\"reasoning\")\n expect(Duration.toMillis(head(ttft).elapsed)).toBe(120)\n }),\n )\n\n it.effect(\"loop level: one TTFT per turn, turnIndex increments\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"200 millis\", text(\"a\")],\n [\"100 millis\", turnComplete({})],\n [\"300 millis\", text(\"b\")],\n [\"100 millis\", turnComplete({})],\n ]).pipe(timeToFirstToken())\n const out = yield* runTimed(stream, [\"200 millis\", \"100 millis\", \"300 millis\", \"100 millis\"])\n const ttft = tagged(out, \"TimeToFirstToken\") as ReadonlyArray<TimeToFirstToken>\n expect(ttft.map((t) => t.turnIndex)).toEqual([0, 1])\n // turn 0 from init; turn 1 from the previous TurnComplete (loop-scope approximation)\n expect(ttft.map((t) => Duration.toMillis(t.elapsed))).toEqual([200, 300])\n }),\n )\n})\n\ndescribe(\"timeToCompletion\", () => {\n it.effect(\"reports duration (request->complete) and generation (first token->complete)\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"300 millis\", text(\"hi\")],\n [\"700 millis\", turnComplete({})],\n ]).pipe(timeToCompletion)\n const out = yield* runTimed(stream, [\"300 millis\", \"700 millis\"])\n const done = tagged(out, \"TimeToCompletion\") as ReadonlyArray<TimeToCompletion>\n expect(done).toHaveLength(1)\n expect(Duration.toMillis(head(done).duration)).toBe(1000)\n expect(Duration.toMillis(head(done).generation)).toBe(700)\n }),\n )\n})\n\ndescribe(\"computeThroughputTick\", () => {\n const base = {\n units: 0,\n lastUnits: 0,\n lastMillis: 0,\n firstMillis: Option.none<number>(),\n smoothed: Option.none<number>(),\n turnIndex: 0,\n }\n\n it(\"returns None before the first token\", () => {\n expect(Option.isNone(computeThroughputTick(base, 500, \"windowed\", undefined))).toBe(true)\n })\n\n it(\"windowed: rate is units in the last interval over that interval\", () => {\n const state = {\n ...base,\n units: 30,\n lastUnits: 10,\n lastMillis: 500,\n firstMillis: Option.some(100),\n }\n const r = computeThroughputTick(state, 1000, \"windowed\", undefined)\n // (30 - 10) units over (1000 - 500) ms = 40/s\n expect(Option.getOrThrow(r).rate).toBe(40)\n })\n\n it(\"cumulative: rate is total units over time since first token\", () => {\n const state = {\n ...base,\n units: 30,\n lastUnits: 10,\n lastMillis: 500,\n firstMillis: Option.some(100),\n }\n const r = computeThroughputTick(state, 1000, \"cumulative\", undefined)\n // 30 units over (1000 - 100) ms ~= 33.33/s\n expect(Option.getOrThrow(r).rate).toBeCloseTo(33.333, 2)\n })\n\n it(\"smooth: blends the instantaneous rate with the carried average\", () => {\n const state = {\n ...base,\n units: 20,\n lastUnits: 0,\n lastMillis: 0,\n firstMillis: Option.some(0),\n smoothed: Option.some(100),\n }\n // windowed instant = 20 units / 1000 ms = 20/s; smoothed = 0.5*20 + 0.5*100 = 60\n const r = computeThroughputTick(state, 1000, \"windowed\", 0.5)\n expect(Option.getOrThrow(r).rate).toBe(60)\n })\n})\n\ndescribe(\"throughput (metronome)\", () => {\n it.effect(\"emits a live char rate on the tick, over the elapsed window\", () =>\n Effect.gen(function* () {\n // 10 chars land at t=0; the source then stays open so the metronome can\n // tick. take(1) ends the stream on the first sample, so termination does\n // not depend on the source closing.\n const source = Stream.make(text(\"aaaaa\"), text(\"aaaaa\")).pipe(\n Stream.concat(timed<TurnEvent>([[\"10 seconds\", turnComplete({})]])),\n )\n const stream = source.pipe(\n throughput({ every: \"1 second\", unit: \"char\" }),\n metricEvents,\n Stream.take(1),\n )\n const out = yield* runTimed(stream, [\"1 second\"])\n const rates = out.filter((e): e is Throughput => e._tag === \"Throughput\")\n expect(rates).toHaveLength(1)\n expect(head(rates).unit).toBe(\"char\")\n expect(head(rates).turnIndex).toBe(0)\n // exact rate math is pinned by the computeThroughputTick tests; here we\n // only assert the metronome wired a positive char rate onto the tick.\n expect(head(rates).ratePerSecond).toBeGreaterThan(0)\n }),\n )\n\n it.effect(\"counts tool-call arguments: a turn with no prose still reports a rate\", () =>\n Effect.gen(function* () {\n // The shape of an agent turn that only calls tools. Before output deltas\n // were counted beyond TextDelta this measured 0 for the whole run.\n const source = Stream.make(toolArgs('{\"city\":'), toolArgs('\"Lisbon\"}')).pipe(\n Stream.concat(timed<TurnEvent>([[\"10 seconds\", turnComplete({})]])),\n )\n const stream = source.pipe(\n throughput({ every: \"1 second\", unit: \"char\" }),\n metricEvents,\n Stream.take(1),\n )\n const out = yield* runTimed(stream, [\"1 second\"])\n const rates = out.filter((e): e is Throughput => e._tag === \"Throughput\")\n expect(rates).toHaveLength(1)\n expect(head(rates).ratePerSecond).toBeGreaterThan(0)\n }),\n )\n\n // Runs on the real clock: this asserts an absence, so there is no sample to\n // wait for and the stream has to end by the source closing. Under TestClock\n // that path does not settle, because the metronome re-arms a sleep that no\n // remaining `adjust` resolves.\n it.live(\"ignores events that carry no generated output\", () =>\n Effect.gen(function* () {\n // ToolCallStart and UsageUpdate are turn bookkeeping, not output.\n const source = Stream.make(\n TurnEvent.ToolCallStart({ call_id: \"call_1\", name: \"get_weather\" }),\n TurnEvent.UsageUpdate({ usage: { output_tokens: 5 } }),\n ).pipe(Stream.concat(timed<TurnEvent>([[\"30 millis\", turnComplete({})]])))\n const stream = source.pipe(throughput({ every: \"10 millis\", unit: \"char\" }), metricEvents)\n const out = yield* Stream.runCollect(stream)\n // No output delta ever landed, so the accumulator never started and the\n // metronome ticks produce nothing.\n expect(out.filter((e): e is Throughput => e._tag === \"Throughput\")).toHaveLength(0)\n }),\n )\n})\n\ndescribe(\"custom events and helpers\", () => {\n it(\"makeEvent stamps the brand so isMetricEvent and the recorder see it\", () => {\n const ev = makeEvent({\n _tag: \"ToolLatency\",\n turnIndex: 0,\n measurements: [{ name: \"myapp_tool_latency\", kind: \"timer\", value: Duration.millis(42) }],\n })\n expect(isMetricEvent(ev)).toBe(true)\n expect(isMetricEvent(text(\"x\"))).toBe(false)\n expect(isMetricEvent(turnComplete({}))).toBe(false)\n })\n\n it.effect(\"metricEvents projects a mixed stream onto just the samples\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(text(\"hi\"), turnComplete({ output_tokens: 3 })).pipe(\n tokenTotals,\n metricEvents,\n )\n const out = yield* Stream.runCollect(stream)\n expect(out.every(isMetricEvent)).toBe(true)\n expect(out.map((e) => e._tag)).toEqual([\"TokenTotals\"])\n }),\n )\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"Metrics.test.mjs","names":["Arr"],"sources":["../../src/observability/Metrics.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Fiber, Option, Stream } from \"effect\"\nimport * as TestClock from \"effect/testing/TestClock\"\nimport { expect } from \"vitest\"\nimport type { Usage } from \"../domain/Items.js\"\nimport { TurnEvent } from \"../domain/Turn.js\"\nimport {\n type MetricEvent,\n type Throughput,\n type TimeToCompletion,\n type TimeToFirstToken,\n type TokenTotals,\n computeThroughputTick,\n isMetricEvent,\n makeEvent,\n metricEvents,\n throughput,\n timeToCompletion,\n timeToFirstToken,\n tokenTotals,\n} from \"./Metrics.js\"\n\nconst turnComplete = (usage: Usage): TurnEvent =>\n TurnEvent.TurnComplete({ turn: { items: [], usage, stop_reason: \"stop\" } })\n\nconst text = (s: string): TurnEvent => TurnEvent.TextDelta({ text: s })\n\nconst toolArgs = (s: string): TurnEvent =>\n TurnEvent.ToolCallArgsDelta({ call_id: \"call_1\", delta: s })\n\nconst samples = (events: ReadonlyArray<unknown>): ReadonlyArray<MetricEvent> =>\n events.filter(isMetricEvent)\n\nconst head = <A>(xs: ReadonlyArray<A>): A => Option.getOrThrow(Arr.head(xs))\n\nconst tagged = <T extends MetricEvent[\"_tag\"]>(\n events: ReadonlyArray<unknown>,\n tag: T,\n): ReadonlyArray<Extract<MetricEvent, { readonly _tag: T }>> =>\n samples(events).filter((e): e is Extract<MetricEvent, { readonly _tag: T }> => e._tag === tag)\n\n// A stream that emits each event after the given delay since the previous one.\nconst timed = <A>(steps: ReadonlyArray<readonly [Duration.Input, A]>): Stream.Stream<A> =>\n Stream.fromIterable(steps).pipe(\n Stream.mapEffect(([delay, ev]) => Effect.as(Effect.sleep(delay), ev)),\n )\n\n// Collect a metered stream while advancing the test clock by each step, so\n// every per-event clock read lands at a known virtual time.\nconst runTimed = <A>(\n metered: Stream.Stream<A>,\n steps: ReadonlyArray<Duration.Input>,\n): Effect.Effect<ReadonlyArray<A>> =>\n Effect.gen(function* () {\n const fiber = yield* Effect.forkChild(Stream.runCollect(metered))\n yield* Effect.forEach(steps, (step) => TestClock.adjust(step), { discard: true })\n return yield* Fiber.join(fiber)\n })\n\ndescribe(\"tokenTotals\", () => {\n it.effect(\"turn level: a single TurnComplete reports that generation's usage\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(\n text(\"hi\"),\n turnComplete({ input_tokens: 100, output_tokens: 20, total_tokens: 120 }),\n )\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const totals = tagged(out, \"TokenTotals\") as ReadonlyArray<TokenTotals>\n expect(totals).toHaveLength(1)\n expect(head(totals).turnIndex).toBe(0)\n expect(head(totals).usage.output_tokens).toBe(20)\n expect(head(totals).cumulative.output_tokens).toBe(20)\n }),\n )\n\n it.effect(\"loop level: cumulative sums across every turn, usage stays per-turn\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(\n text(\"a\"),\n turnComplete({ input_tokens: 1000, output_tokens: 120 }),\n turnComplete({ input_tokens: 1300, output_tokens: 410 }),\n )\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const totals = tagged(out, \"TokenTotals\") as ReadonlyArray<TokenTotals>\n expect(totals.map((t) => t.turnIndex)).toEqual([0, 1])\n expect(totals.map((t) => t.usage.output_tokens)).toEqual([120, 410])\n expect(totals.map((t) => t.cumulative.input_tokens)).toEqual([1000, 2300])\n expect(totals.map((t) => t.cumulative.output_tokens)).toEqual([120, 530])\n }),\n )\n\n it.effect(\"emits an incremental output_tokens counter measurement per turn\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(turnComplete({ output_tokens: 7 }))\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const m = head(samples(out)).measurements.find((m) => m.name === \"effect_uai_output_tokens\")\n expect(m).toEqual({ name: \"effect_uai_output_tokens\", kind: \"counter\", value: 7 })\n }),\n )\n\n it.effect(\"carries cache read and write tokens through usage and measurements\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(\n turnComplete({\n input_tokens: 94,\n input_tokens_details: { cached_tokens: 31851, cache_write_tokens: 604 },\n }),\n turnComplete({ input_tokens: 6, input_tokens_details: { cached_tokens: 32000 } }),\n )\n const out = yield* Stream.runCollect(stream.pipe(tokenTotals))\n const totals = tagged(out, \"TokenTotals\") as ReadonlyArray<TokenTotals>\n expect(totals.map((t) => t.cumulative.input_tokens_details?.cache_write_tokens)).toEqual([\n 604, 604,\n ])\n expect(totals.map((t) => t.cumulative.input_tokens_details?.cached_tokens)).toEqual([\n 31851, 63851,\n ])\n const write = head(totals).measurements.find(\n (m) => m.name === \"effect_uai_cache_write_tokens\",\n )\n expect(write).toEqual({ name: \"effect_uai_cache_write_tokens\", kind: \"counter\", value: 604 })\n }),\n )\n})\n\ndescribe(\"timeToFirstToken\", () => {\n it.effect(\"fires on the first content delta, measured from request (stream init)\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"300 millis\", text(\"hello\")],\n [\"700 millis\", turnComplete({})],\n ]).pipe(timeToFirstToken())\n const out = yield* runTimed(stream, [\"300 millis\", \"700 millis\"])\n const ttft = tagged(out, \"TimeToFirstToken\") as ReadonlyArray<TimeToFirstToken>\n expect(ttft).toHaveLength(1)\n expect(Duration.toMillis(head(ttft).elapsed)).toBe(300)\n expect(head(ttft).kind).toBe(\"text\")\n }),\n )\n\n it.effect(\"reports kind reasoning when reasoning arrives first\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"120 millis\", TurnEvent.ReasoningDelta({ text: \"...\", kind: \"trace\" })],\n [\"80 millis\", text(\"answer\")],\n [\"50 millis\", turnComplete({})],\n ]).pipe(timeToFirstToken())\n const out = yield* runTimed(stream, [\"120 millis\", \"80 millis\", \"50 millis\"])\n const ttft = tagged(out, \"TimeToFirstToken\") as ReadonlyArray<TimeToFirstToken>\n expect(ttft).toHaveLength(1)\n expect(head(ttft).kind).toBe(\"reasoning\")\n expect(Duration.toMillis(head(ttft).elapsed)).toBe(120)\n }),\n )\n\n it.effect(\"loop level: one TTFT per turn, turnIndex increments\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"200 millis\", text(\"a\")],\n [\"100 millis\", turnComplete({})],\n [\"300 millis\", text(\"b\")],\n [\"100 millis\", turnComplete({})],\n ]).pipe(timeToFirstToken())\n const out = yield* runTimed(stream, [\"200 millis\", \"100 millis\", \"300 millis\", \"100 millis\"])\n const ttft = tagged(out, \"TimeToFirstToken\") as ReadonlyArray<TimeToFirstToken>\n expect(ttft.map((t) => t.turnIndex)).toEqual([0, 1])\n // turn 0 from init; turn 1 from the previous TurnComplete (loop-scope approximation)\n expect(ttft.map((t) => Duration.toMillis(t.elapsed))).toEqual([200, 300])\n }),\n )\n})\n\ndescribe(\"timeToCompletion\", () => {\n it.effect(\"reports duration (request->complete) and generation (first token->complete)\", () =>\n Effect.gen(function* () {\n const stream = timed<TurnEvent>([\n [\"300 millis\", text(\"hi\")],\n [\"700 millis\", turnComplete({})],\n ]).pipe(timeToCompletion)\n const out = yield* runTimed(stream, [\"300 millis\", \"700 millis\"])\n const done = tagged(out, \"TimeToCompletion\") as ReadonlyArray<TimeToCompletion>\n expect(done).toHaveLength(1)\n expect(Duration.toMillis(head(done).duration)).toBe(1000)\n expect(Duration.toMillis(head(done).generation)).toBe(700)\n }),\n )\n})\n\ndescribe(\"computeThroughputTick\", () => {\n const base = {\n units: 0,\n lastUnits: 0,\n lastMillis: 0,\n firstMillis: Option.none<number>(),\n smoothed: Option.none<number>(),\n turnIndex: 0,\n }\n\n it(\"returns None before the first token\", () => {\n expect(Option.isNone(computeThroughputTick(base, 500, \"windowed\", undefined))).toBe(true)\n })\n\n it(\"windowed: rate is units in the last interval over that interval\", () => {\n const state = {\n ...base,\n units: 30,\n lastUnits: 10,\n lastMillis: 500,\n firstMillis: Option.some(100),\n }\n const r = computeThroughputTick(state, 1000, \"windowed\", undefined)\n // (30 - 10) units over (1000 - 500) ms = 40/s\n expect(Option.getOrThrow(r).rate).toBe(40)\n })\n\n it(\"cumulative: rate is total units over time since first token\", () => {\n const state = {\n ...base,\n units: 30,\n lastUnits: 10,\n lastMillis: 500,\n firstMillis: Option.some(100),\n }\n const r = computeThroughputTick(state, 1000, \"cumulative\", undefined)\n // 30 units over (1000 - 100) ms ~= 33.33/s\n expect(Option.getOrThrow(r).rate).toBeCloseTo(33.333, 2)\n })\n\n it(\"smooth: blends the instantaneous rate with the carried average\", () => {\n const state = {\n ...base,\n units: 20,\n lastUnits: 0,\n lastMillis: 0,\n firstMillis: Option.some(0),\n smoothed: Option.some(100),\n }\n // windowed instant = 20 units / 1000 ms = 20/s; smoothed = 0.5*20 + 0.5*100 = 60\n const r = computeThroughputTick(state, 1000, \"windowed\", 0.5)\n expect(Option.getOrThrow(r).rate).toBe(60)\n })\n})\n\ndescribe(\"throughput (metronome)\", () => {\n it.effect(\"emits a live char rate on the tick, over the elapsed window\", () =>\n Effect.gen(function* () {\n // 10 chars land at t=0; the source then stays open so the metronome can\n // tick. take(1) ends the stream on the first sample, so termination does\n // not depend on the source closing.\n const source = Stream.make(text(\"aaaaa\"), text(\"aaaaa\")).pipe(\n Stream.concat(timed<TurnEvent>([[\"10 seconds\", turnComplete({})]])),\n )\n const stream = source.pipe(\n throughput({ every: \"1 second\", unit: \"char\" }),\n metricEvents,\n Stream.take(1),\n )\n const out = yield* runTimed(stream, [\"1 second\"])\n const rates = out.filter((e): e is Throughput => e._tag === \"Throughput\")\n expect(rates).toHaveLength(1)\n expect(head(rates).unit).toBe(\"char\")\n expect(head(rates).turnIndex).toBe(0)\n // exact rate math is pinned by the computeThroughputTick tests; here we\n // only assert the metronome wired a positive char rate onto the tick.\n expect(head(rates).ratePerSecond).toBeGreaterThan(0)\n }),\n )\n\n it.effect(\"counts tool-call arguments: a turn with no prose still reports a rate\", () =>\n Effect.gen(function* () {\n // The shape of an agent turn that only calls tools. Before output deltas\n // were counted beyond TextDelta this measured 0 for the whole run.\n const source = Stream.make(toolArgs('{\"city\":'), toolArgs('\"Lisbon\"}')).pipe(\n Stream.concat(timed<TurnEvent>([[\"10 seconds\", turnComplete({})]])),\n )\n const stream = source.pipe(\n throughput({ every: \"1 second\", unit: \"char\" }),\n metricEvents,\n Stream.take(1),\n )\n const out = yield* runTimed(stream, [\"1 second\"])\n const rates = out.filter((e): e is Throughput => e._tag === \"Throughput\")\n expect(rates).toHaveLength(1)\n expect(head(rates).ratePerSecond).toBeGreaterThan(0)\n }),\n )\n\n // Runs on the real clock: this asserts an absence, so there is no sample to\n // wait for and the stream has to end by the source closing. Under TestClock\n // that path does not settle, because the metronome re-arms a sleep that no\n // remaining `adjust` resolves.\n it.live(\"ignores events that carry no generated output\", () =>\n Effect.gen(function* () {\n // ToolCallStart and UsageUpdate are turn bookkeeping, not output.\n const source = Stream.make(\n TurnEvent.ToolCallStart({ call_id: \"call_1\", name: \"get_weather\" }),\n TurnEvent.UsageUpdate({ usage: { output_tokens: 5 } }),\n ).pipe(Stream.concat(timed<TurnEvent>([[\"30 millis\", turnComplete({})]])))\n const stream = source.pipe(throughput({ every: \"10 millis\", unit: \"char\" }), metricEvents)\n const out = yield* Stream.runCollect(stream)\n // No output delta ever landed, so the accumulator never started and the\n // metronome ticks produce nothing.\n expect(out.filter((e): e is Throughput => e._tag === \"Throughput\")).toHaveLength(0)\n }),\n )\n})\n\ndescribe(\"custom events and helpers\", () => {\n it(\"makeEvent stamps the brand so isMetricEvent and the recorder see it\", () => {\n const ev = makeEvent({\n _tag: \"ToolLatency\",\n turnIndex: 0,\n measurements: [{ name: \"myapp_tool_latency\", kind: \"timer\", value: Duration.millis(42) }],\n })\n expect(isMetricEvent(ev)).toBe(true)\n expect(isMetricEvent(text(\"x\"))).toBe(false)\n expect(isMetricEvent(turnComplete({}))).toBe(false)\n })\n\n it.effect(\"metricEvents projects a mixed stream onto just the samples\", () =>\n Effect.gen(function* () {\n const stream = Stream.make(text(\"hi\"), turnComplete({ output_tokens: 3 })).pipe(\n tokenTotals,\n metricEvents,\n )\n const out = yield* Stream.runCollect(stream)\n expect(out.every(isMetricEvent)).toBe(true)\n expect(out.map((e) => e._tag)).toEqual([\"TokenTotals\"])\n }),\n )\n})\n"],"mappings":"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"}