@effect-uai/core 0.5.2 → 0.7.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 (175) hide show
  1. package/README.md +1 -1
  2. package/dist/{Audio-BfCTGnH3.d.mts → Audio-DPlTsNdz.d.mts} +3 -2
  3. package/dist/Audio-DPlTsNdz.d.mts.map +1 -0
  4. package/dist/{Items-BH8xUkoR.d.mts → Items-D8UaXQx_.d.mts} +15 -15
  5. package/dist/Items-D8UaXQx_.d.mts.map +1 -0
  6. package/dist/SandboxError-BvKypkJm.d.mts +158 -0
  7. package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
  8. package/dist/{Tool-87ViKCCO.d.mts → Tool-CiFXYjmR.d.mts} +11 -11
  9. package/dist/{Tool-87ViKCCO.d.mts.map → Tool-CiFXYjmR.d.mts.map} +1 -1
  10. package/dist/capabilities/Capabilities.d.mts +78 -0
  11. package/dist/capabilities/Capabilities.d.mts.map +1 -0
  12. package/dist/capabilities/Capabilities.mjs +65 -0
  13. package/dist/capabilities/Capabilities.mjs.map +1 -0
  14. package/dist/domain/Audio.d.mts +1 -1
  15. package/dist/domain/Audio.mjs.map +1 -1
  16. package/dist/domain/Items.d.mts +2 -2
  17. package/dist/domain/Items.mjs +15 -15
  18. package/dist/domain/Items.mjs.map +1 -1
  19. package/dist/domain/Music.d.mts +131 -72
  20. package/dist/domain/Music.d.mts.map +1 -1
  21. package/dist/domain/Music.mjs +36 -9
  22. package/dist/domain/Music.mjs.map +1 -1
  23. package/dist/domain/Transcript.d.mts +8 -2
  24. package/dist/domain/Transcript.d.mts.map +1 -1
  25. package/dist/domain/Transcript.mjs.map +1 -1
  26. package/dist/domain/Turn.d.mts +388 -2
  27. package/dist/domain/Turn.d.mts.map +1 -0
  28. package/dist/domain/Turn.mjs +13 -13
  29. package/dist/domain/Turn.mjs.map +1 -1
  30. package/dist/embedding-model/Embedding.d.mts +12 -2
  31. package/dist/embedding-model/Embedding.d.mts.map +1 -1
  32. package/dist/embedding-model/Embedding.mjs.map +1 -1
  33. package/dist/embedding-model/EmbeddingModel.d.mts +36 -23
  34. package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
  35. package/dist/embedding-model/EmbeddingModel.mjs +20 -1
  36. package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
  37. package/dist/embedding-model/EmbeddingModel.test.mjs +15 -3
  38. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  39. package/dist/index.d.mts +13 -7
  40. package/dist/index.mjs +9 -3
  41. package/dist/language-model/LanguageModel.d.mts +5 -5
  42. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  43. package/dist/language-model/LanguageModel.mjs.map +1 -1
  44. package/dist/loop/Loop.d.mts +37 -90
  45. package/dist/loop/Loop.d.mts.map +1 -1
  46. package/dist/loop/Loop.mjs +39 -93
  47. package/dist/loop/Loop.mjs.map +1 -1
  48. package/dist/loop/Loop.test.mjs +46 -54
  49. package/dist/loop/Loop.test.mjs.map +1 -1
  50. package/dist/music-generator/MusicGenerator.d.mts +48 -25
  51. package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
  52. package/dist/music-generator/MusicGenerator.mjs +17 -9
  53. package/dist/music-generator/MusicGenerator.mjs.map +1 -1
  54. package/dist/music-generator/MusicGenerator.test.mjs +81 -56
  55. package/dist/music-generator/MusicGenerator.test.mjs.map +1 -1
  56. package/dist/sandbox/Memory.d.mts +53 -0
  57. package/dist/sandbox/Memory.d.mts.map +1 -0
  58. package/dist/sandbox/Memory.mjs +68 -0
  59. package/dist/sandbox/Memory.mjs.map +1 -0
  60. package/dist/sandbox/Sandbox.d.mts +717 -0
  61. package/dist/sandbox/Sandbox.d.mts.map +1 -0
  62. package/dist/sandbox/Sandbox.mjs +192 -0
  63. package/dist/sandbox/Sandbox.mjs.map +1 -0
  64. package/dist/sandbox/SandboxError.d.mts +2 -0
  65. package/dist/sandbox/SandboxError.mjs +89 -0
  66. package/dist/sandbox/SandboxError.mjs.map +1 -0
  67. package/dist/sandbox/SandboxImage.d.mts +28 -0
  68. package/dist/sandbox/SandboxImage.d.mts.map +1 -0
  69. package/dist/sandbox/SandboxImage.mjs +32 -0
  70. package/dist/sandbox/SandboxImage.mjs.map +1 -0
  71. package/dist/sandbox/SandboxNetwork.d.mts +34 -0
  72. package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
  73. package/dist/sandbox/SandboxNetwork.mjs +36 -0
  74. package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
  75. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +84 -3
  76. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
  77. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
  78. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  79. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +59 -5
  80. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
  81. package/dist/testing/MockMusicGenerator.d.mts +12 -7
  82. package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
  83. package/dist/testing/MockMusicGenerator.mjs +4 -3
  84. package/dist/testing/MockMusicGenerator.mjs.map +1 -1
  85. package/dist/testing/MockProvider.d.mts +3 -3
  86. package/dist/testing/MockProvider.d.mts.map +1 -1
  87. package/dist/testing/MockProvider.mjs.map +1 -1
  88. package/dist/testing/MockSandbox.d.mts +604 -0
  89. package/dist/testing/MockSandbox.d.mts.map +1 -0
  90. package/dist/testing/MockSandbox.mjs +160 -0
  91. package/dist/testing/MockSandbox.mjs.map +1 -0
  92. package/dist/testing/MockSpeechSynthesizer.d.mts +27 -12
  93. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
  94. package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
  95. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
  96. package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
  97. package/dist/tool/Approval.d.mts.map +1 -0
  98. package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
  99. package/dist/tool/Approval.mjs.map +1 -0
  100. package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
  101. package/dist/tool/Approval.test.mjs.map +1 -0
  102. package/dist/tool/HistoryCheck.d.mts +6 -6
  103. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  104. package/dist/tool/HistoryCheck.mjs +5 -5
  105. package/dist/tool/HistoryCheck.mjs.map +1 -1
  106. package/dist/tool/Tool.d.mts +2 -2
  107. package/dist/tool/Tool.mjs +4 -4
  108. package/dist/tool/Tool.mjs.map +1 -1
  109. package/dist/tool/ToolEvent.d.mts +19 -19
  110. package/dist/tool/ToolEvent.d.mts.map +1 -1
  111. package/dist/tool/ToolEvent.mjs +8 -8
  112. package/dist/tool/ToolEvent.mjs.map +1 -1
  113. package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
  114. package/dist/tool/ToolResult.d.mts.map +1 -0
  115. package/dist/tool/ToolResult.mjs +61 -0
  116. package/dist/tool/ToolResult.mjs.map +1 -0
  117. package/dist/tool/Toolkit.d.mts +58 -26
  118. package/dist/tool/Toolkit.d.mts.map +1 -1
  119. package/dist/tool/Toolkit.mjs +62 -34
  120. package/dist/tool/Toolkit.mjs.map +1 -1
  121. package/dist/tool/Toolkit.test.mjs +13 -13
  122. package/dist/tool/Toolkit.test.mjs.map +1 -1
  123. package/dist/transcriber/Transcriber.d.mts +11 -10
  124. package/dist/transcriber/Transcriber.d.mts.map +1 -1
  125. package/dist/transcriber/Transcriber.mjs.map +1 -1
  126. package/dist/transcriber/Transcriber.test.mjs +3 -3
  127. package/dist/transcriber/Transcriber.test.mjs.map +1 -1
  128. package/package.json +35 -7
  129. package/src/capabilities/Capabilities.ts +81 -0
  130. package/src/domain/Audio.ts +2 -1
  131. package/src/domain/Items.ts +9 -9
  132. package/src/domain/Music.ts +142 -68
  133. package/src/domain/Transcript.ts +7 -1
  134. package/src/domain/Turn.ts +16 -16
  135. package/src/embedding-model/Embedding.ts +11 -0
  136. package/src/embedding-model/EmbeddingModel.test.ts +15 -4
  137. package/src/embedding-model/EmbeddingModel.ts +48 -21
  138. package/src/index.ts +8 -2
  139. package/src/language-model/LanguageModel.ts +3 -3
  140. package/src/loop/Loop.test.ts +93 -91
  141. package/src/loop/Loop.ts +72 -170
  142. package/src/music-generator/MusicGenerator.test.ts +78 -53
  143. package/src/music-generator/MusicGenerator.ts +65 -24
  144. package/src/sandbox/Memory.ts +128 -0
  145. package/src/sandbox/Sandbox.ts +639 -0
  146. package/src/sandbox/SandboxError.ts +140 -0
  147. package/src/sandbox/SandboxImage.ts +26 -0
  148. package/src/sandbox/SandboxNetwork.ts +34 -0
  149. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +92 -3
  150. package/src/speech-synthesizer/SpeechSynthesizer.ts +109 -0
  151. package/src/testing/MockMusicGenerator.ts +28 -18
  152. package/src/testing/MockProvider.ts +21 -20
  153. package/src/testing/MockSandbox.ts +342 -0
  154. package/src/testing/MockSpeechSynthesizer.ts +137 -77
  155. package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
  156. package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
  157. package/src/tool/HistoryCheck.ts +10 -13
  158. package/src/tool/Tool.ts +13 -15
  159. package/src/tool/ToolEvent.ts +7 -7
  160. package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
  161. package/src/tool/Toolkit.test.ts +13 -13
  162. package/src/tool/Toolkit.ts +97 -73
  163. package/src/transcriber/Transcriber.test.ts +3 -3
  164. package/src/transcriber/Transcriber.ts +10 -7
  165. package/dist/Audio-BfCTGnH3.d.mts.map +0 -1
  166. package/dist/Items-BH8xUkoR.d.mts.map +0 -1
  167. package/dist/Turn-0CwCAyVe.d.mts +0 -388
  168. package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
  169. package/dist/tool/Outcome.d.mts.map +0 -1
  170. package/dist/tool/Outcome.mjs +0 -61
  171. package/dist/tool/Outcome.mjs.map +0 -1
  172. package/dist/tool/Resolvers.d.mts.map +0 -1
  173. package/dist/tool/Resolvers.mjs.map +0 -1
  174. package/dist/tool/Resolvers.test.mjs.map +0 -1
  175. /package/dist/tool/{Resolvers.test.d.mts → Approval.test.d.mts} +0 -0
@@ -1,10 +1,11 @@
1
- import type { AudioBlob, AudioFormat } from "./Audio.js"
1
+ import type { Duration } from "effect"
2
+ import type { AudioBlob, AudioChunk, AudioFormat } from "./Audio.js"
2
3
 
3
4
  /**
4
5
  * Prompt fragment with a relative weight. Native to Lyria RealTime
5
- * (`{ text, weight }` pairs blended in the model). Single-prompt
6
- * providers (Suno, Mureka, MiniMax) flatten to text at the adapter
7
- * layer.
6
+ * (`{ text, weight }` pairs blended in the model) and Riffusion compose.
7
+ * Single-prompt providers (the majority) treat a 1-element array with
8
+ * `weight: 1` as a plain prompt.
8
9
  */
9
10
  export type WeightedPrompt = {
10
11
  readonly text: string
@@ -13,109 +14,182 @@ export type WeightedPrompt = {
13
14
  }
14
15
 
15
16
  /**
16
- * Cross-provider music-generation request. Provider-specific extras
17
- * (Lyria `mode`, ElevenLabs `composition_plan`, Suno custom-mode `title`,
18
- * MiniMax `lyrics_optimizer`) live on each provider's typed request
19
- * which extends this and narrows `model`.
17
+ * Cross-provider music-generation request. Trimmed to fields the
18
+ * majority of music providers honor structurally; provider-specific
19
+ * extras (Lyria `bpm` / `scale`, ElevenLabs `compositionPlan` /
20
+ * `signWithC2pa`, Suno `style` / `personaId`, MiniMax `lyricsOptimizer`,
21
+ * `instrumental` on providers that ship a wire toggle) live on each
22
+ * provider's typed request which extends this and narrows `model`.
23
+ *
24
+ * Music providers are one-shot text-to-audio, not conversational. No
25
+ * `messages[]` shape applies.
20
26
  */
21
27
  export type CommonGenerateMusicRequest = {
22
28
  /** Model identifier. Each provider narrows. */
23
29
  readonly model: string
24
- /** Single prompt string or weighted-prompt list (blended where supported). */
25
- readonly prompts: string | ReadonlyArray<WeightedPrompt>
30
+ /** Single prompt string. Weighted blends are provider-typed extras. */
31
+ readonly prompt: string
26
32
  /**
27
- * Lyrics text, optionally with section tags like `[Verse]` / `[Chorus]` /
28
- * `[Bridge]` / `[Outro]`. Ignored for instrumental-only providers or
29
- * when `instrumental: true`.
33
+ * Lyrics text, optionally with section tags like `[Verse]` /
34
+ * `[Chorus]` / `[Bridge]` / `[Outro]`. Routed to a structured wire
35
+ * field on providers that have one (ElevenLabs `lines`, MiniMax
36
+ * `lyrics`, Mureka `lyrics`, Suno custom-mode `prompt`); embedded in
37
+ * the prompt with a warning on providers without one (Lyria 3,
38
+ * Riffusion).
30
39
  */
31
40
  readonly lyrics?: string
32
- /** Target duration in seconds. Provider may treat as a hint or hard limit. */
33
- readonly durationSeconds?: number
34
- /** Beats per minute (60–200 typical). */
35
- readonly bpm?: number
41
+ /** Target duration. Provider may treat as a hint or hard limit. */
42
+ readonly duration?: Duration.Duration
36
43
  /**
37
- * Musical key/mode hint. Provider-specific vocabulary (e.g. Lyria
38
- * RealTime uses enum values like `"C_MAJOR"`, `"A_MINOR"`).
44
+ * Reproducibility seed. Tuning hint (bucket 3): honored where the
45
+ * provider takes one (Lyria 2 Vertex, Lyria RealTime, ElevenLabs
46
+ * with `compositionPlan`, Stable Audio, MusicGen); silently ignored
47
+ * elsewhere.
39
48
  */
40
- readonly scale?: string
41
- /** Skip vocals / lyrics. */
42
- readonly instrumental?: boolean
49
+ readonly seed?: number
43
50
  /** Preferred output format. Provider may override. */
44
51
  readonly outputFormat?: AudioFormat
45
52
  }
46
53
 
47
54
  /**
48
- * Streamed-output request. Same shape as the sync request — the
55
+ * Streamed-output request. Same shape as the sync request: the
49
56
  * streaming variant only differs in how the response is delivered.
50
57
  */
51
58
  export type CommonStreamGenerateMusicRequest = CommonGenerateMusicRequest
52
59
 
53
60
  /**
54
- * Bidirectional-session input. The user pushes one of these per
55
- * change: a new prompt blend, a config delta, or a playback control.
56
- * Lyria RealTime is the only provider currently surfacing these.
61
+ * Playback-control vocabulary that converges across interactive-media
62
+ * protocols (Web Audio, MIDI sequencer control, music SDK surfaces).
63
+ * Provider-specific control actions (Lyria's `reset_context` and any
64
+ * future provider's extras) live on the provider-typed session-input
65
+ * union.
66
+ */
67
+ export type MusicSessionControl = "play" | "pause" | "stop" | "reset"
68
+
69
+ /**
70
+ * Bidirectional-session input on the cross-provider surface. Two
71
+ * load-bearing actions every interactive music session needs:
72
+ *
73
+ * - `prompts` — steer the active generation with a (single or
74
+ * weighted) prompt blend.
75
+ * - `control` — play / pause / stop / reset.
76
+ *
77
+ * Provider-typed services extend this union with their own `config`
78
+ * variant for model-specific knobs (Lyria RealTime exposes density /
79
+ * brightness / mute-stems / BPM / scale / etc. via its own
80
+ * `LyriaRealtimeSessionInput`).
57
81
  */
58
82
  export type MusicSessionInput =
59
83
  | { readonly _tag: "prompts"; readonly prompts: ReadonlyArray<WeightedPrompt> }
60
- | {
61
- readonly _tag: "config"
62
- readonly config: {
63
- readonly bpm?: number
64
- readonly scale?: string
65
- readonly density?: number
66
- readonly brightness?: number
67
- readonly guidance?: number
68
- readonly temperature?: number
69
- readonly topK?: number
70
- readonly seed?: number
71
- readonly muteBass?: boolean
72
- readonly muteDrums?: boolean
73
- readonly onlyBassAndDrums?: boolean
74
- }
75
- }
76
- | { readonly _tag: "control"; readonly action: "play" | "pause" | "stop" | "reset_context" }
84
+ | { readonly _tag: "control"; readonly action: MusicSessionControl }
77
85
 
78
86
  export const promptsInput = (prompts: ReadonlyArray<WeightedPrompt>): MusicSessionInput => ({
79
87
  _tag: "prompts",
80
88
  prompts,
81
89
  })
82
90
 
83
- export const configInput = (
84
- config: (MusicSessionInput & { _tag: "config" })["config"],
85
- ): MusicSessionInput => ({ _tag: "config", config })
91
+ export const controlInput = (action: MusicSessionControl): MusicSessionInput => ({
92
+ _tag: "control",
93
+ action,
94
+ })
95
+
96
+ /**
97
+ * Watermark kind on the response. SynthID is mandatory on Lyria; C2PA
98
+ * is opt-in on ElevenLabs. No provider exposes additional metadata
99
+ * about the watermark itself, so a bare literal-union is enough.
100
+ */
101
+ export type Watermark = "synthid" | "c2pa" | (string & {})
86
102
 
87
- export const controlInput = (
88
- action: (MusicSessionInput & { _tag: "control" })["action"],
89
- ): MusicSessionInput => ({ _tag: "control", action })
103
+ /**
104
+ * Labelled section with absolute timing. Populated when the provider
105
+ * returns structured section metadata (Lyria text part, ElevenLabs
106
+ * `/v1/music/detailed` with `with_timestamps`).
107
+ */
108
+ export type MusicSection = {
109
+ readonly label: string
110
+ readonly startSeconds: number
111
+ readonly endSeconds: number
112
+ }
90
113
 
91
114
  /**
92
- * Sync-generation result. Extends `AudioBlob` with provider-side
93
- * metadata that's common across music providers:
94
- *
95
- * - `songId` — Suno task id, ElevenLabs `song_id`, etc. Used for
96
- * back-reference (re-download, stem export, follow-up edits).
97
- * - `lyrics` — generated lyrics when the model returned them (Lyria
98
- * text part, Mureka, Suno).
99
- * - `sections` — structured section markers (Lyria optional JSON
100
- * structure response).
101
- * - `watermark` — presence marker (Lyria SynthID is always set).
115
+ * Single generated track. Composes `AudioBlob` rather than extending
116
+ * it — `result.audio` is the raw blob, `result.songId` / `lyrics` /
117
+ * `sections` / `watermark` are provider-side metadata when returned.
102
118
  */
103
- export type MusicResult = AudioBlob & {
119
+ export type MusicResult = {
120
+ readonly audio: AudioBlob
121
+ /**
122
+ * Provider identifier, useful when routing through multiple
123
+ * providers in a single pipeline (`"lyria"`, `"elevenlabs-music"`,
124
+ * etc.).
125
+ */
126
+ readonly provider?: string
127
+ /** Provider-side ID for back-reference (re-download, extension, stems). */
104
128
  readonly songId?: string
129
+ /** Generated or transcribed lyrics when the provider returned them. */
105
130
  readonly lyrics?: string
106
- readonly sections?: ReadonlyArray<{
107
- readonly label: string
108
- readonly startSeconds: number
109
- readonly endSeconds: number
110
- }>
111
- readonly watermark?: { readonly kind: string }
131
+ /** Structured section markers when the provider returned them. */
132
+ readonly sections?: ReadonlyArray<MusicSection>
133
+ /** Watermark presence indicator when the provider applied one. */
134
+ readonly watermark?: Watermark
112
135
  }
113
136
 
137
+ /**
138
+ * Result of a `generate` call. Suno and Mureka always return 2 tracks
139
+ * per request; every other provider returns 1. `primary` is the
140
+ * caller-friendly convenience; `variants` carries every track the
141
+ * provider produced (length ≥ 1; `primary === variants[0]`).
142
+ */
143
+ export type GenerateResult = {
144
+ readonly primary: MusicResult
145
+ readonly variants: ReadonlyArray<MusicResult>
146
+ }
147
+
148
+ /**
149
+ * In-band event on the bidirectional `streamGenerationFrom` output
150
+ * stream. Audio chunks flow as `audio`; server-side warnings
151
+ * (`warning`) and prompt-filter rejections (`filteredPrompt`) flow
152
+ * alongside rather than going to a side-channel log.
153
+ */
154
+ export type MusicStreamEvent =
155
+ | { readonly _tag: "audio"; readonly chunk: AudioChunk }
156
+ | { readonly _tag: "warning"; readonly message: string }
157
+ | { readonly _tag: "filteredPrompt"; readonly prompt: string; readonly reason: string }
158
+
114
159
  export const isPromptsInput = (
115
160
  i: MusicSessionInput,
116
161
  ): i is MusicSessionInput & { _tag: "prompts" } => i._tag === "prompts"
117
- export const isConfigInput = (i: MusicSessionInput): i is MusicSessionInput & { _tag: "config" } =>
118
- i._tag === "config"
119
162
  export const isControlInput = (
120
163
  i: MusicSessionInput,
121
164
  ): i is MusicSessionInput & { _tag: "control" } => i._tag === "control"
165
+
166
+ export const audioEvent = (chunk: AudioChunk): MusicStreamEvent => ({ _tag: "audio", chunk })
167
+ export const warningEvent = (message: string): MusicStreamEvent => ({
168
+ _tag: "warning",
169
+ message,
170
+ })
171
+ export const filteredPromptEvent = (prompt: string, reason: string): MusicStreamEvent => ({
172
+ _tag: "filteredPrompt",
173
+ prompt,
174
+ reason,
175
+ })
176
+
177
+ export const isAudioEvent = (e: MusicStreamEvent): e is MusicStreamEvent & { _tag: "audio" } =>
178
+ e._tag === "audio"
179
+ export const isWarningEvent = (e: MusicStreamEvent): e is MusicStreamEvent & { _tag: "warning" } =>
180
+ e._tag === "warning"
181
+ export const isFilteredPromptEvent = (
182
+ e: MusicStreamEvent,
183
+ ): e is MusicStreamEvent & { _tag: "filteredPrompt" } => e._tag === "filteredPrompt"
184
+
185
+ /**
186
+ * Build a single-variant `GenerateResult` from a `MusicResult`.
187
+ * Convenience for providers that always return exactly one track
188
+ * (Lyria, ElevenLabs, MiniMax, Stable Audio, MusicGen, Riffusion,
189
+ * Tencent). Providers that return multiple tracks (Suno, Mureka)
190
+ * construct the `GenerateResult` directly.
191
+ */
192
+ export const singleVariant = (result: MusicResult): GenerateResult => ({
193
+ primary: result,
194
+ variants: [result],
195
+ })
@@ -1,7 +1,12 @@
1
+ import type { Duration } from "effect"
2
+
1
3
  /**
2
4
  * Per-word timing + metadata. `confidence` and `speakerId` are optional
3
5
  * because providers vary widely in what they emit and when (some only on
4
6
  * final, some only with diarization enabled, some not at all).
7
+ *
8
+ * `startSeconds` / `endSeconds` stay raw `number` offsets (not `Duration`)
9
+ * — they're positions in the audio, not durations.
5
10
  */
6
11
  export type WordTimestamp = {
7
12
  readonly text: string
@@ -20,7 +25,8 @@ export type WordTimestamp = {
20
25
  export type TranscriptResult = {
21
26
  readonly text: string
22
27
  readonly languageCode?: string
23
- readonly durationSeconds?: number
28
+ /** Total audio duration. (Word offsets stay raw seconds on `WordTimestamp`.) */
29
+ readonly duration?: Duration.Duration
24
30
  readonly words?: ReadonlyArray<WordTimestamp>
25
31
  readonly raw?: unknown
26
32
  }
@@ -1,15 +1,17 @@
1
1
  import { Data, Effect, Result, Schema, Stream, pipe } from "effect"
2
2
  import * as StructuredFormat from "../structured-format/StructuredFormat.js"
3
3
  import {
4
- FunctionCall,
5
- FunctionCallOutput,
6
- Item,
7
- isOutputText,
8
- isRefusal,
4
+ HistoryItem,
9
5
  Message,
10
6
  Reasoning,
11
7
  StopReason,
8
+ ToolCall,
9
+ ToolCallOutput,
12
10
  Usage,
11
+ isOutputText,
12
+ isReasoning,
13
+ isRefusal,
14
+ isToolCall,
13
15
  } from "./Items.js"
14
16
 
15
17
  /**
@@ -18,7 +20,7 @@ import {
18
20
  * and reports usage + a stop reason.
19
21
  */
20
22
  export const Turn = Schema.Struct({
21
- items: Schema.Array(Item),
23
+ items: Schema.Array(HistoryItem),
22
24
  usage: Usage,
23
25
  stop_reason: StopReason,
24
26
  })
@@ -61,15 +63,13 @@ export const TurnEvent = Data.taggedEnum<TurnEvent>()
61
63
  * carrying the assembled `Turn`), plus the output of any tool the loop ran.
62
64
  * Both variants carry a `_tag` discriminator.
63
65
  */
64
- export type InteractionEvent = TurnEvent | FunctionCallOutput
66
+ export type InteractionEvent = TurnEvent | ToolCallOutput
65
67
 
66
68
  export const isTurnComplete = TurnEvent.$is("TurnComplete")
67
69
 
68
- export const functionCalls = (turn: Turn): ReadonlyArray<FunctionCall> =>
69
- turn.items.filter((i): i is FunctionCall => i.type === "function_call")
70
+ export const getToolCalls = (turn: Turn): ReadonlyArray<ToolCall> => turn.items.filter(isToolCall)
70
71
 
71
- export const reasonings = (turn: Turn): ReadonlyArray<Reasoning> =>
72
- turn.items.filter((i): i is Reasoning => i.type === "reasoning")
72
+ export const reasonings = (turn: Turn): ReadonlyArray<Reasoning> => turn.items.filter(isReasoning)
73
73
 
74
74
  export const assistantMessages = (turn: Turn): ReadonlyArray<Message> =>
75
75
  turn.items.filter((i): i is Message => i.type === "message" && i.role === "assistant")
@@ -97,12 +97,12 @@ export const assistantText = (turn: Turn): string => assistantTexts(turn).join("
97
97
  /**
98
98
  * Append a completed turn and optional follow-up items to a state record's
99
99
  * history. Recipes use this at the point where structured tool results are
100
- * converted to model-facing `FunctionCallOutput`s.
100
+ * converted to model-facing `ToolCallOutput`s.
101
101
  */
102
- export const appendTurn = <S extends { readonly history: ReadonlyArray<Item> }>(
102
+ export const appendToHistory = <S extends { readonly history: ReadonlyArray<HistoryItem> }>(
103
103
  state: S,
104
104
  turn: Turn,
105
- items: ReadonlyArray<Item> = [],
105
+ items: ReadonlyArray<HistoryItem> = [],
106
106
  ): S => ({
107
107
  ...state,
108
108
  history: [...state.history, ...turn.items, ...items],
@@ -130,7 +130,7 @@ export const textDeltas = <E, R>(
130
130
 
131
131
  /**
132
132
  * The assistant message on the just-completed turn was a refusal block,
133
- * not an `output_text` payload. Returned by `toStructured` to short-circuit
133
+ * not an `output_text` payload. Returned by `decodeStructured` to short-circuit
134
134
  * decoding before `JSON.parse` / schema validation runs.
135
135
  */
136
136
  export class RefusalRejected extends Data.TaggedError("RefusalRejected")<{
@@ -159,7 +159,7 @@ const lastAssistantContent = (turn: Turn): { readonly text: string; readonly ref
159
159
  * - `JsonParseError` — the assembled text wasn't valid JSON.
160
160
  * - `StructuredDecodeError` — the JSON didn't match the schema.
161
161
  */
162
- export const toStructured = <A>(
162
+ export const decodeStructured = <A>(
163
163
  turn: Turn,
164
164
  format: StructuredFormat.StructuredFormat<A>,
165
165
  ): Effect.Effect<
@@ -107,6 +107,17 @@ export const isBinary = (e: Embedding): e is BinaryEmbedding => e._tag === "bina
107
107
  export const isSparse = (e: Embedding): e is SparseEmbedding => e._tag === "sparse"
108
108
  export const isMultivector = (e: Embedding): e is MultivectorEmbedding => e._tag === "multivector"
109
109
 
110
+ /**
111
+ * Domain of `EmbeddingFor` and the response type parameter on
112
+ * `EmbedResponse` / `EmbedManyResponse`: every encoding a provider can
113
+ * *return*, including Jina's widened `sparse` / `multivector`. Distinct
114
+ * from `EmbedEncoding` (the cross-provider *request* set, which is dense
115
+ * quantization only). Providers whose typed request widens beyond the
116
+ * dense set (Jina) parameterize their responses with their own encoding
117
+ * union, which is a subset of this.
118
+ */
119
+ export type ResponseEncoding = "float32" | "int8" | "binary" | "sparse" | "multivector"
120
+
110
121
  /**
111
122
  * Maps an `encoding` request field to the corresponding response embedding
112
123
  * variant. `undefined` (no encoding requested) defaults to `Float32Embedding`,
@@ -27,7 +27,7 @@ describe("EmbedResponse<E> conditional narrowing", () => {
27
27
  }>()
28
28
  })
29
29
 
30
- it("maps each EmbedEncoding literal to the matching Embedding variant", () => {
30
+ it("maps each encoding literal to the matching Embedding variant", () => {
31
31
  expectTypeOf<EmbedResponse<"float32">["embedding"]>().toEqualTypeOf<Float32Embedding>()
32
32
  expectTypeOf<EmbedResponse<"int8">["embedding"]>().toEqualTypeOf<Int8Embedding>()
33
33
  expectTypeOf<EmbedResponse<"binary">["embedding"]>().toEqualTypeOf<BinaryEmbedding>()
@@ -69,13 +69,24 @@ describe("embed / embedMany free exports preserve E in their return type", () =>
69
69
  >()
70
70
  })
71
71
 
72
- it("embedMany with encoding: multivector returns EmbedManyResponse<multivector>", () => {
73
- const result = embedMany({ inputs: ["x"], model: "m", encoding: "multivector" })
72
+ it("embedMany with encoding: binary returns EmbedManyResponse<binary>", () => {
73
+ const result = embedMany({ inputs: ["x"], model: "m", encoding: "binary" })
74
74
  expectTypeOf(result).toEqualTypeOf<
75
- Effect.Effect<EmbedManyResponse<"multivector">, AiError.AiError, EmbeddingModel>
75
+ Effect.Effect<EmbedManyResponse<"binary">, AiError.AiError, EmbeddingModel>
76
76
  >()
77
77
  })
78
78
 
79
+ it("rejects non-dense encodings on the generic path (trimmed EmbedEncoding)", () => {
80
+ // `sparse` / `multivector` are Jina-only representation kinds, not part
81
+ // of the cross-provider request set. Requesting them on the generic
82
+ // helper is a compile error; they're reachable only via the typed
83
+ // `JinaEmbedding` tag.
84
+ // @ts-expect-error "multivector" is not an EmbedEncoding
85
+ embedMany({ inputs: ["x"], model: "m", encoding: "multivector" })
86
+ // @ts-expect-error "sparse" is not an EmbedEncoding
87
+ embed({ input: "x", model: "m", encoding: "sparse" })
88
+ })
89
+
79
90
  it("when the request is widened to CommonEmbedRequest, the response is the open union", () => {
80
91
  // Demonstrates the documented case: annotating the request type erases
81
92
  // the literal `encoding` and the response falls back to `Embedding`.
@@ -1,29 +1,29 @@
1
1
  import { Context, Effect } from "effect"
2
2
  import * as AiError from "../domain/AiError.js"
3
- import type { EmbeddingFor, EmbedInput, Usage } from "./Embedding.js"
3
+ import type { EmbeddingFor, EmbedInput, ResponseEncoding, Usage } from "./Embedding.js"
4
4
 
5
5
  /**
6
- * Output representation requested from the provider.
6
+ * Output representation requested from the provider. Cross-provider dense
7
+ * quantization only, the same vector at different storage cost:
8
+ * - `float32`: universal default.
9
+ * - `int8`: ~4x smaller, minimal recall loss on most benchmarks. Supported
10
+ * by Cohere, Voyage, Jina (binary only), Mixedbread.
11
+ * - `binary`: ~32x smaller, meaningful recall loss but pairs well with a
12
+ * float32 reranker pass over a small candidate set.
7
13
  *
8
- * Dense quantizations - same vector at different storage cost:
9
- * - `float32` — universal default.
10
- * - `int8` — ~4x smaller; minimal recall loss on most benchmarks.
11
- * - `binary` — ~32x smaller; meaningful recall loss but pairs well with
12
- * a float32 reranker pass over a small candidate set.
14
+ * Non-dense representations (`sparse`, `multivector`) are a different vector
15
+ * *structure*, not a storage option, and are produced by Jina only. They
16
+ * live on `JinaEncoding`, not here (the same widening pattern `task` uses).
17
+ * The response `Embedding` union still carries all arms, see
18
+ * {@link ResponseEncoding}.
13
19
  *
14
- * Non-dense representations:
15
- * - `sparse` — learned sparse vector for hybrid (dense + lexical) search.
16
- * Currently Jina ELSER only on hosted APIs.
17
- * - `multivector` — one vector per token for late-interaction (ColBERT-
18
- * style) scoring via `Vector.maxSim`. Currently Jina v4 only.
19
- *
20
- * Each provider's typed request narrows this to its supported set at
21
- * compile time (e.g. `JinaEmbedEncoding = "float32" | "binary" | "sparse" |
22
- * "multivector"`). On the generic `EmbeddingModel` path, callers can
23
- * pass any `EmbedEncoding` and the provider's API will reject mismatches at
24
- * runtime.
20
+ * Each provider's typed request narrows this to its supported set. On the
21
+ * generic `EmbeddingModel` path, a provider that emits float32 only (OpenAI,
22
+ * Gemini) rejects a non-float32 encoding with `AiError.Unsupported`
23
+ * (bucket 1) via {@link assertEncoding}, rather than silently returning a
24
+ * mislabeled float32 vector.
25
25
  */
26
- export type EmbedEncoding = "float32" | "int8" | "binary" | "sparse" | "multivector"
26
+ export type EmbedEncoding = "float32" | "int8" | "binary"
27
27
 
28
28
  /**
29
29
  * Cross-provider single-embed request. Mirrors the shape of
@@ -80,13 +80,13 @@ export type CommonEmbedManyRequest = Omit<CommonEmbedRequest, "input"> & {
80
80
  * narrowing. Defaults to `undefined` for back-compat with consumers that
81
81
  * use the bare `EmbedResponse` name.
82
82
  */
83
- export type EmbedResponse<E extends EmbedEncoding | undefined = undefined> = {
83
+ export type EmbedResponse<E extends ResponseEncoding | undefined = undefined> = {
84
84
  readonly embedding: EmbeddingFor<E>
85
85
  readonly usage: Usage
86
86
  }
87
87
 
88
88
  /** Batch-embed response. Same encoding rule as {@link EmbedResponse}. */
89
- export type EmbedManyResponse<E extends EmbedEncoding | undefined = undefined> = {
89
+ export type EmbedManyResponse<E extends ResponseEncoding | undefined = undefined> = {
90
90
  readonly embeddings: ReadonlyArray<EmbeddingFor<E>>
91
91
  readonly usage: Usage
92
92
  }
@@ -115,3 +115,30 @@ export const embedMany = <E extends EmbedEncoding | undefined = undefined>(
115
115
  request: Omit<CommonEmbedManyRequest, "encoding"> & { readonly encoding?: E },
116
116
  ): Effect.Effect<EmbedManyResponse<E>, AiError.AiError, EmbeddingModel> =>
117
117
  Effect.flatMap(EmbeddingModel.asEffect(), (m) => m.embedMany(request))
118
+
119
+ /**
120
+ * Guard a requested `encoding` against a provider's supported set. Returns
121
+ * `void` when the encoding is unset or supported, and fails
122
+ * `AiError.Unsupported` (bucket 1) otherwise.
123
+ *
124
+ * Used by the generic-tag registration of providers that emit a strict
125
+ * subset of `EmbedEncoding`: OpenAI / Gemini pass `["float32"]`, Jina passes
126
+ * `["float32", "binary"]`. Without this guard, a non-supported encoding on
127
+ * the generic path silently returns a float32 vector mislabeled as the
128
+ * requested type (the `as`-cast lie), which breaks downstream storage and
129
+ * vector math.
130
+ */
131
+ export const assertEncoding = (
132
+ encoding: EmbedEncoding | undefined,
133
+ supported: ReadonlyArray<EmbedEncoding>,
134
+ provider: string,
135
+ ): Effect.Effect<void, AiError.AiError> =>
136
+ encoding === undefined || supported.includes(encoding)
137
+ ? Effect.void
138
+ : Effect.fail(
139
+ new AiError.Unsupported({
140
+ provider,
141
+ capability: "encoding",
142
+ reason: `${provider} emits ${supported.join(" / ")} only; encoding="${encoding}" is unavailable.`,
143
+ }),
144
+ )
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * as AiError from "./domain/AiError.js"
2
2
  export * as Audio from "./domain/Audio.js"
3
+ export * as Capabilities from "./capabilities/Capabilities.js"
3
4
  export * as Image from "./domain/Image.js"
4
5
  export * as Items from "./domain/Items.js"
5
6
  export * as Media from "./domain/Media.js"
@@ -10,6 +11,11 @@ export * as Embedding from "./embedding-model/Embedding.js"
10
11
  export * as EmbeddingModel from "./embedding-model/EmbeddingModel.js"
11
12
  export * as LanguageModel from "./language-model/LanguageModel.js"
12
13
  export * as MusicGenerator from "./music-generator/MusicGenerator.js"
14
+ export * as Memory from "./sandbox/Memory.js"
15
+ export * as Sandbox from "./sandbox/Sandbox.js"
16
+ export * as SandboxError from "./sandbox/SandboxError.js"
17
+ export * as SandboxImage from "./sandbox/SandboxImage.js"
18
+ export * as SandboxNetwork from "./sandbox/SandboxNetwork.js"
13
19
  export * as SpeechSynthesizer from "./speech-synthesizer/SpeechSynthesizer.js"
14
20
  export * as Transcriber from "./transcriber/Transcriber.js"
15
21
  export * as Vector from "./math/Vector.js"
@@ -17,9 +23,9 @@ export * as Loop from "./loop/Loop.js"
17
23
  export * as Retry from "./retry/Retry.js"
18
24
  export * as Tool from "./tool/Tool.js"
19
25
  export * as Toolkit from "./tool/Toolkit.js"
20
- export * as Outcome from "./tool/Outcome.js"
26
+ export * as ToolResult from "./tool/ToolResult.js"
21
27
  export * as ToolEvent from "./tool/ToolEvent.js"
22
- export * as Resolvers from "./tool/Resolvers.js"
28
+ export * as Approval from "./tool/Approval.js"
23
29
  export * as HistoryCheck from "./tool/HistoryCheck.js"
24
30
  export * as JSONL from "./streaming/JSONL.js"
25
31
  export * as Lines from "./streaming/Lines.js"
@@ -1,6 +1,6 @@
1
1
  import { Array as Arr, Context, Effect, Option, Stream } from "effect"
2
2
  import * as AiError from "../domain/AiError.js"
3
- import type { Item } from "../domain/Items.js"
3
+ import type { HistoryItem } from "../domain/Items.js"
4
4
  import type * as StructuredFormat from "../structured-format/StructuredFormat.js"
5
5
  import type { ToolDescriptor } from "../tool/Tool.js"
6
6
  import { isTurnComplete, type Turn, type TurnEvent } from "../domain/Turn.js"
@@ -12,7 +12,7 @@ import { isTurnComplete, type Turn, type TurnEvent } from "../domain/Turn.js"
12
12
  * ...) lives in that provider's own request interface, which extends this.
13
13
  */
14
14
  export type CommonRequest = {
15
- readonly history: ReadonlyArray<Item>
15
+ readonly history: ReadonlyArray<HistoryItem>
16
16
  /**
17
17
  * Model identifier. Each provider narrows this to its typed literal union,
18
18
  * so code that yields a typed provider tag gets autocompletion.
@@ -29,7 +29,7 @@ export type CommonRequest = {
29
29
  readonly maxOutputTokens?: number
30
30
  /**
31
31
  * Schema-bound JSON output. The provider constrains the wire to match the
32
- * schema; pair with `Turn.toStructured` for runtime validation. Supported
32
+ * schema; pair with `Turn.decodeStructured` for runtime validation. Supported
33
33
  * across all current providers (OpenAI Responses json_schema, Anthropic
34
34
  * `output_config`, Gemini `responseJsonSchema`).
35
35
  */