@opencode/ai 2.0.14 → 2.0.16

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 (208) hide show
  1. package/README.md +399 -56
  2. package/dist/experimental/evaluation-client.d.ts +1 -1
  3. package/dist/experimental/evaluation-client.js +39 -3
  4. package/dist/experimental/evaluation.d.ts +4 -4
  5. package/dist/experimental/evaluation.js +2 -2
  6. package/dist/experimental/system-one.d.ts +3 -3
  7. package/dist/experimental/system-one.js +40 -51
  8. package/dist/generation.d.ts +83 -0
  9. package/dist/generation.js +113 -0
  10. package/dist/image-client.d.ts +16 -7
  11. package/dist/image-client.js +29 -13
  12. package/dist/image.d.ts +1410 -81
  13. package/dist/image.js +97 -67
  14. package/dist/index.d.ts +17 -2
  15. package/dist/index.js +12 -1
  16. package/dist/llm.d.ts +9 -1
  17. package/dist/media-model.d.ts +44 -0
  18. package/dist/media-model.js +49 -0
  19. package/dist/media.d.ts +213 -0
  20. package/dist/media.js +227 -0
  21. package/dist/promise.d.ts +974 -0
  22. package/dist/promise.js +81 -0
  23. package/dist/protocols/alibaba-chat.d.ts +12 -0
  24. package/dist/protocols/alibaba-responses.d.ts +2 -2
  25. package/dist/protocols/anthropic-messages.js +8 -19
  26. package/dist/protocols/assemblyai-transcription.d.ts +40 -0
  27. package/dist/protocols/assemblyai-transcription.js +138 -0
  28. package/dist/protocols/bedrock-converse.d.ts +4 -4
  29. package/dist/protocols/bedrock-converse.js +6 -17
  30. package/dist/protocols/bfl-images.d.ts +32 -0
  31. package/dist/protocols/bfl-images.js +153 -0
  32. package/dist/protocols/cartesia-speech.d.ts +127 -0
  33. package/dist/protocols/cartesia-speech.js +126 -0
  34. package/dist/protocols/deepgram-speech.d.ts +119 -0
  35. package/dist/protocols/deepgram-speech.js +92 -0
  36. package/dist/protocols/deepgram-transcription.d.ts +25 -0
  37. package/dist/protocols/deepgram-transcription.js +129 -0
  38. package/dist/protocols/elevenlabs-speech.d.ts +122 -0
  39. package/dist/protocols/elevenlabs-speech.js +115 -0
  40. package/dist/protocols/fal-images.d.ts +24 -0
  41. package/dist/protocols/fal-images.js +114 -0
  42. package/dist/protocols/fal-video.d.ts +29 -0
  43. package/dist/protocols/fal-video.js +88 -0
  44. package/dist/protocols/gemini.d.ts +30 -9
  45. package/dist/protocols/gemini.js +45 -35
  46. package/dist/protocols/google-images.d.ts +9 -21
  47. package/dist/protocols/google-images.js +158 -133
  48. package/dist/protocols/google-speech.d.ts +130 -0
  49. package/dist/protocols/google-speech.js +84 -0
  50. package/dist/protocols/google-transcription.d.ts +173 -0
  51. package/dist/protocols/google-transcription.js +138 -0
  52. package/dist/protocols/google-video.d.ts +26 -0
  53. package/dist/protocols/google-video.js +158 -0
  54. package/dist/protocols/meta-images.d.ts +7 -12
  55. package/dist/protocols/meta-images.js +85 -66
  56. package/dist/protocols/meta-responses.d.ts +4 -4
  57. package/dist/protocols/meta-responses.js +1 -1
  58. package/dist/protocols/mistral-chat.js +7 -6
  59. package/dist/protocols/open-responses.d.ts +17 -9
  60. package/dist/protocols/open-responses.js +24 -14
  61. package/dist/protocols/openai-chat.d.ts +118 -1
  62. package/dist/protocols/openai-chat.js +125 -44
  63. package/dist/protocols/openai-compatible-chat.d.ts +12 -0
  64. package/dist/protocols/openai-compatible-responses.d.ts +2 -2
  65. package/dist/protocols/openai-images.d.ts +128 -18
  66. package/dist/protocols/openai-images.js +177 -154
  67. package/dist/protocols/openai-responses.d.ts +15 -15
  68. package/dist/protocols/openai-responses.js +5 -6
  69. package/dist/protocols/openai-speech.d.ts +116 -0
  70. package/dist/protocols/openai-speech.js +98 -0
  71. package/dist/protocols/openai-transcription.d.ts +207 -0
  72. package/dist/protocols/openai-transcription.js +190 -0
  73. package/dist/protocols/replicate-images.d.ts +28 -0
  74. package/dist/protocols/replicate-images.js +133 -0
  75. package/dist/protocols/runway-video.d.ts +38 -0
  76. package/dist/protocols/runway-video.js +146 -0
  77. package/dist/protocols/shared.d.ts +27 -17
  78. package/dist/protocols/shared.js +52 -35
  79. package/dist/protocols/stability-images.d.ts +38 -0
  80. package/dist/protocols/stability-images.js +148 -0
  81. package/dist/protocols/utils/bedrock-media.d.ts +2 -3
  82. package/dist/protocols/utils/bedrock-media.js +4 -4
  83. package/dist/protocols/utils/fal-queue.d.ts +28 -0
  84. package/dist/protocols/utils/fal-queue.js +69 -0
  85. package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
  86. package/dist/protocols/utils/gemini-generate-content.js +65 -0
  87. package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
  88. package/dist/protocols/utils/gemini-json-schema.js +76 -0
  89. package/dist/protocols/utils/media-input.d.ts +18 -0
  90. package/dist/protocols/utils/media-input.js +35 -0
  91. package/dist/protocols/utils/responses-compaction.js +6 -5
  92. package/dist/protocols/utils/speech-stream.d.ts +49 -0
  93. package/dist/protocols/utils/speech-stream.js +67 -0
  94. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  95. package/dist/protocols/utils/tool-schema.js +40 -17
  96. package/dist/protocols/utils/tool-stream.d.ts +27 -3
  97. package/dist/protocols/xai-images.d.ts +9 -15
  98. package/dist/protocols/xai-images.js +75 -84
  99. package/dist/protocols/xai-responses.d.ts +2 -2
  100. package/dist/protocols/xai-video.d.ts +34 -0
  101. package/dist/protocols/xai-video.js +147 -0
  102. package/dist/protocols/zai-chat.d.ts +13 -1
  103. package/dist/protocols/zai-images.d.ts +9 -13
  104. package/dist/protocols/zai-images.js +59 -57
  105. package/dist/provider-error.js +3 -0
  106. package/dist/providers/alibaba.d.ts +14 -2
  107. package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
  108. package/dist/providers/amazon-bedrock.d.ts +2 -2
  109. package/dist/providers/assemblyai.d.ts +25 -0
  110. package/dist/providers/assemblyai.js +29 -0
  111. package/dist/providers/azure.d.ts +18 -6
  112. package/dist/providers/baseten.d.ts +24 -0
  113. package/dist/providers/black-forest-labs.d.ts +25 -0
  114. package/dist/providers/black-forest-labs.js +28 -0
  115. package/dist/providers/cartesia.d.ts +24 -0
  116. package/dist/providers/cartesia.js +22 -0
  117. package/dist/providers/cerebras.d.ts +24 -0
  118. package/dist/providers/cerebras.js +6 -1
  119. package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
  120. package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
  121. package/dist/providers/deepgram.d.ts +29 -0
  122. package/dist/providers/deepgram.js +31 -0
  123. package/dist/providers/deepinfra.d.ts +24 -0
  124. package/dist/providers/deepinfra.js +6 -1
  125. package/dist/providers/deepseek.d.ts +24 -0
  126. package/dist/providers/elevenlabs.d.ts +24 -0
  127. package/dist/providers/elevenlabs.js +28 -0
  128. package/dist/providers/fal.d.ts +29 -0
  129. package/dist/providers/fal.js +33 -0
  130. package/dist/providers/fireworks.d.ts +24 -0
  131. package/dist/providers/google-vertex-chat.d.ts +12 -0
  132. package/dist/providers/google-vertex-responses.d.ts +2 -2
  133. package/dist/providers/google-vertex.d.ts +10 -3
  134. package/dist/providers/google.d.ts +25 -3
  135. package/dist/providers/google.js +11 -2
  136. package/dist/providers/groq.d.ts +24 -0
  137. package/dist/providers/index.d.ts +10 -0
  138. package/dist/providers/index.js +10 -0
  139. package/dist/providers/meta.d.ts +14 -2
  140. package/dist/providers/minimax.d.ts +14 -2
  141. package/dist/providers/moonshot.d.ts +14 -2
  142. package/dist/providers/moonshot.js +3 -3
  143. package/dist/providers/openai-compatible-responses.d.ts +2 -2
  144. package/dist/providers/openai-compatible.d.ts +12 -0
  145. package/dist/providers/openai.d.ts +25 -3
  146. package/dist/providers/openai.js +10 -1
  147. package/dist/providers/openrouter.d.ts +67 -0
  148. package/dist/providers/openrouter.js +13 -1
  149. package/dist/providers/replicate.d.ts +25 -0
  150. package/dist/providers/replicate.js +22 -0
  151. package/dist/providers/runway.d.ts +24 -0
  152. package/dist/providers/runway.js +22 -0
  153. package/dist/providers/stability.d.ts +28 -0
  154. package/dist/providers/stability.js +23 -0
  155. package/dist/providers/togetherai.d.ts +24 -0
  156. package/dist/providers/vercel-ai-gateway.d.ts +41 -0
  157. package/dist/providers/vercel-ai-gateway.js +85 -0
  158. package/dist/providers/xai.d.ts +17 -0
  159. package/dist/providers/xai.js +5 -2
  160. package/dist/providers/zai-coding-plan.d.ts +15 -3
  161. package/dist/providers/zai.d.ts +13 -1
  162. package/dist/route/auth.d.ts +4 -1
  163. package/dist/route/auth.js +6 -0
  164. package/dist/route/client.d.ts +9 -1
  165. package/dist/route/endpoint.d.ts +10 -10
  166. package/dist/route/executor-service.d.ts +12 -0
  167. package/dist/route/executor-service.js +3 -0
  168. package/dist/route/executor.d.ts +4 -9
  169. package/dist/route/executor.js +3 -3
  170. package/dist/route/framing.d.ts +5 -1
  171. package/dist/route/framing.js +9 -0
  172. package/dist/route/index.d.ts +2 -0
  173. package/dist/route/index.js +2 -0
  174. package/dist/route/media-protocol.d.ts +158 -0
  175. package/dist/route/media-protocol.js +96 -0
  176. package/dist/route/media.d.ts +97 -0
  177. package/dist/route/media.js +236 -0
  178. package/dist/schema/errors.d.ts +13 -3
  179. package/dist/schema/errors.js +7 -0
  180. package/dist/schema/events.d.ts +557 -40
  181. package/dist/schema/events.js +35 -2
  182. package/dist/schema/messages.d.ts +95 -8
  183. package/dist/schema/messages.js +8 -6
  184. package/dist/schema/options.d.ts +6 -3
  185. package/dist/schema/options.js +6 -2
  186. package/dist/speech-client.d.ts +21 -0
  187. package/dist/speech-client.js +25 -0
  188. package/dist/speech.d.ts +1150 -0
  189. package/dist/speech.js +119 -0
  190. package/dist/testing.d.ts +72 -8
  191. package/dist/transcription-client.d.ts +28 -0
  192. package/dist/transcription-client.js +44 -0
  193. package/dist/transcription.d.ts +1504 -0
  194. package/dist/transcription.js +133 -0
  195. package/dist/utils/bytes.d.ts +1 -0
  196. package/dist/utils/bytes.js +10 -0
  197. package/dist/utils/media-type.d.ts +7 -0
  198. package/dist/utils/media-type.js +70 -0
  199. package/dist/utils/sanitize.js +3 -1
  200. package/dist/video-client.d.ts +28 -0
  201. package/dist/video-client.js +40 -0
  202. package/dist/video.d.ts +1359 -0
  203. package/dist/video.js +119 -0
  204. package/package.json +7 -3
  205. package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
  206. package/dist/protocols/utils/gemini-tool-schema.js +0 -103
  207. package/dist/protocols/utils/image-input.d.ts +0 -21
  208. package/dist/protocols/utils/image-input.js +0 -20
@@ -12,6 +12,37 @@ declare const OpenAIChatCacheControl: Schema.Struct<{
12
12
  readonly type: Schema.Literal<"ephemeral">;
13
13
  readonly ttl: Schema.optional<Schema.String>;
14
14
  }>;
15
+ declare const ReasoningDetail: Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
16
+ readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
17
+ readonly format: Schema.optional<Schema.String>;
18
+ readonly index: Schema.optional<Schema.Number>;
19
+ readonly signature: Schema.optional<Schema.NullOr<Schema.String>>;
20
+ readonly type: Schema.Literal<"reasoning.text">;
21
+ readonly text: Schema.optional<Schema.String>;
22
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.StructWithRest<Schema.Struct<{
23
+ readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
24
+ readonly format: Schema.optional<Schema.String>;
25
+ readonly index: Schema.optional<Schema.Number>;
26
+ readonly signature: Schema.optional<Schema.NullOr<Schema.String>>;
27
+ readonly type: Schema.Literal<"reasoning.summary">;
28
+ readonly summary: Schema.optional<Schema.String>;
29
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.StructWithRest<Schema.Struct<{
30
+ readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
31
+ readonly format: Schema.optional<Schema.String>;
32
+ readonly index: Schema.optional<Schema.Number>;
33
+ readonly signature: Schema.optional<Schema.NullOr<Schema.String>>;
34
+ readonly type: Schema.Literal<"reasoning.encrypted">;
35
+ readonly data: Schema.String;
36
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.Struct<{
37
+ readonly type: Schema.Literal<"summary">;
38
+ readonly summary: Schema.String;
39
+ readonly index: Schema.optional<Schema.Number>;
40
+ }>, Schema.Struct<{
41
+ readonly type: Schema.Literal<"encrypted">;
42
+ readonly encrypted: Schema.String;
43
+ readonly index: Schema.optional<Schema.Number>;
44
+ }>]>;
45
+ type ReasoningDetail = Schema.Schema.Type<typeof ReasoningDetail>;
15
46
  export declare const bodyFields: {
16
47
  model: Schema.String;
17
48
  messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
@@ -28,6 +59,12 @@ export declare const bodyFields: {
28
59
  readonly image_url: Schema.Struct<{
29
60
  readonly url: Schema.String;
30
61
  }>;
62
+ }>, Schema.Struct<{
63
+ readonly type: Schema.Literal<"file">;
64
+ readonly file: Schema.Struct<{
65
+ readonly filename: Schema.String;
66
+ readonly file_data: Schema.String;
67
+ }>;
31
68
  }>]>>]>;
32
69
  }>, Schema.Struct<{
33
70
  readonly role: Schema.Literal<"user">;
@@ -43,6 +80,12 @@ export declare const bodyFields: {
43
80
  readonly image_url: Schema.Struct<{
44
81
  readonly url: Schema.String;
45
82
  }>;
83
+ }>, Schema.Struct<{
84
+ readonly type: Schema.Literal<"file">;
85
+ readonly file: Schema.Struct<{
86
+ readonly filename: Schema.String;
87
+ readonly file_data: Schema.String;
88
+ }>;
46
89
  }>]>>]>;
47
90
  }>, Schema.StructWithRest<Schema.Struct<{
48
91
  readonly role: Schema.Literal<"assistant">;
@@ -124,6 +167,12 @@ declare const OpenAIChatBody: Schema.Struct<{
124
167
  readonly image_url: Schema.Struct<{
125
168
  readonly url: Schema.String;
126
169
  }>;
170
+ }>, Schema.Struct<{
171
+ readonly type: Schema.Literal<"file">;
172
+ readonly file: Schema.Struct<{
173
+ readonly filename: Schema.String;
174
+ readonly file_data: Schema.String;
175
+ }>;
127
176
  }>]>>]>;
128
177
  }>, Schema.Struct<{
129
178
  readonly role: Schema.Literal<"user">;
@@ -139,6 +188,12 @@ declare const OpenAIChatBody: Schema.Struct<{
139
188
  readonly image_url: Schema.Struct<{
140
189
  readonly url: Schema.String;
141
190
  }>;
191
+ }>, Schema.Struct<{
192
+ readonly type: Schema.Literal<"file">;
193
+ readonly file: Schema.Struct<{
194
+ readonly filename: Schema.String;
195
+ readonly file_data: Schema.String;
196
+ }>;
142
197
  }>]>>]>;
143
198
  }>, Schema.StructWithRest<Schema.Struct<{
144
199
  readonly role: Schema.Literal<"assistant">;
@@ -278,7 +333,9 @@ export interface ParserState {
278
333
  readonly finishReason?: FinishReasonDetails;
279
334
  readonly lifecycle: Lifecycle.State;
280
335
  readonly reasoningField?: string;
281
- readonly reasoningDetails: Array<unknown>;
336
+ /** A scalar reasoning field (`reasoning_content`, ...) has carried text in this stream. */
337
+ readonly reasoningTextObserved: boolean;
338
+ readonly reasoningDetails: Array<ReasoningDetail>;
282
339
  readonly reasoningDetailsObserved: boolean;
283
340
  readonly reasoningEmitted: boolean;
284
341
  readonly latestToolIndex?: number;
@@ -321,6 +378,12 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
321
378
  readonly image_url: {
322
379
  readonly url: string;
323
380
  };
381
+ } | {
382
+ readonly type: "file";
383
+ readonly file: {
384
+ readonly filename: string;
385
+ readonly file_data: string;
386
+ };
324
387
  })[];
325
388
  } | {
326
389
  readonly role: "user";
@@ -336,6 +399,12 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
336
399
  readonly image_url: {
337
400
  readonly url: string;
338
401
  };
402
+ } | {
403
+ readonly type: "file";
404
+ readonly file: {
405
+ readonly filename: string;
406
+ readonly file_data: string;
407
+ };
339
408
  })[];
340
409
  } | {
341
410
  readonly [x: string]: unknown;
@@ -418,6 +487,12 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
418
487
  readonly image_url: {
419
488
  readonly url: string;
420
489
  };
490
+ } | {
491
+ readonly type: "file";
492
+ readonly file: {
493
+ readonly filename: string;
494
+ readonly file_data: string;
495
+ };
421
496
  })[];
422
497
  } | {
423
498
  readonly role: "user";
@@ -433,6 +508,12 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
433
508
  readonly image_url: {
434
509
  readonly url: string;
435
510
  };
511
+ } | {
512
+ readonly type: "file";
513
+ readonly file: {
514
+ readonly filename: string;
515
+ readonly file_data: string;
516
+ };
436
517
  })[];
437
518
  } | {
438
519
  readonly [x: string]: unknown;
@@ -508,6 +589,12 @@ export declare const protocol: Protocol<{
508
589
  readonly image_url: {
509
590
  readonly url: string;
510
591
  };
592
+ } | {
593
+ readonly type: "file";
594
+ readonly file: {
595
+ readonly filename: string;
596
+ readonly file_data: string;
597
+ };
511
598
  })[];
512
599
  } | {
513
600
  readonly role: "user";
@@ -523,6 +610,12 @@ export declare const protocol: Protocol<{
523
610
  readonly image_url: {
524
611
  readonly url: string;
525
612
  };
613
+ } | {
614
+ readonly type: "file";
615
+ readonly file: {
616
+ readonly filename: string;
617
+ readonly file_data: string;
618
+ };
526
619
  })[];
527
620
  } | {
528
621
  readonly [x: string]: unknown;
@@ -675,6 +768,12 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
675
768
  readonly image_url: {
676
769
  readonly url: string;
677
770
  };
771
+ } | {
772
+ readonly type: "file";
773
+ readonly file: {
774
+ readonly filename: string;
775
+ readonly file_data: string;
776
+ };
678
777
  })[];
679
778
  } | {
680
779
  readonly role: "user";
@@ -690,6 +789,12 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
690
789
  readonly image_url: {
691
790
  readonly url: string;
692
791
  };
792
+ } | {
793
+ readonly type: "file";
794
+ readonly file: {
795
+ readonly filename: string;
796
+ readonly file_data: string;
797
+ };
693
798
  })[];
694
799
  } | {
695
800
  readonly [x: string]: unknown;
@@ -774,6 +879,12 @@ export declare const route: Route<{
774
879
  readonly image_url: {
775
880
  readonly url: string;
776
881
  };
882
+ } | {
883
+ readonly type: "file";
884
+ readonly file: {
885
+ readonly filename: string;
886
+ readonly file_data: string;
887
+ };
777
888
  })[];
778
889
  } | {
779
890
  readonly role: "user";
@@ -789,6 +900,12 @@ export declare const route: Route<{
789
900
  readonly image_url: {
790
901
  readonly url: string;
791
902
  };
903
+ } | {
904
+ readonly type: "file";
905
+ readonly file: {
906
+ readonly filename: string;
907
+ readonly file_data: string;
908
+ };
792
909
  })[];
793
910
  } | {
794
911
  readonly [x: string]: unknown;
@@ -1,4 +1,4 @@
1
- import { Effect, Schema } from "effect";
1
+ import { Effect, Option, Schema } from "effect";
2
2
  import { Tool } from "@opencode/schema/tool";
3
3
  import { Route } from "../route/client.js";
4
4
  import { Auth } from "../route/auth.js";
@@ -50,6 +50,32 @@ const OpenAIChatAssistantToolCall = Schema.Struct({
50
50
  arguments: Schema.String,
51
51
  }),
52
52
  });
53
+ // `reasoning_details` carries two dialects. OpenRouter's `reasoning.*` entries
54
+ // must be replayed unmodified (`index` included), so they keep every field they
55
+ // arrived with. Kimi's OpenAI-compatible surface streams preserved thinking as
56
+ // bare `summary` / `encrypted` entries keyed by a stream-only `index`; Kimi does
57
+ // not document this publicly, so the handling follows Kimi Code (Kimi's own
58
+ // client): merge summary deltas by `index`, replay without `index`, and always
59
+ // send `reasoning_content` alongside. Anything else is dropped at the boundary.
60
+ const OpenRouterDetailFields = {
61
+ id: Schema.optional(Schema.NullOr(Schema.String)),
62
+ format: Schema.optional(Schema.String),
63
+ index: Schema.optional(Schema.Number),
64
+ signature: Schema.optional(Schema.NullOr(Schema.String)),
65
+ };
66
+ const ReasoningDetail = Schema.Union([
67
+ Schema.StructWithRest(Schema.Struct({ type: Schema.Literal("reasoning.text"), text: Schema.optional(Schema.String), ...OpenRouterDetailFields }), [Schema.Record(Schema.String, Schema.Unknown)]),
68
+ Schema.StructWithRest(Schema.Struct({
69
+ type: Schema.Literal("reasoning.summary"),
70
+ summary: Schema.optional(Schema.String),
71
+ ...OpenRouterDetailFields,
72
+ }), [Schema.Record(Schema.String, Schema.Unknown)]),
73
+ Schema.StructWithRest(Schema.Struct({ type: Schema.Literal("reasoning.encrypted"), data: Schema.String, ...OpenRouterDetailFields }), [Schema.Record(Schema.String, Schema.Unknown)]),
74
+ Schema.Struct({ type: Schema.Literal("summary"), summary: Schema.String, index: Schema.optional(Schema.Number) }),
75
+ Schema.Struct({ type: Schema.Literal("encrypted"), encrypted: Schema.String, index: Schema.optional(Schema.Number) }),
76
+ ]);
77
+ const decodeReasoningDetail = Schema.decodeUnknownOption(ReasoningDetail);
78
+ const knownReasoningDetails = (details) => details.flatMap((detail) => Option.toArray(decodeReasoningDetail(detail)));
53
79
  // Intentionally omit Gemini's provider-specific `extra_content.google.thought_signature`
54
80
  // extension until direct Google OpenAI-compatible routing is supported here:
55
81
  // https://github.com/vercel/ai/issues/11590
@@ -65,6 +91,10 @@ const OpenAIChatUserContent = Schema.Union([
65
91
  type: Schema.Literal("image_url"),
66
92
  image_url: Schema.Struct({ url: Schema.String }),
67
93
  }),
94
+ Schema.Struct({
95
+ type: Schema.Literal("file"),
96
+ file: Schema.Struct({ filename: Schema.String, file_data: Schema.String }),
97
+ }),
68
98
  ]);
69
99
  const OpenAIChatMessage = Schema.Union([
70
100
  Schema.Struct({
@@ -204,12 +234,18 @@ const lowerToolCall = (part, options) => ({
204
234
  },
205
235
  });
206
236
  const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part) {
207
- const media = ProviderShared.normalizeMedia(part);
208
- if (!media.mime.startsWith("image/"))
209
- return yield* ProviderShared.invalidRequest(`OpenAI Chat does not support media type ${part.mediaType}`);
210
- const url = typeof part.data === "string" && (part.data.startsWith("https://") || part.data.startsWith("http://"))
211
- ? part.data
212
- : media.dataUrl;
237
+ // Chat Completions accepts PDFs, and no other documents, as inline `file` parts; file URLs are not supported.
238
+ if (part.media.mediaType.toLowerCase() === "application/pdf")
239
+ return {
240
+ type: "file",
241
+ file: {
242
+ filename: part.filename ?? "document.pdf",
243
+ file_data: (yield* ProviderShared.requireInlineMedia("OpenAI Chat", part.media)).dataUrl,
244
+ },
245
+ };
246
+ if (part.media.kind !== "image")
247
+ return yield* ProviderShared.invalidRequest(`OpenAI Chat does not support media type ${part.media.mediaType}`);
248
+ const url = ProviderShared.mediaUrl(part.media) ?? (yield* ProviderShared.requireInlineMedia("OpenAI Chat", part.media)).dataUrl;
213
249
  return { type: "image_url", image_url: { url } };
214
250
  });
215
251
  const openAICompatibleReasoningContent = (native) => isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined;
@@ -223,10 +259,20 @@ const reasoningDetails = (parts, native, providerMetadataKey) => {
223
259
  return Array.isArray(details) ? details : [];
224
260
  });
225
261
  if (parts.some((part) => Array.isArray(part.providerMetadata?.[providerMetadataKey]?.reasoningDetails)))
226
- return observed;
262
+ return knownReasoningDetails(observed).map(lowerReasoningDetail);
227
263
  if (isRecord(native) && Array.isArray(native.reasoning_details))
228
- return native.reasoning_details;
264
+ return knownReasoningDetails(native.reasoning_details).map(lowerReasoningDetail);
265
+ };
266
+ // Kimi rejects its stream-only `index` on requests
267
+ // ("the reasoning_details ... must not contain streaming index").
268
+ const lowerReasoningDetail = (detail) => {
269
+ if (detail.type === "summary")
270
+ return { type: detail.type, summary: detail.summary };
271
+ if (detail.type === "encrypted")
272
+ return { type: detail.type, encrypted: detail.encrypted };
273
+ return detail;
229
274
  };
275
+ const isKimiDetail = (detail) => detail.type === "summary" || detail.type === "encrypted";
230
276
  const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message, options) {
231
277
  const content = [];
232
278
  for (const part of message.content) {
@@ -285,6 +331,10 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
285
331
  return "reasoning_content";
286
332
  if (!fullyStructured || requireReasoning)
287
333
  return "reasoning_content";
334
+ // Kimi always expects `reasoning_content` on replayed assistant messages,
335
+ // even when thinking arrived only through structured details.
336
+ if (details?.some(isKimiDetail))
337
+ return "reasoning_content";
288
338
  })();
289
339
  const reasoningText = (() => {
290
340
  if (configuredField !== undefined)
@@ -308,7 +358,7 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
308
358
  });
309
359
  const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (message, options) {
310
360
  const messages = [];
311
- const images = [];
361
+ const attachments = [];
312
362
  for (const part of message.content) {
313
363
  if (!ProviderShared.supportsContent(part, ["tool-result"]))
314
364
  return yield* ProviderShared.unsupportedContent("OpenAI Chat", "tool", ["tool-result"]);
@@ -330,9 +380,9 @@ const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (m
330
380
  cache_control: options.cacheControl?.(part.cache),
331
381
  });
332
382
  const files = content.filter((item) => item.type === "file");
333
- images.push(...(yield* Effect.forEach(files, (item) => lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name }))));
383
+ attachments.push(...(yield* Effect.forEach(files, (item) => lowerMedia(ProviderShared.toolFileMedia(item)))));
334
384
  }
335
- return { messages, images };
385
+ return { messages, attachments };
336
386
  });
337
387
  const lowerMessage = Effect.fn("OpenAIChat.lowerMessage")(function* (message, reasoningField, requireReasoning, options) {
338
388
  if (message.role === "user")
@@ -386,23 +436,23 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
386
436
  if (requireAssistantAfterTool && messages.at(-1)?.role === "tool")
387
437
  messages.push({ role: "assistant", content: "Done." });
388
438
  };
389
- const pendingImages = [];
390
- const flushImages = () => {
391
- if (pendingImages.length === 0)
439
+ const pendingAttachments = [];
440
+ const flushAttachments = () => {
441
+ if (pendingAttachments.length === 0)
392
442
  return;
393
443
  bridgeTools();
394
- messages.push({ role: "user", content: pendingImages.splice(0) });
444
+ messages.push({ role: "user", content: pendingAttachments.splice(0) });
395
445
  };
396
446
  for (const message of request.messages) {
397
447
  if (message.role === "user")
398
448
  bridgeTools();
399
449
  if (message.role === "system") {
400
450
  const part = yield* ProviderShared.wrappedSystemUpdate("OpenAI Chat", message);
401
- if (pendingImages.length > 0) {
451
+ if (pendingAttachments.length > 0) {
402
452
  messages.push({
403
453
  role: "user",
404
454
  content: [
405
- ...pendingImages.splice(0),
455
+ ...pendingAttachments.splice(0),
406
456
  { type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
407
457
  ],
408
458
  });
@@ -442,13 +492,13 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
442
492
  if (message.role === "tool") {
443
493
  const lowered = yield* lowerToolMessages(message, lowering);
444
494
  messages.push(...lowered.messages);
445
- pendingImages.push(...lowered.images);
495
+ pendingAttachments.push(...lowered.attachments);
446
496
  continue;
447
497
  }
448
- flushImages();
498
+ flushAttachments();
449
499
  messages.push(...(yield* lowerMessage(message, reasoningField, requireReasoning, lowering)));
450
500
  }
451
- flushImages();
501
+ flushAttachments();
452
502
  return messages;
453
503
  });
454
504
  // Anthropic via LiteLLM and Amazon Bedrock require `tools` to be present
@@ -581,7 +631,6 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
581
631
  if (reasoningField && RESERVED_REASONING_FIELDS.has(reasoningField))
582
632
  return yield* ProviderShared.invalidRequest(`OpenAI Chat reasoning field conflicts with reserved field ${reasoningField}`);
583
633
  const generation = request.generation;
584
- const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
585
634
  const flattened = ProviderShared.flattenToolRequest(request);
586
635
  const provider = String(request.model.provider);
587
636
  const baseURL = request.model.route.endpoint.baseURL;
@@ -600,7 +649,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
600
649
  ? hasHistory
601
650
  ? []
602
651
  : undefined
603
- : flattened.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility), options, supportsStrictMode)),
652
+ : flattened.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model), options, supportsStrictMode)),
604
653
  tool_choice: hasActiveTools && request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
605
654
  stream: true,
606
655
  ...(supportsUsageInStreaming ? { stream_options: { include_usage: true } } : {}),
@@ -708,14 +757,16 @@ const reasoningDelta = (delta, configuredField) => {
708
757
  }
709
758
  return undefined;
710
759
  };
711
- const detailText = (details) => {
760
+ const detailText = (details, hideKimiSummary) => {
712
761
  const text = details.flatMap((detail) => {
713
- if (!isRecord(detail))
714
- return [];
715
- if (detail.type === "reasoning.text" && typeof detail.text === "string" && detail.text)
716
- return [detail.text];
717
- if (detail.type === "reasoning.summary" && typeof detail.summary === "string" && detail.summary)
718
- return [detail.summary];
762
+ if (detail.type === "reasoning.text")
763
+ return detail.text ? [detail.text] : [];
764
+ if (detail.type === "reasoning.summary")
765
+ return detail.summary ? [detail.summary] : [];
766
+ // Kimi streams the full thinking through `reasoning_content` and a separate
767
+ // summary through details; show the summary only when nothing else does.
768
+ if (detail.type === "summary")
769
+ return detail.summary && !hideKimiSummary ? [detail.summary] : [];
719
770
  return [];
720
771
  });
721
772
  if (text.length > 0)
@@ -724,25 +775,48 @@ const detailText = (details) => {
724
775
  const appendReasoningDetails = (result, details) => {
725
776
  for (const detail of details) {
726
777
  const previous = result.at(-1);
727
- if (!isRecord(previous) ||
728
- previous.type !== "reasoning.text" ||
729
- !isRecord(detail) ||
730
- detail.type !== "reasoning.text" ||
731
- conflictingReasoningTextDetails(previous, detail)) {
778
+ const merged = previous === undefined ? undefined : mergeReasoningDetails(previous, detail);
779
+ if (merged === undefined) {
732
780
  result.push(detail);
733
781
  continue;
734
782
  }
735
- result[result.length - 1] = {
783
+ result[result.length - 1] = merged;
784
+ }
785
+ };
786
+ // Consecutive text or summary deltas of the same kind accumulate into one
787
+ // entry; encrypted entries are opaque and never merge.
788
+ const mergeReasoningDetails = (previous, detail) => {
789
+ if (conflictingReasoningDetails(previous, detail))
790
+ return undefined;
791
+ if (previous.type === "reasoning.text" && detail.type === "reasoning.text")
792
+ return {
736
793
  ...previous,
737
- ...Object.fromEntries(Object.entries(detail).filter((entry) => entry[1] !== undefined)),
738
- text: `${typeof previous.text === "string" ? previous.text : ""}${typeof detail.text === "string" ? detail.text : ""}`,
739
- signature: mergeDetailValue(previous.signature, detail.signature),
740
- format: mergeDetailValue(previous.format, detail.format),
794
+ ...detail,
795
+ text: `${previous.text ?? ""}${detail.text ?? ""}`,
796
+ ...mergeDetailIdentity(previous, detail),
741
797
  };
742
- }
798
+ if (previous.type === "reasoning.summary" && detail.type === "reasoning.summary")
799
+ return {
800
+ ...previous,
801
+ ...detail,
802
+ summary: `${previous.summary ?? ""}${detail.summary ?? ""}`,
803
+ ...mergeDetailIdentity(previous, detail),
804
+ };
805
+ if (previous.type === "summary" && detail.type === "summary")
806
+ return { ...previous, ...detail, summary: previous.summary + detail.summary };
807
+ };
808
+ // The first non-empty signature and format win; a later delta may carry the
809
+ // signature for text that streamed earlier.
810
+ const mergeDetailIdentity = (previous, current) => {
811
+ const signature = mergeDetailValue(previous.signature, current.signature);
812
+ const format = mergeDetailValue(previous.format, current.format);
813
+ return {
814
+ ...(signature === undefined ? {} : { signature }),
815
+ ...(format === undefined ? {} : { format }),
816
+ };
743
817
  };
744
818
  const mergeDetailValue = (previous, current) => previous || current || (previous !== undefined ? previous : current);
745
- const conflictingReasoningTextDetails = (previous, current) => conflictingDetailValue(previous.id, current.id) ||
819
+ const conflictingReasoningDetails = (previous, current) => conflictingDetailValue(previous.id, current.id) ||
746
820
  conflictingDetailValue(previous.index, current.index) ||
747
821
  conflictingDetailValue(previous.format, current.format) ||
748
822
  (Boolean(previous.signature) && Boolean(current.signature) && previous.signature !== current.signature);
@@ -798,12 +872,17 @@ const step = (state, event) => Effect.gen(function* () {
798
872
  return [{ ...state, usage }, events];
799
873
  }
800
874
  const reasoningField = state.reasoningField ?? reasoning?.field;
801
- const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined;
875
+ const reasoningTextObserved = state.reasoningTextObserved || reasoning !== undefined;
876
+ const detailDelta = Array.isArray(delta?.reasoning_details)
877
+ ? knownReasoningDetails(delta.reasoning_details)
878
+ : undefined;
802
879
  if (detailDelta !== undefined)
803
880
  appendReasoningDetails(state.reasoningDetails, detailDelta);
804
881
  const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined;
805
882
  const deltaMetadata = reasoningMetadata(state.providerMetadataKey, reasoningField);
806
- const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text;
883
+ const text = detailDelta?.length
884
+ ? (detailText(detailDelta, reasoningTextObserved) ?? reasoning?.text)
885
+ : reasoning?.text;
807
886
  if (text !== undefined)
808
887
  lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata);
809
888
  else if (reasoningDetailsObserved &&
@@ -881,6 +960,7 @@ const step = (state, event) => Effect.gen(function* () {
881
960
  finishReason,
882
961
  lifecycle,
883
962
  reasoningField,
963
+ reasoningTextObserved,
884
964
  reasoningDetails: state.reasoningDetails,
885
965
  reasoningDetailsObserved,
886
966
  reasoningEmitted,
@@ -947,6 +1027,7 @@ export const protocol = Protocol.make({
947
1027
  toolCallEvents: [],
948
1028
  lifecycle: Lifecycle.initial(),
949
1029
  reasoningField: request.model.compatibility?.reasoningField,
1030
+ reasoningTextObserved: false,
950
1031
  reasoningDetails: [],
951
1032
  reasoningDetailsObserved: false,
952
1033
  reasoningEmitted: false,
@@ -23,6 +23,12 @@ export declare const route: Route<{
23
23
  readonly image_url: {
24
24
  readonly url: string;
25
25
  };
26
+ } | {
27
+ readonly type: "file";
28
+ readonly file: {
29
+ readonly filename: string;
30
+ readonly file_data: string;
31
+ };
26
32
  })[];
27
33
  } | {
28
34
  readonly role: "user";
@@ -38,6 +44,12 @@ export declare const route: Route<{
38
44
  readonly image_url: {
39
45
  readonly url: string;
40
46
  };
47
+ } | {
48
+ readonly type: "file";
49
+ readonly file: {
50
+ readonly filename: string;
51
+ readonly file_data: string;
52
+ };
41
53
  })[];
42
54
  } | {
43
55
  readonly [x: string]: unknown;
@@ -72,8 +72,8 @@ export declare const route: Route<{
72
72
  } | {
73
73
  readonly type: "input_file";
74
74
  readonly filename: string;
75
- readonly detail?: string | undefined;
76
75
  readonly file_data?: string | undefined;
76
+ readonly detail?: string | undefined;
77
77
  readonly file_url?: string | undefined;
78
78
  } | {
79
79
  readonly type: "input_text";
@@ -109,8 +109,8 @@ export declare const route: Route<{
109
109
  } | {
110
110
  readonly type: "input_file";
111
111
  readonly filename: string;
112
- readonly detail?: string | undefined;
113
112
  readonly file_data?: string | undefined;
113
+ readonly detail?: string | undefined;
114
114
  readonly file_url?: string | undefined;
115
115
  } | {
116
116
  readonly type: "input_text";