@effect-uai/mistral 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/Mistral.d.mts +1 -1
  2. package/dist/Mistral.d.mts.map +1 -1
  3. package/dist/Mistral.mjs +2 -2
  4. package/dist/Mistral.mjs.map +1 -1
  5. package/dist/MistralRealtimeTranscriber.d.mts +1 -1
  6. package/dist/MistralRealtimeTranscriber.mjs +1 -1
  7. package/dist/MistralRealtimeTranscriber.mjs.map +1 -1
  8. package/dist/MistralSynthesizer.d.mts +1 -1
  9. package/dist/MistralSynthesizer.mjs +1 -1
  10. package/dist/MistralSynthesizer.mjs.map +1 -1
  11. package/dist/MistralTranscriber.d.mts +1 -1
  12. package/dist/MistralTranscriber.mjs +1 -1
  13. package/dist/MistralTranscriber.mjs.map +1 -1
  14. package/dist/codec.d.mts +1 -1
  15. package/dist/codec.test.mjs +1 -13630
  16. package/dist/codec.test.mjs.map +1 -1
  17. package/dist/index.d.mts +1 -1
  18. package/dist/{magic-string.es-PAZgLX4h.mjs → magic-string.es-CaWgF-3p.mjs} +1 -1
  19. package/dist/{magic-string.es-PAZgLX4h.mjs.map → magic-string.es-CaWgF-3p.mjs.map} +1 -1
  20. package/dist/{models-DnDmylpj.d.mts → models-B1wZuvz5.d.mts} +1 -1
  21. package/dist/models-B1wZuvz5.d.mts.map +1 -0
  22. package/dist/models.d.mts +1 -1
  23. package/dist/onHalt.test.d.mts +1 -0
  24. package/dist/onHalt.test.mjs +61 -0
  25. package/dist/onHalt.test.mjs.map +1 -0
  26. package/dist/{realtimeStt-ClXWXbXX.d.mts → realtimeStt-BFcctqFC.d.mts} +1 -1
  27. package/dist/{realtimeStt-ClXWXbXX.d.mts.map → realtimeStt-BFcctqFC.d.mts.map} +1 -1
  28. package/dist/realtimeStt.d.mts +1 -1
  29. package/dist/test.DNmyFkvJ-CPKukVWS.mjs +13633 -0
  30. package/dist/test.DNmyFkvJ-CPKukVWS.mjs.map +1 -0
  31. package/package.json +4 -4
  32. package/src/Mistral.ts +7 -2
  33. package/src/MistralRealtimeTranscriber.ts +1 -1
  34. package/src/MistralSynthesizer.ts +1 -1
  35. package/src/MistralTranscriber.ts +1 -1
  36. package/src/onHalt.test.ts +75 -0
  37. package/dist/models-DnDmylpj.d.mts.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as MistralModel } from "./models-DnDmylpj.mjs";
1
+ import { t as MistralModel } from "./models-B1wZuvz5.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";
@@ -1 +1 @@
1
- {"version":3,"file":"Mistral.d.mts","names":[],"sources":["../src/Mistral.ts"],"mappings":";;;;;;;;;;;KA+BY,cAAA,GAAiB,IAAA,CAAK,aAAA;;;;;WAKvB,KAAA,EAAO,YAAA;;;;WAIP,UAAA;WAEA,UAAA;AAAA;AAAA,KAGC,cAAA;EAdsB,2DAgBvB,UAAA,GAAa,OAAA,EAAS,cAAA,KAAmB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA,GAXnE;EAAA,SAaP,IAAA,GAAO,OAAA,EAAS,cAAA,KAAmB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,CAAQ,OAAA;AAAA;AAAA,cACzE,YAAA;;;;;;cAOY,OAAA,SAAgB,YAAA;AAAA,KAIjB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;cAgGE,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,cAAA,SAAuB,UAAA,CAAW,UAAA;;;;;cActE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,OAAA,GAAU,aAAA,SAAsB,UAAA,CAAW,UAAA"}
1
+ {"version":3,"file":"Mistral.d.mts","names":[],"sources":["../src/Mistral.ts"],"mappings":";;;;;;;;;;;KA+BY,cAAA,GAAiB,IAAA,CAAK,aAAA;;;;;WAKvB,KAAA,EAAO,YAAA;;;;WAIP,UAAA;WAEA,UAAA;AAAA;AAAA,KAGC,cAAA;EAdsB,2DAgBvB,UAAA,GAAa,OAAA,EAAS,cAAA,KAAmB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA,GAXnE;EAAA,SAaP,IAAA,GAAO,OAAA,EAAS,cAAA,KAAmB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,CAAQ,OAAA;AAAA;AAAA,cACzE,YAAA;;;;;;cAOY,OAAA,SAAgB,YAAA;AAAA,KAIjB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;cAqGE,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,cAAA,SAAuB,UAAA,CAAW,UAAA;;;;;cActE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,OAAA,GAAU,aAAA,SAAsB,UAAA,CAAW,UAAA"}
package/dist/Mistral.mjs CHANGED
@@ -59,11 +59,11 @@ const buildStream = (cfg) => {
59
59
  return response.stream.pipe(Stream.mapError((cause) => new AiError.Unavailable({
60
60
  provider: "mistral",
61
61
  raw: cause
62
- })), SSE.fromBytes, Stream.mapEffect((ev) => decodeEvent(ev.data)), Stream.filterMap((chunk) => Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid), Stream.mapAccum(() => emptyAccumulator, applyChunk, { onHalt: (acc) => [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })] }));
62
+ })), SSE.fromBytes, Stream.mapEffect((ev) => decodeEvent(ev.data)), Stream.filterMap((chunk) => Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid), Stream.mapAccum(() => emptyAccumulator, applyChunk, { onHalt: (acc) => Option.isSome(acc.finishReason) ? [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })] : [] }));
63
63
  }));
64
64
  };
65
65
  /** Build a `MistralService` value. For Layer-based setup, prefer `layer`. */
66
- const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => {
66
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => {
67
67
  const streamTurn = (request) => buildStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client));
68
68
  return {
69
69
  streamTurn,
@@ -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: (acc) => [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })],\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.asEffect(), (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(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as MistralRequest),\n turn: (request) => s.turn(request as MistralRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
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(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as MistralRequest),\n turn: (request) => s.turn(request as MistralRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { MistralTranscribeRequest, MistralTranscriber, MistralTranscriberService } from "./MistralTranscriber.mjs";
2
- import { t as Config } from "./realtimeStt-ClXWXbXX.mjs";
2
+ import { t as Config } from "./realtimeStt-BFcctqFC.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";
@@ -20,7 +20,7 @@ var MistralRealtimeTranscriber_exports = /* @__PURE__ */ __exportAll({
20
20
  layer: () => layer,
21
21
  make: () => make
22
22
  });
23
- const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
23
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({
24
24
  transcribe: (request) => transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
25
25
  streamTranscriptionFrom: streamTranscription(cfg)
26
26
  }));
@@ -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.asEffect(), (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(\n make(cfg),\n (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as MistralTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n }),\n ),\n ),\n Layer.succeed(SttStreaming, undefined),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,QACX,QAEA,OAAO,IAAI,WAAW,WAAW,UAAU,GAAG,YAAY;CACxD,aAAa,YACX,eAAe,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;CACzF,yBAAyB,oBAAoB,IAAI;CAClD,EAAE;AAEL,MAAa,SACX,QAEA,MAAM,SACJ,MAAM,OAAO,oBAAoB,KAAK,IAAI,CAAC,EAC3C,MAAM,OACJ,aACA,OAAO,IACL,KAAK,IAAI,GACR,OAA2B;CAC1B,aAAa,QACX,EAAE,WAAW,IAAgC;CAC/C,yBAAyB,EAAE;CAC5B,EACF,CACF,EACD,MAAM,QAAQ,cAAc,KAAA,EAAU,CACvC"}
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(\n make(cfg),\n (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as MistralTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n }),\n ),\n ),\n Layer.succeed(SttStreaming, undefined),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,QACX,QAEA,OAAO,IAAI,WAAW,aAAa,YAAY;CAC7C,aAAa,YACX,eAAe,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;CACzF,yBAAyB,oBAAoB,IAAI;CAClD,EAAE;AAEL,MAAa,SACX,QAEA,MAAM,SACJ,MAAM,OAAO,oBAAoB,KAAK,IAAI,CAAC,EAC3C,MAAM,OACJ,aACA,OAAO,IACL,KAAK,IAAI,GACR,OAA2B;CAC1B,aAAa,QACX,EAAE,WAAW,IAAgC;CAC/C,yBAAyB,EAAE;CAC5B,EACF,CACF,EACD,MAAM,QAAQ,cAAc,KAAA,EAAU,CACvC"}
@@ -1,4 +1,4 @@
1
- import { i as MistralTtsModel } from "./models-DnDmylpj.mjs";
1
+ import { i as MistralTtsModel } from "./models-B1wZuvz5.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";
@@ -93,7 +93,7 @@ const streamSynthesizeDialogueUnsupported = () => Stream.fail(new AiError.Unsupp
93
93
  capability: "streamSynthesizeDialogue",
94
94
  reason: "Voxtral TTS has no multi-speaker dialogue endpoint."
95
95
  }));
96
- const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
96
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({
97
97
  synthesize: (request) => synthesizeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
98
98
  streamSynthesis: (request) => streamSynthesisImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),
99
99
  streamSynthesisFrom: (textIn, request) => Stream.unwrap(Effect.map(Stream.runFold(textIn, () => "", (acc, s) => acc + s), (text) => streamSynthesisImpl(cfg)({
@@ -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.asEffect(), (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(\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,4 +1,4 @@
1
- import { r as MistralTranscribeModel } from "./models-DnDmylpj.mjs";
1
+ import { r as MistralTranscribeModel } from "./models-B1wZuvz5.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";
@@ -92,7 +92,7 @@ const streamUnsupported = (_audioIn, _request) => Stream.fail(new AiError.Unsupp
92
92
  capability: "streamTranscriptionFrom",
93
93
  reason: "This Layer is sync-only. Use `@effect-uai/mistral/MistralRealtimeTranscriber` for live Voxtral transcription."
94
94
  }));
95
- const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
95
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({
96
96
  transcribe: (request) => transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
97
97
  streamTranscriptionFrom: streamUnsupported
98
98
  }));
@@ -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.asEffect(), (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(\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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"}
package/dist/codec.d.mts CHANGED
@@ -60,8 +60,8 @@ declare const decodeChunk: (input: unknown, options?: _$effect_SchemaAST0.ParseO
60
60
  readonly name?: string | null | undefined;
61
61
  readonly arguments?: string | null | undefined;
62
62
  } | undefined;
63
- readonly index?: number | undefined;
64
63
  readonly id?: string | null | undefined;
64
+ readonly index?: number | undefined;
65
65
  }[] | null | undefined;
66
66
  } | undefined;
67
67
  readonly finish_reason?: string | null | undefined;