@opencode/ai 2.0.20 → 2.0.22

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 (59) hide show
  1. package/dist/cache-policy.js +22 -3
  2. package/dist/llm.d.ts +4 -0
  3. package/dist/promise.d.ts +8 -0
  4. package/dist/protocols/alibaba-chat.d.ts +20 -10
  5. package/dist/protocols/alibaba-chat.js +2 -1
  6. package/dist/protocols/anthropic-messages.js +14 -12
  7. package/dist/protocols/bedrock-converse.js +30 -1
  8. package/dist/protocols/open-responses.d.ts +4 -0
  9. package/dist/protocols/openai-chat.d.ts +122 -71
  10. package/dist/protocols/openai-chat.js +40 -37
  11. package/dist/protocols/openai-compatible-chat.d.ts +16 -10
  12. package/dist/protocols/openai-responses.js +4 -3
  13. package/dist/protocols/utils/cache.d.ts +5 -0
  14. package/dist/protocols/utils/cache.js +9 -1
  15. package/dist/protocols/utils/tool-stream.d.ts +12 -0
  16. package/dist/protocols/zai-chat.d.ts +20 -10
  17. package/dist/protocols/zai-chat.js +6 -3
  18. package/dist/provider-error.js +24 -6
  19. package/dist/providers/alibaba.d.ts +16 -10
  20. package/dist/providers/amazon-bedrock-mantle.d.ts +16 -10
  21. package/dist/providers/azure.d.ts +16 -10
  22. package/dist/providers/baseten.d.ts +32 -20
  23. package/dist/providers/cerebras.d.ts +32 -20
  24. package/dist/providers/cloudflare-ai-gateway.d.ts +32 -20
  25. package/dist/providers/cloudflare-ai-gateway.js +43 -35
  26. package/dist/providers/cloudflare-workers-ai.d.ts +32 -20
  27. package/dist/providers/deepinfra.d.ts +32 -20
  28. package/dist/providers/deepseek.d.ts +32 -20
  29. package/dist/providers/digitalocean.d.ts +469 -0
  30. package/dist/providers/digitalocean.js +53 -0
  31. package/dist/providers/fireworks.d.ts +32 -20
  32. package/dist/providers/google-vertex-chat.d.ts +16 -10
  33. package/dist/providers/google-vertex-messages.js +1 -1
  34. package/dist/providers/groq.d.ts +36 -20
  35. package/dist/providers/groq.js +1 -1
  36. package/dist/providers/index.d.ts +1 -0
  37. package/dist/providers/index.js +1 -0
  38. package/dist/providers/meta.d.ts +16 -10
  39. package/dist/providers/minimax.d.ts +16 -10
  40. package/dist/providers/moonshot.d.ts +16 -10
  41. package/dist/providers/openai-compatible.d.ts +16 -10
  42. package/dist/providers/openai.d.ts +16 -10
  43. package/dist/providers/openrouter.d.ts +68 -40
  44. package/dist/providers/openrouter.js +1 -13
  45. package/dist/providers/togetherai.d.ts +32 -20
  46. package/dist/providers/xai.d.ts +16 -10
  47. package/dist/providers/zai-coding-plan.d.ts +16 -10
  48. package/dist/providers/zai.d.ts +16 -10
  49. package/dist/route/client.d.ts +4 -0
  50. package/dist/route/executor.js +13 -6
  51. package/dist/route/transport/http.d.ts +2 -1
  52. package/dist/route/transport/http.js +28 -6
  53. package/dist/schema/events.d.ts +176 -0
  54. package/dist/schema/events.js +4 -0
  55. package/dist/schema/options.d.ts +11 -0
  56. package/dist/schema/options.js +15 -2
  57. package/dist/testing.d.ts +32 -0
  58. package/dist/tool-history.js +17 -3
  59. package/package.json +3 -3
@@ -12,6 +12,7 @@ declare const OpenAIChatCacheControl: Schema.Struct<{
12
12
  readonly type: Schema.Literal<"ephemeral">;
13
13
  readonly ttl: Schema.optional<Schema.String>;
14
14
  }>;
15
+ type OpenAIChatCacheControl = Schema.Schema.Type<typeof OpenAIChatCacheControl>;
15
16
  declare const ExtraContent: Schema.Struct<{
16
17
  readonly google: Schema.Struct<{
17
18
  readonly thought_signature: Schema.String;
@@ -94,7 +95,14 @@ export declare const bodyFields: {
94
95
  }>]>>]>;
95
96
  }>, Schema.StructWithRest<Schema.Struct<{
96
97
  readonly role: Schema.Literal<"assistant">;
97
- readonly content: Schema.NullOr<Schema.String>;
98
+ readonly content: Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Struct<{
99
+ readonly type: Schema.Literal<"text">;
100
+ readonly text: Schema.String;
101
+ readonly cache_control: Schema.optional<Schema.Struct<{
102
+ readonly type: Schema.Literal<"ephemeral">;
103
+ readonly ttl: Schema.optional<Schema.String>;
104
+ }>>;
105
+ }>>]>>;
98
106
  readonly tool_calls: Schema.optional<Schema.$Array<Schema.Struct<{
99
107
  readonly id: Schema.String;
100
108
  readonly type: Schema.tag<"function">;
@@ -112,18 +120,17 @@ export declare const bodyFields: {
112
120
  readonly reasoning: Schema.optional<Schema.String>;
113
121
  readonly reasoning_text: Schema.optional<Schema.String>;
114
122
  readonly reasoning_details: Schema.optional<Schema.Unknown>;
115
- readonly cache_control: Schema.optional<Schema.Struct<{
116
- readonly type: Schema.Literal<"ephemeral">;
117
- readonly ttl: Schema.optional<Schema.String>;
118
- }>>;
119
123
  }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.Struct<{
120
124
  readonly role: Schema.Literal<"tool">;
121
125
  readonly tool_call_id: Schema.String;
122
- readonly content: Schema.String;
123
- readonly cache_control: Schema.optional<Schema.Struct<{
124
- readonly type: Schema.Literal<"ephemeral">;
125
- readonly ttl: Schema.optional<Schema.String>;
126
- }>>;
126
+ readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Struct<{
127
+ readonly type: Schema.Literal<"text">;
128
+ readonly text: Schema.String;
129
+ readonly cache_control: Schema.optional<Schema.Struct<{
130
+ readonly type: Schema.Literal<"ephemeral">;
131
+ readonly ttl: Schema.optional<Schema.String>;
132
+ }>>;
133
+ }>>]>;
127
134
  }>]>>;
128
135
  tools: Schema.optional<Schema.$Array<Schema.Struct<{
129
136
  readonly type: Schema.tag<"function">;
@@ -207,7 +214,14 @@ declare const OpenAIChatBody: Schema.Struct<{
207
214
  }>]>>]>;
208
215
  }>, Schema.StructWithRest<Schema.Struct<{
209
216
  readonly role: Schema.Literal<"assistant">;
210
- readonly content: Schema.NullOr<Schema.String>;
217
+ readonly content: Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Struct<{
218
+ readonly type: Schema.Literal<"text">;
219
+ readonly text: Schema.String;
220
+ readonly cache_control: Schema.optional<Schema.Struct<{
221
+ readonly type: Schema.Literal<"ephemeral">;
222
+ readonly ttl: Schema.optional<Schema.String>;
223
+ }>>;
224
+ }>>]>>;
211
225
  readonly tool_calls: Schema.optional<Schema.$Array<Schema.Struct<{
212
226
  readonly id: Schema.String;
213
227
  readonly type: Schema.tag<"function">;
@@ -225,18 +239,17 @@ declare const OpenAIChatBody: Schema.Struct<{
225
239
  readonly reasoning: Schema.optional<Schema.String>;
226
240
  readonly reasoning_text: Schema.optional<Schema.String>;
227
241
  readonly reasoning_details: Schema.optional<Schema.Unknown>;
228
- readonly cache_control: Schema.optional<Schema.Struct<{
229
- readonly type: Schema.Literal<"ephemeral">;
230
- readonly ttl: Schema.optional<Schema.String>;
231
- }>>;
232
242
  }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.Struct<{
233
243
  readonly role: Schema.Literal<"tool">;
234
244
  readonly tool_call_id: Schema.String;
235
- readonly content: Schema.String;
236
- readonly cache_control: Schema.optional<Schema.Struct<{
237
- readonly type: Schema.Literal<"ephemeral">;
238
- readonly ttl: Schema.optional<Schema.String>;
239
- }>>;
245
+ readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Struct<{
246
+ readonly type: Schema.Literal<"text">;
247
+ readonly text: Schema.String;
248
+ readonly cache_control: Schema.optional<Schema.Struct<{
249
+ readonly type: Schema.Literal<"ephemeral">;
250
+ readonly ttl: Schema.optional<Schema.String>;
251
+ }>>;
252
+ }>>]>;
240
253
  }>]>>;
241
254
  tools: Schema.optional<Schema.$Array<Schema.Struct<{
242
255
  readonly type: Schema.tag<"function">;
@@ -302,6 +315,8 @@ export declare const OpenAIChatEvent: Schema.StructWithRest<Schema.Struct<{
302
315
  readonly total_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
303
316
  readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
304
317
  readonly prompt_cache_hit_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
318
+ readonly cache_read_input_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
319
+ readonly cache_created_input_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
305
320
  readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
306
321
  readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
307
322
  readonly cache_write_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
@@ -319,6 +334,8 @@ export declare const OpenAIChatEvent: Schema.StructWithRest<Schema.Struct<{
319
334
  readonly total_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
320
335
  readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
321
336
  readonly prompt_cache_hit_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
337
+ readonly cache_read_input_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
338
+ readonly cache_created_input_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
322
339
  readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
323
340
  readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
324
341
  readonly cache_write_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
@@ -360,7 +377,7 @@ export interface ParserState {
360
377
  readonly requireFinishReason: boolean;
361
378
  }
362
379
  interface LoweringOptions {
363
- readonly cacheControl?: (cache: CacheHint | undefined) => Schema.Schema.Type<typeof OpenAIChatCacheControl> | undefined;
380
+ readonly cacheControl?: (cache: CacheHint | undefined) => OpenAIChatCacheControl | undefined;
364
381
  readonly toolCallID?: (id: string) => string;
365
382
  }
366
383
  export declare const fromRequest: (request: LLMRequest<import("../schema/options.js").LanguageModel<{
@@ -425,15 +442,18 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
425
442
  })[];
426
443
  } | {
427
444
  readonly [x: string]: unknown;
428
- readonly content: string | null;
445
+ readonly content: string | readonly {
446
+ readonly type: "text";
447
+ readonly text: string;
448
+ readonly cache_control?: {
449
+ readonly type: "ephemeral";
450
+ readonly ttl?: string | undefined;
451
+ } | undefined;
452
+ }[] | null;
429
453
  readonly role: "assistant";
430
454
  readonly reasoning?: string | undefined;
431
455
  readonly reasoning_content?: string | undefined;
432
456
  readonly reasoning_text?: string | undefined;
433
- readonly cache_control?: {
434
- readonly type: "ephemeral";
435
- readonly ttl?: string | undefined;
436
- } | undefined;
437
457
  readonly tool_calls?: readonly {
438
458
  readonly function: {
439
459
  readonly name: string;
@@ -449,13 +469,16 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
449
469
  }[] | undefined;
450
470
  readonly reasoning_details?: unknown;
451
471
  } | {
452
- readonly content: string;
453
472
  readonly role: "tool";
454
473
  readonly tool_call_id: string;
455
- readonly cache_control?: {
456
- readonly type: "ephemeral";
457
- readonly ttl?: string | undefined;
458
- } | undefined;
474
+ readonly content: string | readonly {
475
+ readonly type: "text";
476
+ readonly text: string;
477
+ readonly cache_control?: {
478
+ readonly type: "ephemeral";
479
+ readonly ttl?: string | undefined;
480
+ } | undefined;
481
+ }[];
459
482
  })[];
460
483
  tools: {
461
484
  readonly function: {
@@ -539,15 +562,18 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
539
562
  })[];
540
563
  } | {
541
564
  readonly [x: string]: unknown;
542
- readonly content: string | null;
565
+ readonly content: string | readonly {
566
+ readonly type: "text";
567
+ readonly text: string;
568
+ readonly cache_control?: {
569
+ readonly type: "ephemeral";
570
+ readonly ttl?: string | undefined;
571
+ } | undefined;
572
+ }[] | null;
543
573
  readonly role: "assistant";
544
574
  readonly reasoning?: string | undefined;
545
575
  readonly reasoning_content?: string | undefined;
546
576
  readonly reasoning_text?: string | undefined;
547
- readonly cache_control?: {
548
- readonly type: "ephemeral";
549
- readonly ttl?: string | undefined;
550
- } | undefined;
551
577
  readonly tool_calls?: readonly {
552
578
  readonly function: {
553
579
  readonly name: string;
@@ -563,13 +589,16 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
563
589
  }[] | undefined;
564
590
  readonly reasoning_details?: unknown;
565
591
  } | {
566
- readonly content: string;
567
592
  readonly role: "tool";
568
593
  readonly tool_call_id: string;
569
- readonly cache_control?: {
570
- readonly type: "ephemeral";
571
- readonly ttl?: string | undefined;
572
- } | undefined;
594
+ readonly content: string | readonly {
595
+ readonly type: "text";
596
+ readonly text: string;
597
+ readonly cache_control?: {
598
+ readonly type: "ephemeral";
599
+ readonly ttl?: string | undefined;
600
+ } | undefined;
601
+ }[];
573
602
  })[];
574
603
  tools: {
575
604
  readonly function: {
@@ -646,15 +675,18 @@ export declare const protocol: Protocol<{
646
675
  })[];
647
676
  } | {
648
677
  readonly [x: string]: unknown;
649
- readonly content: string | null;
678
+ readonly content: string | readonly {
679
+ readonly type: "text";
680
+ readonly text: string;
681
+ readonly cache_control?: {
682
+ readonly type: "ephemeral";
683
+ readonly ttl?: string | undefined;
684
+ } | undefined;
685
+ }[] | null;
650
686
  readonly role: "assistant";
651
687
  readonly reasoning?: string | undefined;
652
688
  readonly reasoning_content?: string | undefined;
653
689
  readonly reasoning_text?: string | undefined;
654
- readonly cache_control?: {
655
- readonly type: "ephemeral";
656
- readonly ttl?: string | undefined;
657
- } | undefined;
658
690
  readonly tool_calls?: readonly {
659
691
  readonly function: {
660
692
  readonly name: string;
@@ -670,13 +702,16 @@ export declare const protocol: Protocol<{
670
702
  }[] | undefined;
671
703
  readonly reasoning_details?: unknown;
672
704
  } | {
673
- readonly content: string;
674
705
  readonly role: "tool";
675
706
  readonly tool_call_id: string;
676
- readonly cache_control?: {
677
- readonly type: "ephemeral";
678
- readonly ttl?: string | undefined;
679
- } | undefined;
707
+ readonly content: string | readonly {
708
+ readonly type: "text";
709
+ readonly text: string;
710
+ readonly cache_control?: {
711
+ readonly type: "ephemeral";
712
+ readonly ttl?: string | undefined;
713
+ } | undefined;
714
+ }[];
680
715
  })[];
681
716
  readonly stream: true;
682
717
  readonly max_completion_tokens?: number | undefined;
@@ -724,6 +759,7 @@ export declare const protocol: Protocol<{
724
759
  } | null | undefined;
725
760
  readonly usage?: {
726
761
  readonly [x: string]: unknown;
762
+ readonly cache_read_input_tokens?: number | null | undefined;
727
763
  readonly cached_tokens?: number | null | undefined;
728
764
  readonly prompt_tokens?: number | null | undefined;
729
765
  readonly completion_tokens?: number | null | undefined;
@@ -734,6 +770,7 @@ export declare const protocol: Protocol<{
734
770
  readonly cache_write_tokens?: number | null | undefined;
735
771
  } | null | undefined;
736
772
  readonly prompt_cache_hit_tokens?: number | null | undefined;
773
+ readonly cache_created_input_tokens?: number | null | undefined;
737
774
  readonly completion_tokens_details?: {
738
775
  readonly [x: string]: unknown;
739
776
  readonly reasoning_tokens?: number | null | undefined;
@@ -763,6 +800,7 @@ export declare const protocol: Protocol<{
763
800
  } | null | undefined;
764
801
  readonly usage?: {
765
802
  readonly [x: string]: unknown;
803
+ readonly cache_read_input_tokens?: number | null | undefined;
766
804
  readonly cached_tokens?: number | null | undefined;
767
805
  readonly prompt_tokens?: number | null | undefined;
768
806
  readonly completion_tokens?: number | null | undefined;
@@ -773,6 +811,7 @@ export declare const protocol: Protocol<{
773
811
  readonly cache_write_tokens?: number | null | undefined;
774
812
  } | null | undefined;
775
813
  readonly prompt_cache_hit_tokens?: number | null | undefined;
814
+ readonly cache_created_input_tokens?: number | null | undefined;
776
815
  readonly completion_tokens_details?: {
777
816
  readonly [x: string]: unknown;
778
817
  readonly reasoning_tokens?: number | null | undefined;
@@ -831,15 +870,18 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
831
870
  })[];
832
871
  } | {
833
872
  readonly [x: string]: unknown;
834
- readonly content: string | null;
873
+ readonly content: string | readonly {
874
+ readonly type: "text";
875
+ readonly text: string;
876
+ readonly cache_control?: {
877
+ readonly type: "ephemeral";
878
+ readonly ttl?: string | undefined;
879
+ } | undefined;
880
+ }[] | null;
835
881
  readonly role: "assistant";
836
882
  readonly reasoning?: string | undefined;
837
883
  readonly reasoning_content?: string | undefined;
838
884
  readonly reasoning_text?: string | undefined;
839
- readonly cache_control?: {
840
- readonly type: "ephemeral";
841
- readonly ttl?: string | undefined;
842
- } | undefined;
843
885
  readonly tool_calls?: readonly {
844
886
  readonly function: {
845
887
  readonly name: string;
@@ -855,13 +897,16 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
855
897
  }[] | undefined;
856
898
  readonly reasoning_details?: unknown;
857
899
  } | {
858
- readonly content: string;
859
900
  readonly role: "tool";
860
901
  readonly tool_call_id: string;
861
- readonly cache_control?: {
862
- readonly type: "ephemeral";
863
- readonly ttl?: string | undefined;
864
- } | undefined;
902
+ readonly content: string | readonly {
903
+ readonly type: "text";
904
+ readonly text: string;
905
+ readonly cache_control?: {
906
+ readonly type: "ephemeral";
907
+ readonly ttl?: string | undefined;
908
+ } | undefined;
909
+ }[];
865
910
  })[];
866
911
  readonly stream: true;
867
912
  readonly max_completion_tokens?: number | undefined;
@@ -947,15 +992,18 @@ export declare const route: Route<{
947
992
  })[];
948
993
  } | {
949
994
  readonly [x: string]: unknown;
950
- readonly content: string | null;
995
+ readonly content: string | readonly {
996
+ readonly type: "text";
997
+ readonly text: string;
998
+ readonly cache_control?: {
999
+ readonly type: "ephemeral";
1000
+ readonly ttl?: string | undefined;
1001
+ } | undefined;
1002
+ }[] | null;
951
1003
  readonly role: "assistant";
952
1004
  readonly reasoning?: string | undefined;
953
1005
  readonly reasoning_content?: string | undefined;
954
1006
  readonly reasoning_text?: string | undefined;
955
- readonly cache_control?: {
956
- readonly type: "ephemeral";
957
- readonly ttl?: string | undefined;
958
- } | undefined;
959
1007
  readonly tool_calls?: readonly {
960
1008
  readonly function: {
961
1009
  readonly name: string;
@@ -971,13 +1019,16 @@ export declare const route: Route<{
971
1019
  }[] | undefined;
972
1020
  readonly reasoning_details?: unknown;
973
1021
  } | {
974
- readonly content: string;
975
1022
  readonly role: "tool";
976
1023
  readonly tool_call_id: string;
977
- readonly cache_control?: {
978
- readonly type: "ephemeral";
979
- readonly ttl?: string | undefined;
980
- } | undefined;
1024
+ readonly content: string | readonly {
1025
+ readonly type: "text";
1026
+ readonly text: string;
1027
+ readonly cache_control?: {
1028
+ readonly type: "ephemeral";
1029
+ readonly ttl?: string | undefined;
1030
+ } | undefined;
1031
+ }[];
981
1032
  })[];
982
1033
  readonly stream: true;
983
1034
  readonly max_completion_tokens?: number | undefined;
@@ -83,12 +83,13 @@ const ReasoningDetail = Schema.Union([
83
83
  ]);
84
84
  const decodeReasoningDetail = Schema.decodeUnknownOption(ReasoningDetail);
85
85
  const knownReasoningDetails = (details) => details.flatMap((detail) => Option.toArray(decodeReasoningDetail(detail)));
86
+ const OpenAIChatTextContent = Schema.Struct({
87
+ type: Schema.Literal("text"),
88
+ text: Schema.String,
89
+ cache_control: Schema.optional(OpenAIChatCacheControl),
90
+ });
86
91
  const OpenAIChatUserContent = Schema.Union([
87
- Schema.Struct({
88
- type: Schema.Literal("text"),
89
- text: Schema.String,
90
- cache_control: Schema.optional(OpenAIChatCacheControl),
91
- }),
92
+ OpenAIChatTextContent,
92
93
  Schema.Struct({
93
94
  type: Schema.Literal("image_url"),
94
95
  image_url: Schema.Struct({ url: Schema.String }),
@@ -109,19 +110,17 @@ const OpenAIChatMessage = Schema.Union([
109
110
  }),
110
111
  Schema.StructWithRest(Schema.Struct({
111
112
  role: Schema.Literal("assistant"),
112
- content: Schema.NullOr(Schema.String),
113
+ content: Schema.NullOr(Schema.Union([Schema.String, Schema.Array(OpenAIChatTextContent)])),
113
114
  tool_calls: optionalArray(OpenAIChatAssistantToolCall),
114
115
  reasoning_content: Schema.optional(Schema.String),
115
116
  reasoning: Schema.optional(Schema.String),
116
117
  reasoning_text: Schema.optional(Schema.String),
117
118
  reasoning_details: Schema.optional(Schema.Unknown),
118
- cache_control: Schema.optional(OpenAIChatCacheControl),
119
119
  }), [Schema.Record(Schema.String, Schema.Unknown)]),
120
120
  Schema.Struct({
121
121
  role: Schema.Literal("tool"),
122
122
  tool_call_id: Schema.String,
123
- content: Schema.String,
124
- cache_control: Schema.optional(OpenAIChatCacheControl),
123
+ content: Schema.Union([Schema.String, Schema.Array(OpenAIChatTextContent)]),
125
124
  }),
126
125
  ]).pipe(Schema.toTaggedUnion("role"));
127
126
  const OpenAIChatToolChoice = Schema.Union([
@@ -162,9 +161,11 @@ const OpenAIChatUsage = Schema.StructWithRest(Schema.Struct({
162
161
  prompt_tokens: optionalNull(Schema.Number),
163
162
  completion_tokens: optionalNull(Schema.Number),
164
163
  total_tokens: optionalNull(Schema.Number),
165
- // Zai reports cache hits as top-level `cached_tokens`; DeepSeek uses `prompt_cache_hit_tokens`.
164
+ // Provider-specific cache accounting fields.
166
165
  cached_tokens: optionalNull(Schema.Number),
167
166
  prompt_cache_hit_tokens: optionalNull(Schema.Number),
167
+ cache_read_input_tokens: optionalNull(Schema.Number),
168
+ cache_created_input_tokens: optionalNull(Schema.Number),
168
169
  prompt_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({
169
170
  cached_tokens: optionalNull(Schema.Number),
170
171
  cache_write_tokens: optionalNull(Schema.Number),
@@ -305,7 +306,7 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
305
306
  if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
306
307
  return yield* ProviderShared.unsupportedContent("OpenAI Chat", "assistant", ["text", "reasoning", "tool-call"]);
307
308
  if (part.type === "text") {
308
- content.push(part);
309
+ content.push({ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) });
309
310
  continue;
310
311
  }
311
312
  if (part.type === "reasoning") {
@@ -347,14 +348,17 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
347
348
  return nativeReasoning ?? (requireReasoning ? "" : undefined);
348
349
  return text;
349
350
  })();
350
- const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined);
351
- const cacheControl = options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined);
352
351
  const result = {
353
352
  role: "assistant",
354
- content: content.length > 0 ? content.map((part) => part.text).join("") : toolCalls.length > 0 ? null : "",
353
+ content: (() => {
354
+ if (content.some((part) => part.cache_control !== undefined))
355
+ return content;
356
+ if (content.length === 0 && toolCalls.length > 0)
357
+ return null;
358
+ return content.map((part) => part.text).join("");
359
+ })(),
355
360
  ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
356
361
  ...(details !== undefined ? { reasoning_details: details } : {}),
357
- ...(cacheControl !== undefined ? { cache_control: cacheControl } : {}),
358
362
  };
359
363
  if (field === undefined || reasoningText === undefined)
360
364
  return result;
@@ -367,27 +371,23 @@ const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (m
367
371
  if (!ProviderShared.supportsContent(part, ["tool-result"]))
368
372
  return yield* ProviderShared.unsupportedContent("OpenAI Chat", "tool", ["tool-result"]);
369
373
  if (part.result.type !== "content") {
370
- messages.push({
371
- role: "tool",
372
- tool_call_id: options.toolCallID?.(part.id) ?? part.id,
373
- content: ProviderShared.toolResultText(part),
374
- cache_control: options.cacheControl?.(part.cache),
375
- });
374
+ messages.push(toolMessage(options.toolCallID?.(part.id) ?? part.id, ProviderShared.toolResultText(part), options.cacheControl?.(part.cache)));
376
375
  continue;
377
376
  }
378
377
  const content = part.result.value;
379
378
  const text = content.filter((item) => item.type === "text").map((item) => item.text);
380
- messages.push({
381
- role: "tool",
382
- tool_call_id: options.toolCallID?.(part.id) ?? part.id,
383
- content: text.join("\n"),
384
- cache_control: options.cacheControl?.(part.cache),
385
- });
379
+ messages.push(toolMessage(options.toolCallID?.(part.id) ?? part.id, text.join("\n"), options.cacheControl?.(part.cache)));
386
380
  const files = content.filter((item) => item.type === "file");
387
381
  attachments.push(...(yield* Effect.forEach(files, (item) => lowerMedia(ProviderShared.toolFileMedia(item)))));
388
382
  }
389
383
  return { messages, attachments };
390
384
  });
385
+ // Chat cache breakpoints belong on text content parts, not on the message itself.
386
+ const toolMessage = (toolCallID, text, cacheControl) => ({
387
+ role: "tool",
388
+ tool_call_id: toolCallID,
389
+ content: cacheControl === undefined ? text : [{ type: "text", text, cache_control: cacheControl }],
390
+ });
391
391
  const lowerMessage = Effect.fn("OpenAIChat.lowerMessage")(function* (message, reasoningField, requireReasoning, options) {
392
392
  if (message.role === "user")
393
393
  return [yield* lowerUserMessage(message, options)];
@@ -452,24 +452,25 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
452
452
  bridgeTools();
453
453
  if (message.role === "system") {
454
454
  const part = yield* ProviderShared.wrappedSystemUpdate("OpenAI Chat", message);
455
+ const cacheControl = options.cacheControl?.(part.cache);
455
456
  if (pendingAttachments.length > 0) {
456
457
  messages.push({
457
458
  role: "user",
458
459
  content: [
459
460
  ...pendingAttachments.splice(0),
460
- { type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
461
+ { type: "text", text: part.text, cache_control: cacheControl },
461
462
  ],
462
463
  });
463
464
  continue;
464
465
  }
465
466
  const previous = messages.at(-1);
466
467
  if (previous?.role === "user" && typeof previous.content === "string")
467
- messages[messages.length - 1] = options.cacheControl?.(part.cache)
468
+ messages[messages.length - 1] = cacheControl
468
469
  ? {
469
470
  role: "user",
470
471
  content: [
471
472
  { type: "text", text: previous.content },
472
- { type: "text", text: part.text, cache_control: options.cacheControl(part.cache) },
473
+ { type: "text", text: part.text, cache_control: cacheControl },
473
474
  ],
474
475
  }
475
476
  : { role: "user", content: `${previous.content}\n${part.text}` };
@@ -478,14 +479,14 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
478
479
  role: "user",
479
480
  content: [
480
481
  ...previous.content,
481
- { type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
482
+ { type: "text", text: part.text, cache_control: cacheControl },
482
483
  ],
483
484
  };
484
485
  else
485
- messages.push(options.cacheControl?.(part.cache)
486
+ messages.push(cacheControl
486
487
  ? {
487
488
  role: "user",
488
- content: [{ type: "text", text: part.text, cache_control: options.cacheControl(part.cache) }],
489
+ content: [{ type: "text", text: part.text, cache_control: cacheControl }],
489
490
  }
490
491
  : { role: "user", content: part.text });
491
492
  continue;
@@ -720,17 +721,19 @@ const mapFinishReason = Effect.fn("OpenAIChat.mapFinishReason")(function* (event
720
721
  // satisfied on both sides.
721
722
  // Providers differ on cache-hit location: OpenAI uses
722
723
  // `prompt_tokens_details.cached_tokens`, DeepSeek uses
723
- // `prompt_cache_hit_tokens`, and Zai uses top-level `cached_tokens`.
724
+ // `prompt_cache_hit_tokens`, Zai uses top-level `cached_tokens`, and
725
+ // DigitalOcean uses top-level `cache_read_input_tokens` / `cache_created_input_tokens`.
724
726
  const mapUsage = (usage, providerMetadataKey) => {
725
727
  if (!usage)
726
728
  return undefined;
727
729
  const input = usage.prompt_tokens ?? undefined;
728
730
  const output = usage.completion_tokens ?? undefined;
729
- const cached = (usage.prompt_tokens_details?.cached_tokens ??
731
+ const cached = usage.prompt_tokens_details?.cached_tokens ??
730
732
  usage.prompt_cache_hit_tokens ??
731
733
  usage.cached_tokens ??
732
- undefined);
733
- const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens ?? undefined;
734
+ usage.cache_read_input_tokens ??
735
+ undefined;
736
+ const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens ?? usage.cache_created_input_tokens ?? undefined;
734
737
  const reasoning = usage.completion_tokens_details?.reasoning_tokens ?? undefined;
735
738
  const nonCached = ProviderShared.subtractTokens(input, ProviderShared.sumTokens(cached, cacheWrite));
736
739
  return new Usage({
@@ -53,15 +53,18 @@ export declare const route: Route<{
53
53
  })[];
54
54
  } | {
55
55
  readonly [x: string]: unknown;
56
- readonly content: string | null;
56
+ readonly content: string | readonly {
57
+ readonly type: "text";
58
+ readonly text: string;
59
+ readonly cache_control?: {
60
+ readonly type: "ephemeral";
61
+ readonly ttl?: string | undefined;
62
+ } | undefined;
63
+ }[] | null;
57
64
  readonly role: "assistant";
58
65
  readonly reasoning?: string | undefined;
59
66
  readonly reasoning_content?: string | undefined;
60
67
  readonly reasoning_text?: string | undefined;
61
- readonly cache_control?: {
62
- readonly type: "ephemeral";
63
- readonly ttl?: string | undefined;
64
- } | undefined;
65
68
  readonly tool_calls?: readonly {
66
69
  readonly function: {
67
70
  readonly name: string;
@@ -77,13 +80,16 @@ export declare const route: Route<{
77
80
  }[] | undefined;
78
81
  readonly reasoning_details?: unknown;
79
82
  } | {
80
- readonly content: string;
81
83
  readonly role: "tool";
82
84
  readonly tool_call_id: string;
83
- readonly cache_control?: {
84
- readonly type: "ephemeral";
85
- readonly ttl?: string | undefined;
86
- } | undefined;
85
+ readonly content: string | readonly {
86
+ readonly type: "text";
87
+ readonly text: string;
88
+ readonly cache_control?: {
89
+ readonly type: "ephemeral";
90
+ readonly ttl?: string | undefined;
91
+ } | undefined;
92
+ }[];
87
93
  })[];
88
94
  readonly stream: true;
89
95
  readonly max_completion_tokens?: number | undefined;
@@ -111,8 +111,8 @@ const adapter = {
111
111
  name: NAME,
112
112
  restoreHostedToolItem: (item) => (Schema.is(OpenAIResponsesHostedToolItem)(item) ? item : undefined),
113
113
  };
114
- // GPT-6 Astra, Sol, and Luna accept `configuration_update` only in standard mode (not `reasoning.mode: "pro"` or
115
- // `-pro` slugs), and never alongside automatic `context_management` compaction.
114
+ // GPT-6 and later default to `configuration_update` support, except in `reasoning.mode: "pro"`
115
+ // or alongside automatic `context_management` compaction.
116
116
  const supportsEffortUpdates = (request) => {
117
117
  if (request.providerOptions?.contextManagement !== undefined)
118
118
  return false;
@@ -121,7 +121,8 @@ const supportsEffortUpdates = (request) => {
121
121
  const override = request.model.compatibility?.supportsEffortUpdates;
122
122
  if (override !== undefined)
123
123
  return override;
124
- return /(?:^|\/)gpt-6-(?:astra|sol|luna)$/i.test(request.model.id);
124
+ const match = /(?:^|\/)gpt-(\d+)(?:\.\d+)?(?:-|$)/i.exec(request.model.id);
125
+ return match !== null && Number(match[1]) >= 6;
125
126
  };
126
127
  const nativeImageToolInput = (tool) => {
127
128
  const native = tool.native?.openai;
@@ -1,6 +1,11 @@
1
+ import type { CacheHint } from "../../schema/index.js";
1
2
  export interface Breakpoints {
2
3
  remaining: number;
3
4
  dropped: number;
4
5
  }
5
6
  export declare const newBreakpoints: (cap: number) => Breakpoints;
6
7
  export declare const ttlBucket: (ttlSeconds: number | undefined) => "1h" | undefined;
8
+ export declare const cacheControl: () => (cache: CacheHint | undefined) => {
9
+ type: "ephemeral";
10
+ ttl: "1h" | undefined;
11
+ } | undefined;
@@ -1,5 +1,13 @@
1
- // Shared counter and TTL mapping for provider cache-marker lowering.
2
1
  export const newBreakpoints = (cap) => ({ remaining: cap, dropped: 0 });
3
2
  // Requests of at least one hour use the explicit `"1h"` bucket; shorter
4
3
  // requests omit the wire TTL and use the provider default.
5
4
  export const ttlBucket = (ttlSeconds) => ttlSeconds !== undefined && ttlSeconds >= 3600 ? "1h" : undefined;
5
+ export const cacheControl = () => {
6
+ const breakpoints = newBreakpoints(4);
7
+ return (cache) => {
8
+ if (cache === undefined || breakpoints.remaining === 0)
9
+ return undefined;
10
+ breakpoints.remaining -= 1;
11
+ return { type: "ephemeral", ttl: ttlBucket(cache.ttlSeconds) };
12
+ };
13
+ };