@effect-uai/mistral 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/Mistral.d.mts +1 -1
  2. package/dist/Mistral.d.mts.map +1 -1
  3. package/dist/Mistral.mjs +2 -2
  4. package/dist/Mistral.mjs.map +1 -1
  5. package/dist/MistralRealtimeTranscriber.d.mts +1 -1
  6. package/dist/MistralRealtimeTranscriber.mjs +1 -1
  7. package/dist/MistralRealtimeTranscriber.mjs.map +1 -1
  8. package/dist/MistralSynthesizer.d.mts +1 -1
  9. package/dist/MistralSynthesizer.mjs +1 -1
  10. package/dist/MistralSynthesizer.mjs.map +1 -1
  11. package/dist/MistralTranscriber.d.mts +1 -1
  12. package/dist/MistralTranscriber.mjs +1 -1
  13. package/dist/MistralTranscriber.mjs.map +1 -1
  14. package/dist/codec.d.mts +1 -1
  15. package/dist/codec.test.mjs +1 -13630
  16. package/dist/codec.test.mjs.map +1 -1
  17. package/dist/index.d.mts +1 -1
  18. package/dist/{magic-string.es-PAZgLX4h.mjs → magic-string.es-CaWgF-3p.mjs} +1 -1
  19. package/dist/{magic-string.es-PAZgLX4h.mjs.map → magic-string.es-CaWgF-3p.mjs.map} +1 -1
  20. package/dist/{models-DnDmylpj.d.mts → models-B1wZuvz5.d.mts} +1 -1
  21. package/dist/models-B1wZuvz5.d.mts.map +1 -0
  22. package/dist/models.d.mts +1 -1
  23. package/dist/onHalt.test.d.mts +1 -0
  24. package/dist/onHalt.test.mjs +61 -0
  25. package/dist/onHalt.test.mjs.map +1 -0
  26. package/dist/{realtimeStt-ClXWXbXX.d.mts → realtimeStt-BFcctqFC.d.mts} +1 -1
  27. package/dist/{realtimeStt-ClXWXbXX.d.mts.map → realtimeStt-BFcctqFC.d.mts.map} +1 -1
  28. package/dist/realtimeStt.d.mts +1 -1
  29. package/dist/test.DNmyFkvJ-CPKukVWS.mjs +13633 -0
  30. package/dist/test.DNmyFkvJ-CPKukVWS.mjs.map +1 -0
  31. package/package.json +4 -4
  32. package/src/Mistral.ts +7 -2
  33. package/src/MistralRealtimeTranscriber.ts +1 -1
  34. package/src/MistralSynthesizer.ts +1 -1
  35. package/src/MistralTranscriber.ts +1 -1
  36. package/src/onHalt.test.ts +75 -0
  37. package/dist/models-DnDmylpj.d.mts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/mistral",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Mistral provider for @effect-uai/core: chat-completions plus Voxtral speech-to-text and text-to-speech.",
5
5
  "keywords": [
6
6
  "ai",
@@ -60,14 +60,14 @@
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/ws": "^8.5.13",
63
- "effect": "4.0.0-beta.57",
63
+ "effect": "4.0.0-beta.94",
64
64
  "typescript": "^6.0.3",
65
65
  "ws": "^8.18.0",
66
- "@effect-uai/core": "0.9.0"
66
+ "@effect-uai/core": "0.11.0"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@effect-uai/core": ">=0.2.0 <1",
70
- "effect": "4.0.0-beta.57",
70
+ "effect": ">=4.0.0-beta.94 <5.0.0",
71
71
  "ws": "^8.0.0"
72
72
  },
73
73
  "peerDependenciesMeta": {
package/src/Mistral.ts CHANGED
@@ -145,7 +145,12 @@ const buildStream = (cfg: Config) => {
145
145
  Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid,
146
146
  ),
147
147
  Stream.mapAccum((): Accumulator => emptyAccumulator, applyChunk, {
148
- onHalt: (acc) => [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })],
148
+ // `onHalt` also fires on upstream failure and truncated streams,
149
+ // so only emit `TurnComplete` once a `finish_reason` was observed
150
+ onHalt: (acc) =>
151
+ Option.isSome(acc.finishReason)
152
+ ? [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })]
153
+ : [],
149
154
  }),
150
155
  )
151
156
  }),
@@ -158,7 +163,7 @@ const buildStream = (cfg: Config) => {
158
163
 
159
164
  /** Build a `MistralService` value. For Layer-based setup, prefer `layer`. */
160
165
  export const make = (cfg: Config): Effect.Effect<MistralService, never, HttpClient.HttpClient> =>
161
- Effect.map(HttpClient.HttpClient.asEffect(), (client) => {
166
+ Effect.map(HttpClient.HttpClient, (client) => {
162
167
  const streamTurn: MistralService["streamTurn"] = (request) =>
163
168
  buildStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))
164
169
  return {
@@ -34,7 +34,7 @@ export {
34
34
  export const make = (
35
35
  cfg: Config,
36
36
  ): Effect.Effect<MistralTranscriberService, never, HttpClient.HttpClient> =>
37
- Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
37
+ Effect.map(HttpClient.HttpClient, (client) => ({
38
38
  transcribe: (request) =>
39
39
  transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
40
40
  streamTranscriptionFrom: streamTranscription(cfg),
@@ -226,7 +226,7 @@ const streamSynthesizeDialogueUnsupported: SpeechSynthesizerService["streamSynth
226
226
  export const make = (
227
227
  cfg: Config,
228
228
  ): Effect.Effect<MistralSynthesizerService, never, HttpClient.HttpClient> =>
229
- Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
229
+ Effect.map(HttpClient.HttpClient, (client) => ({
230
230
  synthesize: (request) =>
231
231
  synthesizeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
232
232
  streamSynthesis: (request) =>
@@ -194,7 +194,7 @@ const streamUnsupported = <E, R>(
194
194
  export const make = (
195
195
  cfg: Config,
196
196
  ): Effect.Effect<MistralTranscriberService, never, HttpClient.HttpClient> =>
197
- Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
197
+ Effect.map(HttpClient.HttpClient, (client) => ({
198
198
  transcribe: (request) =>
199
199
  transcribeImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
200
200
  streamTranscriptionFrom: streamUnsupported,
@@ -0,0 +1,75 @@
1
+ import { Effect, Exit, Redacted, Stream } from "effect"
2
+ import { HttpClient, HttpClientResponse } from "effect/unstable/http"
3
+ import { describe, expect, it } from "vitest"
4
+ import { TurnEvent } from "@effect-uai/core/Turn"
5
+ import { make as makeMistral } from "./Mistral.js"
6
+
7
+ // An HttpClient backed by a scripted SSE body
8
+ const clientWithBody = (makeBody: () => ReadableStream<Uint8Array>): HttpClient.HttpClient =>
9
+ HttpClient.make((request) =>
10
+ Effect.succeed(
11
+ HttpClientResponse.fromWeb(
12
+ request,
13
+ new Response(makeBody(), {
14
+ status: 200,
15
+ headers: { "content-type": "text/event-stream" },
16
+ }),
17
+ ),
18
+ ),
19
+ )
20
+
21
+ const enc = new TextEncoder()
22
+ const sse = (obj: unknown) => enc.encode(`data: ${JSON.stringify(obj)}\n\n`)
23
+
24
+ const runStream = (client: HttpClient.HttpClient) =>
25
+ Effect.gen(function* () {
26
+ const svc = yield* makeMistral({ apiKey: Redacted.make("k") })
27
+ const events: Array<TurnEvent> = []
28
+ const exit = yield* svc.streamTurn({ model: "mistral-small-latest", history: [] }).pipe(
29
+ Stream.runForEach((e) => Effect.sync(() => events.push(e))),
30
+ Effect.exit,
31
+ )
32
+ return { tags: events.map((e) => e._tag), events, exit }
33
+ }).pipe(Effect.provideService(HttpClient.HttpClient, client))
34
+
35
+ describe("Mistral streamTurn: truncated turn presented as complete", () => {
36
+ it("truncated EOF (no finish_reason) must not synthesize a complete turn", async () => {
37
+ const body = () =>
38
+ new ReadableStream<Uint8Array>({
39
+ start(controller) {
40
+ controller.enqueue(sse({ choices: [{ delta: { content: "Hel" } }] }))
41
+ controller.close() // no finish_reason, no [DONE]
42
+ },
43
+ })
44
+ const { tags } = await Effect.runPromise(runStream(clientWithBody(body)))
45
+ expect(tags).toContain("TextDelta")
46
+ expect(tags).not.toContain("TurnComplete")
47
+ })
48
+
49
+ it("mid-stream error must surface as a failure, not a fake TurnComplete", async () => {
50
+ const body = () =>
51
+ new ReadableStream<Uint8Array>({
52
+ start(controller) {
53
+ controller.enqueue(sse({ choices: [{ delta: { content: "Hel" } }] }))
54
+ controller.error(new Error("connection reset"))
55
+ },
56
+ })
57
+ const { tags, exit } = await Effect.runPromise(runStream(clientWithBody(body)))
58
+ expect(Exit.isFailure(exit)).toBe(true)
59
+ expect(tags).not.toContain("TurnComplete")
60
+ })
61
+
62
+ it("a well-formed stream (finish_reason present) still completes", async () => {
63
+ const body = () =>
64
+ new ReadableStream<Uint8Array>({
65
+ start(controller) {
66
+ controller.enqueue(sse({ choices: [{ delta: { content: "Hel" } }] }))
67
+ controller.enqueue(sse({ choices: [{ delta: {}, finish_reason: "stop" }] }))
68
+ controller.enqueue(enc.encode("data: [DONE]\n\n"))
69
+ controller.close()
70
+ },
71
+ })
72
+ const { tags } = await Effect.runPromise(runStream(clientWithBody(body)))
73
+ expect(tags).toContain("TurnComplete")
74
+ })
75
+ })
@@ -1 +0,0 @@
1
- {"version":3,"file":"models-DnDmylpj.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;AAWA;;;;;AAmBA;;;;;KAnBY,YAAA;;KAmBA,sBAAA;;KAOA,oBAAA;AAMZ;AAAA,KAAY,eAAA"}