@effect-uai/mistral 0.12.0 → 0.12.1
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/dist/Mistral.d.mts +6 -5
- package/dist/Mistral.d.mts.map +1 -1
- package/dist/Mistral.mjs +1 -1
- package/dist/Mistral.mjs.map +1 -1
- package/dist/MistralRealtimeTranscriber.d.mts +1 -3
- package/dist/MistralRealtimeTranscriber.d.mts.map +1 -1
- package/dist/MistralRealtimeTranscriber.mjs +1 -1
- package/dist/MistralRealtimeTranscriber.mjs.map +1 -1
- package/dist/MistralSynthesizer.d.mts +1 -3
- package/dist/MistralSynthesizer.d.mts.map +1 -1
- package/dist/MistralSynthesizer.mjs +4 -2
- package/dist/MistralSynthesizer.mjs.map +1 -1
- package/dist/MistralTranscriber.d.mts +1 -3
- package/dist/MistralTranscriber.d.mts.map +1 -1
- package/dist/MistralTranscriber.mjs +3 -2
- package/dist/MistralTranscriber.mjs.map +1 -1
- package/dist/audioCodec.d.mts +0 -1
- package/dist/audioCodec.d.mts.map +1 -1
- package/dist/audioCodec.mjs.map +1 -1
- package/dist/codec.d.mts +0 -2
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +1 -1
- package/dist/codec.mjs.map +1 -1
- package/dist/codec.test.d.mts +1 -1
- package/dist/codec.test.mjs +1 -1
- package/dist/codec.test.mjs.map +1 -1
- package/dist/http.d.mts +0 -1
- package/dist/http.d.mts.map +1 -1
- package/dist/http.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/{magic-string.es-CaWgF-3p.mjs → magic-string.es-CUAnjKYC.mjs} +14 -13
- package/dist/{magic-string.es-CaWgF-3p.mjs.map → magic-string.es-CUAnjKYC.mjs.map} +1 -1
- package/dist/{models-B1wZuvz5.d.mts → models-gP7kSqeb.d.mts} +1 -1
- package/dist/models-gP7kSqeb.d.mts.map +1 -0
- package/dist/models.d.mts +1 -1
- package/dist/onHalt.test.d.mts +1 -1
- package/dist/onHalt.test.mjs +1 -1
- package/dist/onHalt.test.mjs.map +1 -1
- package/dist/{realtimeStt-BFcctqFC.d.mts → realtimeStt-BcthMc57.d.mts} +3 -3
- package/dist/realtimeStt-BcthMc57.d.mts.map +1 -0
- package/dist/realtimeStt.d.mts +1 -1
- package/dist/realtimeStt.mjs.map +1 -1
- package/dist/{test.DNmyFkvJ-CPKukVWS.mjs → test.DNmyFkvJ-N3QqMRwZ.mjs} +247 -222
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +1 -0
- package/package.json +6 -6
- package/src/Mistral.ts +4 -7
- package/src/MistralRealtimeTranscriber.ts +4 -8
- package/src/MistralSynthesizer.ts +8 -12
- package/src/MistralTranscriber.ts +9 -11
- package/src/audioCodec.ts +25 -20
- package/dist/models-B1wZuvz5.d.mts.map +0 -1
- package/dist/realtimeStt-BFcctqFC.d.mts.map +0 -1
- package/dist/test.DNmyFkvJ-CPKukVWS.mjs.map +0 -1
- /package/dist/{chunk-CfYAbeIz.mjs → rolldown-runtime-D7D4PA-g.mjs} +0 -0
package/dist/Mistral.d.mts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { t as MistralModel } from "./models-
|
|
1
|
+
import { t as MistralModel } from "./models-gP7kSqeb.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Redacted, Stream } from "effect";
|
|
3
3
|
import { HttpClient } from "effect/unstable/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
5
|
import { CommonRequest, LanguageModel } from "@effect-uai/core/LanguageModel";
|
|
6
6
|
import { Turn, TurnEvent } from "@effect-uai/core/Turn";
|
|
7
|
-
|
|
8
|
-
//#region src/Mistral.d.ts
|
|
9
7
|
declare namespace Mistral_d_exports {
|
|
10
8
|
export { Config, Mistral, MistralRequest, MistralService, layer, make };
|
|
11
9
|
}
|
|
@@ -18,11 +16,14 @@ type MistralRequest = Omit<CommonRequest, "model"> & {
|
|
|
18
16
|
/**
|
|
19
17
|
* Prepend a safety system prompt (`safe_prompt` on the wire). Off by default.
|
|
20
18
|
*/
|
|
21
|
-
readonly safePrompt?: boolean;
|
|
19
|
+
readonly safePrompt?: boolean;
|
|
20
|
+
/** Deterministic sampling seed (`random_seed` on the wire). */
|
|
22
21
|
readonly randomSeed?: number;
|
|
23
22
|
};
|
|
24
23
|
type MistralService = {
|
|
25
|
-
/** Stream canonical `TurnEvent`s for one turn. */
|
|
24
|
+
/** Stream canonical `TurnEvent`s for one turn. */
|
|
25
|
+
readonly streamTurn: (request: MistralRequest) => Stream.Stream<TurnEvent, AiError.AiError>;
|
|
26
|
+
/** Drain a single turn and return the assembled `Turn`. */
|
|
26
27
|
readonly turn: (request: MistralRequest) => Effect.Effect<Turn, AiError.AiError>;
|
|
27
28
|
};
|
|
28
29
|
declare const Mistral_base: Context.ServiceClass<Mistral, "@betalyra/effect-uai/providers/mistral/Mistral", MistralService>;
|
package/dist/Mistral.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Mistral.d.mts","names":[],"sources":["../src/Mistral.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Mistral.d.mts","names":[],"sources":["../src/Mistral.ts"],"mappings":";;;;;;;;;KA+BY,iBAAiB,KAAK;;;;;WAKvB,OAAO;;;;WAIP;;WAEA;;KAGC;;WAED,aAAa,SAAS,mBAAmB,OAAO,OAAO,WAAW,QAAQ;;WAE1E,OAAO,SAAS,mBAAmB,OAAO,OAAO,MAAM,QAAQ;;;;;;;;cAQ7D,gBAAgB;KAIjB;WACD,QAAQ,SAAS;WACjB;;;cAqGE,OAAI,KAAS,WAAS,OAAO,OAAO,uBAAuB,WAAW;;;;;cActE,QAAK,KACX,WACJ,MAAM,MAAM,UAAU,sBAAsB,WAAW"}
|
package/dist/Mistral.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, toolChoiceWire, toolsWire } from "./codec.mjs";
|
|
3
3
|
import { httpStatusError } from "./http.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Option, Redacted, Result, Schema, Stream } from "effect";
|
package/dist/Mistral.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Mistral.mjs","names":[],"sources":["../src/Mistral.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Result, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf } from \"@effect-uai/core/Tool\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type Accumulator,\n type WireChunk,\n accumulatorToTurn,\n applyChunk,\n decodeChunk,\n emptyAccumulator,\n itemsToMessages,\n responseFormatWire,\n toolChoiceWire,\n toolsWire,\n} from \"./codec.js\"\nimport { httpStatusError } from \"./http.js\"\nimport type { MistralModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type MistralRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `MistralModel`\n * literal union for autocomplete.\n */\n readonly model: MistralModel\n /**\n * Prepend a safety system prompt (`safe_prompt` on the wire). Off by default.\n */\n readonly safePrompt?: boolean\n /** Deterministic sampling seed (`random_seed` on the wire). */\n readonly randomSeed?: number\n}\n\nexport type MistralService = {\n /** Stream canonical `TurnEvent`s for one turn. */\n readonly streamTurn: (request: MistralRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /** Drain a single turn and return the assembled `Turn`. */\n readonly turn: (request: MistralRequest) => Effect.Effect<Turn, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Mistral-specific options\n * (`safePrompt`, `randomSeed`); yield the generic `LanguageModel` tag for\n * provider-portable code. Both are registered by `layer`.\n */\nexport class Mistral extends Context.Service<Mistral, MistralService>()(\n \"@betalyra/effect-uai/providers/mistral/Mistral\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildRequestBody = (request: MistralRequest): Record<string, unknown> => {\n const tools = toolsWire(descriptorsOf(request.tools))\n return {\n model: request.model,\n stream: true,\n messages: itemsToMessages(request.history),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.topP !== undefined && { top_p: request.topP }),\n ...(request.maxOutputTokens !== undefined && { max_tokens: request.maxOutputTokens }),\n ...(request.safePrompt !== undefined && { safe_prompt: request.safePrompt }),\n ...(request.randomSeed !== undefined && { random_seed: request.randomSeed }),\n ...(Option.isSome(tools) && { tools: tools.value }),\n ...(request.toolChoice !== undefined && { tool_choice: toolChoiceWire(request.toolChoice) }),\n ...(request.structured !== undefined && {\n response_format: responseFormatWire(request.structured),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE → WireChunk\n// ---------------------------------------------------------------------------\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\n/**\n * Decode one SSE event's `data` payload into a `WireChunk`. Returns `None`\n * for the `[DONE]` sentinel and for any payload that isn't a chunk we model,\n * so a stray keep-alive never aborts the turn.\n */\nconst decodeEvent = (data: string): Effect.Effect<Option.Option<WireChunk>> =>\n data.trim() === \"[DONE]\"\n ? Effect.succeedNone\n : parseJsonUnknown(data).pipe(Effect.flatMap(decodeChunk), Effect.option)\n\n// ---------------------------------------------------------------------------\n// Stream\n// ---------------------------------------------------------------------------\n\nconst buildStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://api.mistral.ai\"\n const url = `${baseUrl}/v1/chat/completions`\n return (\n request: MistralRequest,\n ): Stream.Stream<TurnEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"authorization\", `Bearer ${Redacted.value(cfg.apiKey)}`),\n HttpClientRequest.bodyJsonUnsafe(buildRequestBody(request)),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"mistral\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"mistral\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) => decodeEvent(ev.data)),\n Stream.filterMap((chunk) =>\n Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid,\n ),\n Stream.mapAccum((): Accumulator => emptyAccumulator, applyChunk, {\n // `onHalt` also fires on upstream failure and truncated streams,\n // so only emit `TurnComplete` once a `finish_reason` was observed\n onHalt: (acc) =>\n Option.isSome(acc.finishReason)\n ? [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })]\n : [],\n }),\n )\n }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/** Build a `MistralService` value. For Layer-based setup, prefer `layer`. */\nexport const make = (cfg: Config): Effect.Effect<MistralService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => {\n const streamTurn: MistralService[\"streamTurn\"] = (request) =>\n buildStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamTurn,\n turn: turnFromStream(streamTurn),\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Mistral` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Mistral | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Mistral, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(
|
|
1
|
+
{"version":3,"file":"Mistral.mjs","names":[],"sources":["../src/Mistral.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Result, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf } from \"@effect-uai/core/Tool\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type Accumulator,\n type WireChunk,\n accumulatorToTurn,\n applyChunk,\n decodeChunk,\n emptyAccumulator,\n itemsToMessages,\n responseFormatWire,\n toolChoiceWire,\n toolsWire,\n} from \"./codec.js\"\nimport { httpStatusError } from \"./http.js\"\nimport type { MistralModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type MistralRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `MistralModel`\n * literal union for autocomplete.\n */\n readonly model: MistralModel\n /**\n * Prepend a safety system prompt (`safe_prompt` on the wire). Off by default.\n */\n readonly safePrompt?: boolean\n /** Deterministic sampling seed (`random_seed` on the wire). */\n readonly randomSeed?: number\n}\n\nexport type MistralService = {\n /** Stream canonical `TurnEvent`s for one turn. */\n readonly streamTurn: (request: MistralRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /** Drain a single turn and return the assembled `Turn`. */\n readonly turn: (request: MistralRequest) => Effect.Effect<Turn, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Mistral-specific options\n * (`safePrompt`, `randomSeed`); yield the generic `LanguageModel` tag for\n * provider-portable code. Both are registered by `layer`.\n */\nexport class Mistral extends Context.Service<Mistral, MistralService>()(\n \"@betalyra/effect-uai/providers/mistral/Mistral\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildRequestBody = (request: MistralRequest): Record<string, unknown> => {\n const tools = toolsWire(descriptorsOf(request.tools))\n return {\n model: request.model,\n stream: true,\n messages: itemsToMessages(request.history),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.topP !== undefined && { top_p: request.topP }),\n ...(request.maxOutputTokens !== undefined && { max_tokens: request.maxOutputTokens }),\n ...(request.safePrompt !== undefined && { safe_prompt: request.safePrompt }),\n ...(request.randomSeed !== undefined && { random_seed: request.randomSeed }),\n ...(Option.isSome(tools) && { tools: tools.value }),\n ...(request.toolChoice !== undefined && { tool_choice: toolChoiceWire(request.toolChoice) }),\n ...(request.structured !== undefined && {\n response_format: responseFormatWire(request.structured),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE → WireChunk\n// ---------------------------------------------------------------------------\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\n/**\n * Decode one SSE event's `data` payload into a `WireChunk`. Returns `None`\n * for the `[DONE]` sentinel and for any payload that isn't a chunk we model,\n * so a stray keep-alive never aborts the turn.\n */\nconst decodeEvent = (data: string): Effect.Effect<Option.Option<WireChunk>> =>\n data.trim() === \"[DONE]\"\n ? Effect.succeedNone\n : parseJsonUnknown(data).pipe(Effect.flatMap(decodeChunk), Effect.option)\n\n// ---------------------------------------------------------------------------\n// Stream\n// ---------------------------------------------------------------------------\n\nconst buildStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://api.mistral.ai\"\n const url = `${baseUrl}/v1/chat/completions`\n return (\n request: MistralRequest,\n ): Stream.Stream<TurnEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"authorization\", `Bearer ${Redacted.value(cfg.apiKey)}`),\n HttpClientRequest.bodyJsonUnsafe(buildRequestBody(request)),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"mistral\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"mistral\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) => decodeEvent(ev.data)),\n Stream.filterMap((chunk) =>\n Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid,\n ),\n Stream.mapAccum((): Accumulator => emptyAccumulator, applyChunk, {\n // `onHalt` also fires on upstream failure and truncated streams,\n // so only emit `TurnComplete` once a `finish_reason` was observed\n onHalt: (acc) =>\n Option.isSome(acc.finishReason)\n ? [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })]\n : [],\n }),\n )\n }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/** Build a `MistralService` value. For Layer-based setup, prefer `layer`. */\nexport const make = (cfg: Config): Effect.Effect<MistralService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => {\n const streamTurn: MistralService[\"streamTurn\"] = (request) =>\n buildStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamTurn,\n turn: turnFromStream(streamTurn),\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Mistral` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Mistral | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Mistral, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(make(cfg), (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as MistralRequest),\n turn: (request) => s.turn(request as MistralRequest),\n })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { MistralTranscribeRequest, MistralTranscriber, MistralTranscriberService } from "./MistralTranscriber.mjs";
|
|
2
|
-
import { t as Config } from "./realtimeStt-
|
|
2
|
+
import { t as Config } from "./realtimeStt-BcthMc57.mjs";
|
|
3
3
|
import { Effect, Layer } from "effect";
|
|
4
4
|
import { HttpClient } from "effect/unstable/http";
|
|
5
5
|
import { SttStreaming, Transcriber } from "@effect-uai/core/Transcriber";
|
|
6
|
-
|
|
7
|
-
//#region src/MistralRealtimeTranscriber.d.ts
|
|
8
6
|
declare namespace MistralRealtimeTranscriber_d_exports {
|
|
9
7
|
export { Config, MistralTranscribeRequest, MistralTranscriber, MistralTranscriberService, layer, make };
|
|
10
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MistralRealtimeTranscriber.d.mts","names":[],"sources":["../src/MistralRealtimeTranscriber.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"MistralRealtimeTranscriber.d.mts","names":[],"sources":["../src/MistralRealtimeTranscriber.ts"],"mappings":";;;;;;;;cAiCa,OAAI,KACV,WACJ,OAAO,OAAO,kCAAkC,WAAW;cAOjD,QAAK,KACX,WACJ,MAAM,MAAM,qBAAqB,cAAc,qBAAqB,WAAW"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { MistralTranscriber, transcribeImpl } from "./MistralTranscriber.mjs";
|
|
3
3
|
import { streamTranscription } from "./realtimeStt.mjs";
|
|
4
4
|
import { Effect, Layer } from "effect";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MistralRealtimeTranscriber.mjs","names":[],"sources":["../src/MistralRealtimeTranscriber.ts"],"sourcesContent":["/**\n * Realtime variant of `MistralTranscriber`. Wires `streamTranscriptionFrom`\n * to the Voxtral Realtime WebSocket endpoint and registers the `SttStreaming`\n * capability marker, so live-transcription calls compile against this Layer\n * alone (unlike the sync-only `MistralTranscriber` Layer).\n *\n * Pulls in `ws` (optional peer dep) transitively via `./realtimeStt.js`.\n * Node/Bun only — the browser `WebSocket` API can't set the `Authorization`\n * header the WS upgrade requires.\n */\nimport { Effect, Layer } from \"effect\"\nimport { HttpClient } from \"effect/unstable/http\"\nimport {\n type CommonTranscribeRequest,\n SttStreaming,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport {\n MistralTranscriber,\n type MistralTranscriberService,\n type MistralTranscribeRequest,\n transcribeImpl,\n} from \"./MistralTranscriber.js\"\nimport { type Config, streamTranscription } from \"./realtimeStt.js\"\n\nexport type { Config } from \"./realtimeStt.js\"\nexport {\n MistralTranscriber,\n type MistralTranscriberService,\n type MistralTranscribeRequest,\n} from \"./MistralTranscriber.js\"\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<MistralTranscriberService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n transcribe: (request) =>\n transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: streamTranscription(cfg),\n }))\n\nexport const layer = (\n cfg: Config,\n): Layer.Layer<MistralTranscriber | Transcriber | SttStreaming, never, HttpClient.HttpClient> =>\n Layer.mergeAll(\n Layer.effect(MistralTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(
|
|
1
|
+
{"version":3,"file":"MistralRealtimeTranscriber.mjs","names":[],"sources":["../src/MistralRealtimeTranscriber.ts"],"sourcesContent":["/**\n * Realtime variant of `MistralTranscriber`. Wires `streamTranscriptionFrom`\n * to the Voxtral Realtime WebSocket endpoint and registers the `SttStreaming`\n * capability marker, so live-transcription calls compile against this Layer\n * alone (unlike the sync-only `MistralTranscriber` Layer).\n *\n * Pulls in `ws` (optional peer dep) transitively via `./realtimeStt.js`.\n * Node/Bun only — the browser `WebSocket` API can't set the `Authorization`\n * header the WS upgrade requires.\n */\nimport { Effect, Layer } from \"effect\"\nimport { HttpClient } from \"effect/unstable/http\"\nimport {\n type CommonTranscribeRequest,\n SttStreaming,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport {\n MistralTranscriber,\n type MistralTranscriberService,\n type MistralTranscribeRequest,\n transcribeImpl,\n} from \"./MistralTranscriber.js\"\nimport { type Config, streamTranscription } from \"./realtimeStt.js\"\n\nexport type { Config } from \"./realtimeStt.js\"\nexport {\n MistralTranscriber,\n type MistralTranscriberService,\n type MistralTranscribeRequest,\n} from \"./MistralTranscriber.js\"\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<MistralTranscriberService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n transcribe: (request) =>\n transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: streamTranscription(cfg),\n }))\n\nexport const layer = (\n cfg: Config,\n): Layer.Layer<MistralTranscriber | Transcriber | SttStreaming, never, HttpClient.HttpClient> =>\n Layer.mergeAll(\n Layer.effect(MistralTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(make(cfg), (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) => s.transcribe(req as MistralTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n })),\n ),\n Layer.succeed(SttStreaming, undefined),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,QACX,QAEA,OAAO,IAAI,WAAW,aAAa,YAAY;CAC7C,aAAa,YACX,eAAe,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,MAAM,CAAC;CACxF,yBAAyB,oBAAoB,GAAG;AAClD,EAAE;AAEJ,MAAa,SACX,QAEA,MAAM,SACJ,MAAM,OAAO,oBAAoB,KAAK,GAAG,CAAC,GAC1C,MAAM,OACJ,aACA,OAAO,IAAI,KAAK,GAAG,IAAI,OAA2B;CAChD,aAAa,QAAiC,EAAE,WAAW,GAA+B;CAC1F,yBAAyB,EAAE;AAC7B,EAAE,CACJ,GACA,MAAM,QAAQ,cAAc,KAAA,CAAS,CACvC"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { i as MistralTtsModel } from "./models-
|
|
1
|
+
import { i as MistralTtsModel } from "./models-gP7kSqeb.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Redacted, Stream } from "effect";
|
|
3
3
|
import { HttpClient } from "effect/unstable/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
5
|
import { AudioBlob, AudioChunk } from "@effect-uai/core/Audio";
|
|
6
6
|
import { CommonStreamSynthesizeRequest, CommonSynthesizeRequest, SpeechSynthesizer, SpeechSynthesizerService, TtsIncrementalText } from "@effect-uai/core/SpeechSynthesizer";
|
|
7
|
-
|
|
8
|
-
//#region src/MistralSynthesizer.d.ts
|
|
9
7
|
declare namespace MistralSynthesizer_d_exports {
|
|
10
8
|
export { Config, MistralSynthesizeRequest, MistralSynthesizer, MistralSynthesizerService, layer, make };
|
|
11
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MistralSynthesizer.d.mts","names":[],"sources":["../src/MistralSynthesizer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"MistralSynthesizer.d.mts","names":[],"sources":["../src/MistralSynthesizer.ts"],"mappings":";;;;;;;;;;;;;;KAmCY,2BAA2B,KAAK;WACjC,OAAO;WACP;;KAGC;WACD,aACP,SAAS,6BACN,OAAO,OAAO,WAAW,QAAQ;WAC7B,kBACP,SAAS,6BACN,OAAO,OAAO,YAAY,QAAQ;WAC9B,sBAAsB,GAAG,GAChC,QAAQ,OAAO,eAAe,GAAG,IACjC,SAAS,kCACN,OAAO,OAAO,YAAY,QAAQ,UAAU,GAAG;WAC3C,oBAAoB;WACpB,0BAA0B;;;cAGxB,2BAA2B;KAK5B;WACD,QAAQ,SAAS;WACjB;;cAmKE,OAAI,KACV,WACJ,OAAO,OAAO,kCAAkC,WAAW;;;;;;cAgCjD,QAAK,KACX,WACJ,MAAM,MACP,qBAAqB,oBAAoB,2BAEzC,WAAW"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { httpStatusError, transportFailure } from "./http.mjs";
|
|
3
3
|
import { containerToTtsFormat, realizedTtsFormat } from "./audioCodec.mjs";
|
|
4
4
|
import { Array, Context, Effect, Encoding, Layer, Result, Schema, Stream } from "effect";
|
|
@@ -70,7 +70,9 @@ const synthesizeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
70
70
|
});
|
|
71
71
|
return yield* Effect.fail(httpStatusError(response.status, text));
|
|
72
72
|
}
|
|
73
|
-
const
|
|
73
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
74
|
+
const decoded = yield* decodeResponse(json).pipe(Effect.mapError(transportFailure));
|
|
75
|
+
const bytes = yield* decodeAudioData(decoded.audio_data);
|
|
74
76
|
yield* Effect.logDebug("[voxtral-tts] ok", {
|
|
75
77
|
bytes: bytes.byteLength,
|
|
76
78
|
voice: request.voiceId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MistralSynthesizer.mjs","names":["Arr"],"sources":["../src/MistralSynthesizer.ts"],"sourcesContent":["import {\n Array as Arr,\n Context,\n Effect,\n Encoding,\n Layer,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioBlob, AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport {\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n SpeechSynthesizer,\n type SpeechSynthesizerService,\n TtsIncrementalText,\n} from \"@effect-uai/core/SpeechSynthesizer\"\nimport { containerToTtsFormat, type MistralTtsFormat, realizedTtsFormat } from \"./audioCodec.js\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\nimport type { MistralTtsModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Mistral-typed synthesize request. `model` narrows to `MistralTtsModel`;\n * `voiceId` selects a saved Voxtral voice. `refAudio` (base64 of a 2-3s clip)\n * triggers zero-shot voice cloning and takes precedence over `voiceId`.\n */\nexport type MistralSynthesizeRequest = Omit<CommonSynthesizeRequest, \"model\"> & {\n readonly model: MistralTtsModel\n readonly refAudio?: string\n}\n\nexport type MistralSynthesizerService = {\n readonly synthesize: (\n request: MistralSynthesizeRequest,\n ) => Effect.Effect<AudioBlob, AiError.AiError>\n readonly streamSynthesis: (\n request: MistralSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamSynthesisFrom: <E, R>(\n textIn: Stream.Stream<string, E, R>,\n request: CommonStreamSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError | E, R>\n readonly synthesizeDialogue: SpeechSynthesizerService[\"synthesizeDialogue\"]\n readonly streamSynthesizeDialogue: SpeechSynthesizerService[\"streamSynthesizeDialogue\"]\n}\n\nexport class MistralSynthesizer extends Context.Service<\n MistralSynthesizer,\n MistralSynthesizerService\n>()(\"@betalyra/effect-uai/providers/mistral/MistralSynthesizer\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec — request → JSON body\n// ---------------------------------------------------------------------------\n\nconst defaultFormat: AudioFormat = { container: \"mp3\", encoding: \"mp3\", sampleRate: 24000 }\n\ntype WireBody = {\n readonly model: string\n readonly input: string\n readonly response_format: MistralTtsFormat\n readonly voice_id?: string\n readonly ref_audio?: string\n}\n\nconst buildBody = (\n request: MistralSynthesizeRequest,\n): Effect.Effect<{ readonly body: WireBody; readonly format: AudioFormat }, AiError.AiError> =>\n Effect.gen(function* () {\n // Voxtral has no phoneme field; pronunciations are load-bearing, so reject\n // rather than silently mispronounce.\n if (request.pronunciations !== undefined && request.pronunciations.length > 0) {\n return yield* Effect.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"pronunciations\",\n reason:\n \"Voxtral TTS has no phoneme field. Use a provider with a phoneme path (Inworld, Google) for pronunciation overrides.\",\n }),\n )\n }\n yield* Capabilities.warnDroppedWhen(request.speed, {\n provider: \"mistral\",\n capability: \"speed\",\n field: \"speed\",\n reason: \"Voxtral TTS has no speed control; prosody follows the voice prompt.\",\n })\n yield* Capabilities.warnDroppedWhen(request.languageCode, {\n provider: \"mistral\",\n capability: \"languageCode\",\n field: \"languageCode\",\n reason: \"Voxtral TTS auto-detects language and clones cross-lingually.\",\n })\n const responseFormat = yield* containerToTtsFormat(\n (request.outputFormat ?? defaultFormat).container,\n )\n return {\n body: {\n model: request.model,\n input: request.text,\n response_format: responseFormat,\n // Cloning clip wins over a saved voice id when both are present.\n ...(request.refAudio !== undefined\n ? { ref_audio: request.refAudio }\n : { voice_id: request.voiceId }),\n },\n format: realizedTtsFormat(responseFormat),\n }\n })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst buildHttpRequest = (cfg: Config, body: WireBody, accept: string) =>\n HttpClientRequest.post(`${cfg.baseUrl ?? \"https://api.mistral.ai\"}/v1/audio/speech`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.accept(accept),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n\n// One-shot response: base64 audio. Voxtral's streaming SSE wraps this same\n// payload; since `streamSynthesisFrom` buffers the whole utterance anyway, we\n// drive everything off the simpler one-shot endpoint and slice for pacing.\nconst WireResponse = Schema.Struct({ audio_data: Schema.String })\nconst decodeResponse = Schema.decodeUnknownEffect(WireResponse)\n\nconst decodeAudioData = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(\n new AiError.InvalidRequest({ provider: \"mistral\", param: \"audio_data\", raw: cause }),\n ),\n })\n\nconst synthesizeImpl =\n (cfg: Config) =>\n (\n request: MistralSynthesizeRequest,\n ): Effect.Effect<AudioBlob, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const { body, format } = yield* buildBody(request)\n const response = yield* client\n .execute(buildHttpRequest(cfg, body, \"application/json\"))\n .pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n yield* Effect.logWarning(\"[voxtral-tts] request failed\", {\n status: response.status,\n body: text,\n })\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const decoded = yield* decodeResponse(json).pipe(Effect.mapError(transportFailure))\n const bytes = yield* decodeAudioData(decoded.audio_data)\n yield* Effect.logDebug(\"[voxtral-tts] ok\", {\n bytes: bytes.byteLength,\n voice: request.voiceId,\n })\n return { format, bytes }\n })\n\n// ~40 ms frames so the voice loop can pace playback and cut on interrupt.\nconst FRAME_BYTES = 8192\n\nconst sliceIntoChunks = (bytes: Uint8Array): ReadonlyArray<AudioChunk> =>\n Arr.makeBy(Math.max(1, Math.ceil(bytes.byteLength / FRAME_BYTES)), (i) => ({\n bytes: bytes.subarray(i * FRAME_BYTES, (i + 1) * FRAME_BYTES),\n }))\n\nconst streamSynthesisImpl =\n (cfg: Config) =>\n (\n request: MistralSynthesizeRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(synthesizeImpl(cfg)(request), (blob) =>\n Stream.fromIterable(sliceIntoChunks(blob.bytes)),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Dialogue — unsupported (Voxtral has no multi-speaker endpoint)\n// ---------------------------------------------------------------------------\n\nconst synthesizeDialogueUnsupported: SpeechSynthesizerService[\"synthesizeDialogue\"] = () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"synthesizeDialogue\",\n reason: \"Voxtral TTS has no multi-speaker dialogue endpoint.\",\n }),\n )\n\nconst streamSynthesizeDialogueUnsupported: SpeechSynthesizerService[\"streamSynthesizeDialogue\"] =\n () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"streamSynthesizeDialogue\",\n reason: \"Voxtral TTS has no multi-speaker dialogue endpoint.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<MistralSynthesizerService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n synthesize: (request) =>\n synthesizeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamSynthesis: (request) =>\n streamSynthesisImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n // Voxtral has no incremental-text-in WS endpoint, so we fold the text\n // stream into one utterance and hand it to the chunked streaming endpoint.\n // Audio still streams out; only the text side buffers (one turn per call).\n streamSynthesisFrom: (textIn, request) =>\n Stream.unwrap(\n Effect.map(\n Stream.runFold(\n textIn,\n () => \"\",\n (acc, s) => acc + s,\n ),\n (text) =>\n streamSynthesisImpl(cfg)({ ...request, text } as MistralSynthesizeRequest).pipe(\n Stream.provideService(HttpClient.HttpClient, client),\n ),\n ),\n ),\n synthesizeDialogue: synthesizeDialogueUnsupported,\n streamSynthesizeDialogue: streamSynthesizeDialogueUnsupported,\n }))\n\n/**\n * Layer registering `MistralSynthesizer`, the generic `SpeechSynthesizer` tag,\n * and the `TtsIncrementalText` capability marker (so `streamSynthesisFrom`\n * composes — note the text side buffers; see its docs).\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<\n MistralSynthesizer | SpeechSynthesizer | TtsIncrementalText,\n never,\n HttpClient.HttpClient\n> =>\n Layer.mergeAll(\n Layer.effect(MistralSynthesizer, make(cfg)),\n Layer.effect(\n SpeechSynthesizer,\n Effect.map(\n make(cfg),\n (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) =>\n s.synthesize(req as MistralSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as MistralSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n synthesizeDialogue: s.synthesizeDialogue,\n streamSynthesizeDialogue: s.streamSynthesizeDialogue,\n }),\n ),\n ),\n Layer.succeed(TtsIncrementalText, undefined),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MistralSynthesizer.mjs","names":["Arr"],"sources":["../src/MistralSynthesizer.ts"],"sourcesContent":["import {\n Array as Arr,\n Context,\n Effect,\n Encoding,\n Layer,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioBlob, AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport {\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n SpeechSynthesizer,\n type SpeechSynthesizerService,\n TtsIncrementalText,\n} from \"@effect-uai/core/SpeechSynthesizer\"\nimport { containerToTtsFormat, type MistralTtsFormat, realizedTtsFormat } from \"./audioCodec.js\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\nimport type { MistralTtsModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Mistral-typed synthesize request. `model` narrows to `MistralTtsModel`;\n * `voiceId` selects a saved Voxtral voice. `refAudio` (base64 of a 2-3s clip)\n * triggers zero-shot voice cloning and takes precedence over `voiceId`.\n */\nexport type MistralSynthesizeRequest = Omit<CommonSynthesizeRequest, \"model\"> & {\n readonly model: MistralTtsModel\n readonly refAudio?: string\n}\n\nexport type MistralSynthesizerService = {\n readonly synthesize: (\n request: MistralSynthesizeRequest,\n ) => Effect.Effect<AudioBlob, AiError.AiError>\n readonly streamSynthesis: (\n request: MistralSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamSynthesisFrom: <E, R>(\n textIn: Stream.Stream<string, E, R>,\n request: CommonStreamSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError | E, R>\n readonly synthesizeDialogue: SpeechSynthesizerService[\"synthesizeDialogue\"]\n readonly streamSynthesizeDialogue: SpeechSynthesizerService[\"streamSynthesizeDialogue\"]\n}\n\nexport class MistralSynthesizer extends Context.Service<\n MistralSynthesizer,\n MistralSynthesizerService\n>()(\"@betalyra/effect-uai/providers/mistral/MistralSynthesizer\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec — request → JSON body\n// ---------------------------------------------------------------------------\n\nconst defaultFormat: AudioFormat = { container: \"mp3\", encoding: \"mp3\", sampleRate: 24000 }\n\ntype WireBody = {\n readonly model: string\n readonly input: string\n readonly response_format: MistralTtsFormat\n readonly voice_id?: string\n readonly ref_audio?: string\n}\n\nconst buildBody = (\n request: MistralSynthesizeRequest,\n): Effect.Effect<{ readonly body: WireBody; readonly format: AudioFormat }, AiError.AiError> =>\n Effect.gen(function* () {\n // Voxtral has no phoneme field; pronunciations are load-bearing, so reject\n // rather than silently mispronounce.\n if (request.pronunciations !== undefined && request.pronunciations.length > 0) {\n return yield* Effect.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"pronunciations\",\n reason:\n \"Voxtral TTS has no phoneme field. Use a provider with a phoneme path (Inworld, Google) for pronunciation overrides.\",\n }),\n )\n }\n yield* Capabilities.warnDroppedWhen(request.speed, {\n provider: \"mistral\",\n capability: \"speed\",\n field: \"speed\",\n reason: \"Voxtral TTS has no speed control; prosody follows the voice prompt.\",\n })\n yield* Capabilities.warnDroppedWhen(request.languageCode, {\n provider: \"mistral\",\n capability: \"languageCode\",\n field: \"languageCode\",\n reason: \"Voxtral TTS auto-detects language and clones cross-lingually.\",\n })\n const responseFormat = yield* containerToTtsFormat(\n (request.outputFormat ?? defaultFormat).container,\n )\n return {\n body: {\n model: request.model,\n input: request.text,\n response_format: responseFormat,\n // Cloning clip wins over a saved voice id when both are present.\n ...(request.refAudio !== undefined\n ? { ref_audio: request.refAudio }\n : { voice_id: request.voiceId }),\n },\n format: realizedTtsFormat(responseFormat),\n }\n })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst buildHttpRequest = (cfg: Config, body: WireBody, accept: string) =>\n HttpClientRequest.post(`${cfg.baseUrl ?? \"https://api.mistral.ai\"}/v1/audio/speech`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.accept(accept),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n\n// One-shot response: base64 audio. Voxtral's streaming SSE wraps this same\n// payload; since `streamSynthesisFrom` buffers the whole utterance anyway, we\n// drive everything off the simpler one-shot endpoint and slice for pacing.\nconst WireResponse = Schema.Struct({ audio_data: Schema.String })\nconst decodeResponse = Schema.decodeUnknownEffect(WireResponse)\n\nconst decodeAudioData = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(\n new AiError.InvalidRequest({ provider: \"mistral\", param: \"audio_data\", raw: cause }),\n ),\n })\n\nconst synthesizeImpl =\n (cfg: Config) =>\n (\n request: MistralSynthesizeRequest,\n ): Effect.Effect<AudioBlob, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const { body, format } = yield* buildBody(request)\n const response = yield* client\n .execute(buildHttpRequest(cfg, body, \"application/json\"))\n .pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n yield* Effect.logWarning(\"[voxtral-tts] request failed\", {\n status: response.status,\n body: text,\n })\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const decoded = yield* decodeResponse(json).pipe(Effect.mapError(transportFailure))\n const bytes = yield* decodeAudioData(decoded.audio_data)\n yield* Effect.logDebug(\"[voxtral-tts] ok\", {\n bytes: bytes.byteLength,\n voice: request.voiceId,\n })\n return { format, bytes }\n })\n\n// ~40 ms frames so the voice loop can pace playback and cut on interrupt.\nconst FRAME_BYTES = 8192\n\nconst sliceIntoChunks = (bytes: Uint8Array): ReadonlyArray<AudioChunk> =>\n Arr.makeBy(Math.max(1, Math.ceil(bytes.byteLength / FRAME_BYTES)), (i) => ({\n bytes: bytes.subarray(i * FRAME_BYTES, (i + 1) * FRAME_BYTES),\n }))\n\nconst streamSynthesisImpl =\n (cfg: Config) =>\n (\n request: MistralSynthesizeRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(synthesizeImpl(cfg)(request), (blob) =>\n Stream.fromIterable(sliceIntoChunks(blob.bytes)),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Dialogue — unsupported (Voxtral has no multi-speaker endpoint)\n// ---------------------------------------------------------------------------\n\nconst synthesizeDialogueUnsupported: SpeechSynthesizerService[\"synthesizeDialogue\"] = () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"synthesizeDialogue\",\n reason: \"Voxtral TTS has no multi-speaker dialogue endpoint.\",\n }),\n )\n\nconst streamSynthesizeDialogueUnsupported: SpeechSynthesizerService[\"streamSynthesizeDialogue\"] =\n () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"streamSynthesizeDialogue\",\n reason: \"Voxtral TTS has no multi-speaker dialogue endpoint.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<MistralSynthesizerService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n synthesize: (request) =>\n synthesizeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamSynthesis: (request) =>\n streamSynthesisImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n // Voxtral has no incremental-text-in WS endpoint, so we fold the text\n // stream into one utterance and hand it to the chunked streaming endpoint.\n // Audio still streams out; only the text side buffers (one turn per call).\n streamSynthesisFrom: (textIn, request) =>\n Stream.unwrap(\n Effect.map(\n Stream.runFold(\n textIn,\n () => \"\",\n (acc, s) => acc + s,\n ),\n (text) =>\n streamSynthesisImpl(cfg)({ ...request, text } as MistralSynthesizeRequest).pipe(\n Stream.provideService(HttpClient.HttpClient, client),\n ),\n ),\n ),\n synthesizeDialogue: synthesizeDialogueUnsupported,\n streamSynthesizeDialogue: streamSynthesizeDialogueUnsupported,\n }))\n\n/**\n * Layer registering `MistralSynthesizer`, the generic `SpeechSynthesizer` tag,\n * and the `TtsIncrementalText` capability marker (so `streamSynthesisFrom`\n * composes — note the text side buffers; see its docs).\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<\n MistralSynthesizer | SpeechSynthesizer | TtsIncrementalText,\n never,\n HttpClient.HttpClient\n> =>\n Layer.mergeAll(\n Layer.effect(MistralSynthesizer, make(cfg)),\n Layer.effect(\n SpeechSynthesizer,\n Effect.map(make(cfg), (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) => s.synthesize(req as MistralSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as MistralSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n synthesizeDialogue: s.synthesizeDialogue,\n streamSynthesizeDialogue: s.streamSynthesizeDialogue,\n })),\n ),\n Layer.succeed(TtsIncrementalText, undefined),\n )\n"],"mappings":"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"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { r as MistralTranscribeModel } from "./models-
|
|
1
|
+
import { r as MistralTranscribeModel } from "./models-gP7kSqeb.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Redacted, Stream } from "effect";
|
|
3
3
|
import { HttpClient } from "effect/unstable/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
5
|
import { CommonStreamTranscribeRequest, CommonTranscribeRequest, Transcriber } from "@effect-uai/core/Transcriber";
|
|
6
6
|
import { TranscriptEvent, TranscriptResult } from "@effect-uai/core/Transcript";
|
|
7
|
-
|
|
8
|
-
//#region src/MistralTranscriber.d.ts
|
|
9
7
|
declare namespace MistralTranscriber_d_exports {
|
|
10
8
|
export { Config, MistralTranscribeRequest, MistralTranscriber, MistralTranscriberService, layer, make, transcribeImpl };
|
|
11
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MistralTranscriber.d.mts","names":[],"sources":["../src/MistralTranscriber.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"MistralTranscriber.d.mts","names":[],"sources":["../src/MistralTranscriber.ts"],"mappings":";;;;;;;;;;;;;;;KA0BY,2BAA2B,KAAK;WACjC,OAAO;WACP;WACA;;KAGC;WACD,aACP,SAAS,6BACN,OAAO,OAAO,kBAAkB,QAAQ;WACpC,0BAA0B,GAAG,GACpC,SAAS,OAAO,OAAO,YAAY,GAAG,IACtC,SAAS,kCACN,OAAO,OAAO,iBAAiB,QAAQ,UAAU,GAAG;;;cAG9C,2BAA2B;KAK5B;WACD,QAAQ,SAAS;WACjB;;;cAiGE,iBAAc,KACnB,YAAM,SAED,6BACR,OAAO,OAAO,kBAAkB,QAAQ,SAAS,WAAW;cA6CpD,OAAI,KACV,WACJ,OAAO,OAAO,kCAAkC,WAAW;;;;;;cAYjD,QAAK,KACX,WACJ,MAAM,MAAM,qBAAqB,oBAAoB,WAAW"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { httpStatusError, transportFailure } from "./http.mjs";
|
|
3
3
|
import { audioToBlob, defaultFileName } from "./audioCodec.mjs";
|
|
4
4
|
import { Array, Context, Effect, Layer, Schema, Stream } from "effect";
|
|
@@ -80,7 +80,8 @@ const transcribeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
80
80
|
return yield* Effect.fail(httpStatusError(response.status, text));
|
|
81
81
|
}
|
|
82
82
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
83
|
-
|
|
83
|
+
const decoded = yield* decodeResponse(json).pipe(Effect.mapError(transportFailure));
|
|
84
|
+
return toResult(json, decoded);
|
|
84
85
|
});
|
|
85
86
|
/**
|
|
86
87
|
* Sync-only Layer's streaming impl. For live transcription import the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MistralTranscriber.mjs","names":["Arr"],"sources":["../src/MistralTranscriber.ts"],"sourcesContent":["import { Array as Arr, Context, Effect, Layer, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport { isAudioUrl } from \"@effect-uai/core/Audio\"\nimport type { TranscriptEvent, TranscriptResult, WordTimestamp } from \"@effect-uai/core/Transcript\"\nimport {\n type CommonStreamTranscribeRequest,\n type CommonTranscribeRequest,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport { audioToBlob, defaultFileName } from \"./audioCodec.js\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\nimport type { MistralTranscribeModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Mistral-typed transcribe request. `model` narrows to the typed\n * `MistralTranscribeModel` union; `fileName` overrides the multipart upload's\n * filename. Voxtral's batch endpoint supports diarization, word timestamps,\n * and context biasing.\n */\nexport type MistralTranscribeRequest = Omit<CommonTranscribeRequest, \"model\"> & {\n readonly model: MistralTranscribeModel\n readonly temperature?: number\n readonly fileName?: string\n}\n\nexport type MistralTranscriberService = {\n readonly transcribe: (\n request: MistralTranscribeRequest,\n ) => Effect.Effect<TranscriptResult, AiError.AiError>\n readonly streamTranscriptionFrom: <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n request: CommonStreamTranscribeRequest,\n ) => Stream.Stream<TranscriptEvent, AiError.AiError | E, R>\n}\n\nexport class MistralTranscriber extends Context.Service<\n MistralTranscriber,\n MistralTranscriberService\n>()(\"@betalyra/effect-uai/providers/mistral/MistralTranscriber\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec — request → FormData\n//\n// Each field is described as a data `Part`, then the whole list is folded into\n// one FormData. A `Part` is either a scalar field or a file field (with an\n// upload filename).\n// ---------------------------------------------------------------------------\n\ntype Part =\n | readonly [name: string, value: string]\n | readonly [name: string, file: Blob, filename: string]\n\nconst when = (condition: boolean, part: Part): ReadonlyArray<Part> => (condition ? [part] : [])\n\nconst appendPart = (fd: FormData, part: Part): FormData => {\n if (part.length === 3) fd.append(part[0], part[1], part[2])\n else fd.append(part[0], part[1])\n return fd\n}\n\nconst buildFormData = (\n request: MistralTranscribeRequest,\n): Effect.Effect<FormData, AiError.AiError> =>\n Effect.gen(function* () {\n // Voxtral transcription has no Whisper-style prose `prompt` field.\n yield* Capabilities.warnDroppedWhen(request.prompt, {\n provider: \"mistral\",\n capability: \"prompt\",\n field: \"prompt\",\n reason:\n \"Voxtral transcription has no prose prompt field; use `biasingTerms` for vocabulary hints.\",\n })\n // URL audio rides the `file_url` field; inline bytes/base64 upload directly.\n const filePart: Part = isAudioUrl(request.audio)\n ? [\"file_url\", request.audio.url]\n : yield* audioToBlob(request.audio).pipe(\n Effect.map(\n (blob): Part => [\"file\", blob, request.fileName ?? defaultFileName(blob.type)],\n ),\n )\n const parts: ReadonlyArray<Part> = [\n filePart,\n [\"model\", request.model],\n ...when(request.language !== undefined, [\"language\", request.language ?? \"\"]),\n ...when(request.diarization === true, [\"diarize\", \"true\"]),\n ...when(request.wordTimestamps === true, [\"timestamp_granularities[]\", \"word\"]),\n ...when(request.temperature !== undefined, [\"temperature\", String(request.temperature)]),\n // Voxtral biases on a `context_bias` term list; one entry per term.\n ...(request.biasingTerms ?? []).map((term): Part => [\"context_bias\", term]),\n ]\n return Arr.reduce(parts, new FormData(), appendPart)\n })\n\n// ---------------------------------------------------------------------------\n// Codec — response → TranscriptResult\n// ---------------------------------------------------------------------------\n\nconst WireWord = Schema.Struct({\n text: Schema.optional(Schema.String),\n word: Schema.optional(Schema.String),\n start: Schema.Number,\n end: Schema.Number,\n speaker: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\nconst WireResponse = Schema.Struct({\n text: Schema.String,\n language: Schema.optional(Schema.NullOr(Schema.String)),\n words: Schema.optional(Schema.NullOr(Schema.Array(WireWord))),\n})\n\nconst decodeResponse = Schema.decodeUnknownEffect(WireResponse)\n\nconst wireWordToCommon = (w: typeof WireWord.Type): WordTimestamp => ({\n text: w.text ?? w.word ?? \"\",\n startSeconds: w.start,\n endSeconds: w.end,\n ...(w.speaker != null && { speakerId: w.speaker }),\n})\n\nconst toResult = (raw: unknown, decoded: typeof WireResponse.Type): TranscriptResult => ({\n text: decoded.text,\n ...(decoded.language != null && { languageCode: decoded.language }),\n ...(decoded.words != null && { words: decoded.words.map(wireWordToCommon) }),\n raw,\n})\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\n/** Exported for reuse by `MistralRealtimeTranscriber` (same sync path). */\nexport const transcribeImpl =\n (cfg: Config) =>\n (\n request: MistralTranscribeRequest,\n ): Effect.Effect<TranscriptResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const baseUrl = cfg.baseUrl ?? \"https://api.mistral.ai\"\n const formData = yield* buildFormData(request)\n const httpRequest = HttpClientRequest.post(`${baseUrl}/v1/audio/transcriptions`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyFormData(formData),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const decoded = yield* decodeResponse(json).pipe(Effect.mapError(transportFailure))\n return toResult(json, decoded)\n })\n\n// ---------------------------------------------------------------------------\n// streamTranscriptionFrom — Unsupported on the sync-only Layer\n// ---------------------------------------------------------------------------\n\n/**\n * Sync-only Layer's streaming impl. For live transcription import the\n * `MistralRealtimeTranscriber` subpath, which registers `SttStreaming` and\n * wires the Voxtral Realtime WebSocket endpoint.\n */\nconst streamUnsupported = <E, R>(\n _audioIn: Stream.Stream<Uint8Array, E, R>,\n _request: CommonStreamTranscribeRequest,\n): Stream.Stream<TranscriptEvent, AiError.AiError | E, R> =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"streamTranscriptionFrom\",\n reason:\n \"This Layer is sync-only. Use `@effect-uai/mistral/MistralRealtimeTranscriber` for live Voxtral transcription.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<MistralTranscriberService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n transcribe: (request) =>\n transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: streamUnsupported,\n }))\n\n/**\n * Sync-only Layer. Registers `MistralTranscriber` + the generic `Transcriber`\n * tag. Does NOT register `SttStreaming` — for live transcription use\n * `@effect-uai/mistral/MistralRealtimeTranscriber`.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<MistralTranscriber | Transcriber, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(MistralTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(\n make(cfg),\n (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as MistralTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n }),\n ),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MistralTranscriber.mjs","names":["Arr"],"sources":["../src/MistralTranscriber.ts"],"sourcesContent":["import { Array as Arr, Context, Effect, Layer, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport { isAudioUrl } from \"@effect-uai/core/Audio\"\nimport type { TranscriptEvent, TranscriptResult, WordTimestamp } from \"@effect-uai/core/Transcript\"\nimport {\n type CommonStreamTranscribeRequest,\n type CommonTranscribeRequest,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport { audioToBlob, defaultFileName } from \"./audioCodec.js\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\nimport type { MistralTranscribeModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Mistral-typed transcribe request. `model` narrows to the typed\n * `MistralTranscribeModel` union; `fileName` overrides the multipart upload's\n * filename. Voxtral's batch endpoint supports diarization, word timestamps,\n * and context biasing.\n */\nexport type MistralTranscribeRequest = Omit<CommonTranscribeRequest, \"model\"> & {\n readonly model: MistralTranscribeModel\n readonly temperature?: number\n readonly fileName?: string\n}\n\nexport type MistralTranscriberService = {\n readonly transcribe: (\n request: MistralTranscribeRequest,\n ) => Effect.Effect<TranscriptResult, AiError.AiError>\n readonly streamTranscriptionFrom: <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n request: CommonStreamTranscribeRequest,\n ) => Stream.Stream<TranscriptEvent, AiError.AiError | E, R>\n}\n\nexport class MistralTranscriber extends Context.Service<\n MistralTranscriber,\n MistralTranscriberService\n>()(\"@betalyra/effect-uai/providers/mistral/MistralTranscriber\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec — request → FormData\n//\n// Each field is described as a data `Part`, then the whole list is folded into\n// one FormData. A `Part` is either a scalar field or a file field (with an\n// upload filename).\n// ---------------------------------------------------------------------------\n\ntype Part =\n | readonly [name: string, value: string]\n | readonly [name: string, file: Blob, filename: string]\n\nconst when = (condition: boolean, part: Part): ReadonlyArray<Part> => (condition ? [part] : [])\n\nconst appendPart = (fd: FormData, part: Part): FormData => {\n if (part.length === 3) fd.append(part[0], part[1], part[2])\n else fd.append(part[0], part[1])\n return fd\n}\n\nconst buildFormData = (\n request: MistralTranscribeRequest,\n): Effect.Effect<FormData, AiError.AiError> =>\n Effect.gen(function* () {\n // Voxtral transcription has no Whisper-style prose `prompt` field.\n yield* Capabilities.warnDroppedWhen(request.prompt, {\n provider: \"mistral\",\n capability: \"prompt\",\n field: \"prompt\",\n reason:\n \"Voxtral transcription has no prose prompt field; use `biasingTerms` for vocabulary hints.\",\n })\n // URL audio rides the `file_url` field; inline bytes/base64 upload directly.\n const filePart: Part = isAudioUrl(request.audio)\n ? [\"file_url\", request.audio.url]\n : yield* audioToBlob(request.audio).pipe(\n Effect.map((blob): Part => [\n \"file\",\n blob,\n request.fileName ?? defaultFileName(blob.type),\n ]),\n )\n const parts: ReadonlyArray<Part> = [\n filePart,\n [\"model\", request.model],\n ...when(request.language !== undefined, [\"language\", request.language ?? \"\"]),\n ...when(request.diarization === true, [\"diarize\", \"true\"]),\n ...when(request.wordTimestamps === true, [\"timestamp_granularities[]\", \"word\"]),\n ...when(request.temperature !== undefined, [\"temperature\", String(request.temperature)]),\n // Voxtral biases on a `context_bias` term list; one entry per term.\n ...(request.biasingTerms ?? []).map((term): Part => [\"context_bias\", term]),\n ]\n return Arr.reduce(parts, new FormData(), appendPart)\n })\n\n// ---------------------------------------------------------------------------\n// Codec — response → TranscriptResult\n// ---------------------------------------------------------------------------\n\nconst WireWord = Schema.Struct({\n text: Schema.optional(Schema.String),\n word: Schema.optional(Schema.String),\n start: Schema.Number,\n end: Schema.Number,\n speaker: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\nconst WireResponse = Schema.Struct({\n text: Schema.String,\n language: Schema.optional(Schema.NullOr(Schema.String)),\n words: Schema.optional(Schema.NullOr(Schema.Array(WireWord))),\n})\n\nconst decodeResponse = Schema.decodeUnknownEffect(WireResponse)\n\nconst wireWordToCommon = (w: typeof WireWord.Type): WordTimestamp => ({\n text: w.text ?? w.word ?? \"\",\n startSeconds: w.start,\n endSeconds: w.end,\n ...(w.speaker != null && { speakerId: w.speaker }),\n})\n\nconst toResult = (raw: unknown, decoded: typeof WireResponse.Type): TranscriptResult => ({\n text: decoded.text,\n ...(decoded.language != null && { languageCode: decoded.language }),\n ...(decoded.words != null && { words: decoded.words.map(wireWordToCommon) }),\n raw,\n})\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\n/** Exported for reuse by `MistralRealtimeTranscriber` (same sync path). */\nexport const transcribeImpl =\n (cfg: Config) =>\n (\n request: MistralTranscribeRequest,\n ): Effect.Effect<TranscriptResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const baseUrl = cfg.baseUrl ?? \"https://api.mistral.ai\"\n const formData = yield* buildFormData(request)\n const httpRequest = HttpClientRequest.post(`${baseUrl}/v1/audio/transcriptions`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyFormData(formData),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const decoded = yield* decodeResponse(json).pipe(Effect.mapError(transportFailure))\n return toResult(json, decoded)\n })\n\n// ---------------------------------------------------------------------------\n// streamTranscriptionFrom — Unsupported on the sync-only Layer\n// ---------------------------------------------------------------------------\n\n/**\n * Sync-only Layer's streaming impl. For live transcription import the\n * `MistralRealtimeTranscriber` subpath, which registers `SttStreaming` and\n * wires the Voxtral Realtime WebSocket endpoint.\n */\nconst streamUnsupported = <E, R>(\n _audioIn: Stream.Stream<Uint8Array, E, R>,\n _request: CommonStreamTranscribeRequest,\n): Stream.Stream<TranscriptEvent, AiError.AiError | E, R> =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"streamTranscriptionFrom\",\n reason:\n \"This Layer is sync-only. Use `@effect-uai/mistral/MistralRealtimeTranscriber` for live Voxtral transcription.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<MistralTranscriberService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n transcribe: (request) =>\n transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: streamUnsupported,\n }))\n\n/**\n * Sync-only Layer. Registers `MistralTranscriber` + the generic `Transcriber`\n * tag. Does NOT register `SttStreaming` — for live transcription use\n * `@effect-uai/mistral/MistralRealtimeTranscriber`.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<MistralTranscriber | Transcriber, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(MistralTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(make(cfg), (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) => s.transcribe(req as MistralTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n })),\n ),\n )\n"],"mappings":"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"}
|
package/dist/audioCodec.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import * as AiError from "@effect-uai/core/AiError";
|
|
3
3
|
import { AudioFormat, AudioSource } from "@effect-uai/core/Audio";
|
|
4
|
-
|
|
5
4
|
//#region src/audioCodec.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Build a `Blob` from any `AudioSource` variant. `url` variants pass through
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioCodec.d.mts","names":[],"sources":["../src/audioCodec.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"audioCodec.d.mts","names":[],"sources":["../src/audioCodec.ts"],"mappings":";;;;;;;;cA2Ba,cAAc,OAAO,gBAAgB,OAAO,OAAO,MAAM,QAAQ;;cAgCjE,kBAAe;KAShB;;;;;cAMC,uBACX,WAAW,6BACR,OAAO,OAAO,kBAAkB,QAAQ;;;;;;cAuBhC,oBAAoB,IAAI,qBAAqB"}
|
package/dist/audioCodec.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioCodec.mjs","names":["Arr"],"sources":["../src/audioCodec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat, AudioSource } from \"@effect-uai/core/Audio\"\n\n// ---------------------------------------------------------------------------\n// AudioSource → Blob (for multipart upload to /audio/transcriptions)\n// ---------------------------------------------------------------------------\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"mistral\", param: \"audio\", raw: cause })),\n })\n\n/**\n * TS 6's `Blob` constructor wants `Uint8Array<ArrayBuffer>`; our domain carries\n * the broader `Uint8Array<ArrayBufferLike>`. The cast is sound: `Blob()` does\n * not mutate the buffer and the runtime accepts both.\n */\nconst bytesToBlob = (bytes: Uint8Array, mimeType: string): Blob =>\n new Blob([bytes as Uint8Array<ArrayBuffer>], { type: mimeType })\n\n/**\n * Build a `Blob` from any `AudioSource` variant. `url` variants pass through\n * to the `file_url` field upstream, so this only handles inline bytes/base64.\n */\nexport const audioToBlob: (audio: AudioSource) => Effect.Effect<Blob, AiError.AiError> =\n Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) => Effect.succeed(bytesToBlob(a.bytes, a.mimeType))),\n Match.tag(\"base64\", (a) =>\n decodeBase64ToBytes(a.base64).pipe(Effect.map((bytes) => bytesToBlob(bytes, a.mimeType))),\n ),\n Match.tag(\"url\", (a) =>\n Effect.fail(\n new AiError.InvalidRequest({\n provider: \"mistral\",\n param: \"audio\",\n raw: `Pass URL audio via the request's url source; got ${a.url} where inline bytes were expected.`,\n }),\n ),\n ),\n Match.exhaustive,\n )\n\nconst EXTENSION_BY_MIME: ReadonlyArray<readonly [pattern: string, ext: string]> = [\n [\"mpeg\", \"mp3\"],\n [\"mp3\", \"mp3\"],\n [\"wav\", \"wav\"],\n [\"ogg\", \"ogg\"],\n [\"opus\", \"ogg\"],\n [\"flac\", \"flac\"],\n [\"aac\", \"aac\"],\n [\"mp4\", \"m4a\"],\n [\"m4a\", \"m4a\"],\n [\"webm\", \"webm\"],\n]\n\n/** Derive a default upload filename from a MIME type, e.g. `audio/mpeg` -> `audio.mp3`. */\nexport const defaultFileName = (mimeType: string): string =>\n Arr.findFirst(EXTENSION_BY_MIME, ([pattern]) => mimeType.includes(pattern)).pipe(\n Option.match({ onNone: () => \"audio\", onSome: ([, ext]) => `audio.${ext}` }),\n )\n\n// ---------------------------------------------------------------------------\n// AudioFormat → Voxtral TTS `response_format`\n// ---------------------------------------------------------------------------\n\nexport type MistralTtsFormat = \"mp3\" | \"wav\" | \"pcm\" | \"flac\" | \"opus\"\n\n/**\n * Map an `AudioFormat.container` to Voxtral TTS's `response_format`. Voxtral\n * produces mp3 / wav / pcm / flac / opus; aac / ogg / webm are unsupported.\n */\nexport const containerToTtsFormat: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<MistralTtsFormat, AiError.AiError> = Match.type<AudioFormat[\"container\"]>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<MistralTtsFormat>(\"mp3\")),\n Match.when(\"wav\", () => Effect.succeed<MistralTtsFormat>(\"wav\")),\n Match.when(\"raw\", () => Effect.succeed<MistralTtsFormat>(\"pcm\")),\n Match.when(\"flac\", () => Effect.succeed<MistralTtsFormat>(\"flac\")),\n Match.when(\"opus\", () => Effect.succeed<MistralTtsFormat>(\"opus\")),\n Match.whenOr(\"aac\", \"ogg\", \"webm\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"outputFormat\",\n reason: `Voxtral TTS does not produce ${c} output; supported: mp3 | wav | pcm (raw) | flac | opus.`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * The on-wire `AudioFormat` Voxtral actually returns for each\n * `response_format`. Voxtral's `pcm` is float32 LE at 24 kHz; the caller's\n * requested sample rate is not honoured for raw output.\n */\nexport const realizedTtsFormat: (rf: MistralTtsFormat) => AudioFormat =\n Match.type<MistralTtsFormat>().pipe(\n Match.when(\
|
|
1
|
+
{"version":3,"file":"audioCodec.mjs","names":["Arr"],"sources":["../src/audioCodec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat, AudioSource } from \"@effect-uai/core/Audio\"\n\n// ---------------------------------------------------------------------------\n// AudioSource → Blob (for multipart upload to /audio/transcriptions)\n// ---------------------------------------------------------------------------\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"mistral\", param: \"audio\", raw: cause })),\n })\n\n/**\n * TS 6's `Blob` constructor wants `Uint8Array<ArrayBuffer>`; our domain carries\n * the broader `Uint8Array<ArrayBufferLike>`. The cast is sound: `Blob()` does\n * not mutate the buffer and the runtime accepts both.\n */\nconst bytesToBlob = (bytes: Uint8Array, mimeType: string): Blob =>\n new Blob([bytes as Uint8Array<ArrayBuffer>], { type: mimeType })\n\n/**\n * Build a `Blob` from any `AudioSource` variant. `url` variants pass through\n * to the `file_url` field upstream, so this only handles inline bytes/base64.\n */\nexport const audioToBlob: (audio: AudioSource) => Effect.Effect<Blob, AiError.AiError> =\n Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) => Effect.succeed(bytesToBlob(a.bytes, a.mimeType))),\n Match.tag(\"base64\", (a) =>\n decodeBase64ToBytes(a.base64).pipe(Effect.map((bytes) => bytesToBlob(bytes, a.mimeType))),\n ),\n Match.tag(\"url\", (a) =>\n Effect.fail(\n new AiError.InvalidRequest({\n provider: \"mistral\",\n param: \"audio\",\n raw: `Pass URL audio via the request's url source; got ${a.url} where inline bytes were expected.`,\n }),\n ),\n ),\n Match.exhaustive,\n )\n\nconst EXTENSION_BY_MIME: ReadonlyArray<readonly [pattern: string, ext: string]> = [\n [\"mpeg\", \"mp3\"],\n [\"mp3\", \"mp3\"],\n [\"wav\", \"wav\"],\n [\"ogg\", \"ogg\"],\n [\"opus\", \"ogg\"],\n [\"flac\", \"flac\"],\n [\"aac\", \"aac\"],\n [\"mp4\", \"m4a\"],\n [\"m4a\", \"m4a\"],\n [\"webm\", \"webm\"],\n]\n\n/** Derive a default upload filename from a MIME type, e.g. `audio/mpeg` -> `audio.mp3`. */\nexport const defaultFileName = (mimeType: string): string =>\n Arr.findFirst(EXTENSION_BY_MIME, ([pattern]) => mimeType.includes(pattern)).pipe(\n Option.match({ onNone: () => \"audio\", onSome: ([, ext]) => `audio.${ext}` }),\n )\n\n// ---------------------------------------------------------------------------\n// AudioFormat → Voxtral TTS `response_format`\n// ---------------------------------------------------------------------------\n\nexport type MistralTtsFormat = \"mp3\" | \"wav\" | \"pcm\" | \"flac\" | \"opus\"\n\n/**\n * Map an `AudioFormat.container` to Voxtral TTS's `response_format`. Voxtral\n * produces mp3 / wav / pcm / flac / opus; aac / ogg / webm are unsupported.\n */\nexport const containerToTtsFormat: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<MistralTtsFormat, AiError.AiError> = Match.type<AudioFormat[\"container\"]>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<MistralTtsFormat>(\"mp3\")),\n Match.when(\"wav\", () => Effect.succeed<MistralTtsFormat>(\"wav\")),\n Match.when(\"raw\", () => Effect.succeed<MistralTtsFormat>(\"pcm\")),\n Match.when(\"flac\", () => Effect.succeed<MistralTtsFormat>(\"flac\")),\n Match.when(\"opus\", () => Effect.succeed<MistralTtsFormat>(\"opus\")),\n Match.whenOr(\"aac\", \"ogg\", \"webm\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"mistral\",\n capability: \"outputFormat\",\n reason: `Voxtral TTS does not produce ${c} output; supported: mp3 | wav | pcm (raw) | flac | opus.`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * The on-wire `AudioFormat` Voxtral actually returns for each\n * `response_format`. Voxtral's `pcm` is float32 LE at 24 kHz; the caller's\n * requested sample rate is not honoured for raw output.\n */\nexport const realizedTtsFormat: (rf: MistralTtsFormat) => AudioFormat =\n Match.type<MistralTtsFormat>().pipe(\n Match.when(\"mp3\", (): AudioFormat => ({\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 24000,\n })),\n Match.when(\"wav\", (): AudioFormat => ({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 24000,\n })),\n Match.when(\"pcm\", (): AudioFormat => ({\n container: \"raw\",\n encoding: \"pcm_f32le\",\n sampleRate: 24000,\n })),\n Match.when(\"flac\", (): AudioFormat => ({\n container: \"flac\",\n encoding: \"flac\",\n sampleRate: 24000,\n })),\n Match.when(\"opus\", (): AudioFormat => ({\n container: \"opus\",\n encoding: \"opus\",\n sampleRate: 24000,\n })),\n Match.exhaustive,\n )\n"],"mappings":";;;AAQA,MAAM,uBAAuB,QAC3B,OAAO,MAAM,SAAS,aAAa,GAAG,GAAG;CACvC,WAAW,OAAO;CAClB,YAAY,UACV,OAAO,KAAK,IAAI,QAAQ,eAAe;EAAE,UAAU;EAAW,OAAO;EAAS,KAAK;CAAM,CAAC,CAAC;AAC/F,CAAC;;;;;;AAOH,MAAM,eAAe,OAAmB,aACtC,IAAI,KAAK,CAAC,KAAgC,GAAG,EAAE,MAAM,SAAS,CAAC;;;;;AAMjE,MAAa,cACX,MAAM,KAAkB,CAAC,CAAC,KACxB,MAAM,IAAI,UAAU,MAAM,OAAO,QAAQ,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,GAC1E,MAAM,IAAI,WAAW,MACnB,oBAAoB,EAAE,MAAM,CAAC,CAAC,KAAK,OAAO,KAAK,UAAU,YAAY,OAAO,EAAE,QAAQ,CAAC,CAAC,CAC1F,GACA,MAAM,IAAI,QAAQ,MAChB,OAAO,KACL,IAAI,QAAQ,eAAe;CACzB,UAAU;CACV,OAAO;CACP,KAAK,oDAAoD,EAAE,IAAI;AACjE,CAAC,CACH,CACF,GACA,MAAM,UACR;AAEF,MAAM,oBAA4E;CAChF,CAAC,QAAQ,KAAK;CACd,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,QAAQ,KAAK;CACd,CAAC,QAAQ,MAAM;CACf,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,QAAQ,MAAM;AACjB;;AAGA,MAAa,mBAAmB,aAC9BA,MAAI,UAAU,oBAAoB,CAAC,aAAa,SAAS,SAAS,OAAO,CAAC,CAAC,CAAC,KAC1E,OAAO,MAAM;CAAE,cAAc;CAAS,SAAS,GAAG,SAAS,SAAS;AAAM,CAAC,CAC7E;;;;;AAYF,MAAa,uBAE2C,MAAM,KAA+B,CAAC,CAAC,KAC7F,MAAM,KAAK,aAAa,OAAO,QAA0B,KAAK,CAAC,GAC/D,MAAM,KAAK,aAAa,OAAO,QAA0B,KAAK,CAAC,GAC/D,MAAM,KAAK,aAAa,OAAO,QAA0B,KAAK,CAAC,GAC/D,MAAM,KAAK,cAAc,OAAO,QAA0B,MAAM,CAAC,GACjE,MAAM,KAAK,cAAc,OAAO,QAA0B,MAAM,CAAC,GACjE,MAAM,OAAO,OAAO,OAAO,SAAS,MAClC,OAAO,KACL,IAAI,QAAQ,YAAY;CACtB,UAAU;CACV,YAAY;CACZ,QAAQ,gCAAgC,EAAE;AAC5C,CAAC,CACH,CACF,GACA,MAAM,UACR;;;;;;AAOA,MAAa,oBACX,MAAM,KAAuB,CAAC,CAAC,KAC7B,MAAM,KAAK,cAA2B;CACpC,WAAW;CACX,UAAU;CACV,YAAY;AACd,EAAE,GACF,MAAM,KAAK,cAA2B;CACpC,WAAW;CACX,UAAU;CACV,YAAY;AACd,EAAE,GACF,MAAM,KAAK,cAA2B;CACpC,WAAW;CACX,UAAU;CACV,YAAY;AACd,EAAE,GACF,MAAM,KAAK,eAA4B;CACrC,WAAW;CACX,UAAU;CACV,YAAY;AACd,EAAE,GACF,MAAM,KAAK,eAA4B;CACrC,WAAW;CACX,UAAU;CACV,YAAY;AACd,EAAE,GACF,MAAM,UACR"}
|
package/dist/codec.d.mts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { Accumulator, WireChunk, WireChunk as WireChunk$1, accumulatorToTurn, applyChunk, emptyAccumulator, responseFormatWire, toolsWire } from "@effect-uai/chat-completions/codec";
|
|
3
3
|
import { HistoryItem } from "@effect-uai/core/Items";
|
|
4
|
-
|
|
5
|
-
//#region src/codec.d.ts
|
|
6
4
|
declare namespace codec_d_exports {
|
|
7
5
|
export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, toolChoiceWire, toolsWire };
|
|
8
6
|
}
|
package/dist/codec.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;KA4BK;EACH;EACA,kBAAkB,cAAc;EAChC,aAAa,cAAc;EAC3B;;;cAyEW,kBAAe,OAAW,cAAc,iBAAe,cAAc;KAO7E;WAIU;WAA2B;;cAG7B,iBAAc,QAAY,wBAAsB;cAyBhD,cAAW,eAAiB,OAAO,OAAO"}
|
package/dist/codec.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { Array, Effect, Encoding, Match } from "effect";
|
|
3
3
|
import { accumulatorToTurn, applyChunk, decodeChunk as decodeChunk$1, emptyAccumulator, responseFormatWire, toolsWire } from "@effect-uai/chat-completions/codec";
|
|
4
4
|
//#region src/codec.ts
|
package/dist/codec.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec.mjs","names":["Arr","decodeChunkShared"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option } from \"effect\"\nimport type { ContentBlock, HistoryItem, InputImage } from \"@effect-uai/core/Items\"\n\n// The streaming decoder, tool encoding, and terminal-turn assembly are the\n// generic OpenAI chat-completions codec: Mistral speaks that dialect on the\n// wire, so it reuses them verbatim. Only request encoding diverges (below).\nexport {\n type Accumulator,\n type WireChunk,\n accumulatorToTurn,\n applyChunk,\n emptyAccumulator,\n responseFormatWire,\n toolsWire,\n} from \"@effect-uai/chat-completions/codec\"\nimport {\n type WireChunk,\n decodeChunk as decodeChunkShared,\n} from \"@effect-uai/chat-completions/codec\"\n\n// ---------------------------------------------------------------------------\n// History → Mistral chat `messages`\n//\n// Same shape as the generic codec, with one wire quirk: Mistral takes a bare\n// `image_url` string, not OpenAI's `image_url: { url }` object. That is the\n// reason this half stays local rather than importing `itemsToMessages`.\n// ---------------------------------------------------------------------------\n\ntype WireMessage = {\n role: string\n content: string | ReadonlyArray<Record<string, unknown>>\n tool_calls?: ReadonlyArray<Record<string, unknown>>\n tool_call_id?: string\n}\n\n/** A `data:` URI for inline image bytes; URLs pass through untouched. */\nconst imageSourceToUrl = Match.type<InputImage[\"source\"]>().pipe(\n Match.tag(\"url\", (s) => s.url),\n Match.tag(\"base64\", (s) => `data:${s.mimeType};base64,${s.base64}`),\n Match.tag(\"bytes\", (s) => `data:${s.mimeType};base64,${Encoding.encodeBase64(s.bytes)}`),\n Match.exhaustive,\n)\n\nconst blockToPart = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => ({ type: \"text\", text: b.text }),\n output_text: (b) => ({ type: \"text\", text: b.text }),\n refusal: (b) => ({ type: \"text\", text: b.text }),\n // Bare string, not `{ url }`: the Mistral divergence from OpenAI.\n input_image: (b) => ({ type: \"image_url\", image_url: imageSourceToUrl(b.source) }),\n }),\n)\n\nconst isTextBlock = (b: ContentBlock): boolean =>\n b.type === \"input_text\" || b.type === \"output_text\" || b.type === \"refusal\"\n\nconst textOfBlock = (b: ContentBlock): string => (b.type === \"input_image\" ? \"\" : b.text)\n\n/**\n * Collapse a message's content to a plain string when it is text-only\n * (the common case Mistral prefers), otherwise emit the multimodal parts\n * array.\n */\nconst encodeContent = (\n content: ReadonlyArray<ContentBlock>,\n): string | ReadonlyArray<Record<string, unknown>> =>\n Arr.every(content, isTextBlock) ? content.map(textOfBlock).join(\"\") : content.map(blockToPart)\n\nconst toolCallWire = (call_id: string, name: string, args: string): Record<string, unknown> => ({\n id: call_id,\n type: \"function\",\n function: { name, arguments: args },\n})\n\nconst appendToolCall = (\n acc: ReadonlyArray<WireMessage>,\n call_id: string,\n name: string,\n args: string,\n): ReadonlyArray<WireMessage> => {\n const last = acc[acc.length - 1]\n const wire = toolCallWire(call_id, name, args)\n // Attach to the immediately-preceding assistant message (not a tool result),\n // matching the OpenAI/Mistral shape where tool calls ride the assistant turn.\n if (last !== undefined && last.role === \"assistant\" && last.tool_call_id === undefined) {\n return [...acc.slice(0, -1), { ...last, tool_calls: [...(last.tool_calls ?? []), wire] }]\n }\n return [...acc, { role: \"assistant\", content: \"\", tool_calls: [wire] }]\n}\n\nconst foldItem = (acc: ReadonlyArray<WireMessage>, item: HistoryItem): ReadonlyArray<WireMessage> =>\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m) => [...acc, { role: m.role, content: encodeContent(m.content) }],\n function_call: (f) => appendToolCall(acc, f.call_id, f.name, f.arguments),\n function_call_output: (o) => [\n ...acc,\n { role: \"tool\", tool_call_id: o.call_id, content: o.output },\n ],\n // Reasoning items are not replayed to the chat-completions endpoint.\n reasoning: () => acc,\n }),\n )\n\n/** Convert our `HistoryItem[]` history into Mistral chat `messages`. */\nexport const itemsToMessages = (items: ReadonlyArray<HistoryItem>): ReadonlyArray<WireMessage> =>\n Arr.reduce(items, [] as ReadonlyArray<WireMessage>, foldItem)\n\n// ---------------------------------------------------------------------------\n// tool_choice\n// ---------------------------------------------------------------------------\n\ntype ToolChoice =\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n\n// Mistral uses \"any\" for forced tool use (OpenAI's \"required\").\nexport const toolChoiceWire = (choice: ToolChoice): string | Record<string, unknown> =>\n Match.value(choice).pipe(\n Match.when(\"auto\", () => \"auto\" as const),\n Match.when(\"required\", () => \"any\" as const),\n Match.when(\"none\", () => \"none\" as const),\n Match.orElse((c) => ({ type: \"function\", function: { name: c.name } })),\n )\n\n// ---------------------------------------------------------------------------\n// Streaming decode\n// ---------------------------------------------------------------------------\n\n// Mistral emits `model_length` when it truncates on the context window; the\n// shared decoder only knows OpenAI's `length`. Rewrite it before folding so the\n// shared accumulator maps it to `max_tokens` rather than the `stop` fallback.\nconst normalizeChunk = (chunk: WireChunk): WireChunk =>\n chunk.choices === undefined\n ? chunk\n : {\n ...chunk,\n choices: chunk.choices.map((c) =>\n c.finish_reason === \"model_length\" ? { ...c, finish_reason: \"length\" } : c,\n ),\n }\n\nexport const decodeChunk = (u: unknown): Effect.Effect<WireChunk, unknown> =>\n decodeChunkShared(u).pipe(Effect.map(normalizeChunk))\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,MAAM,mBAAmB,MAAM,
|
|
1
|
+
{"version":3,"file":"codec.mjs","names":["Arr","decodeChunkShared"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option } from \"effect\"\nimport type { ContentBlock, HistoryItem, InputImage } from \"@effect-uai/core/Items\"\n\n// The streaming decoder, tool encoding, and terminal-turn assembly are the\n// generic OpenAI chat-completions codec: Mistral speaks that dialect on the\n// wire, so it reuses them verbatim. Only request encoding diverges (below).\nexport {\n type Accumulator,\n type WireChunk,\n accumulatorToTurn,\n applyChunk,\n emptyAccumulator,\n responseFormatWire,\n toolsWire,\n} from \"@effect-uai/chat-completions/codec\"\nimport {\n type WireChunk,\n decodeChunk as decodeChunkShared,\n} from \"@effect-uai/chat-completions/codec\"\n\n// ---------------------------------------------------------------------------\n// History → Mistral chat `messages`\n//\n// Same shape as the generic codec, with one wire quirk: Mistral takes a bare\n// `image_url` string, not OpenAI's `image_url: { url }` object. That is the\n// reason this half stays local rather than importing `itemsToMessages`.\n// ---------------------------------------------------------------------------\n\ntype WireMessage = {\n role: string\n content: string | ReadonlyArray<Record<string, unknown>>\n tool_calls?: ReadonlyArray<Record<string, unknown>>\n tool_call_id?: string\n}\n\n/** A `data:` URI for inline image bytes; URLs pass through untouched. */\nconst imageSourceToUrl = Match.type<InputImage[\"source\"]>().pipe(\n Match.tag(\"url\", (s) => s.url),\n Match.tag(\"base64\", (s) => `data:${s.mimeType};base64,${s.base64}`),\n Match.tag(\"bytes\", (s) => `data:${s.mimeType};base64,${Encoding.encodeBase64(s.bytes)}`),\n Match.exhaustive,\n)\n\nconst blockToPart = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => ({ type: \"text\", text: b.text }),\n output_text: (b) => ({ type: \"text\", text: b.text }),\n refusal: (b) => ({ type: \"text\", text: b.text }),\n // Bare string, not `{ url }`: the Mistral divergence from OpenAI.\n input_image: (b) => ({ type: \"image_url\", image_url: imageSourceToUrl(b.source) }),\n }),\n)\n\nconst isTextBlock = (b: ContentBlock): boolean =>\n b.type === \"input_text\" || b.type === \"output_text\" || b.type === \"refusal\"\n\nconst textOfBlock = (b: ContentBlock): string => (b.type === \"input_image\" ? \"\" : b.text)\n\n/**\n * Collapse a message's content to a plain string when it is text-only\n * (the common case Mistral prefers), otherwise emit the multimodal parts\n * array.\n */\nconst encodeContent = (\n content: ReadonlyArray<ContentBlock>,\n): string | ReadonlyArray<Record<string, unknown>> =>\n Arr.every(content, isTextBlock) ? content.map(textOfBlock).join(\"\") : content.map(blockToPart)\n\nconst toolCallWire = (call_id: string, name: string, args: string): Record<string, unknown> => ({\n id: call_id,\n type: \"function\",\n function: { name, arguments: args },\n})\n\nconst appendToolCall = (\n acc: ReadonlyArray<WireMessage>,\n call_id: string,\n name: string,\n args: string,\n): ReadonlyArray<WireMessage> => {\n const last = acc[acc.length - 1]\n const wire = toolCallWire(call_id, name, args)\n // Attach to the immediately-preceding assistant message (not a tool result),\n // matching the OpenAI/Mistral shape where tool calls ride the assistant turn.\n if (last !== undefined && last.role === \"assistant\" && last.tool_call_id === undefined) {\n return [...acc.slice(0, -1), { ...last, tool_calls: [...(last.tool_calls ?? []), wire] }]\n }\n return [...acc, { role: \"assistant\", content: \"\", tool_calls: [wire] }]\n}\n\nconst foldItem = (acc: ReadonlyArray<WireMessage>, item: HistoryItem): ReadonlyArray<WireMessage> =>\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m) => [...acc, { role: m.role, content: encodeContent(m.content) }],\n function_call: (f) => appendToolCall(acc, f.call_id, f.name, f.arguments),\n function_call_output: (o) => [\n ...acc,\n { role: \"tool\", tool_call_id: o.call_id, content: o.output },\n ],\n // Reasoning items are not replayed to the chat-completions endpoint.\n reasoning: () => acc,\n }),\n )\n\n/** Convert our `HistoryItem[]` history into Mistral chat `messages`. */\nexport const itemsToMessages = (items: ReadonlyArray<HistoryItem>): ReadonlyArray<WireMessage> =>\n Arr.reduce(items, [] as ReadonlyArray<WireMessage>, foldItem)\n\n// ---------------------------------------------------------------------------\n// tool_choice\n// ---------------------------------------------------------------------------\n\ntype ToolChoice =\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n\n// Mistral uses \"any\" for forced tool use (OpenAI's \"required\").\nexport const toolChoiceWire = (choice: ToolChoice): string | Record<string, unknown> =>\n Match.value(choice).pipe(\n Match.when(\"auto\", () => \"auto\" as const),\n Match.when(\"required\", () => \"any\" as const),\n Match.when(\"none\", () => \"none\" as const),\n Match.orElse((c) => ({ type: \"function\", function: { name: c.name } })),\n )\n\n// ---------------------------------------------------------------------------\n// Streaming decode\n// ---------------------------------------------------------------------------\n\n// Mistral emits `model_length` when it truncates on the context window; the\n// shared decoder only knows OpenAI's `length`. Rewrite it before folding so the\n// shared accumulator maps it to `max_tokens` rather than the `stop` fallback.\nconst normalizeChunk = (chunk: WireChunk): WireChunk =>\n chunk.choices === undefined\n ? chunk\n : {\n ...chunk,\n choices: chunk.choices.map((c) =>\n c.finish_reason === \"model_length\" ? { ...c, finish_reason: \"length\" } : c,\n ),\n }\n\nexport const decodeChunk = (u: unknown): Effect.Effect<WireChunk, unknown> =>\n decodeChunkShared(u).pipe(Effect.map(normalizeChunk))\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,MAAM,mBAAmB,MAAM,KAA2B,CAAC,CAAC,KAC1D,MAAM,IAAI,QAAQ,MAAM,EAAE,GAAG,GAC7B,MAAM,IAAI,WAAW,MAAM,QAAQ,EAAE,SAAS,UAAU,EAAE,QAAQ,GAClE,MAAM,IAAI,UAAU,MAAM,QAAQ,EAAE,SAAS,UAAU,SAAS,aAAa,EAAE,KAAK,GAAG,GACvF,MAAM,UACR;AAEA,MAAM,cAAc,MAAM,KAAmB,CAAC,CAAC,KAC7C,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,aAAa,OAAO;EAAE,MAAM;EAAQ,MAAM,EAAE;CAAK;CACjD,cAAc,OAAO;EAAE,MAAM;EAAQ,MAAM,EAAE;CAAK;CAClD,UAAU,OAAO;EAAE,MAAM;EAAQ,MAAM,EAAE;CAAK;CAE9C,cAAc,OAAO;EAAE,MAAM;EAAa,WAAW,iBAAiB,EAAE,MAAM;CAAE;AAClF,CAAC,CACH;AAEA,MAAM,eAAe,MACnB,EAAE,SAAS,gBAAgB,EAAE,SAAS,iBAAiB,EAAE,SAAS;AAEpE,MAAM,eAAe,MAA6B,EAAE,SAAS,gBAAgB,KAAK,EAAE;;;;;;AAOpF,MAAM,iBACJ,YAEAA,MAAI,MAAM,SAAS,WAAW,IAAI,QAAQ,IAAI,WAAW,CAAC,CAAC,KAAK,EAAE,IAAI,QAAQ,IAAI,WAAW;AAE/F,MAAM,gBAAgB,SAAiB,MAAc,UAA2C;CAC9F,IAAI;CACJ,MAAM;CACN,UAAU;EAAE;EAAM,WAAW;CAAK;AACpC;AAEA,MAAM,kBACJ,KACA,SACA,MACA,SAC+B;CAC/B,MAAM,OAAO,IAAI,IAAI,SAAS;CAC9B,MAAM,OAAO,aAAa,SAAS,MAAM,IAAI;CAG7C,IAAI,SAAS,KAAA,KAAa,KAAK,SAAS,eAAe,KAAK,iBAAiB,KAAA,GAC3E,OAAO,CAAC,GAAG,IAAI,MAAM,GAAG,EAAE,GAAG;EAAE,GAAG;EAAM,YAAY,CAAC,GAAI,KAAK,cAAc,CAAC,GAAI,IAAI;CAAE,CAAC;CAE1F,OAAO,CAAC,GAAG,KAAK;EAAE,MAAM;EAAa,SAAS;EAAI,YAAY,CAAC,IAAI;CAAE,CAAC;AACxE;AAEA,MAAM,YAAY,KAAiC,SACjD,MAAM,MAAM,IAAI,CAAC,CAAC,KAChB,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,UAAU,MAAM,CAAC,GAAG,KAAK;EAAE,MAAM,EAAE;EAAM,SAAS,cAAc,EAAE,OAAO;CAAE,CAAC;CAC5E,gBAAgB,MAAM,eAAe,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS;CACxE,uBAAuB,MAAM,CAC3B,GAAG,KACH;EAAE,MAAM;EAAQ,cAAc,EAAE;EAAS,SAAS,EAAE;CAAO,CAC7D;CAEA,iBAAiB;AACnB,CAAC,CACH;;AAGF,MAAa,mBAAmB,UAC9BA,MAAI,OAAO,OAAO,CAAC,GAAiC,QAAQ;AAa9D,MAAa,kBAAkB,WAC7B,MAAM,MAAM,MAAM,CAAC,CAAC,KAClB,MAAM,KAAK,cAAc,MAAe,GACxC,MAAM,KAAK,kBAAkB,KAAc,GAC3C,MAAM,KAAK,cAAc,MAAe,GACxC,MAAM,QAAQ,OAAO;CAAE,MAAM;CAAY,UAAU,EAAE,MAAM,EAAE,KAAK;AAAE,EAAE,CACxE;AASF,MAAM,kBAAkB,UACtB,MAAM,YAAY,KAAA,IACd,QACA;CACE,GAAG;CACH,SAAS,MAAM,QAAQ,KAAK,MAC1B,EAAE,kBAAkB,iBAAiB;EAAE,GAAG;EAAG,eAAe;CAAS,IAAI,CAC3E;AACF;AAEN,MAAa,eAAe,MAC1BC,cAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,IAAI,cAAc,CAAC"}
|
package/dist/codec.test.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {}
|
package/dist/codec.test.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { accumulatorToTurn, applyChunk, emptyAccumulator, itemsToMessages, toolChoiceWire } from "./codec.mjs";
|
|
2
|
-
import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-
|
|
2
|
+
import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-N3QqMRwZ.mjs";
|
|
3
3
|
//#region src/codec.test.ts
|
|
4
4
|
describe("itemsToMessages", () => {
|
|
5
5
|
it("folds tool calls onto the assistant message and emits tool results", () => {
|
package/dist/codec.test.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec.test.mjs","names":[],"sources":["../src/codec.test.ts"],"sourcesContent":["import type { HistoryItem } from \"@effect-uai/core/Items\"\nimport { describe, expect, it } from \"vitest\"\nimport {\n type Accumulator,\n accumulatorToTurn,\n applyChunk,\n emptyAccumulator,\n itemsToMessages,\n toolChoiceWire,\n type WireChunk,\n} from \"./codec.js\"\n\ndescribe(\"itemsToMessages\", () => {\n // The interesting case: our flat history (assistant message, then separate\n // function_call items) must fold into the OpenAI/Mistral shape where the\n // tool calls ride the assistant message and each result is a `tool` message.\n it(\"folds tool calls onto the assistant message and emits tool results\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n { type: \"message\", role: \"assistant\", content: [{ type: \"output_text\", text: \"checking\" }] },\n { type: \"function_call\", call_id: \"c1\", name: \"a\", arguments: \"{}\" },\n { type: \"function_call\", call_id: \"c2\", name: \"b\", arguments: \"{}\" },\n { type: \"function_call_output\", call_id: \"c1\", output: \"ra\" },\n ]\n expect(itemsToMessages(history)).toEqual([\n {\n role: \"assistant\",\n content: \"checking\",\n tool_calls: [\n { id: \"c1\", type: \"function\", function: { name: \"a\", arguments: \"{}\" } },\n { id: \"c2\", type: \"function\", function: { name: \"b\", arguments: \"{}\" } },\n ],\n },\n { role: \"tool\", tool_call_id: \"c1\", content: \"ra\" },\n ])\n })\n\n it(\"encodes a message with an image as multimodal parts\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"message\",\n role: \"user\",\n content: [\n { type: \"input_text\", text: \"what is this\" },\n { type: \"input_image\", source: { _tag: \"url\", url: \"https://x/y.png\" } },\n ],\n },\n ]\n expect(itemsToMessages(history)).toEqual([\n {\n role: \"user\",\n content: [\n { type: \"text\", text: \"what is this\" },\n { type: \"image_url\", image_url: \"https://x/y.png\" },\n ],\n },\n ])\n })\n})\n\nit(\"toolChoiceWire maps 'required' to Mistral's 'any'\", () => {\n // Mistral diverges from OpenAI here; the rest pass through unchanged.\n expect(toolChoiceWire(\"required\")).toBe(\"any\")\n})\n\n// Run a sequence of chunks through the accumulator, collecting event tags.\nconst runChunks = (\n chunks: ReadonlyArray<WireChunk>,\n): { acc: Accumulator; tags: ReadonlyArray<string> } => {\n let acc = emptyAccumulator\n const tags: Array<string> = []\n for (const chunk of chunks) {\n const [next, events] = applyChunk(acc, chunk)\n acc = next\n for (const e of events) tags.push(e._tag)\n }\n return { acc, tags }\n}\n\ndescribe(\"streaming accumulation\", () => {\n it(\"accumulates text deltas + usage into an assistant turn\", () => {\n const { acc, tags } = runChunks([\n { choices: [{ delta: { content: \"Hel\" } }] },\n { choices: [{ delta: { content: \"lo\" } }] },\n { choices: [{ delta: {}, finish_reason: \"stop\" }] },\n { usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 } },\n ])\n expect(tags).toEqual([\"TextDelta\", \"TextDelta\", \"UsageUpdate\"])\n const turn = accumulatorToTurn(acc)\n expect(turn.items).toEqual([\n { type: \"message\", role: \"assistant\", content: [{ type: \"output_text\", text: \"Hello\" }] },\n ])\n expect(turn.usage).toEqual({ input_tokens: 3, output_tokens: 2, total_tokens: 5 })\n expect(turn.stop_reason).toBe(\"stop\")\n })\n\n it(\"stitches a tool call split across chunks\", () => {\n const { acc, tags } = runChunks([\n {\n choices: [\n {\n delta: {\n tool_calls: [\n { index: 0, id: \"c1\", function: { name: \"get_weather\", arguments: '{\"ci' } },\n ],\n },\n },\n ],\n },\n {\n choices: [{ delta: { tool_calls: [{ index: 0, function: { arguments: 'ty\":\"NYC\"}' } }] } }],\n },\n { choices: [{ delta: {}, finish_reason: \"tool_calls\" }] },\n ])\n expect(tags).toEqual([\"ToolCallStart\", \"ToolCallArgsDelta\", \"ToolCallArgsDelta\"])\n const turn = accumulatorToTurn(acc)\n expect(turn.items).toEqual([\n { type: \"function_call\", call_id: \"c1\", name: \"get_weather\", arguments: '{\"city\":\"NYC\"}' },\n ])\n expect(turn.stop_reason).toBe(\"tool_calls\")\n })\n})\n"],"mappings":";;;AAYA,SAAS,yBAAyB;
|
|
1
|
+
{"version":3,"file":"codec.test.mjs","names":[],"sources":["../src/codec.test.ts"],"sourcesContent":["import type { HistoryItem } from \"@effect-uai/core/Items\"\nimport { describe, expect, it } from \"vitest\"\nimport {\n type Accumulator,\n accumulatorToTurn,\n applyChunk,\n emptyAccumulator,\n itemsToMessages,\n toolChoiceWire,\n type WireChunk,\n} from \"./codec.js\"\n\ndescribe(\"itemsToMessages\", () => {\n // The interesting case: our flat history (assistant message, then separate\n // function_call items) must fold into the OpenAI/Mistral shape where the\n // tool calls ride the assistant message and each result is a `tool` message.\n it(\"folds tool calls onto the assistant message and emits tool results\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n { type: \"message\", role: \"assistant\", content: [{ type: \"output_text\", text: \"checking\" }] },\n { type: \"function_call\", call_id: \"c1\", name: \"a\", arguments: \"{}\" },\n { type: \"function_call\", call_id: \"c2\", name: \"b\", arguments: \"{}\" },\n { type: \"function_call_output\", call_id: \"c1\", output: \"ra\" },\n ]\n expect(itemsToMessages(history)).toEqual([\n {\n role: \"assistant\",\n content: \"checking\",\n tool_calls: [\n { id: \"c1\", type: \"function\", function: { name: \"a\", arguments: \"{}\" } },\n { id: \"c2\", type: \"function\", function: { name: \"b\", arguments: \"{}\" } },\n ],\n },\n { role: \"tool\", tool_call_id: \"c1\", content: \"ra\" },\n ])\n })\n\n it(\"encodes a message with an image as multimodal parts\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"message\",\n role: \"user\",\n content: [\n { type: \"input_text\", text: \"what is this\" },\n { type: \"input_image\", source: { _tag: \"url\", url: \"https://x/y.png\" } },\n ],\n },\n ]\n expect(itemsToMessages(history)).toEqual([\n {\n role: \"user\",\n content: [\n { type: \"text\", text: \"what is this\" },\n { type: \"image_url\", image_url: \"https://x/y.png\" },\n ],\n },\n ])\n })\n})\n\nit(\"toolChoiceWire maps 'required' to Mistral's 'any'\", () => {\n // Mistral diverges from OpenAI here; the rest pass through unchanged.\n expect(toolChoiceWire(\"required\")).toBe(\"any\")\n})\n\n// Run a sequence of chunks through the accumulator, collecting event tags.\nconst runChunks = (\n chunks: ReadonlyArray<WireChunk>,\n): { acc: Accumulator; tags: ReadonlyArray<string> } => {\n let acc = emptyAccumulator\n const tags: Array<string> = []\n for (const chunk of chunks) {\n const [next, events] = applyChunk(acc, chunk)\n acc = next\n for (const e of events) tags.push(e._tag)\n }\n return { acc, tags }\n}\n\ndescribe(\"streaming accumulation\", () => {\n it(\"accumulates text deltas + usage into an assistant turn\", () => {\n const { acc, tags } = runChunks([\n { choices: [{ delta: { content: \"Hel\" } }] },\n { choices: [{ delta: { content: \"lo\" } }] },\n { choices: [{ delta: {}, finish_reason: \"stop\" }] },\n { usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 } },\n ])\n expect(tags).toEqual([\"TextDelta\", \"TextDelta\", \"UsageUpdate\"])\n const turn = accumulatorToTurn(acc)\n expect(turn.items).toEqual([\n { type: \"message\", role: \"assistant\", content: [{ type: \"output_text\", text: \"Hello\" }] },\n ])\n expect(turn.usage).toEqual({ input_tokens: 3, output_tokens: 2, total_tokens: 5 })\n expect(turn.stop_reason).toBe(\"stop\")\n })\n\n it(\"stitches a tool call split across chunks\", () => {\n const { acc, tags } = runChunks([\n {\n choices: [\n {\n delta: {\n tool_calls: [\n { index: 0, id: \"c1\", function: { name: \"get_weather\", arguments: '{\"ci' } },\n ],\n },\n },\n ],\n },\n {\n choices: [{ delta: { tool_calls: [{ index: 0, function: { arguments: 'ty\":\"NYC\"}' } }] } }],\n },\n { choices: [{ delta: {}, finish_reason: \"tool_calls\" }] },\n ])\n expect(tags).toEqual([\"ToolCallStart\", \"ToolCallArgsDelta\", \"ToolCallArgsDelta\"])\n const turn = accumulatorToTurn(acc)\n expect(turn.items).toEqual([\n { type: \"function_call\", call_id: \"c1\", name: \"get_weather\", arguments: '{\"city\":\"NYC\"}' },\n ])\n expect(turn.stop_reason).toBe(\"tool_calls\")\n })\n})\n"],"mappings":"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"}
|
package/dist/http.d.mts
CHANGED
package/dist/http.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.mts","names":[],"sources":["../src/http.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.mts","names":[],"sources":["../src/http.ts"],"mappings":";;;cAIa,kBAAkB,gBAAgB,iBAAiB,QAAQ;cAwC3D,mBAAgB,mBAAqB,QAAQ"}
|