@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.
- package/README.md +1 -1
- package/dist/{Audio-BfCTGnH3.d.mts → Audio-DPlTsNdz.d.mts} +3 -2
- package/dist/Audio-DPlTsNdz.d.mts.map +1 -0
- package/dist/{Items-BH8xUkoR.d.mts → Items-D8UaXQx_.d.mts} +15 -15
- package/dist/Items-D8UaXQx_.d.mts.map +1 -0
- package/dist/SandboxError-BvKypkJm.d.mts +158 -0
- package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
- package/dist/{Tool-87ViKCCO.d.mts → Tool-CiFXYjmR.d.mts} +11 -11
- package/dist/{Tool-87ViKCCO.d.mts.map → Tool-CiFXYjmR.d.mts.map} +1 -1
- package/dist/capabilities/Capabilities.d.mts +78 -0
- package/dist/capabilities/Capabilities.d.mts.map +1 -0
- package/dist/capabilities/Capabilities.mjs +65 -0
- package/dist/capabilities/Capabilities.mjs.map +1 -0
- package/dist/domain/Audio.d.mts +1 -1
- package/dist/domain/Audio.mjs.map +1 -1
- package/dist/domain/Items.d.mts +2 -2
- package/dist/domain/Items.mjs +15 -15
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Music.d.mts +131 -72
- package/dist/domain/Music.d.mts.map +1 -1
- package/dist/domain/Music.mjs +36 -9
- package/dist/domain/Music.mjs.map +1 -1
- package/dist/domain/Transcript.d.mts +8 -2
- package/dist/domain/Transcript.d.mts.map +1 -1
- package/dist/domain/Transcript.mjs.map +1 -1
- package/dist/domain/Turn.d.mts +388 -2
- package/dist/domain/Turn.d.mts.map +1 -0
- package/dist/domain/Turn.mjs +13 -13
- package/dist/domain/Turn.mjs.map +1 -1
- package/dist/embedding-model/Embedding.d.mts +12 -2
- package/dist/embedding-model/Embedding.d.mts.map +1 -1
- package/dist/embedding-model/Embedding.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.d.mts +36 -23
- package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
- package/dist/embedding-model/EmbeddingModel.mjs +20 -1
- package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +15 -3
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
- package/dist/index.d.mts +13 -7
- package/dist/index.mjs +9 -3
- package/dist/language-model/LanguageModel.d.mts +5 -5
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/loop/Loop.d.mts +37 -90
- package/dist/loop/Loop.d.mts.map +1 -1
- package/dist/loop/Loop.mjs +39 -93
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +46 -54
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +48 -25
- package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
- package/dist/music-generator/MusicGenerator.mjs +17 -9
- package/dist/music-generator/MusicGenerator.mjs.map +1 -1
- package/dist/music-generator/MusicGenerator.test.mjs +81 -56
- package/dist/music-generator/MusicGenerator.test.mjs.map +1 -1
- package/dist/sandbox/Memory.d.mts +53 -0
- package/dist/sandbox/Memory.d.mts.map +1 -0
- package/dist/sandbox/Memory.mjs +68 -0
- package/dist/sandbox/Memory.mjs.map +1 -0
- package/dist/sandbox/Sandbox.d.mts +717 -0
- package/dist/sandbox/Sandbox.d.mts.map +1 -0
- package/dist/sandbox/Sandbox.mjs +192 -0
- package/dist/sandbox/Sandbox.mjs.map +1 -0
- package/dist/sandbox/SandboxError.d.mts +2 -0
- package/dist/sandbox/SandboxError.mjs +89 -0
- package/dist/sandbox/SandboxError.mjs.map +1 -0
- package/dist/sandbox/SandboxImage.d.mts +28 -0
- package/dist/sandbox/SandboxImage.d.mts.map +1 -0
- package/dist/sandbox/SandboxImage.mjs +32 -0
- package/dist/sandbox/SandboxImage.mjs.map +1 -0
- package/dist/sandbox/SandboxNetwork.d.mts +34 -0
- package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
- package/dist/sandbox/SandboxNetwork.mjs +36 -0
- package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +84 -3
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +59 -5
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
- package/dist/testing/MockMusicGenerator.d.mts +12 -7
- package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
- package/dist/testing/MockMusicGenerator.mjs +4 -3
- package/dist/testing/MockMusicGenerator.mjs.map +1 -1
- package/dist/testing/MockProvider.d.mts +3 -3
- package/dist/testing/MockProvider.d.mts.map +1 -1
- package/dist/testing/MockProvider.mjs.map +1 -1
- package/dist/testing/MockSandbox.d.mts +604 -0
- package/dist/testing/MockSandbox.d.mts.map +1 -0
- package/dist/testing/MockSandbox.mjs +160 -0
- package/dist/testing/MockSandbox.mjs.map +1 -0
- package/dist/testing/MockSpeechSynthesizer.d.mts +27 -12
- package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
- package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
- package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
- package/dist/tool/Approval.d.mts.map +1 -0
- package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
- package/dist/tool/Approval.mjs.map +1 -0
- package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
- package/dist/tool/Approval.test.mjs.map +1 -0
- package/dist/tool/HistoryCheck.d.mts +6 -6
- package/dist/tool/HistoryCheck.d.mts.map +1 -1
- package/dist/tool/HistoryCheck.mjs +5 -5
- package/dist/tool/HistoryCheck.mjs.map +1 -1
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +4 -4
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +19 -19
- package/dist/tool/ToolEvent.d.mts.map +1 -1
- package/dist/tool/ToolEvent.mjs +8 -8
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
- package/dist/tool/ToolResult.d.mts.map +1 -0
- package/dist/tool/ToolResult.mjs +61 -0
- package/dist/tool/ToolResult.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +58 -26
- package/dist/tool/Toolkit.d.mts.map +1 -1
- package/dist/tool/Toolkit.mjs +62 -34
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +13 -13
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +11 -10
- package/dist/transcriber/Transcriber.d.mts.map +1 -1
- package/dist/transcriber/Transcriber.mjs.map +1 -1
- package/dist/transcriber/Transcriber.test.mjs +3 -3
- package/dist/transcriber/Transcriber.test.mjs.map +1 -1
- package/package.json +35 -7
- package/src/capabilities/Capabilities.ts +81 -0
- package/src/domain/Audio.ts +2 -1
- package/src/domain/Items.ts +9 -9
- package/src/domain/Music.ts +142 -68
- package/src/domain/Transcript.ts +7 -1
- package/src/domain/Turn.ts +16 -16
- package/src/embedding-model/Embedding.ts +11 -0
- package/src/embedding-model/EmbeddingModel.test.ts +15 -4
- package/src/embedding-model/EmbeddingModel.ts +48 -21
- package/src/index.ts +8 -2
- package/src/language-model/LanguageModel.ts +3 -3
- package/src/loop/Loop.test.ts +93 -91
- package/src/loop/Loop.ts +72 -170
- package/src/music-generator/MusicGenerator.test.ts +78 -53
- package/src/music-generator/MusicGenerator.ts +65 -24
- package/src/sandbox/Memory.ts +128 -0
- package/src/sandbox/Sandbox.ts +639 -0
- package/src/sandbox/SandboxError.ts +140 -0
- package/src/sandbox/SandboxImage.ts +26 -0
- package/src/sandbox/SandboxNetwork.ts +34 -0
- package/src/speech-synthesizer/SpeechSynthesizer.test.ts +92 -3
- package/src/speech-synthesizer/SpeechSynthesizer.ts +109 -0
- package/src/testing/MockMusicGenerator.ts +28 -18
- package/src/testing/MockProvider.ts +21 -20
- package/src/testing/MockSandbox.ts +342 -0
- package/src/testing/MockSpeechSynthesizer.ts +137 -77
- package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
- package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
- package/src/tool/HistoryCheck.ts +10 -13
- package/src/tool/Tool.ts +13 -15
- package/src/tool/ToolEvent.ts +7 -7
- package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
- package/src/tool/Toolkit.test.ts +13 -13
- package/src/tool/Toolkit.ts +97 -73
- package/src/transcriber/Transcriber.test.ts +3 -3
- package/src/transcriber/Transcriber.ts +10 -7
- package/dist/Audio-BfCTGnH3.d.mts.map +0 -1
- package/dist/Items-BH8xUkoR.d.mts.map +0 -1
- package/dist/Turn-0CwCAyVe.d.mts +0 -388
- package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
- package/dist/tool/Outcome.d.mts.map +0 -1
- package/dist/tool/Outcome.mjs +0 -61
- package/dist/tool/Outcome.mjs.map +0 -1
- package/dist/tool/Resolvers.d.mts.map +0 -1
- package/dist/tool/Resolvers.mjs.map +0 -1
- package/dist/tool/Resolvers.test.mjs.map +0 -1
- /package/dist/tool/{Resolvers.test.d.mts → Approval.test.d.mts} +0 -0
|
@@ -1,77 +1,100 @@
|
|
|
1
1
|
import { t as AiError } from "../AiError-CAX_48RU.mjs";
|
|
2
|
-
import { n as AudioChunk } from "../Audio-
|
|
3
|
-
import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, MusicResult, MusicSessionInput, WeightedPrompt } from "../domain/Music.mjs";
|
|
2
|
+
import { n as AudioChunk } from "../Audio-DPlTsNdz.mjs";
|
|
3
|
+
import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicResult, MusicSection, MusicSessionControl, MusicSessionInput, MusicStreamEvent, Watermark, WeightedPrompt } from "../domain/Music.mjs";
|
|
4
4
|
import { Context, Effect, Stream } from "effect";
|
|
5
5
|
|
|
6
6
|
//#region src/music-generator/MusicGenerator.d.ts
|
|
7
7
|
declare namespace MusicGenerator_d_exports {
|
|
8
|
-
export { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, MusicGenerator, MusicGeneratorService, MusicInteractiveSession, MusicResult, MusicSessionInput, WeightedPrompt, generate, streamGeneration, streamGenerationFrom };
|
|
8
|
+
export { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicGenerator, MusicGeneratorService, MusicInteractiveSession, MusicResult, MusicSection, MusicSessionControl, MusicSessionInput, MusicStreamEvent, Watermark, WeightedPrompt, generate, streamGeneration, streamGenerationFrom };
|
|
9
9
|
}
|
|
10
10
|
type MusicGeneratorService = {
|
|
11
11
|
/**
|
|
12
12
|
* One-shot. Prompt in, full audio bytes out. Universally supported.
|
|
13
13
|
* Async/poll-based providers (Suno, Mureka) hide their poll loop
|
|
14
|
-
* inside the adapter
|
|
14
|
+
* inside the adapter, caller still sees a single `Effect`.
|
|
15
|
+
*
|
|
16
|
+
* Returns `GenerateResult` with `primary` plus a `variants` array.
|
|
17
|
+
* Suno and Mureka always return 2 tracks; every other provider
|
|
18
|
+
* returns 1 (and `primary === variants[0]`).
|
|
15
19
|
*/
|
|
16
|
-
readonly generate: (request: CommonGenerateMusicRequest) => Effect.Effect<
|
|
20
|
+
readonly generate: (request: CommonGenerateMusicRequest) => Effect.Effect<GenerateResult, AiError>;
|
|
17
21
|
/**
|
|
18
22
|
* Prompt in, audio chunks streamed out. Providers without a native
|
|
19
23
|
* chunked-output endpoint (Lyria 3 sync, Mureka, MiniMax, Stable
|
|
20
24
|
* Audio) emulate this by calling `generate` and emitting a single
|
|
21
|
-
* `AudioChunk
|
|
25
|
+
* `AudioChunk`, first-class, no `Unsupported`.
|
|
22
26
|
*/
|
|
23
27
|
readonly streamGeneration: (request: CommonStreamGenerateMusicRequest) => Stream.Stream<AudioChunk, AiError>;
|
|
24
28
|
/**
|
|
25
|
-
* Bidirectional session: a `Stream` of
|
|
26
|
-
* in, a `Stream` of
|
|
27
|
-
*
|
|
28
|
-
* finalized via `Stream.scoped`.
|
|
29
|
+
* Bidirectional session: a `Stream` of `MusicSessionInput` (prompts
|
|
30
|
+
* + playback control) flows in, a `Stream` of `MusicStreamEvent`
|
|
31
|
+
* flows out. The session WS / RPC is acquired on first pull and
|
|
32
|
+
* released when the output stream is finalized via `Stream.scoped`.
|
|
33
|
+
*
|
|
34
|
+
* Cross-provider callers send `prompts` (steer the active
|
|
35
|
+
* generation) and `control` (play / pause / stop / reset) — the two
|
|
36
|
+
* actions that converge across interactive-media protocols.
|
|
37
|
+
* Provider-specific model knobs (Lyria RealTime's density /
|
|
38
|
+
* brightness / mute-stems / BPM / scale, etc.) live on the
|
|
39
|
+
* provider-typed service (`LyriaRealtimeGenerator.streamGenerationFrom`),
|
|
40
|
+
* which extends this union with its own `config` variant.
|
|
41
|
+
*
|
|
42
|
+
* Output is `MusicStreamEvent`: `audio` chunks alongside in-band
|
|
43
|
+
* `warning` and `filteredPrompt` events the model emits server-side.
|
|
29
44
|
*
|
|
30
45
|
* Gated by the `MusicInteractiveSession` capability marker on the
|
|
31
|
-
* top-level helper
|
|
46
|
+
* top-level helper. Providers without bidirectional support don't
|
|
32
47
|
* ship the marker, so calls fail at `Effect.provide` with a type
|
|
33
48
|
* error.
|
|
34
49
|
*/
|
|
35
|
-
readonly streamGenerationFrom: <E, R>(input: Stream.Stream<MusicSessionInput, E, R>, request: CommonStreamGenerateMusicRequest) => Stream.Stream<
|
|
50
|
+
readonly streamGenerationFrom: <E, R>(input: Stream.Stream<MusicSessionInput, E, R>, request: CommonStreamGenerateMusicRequest) => Stream.Stream<MusicStreamEvent, AiError | E, R>;
|
|
36
51
|
};
|
|
37
52
|
declare const MusicGenerator_base: Context.ServiceClass<MusicGenerator, "@betalyra/effect-uai/MusicGenerator", MusicGeneratorService>;
|
|
38
53
|
declare class MusicGenerator extends MusicGenerator_base {}
|
|
39
54
|
declare const MusicInteractiveSession_base: Context.ServiceClass<MusicInteractiveSession, "@betalyra/effect-uai/capability/MusicInteractiveSession", void>;
|
|
40
55
|
/**
|
|
41
|
-
* Capability marker
|
|
56
|
+
* Capability marker, provided by provider layers whose
|
|
42
57
|
* `streamGenerationFrom` is wired up at the wire level. Currently only
|
|
43
58
|
* Lyria RealTime (via the BidiGenerateMusic WebSocket) ships it.
|
|
44
59
|
* Calling `streamGenerationFrom` while only a non-interactive Layer is
|
|
45
60
|
* in scope fails at `Effect.provide` with a type error.
|
|
46
61
|
*
|
|
47
|
-
* Phantom
|
|
62
|
+
* Phantom: the value is `void`; providers register with
|
|
48
63
|
* `Layer.succeed(MusicInteractiveSession, undefined)`.
|
|
49
64
|
*/
|
|
50
65
|
declare class MusicInteractiveSession extends MusicInteractiveSession_base {}
|
|
51
|
-
/** One-shot generation. */
|
|
52
|
-
declare const generate: (request: CommonGenerateMusicRequest) => Effect.Effect<
|
|
66
|
+
/** One-shot generation. Returns the full `GenerateResult`. */
|
|
67
|
+
declare const generate: (request: CommonGenerateMusicRequest) => Effect.Effect<GenerateResult, AiError, MusicGenerator>;
|
|
53
68
|
/** Prompt in, audio chunks out. */
|
|
54
69
|
declare const streamGeneration: (request: CommonStreamGenerateMusicRequest) => Stream.Stream<AudioChunk, AiError, MusicGenerator>;
|
|
55
70
|
/**
|
|
56
71
|
* Bidirectional generation. Dual-arity: pipeable (data-last) and
|
|
57
|
-
* direct (data-first). Requires `MusicInteractiveSession` in R
|
|
72
|
+
* direct (data-first). Requires `MusicInteractiveSession` in R,
|
|
58
73
|
* providers without bidirectional support are a type error at provide
|
|
59
74
|
* time.
|
|
60
75
|
*
|
|
76
|
+
* On the cross-provider surface, `input` is `Stream<unknown>` and
|
|
77
|
+
* output is `Stream<MusicStreamEvent>`. Use the provider-typed
|
|
78
|
+
* service (`LyriaRealtimeGenerator`) when you want to construct
|
|
79
|
+
* typed session messages.
|
|
80
|
+
*
|
|
61
81
|
* @example
|
|
62
82
|
* ```ts
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
83
|
+
* // Generic surface (opaque input):
|
|
84
|
+
* const audio = inputs.pipe(
|
|
85
|
+
* MusicGenerator.streamGenerationFrom({ model: "lyria-realtime-exp", prompt: "" }),
|
|
86
|
+
* )
|
|
87
|
+
*
|
|
88
|
+
* // Lyria-typed surface (full config knobs):
|
|
89
|
+
* const audio = inputs.pipe(
|
|
90
|
+
* LyriaRealtimeGenerator.streamGenerationFrom({ model: "lyria-realtime-exp", prompt: "" }),
|
|
68
91
|
* )
|
|
69
92
|
* ```
|
|
70
93
|
*/
|
|
71
94
|
declare const streamGenerationFrom: {
|
|
72
|
-
(request: CommonStreamGenerateMusicRequest): <E, R>(input: Stream.Stream<MusicSessionInput, E, R>) => Stream.Stream<
|
|
73
|
-
<E, R>(input: Stream.Stream<MusicSessionInput, E, R>, request: CommonStreamGenerateMusicRequest): Stream.Stream<
|
|
95
|
+
(request: CommonStreamGenerateMusicRequest): <E, R>(input: Stream.Stream<MusicSessionInput, E, R>) => Stream.Stream<MusicStreamEvent, AiError | E, R | MusicGenerator | MusicInteractiveSession>;
|
|
96
|
+
<E, R>(input: Stream.Stream<MusicSessionInput, E, R>, request: CommonStreamGenerateMusicRequest): Stream.Stream<MusicStreamEvent, AiError | E, R | MusicGenerator | MusicInteractiveSession>;
|
|
74
97
|
};
|
|
75
98
|
//#endregion
|
|
76
|
-
export { type CommonGenerateMusicRequest, type CommonStreamGenerateMusicRequest, MusicGenerator, MusicGeneratorService, MusicInteractiveSession, type MusicResult, type MusicSessionInput, type WeightedPrompt, generate, streamGeneration, streamGenerationFrom, MusicGenerator_d_exports as t };
|
|
99
|
+
export { type CommonGenerateMusicRequest, type CommonStreamGenerateMusicRequest, type GenerateResult, MusicGenerator, MusicGeneratorService, MusicInteractiveSession, type MusicResult, type MusicSection, type MusicSessionControl, type MusicSessionInput, type MusicStreamEvent, type Watermark, type WeightedPrompt, generate, streamGeneration, streamGenerationFrom, MusicGenerator_d_exports as t };
|
|
77
100
|
//# sourceMappingURL=MusicGenerator.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MusicGenerator.d.mts","names":[],"sources":["../../src/music-generator/MusicGenerator.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"MusicGenerator.d.mts","names":[],"sources":["../../src/music-generator/MusicGenerator.ts"],"mappings":";;;;;;;;;KAwBY,qBAAA;;;;;;;;;;WAUD,QAAA,GACP,OAAA,EAAS,0BAAA,KACN,MAAA,CAAO,MAAA,CAAO,cAAA,EAAgB,OAAA;;;;;;;WAO1B,gBAAA,GACP,OAAA,EAAS,gCAAA,KACN,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA;;;AArBjC;;;;;;;;;;;;;;;;;;;;WA4CW,oBAAA,SACP,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,CAAA,EAAG,CAAA,GAC3C,OAAA,EAAS,gCAAA,KACN,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,OAAA,GAAkB,CAAA,EAAG,CAAA;AAAA;AAAA,cAC3D,mBAAA;cAEY,cAAA,SAAuB,mBAAA;AAAA,cAEhC,4BAAA;;;;;;;;;;;cAYS,uBAAA,SAAgC,4BAAA;;cAKhC,QAAA,GACX,OAAA,EAAS,0BAAA,KACR,MAAA,CAAO,MAAA,CAAO,cAAA,EAAgB,OAAA,EAAiB,cAAA;;cAIrC,gBAAA,GACX,OAAA,EAAS,gCAAA,KACR,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,EAAiB,cAAA;;;;;;;;;;;;;;;;;;AA7B7C;;;;;;;cAwDY,oBAAA;EAAA,CAET,OAAA,EAAS,gCAAA,UAET,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,CAAA,EAAG,CAAA,MACxC,MAAA,CAAO,MAAA,CACV,gBAAA,EACA,OAAA,GAAkB,CAAA,EAClB,CAAA,GAAI,cAAA,GAAiB,uBAAA;EAAA,OAGrB,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,CAAA,EAAG,CAAA,GAC3C,OAAA,EAAS,gCAAA,GACR,MAAA,CAAO,MAAA,CACR,gBAAA,EACA,OAAA,GAAkB,CAAA,EAClB,CAAA,GAAI,cAAA,GAAiB,uBAAA;AAAA"}
|
|
@@ -10,33 +10,41 @@ var MusicGenerator_exports = /* @__PURE__ */ __exportAll({
|
|
|
10
10
|
});
|
|
11
11
|
var MusicGenerator = class extends Context.Service()("@betalyra/effect-uai/MusicGenerator") {};
|
|
12
12
|
/**
|
|
13
|
-
* Capability marker
|
|
13
|
+
* Capability marker, provided by provider layers whose
|
|
14
14
|
* `streamGenerationFrom` is wired up at the wire level. Currently only
|
|
15
15
|
* Lyria RealTime (via the BidiGenerateMusic WebSocket) ships it.
|
|
16
16
|
* Calling `streamGenerationFrom` while only a non-interactive Layer is
|
|
17
17
|
* in scope fails at `Effect.provide` with a type error.
|
|
18
18
|
*
|
|
19
|
-
* Phantom
|
|
19
|
+
* Phantom: the value is `void`; providers register with
|
|
20
20
|
* `Layer.succeed(MusicInteractiveSession, undefined)`.
|
|
21
21
|
*/
|
|
22
22
|
var MusicInteractiveSession = class extends Context.Service()("@betalyra/effect-uai/capability/MusicInteractiveSession") {};
|
|
23
|
-
/** One-shot generation. */
|
|
23
|
+
/** One-shot generation. Returns the full `GenerateResult`. */
|
|
24
24
|
const generate = (request) => Effect.flatMap(MusicGenerator.asEffect(), (s) => s.generate(request));
|
|
25
25
|
/** Prompt in, audio chunks out. */
|
|
26
26
|
const streamGeneration = (request) => Stream.unwrap(Effect.map(MusicGenerator.asEffect(), (s) => s.streamGeneration(request)));
|
|
27
27
|
/**
|
|
28
28
|
* Bidirectional generation. Dual-arity: pipeable (data-last) and
|
|
29
|
-
* direct (data-first). Requires `MusicInteractiveSession` in R
|
|
29
|
+
* direct (data-first). Requires `MusicInteractiveSession` in R,
|
|
30
30
|
* providers without bidirectional support are a type error at provide
|
|
31
31
|
* time.
|
|
32
32
|
*
|
|
33
|
+
* On the cross-provider surface, `input` is `Stream<unknown>` and
|
|
34
|
+
* output is `Stream<MusicStreamEvent>`. Use the provider-typed
|
|
35
|
+
* service (`LyriaRealtimeGenerator`) when you want to construct
|
|
36
|
+
* typed session messages.
|
|
37
|
+
*
|
|
33
38
|
* @example
|
|
34
39
|
* ```ts
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* // Generic surface (opaque input):
|
|
41
|
+
* const audio = inputs.pipe(
|
|
42
|
+
* MusicGenerator.streamGenerationFrom({ model: "lyria-realtime-exp", prompt: "" }),
|
|
43
|
+
* )
|
|
44
|
+
*
|
|
45
|
+
* // Lyria-typed surface (full config knobs):
|
|
46
|
+
* const audio = inputs.pipe(
|
|
47
|
+
* LyriaRealtimeGenerator.streamGenerationFrom({ model: "lyria-realtime-exp", prompt: "" }),
|
|
40
48
|
* )
|
|
41
49
|
* ```
|
|
42
50
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MusicGenerator.mjs","names":[],"sources":["../../src/music-generator/MusicGenerator.ts"],"sourcesContent":["import { Context, Effect, Function, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type {
|
|
1
|
+
{"version":3,"file":"MusicGenerator.mjs","names":[],"sources":["../../src/music-generator/MusicGenerator.ts"],"sourcesContent":["import { Context, Effect, Function, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n GenerateResult,\n MusicSessionInput,\n MusicStreamEvent,\n} from \"../domain/Music.js\"\nimport type { AudioChunk } from \"../domain/Audio.js\"\n\nexport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n GenerateResult,\n MusicResult,\n MusicSection,\n MusicSessionControl,\n MusicSessionInput,\n MusicStreamEvent,\n Watermark,\n WeightedPrompt,\n} from \"../domain/Music.js\"\n\nexport type MusicGeneratorService = {\n /**\n * One-shot. Prompt in, full audio bytes out. Universally supported.\n * Async/poll-based providers (Suno, Mureka) hide their poll loop\n * inside the adapter, caller still sees a single `Effect`.\n *\n * Returns `GenerateResult` with `primary` plus a `variants` array.\n * Suno and Mureka always return 2 tracks; every other provider\n * returns 1 (and `primary === variants[0]`).\n */\n readonly generate: (\n request: CommonGenerateMusicRequest,\n ) => Effect.Effect<GenerateResult, AiError.AiError>\n /**\n * Prompt in, audio chunks streamed out. Providers without a native\n * chunked-output endpoint (Lyria 3 sync, Mureka, MiniMax, Stable\n * Audio) emulate this by calling `generate` and emitting a single\n * `AudioChunk`, first-class, no `Unsupported`.\n */\n readonly streamGeneration: (\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n /**\n * Bidirectional session: a `Stream` of `MusicSessionInput` (prompts\n * + playback control) flows in, a `Stream` of `MusicStreamEvent`\n * flows out. The session WS / RPC is acquired on first pull and\n * released when the output stream is finalized via `Stream.scoped`.\n *\n * Cross-provider callers send `prompts` (steer the active\n * generation) and `control` (play / pause / stop / reset) — the two\n * actions that converge across interactive-media protocols.\n * Provider-specific model knobs (Lyria RealTime's density /\n * brightness / mute-stems / BPM / scale, etc.) live on the\n * provider-typed service (`LyriaRealtimeGenerator.streamGenerationFrom`),\n * which extends this union with its own `config` variant.\n *\n * Output is `MusicStreamEvent`: `audio` chunks alongside in-band\n * `warning` and `filteredPrompt` events the model emits server-side.\n *\n * Gated by the `MusicInteractiveSession` capability marker on the\n * top-level helper. Providers without bidirectional support don't\n * ship the marker, so calls fail at `Effect.provide` with a type\n * error.\n */\n readonly streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<MusicStreamEvent, AiError.AiError | E, R>\n}\n\nexport class MusicGenerator extends Context.Service<MusicGenerator, MusicGeneratorService>()(\n \"@betalyra/effect-uai/MusicGenerator\",\n) {}\n\n/**\n * Capability marker, provided by provider layers whose\n * `streamGenerationFrom` is wired up at the wire level. Currently only\n * Lyria RealTime (via the BidiGenerateMusic WebSocket) ships it.\n * Calling `streamGenerationFrom` while only a non-interactive Layer is\n * in scope fails at `Effect.provide` with a type error.\n *\n * Phantom: the value is `void`; providers register with\n * `Layer.succeed(MusicInteractiveSession, undefined)`.\n */\nexport class MusicInteractiveSession extends Context.Service<MusicInteractiveSession, void>()(\n \"@betalyra/effect-uai/capability/MusicInteractiveSession\",\n) {}\n\n/** One-shot generation. Returns the full `GenerateResult`. */\nexport const generate = (\n request: CommonGenerateMusicRequest,\n): Effect.Effect<GenerateResult, AiError.AiError, MusicGenerator> =>\n Effect.flatMap(MusicGenerator.asEffect(), (s) => s.generate(request))\n\n/** Prompt in, audio chunks out. */\nexport const streamGeneration = (\n request: CommonStreamGenerateMusicRequest,\n): Stream.Stream<AudioChunk, AiError.AiError, MusicGenerator> =>\n Stream.unwrap(Effect.map(MusicGenerator.asEffect(), (s) => s.streamGeneration(request)))\n\n/**\n * Bidirectional generation. Dual-arity: pipeable (data-last) and\n * direct (data-first). Requires `MusicInteractiveSession` in R,\n * providers without bidirectional support are a type error at provide\n * time.\n *\n * On the cross-provider surface, `input` is `Stream<unknown>` and\n * output is `Stream<MusicStreamEvent>`. Use the provider-typed\n * service (`LyriaRealtimeGenerator`) when you want to construct\n * typed session messages.\n *\n * @example\n * ```ts\n * // Generic surface (opaque input):\n * const audio = inputs.pipe(\n * MusicGenerator.streamGenerationFrom({ model: \"lyria-realtime-exp\", prompt: \"\" }),\n * )\n *\n * // Lyria-typed surface (full config knobs):\n * const audio = inputs.pipe(\n * LyriaRealtimeGenerator.streamGenerationFrom({ model: \"lyria-realtime-exp\", prompt: \"\" }),\n * )\n * ```\n */\nexport const streamGenerationFrom: {\n (\n request: CommonStreamGenerateMusicRequest,\n ): <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n ) => Stream.Stream<\n MusicStreamEvent,\n AiError.AiError | E,\n R | MusicGenerator | MusicInteractiveSession\n >\n <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ): Stream.Stream<\n MusicStreamEvent,\n AiError.AiError | E,\n R | MusicGenerator | MusicInteractiveSession\n >\n} = Function.dual(\n 2,\n <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ): Stream.Stream<\n MusicStreamEvent,\n AiError.AiError | E,\n R | MusicGenerator | MusicInteractiveSession\n > =>\n Stream.unwrap(\n Effect.gen(function* () {\n const s = yield* MusicGenerator.asEffect()\n yield* MusicInteractiveSession.asEffect()\n return s.streamGenerationFrom(input, request)\n }),\n ),\n)\n"],"mappings":";;;;;;;;;;AA0EA,IAAa,iBAAb,cAAoC,QAAQ,SAAgD,CAC1F,sCACD,CAAC;;;;;;;;;;;AAYF,IAAa,0BAAb,cAA6C,QAAQ,SAAwC,CAC3F,0DACD,CAAC;;AAGF,MAAa,YACX,YAEA,OAAO,QAAQ,eAAe,UAAU,GAAG,MAAM,EAAE,SAAS,QAAQ,CAAC;;AAGvE,MAAa,oBACX,YAEA,OAAO,OAAO,OAAO,IAAI,eAAe,UAAU,GAAG,MAAM,EAAE,iBAAiB,QAAQ,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA0B1F,MAAa,uBAkBT,SAAS,KACX,IAEE,OACA,YAMA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,IAAI,OAAO,eAAe,UAAU;AAC1C,QAAO,wBAAwB,UAAU;AACzC,QAAO,EAAE,qBAAqB,OAAO,QAAQ;EAC7C,CACH,CACJ"}
|
|
@@ -1,66 +1,90 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { audioEvent, controlInput, promptsInput, singleVariant } from "../domain/Music.mjs";
|
|
2
2
|
import { generate, streamGeneration, streamGenerationFrom } from "./MusicGenerator.mjs";
|
|
3
3
|
import { i as it, n as globalExpect, r as describe, t as import_dist } from "../dist-DV5ISja1.mjs";
|
|
4
4
|
import { layer, layerWithoutInteractive } from "../testing/MockMusicGenerator.mjs";
|
|
5
|
-
import { Effect, Stream } from "effect";
|
|
5
|
+
import { Duration, Effect, Stream } from "effect";
|
|
6
6
|
//#region src/music-generator/MusicGenerator.test.ts
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const oneResult = {
|
|
8
|
+
audio: {
|
|
9
|
+
format: {
|
|
10
|
+
container: "mp3",
|
|
11
|
+
encoding: "mp3",
|
|
12
|
+
sampleRate: 44100,
|
|
13
|
+
channels: 2
|
|
14
|
+
},
|
|
15
|
+
bytes: new Uint8Array([
|
|
16
|
+
255,
|
|
17
|
+
251,
|
|
18
|
+
144,
|
|
19
|
+
0
|
|
20
|
+
]),
|
|
21
|
+
duration: Duration.seconds(30)
|
|
13
22
|
},
|
|
14
|
-
|
|
15
|
-
255,
|
|
16
|
-
251,
|
|
17
|
-
144,
|
|
18
|
-
0
|
|
19
|
-
]),
|
|
20
|
-
durationSeconds: 30,
|
|
23
|
+
provider: "mock-music",
|
|
21
24
|
lyrics: "[Verse]\nhello\n",
|
|
22
|
-
watermark:
|
|
25
|
+
watermark: "synthid"
|
|
23
26
|
};
|
|
27
|
+
const oneVariant = singleVariant(oneResult);
|
|
24
28
|
const chunk = (n) => ({ bytes: new Uint8Array([n]) });
|
|
25
29
|
describe("MusicGenerator.generate", () => {
|
|
26
|
-
it("returns the scripted
|
|
27
|
-
const mock = layer({ results: [
|
|
30
|
+
it("returns the scripted GenerateResult", async () => {
|
|
31
|
+
const mock = layer({ results: [oneVariant] });
|
|
28
32
|
const program = generate({
|
|
29
33
|
model: "mock-music",
|
|
30
|
-
|
|
34
|
+
prompt: "upbeat indie pop"
|
|
31
35
|
});
|
|
32
36
|
const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
|
|
33
|
-
globalExpect(out.bytes).toEqual(
|
|
34
|
-
globalExpect(out.
|
|
35
|
-
globalExpect(out.watermark
|
|
36
|
-
globalExpect(out.lyrics).toContain("[Verse]");
|
|
37
|
+
globalExpect(out.primary.audio.bytes).toEqual(oneResult.audio.bytes);
|
|
38
|
+
globalExpect(out.primary.audio.duration).toEqual(Duration.seconds(30));
|
|
39
|
+
globalExpect(out.primary.watermark).toBe("synthid");
|
|
40
|
+
globalExpect(out.primary.lyrics).toContain("[Verse]");
|
|
41
|
+
globalExpect(out.variants).toHaveLength(1);
|
|
42
|
+
globalExpect(out.variants[0]).toBe(out.primary);
|
|
43
|
+
});
|
|
44
|
+
it("exposes multiple variants when the provider returned more than one", async () => {
|
|
45
|
+
const secondTrack = {
|
|
46
|
+
...oneResult,
|
|
47
|
+
audio: {
|
|
48
|
+
...oneResult.audio,
|
|
49
|
+
bytes: new Uint8Array([
|
|
50
|
+
255,
|
|
51
|
+
251,
|
|
52
|
+
160,
|
|
53
|
+
0
|
|
54
|
+
])
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const mock = layer({ results: [{
|
|
58
|
+
primary: oneResult,
|
|
59
|
+
variants: [oneResult, secondTrack]
|
|
60
|
+
}] });
|
|
61
|
+
const program = generate({
|
|
62
|
+
model: "m",
|
|
63
|
+
prompt: "house"
|
|
64
|
+
});
|
|
65
|
+
const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
|
|
66
|
+
globalExpect(out.variants).toHaveLength(2);
|
|
67
|
+
globalExpect(out.variants[1].audio.bytes).toEqual(secondTrack.audio.bytes);
|
|
37
68
|
});
|
|
38
69
|
it("records the request shape on the recorder", async () => {
|
|
39
|
-
const mock = layer({ results: [
|
|
70
|
+
const mock = layer({ results: [oneVariant, oneVariant] });
|
|
40
71
|
const program = Effect.gen(function* () {
|
|
41
72
|
yield* generate({
|
|
42
73
|
model: "m",
|
|
43
|
-
|
|
74
|
+
prompt: "techno"
|
|
44
75
|
});
|
|
45
76
|
yield* generate({
|
|
46
77
|
model: "m",
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}, {
|
|
51
|
-
text: "80s movie OST",
|
|
52
|
-
weight: .4
|
|
53
|
-
}],
|
|
54
|
-
bpm: 120,
|
|
55
|
-
instrumental: true
|
|
78
|
+
prompt: "synthwave",
|
|
79
|
+
duration: Duration.seconds(45),
|
|
80
|
+
seed: 42
|
|
56
81
|
});
|
|
57
82
|
return yield* mock.recorder;
|
|
58
83
|
});
|
|
59
84
|
const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
|
|
60
85
|
globalExpect(rec.generateCalls.length).toBe(2);
|
|
61
|
-
globalExpect(rec.generateCalls[1].
|
|
62
|
-
globalExpect(rec.generateCalls[1].
|
|
63
|
-
globalExpect(Array.isArray(rec.generateCalls[1].prompts)).toBe(true);
|
|
86
|
+
globalExpect(rec.generateCalls[1].duration).toEqual(Duration.seconds(45));
|
|
87
|
+
globalExpect(rec.generateCalls[1].seed).toBe(42);
|
|
64
88
|
});
|
|
65
89
|
});
|
|
66
90
|
describe("MusicGenerator.streamGeneration", () => {
|
|
@@ -72,7 +96,7 @@ describe("MusicGenerator.streamGeneration", () => {
|
|
|
72
96
|
]] });
|
|
73
97
|
const program = Stream.runCollect(streamGeneration({
|
|
74
98
|
model: "m",
|
|
75
|
-
|
|
99
|
+
prompt: "ambient"
|
|
76
100
|
}));
|
|
77
101
|
globalExpect((await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))).map((c) => Array.from(c.bytes))).toEqual([
|
|
78
102
|
[1],
|
|
@@ -84,7 +108,7 @@ describe("MusicGenerator.streamGeneration", () => {
|
|
|
84
108
|
describe("MusicGenerator capability marker (compile-time)", () => {
|
|
85
109
|
const sgfReq = {
|
|
86
110
|
model: "m",
|
|
87
|
-
|
|
111
|
+
prompt: ""
|
|
88
112
|
};
|
|
89
113
|
it("requires `MusicInteractiveSession` on the R channel of streamGenerationFrom", () => {
|
|
90
114
|
(0, import_dist.expectTypeOf)(Stream.fromIterable([promptsInput([{ text: "techno" }])]).pipe(streamGenerationFrom(sgfReq))).toEqualTypeOf();
|
|
@@ -92,39 +116,39 @@ describe("MusicGenerator capability marker (compile-time)", () => {
|
|
|
92
116
|
it("does NOT require `MusicInteractiveSession` for sync `generate`", () => {
|
|
93
117
|
(0, import_dist.expectTypeOf)(generate({
|
|
94
118
|
model: "m",
|
|
95
|
-
|
|
119
|
+
prompt: "ambient"
|
|
96
120
|
})).toEqualTypeOf();
|
|
97
121
|
});
|
|
98
122
|
it("does NOT require `MusicInteractiveSession` for `streamGeneration`", () => {
|
|
99
123
|
(0, import_dist.expectTypeOf)(streamGeneration({
|
|
100
124
|
model: "m",
|
|
101
|
-
|
|
125
|
+
prompt: "ambient"
|
|
102
126
|
})).toEqualTypeOf();
|
|
103
127
|
});
|
|
104
128
|
it("a layer without the marker leaves `MusicInteractiveSession` unsatisfied in R", () => {
|
|
105
129
|
const noMarker = layerWithoutInteractive({});
|
|
106
|
-
const
|
|
107
|
-
(0, import_dist.expectTypeOf)(Stream.runDrain(
|
|
130
|
+
const events = Stream.fromIterable([promptsInput([{ text: "techno" }])]).pipe(streamGenerationFrom(sgfReq));
|
|
131
|
+
(0, import_dist.expectTypeOf)(Stream.runDrain(events).pipe(Effect.provide(noMarker.layer))).toEqualTypeOf();
|
|
108
132
|
});
|
|
109
133
|
it("a full layer (with marker) clears R to never", () => {
|
|
110
|
-
const fullMock = layer({
|
|
111
|
-
const
|
|
112
|
-
(0, import_dist.expectTypeOf)(Stream.runDrain(
|
|
134
|
+
const fullMock = layer({ streamGenerationFromEvents: [[]] });
|
|
135
|
+
const events = Stream.fromIterable([promptsInput([{ text: "techno" }])]).pipe(streamGenerationFrom(sgfReq));
|
|
136
|
+
(0, import_dist.expectTypeOf)(Stream.runDrain(events).pipe(Effect.provide(fullMock.layer))).toEqualTypeOf();
|
|
113
137
|
});
|
|
114
138
|
});
|
|
115
139
|
describe("MusicGenerator.streamGenerationFrom", () => {
|
|
116
140
|
const sgfReq = {
|
|
117
|
-
model: "lyria-realtime-
|
|
118
|
-
|
|
141
|
+
model: "lyria-realtime-exp",
|
|
142
|
+
prompt: ""
|
|
119
143
|
};
|
|
120
|
-
it("drains a session-input stream and emits scripted
|
|
121
|
-
const mock = layer({
|
|
122
|
-
const
|
|
144
|
+
it("drains a session-input stream and emits scripted events", async () => {
|
|
145
|
+
const mock = layer({ streamGenerationFromEvents: [[audioEvent(chunk(10)), audioEvent(chunk(20))]] });
|
|
146
|
+
const events = Stream.fromIterable([
|
|
123
147
|
promptsInput([{
|
|
124
148
|
text: "minimal techno",
|
|
125
149
|
weight: 1
|
|
126
150
|
}]),
|
|
127
|
-
|
|
151
|
+
controlInput("play"),
|
|
128
152
|
promptsInput([{
|
|
129
153
|
text: "minimal techno",
|
|
130
154
|
weight: 1
|
|
@@ -133,17 +157,18 @@ describe("MusicGenerator.streamGenerationFrom", () => {
|
|
|
133
157
|
weight: .3
|
|
134
158
|
}])
|
|
135
159
|
]).pipe(streamGenerationFrom(sgfReq));
|
|
136
|
-
globalExpect((await Effect.runPromise(Stream.runCollect(
|
|
160
|
+
globalExpect((await Effect.runPromise(Stream.runCollect(events).pipe(Effect.provide(mock.layer)))).flatMap((e) => e._tag === "audio" ? [e.chunk] : []).map((c) => Array.from(c.bytes))).toEqual([[10], [20]]);
|
|
137
161
|
});
|
|
138
162
|
it("records the request on the streamGenerationFrom call channel", async () => {
|
|
139
|
-
const mock = layer({
|
|
163
|
+
const mock = layer({ streamGenerationFromEvents: [[audioEvent(chunk(42))]] });
|
|
140
164
|
const program = Effect.gen(function* () {
|
|
141
|
-
|
|
165
|
+
const inputs = Stream.fromIterable([promptsInput([{ text: "x" }])]);
|
|
166
|
+
yield* Stream.runDrain(inputs.pipe(streamGenerationFrom(sgfReq)));
|
|
142
167
|
return yield* mock.recorder;
|
|
143
168
|
});
|
|
144
169
|
const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
|
|
145
170
|
globalExpect(rec.streamGenerationFromCalls.length).toBe(1);
|
|
146
|
-
globalExpect(rec.streamGenerationFromCalls[0].model).toBe("lyria-realtime-
|
|
171
|
+
globalExpect(rec.streamGenerationFromCalls[0].model).toBe("lyria-realtime-exp");
|
|
147
172
|
globalExpect(rec.generateCalls.length).toBe(0);
|
|
148
173
|
globalExpect(rec.streamGenerationCalls.length).toBe(0);
|
|
149
174
|
});
|
|
@@ -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 { 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 { configInput, promptsInput, type MusicResult } 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 result: MusicResult = {\n format: mp3Format,\n bytes: new Uint8Array([0xff, 0xfb, 0x90, 0x00]),\n durationSeconds: 30,\n lyrics: \"[Verse]\\nhello\\n\",\n watermark: { kind: \"synthid\" },\n}\n\nconst chunk = (n: number): AudioChunk => ({ bytes: new Uint8Array([n]) })\n\ndescribe(\"MusicGenerator.generate\", () => {\n it(\"returns the scripted MusicResult\", async () => {\n const mock = MockMusicGenerator.layer({ results: [result] })\n const program = MusicGenerator.generate({\n model: \"mock-music\",\n prompts: \"upbeat indie pop\",\n })\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.bytes).toEqual(result.bytes)\n expect(out.durationSeconds).toBe(30)\n expect(out.watermark?.kind).toBe(\"synthid\")\n expect(out.lyrics).toContain(\"[Verse]\")\n })\n\n it(\"records the request shape on the recorder\", async () => {\n const mock = MockMusicGenerator.layer({ results: [result, result] })\n const program = Effect.gen(function* () {\n yield* MusicGenerator.generate({ model: \"m\", prompts: \"techno\" })\n yield* MusicGenerator.generate({\n model: \"m\",\n prompts: [\n { text: \"synthwave\", weight: 1.0 },\n { text: \"80s movie OST\", weight: 0.4 },\n ],\n bpm: 120,\n instrumental: true,\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]!.bpm).toBe(120)\n expect(rec.generateCalls[1]!.instrumental).toBe(true)\n expect(Array.isArray(rec.generateCalls[1]!.prompts)).toBe(true)\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\", prompts: \"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 prompts: \"\",\n }\n\n it(\"requires `MusicInteractiveSession` on the R channel of streamGenerationFrom\", () => {\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const audio = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n expectTypeOf(audio).toEqualTypeOf<\n Stream.Stream<\n AudioChunk,\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\", prompts: \"ambient\" })\n expectTypeOf(eff).toEqualTypeOf<\n Effect.Effect<MusicResult, AiError.AiError, MusicGenerator.MusicGenerator>\n >()\n })\n\n it(\"does NOT require `MusicInteractiveSession` for `streamGeneration`\", () => {\n const audio = MusicGenerator.streamGeneration({ model: \"m\", prompts: \"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 audio = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const program = Stream.runDrain(audio).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 streamGenerationFromChunks: [[]],\n })\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const audio = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const program = Stream.runDrain(audio).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-001\",\n prompts: \"\",\n }\n\n it(\"drains a session-input stream and emits scripted audio\", async () => {\n const mock = MockMusicGenerator.layer({\n streamGenerationFromChunks: [[chunk(10), chunk(20)]],\n })\n const inputs = Stream.fromIterable([\n promptsInput([{ text: \"minimal techno\", weight: 1.0 }]),\n configInput({ bpm: 124 }),\n promptsInput([\n { text: \"minimal techno\", weight: 1.0 },\n { text: \"1980s synthwave\", weight: 0.3 },\n ]),\n ])\n const audio = inputs.pipe(MusicGenerator.streamGenerationFrom(sgfReq))\n const out = await Effect.runPromise(Stream.runCollect(audio).pipe(Effect.provide(mock.layer)))\n expect(out.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 streamGenerationFromChunks: [[chunk(42)]],\n })\n const program = Effect.gen(function* () {\n yield* Stream.runDrain(\n Stream.fromIterable([promptsInput([{ text: \"x\" }])]).pipe(\n MusicGenerator.streamGenerationFrom(sgfReq),\n ),\n )\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-001\")\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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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Brand } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/sandbox/Memory.d.ts
|
|
4
|
+
declare namespace Memory_d_exports {
|
|
5
|
+
export { Input, Memory, bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, toBytes };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A memory / data size, always represented internally as a number of
|
|
9
|
+
* bytes. Mirrors the `Duration` shape from `effect` — a branded value
|
|
10
|
+
* type with a wider `Input` union for ergonomic construction at call
|
|
11
|
+
* sites.
|
|
12
|
+
*
|
|
13
|
+
* `effect` itself has no byte-count primitive (only `Duration`), so we
|
|
14
|
+
* own this here rather than leak provider-specific shapes (Deno's
|
|
15
|
+
* template-literal `Memory`, Vercel's `mb`, Modal's `gpuMemory`, …)
|
|
16
|
+
* through the cross-provider `Sandbox` request types.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import * as Memory from "@effect-uai/core/Memory"
|
|
21
|
+
*
|
|
22
|
+
* Memory.gib(2) // 2 GiB → 2_147_483_648 bytes
|
|
23
|
+
* Memory.fromInputUnsafe("1.5 GiB") // human-string parsing
|
|
24
|
+
* Memory.fromInputUnsafe(1_280_000_000) // raw bytes
|
|
25
|
+
* Memory.toBytes(m) // unwrap
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
type Memory = Brand.Branded<number, "Memory">;
|
|
29
|
+
type Input = number | Memory | string;
|
|
30
|
+
/**
|
|
31
|
+
* Coerce a {@link Input} to a {@link Memory}. Throws on malformed
|
|
32
|
+
* strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
|
|
33
|
+
*/
|
|
34
|
+
declare const fromInputUnsafe: (input: Input) => Memory;
|
|
35
|
+
/** Return the underlying byte count. */
|
|
36
|
+
declare const toBytes: (m: Memory | Input) => number;
|
|
37
|
+
/** N bytes. */
|
|
38
|
+
declare const bytes: (n: number) => Memory;
|
|
39
|
+
/** N KiB (binary kibibytes, `n * 1024`). */
|
|
40
|
+
declare const kib: (n: number) => Memory;
|
|
41
|
+
/** N MiB (binary mebibytes, `n * 1024^2`). */
|
|
42
|
+
declare const mib: (n: number) => Memory;
|
|
43
|
+
/** N GiB (binary gibibytes, `n * 1024^3`). */
|
|
44
|
+
declare const gib: (n: number) => Memory;
|
|
45
|
+
/** N kB (decimal kilobytes, `n * 1000`). */
|
|
46
|
+
declare const kb: (n: number) => Memory;
|
|
47
|
+
/** N MB (decimal megabytes, `n * 1_000_000`). */
|
|
48
|
+
declare const mb: (n: number) => Memory;
|
|
49
|
+
/** N GB (decimal gigabytes, `n * 1_000_000_000`). */
|
|
50
|
+
declare const gb: (n: number) => Memory;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Input, Memory, bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, Memory_d_exports as t, toBytes };
|
|
53
|
+
//# sourceMappingURL=Memory.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Memory.d.mts","names":[],"sources":["../../src/sandbox/Memory.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;KAAY,MAAA,GAAS,KAAA,CAAM,OAAA;AAAA,KAuBf,KAAA,YAAiB,MAAA;;;;AAqC7B;cAAa,eAAA,GAAmB,KAAA,EAAO,KAAA,KAAQ,MAAA;;cAelC,OAAA,GAAW,CAAA,EAAG,MAAA,GAAS,KAAA;;cAUvB,KAAA,GAAS,CAAA,aAAY,MAAA;;cAGrB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,EAAA,GAAM,CAAA,aAAY,MAAA;;cAGlB,EAAA,GAAM,CAAA,aAAY,MAAA;;cAGlB,EAAA,GAAM,CAAA,aAAY,MAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { Brand } from "effect";
|
|
3
|
+
//#region src/sandbox/Memory.ts
|
|
4
|
+
var Memory_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
bytes: () => bytes,
|
|
6
|
+
fromInputUnsafe: () => fromInputUnsafe,
|
|
7
|
+
gb: () => gb,
|
|
8
|
+
gib: () => gib,
|
|
9
|
+
kb: () => kb,
|
|
10
|
+
kib: () => kib,
|
|
11
|
+
mb: () => mb,
|
|
12
|
+
mib: () => mib,
|
|
13
|
+
toBytes: () => toBytes
|
|
14
|
+
});
|
|
15
|
+
const make = Brand.nominal();
|
|
16
|
+
const UNIT_BYTES = {
|
|
17
|
+
b: 1,
|
|
18
|
+
kb: 1e3,
|
|
19
|
+
mb: 1e6,
|
|
20
|
+
gb: 1e9,
|
|
21
|
+
tb: 0xe8d4a51000,
|
|
22
|
+
kib: 1024,
|
|
23
|
+
mib: 1024 * 1024,
|
|
24
|
+
gib: 1024 * 1024 * 1024,
|
|
25
|
+
tib: 1024 * 1024 * 1024 * 1024
|
|
26
|
+
};
|
|
27
|
+
const PARSE_RE = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]*)\s*$/;
|
|
28
|
+
const parseString = (s) => {
|
|
29
|
+
const m = PARSE_RE.exec(s);
|
|
30
|
+
if (m === null) throw new Error(`Memory: cannot parse "${s}" — expected forms like "1024", "10 MB", "1GiB"`);
|
|
31
|
+
const value = Number(m[1]);
|
|
32
|
+
const unit = (m[2] ?? "").toLowerCase();
|
|
33
|
+
if (unit === "") return value;
|
|
34
|
+
const factor = UNIT_BYTES[unit];
|
|
35
|
+
if (factor === void 0) throw new Error(`Memory: unknown unit "${m[2]}" in "${s}" — supported: ${Object.keys(UNIT_BYTES).join(", ")}`);
|
|
36
|
+
return value * factor;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Coerce a {@link Input} to a {@link Memory}. Throws on malformed
|
|
40
|
+
* strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
|
|
41
|
+
*/
|
|
42
|
+
const fromInputUnsafe = (input) => {
|
|
43
|
+
if (typeof input === "number") {
|
|
44
|
+
if (!Number.isFinite(input) || input < 0) throw new Error(`Memory: bytes must be a non-negative finite number, got ${input}`);
|
|
45
|
+
return make(Math.floor(input));
|
|
46
|
+
}
|
|
47
|
+
return make(Math.floor(parseString(input)));
|
|
48
|
+
};
|
|
49
|
+
/** Return the underlying byte count. */
|
|
50
|
+
const toBytes = (m) => typeof m === "number" && m === Math.floor(m) && m >= 0 ? m : fromInputUnsafe(m);
|
|
51
|
+
/** N bytes. */
|
|
52
|
+
const bytes = (n) => make(Math.floor(n));
|
|
53
|
+
/** N KiB (binary kibibytes, `n * 1024`). */
|
|
54
|
+
const kib = (n) => make(Math.floor(n * 1024));
|
|
55
|
+
/** N MiB (binary mebibytes, `n * 1024^2`). */
|
|
56
|
+
const mib = (n) => make(Math.floor(n * 1024 * 1024));
|
|
57
|
+
/** N GiB (binary gibibytes, `n * 1024^3`). */
|
|
58
|
+
const gib = (n) => make(Math.floor(n * 1024 * 1024 * 1024));
|
|
59
|
+
/** N kB (decimal kilobytes, `n * 1000`). */
|
|
60
|
+
const kb = (n) => make(Math.floor(n * 1e3));
|
|
61
|
+
/** N MB (decimal megabytes, `n * 1_000_000`). */
|
|
62
|
+
const mb = (n) => make(Math.floor(n * 1e6));
|
|
63
|
+
/** N GB (decimal gigabytes, `n * 1_000_000_000`). */
|
|
64
|
+
const gb = (n) => make(Math.floor(n * 1e9));
|
|
65
|
+
//#endregion
|
|
66
|
+
export { bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, Memory_exports as t, toBytes };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=Memory.mjs.map
|