@opencode/ai 2.0.16 → 2.0.17

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 (185) hide show
  1. package/README.md +187 -129
  2. package/dist/ai-client.d.ts +8 -0
  3. package/dist/ai-client.js +12 -0
  4. package/dist/experimental/evaluation-client.d.ts +3 -3
  5. package/dist/experimental/evaluation-client.js +1 -1
  6. package/dist/experimental/evaluation.js +1 -1
  7. package/dist/generation.d.ts +6 -10
  8. package/dist/generation.js +17 -20
  9. package/dist/image-client.d.ts +59 -24
  10. package/dist/image-client.js +16 -40
  11. package/dist/image.d.ts +15 -27
  12. package/dist/image.js +4 -15
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.js +1 -0
  15. package/dist/llm.d.ts +7 -5
  16. package/dist/llm.js +10 -4
  17. package/dist/media-client.d.ts +30 -0
  18. package/dist/media-client.js +51 -0
  19. package/dist/media-model.d.ts +9 -10
  20. package/dist/media-model.js +10 -12
  21. package/dist/media.js +2 -2
  22. package/dist/promise.d.ts +68 -30
  23. package/dist/promise.js +52 -31
  24. package/dist/protocols/alibaba-chat.js +4 -1
  25. package/dist/protocols/alibaba-messages.d.ts +1 -1
  26. package/dist/protocols/alibaba-messages.js +6 -4
  27. package/dist/protocols/anthropic-messages.d.ts +34 -34
  28. package/dist/protocols/anthropic-messages.js +14 -8
  29. package/dist/protocols/assemblyai-transcription.d.ts +2 -1
  30. package/dist/protocols/assemblyai-transcription.js +14 -16
  31. package/dist/protocols/bedrock-converse.d.ts +7 -0
  32. package/dist/protocols/bedrock-converse.js +37 -11
  33. package/dist/protocols/bfl-images.d.ts +7 -1
  34. package/dist/protocols/bfl-images.js +36 -36
  35. package/dist/protocols/cartesia-speech.d.ts +18 -2
  36. package/dist/protocols/cartesia-speech.js +10 -16
  37. package/dist/protocols/deepgram-speech.d.ts +19 -3
  38. package/dist/protocols/deepgram-speech.js +11 -12
  39. package/dist/protocols/deepgram-transcription.d.ts +2 -1
  40. package/dist/protocols/deepgram-transcription.js +9 -13
  41. package/dist/protocols/elevenlabs-speech.d.ts +19 -3
  42. package/dist/protocols/elevenlabs-speech.js +9 -13
  43. package/dist/protocols/fal-images.d.ts +2 -1
  44. package/dist/protocols/fal-images.js +30 -40
  45. package/dist/protocols/fal-video.d.ts +2 -2
  46. package/dist/protocols/fal-video.js +9 -24
  47. package/dist/protocols/gemini.d.ts +13 -13
  48. package/dist/protocols/gemini.js +16 -7
  49. package/dist/protocols/google-images.d.ts +3 -3
  50. package/dist/protocols/google-images.js +11 -21
  51. package/dist/protocols/google-speech.d.ts +16 -0
  52. package/dist/protocols/google-speech.js +20 -16
  53. package/dist/protocols/google-transcription.d.ts +2 -1
  54. package/dist/protocols/google-transcription.js +8 -20
  55. package/dist/protocols/google-video.d.ts +2 -2
  56. package/dist/protocols/google-video.js +14 -30
  57. package/dist/protocols/meta-images.d.ts +3 -4
  58. package/dist/protocols/meta-images.js +12 -21
  59. package/dist/protocols/meta-messages.d.ts +5 -5
  60. package/dist/protocols/meta-responses.js +6 -4
  61. package/dist/protocols/open-responses.d.ts +4 -3
  62. package/dist/protocols/open-responses.js +4 -8
  63. package/dist/protocols/openai-chat.js +3 -4
  64. package/dist/protocols/openai-images.d.ts +7 -5
  65. package/dist/protocols/openai-images.js +65 -66
  66. package/dist/protocols/openai-responses.d.ts +10 -10
  67. package/dist/protocols/openai-responses.js +31 -30
  68. package/dist/protocols/openai-speech.d.ts +21 -1
  69. package/dist/protocols/openai-speech.js +11 -12
  70. package/dist/protocols/openai-transcription.d.ts +4 -0
  71. package/dist/protocols/openai-transcription.js +44 -32
  72. package/dist/protocols/replicate-images.js +11 -17
  73. package/dist/protocols/runway-video.d.ts +3 -3
  74. package/dist/protocols/runway-video.js +11 -17
  75. package/dist/protocols/shared.d.ts +11 -17
  76. package/dist/protocols/shared.js +11 -40
  77. package/dist/protocols/stability-images.d.ts +2 -1
  78. package/dist/protocols/stability-images.js +24 -36
  79. package/dist/protocols/utils/fal-queue.d.ts +1 -3
  80. package/dist/protocols/utils/fal-queue.js +4 -6
  81. package/dist/protocols/utils/media-input.d.ts +15 -1
  82. package/dist/protocols/utils/media-input.js +22 -0
  83. package/dist/protocols/utils/responses-checkpoint.js +3 -7
  84. package/dist/protocols/utils/responses-compaction.d.ts +3 -1
  85. package/dist/protocols/utils/responses-compaction.js +16 -3
  86. package/dist/protocols/utils/speech-stream.d.ts +3 -3
  87. package/dist/protocols/utils/speech-stream.js +1 -4
  88. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  89. package/dist/protocols/utils/tool-schema.js +29 -9
  90. package/dist/protocols/xai-images.d.ts +4 -4
  91. package/dist/protocols/xai-images.js +14 -29
  92. package/dist/protocols/xai-responses.js +1 -1
  93. package/dist/protocols/xai-video.js +12 -18
  94. package/dist/protocols/zai-images.d.ts +2 -2
  95. package/dist/protocols/zai-images.js +11 -14
  96. package/dist/protocols/zai-messages.d.ts +1 -1
  97. package/dist/provider-error.js +7 -1
  98. package/dist/providers/alibaba.d.ts +1 -1
  99. package/dist/providers/amazon-bedrock.d.ts +2 -0
  100. package/dist/providers/amazon-bedrock.js +1 -0
  101. package/dist/providers/anthropic-compatible.d.ts +5 -5
  102. package/dist/providers/anthropic.d.ts +5 -5
  103. package/dist/providers/assemblyai.d.ts +1 -1
  104. package/dist/providers/assemblyai.js +5 -10
  105. package/dist/providers/azure.d.ts +4 -4
  106. package/dist/providers/azure.js +2 -2
  107. package/dist/providers/black-forest-labs.d.ts +1 -1
  108. package/dist/providers/black-forest-labs.js +5 -10
  109. package/dist/providers/cartesia.d.ts +1 -1
  110. package/dist/providers/cartesia.js +5 -10
  111. package/dist/providers/cloudflare-ai-gateway.d.ts +14 -14
  112. package/dist/providers/deepgram.d.ts +1 -1
  113. package/dist/providers/deepgram.js +6 -12
  114. package/dist/providers/elevenlabs.d.ts +1 -1
  115. package/dist/providers/elevenlabs.js +5 -10
  116. package/dist/providers/fal.d.ts +1 -1
  117. package/dist/providers/fal.js +5 -12
  118. package/dist/providers/google-vertex-messages.d.ts +5 -5
  119. package/dist/providers/google-vertex.d.ts +3 -3
  120. package/dist/providers/google.d.ts +3 -3
  121. package/dist/providers/google.js +7 -12
  122. package/dist/providers/meta.d.ts +8 -8
  123. package/dist/providers/meta.js +4 -8
  124. package/dist/providers/minimax.d.ts +5 -5
  125. package/dist/providers/moonshot.d.ts +5 -5
  126. package/dist/providers/openai-options.d.ts +3 -9
  127. package/dist/providers/openai-options.js +4 -7
  128. package/dist/providers/openai.d.ts +9 -10
  129. package/dist/providers/openai.js +11 -12
  130. package/dist/providers/opencode-zen.js +1 -1
  131. package/dist/providers/openrouter.d.ts +6 -7
  132. package/dist/providers/openrouter.js +10 -5
  133. package/dist/providers/replicate.d.ts +1 -1
  134. package/dist/providers/replicate.js +5 -10
  135. package/dist/providers/runway.d.ts +1 -1
  136. package/dist/providers/runway.js +5 -10
  137. package/dist/providers/stability.d.ts +1 -1
  138. package/dist/providers/stability.js +6 -11
  139. package/dist/providers/typesafe-ai.js +1 -1
  140. package/dist/providers/vercel-ai-gateway.js +1 -1
  141. package/dist/providers/xai.js +5 -10
  142. package/dist/providers/zai-coding-plan.d.ts +1 -1
  143. package/dist/providers/zai.js +4 -8
  144. package/dist/route/auth.d.ts +1 -1
  145. package/dist/route/auth.js +14 -13
  146. package/dist/route/client.d.ts +9 -7
  147. package/dist/route/client.js +6 -8
  148. package/dist/route/endpoint.d.ts +1 -0
  149. package/dist/route/endpoint.js +2 -2
  150. package/dist/route/executor-service.d.ts +4 -2
  151. package/dist/route/executor-service.js +2 -1
  152. package/dist/route/executor.d.ts +3 -1
  153. package/dist/route/executor.js +7 -0
  154. package/dist/route/framing.d.ts +10 -1
  155. package/dist/route/framing.js +45 -5
  156. package/dist/route/index.d.ts +1 -1
  157. package/dist/route/media-protocol.d.ts +49 -35
  158. package/dist/route/media-protocol.js +77 -65
  159. package/dist/route/media.d.ts +10 -16
  160. package/dist/route/media.js +46 -62
  161. package/dist/route/protocol.d.ts +3 -1
  162. package/dist/schema/options.d.ts +6 -3
  163. package/dist/schema/options.js +7 -3
  164. package/dist/speech-client.d.ts +62 -17
  165. package/dist/speech-client.js +17 -21
  166. package/dist/speech.d.ts +178 -21
  167. package/dist/speech.js +13 -8
  168. package/dist/testing.d.ts +2 -2
  169. package/dist/transcription-client.d.ts +78 -24
  170. package/dist/transcription-client.js +9 -40
  171. package/dist/transcription.d.ts +15 -27
  172. package/dist/transcription.js +4 -10
  173. package/dist/utils/json.d.ts +4 -0
  174. package/dist/utils/json.js +4 -0
  175. package/dist/utils/media-type.d.ts +2 -1
  176. package/dist/utils/media-type.js +3 -1
  177. package/dist/video-client.d.ts +55 -24
  178. package/dist/video-client.js +16 -36
  179. package/dist/video.d.ts +18 -26
  180. package/dist/video.js +15 -16
  181. package/package.json +3 -3
  182. package/dist/protocols/utils/meta-image.d.ts +0 -2
  183. package/dist/protocols/utils/meta-image.js +0 -13
  184. package/dist/protocols/utils/openai-image.d.ts +0 -5
  185. package/dist/protocols/utils/openai-image.js +0 -18
package/README.md CHANGED
@@ -1,40 +1,38 @@
1
1
  # @opencode/ai
2
2
 
3
- Schema-first language model and image-generation APIs built with Effect.
3
+ Schema-first APIs for text, images, video, speech, and transcription, built with Effect.
4
4
 
5
5
  ```ts
6
- import { Effect, Layer } from "effect"
7
- import { LLM, LLMClient } from "@opencode/ai"
8
- import { RequestExecutor } from "@opencode/ai/route"
6
+ import { Effect } from "effect"
7
+ import { AIClient, LLM } from "@opencode/ai"
9
8
  import { OpenAI } from "@opencode/ai/providers"
10
9
 
11
10
  const openai = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY })
12
11
 
13
- const request = LLM.request({
14
- model: openai.responses("gpt-4o-mini"), // `.chat(...)` selects the Chat Completions API instead
15
- system: "You are concise.",
16
- prompt: "Say hello in one short sentence.",
17
- generation: { maxTokens: 40 },
18
- })
19
-
20
12
  const program = Effect.gen(function* () {
21
- const response = yield* LLMClient.generate(request)
13
+ const response = yield* LLM.generate({
14
+ model: openai.responses("gpt-4o-mini"), // `.chat(...)` selects the Chat Completions API instead
15
+ system: "You are concise.",
16
+ prompt: "Say hello in one short sentence.",
17
+ generation: { maxTokens: 40 },
18
+ })
22
19
  console.log(response.text)
23
20
  })
24
21
 
25
- const llmLayer = LLMClient.layer.pipe(Layer.provide(RequestExecutor.fetchLayer))
26
-
27
- await Effect.runPromise(program.pipe(Effect.provide(llmLayer)))
22
+ // Every modality client plus the HTTP request executor; `AIClient.layerWith(executor)` swaps the executor.
23
+ await Effect.runPromise(program.pipe(Effect.provide(AIClient.layer)))
28
24
  ```
29
25
 
30
- Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
26
+ Run `LLM.stream(...)` instead of `generate` when you want incremental `LLMEvent`s. Both accept input or a prebuilt
27
+ `LLM.request(...)`. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses,
28
+ Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
31
29
 
32
- The same configured facade names image models. `Image.request` resolves the provider's image route from the ref and
33
- returns `Media.Asset`s with lazily decoded bytes:
30
+ The same configured facade names image, video, speech, and transcription models. `Image.generate` resolves the
31
+ provider's image route from the model and returns `Media.Asset`s with lazily decoded bytes:
34
32
 
35
33
  ```ts
36
34
  import { NodeFileSystem } from "@effect/platform-node"
37
- import { Image, ImageClient, Media } from "@opencode/ai"
35
+ import { Image, Media } from "@opencode/ai"
38
36
 
39
37
  const image = Effect.gen(function* () {
40
38
  const response = yield* Image.generate({
@@ -46,21 +44,38 @@ const image = Effect.gen(function* () {
46
44
  yield* Media.write(response.image, "./garden.png")
47
45
  })
48
46
 
49
- // `asset.bytes()` / `Media.write` also need the executor, so merge it into the environment instead of hiding it.
50
- const imageLayer = ImageClient.layer.pipe(Layer.provideMerge(RequestExecutor.fetchLayer))
47
+ // `Media.file` / `Media.write` use the Effect `FileSystem` service; provide your platform's layer.
48
+ await Effect.runPromise(image.pipe(Effect.provide(AIClient.layer), Effect.provide(NodeFileSystem.layer)))
49
+ ```
50
+
51
+ Advanced: each client also has its own `layer`, which requires `RequestExecutor.Service`. Compose client layers with
52
+ `Layer.provideMerge`, not `Layer.provide`: `asset.bytes()`, `Media.write`, and Gemini's `media` output parts need the
53
+ executor too, and hiding it fails type-checking with `RequestExecutorService` left in the requirements.
54
+
55
+ To share a policy such as logging across every client, wrap the executor once with `RequestExecutor.middleware`:
56
+
57
+ ```ts
58
+ import { RequestExecutor } from "@opencode/ai/route"
59
+
60
+ const logged = RequestExecutor.middleware((request, next) =>
61
+ Effect.log(`${request.method} ${request.url}`).pipe(Effect.andThen(next(request))),
62
+ )
51
63
 
52
- await Effect.runPromise(image.pipe(Effect.provide(imageLayer), Effect.provide(NodeFileSystem.layer)))
64
+ const everything = AIClient.layerWith(logged) // or AI.make({ layer: logged })
53
65
  ```
54
66
 
55
- Prefer promises? `@opencode/ai/promise` exposes the same LLM and image APIs over one managed runtime:
67
+ Prefer promises? `@opencode/ai/promise` exposes the same LLM and media APIs over one managed runtime, plus asset
68
+ helpers; `ai.file` and `ai.write` load `node:fs/promises` on first use, so no Effect `FileSystem` is needed:
56
69
 
57
70
  ```ts
58
71
  import { AI } from "@opencode/ai/promise"
59
72
 
60
73
  const ai = AI.make()
61
- const text = await ai.llm.generate({ model: openai.responses("gpt-4o-mini"), prompt: "Say hello." })
74
+ const input = { model: openai.responses("gpt-4o-mini"), prompt: "Say hello." }
75
+ const text = await ai.llm.generate(input)
62
76
  const generated = await ai.image.generate({ model: openai.image("gpt-image-2"), prompt: "A lighthouse" })
63
- for await (const event of ai.llm.stream({ model: openai.responses("gpt-4o-mini"), prompt: "Stream hello." })) {
77
+ await ai.write(generated.image, "./lighthouse.png") // also ai.file(path), ai.bytes(asset), ai.base64(asset), ai.materialize(asset)
78
+ for await (const event of ai.llm.stream(ai.llm.request(input))) {
64
79
  // LLMEvent
65
80
  }
66
81
  await ai.dispose()
@@ -322,10 +337,9 @@ and `moonshot/responses`; each exports `model(modelID, settings)`.
322
337
  MiniMax defaults to its Messages API and reads `MINIMAX_API_KEY` when `apiKey` is omitted:
323
338
 
324
339
  ```ts
325
- import { Effect, Layer } from "effect"
326
- import { LLM, LLMClient } from "@opencode/ai"
340
+ import { Effect } from "effect"
341
+ import { AIClient, LLM } from "@opencode/ai"
327
342
  import { MiniMax } from "@opencode/ai/providers"
328
- import { RequestExecutor } from "@opencode/ai/route"
329
343
 
330
344
  const minimax = MiniMax.configure({ apiKey: process.env.MINIMAX_API_KEY })
331
345
  const request = LLM.request({
@@ -335,8 +349,7 @@ const request = LLM.request({
335
349
  generation: { maxTokens: 1536 },
336
350
  })
337
351
 
338
- const layer = LLMClient.layer.pipe(Layer.provide(RequestExecutor.fetchLayer))
339
- const response = await Effect.runPromise(LLMClient.generate(request).pipe(Effect.provide(layer)))
352
+ const response = await Effect.runPromise(LLM.generate(request).pipe(Effect.provide(AIClient.layer)))
340
353
  console.log(response.text)
341
354
  ```
342
355
 
@@ -405,14 +418,14 @@ Use `Image.generate` for one-off generation or editing:
405
418
  import { Image, Media } from "@opencode/ai"
406
419
 
407
420
  const generation = Image.generate({
408
- model: meta("muse-image-1.0"),
421
+ model: meta.image("muse-image-1.0"),
409
422
  prompt: "A flat black square on a white background.",
410
423
  n: 1,
411
424
  providerOptions: { reasoningStrength: "low" },
412
425
  })
413
426
 
414
427
  const edit = Image.generate({
415
- model: meta("muse-image-1.0"),
428
+ model: meta.image("muse-image-1.0"),
416
429
  prompt: "Make the square purple.",
417
430
  images: [Media.bytes(imageBytes, "image/webp")],
418
431
  format: "png",
@@ -459,6 +472,25 @@ const program = Effect.gen(function* () {
459
472
  })
460
473
  ```
461
474
 
475
+ Common fields are portable in shape, not in support. Unsupported fields fail with a typed `AIError` before any network
476
+ call rather than being dropped, so check this table before swapping only the `model`:
477
+
478
+ | Provider | `n` | `size` | `aspectRatio` | `seed` | `format` | `images` | `mask` |
479
+ | --------------------- | --- | --------- | ------------- | ------ | -------- | -------------------------------- | ------------------- |
480
+ | OpenAI | ✓¹ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ |
481
+ | Google (Gemini) | 1 | ✗ | ✓ | ✓ | ✗ | ✓ (no public URLs) | ✗ |
482
+ | xAI | ✓ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ |
483
+ | Z.ai | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
484
+ | Meta | ✓ | ✓ (hint) | ✗ | ✗ | ✓ | ✓ | ✗ |
485
+ | Black Forest Labs | 1 | per model | per model | ✓ | ✓ | per model (1–8) | `flux-pro-1.0-fill` |
486
+ | fal | ✓ | per model | per model | ✓ | ✓ | 1 (several on `/edit`, `/multi`) | ✓ |
487
+ | Replicate | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ (use `providerOptions`) | ✗ |
488
+ | Stability `image` | 1 | ✗ | ✓ | ✓ | ✓ | 1 (not on `core`) | ✗ |
489
+ | Stability `upscale()` | ✗ | ✗ | ✗ | ✓ | ✓ | exactly 1 (required) | ✗ |
490
+
491
+ ✓ lowers natively; ✗ fails whenever the field is set (including `n: 1`); `1` means `n > 1` fails. ¹ `Image.stream` on OpenAI generates one image. fal
492
+ rejects `size` and `aspectRatio` together; which one a fal or BFL model takes depends on the model.
493
+
462
494
  `Media.Asset` is the one asset type shared by image requests, image responses, LLM messages, and tool results.
463
495
  `asset.source` is the serializable `Media.Source` (`bytes`, `base64`, `url`, or `ref`); `asset.bytes()`,
464
496
  `asset.base64()`, and `asset.dataUrl()` decode or download lazily and cache; `asset.materialize()` pulls a `url`
@@ -468,9 +500,8 @@ asset into owned bytes before the provider URL expires. Construct assets with `M
468
500
  Pass ordered image inputs to the same method for editing, composition, or image-conditioned generation:
469
501
 
470
502
  ```ts
471
- const response =
472
- yield *
473
- Image.generate({
503
+ const composed = Effect.gen(function* () {
504
+ const response = yield* Image.generate({
474
505
  model,
475
506
  prompt: "Combine these product photos into one studio scene",
476
507
  images: [
@@ -481,23 +512,25 @@ const response =
481
512
  providerOptions,
482
513
  http,
483
514
  })
515
+ return response.images
516
+ })
484
517
  ```
485
518
 
486
519
  `Media.ref(provider, id)` represents provider file handles such as OpenAI file IDs or Gemini Files URIs; routes
487
- only forward refs that belong to their own provider. Raw strings are not accepted as image inputs, avoiding
488
- ambiguity between base64, URLs, and provider IDs. Empty or omitted `images` uses text-to-image generation; a
489
- non-empty array selects the provider's edit behavior without enforcing provider image-count limits locally. OpenAI
520
+ only forward refs that belong to their own provider (OpenAI, xAI, and Gemini images accept them). No shipped route
521
+ returns a ref yet, and `asset.bytes()` / `materialize()` on a ref fail by design. Raw strings are not accepted as
522
+ image inputs, avoiding ambiguity between base64, URLs, and provider IDs. Empty or omitted `images` uses text-to-image generation; a
523
+ non-empty array selects the provider's edit behavior (see the table above for routes that limit the count). OpenAI
490
524
  uses multipart for byte/data-URL edits and its JSON reference body for URL or file-ID edits. The common `mask`
491
525
  field selects inpainting; routes that cannot honor it fail with `UnsupportedOperation`:
492
526
 
493
527
  ```ts
494
- yield *
495
- Image.generate({
496
- model: openai.image("gpt-image-2"),
497
- prompt,
498
- images: [Media.bytes(sourceBytes, "image/png")],
499
- mask: Media.bytes(maskBytes, "image/png"),
500
- })
528
+ const inpainted = Image.generate({
529
+ model: openai.image("gpt-image-2"),
530
+ prompt,
531
+ images: [Media.bytes(sourceBytes, "image/png")],
532
+ mask: Media.bytes(maskBytes, "image/png"),
533
+ })
501
534
  ```
502
535
 
503
536
  On multipart requests, `http.body` can override option fields but not structural `model`, `prompt`, `image[]`,
@@ -508,31 +541,31 @@ not accept image inputs. These cases fail with a typed `AIError` before network
508
541
  Provider-native image options belong to each request. Raw `http.body` fields have final precedence over them:
509
542
 
510
543
  ```ts
511
- yield *
512
- Image.generate({
513
- model: openai.image("gpt-image-2"),
514
- prompt,
515
- providerOptions: { quality: "medium" },
516
- http,
517
- })
544
+ const medium = Image.generate({
545
+ model: openai.image("gpt-image-2"),
546
+ prompt,
547
+ providerOptions: { quality: "medium" },
548
+ http,
549
+ })
518
550
  ```
519
551
 
520
552
  xAI image models use the same request API with xAI-native controls:
521
553
 
522
554
  ```ts
523
- yield *
524
- Image.generate({
525
- model: XAI.configure({ apiKey })("any-model-id"),
526
- prompt,
527
- n: 2,
528
- aspectRatio: "16:9",
529
- providerOptions: {
530
- resolution: "1k",
531
- responseFormat: "b64_json",
532
- future_option: true,
533
- },
534
- http,
535
- })
555
+ import { XAI } from "@opencode/ai/providers"
556
+
557
+ const xai = Image.generate({
558
+ model: XAI.configure({ apiKey }).image("any-model-id"),
559
+ prompt,
560
+ n: 2,
561
+ aspectRatio: "16:9",
562
+ providerOptions: {
563
+ resolution: "1k",
564
+ responseFormat: "b64_json",
565
+ future_option: true,
566
+ },
567
+ http,
568
+ })
536
569
  ```
537
570
 
538
571
  Google's current Gemini image models use the same direct API:
@@ -542,7 +575,7 @@ import { Google } from "@opencode/ai/providers"
542
575
 
543
576
  const googleProgram = Effect.gen(function* () {
544
577
  const response = yield* Image.generate({
545
- model: Google.configure({ apiKey })("any-model-id"),
578
+ model: Google.configure({ apiKey }).image("any-model-id"),
546
579
  prompt: "A robot tending a rooftop garden",
547
580
  aspectRatio: "16:9",
548
581
  seed: 42,
@@ -567,17 +600,18 @@ their mapped aliases, and `http.body` is the final deep overlay. The selected mo
567
600
  Z.ai image models infer open Z.ai-native options from the selected model:
568
601
 
569
602
  ```ts
570
- yield *
571
- Image.generate({
572
- model: ZAI.configure({ apiKey })("any-model-id"),
573
- prompt,
574
- providerOptions: {
575
- quality: "hd",
576
- userID: "user-123",
577
- future_option: true,
578
- },
579
- http,
580
- })
603
+ import { ZAI } from "@opencode/ai/providers"
604
+
605
+ const zai = Image.generate({
606
+ model: ZAI.configure({ apiKey }).image("any-model-id"),
607
+ prompt,
608
+ providerOptions: {
609
+ quality: "hd",
610
+ userID: "user-123",
611
+ future_option: true,
612
+ },
613
+ http,
614
+ })
581
615
  ```
582
616
 
583
617
  Z.ai does not include trustworthy MIME metadata for output URLs, so generated images use
@@ -587,16 +621,17 @@ persist the bytes promptly if they must remain available.
587
621
  ### Partial images
588
622
 
589
623
  OpenAI's GPT image models stream previews. `Image.stream` sends `stream: true` with `partialImages` (0–3, default 2)
590
- and emits `image-partial` events before each final `image`; `Image.generate` keeps the plain JSON request.
591
- `dall-e-*` models do not stream and fail typed:
624
+ and emits `image-partial` events before each final `image`; `Image.generate` keeps the plain JSON request:
592
625
 
593
626
  ```ts
594
- yield *
595
- Image.stream({
596
- model: openai.image("gpt-image-2"),
597
- prompt: "A lighthouse at dusk",
598
- providerOptions: { partialImages: 2 },
599
- }).pipe(Stream.runForEach((event) => (ImageEvent.is.imagePartial(event) ? showPreview(event.image) : Effect.void)))
627
+ import { Stream } from "effect"
628
+ import { ImageEvent } from "@opencode/ai"
629
+
630
+ const previews = Image.stream({
631
+ model: openai.image("gpt-image-2"),
632
+ prompt: "A lighthouse at dusk",
633
+ providerOptions: { partialImages: 2 },
634
+ }).pipe(Stream.runForEach((event) => (ImageEvent.is.imagePartial(event) ? showPreview(event.image) : Effect.void)))
600
635
  ```
601
636
 
602
637
  The provider may send fewer previews than requested when the final image is ready first.
@@ -612,13 +647,20 @@ import { BlackForestLabs, Stability } from "@opencode/ai/providers"
612
647
 
613
648
  const bfl = BlackForestLabs.configure({ apiKey: process.env.BFL_API_KEY })
614
649
 
615
- const generation = yield * Image.start({ model: bfl.image("flux-2-pro"), prompt, size: "1024x768" })
616
- persist(generation.token)
650
+ const submit = Effect.gen(function* () {
651
+ const generation = yield* Image.start({ model: bfl.image("flux-2-pro"), prompt, size: "1024x768" })
652
+ persist({ provider: "black-forest-labs", modelID: "flux-2-pro", token: generation.token })
653
+ })
617
654
 
618
- const resumed = yield * Image.resume(bfl.image("flux-2-pro"), loadToken())
619
- const response = yield * resumed.await({ poll: { interval: "2 seconds" } })
655
+ const finish = Effect.gen(function* () {
656
+ const saved = load()
657
+ const resumed = yield* Image.resume(bfl.image(saved.modelID), saved.token)
658
+ return yield* resumed.await({ poll: { interval: "2 seconds" } })
659
+ })
620
660
  ```
621
661
 
662
+ The token carries no route identity, so persist the provider and model ID alongside it: `resume` needs the model.
663
+
622
664
  - **Black Forest Labs** — results are downloaded before returning, because `result.sample` expires in 10 minutes.
623
665
  - **Replicate** — inputs are model-defined, so only `prompt` lowers: sizing, count, seed, format, and files go in
624
666
  `providerOptions` under the model's names, with files as `Media.Asset` (data URLs up to 256 KB, larger by URL).
@@ -628,12 +670,13 @@ const response = yield * resumed.await({ poll: { interval: "2 seconds" } })
628
670
 
629
671
  ```ts
630
672
  const stability = Stability.configure({ apiKey: process.env.STABILITY_API_KEY })
631
- const upscaled =
632
- yield *
633
- Image.generate(
634
- { model: stability.upscale(), prompt: "A lighthouse", images: [yield * Media.file("./small.png")] },
673
+ const upscaled = Effect.gen(function* () {
674
+ const small = yield* Media.file("./small.png")
675
+ return yield* Image.generate(
676
+ { model: stability.upscale(), prompt: "A lighthouse", images: [small] },
635
677
  { poll: { interval: "5 seconds" } },
636
678
  )
679
+ })
637
680
  ```
638
681
 
639
682
  Imagen is not available: Google shut it down on the Gemini API, and Vertex discontinued the Imagen 4 models on
@@ -655,7 +698,7 @@ const program = Effect.gen(function* () {
655
698
  })
656
699
  ```
657
700
 
658
- The hosted result is represented as a provider-executed tool call and tool result, and the generated image is also emitted as a first-class `media` `LLMEvent` (`response.message` then carries a `media` part). Gemini image-capable models emit the same `media` event for inline image output. Retaining `response.message` preserves the generated image for continuation on both routes.
701
+ The hosted result is represented as a provider-executed tool call and a tool result whose content carries the generated image as a file. Gemini image-capable models instead emit a first-class `media` `LLMEvent` for inline image output (`response.message` then carries a `media` part). Retaining `response.message` preserves the generated image for continuation on both routes.
659
702
 
660
703
  ## Video generation
661
704
 
@@ -666,8 +709,8 @@ Common fields (`frames`, `references`, `video`, `durationSeconds`, `aspectRatio`
666
709
  under `providerOptions`, inferred from the selected model.
667
710
 
668
711
  ```ts
669
- import { Video, VideoClient } from "@opencode/ai"
670
- import { Google } from "@opencode/ai/providers"
712
+ import { Video } from "@opencode/ai"
713
+ import { Google, Runway } from "@opencode/ai/providers"
671
714
 
672
715
  const google = Google.configure({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY })
673
716
 
@@ -696,6 +739,7 @@ const controlled = Effect.gen(function* () {
696
739
  generation.id // provider operation / task / request id
697
740
  generation.status // "queued" | "running" | "completed" | "failed" | "cancelled" | "expired"
698
741
  generation.token // route-owned JSON: `{ operation }`, `{ requestID }`, `{ taskID }`, or fal's follow-up URLs
742
+ // The token carries no route identity: persist the provider and model ID alongside it, since `resume` needs the model.
699
743
  const saved = JSON.stringify(generation.token)
700
744
 
701
745
  const resumed = yield* Video.resume(google.video("veo-3.1-generate-preview"), JSON.parse(saved))
@@ -706,8 +750,8 @@ const controlled = Effect.gen(function* () {
706
750
  const events = Video.stream({ model: Runway.configure({ apiKey }).video("gen4.5"), prompt }, { poll })
707
751
  ```
708
752
 
709
- `VideoClient.layer` needs `RequestExecutor.Service`, and status polls, result fetches, cancels, and asset downloads
710
- all run through the same executor with the route's auth. `Generation.await` and `Generation.events` fail with a
753
+ Status polls, result fetches, cancels, and asset downloads all run through the same request executor with the route's
754
+ auth. `Generation.await` and `Generation.events` fail with a
711
755
  `Timeout` reason when `poll.timeout` (default 10 minutes) elapses. Failed,
712
756
  cancelled, and expired generations fail typed with the provider's terminal document on `reason.body`; moderation
713
757
  outcomes (Veo `raiMediaFilteredReasons`, xAI `respect_moderation`, Runway `SAFETY.*` codes) surface as `notices` when
@@ -726,15 +770,18 @@ Provider notes:
726
770
  - **Runway** expects pixel ratios in `aspectRatio` for most models (`"1280:720"`), pins `X-Runway-Version`, reports
727
771
  `usage: { type: "credits" }`, and its output URLs expire after 24–48 hours.
728
772
 
729
- The promise client exposes the same surface: `ai.video.start(...)` resolves to a handle with `await`, `refresh`,
730
- `cancel`, and `token`; `ai.video.generate`, `ai.video.resume(model, token)`, and `ai.video.stream` mirror the Effect
731
- API.
773
+ The promise client exposes the same surface: `ai.video.start(...)` resolves to a handle with `await`, `events`,
774
+ `result`, `refresh`, `cancel`, and `token`; `ai.video.generate`, `ai.video.resume(model, token)`, and
775
+ `ai.video.stream` mirror the Effect API. The handle's `status` and `progress` are a snapshot from when it was
776
+ created; `refresh()` resolves to a new handle.
732
777
 
733
778
  ```ts
734
779
  import { ai } from "@opencode/ai/promise"
735
780
 
736
781
  const generation = await ai.video.start({ model, prompt })
737
- const video = await generation.await({ poll: { interval: 10_000 }, signal })
782
+ for await (const event of generation.events({ poll: { interval: 10_000 } })) console.log(event.type)
783
+ const video = await generation.result({ signal })
784
+ await ai.write(video.video, "./kite.mp4")
738
785
  ```
739
786
 
740
787
  ## Speech generation
@@ -786,16 +833,16 @@ ElevenLabs and Cartesia. `{ id }` selects an OpenAI custom voice (`{ id: "voice_
786
833
  plain string elsewhere. There is no cross-provider voice catalog. `format` is the container-level word (`mp3`, `wav`,
787
834
  `pcm`, `opus`, `aac`, `flac`); sample rates and bitrates live under `providerOptions`, and a value the route cannot
788
835
  produce fails as `UnsupportedOperation`. Streams buffer every chunk so `finish` can carry the whole clip.
789
- `SpeechClient.layer` needs `RequestExecutor.Service`.
790
836
 
791
837
  Provider notes:
792
838
 
793
839
  - **OpenAI** streams over SSE (`stream_format: "sse"`), which is also the only place it reports token usage; `tts-1`
794
840
  and `tts-1-hd` do not support SSE and stream the raw audio body instead. `pcm` is 24 kHz 16-bit mono. `language`
795
841
  and `timestamps` are not supported.
796
- - **Gemini TTS** returns raw 16-bit PCM only (`audio/L16;codec=pcm;rate=24000`), so any `format` other than `pcm`
797
- fails typed; wrap the samples yourself. Style is directed in the text, so `instructions` and `speed` fail typed.
798
- Only `gemini-3.1-flash-tts-preview` and later support streaming. Two-speaker audio goes through
842
+ - **Gemini TTS** returns the provider's default output: WAV for Gemini 3.8 TTS `generate`, raw 16-bit PCM
843
+ (`audio/L16;codec=pcm;rate=24000`) otherwise. `pcm` is the only explicit `format` it accepts, and it fails typed on
844
+ Gemini 3.8 `generate`; the route never wraps PCM as WAV. Style is directed in the text, so `instructions` and
845
+ `speed` fail typed. Only `gemini-3.1-flash-tts-preview` and later support streaming. Two-speaker audio goes through
799
846
  `providerOptions.speechConfig.multiSpeakerVoiceConfig`.
800
847
  - **ElevenLabs** requires `voice` (the path voice id) and authenticates with `xi-api-key`. `format` maps to the
801
848
  `output_format` query parameter (`mp3_44100_128`, `pcm_24000`, `wav_24000`, `opus_48000_64`);
@@ -814,7 +861,7 @@ The promise client mirrors the Effect API; `ai.speech.stream` is an `AsyncIterab
814
861
  import { ai } from "@opencode/ai/promise"
815
862
 
816
863
  const response = await ai.speech.generate({ model, text: "Hello from OpenCode.", voice: "coral" })
817
- await Bun.write("hello.mp3", await ai.run(response.audio.bytes()))
864
+ await ai.write(response.audio, "hello.mp3")
818
865
 
819
866
  for await (const event of ai.speech.stream({ model, text: "Hello from OpenCode.", voice: "coral" })) {
820
867
  if (event.type === "audio-delta") player.write(event.chunk)
@@ -831,6 +878,7 @@ facades. Common fields (`language`, `prompt`, `timestamps: "none" | "segment" |
831
878
  natively or fail with a typed `AIError` before any network call; a route may return more than asked.
832
879
 
833
880
  ```ts
881
+ import { Console, Effect, Stream } from "effect"
834
882
  import { Media, Transcription, TranscriptionEvent } from "@opencode/ai"
835
883
  import { AssemblyAI, Deepgram, OpenAI } from "@opencode/ai/providers"
836
884
 
@@ -857,7 +905,7 @@ const program = Effect.gen(function* () {
857
905
  Stream.runDrain,
858
906
  )
859
907
 
860
- // Queued: persist the token, resume from another process, and await.
908
+ // Queued: persist the token with the provider and model ID (the token alone cannot pick the model), resume, and await.
861
909
  const model = AssemblyAI.configure({ apiKey }).transcription("universal-3-5-pro")
862
910
  const generation = yield* Transcription.start({ model, audio })
863
911
  const resumed = yield* Transcription.resume(model, JSON.parse(JSON.stringify(generation.token)))
@@ -866,7 +914,7 @@ const program = Effect.gen(function* () {
866
914
  ```
867
915
 
868
916
  Inline routes emit only `finish` from `stream` (no faked deltas); queued routes emit `generation-queued` /
869
- `generation-progress` before it. `TranscriptionClient.layer` needs `RequestExecutor.Service`.
917
+ `generation-progress` before it.
870
918
 
871
919
  Provider notes:
872
920
 
@@ -878,6 +926,7 @@ Provider notes:
878
926
  The promise client mirrors the Effect API:
879
927
 
880
928
  ```ts
929
+ const audio = await ai.file("./call.mp3")
881
930
  const text = (await ai.transcription.generate({ model, audio })).text
882
931
  for await (const event of ai.transcription.stream({ model, audio })) if (event.type === "text-delta") write(event.delta)
883
932
  const generation = await ai.transcription.start({ model: assemblyai, audio })
@@ -887,7 +936,7 @@ const transcript = await generation.await({ poll: { interval: 3_000 } })
887
936
  ## Public API
888
937
 
889
938
  - **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
890
- - **`LLM.generate` / `LLM.stream`** — re-exported from `LLMClient` for one-import use.
939
+ - **`LLM.generate` / `LLM.stream`** — run direct input or an `LLMRequest` through `LLMClient` for one-import use.
891
940
  - **`Message.user(...)` / `Message.assistant(...)` / `Message.tool(...)`** — message constructors from the canonical schema model.
892
941
  - **`LanguageModel.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
893
942
  - **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
@@ -895,9 +944,11 @@ const transcript = await generation.await({ poll: { interval: 3_000 } })
895
944
  - **`ImageClient`** — Effect service and layer for image execution, parallel to `LLMClient`.
896
945
  - **`Media`** — the shared asset type (`Media.Asset`, `Media.Source`) and constructors used by messages, tool results, and media requests.
897
946
  - **`Generation`** — provider-neutral handle for an in-flight media generation (`await`, `refresh`, `cancel`, `events`) used by queued media routes.
947
+ - **`Video.request` / `generate` / `stream` / `start` / `resume`** — queued video generation through a provider-neutral request; `VideoClient` is its Effect service and layer.
898
948
  - **`Speech.request` / `Speech.generate` / `Speech.stream`** — text-to-speech through a provider-neutral request; `SpeechClient` is its Effect service and layer.
899
949
  - **`Transcription.request` / `generate` / `stream` / `start` / `resume`** — speech-to-text over inline, streaming, and queued routes; `TranscriptionClient` is its Effect service and layer.
900
- - **`@opencode/ai/promise`** — `AI.make({ layer? })` and a default `ai` client exposing `llm`, `image`, `video`, `speech`, and `transcription` as Promise / `AsyncIterable` APIs.
950
+ - **`AIClient.layer` / `AIClient.layerWith(executor)`** — every modality client plus the request executor in one layer.
951
+ - **`@opencode/ai/promise`** — `AI.make({ layer? })` and a default `ai` client exposing `llm`, `image`, `video`, `speech`, and `transcription` as Promise / `AsyncIterable` APIs, plus `file`, `write`, `bytes`, `base64`, and `materialize` for assets.
901
952
 
902
953
  ## Testing
903
954
 
@@ -960,11 +1011,13 @@ This is different from prompt caching, server-side history storage, or truncatio
960
1011
  Prefer this operation, where supported, when the application owns compaction policy and durable context updates.
961
1012
 
962
1013
  ```ts
963
- const result = yield * LLMClient.compact(request)
964
- const next = LLMRequest.update(request, {
965
- messages: result.replacement,
1014
+ const compacted = Effect.gen(function* () {
1015
+ const result = yield* LLMClient.compact(request)
1016
+ const next = LLMRequest.update(request, {
1017
+ messages: result.replacement,
1018
+ })
1019
+ return yield* LLMClient.generate(next)
966
1020
  })
967
- const response = yield * LLMClient.generate(next)
968
1021
  ```
969
1022
 
970
1023
  `replacement` replaces the complete input window. Do not append it to the original transcript or extract only the encrypted item: the provider may retain additional messages in its output. Retained user and assistant messages remain ordinary messages with typed text, media, or reasoning parts, in their original order. Provider-specific message IDs, status, and phase use `providerMetadata`, not a raw output array hidden in an assistant message. Unsupported returned item types fail explicitly.
@@ -980,16 +1033,16 @@ The input must still fit the model's context window. Explicit compaction is not
980
1033
  OpenAI Responses also exposes a separate, explicitly selected mechanism:
981
1034
 
982
1035
  ```ts
983
- const result =
984
- yield *
985
- LLMClient.compact(request, {
1036
+ const checkpoint = Effect.gen(function* () {
1037
+ const result = yield* LLMClient.compact(request, {
986
1038
  mechanism: "trigger",
987
1039
  webSocket, // Optional: without it, the request uses HTTP/SSE.
988
1040
  })
989
1041
 
990
- result.checkpoint // Successful encrypted CompactionPart.
991
- result.responseID
992
- result.usage
1042
+ result.checkpoint // Successful encrypted CompactionPart.
1043
+ result.responseID
1044
+ result.usage
1045
+ })
993
1046
  ```
994
1047
 
995
1048
  This appends a native `compaction_trigger` control item to the full input and sends a normal Responses request, with tools and instructions retained, `stream: true`, `store: false`, and parallel tool calls enabled. It removes normal-answer text/output-format controls, forced tool choices, output-token/tool-call limits, and automatic `context_management`. Body overlays cannot replace `input` or supply `previous_response_id`/`conversation`; the complete canonical history is required for safe stateless replay. Request metadata, auth, headers, query parameters, service tier, and supported prompt-cache settings are preserved.
@@ -1003,9 +1056,11 @@ The supplied WebSocket executor can reuse a compatible append baseline for the c
1003
1056
  Trigger support is separate from endpoint support. Only the OpenAI Responses route advertises it; Azure, xAI, Chat, and compatible Responses routes do not inherit it. Untyped calls still fail before sending: missing route capabilities return `UnsupportedOperation`, while unknown mechanism names and invalid inputs return `InvalidRequest`. Dynamic callers must narrow for the selected mechanism:
1004
1057
 
1005
1058
  ```ts
1006
- if (LLMClient.canCompact(request, { mechanism: "trigger" })) {
1007
- const result = yield * LLMClient.compact(request, { mechanism: "trigger" })
1008
- }
1059
+ const narrowed = Effect.gen(function* () {
1060
+ if (LLMClient.canCompact(request, { mechanism: "trigger" })) {
1061
+ const result = yield* LLMClient.compact(request, { mechanism: "trigger" })
1062
+ }
1063
+ })
1009
1064
  ```
1010
1065
 
1011
1066
  This capability describes protocol implementation, **not universal availability on OpenAI API deployments**. The host application owns subscription/deployment eligibility, OAuth, endpoint selection, and deployment-specific headers. Local protocol/socket tests do not establish live provider support.
@@ -1014,9 +1069,10 @@ This capability describes protocol implementation, **not universal availability
1014
1069
 
1015
1070
  `providerOptions.contextManagement` lets the provider decide when to compact during an ordinary `generate` or `stream` call. This is an advanced option for callers that own persistence and recovery: persist the complete assistant message, including its checkpoint, before continuing. Enabling the option does not provide durable checkpoint storage, interruption recovery, or model-switch policy. Keep the prior context until a successful checkpoint has been persisted.
1016
1071
 
1017
- Inside an `Effect.gen`, enable OpenAI compaction with typed provider options:
1072
+ Enable OpenAI compaction with typed provider options:
1018
1073
 
1019
1074
  ```ts
1075
+ import { Effect } from "effect"
1020
1076
  import { LLM, LLMClient, LLMRequest, Message } from "@opencode/ai"
1021
1077
  import { OpenAI } from "@opencode/ai/providers"
1022
1078
 
@@ -1027,9 +1083,11 @@ const request = LLM.request({
1027
1083
  contextManagement: [{ type: "compaction", compactThreshold: 200_000 }],
1028
1084
  },
1029
1085
  })
1030
- const response = yield * LLMClient.generate(request)
1031
- const next = LLMRequest.update(request, {
1032
- messages: [...request.messages, response.message, Message.user("Continue")],
1086
+ const continued = Effect.gen(function* () {
1087
+ const response = yield* LLMClient.generate(request)
1088
+ return LLMRequest.update(request, {
1089
+ messages: [...request.messages, response.message, Message.user("Continue")],
1090
+ })
1033
1091
  })
1034
1092
  ```
1035
1093
 
@@ -1257,7 +1315,7 @@ Compose a route with `Route.make({ protocol, endpoint, auth, framing, ... })`. T
1257
1315
 
1258
1316
  ## Effect
1259
1317
 
1260
- This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for LLM dispatch and `ImageClient.layer` for image dispatch, then import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
1318
+ This package is built on Effect. Public methods return `Effect` or `Stream`; provide `AIClient.layer` (or `AIClient.layerWith(executor)`) for every modality, then import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
1261
1319
 
1262
1320
  ## See also
1263
1321
 
@@ -0,0 +1,8 @@
1
+ import { Layer } from "effect";
2
+ import { RequestExecutor } from "./route/executor.js";
3
+ /** Every modality client over `executor`, which stays in the output so `asset.bytes()` and `Media.write` resolve. */
4
+ export declare const layerWith: <E, R>(executor: Layer.Layer<RequestExecutor.Service, E, R>) => Layer.Layer<import("./route/executor-service.js").RequestExecutorService | import("./route/client.js").LLMClientService | import("./image-client.js").ImageClientService | import("./speech-client.js").SpeechClientService | import("./transcription-client.js").TranscriptionClientService | import("./video-client.js").VideoClientService, E, R>;
5
+ /** Every modality client plus the executor over `RequestExecutor.fetchLayer`: the one layer most programs need. */
6
+ export declare const layer: Layer.Layer<import("./route/executor-service.js").RequestExecutorService | import("./route/client.js").LLMClientService | import("./image-client.js").ImageClientService | import("./speech-client.js").SpeechClientService | import("./transcription-client.js").TranscriptionClientService | import("./video-client.js").VideoClientService, never, never>;
7
+ export type Services = Layer.Success<typeof layer>;
8
+ export * as AIClient from "./ai-client.js";
@@ -0,0 +1,12 @@
1
+ import { Layer } from "effect";
2
+ import { ImageClient } from "./image-client.js";
3
+ import { LLMClient } from "./route/client.js";
4
+ import { RequestExecutor } from "./route/executor.js";
5
+ import { SpeechClient } from "./speech-client.js";
6
+ import { TranscriptionClient } from "./transcription-client.js";
7
+ import { VideoClient } from "./video-client.js";
8
+ /** Every modality client over `executor`, which stays in the output so `asset.bytes()` and `Media.write` resolve. */
9
+ export const layerWith = (executor) => Layer.mergeAll(LLMClient.layer, ImageClient.layer, VideoClient.layer, SpeechClient.layer, TranscriptionClient.layer).pipe(Layer.provideMerge(executor));
10
+ /** Every modality client plus the executor over `RequestExecutor.fetchLayer`: the one layer most programs need. */
11
+ export const layer = layerWith(RequestExecutor.fetchLayer);
12
+ export * as AIClient from "./ai-client.js";