@effect-uai/mistral 0.17.0 → 0.19.0-dev-20261109183959

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 (57) hide show
  1. package/README.md +4 -4
  2. package/dist/Mistral.d.mts +1 -1
  3. package/dist/Mistral.d.mts.map +1 -1
  4. package/dist/Mistral.mjs +4 -3
  5. package/dist/Mistral.mjs.map +1 -1
  6. package/dist/MistralRealtimeTranscriber.d.mts +2 -2
  7. package/dist/MistralRealtimeTranscriber.mjs +2 -2
  8. package/dist/MistralRealtimeTranscriber.mjs.map +1 -1
  9. package/dist/MistralSynthesizer.d.mts +1 -1
  10. package/dist/MistralSynthesizer.d.mts.map +1 -1
  11. package/dist/MistralSynthesizer.mjs +5 -4
  12. package/dist/MistralSynthesizer.mjs.map +1 -1
  13. package/dist/MistralTranscriber.d.mts +1 -1
  14. package/dist/MistralTranscriber.mjs +2 -2
  15. package/dist/MistralTranscriber.mjs.map +1 -1
  16. package/dist/audioCodec.d.mts.map +1 -1
  17. package/dist/audioCodec.mjs +3 -2
  18. package/dist/audioCodec.mjs.map +1 -1
  19. package/dist/codec.d.mts +3 -3
  20. package/dist/codec.d.mts.map +1 -1
  21. package/dist/codec.mjs +8 -6
  22. package/dist/codec.mjs.map +1 -1
  23. package/dist/codec.test.mjs +9 -3
  24. package/dist/codec.test.mjs.map +1 -1
  25. package/dist/dist-DHer1cql.mjs +1533 -0
  26. package/dist/dist-DHer1cql.mjs.map +1 -0
  27. package/dist/http.d.mts +1 -1
  28. package/dist/http.mjs.map +1 -1
  29. package/dist/{test.DNmyFkvJ-N3QqMRwZ.mjs → index.C4tKZ0yW-CUBm3Bmz.mjs} +9446 -5818
  30. package/dist/index.C4tKZ0yW-CUBm3Bmz.mjs.map +1 -0
  31. package/dist/onHalt.test.mjs +2 -2
  32. package/dist/onHalt.test.mjs.map +1 -1
  33. package/dist/{realtimeStt-BcthMc57.d.mts → realtimeStt-CcuQ6EZS.d.mts} +7 -1
  34. package/dist/realtimeStt-CcuQ6EZS.d.mts.map +1 -0
  35. package/dist/realtimeStt.d.mts +1 -1
  36. package/dist/realtimeStt.mjs +27 -30
  37. package/dist/realtimeStt.mjs.map +1 -1
  38. package/dist/realtimeStt.test.d.mts +1 -0
  39. package/dist/realtimeStt.test.mjs +317 -0
  40. package/dist/realtimeStt.test.mjs.map +1 -0
  41. package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-EAsCxrDo.mjs} +2 -1
  42. package/package.json +6 -6
  43. package/src/Mistral.ts +3 -1
  44. package/src/MistralRealtimeTranscriber.ts +1 -1
  45. package/src/MistralSynthesizer.ts +4 -13
  46. package/src/MistralTranscriber.ts +1 -1
  47. package/src/audioCodec.ts +3 -2
  48. package/src/codec.test.ts +9 -2
  49. package/src/codec.ts +6 -3
  50. package/src/http.ts +1 -1
  51. package/src/onHalt.test.ts +1 -1
  52. package/src/realtimeStt.test.ts +116 -0
  53. package/src/realtimeStt.ts +68 -64
  54. package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
  55. package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
  56. package/dist/realtimeStt-BcthMc57.d.mts.map +0 -1
  57. package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +0 -1
package/README.md CHANGED
@@ -20,7 +20,7 @@ One package, the whole Mistral surface:
20
20
  pnpm add @effect-uai/mistral @effect-uai/core effect
21
21
  ```
22
22
 
23
- ESM-only. Requires `effect@4.x` and `@effect-uai/core` as peers. The
23
+ ESM-only. Requires `effect@>=4.0.1` and `@effect-uai/core` as peers. The
24
24
  realtime transcriber additionally needs the optional `ws` peer (Node /
25
25
  Bun only).
26
26
 
@@ -30,12 +30,12 @@ Bun only).
30
30
 
31
31
  ```ts
32
32
  import { Config, Effect, Layer } from "effect"
33
- import { FetchHttpClient } from "effect/unstable/http"
33
+ import { FetchHttpClient } from "effect/http"
34
34
  import { layer as mistralLayer } from "@effect-uai/mistral/Mistral"
35
35
 
36
36
  const provider = Layer.unwrap(
37
37
  Effect.gen(function* () {
38
- const apiKey = yield* Config.redacted("MISTRAL_API_KEY")
38
+ const apiKey = yield* Config.Redacted("MISTRAL_API_KEY")
39
39
  return mistralLayer({ apiKey })
40
40
  }),
41
41
  )
@@ -69,7 +69,7 @@ dependency.
69
69
 
70
70
  ## Docs
71
71
 
72
- <https://effect-uai.betalyra.com/providers/mistral/>
72
+ <https://effect-uai.com/providers/mistral/>
73
73
 
74
74
  ## License
75
75
 
@@ -1,6 +1,6 @@
1
1
  import { t as MistralModel } from "./models-gP7kSqeb.mjs";
2
2
  import { Context, Effect, Layer, Redacted, Stream } from "effect";
3
- import { HttpClient } from "effect/unstable/http";
3
+ import { HttpClient } from "effect/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";
@@ -1 +1 @@
1
- {"version":3,"file":"Mistral.d.mts","names":[],"sources":["../src/Mistral.ts"],"mappings":";;;;;;;;;KAgCY,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;;;cA2GE,OAAI,KAAS,WAAS,OAAO,OAAO,uBAAuB,WAAW;;;;;cActE,QAAK,KACX,WACJ,MAAM,MAAM,UAAU,sBAAsB,WAAW"}
1
+ {"version":3,"file":"Mistral.d.mts","names":[],"sources":["../src/Mistral.ts"],"mappings":";;;;;;;;;KAiCY,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;;;cA4GE,OAAI,KAAS,WAAS,OAAO,OAAO,uBAAuB,WAAW;;;;;cActE,QAAK,KACX,WACJ,MAAM,MAAM,UAAU,sBAAsB,WAAW"}
package/dist/Mistral.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, toolChoiceWire, toolsWire } from "./codec.mjs";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
+ import { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, 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";
5
- import { HttpClient, HttpClientRequest } from "effect/unstable/http";
5
+ import { HttpClient, HttpClientRequest } from "effect/http";
6
6
  import * as AiError from "@effect-uai/core/AiError";
7
7
  import * as Capabilities from "@effect-uai/core/Capabilities";
8
8
  import { LanguageModel, turnFromStream } from "@effect-uai/core/LanguageModel";
@@ -53,6 +53,7 @@ const buildStream = (cfg) => {
53
53
  capability: "output_image",
54
54
  reason: "Assistant messages carry no image on this wire. Use `Turn.imagesAsInput` to resend it as user content."
55
55
  });
56
+ yield* rejectToolOutputMedia("mistral", request.history);
56
57
  const httpRequest = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeader("authorization", `Bearer ${Redacted.value(cfg.apiKey)}`), HttpClientRequest.bodyJsonUnsafe(buildRequestBody(request)), HttpClientRequest.accept("text/event-stream"));
57
58
  const response = yield* client.execute(httpRequest).pipe(Effect.mapError((cause) => new AiError.Unavailable({
58
59
  provider: "mistral",
@@ -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 * as Capabilities from \"@effect-uai/core/Capabilities\"\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 yield* Capabilities.warnDroppedBlocks(request.history, \"output_image\", {\n provider: \"mistral\",\n capability: \"output_image\",\n reason:\n \"Assistant messages carry no image on this wire. Use `Turn.imagesAsInput` to resend it as user content.\",\n })\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
+ {"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/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\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 rejectToolOutputMedia,\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 yield* Capabilities.warnDroppedBlocks(request.history, \"output_image\", {\n provider: \"mistral\",\n capability: \"output_image\",\n reason:\n \"Assistant messages carry no image on this wire. Use `Turn.imagesAsInput` to resend it as user content.\",\n })\n yield* rejectToolOutputMedia(\"mistral\", request.history)\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,7 +1,7 @@
1
1
  import { MistralTranscribeRequest, MistralTranscriber, MistralTranscriberService } from "./MistralTranscriber.mjs";
2
- import { t as Config } from "./realtimeStt-BcthMc57.mjs";
2
+ import { t as Config } from "./realtimeStt-CcuQ6EZS.mjs";
3
3
  import { Effect, Layer } from "effect";
4
- import { HttpClient } from "effect/unstable/http";
4
+ import { HttpClient } from "effect/http";
5
5
  import { SttStreaming, Transcriber } from "@effect-uai/core/Transcriber";
6
6
  declare namespace MistralRealtimeTranscriber_d_exports {
7
7
  export { Config, MistralTranscribeRequest, MistralTranscriber, MistralTranscriberService, layer, make };
@@ -1,8 +1,8 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { MistralTranscriber, transcribeImpl } from "./MistralTranscriber.mjs";
3
3
  import { streamTranscription } from "./realtimeStt.mjs";
4
4
  import { Effect, Layer } from "effect";
5
- import { HttpClient } from "effect/unstable/http";
5
+ import { HttpClient } from "effect/http";
6
6
  import { SttStreaming, Transcriber } from "@effect-uai/core/Transcriber";
7
7
  //#region src/MistralRealtimeTranscriber.ts
8
8
  /**
@@ -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(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
+ {"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/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,6 +1,6 @@
1
1
  import { i as MistralTtsModel } from "./models-gP7kSqeb.mjs";
2
2
  import { Context, Effect, Layer, Redacted, Stream } from "effect";
3
- import { HttpClient } from "effect/unstable/http";
3
+ import { HttpClient } from "effect/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";
@@ -1 +1 @@
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
+ {"version":3,"file":"MistralSynthesizer.d.mts","names":[],"sources":["../src/MistralSynthesizer.ts"],"mappings":";;;;;;;;;;;;;;KA0BY,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,10 +1,11 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { httpStatusError, transportFailure } from "./http.mjs";
3
3
  import { containerToTtsFormat, realizedTtsFormat } from "./audioCodec.mjs";
4
- import { Array, Context, Effect, Encoding, Layer, Result, Schema, Stream } from "effect";
5
- import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
+ import { Array, Context, Effect, Layer, Result, Schema, Stream } from "effect";
5
+ import { HttpClient, HttpClientRequest } from "effect/http";
6
6
  import * as AiError from "@effect-uai/core/AiError";
7
7
  import * as Capabilities from "@effect-uai/core/Capabilities";
8
+ import { Base64 } from "effect/encoding";
8
9
  import { SpeechSynthesizer, TtsIncrementalText } from "@effect-uai/core/SpeechSynthesizer";
9
10
  //#region src/MistralSynthesizer.ts
10
11
  var MistralSynthesizer_exports = /* @__PURE__ */ __exportAll({
@@ -50,7 +51,7 @@ const buildBody = (request) => Effect.gen(function* () {
50
51
  const buildHttpRequest = (cfg, body, accept) => HttpClientRequest.post(`${cfg.baseUrl ?? "https://api.mistral.ai"}/v1/audio/speech`).pipe(HttpClientRequest.bearerToken(cfg.apiKey), HttpClientRequest.accept(accept), HttpClientRequest.bodyJsonUnsafe(body));
51
52
  const WireResponse = Schema.Struct({ audio_data: Schema.String });
52
53
  const decodeResponse = Schema.decodeUnknownEffect(WireResponse);
53
- const decodeAudioData = (b64) => Result.match(Encoding.decodeBase64(b64), {
54
+ const decodeAudioData = (b64) => Result.match(Base64.decode(b64), {
54
55
  onSuccess: Effect.succeed,
55
56
  onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
56
57
  provider: "mistral",
@@ -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(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
+ {"version":3,"file":"MistralSynthesizer.mjs","names":["Arr"],"sources":["../src/MistralSynthesizer.ts"],"sourcesContent":["import { Array as Arr, Context, Effect, Layer, Redacted, Result, Schema, Stream } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport { HttpClient, HttpClientRequest } from \"effect/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(Base64.decode(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,6 +1,6 @@
1
1
  import { r as MistralTranscribeModel } from "./models-gP7kSqeb.mjs";
2
2
  import { Context, Effect, Layer, Redacted, Stream } from "effect";
3
- import { HttpClient } from "effect/unstable/http";
3
+ import { HttpClient } from "effect/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";
@@ -1,8 +1,8 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { bodyMultipart, httpStatusError, transportFailure } from "./http.mjs";
3
3
  import { audioToBlob, defaultFileName } from "./audioCodec.mjs";
4
4
  import { Array, Context, Effect, Layer, Schema, Stream } from "effect";
5
- import { HttpClient, HttpClientRequest } from "effect/unstable/http";
5
+ import { HttpClient, HttpClientRequest } from "effect/http";
6
6
  import * as AiError from "@effect-uai/core/AiError";
7
7
  import * as Capabilities from "@effect-uai/core/Capabilities";
8
8
  import { Transcriber } from "@effect-uai/core/Transcriber";
@@ -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 { bodyMultipart, 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 withBody = yield* Effect.flatMap(buildFormData(request), bodyMultipart)\n const httpRequest = HttpClientRequest.post(`${baseUrl}/v1/audio/transcriptions`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n withBody,\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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"}
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/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 { bodyMultipart, 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 withBody = yield* Effect.flatMap(buildFormData(request), bodyMultipart)\n const httpRequest = HttpClientRequest.post(`${baseUrl}/v1/audio/transcriptions`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n withBody,\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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"}
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"audioCodec.d.mts","names":[],"sources":["../src/audioCodec.ts"],"mappings":";;;;;;;;cA4Ba,cAAc,OAAO,gBAAgB,OAAO,OAAO,MAAM,QAAQ;;cAgCjE,kBAAe;KAShB;;;;;cAMC,uBACX,WAAW,6BACR,OAAO,OAAO,kBAAkB,QAAQ;;;;;;cAuBhC,oBAAoB,IAAI,qBAAqB"}
@@ -1,7 +1,8 @@
1
- import { Array, Effect, Encoding, Match, Option, Result } from "effect";
1
+ import { Array, Effect, Match, Option, Result } from "effect";
2
2
  import * as AiError from "@effect-uai/core/AiError";
3
+ import { Base64 } from "effect/encoding";
3
4
  //#region src/audioCodec.ts
4
- const decodeBase64ToBytes = (b64) => Result.match(Encoding.decodeBase64(b64), {
5
+ const decodeBase64ToBytes = (b64) => Result.match(Base64.decode(b64), {
5
6
  onSuccess: Effect.succeed,
6
7
  onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
7
8
  provider: "mistral",
@@ -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(\"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"}
1
+ {"version":3,"file":"audioCodec.mjs","names":["Arr"],"sources":["../src/audioCodec.ts"],"sourcesContent":["import { Array as Arr, Effect, Match, Option, Result } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\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(Base64.decode(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":";;;;AASA,MAAM,uBAAuB,QAC3B,OAAO,MAAM,OAAO,OAAO,GAAG,GAAG;CAC/B,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,8 @@
1
1
  import { Effect } from "effect";
2
- import { Accumulator, WireChunk, WireChunk as WireChunk$1, accumulatorToTurn, applyChunk, emptyAccumulator, responseFormatWire, toolsWire } from "@effect-uai/chat-completions/codec";
2
+ import { Accumulator, WireChunk, WireChunk as WireChunk$1, accumulatorToTurn, applyChunk, emptyAccumulator, rejectToolOutputMedia, responseFormatWire, toolsWire } from "@effect-uai/chat-completions/codec";
3
3
  import { HistoryItem } from "@effect-uai/core/Items";
4
4
  declare namespace codec_d_exports {
5
- export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, toolChoiceWire, toolsWire };
5
+ export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, toolChoiceWire, toolsWire };
6
6
  }
7
7
  type WireMessage = {
8
8
  role: string;
@@ -19,5 +19,5 @@ type ToolChoice = "auto" | "required" | "none" | {
19
19
  declare const toolChoiceWire: (choice: ToolChoice) => string | Record<string, unknown>;
20
20
  declare const decodeChunk: (u: unknown) => Effect.Effect<WireChunk$1, unknown>;
21
21
  //#endregion
22
- export { type Accumulator, type WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, codec_d_exports as t, toolChoiceWire, toolsWire };
22
+ export { type Accumulator, type WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, codec_d_exports as t, toolChoiceWire, toolsWire };
23
23
  //# sourceMappingURL=codec.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;KA4BK;EACH;EACA,kBAAkB,cAAc;EAChC,aAAa,cAAc;EAC3B;;;cAkFW,kBAAe,OAAW,cAAc,iBAAe,cAAc;KAO7E;WAIU;WAA2B;;cAG7B,iBAAc,QAAY,wBAAsB;cAyBhD,cAAW,eAAiB,OAAO,OAAO"}
1
+ {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;KA+BK;EACH;EACA,kBAAkB,cAAc;EAChC,aAAa,cAAc;EAC3B;;;cAkFW,kBAAe,OAAW,cAAc,iBAAe,cAAc;KAO7E;WAIU;WAA2B;;cAG7B,iBAAc,QAAY,wBAAsB;cAyBhD,cAAW,eAAiB,OAAO,OAAO"}
package/dist/codec.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { Array, Effect, Encoding, Match, Result } from "effect";
3
- import { accumulatorToTurn, applyChunk, decodeChunk as decodeChunk$1, emptyAccumulator, responseFormatWire, toolsWire } from "@effect-uai/chat-completions/codec";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
+ import { Array, Effect, Match, Result } from "effect";
3
+ import { Base64 } from "effect/encoding";
4
+ import { accumulatorToTurn, applyChunk, decodeChunk as decodeChunk$1, emptyAccumulator, rejectToolOutputMedia, responseFormatWire, toolOutputText, toolsWire } from "@effect-uai/chat-completions/codec";
4
5
  //#region src/codec.ts
5
6
  var codec_exports = /* @__PURE__ */ __exportAll({
6
7
  accumulatorToTurn: () => accumulatorToTurn,
@@ -8,12 +9,13 @@ var codec_exports = /* @__PURE__ */ __exportAll({
8
9
  decodeChunk: () => decodeChunk,
9
10
  emptyAccumulator: () => emptyAccumulator,
10
11
  itemsToMessages: () => itemsToMessages,
12
+ rejectToolOutputMedia: () => rejectToolOutputMedia,
11
13
  responseFormatWire: () => responseFormatWire,
12
14
  toolChoiceWire: () => toolChoiceWire,
13
15
  toolsWire: () => toolsWire
14
16
  });
15
17
  /** A `data:` URI for inline image bytes; URLs pass through untouched. */
16
- const imageSourceToUrl = Match.type().pipe(Match.tag("url", (s) => s.url), Match.tag("base64", (s) => `data:${s.mimeType};base64,${s.base64}`), Match.tag("bytes", (s) => `data:${s.mimeType};base64,${Encoding.encodeBase64(s.bytes)}`), Match.exhaustive);
18
+ const imageSourceToUrl = Match.type().pipe(Match.tag("url", (s) => s.url), Match.tag("base64", (s) => `data:${s.mimeType};base64,${s.base64}`), Match.tag("bytes", (s) => `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`), Match.exhaustive);
17
19
  const blockToPart = Match.type().pipe(Match.discriminatorsExhaustive("type")({
18
20
  input_text: (b) => ({
19
21
  type: "text",
@@ -74,7 +76,7 @@ const foldItem = (acc, item) => Match.value(item).pipe(Match.discriminatorsExhau
74
76
  function_call_output: (o) => [...acc, {
75
77
  role: "tool",
76
78
  tool_call_id: o.call_id,
77
- content: o.output
79
+ content: toolOutputText(o)
78
80
  }],
79
81
  reasoning: () => acc
80
82
  }));
@@ -93,6 +95,6 @@ const normalizeChunk = (chunk) => chunk.choices === void 0 ? chunk : {
93
95
  };
94
96
  const decodeChunk = (u) => decodeChunk$1(u).pipe(Effect.map(normalizeChunk));
95
97
  //#endregion
96
- export { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, codec_exports as t, toolChoiceWire, toolsWire };
98
+ export { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, codec_exports as t, toolChoiceWire, toolsWire };
97
99
 
98
100
  //# sourceMappingURL=codec.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.mjs","names":["Arr","decodeChunkShared"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option, Result } 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 // No assistant-image part here. Dropped rather than relocated onto a\n // user message, which is the caller's call: `Turn.imagesAsInput`.\n output_image: () => undefined,\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 =>\n b.type === \"input_image\" || b.type === \"output_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)\n ? content.map(textOfBlock).join(\"\")\n : Arr.filterMap(content, (b) => {\n const part = blockToPart(b)\n return part === undefined ? Result.failVoid : Result.succeed(part)\n })\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":"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"}
1
+ {"version":3,"file":"codec.mjs","names":["Arr","decodeChunkShared"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Match, Option, Result } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\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 rejectToolOutputMedia,\n responseFormatWire,\n toolsWire,\n} from \"@effect-uai/chat-completions/codec\"\nimport {\n type WireChunk,\n decodeChunk as decodeChunkShared,\n toolOutputText,\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,${Base64.encode(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 // No assistant-image part here. Dropped rather than relocated onto a\n // user message, which is the caller's call: `Turn.imagesAsInput`.\n output_image: () => undefined,\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 =>\n b.type === \"input_image\" || b.type === \"output_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)\n ? content.map(textOfBlock).join(\"\")\n : Arr.filterMap(content, (b) => {\n const part = blockToPart(b)\n return part === undefined ? Result.failVoid : Result.succeed(part)\n })\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: toolOutputText(o) },\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":"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"}
@@ -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-N3QqMRwZ.mjs";
2
+ import { n as globalExpect, o as describe, s as it } from "./index.C4tKZ0yW-CUBm3Bmz.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", () => {
@@ -27,7 +27,13 @@ describe("itemsToMessages", () => {
27
27
  {
28
28
  type: "function_call_output",
29
29
  call_id: "c1",
30
- output: "ra"
30
+ output: [{
31
+ type: "input_text",
32
+ text: "ra"
33
+ }, {
34
+ type: "input_json",
35
+ value: { n: 1 }
36
+ }]
31
37
  }
32
38
  ])).toEqual([{
33
39
  role: "assistant",
@@ -50,7 +56,7 @@ describe("itemsToMessages", () => {
50
56
  }, {
51
57
  role: "tool",
52
58
  tool_call_id: "c1",
53
- content: "ra"
59
+ content: "ra\n{\"n\":1}"
54
60
  }]);
55
61
  });
56
62
  it("encodes a message with an image as multimodal parts", () => {