@opencode/ai 2.0.13 → 2.0.15

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 (86) hide show
  1. package/README.md +115 -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 +69 -0
  9. package/dist/generation.js +84 -0
  10. package/dist/image-client.d.ts +6 -4
  11. package/dist/image-client.js +11 -11
  12. package/dist/image.d.ts +1040 -62
  13. package/dist/image.js +80 -52
  14. package/dist/index.d.ts +5 -2
  15. package/dist/index.js +3 -1
  16. package/dist/llm.d.ts +9 -1
  17. package/dist/media.d.ts +212 -0
  18. package/dist/media.js +228 -0
  19. package/dist/promise.d.ts +554 -0
  20. package/dist/promise.js +44 -0
  21. package/dist/protocols/anthropic-messages.js +7 -17
  22. package/dist/protocols/bedrock-converse.d.ts +4 -4
  23. package/dist/protocols/bedrock-converse.js +1 -6
  24. package/dist/protocols/gemini.d.ts +21 -0
  25. package/dist/protocols/gemini.js +42 -6
  26. package/dist/protocols/google-images.d.ts +9 -21
  27. package/dist/protocols/google-images.js +169 -132
  28. package/dist/protocols/meta-images.d.ts +7 -12
  29. package/dist/protocols/meta-images.js +92 -66
  30. package/dist/protocols/mistral-chat.js +7 -6
  31. package/dist/protocols/open-responses.d.ts +11 -3
  32. package/dist/protocols/open-responses.js +23 -12
  33. package/dist/protocols/openai-chat.d.ts +34 -1
  34. package/dist/protocols/openai-chat.js +100 -31
  35. package/dist/protocols/openai-images.d.ts +9 -20
  36. package/dist/protocols/openai-images.js +112 -142
  37. package/dist/protocols/shared.d.ts +17 -17
  38. package/dist/protocols/shared.js +32 -35
  39. package/dist/protocols/utils/bedrock-media.d.ts +2 -3
  40. package/dist/protocols/utils/bedrock-media.js +4 -4
  41. package/dist/protocols/utils/media-input.d.ts +10 -0
  42. package/dist/protocols/utils/media-input.js +17 -0
  43. package/dist/protocols/utils/responses-compaction.js +6 -5
  44. package/dist/protocols/utils/tool-stream.d.ts +27 -3
  45. package/dist/protocols/xai-images.d.ts +9 -15
  46. package/dist/protocols/xai-images.js +85 -83
  47. package/dist/protocols/zai-images.d.ts +9 -13
  48. package/dist/protocols/zai-images.js +59 -57
  49. package/dist/providers/amazon-bedrock.d.ts +2 -2
  50. package/dist/providers/cerebras.js +6 -1
  51. package/dist/providers/deepinfra.js +6 -1
  52. package/dist/providers/google-vertex.d.ts +7 -0
  53. package/dist/providers/google.d.ts +7 -0
  54. package/dist/providers/index.d.ts +1 -0
  55. package/dist/providers/index.js +1 -0
  56. package/dist/providers/openrouter.d.ts +19 -0
  57. package/dist/providers/openrouter.js +13 -1
  58. package/dist/providers/vercel-ai-gateway.d.ts +41 -0
  59. package/dist/providers/vercel-ai-gateway.js +85 -0
  60. package/dist/route/client.d.ts +9 -1
  61. package/dist/route/endpoint.d.ts +10 -10
  62. package/dist/route/executor-service.d.ts +12 -0
  63. package/dist/route/executor-service.js +3 -0
  64. package/dist/route/executor.d.ts +4 -9
  65. package/dist/route/executor.js +3 -3
  66. package/dist/route/index.d.ts +2 -0
  67. package/dist/route/index.js +2 -0
  68. package/dist/route/media-protocol.d.ts +45 -0
  69. package/dist/route/media-protocol.js +40 -0
  70. package/dist/route/media.d.ts +46 -0
  71. package/dist/route/media.js +64 -0
  72. package/dist/schema/errors.d.ts +13 -3
  73. package/dist/schema/errors.js +7 -0
  74. package/dist/schema/events.d.ts +563 -40
  75. package/dist/schema/events.js +35 -2
  76. package/dist/schema/messages.d.ts +98 -8
  77. package/dist/schema/messages.js +8 -6
  78. package/dist/schema/options.d.ts +2 -0
  79. package/dist/schema/options.js +3 -0
  80. package/dist/testing.d.ts +72 -8
  81. package/dist/utils/media-type.d.ts +6 -0
  82. package/dist/utils/media-type.js +49 -0
  83. package/dist/utils/sanitize.js +3 -1
  84. package/package.json +7 -3
  85. package/dist/protocols/utils/image-input.d.ts +0 -21
  86. package/dist/protocols/utils/image-input.js +0 -20
@@ -1,10 +1,11 @@
1
1
  import { Schema } from "effect";
2
2
  import { LLM } from "@opencode/schema/llm";
3
3
  import { ContentBlockID, ToolCallID } from "./ids.js";
4
- import { Message, CompactionPart, ProviderMetadata, ToolCallPart, ToolOutput, ToolResultPart, ToolResultValue, } from "./messages.js";
4
+ import { Message, CompactionPart, ToolCallPart, ToolOutput, ToolResultPart, ToolResultValue, } from "./messages.js";
5
+ import { ProviderMetadata } from "./options.js";
5
6
  import { ProviderFailureClassification } from "./errors.js";
7
+ import { Media } from "../media.js";
6
8
  export const FinishReason = LLM.FinishReason;
7
- export { ProviderMetadata } from "./messages.js";
8
9
  /**
9
10
  * Token usage reported by an LLM provider.
10
11
  *
@@ -75,6 +76,25 @@ export class Usage extends Schema.Class("AI.Usage")({
75
76
  return input instanceof Usage ? input : new Usage(input);
76
77
  }
77
78
  }
79
+ /**
80
+ * Usage reported by media routes. Providers bill images, video, speech, and transcription in different units, so
81
+ * each response carries the unit it was actually metered in instead of forcing everything into tokens.
82
+ */
83
+ export const MediaUsage = Schema.Union([
84
+ Schema.Struct({
85
+ type: Schema.Literal("tokens"),
86
+ input: Schema.optional(Schema.Number),
87
+ output: Schema.optional(Schema.Number),
88
+ total: Schema.optional(Schema.Number),
89
+ details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
90
+ }),
91
+ Schema.Struct({ type: Schema.Literal("seconds"), seconds: Schema.Number }),
92
+ Schema.Struct({ type: Schema.Literal("characters"), characters: Schema.Number }),
93
+ Schema.Struct({ type: Schema.Literal("credits"), credits: Schema.Number }),
94
+ Schema.Struct({ type: Schema.Literal("compute"), seconds: Schema.Number }),
95
+ ])
96
+ .pipe(Schema.toTaggedUnion("type"))
97
+ .annotate({ identifier: "AI.MediaUsage" });
78
98
  /** A replacement context window, not an assistant message to append to prior history. */
79
99
  export class CompactionResponse extends Schema.Class("LLM.CompactionResponse")({
80
100
  replacement: Schema.Array(Message),
@@ -205,6 +225,12 @@ export const Finish = Schema.Struct({
205
225
  usage: Schema.optional(Usage),
206
226
  providerMetadata: Schema.optional(ProviderMetadata),
207
227
  }).annotate({ identifier: "LLM.Event.Finish" });
228
+ /** A generated media asset (image, audio, …) emitted by the model as first-class output rather than a tool result. */
229
+ export const MediaEvent = Schema.Struct({
230
+ type: Schema.tag("media"),
231
+ media: Media.AssetSchema,
232
+ providerMetadata: Schema.optional(ProviderMetadata),
233
+ }).annotate({ identifier: "LLM.Event.Media" });
208
234
  export const ProviderErrorEvent = Schema.Struct({
209
235
  type: Schema.tag("provider-error"),
210
236
  message: Schema.String,
@@ -227,6 +253,7 @@ const llmEventTagged = Schema.Union([
227
253
  ToolCall,
228
254
  ToolResult,
229
255
  ToolError,
256
+ MediaEvent,
230
257
  StepFinish,
231
258
  Finish,
232
259
  ProviderErrorEvent,
@@ -258,6 +285,7 @@ export const LLMEvent = Object.assign(llmEventTagged, {
258
285
  output: input.output === undefined ? undefined : ToolOutput.make(input.output.structured, input.output.content),
259
286
  }),
260
287
  toolError: (input) => ToolError.make({ ...input, id: toolCallID(input.id) }),
288
+ media: MediaEvent.make,
261
289
  stepFinish: (input) => StepFinish.make({
262
290
  ...input,
263
291
  usage: input.usage === undefined ? undefined : Usage.from(input.usage),
@@ -283,6 +311,7 @@ export const LLMEvent = Object.assign(llmEventTagged, {
283
311
  toolCall: llmEventTagged.guards["tool-call"],
284
312
  toolResult: llmEventTagged.guards["tool-result"],
285
313
  toolError: llmEventTagged.guards["tool-error"],
314
+ media: llmEventTagged.guards.media,
286
315
  stepFinish: llmEventTagged.guards["step-finish"],
287
316
  finish: llmEventTagged.guards.finish,
288
317
  providerError: llmEventTagged.guards["provider-error"],
@@ -500,6 +529,10 @@ const reduceResponseState = (state, event) => {
500
529
  return reduceToolCall(next, event);
501
530
  case "tool-result":
502
531
  return appendContent(next, toolResultContent(event));
532
+ case "media":
533
+ return appendContent(next, event.providerMetadata === undefined
534
+ ? { type: "media", media: event.media }
535
+ : { type: "media", media: event.media, providerMetadata: event.providerMetadata });
503
536
  default:
504
537
  return next;
505
538
  }
@@ -1,10 +1,9 @@
1
1
  import { Schema } from "effect";
2
2
  import { Tool } from "@opencode/schema/tool";
3
3
  import { CacheHint, GenerationOptions, HttpOptions, type LanguageModel, ReasoningEffort } from "./options.js";
4
+ import { Media } from "../media.js";
4
5
  export declare const MessageRole: Schema.Literals<readonly ["system", "user", "assistant", "tool"]>;
5
6
  export type MessageRole = Schema.Schema.Type<typeof MessageRole>;
6
- export declare const ProviderMetadata: Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>;
7
- export type ProviderMetadata = Schema.Schema.Type<typeof ProviderMetadata>;
8
7
  declare const systemPartSchema: Schema.Struct<{
9
8
  readonly type: Schema.Literal<"text">;
10
9
  readonly text: Schema.String;
@@ -31,8 +30,38 @@ export declare const TextPart: Schema.Struct<{
31
30
  export type TextPart = Schema.Schema.Type<typeof TextPart>;
32
31
  export declare const MediaPart: Schema.Struct<{
33
32
  readonly type: Schema.Literal<"media">;
34
- readonly mediaType: Schema.String;
35
- readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
33
+ readonly media: Schema.decodeTo<Schema.declare<Media.Asset, Media.Asset>, Schema.Struct<{
34
+ readonly source: Schema.Union<readonly [Schema.Struct<{
35
+ readonly type: Schema.Literal<"bytes">;
36
+ readonly data: Schema.Uint8Array;
37
+ readonly mediaType: Schema.String;
38
+ }>, Schema.Struct<{
39
+ readonly type: Schema.Literal<"base64">;
40
+ readonly data: Schema.String;
41
+ readonly mediaType: Schema.String;
42
+ }>, Schema.Struct<{
43
+ readonly type: Schema.Literal<"url">;
44
+ readonly url: Schema.String;
45
+ readonly mediaType: Schema.optional<Schema.String>;
46
+ readonly expiresAt: Schema.optional<Schema.Number>;
47
+ readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
48
+ }>, Schema.Struct<{
49
+ readonly type: Schema.Literal<"ref">;
50
+ readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
51
+ readonly id: Schema.String;
52
+ readonly mediaType: Schema.optional<Schema.String>;
53
+ }>]>;
54
+ readonly info: Schema.optional<Schema.Struct<{
55
+ readonly width: Schema.optional<Schema.Number>;
56
+ readonly height: Schema.optional<Schema.Number>;
57
+ readonly durationSeconds: Schema.optional<Schema.Number>;
58
+ readonly sampleRate: Schema.optional<Schema.Number>;
59
+ readonly channels: Schema.optional<Schema.Number>;
60
+ readonly encoding: Schema.optional<Schema.String>;
61
+ readonly format: Schema.optional<Schema.String>;
62
+ }>>;
63
+ readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
64
+ }>, never, never>;
36
65
  readonly filename: Schema.optional<Schema.String>;
37
66
  readonly cache: Schema.optional<typeof CacheHint>;
38
67
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
@@ -262,8 +291,38 @@ export declare const ContentPart: Schema.toTaggedUnion<"type", readonly [Schema.
262
291
  readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
263
292
  }>, Schema.Struct<{
264
293
  readonly type: Schema.Literal<"media">;
265
- readonly mediaType: Schema.String;
266
- readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
294
+ readonly media: Schema.decodeTo<Schema.declare<Media.Asset, Media.Asset>, Schema.Struct<{
295
+ readonly source: Schema.Union<readonly [Schema.Struct<{
296
+ readonly type: Schema.Literal<"bytes">;
297
+ readonly data: Schema.Uint8Array;
298
+ readonly mediaType: Schema.String;
299
+ }>, Schema.Struct<{
300
+ readonly type: Schema.Literal<"base64">;
301
+ readonly data: Schema.String;
302
+ readonly mediaType: Schema.String;
303
+ }>, Schema.Struct<{
304
+ readonly type: Schema.Literal<"url">;
305
+ readonly url: Schema.String;
306
+ readonly mediaType: Schema.optional<Schema.String>;
307
+ readonly expiresAt: Schema.optional<Schema.Number>;
308
+ readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
309
+ }>, Schema.Struct<{
310
+ readonly type: Schema.Literal<"ref">;
311
+ readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
312
+ readonly id: Schema.String;
313
+ readonly mediaType: Schema.optional<Schema.String>;
314
+ }>]>;
315
+ readonly info: Schema.optional<Schema.Struct<{
316
+ readonly width: Schema.optional<Schema.Number>;
317
+ readonly height: Schema.optional<Schema.Number>;
318
+ readonly durationSeconds: Schema.optional<Schema.Number>;
319
+ readonly sampleRate: Schema.optional<Schema.Number>;
320
+ readonly channels: Schema.optional<Schema.Number>;
321
+ readonly encoding: Schema.optional<Schema.String>;
322
+ readonly format: Schema.optional<Schema.String>;
323
+ }>>;
324
+ readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
325
+ }>, never, never>;
267
326
  readonly filename: Schema.optional<Schema.String>;
268
327
  readonly cache: Schema.optional<typeof CacheHint>;
269
328
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
@@ -377,8 +436,38 @@ declare const Message_base: Schema.Class<Message, Schema.Struct<{
377
436
  readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
378
437
  }>, Schema.Struct<{
379
438
  readonly type: Schema.Literal<"media">;
380
- readonly mediaType: Schema.String;
381
- readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
439
+ readonly media: Schema.decodeTo<Schema.declare<Media.Asset, Media.Asset>, Schema.Struct<{
440
+ readonly source: Schema.Union<readonly [Schema.Struct<{
441
+ readonly type: Schema.Literal<"bytes">;
442
+ readonly data: Schema.Uint8Array;
443
+ readonly mediaType: Schema.String;
444
+ }>, Schema.Struct<{
445
+ readonly type: Schema.Literal<"base64">;
446
+ readonly data: Schema.String;
447
+ readonly mediaType: Schema.String;
448
+ }>, Schema.Struct<{
449
+ readonly type: Schema.Literal<"url">;
450
+ readonly url: Schema.String;
451
+ readonly mediaType: Schema.optional<Schema.String>;
452
+ readonly expiresAt: Schema.optional<Schema.Number>;
453
+ readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
454
+ }>, Schema.Struct<{
455
+ readonly type: Schema.Literal<"ref">;
456
+ readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
457
+ readonly id: Schema.String;
458
+ readonly mediaType: Schema.optional<Schema.String>;
459
+ }>]>;
460
+ readonly info: Schema.optional<Schema.Struct<{
461
+ readonly width: Schema.optional<Schema.Number>;
462
+ readonly height: Schema.optional<Schema.Number>;
463
+ readonly durationSeconds: Schema.optional<Schema.Number>;
464
+ readonly sampleRate: Schema.optional<Schema.Number>;
465
+ readonly channels: Schema.optional<Schema.Number>;
466
+ readonly encoding: Schema.optional<Schema.String>;
467
+ readonly format: Schema.optional<Schema.String>;
468
+ }>>;
469
+ readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
470
+ }>, never, never>;
382
471
  readonly filename: Schema.optional<Schema.String>;
383
472
  readonly cache: Schema.optional<typeof CacheHint>;
384
473
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
@@ -493,6 +582,7 @@ export declare namespace Message {
493
582
  readonly content: ContentInput;
494
583
  };
495
584
  const text: (value: string) => ContentPart;
585
+ const media: (asset: Media.Asset, options?: Omit<MediaPart, "type" | "media">) => MediaPart;
496
586
  const content: (input: ContentInput) => any[];
497
587
  const make: (input: Message | Input) => Message;
498
588
  const user: (content: ContentInput) => Message;
@@ -1,11 +1,9 @@
1
1
  import { Schema } from "effect";
2
2
  import { Tool } from "@opencode/schema/tool";
3
- import { CacheHint, CachePolicy, GenerationOptions, HttpOptions, JsonSchema, LanguageModelSchema, ProviderOptions, ReasoningEffort, } from "./options.js";
3
+ import { CacheHint, CachePolicy, GenerationOptions, HttpOptions, JsonSchema, LanguageModelSchema, ProviderMetadata, ProviderOptions, ReasoningEffort, } from "./options.js";
4
4
  import { ProviderID } from "./ids.js";
5
+ import { Media } from "../media.js";
5
6
  export const MessageRole = Schema.Literals(["system", "user", "assistant", "tool"]);
6
- export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown)).annotate({
7
- identifier: "LLM.ProviderMetadata",
8
- });
9
7
  const systemPartSchema = Schema.Struct({
10
8
  type: Schema.Literal("text"),
11
9
  text: Schema.String,
@@ -30,8 +28,7 @@ export const TextPart = Schema.Struct({
30
28
  }).annotate({ identifier: "LLM.Content.Text" });
31
29
  export const MediaPart = Schema.Struct({
32
30
  type: Schema.Literal("media"),
33
- mediaType: Schema.String,
34
- data: Schema.Union([Schema.String, Schema.Uint8Array]),
31
+ media: Media.AssetSchema,
35
32
  filename: Schema.optional(Schema.String),
36
33
  cache: Schema.optional(CacheHint),
37
34
  metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
@@ -184,6 +181,11 @@ export class Message extends Schema.Class("LLM.Message")({
184
181
  }
185
182
  (function (Message) {
186
183
  Message.text = (value) => ({ type: "text", text: value });
184
+ Message.media = (asset, options) => ({
185
+ type: "media",
186
+ media: asset,
187
+ ...options,
188
+ });
187
189
  Message.content = (input) => typeof input === "string" ? [Message.text(input)] : Array.isArray(input) ? [...input] : [input];
188
190
  Message.make = (input) => {
189
191
  if (input instanceof Message)
@@ -6,6 +6,8 @@ export type JsonSchema = Schema.Schema.Type<typeof JsonSchema>;
6
6
  export declare const mergeJsonRecords: (...items: ReadonlyArray<Record<string, unknown> | undefined>) => Record<string, unknown> | undefined;
7
7
  export declare const ProviderOptions: Schema.$Record<Schema.String, Schema.Unknown>;
8
8
  export type ProviderOptions = Schema.Schema.Type<typeof ProviderOptions>;
9
+ export declare const ProviderMetadata: Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>;
10
+ export type ProviderMetadata = Schema.Schema.Type<typeof ProviderMetadata>;
9
11
  export declare const mergeProviderOptions: (...items: ReadonlyArray<ProviderOptions | undefined>) => ProviderOptions | undefined;
10
12
  declare const HttpOptions_base: Schema.Class<HttpOptions, Schema.Struct<{
11
13
  readonly body: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
@@ -28,6 +28,9 @@ const mergeStringRecords = (...items) => {
28
28
  return Object.keys(result).length === 0 ? undefined : result;
29
29
  };
30
30
  export const ProviderOptions = Schema.Record(Schema.String, Schema.Unknown);
31
+ export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown)).annotate({
32
+ identifier: "LLM.ProviderMetadata",
33
+ });
31
34
  export const mergeProviderOptions = (...items) => mergeJsonRecords(...items);
32
35
  export class HttpOptions extends Schema.Class("AI.HttpOptions")({
33
36
  body: Schema.optional(JsonSchema),
package/dist/testing.d.ts CHANGED
@@ -215,6 +215,14 @@ export declare const complete: (options: {
215
215
  };
216
216
  } | undefined;
217
217
  readonly namespace?: string | undefined;
218
+ } | {
219
+ readonly type: "media";
220
+ readonly media: import("./media.js").Asset;
221
+ readonly providerMetadata?: {
222
+ readonly [x: string]: {
223
+ readonly [x: string]: unknown;
224
+ };
225
+ } | undefined;
218
226
  } | {
219
227
  readonly type: "step-finish";
220
228
  readonly reason: {
@@ -243,12 +251,12 @@ export declare const complete: (options: {
243
251
  } | {
244
252
  readonly type: "provider-error";
245
253
  readonly message: string;
254
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
246
255
  readonly providerMetadata?: {
247
256
  readonly [x: string]: {
248
257
  readonly [x: string]: unknown;
249
258
  };
250
259
  } | undefined;
251
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
252
260
  })[];
253
261
  export declare const stop: (...events: readonly LLMEvent[]) => (({
254
262
  readonly type: "compaction";
@@ -419,6 +427,14 @@ export declare const stop: (...events: readonly LLMEvent[]) => (({
419
427
  };
420
428
  } | undefined;
421
429
  readonly namespace?: string | undefined;
430
+ } | {
431
+ readonly type: "media";
432
+ readonly media: import("./media.js").Asset;
433
+ readonly providerMetadata?: {
434
+ readonly [x: string]: {
435
+ readonly [x: string]: unknown;
436
+ };
437
+ } | undefined;
422
438
  } | {
423
439
  readonly type: "step-finish";
424
440
  readonly reason: {
@@ -447,12 +463,12 @@ export declare const stop: (...events: readonly LLMEvent[]) => (({
447
463
  } | {
448
464
  readonly type: "provider-error";
449
465
  readonly message: string;
466
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
450
467
  readonly providerMetadata?: {
451
468
  readonly [x: string]: {
452
469
  readonly [x: string]: unknown;
453
470
  };
454
471
  } | undefined;
455
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
456
472
  })[];
457
473
  export declare const toolCalls: (...events: readonly LLMEvent[]) => (({
458
474
  readonly type: "compaction";
@@ -623,6 +639,14 @@ export declare const toolCalls: (...events: readonly LLMEvent[]) => (({
623
639
  };
624
640
  } | undefined;
625
641
  readonly namespace?: string | undefined;
642
+ } | {
643
+ readonly type: "media";
644
+ readonly media: import("./media.js").Asset;
645
+ readonly providerMetadata?: {
646
+ readonly [x: string]: {
647
+ readonly [x: string]: unknown;
648
+ };
649
+ } | undefined;
626
650
  } | {
627
651
  readonly type: "step-finish";
628
652
  readonly reason: {
@@ -651,12 +675,12 @@ export declare const toolCalls: (...events: readonly LLMEvent[]) => (({
651
675
  } | {
652
676
  readonly type: "provider-error";
653
677
  readonly message: string;
678
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
654
679
  readonly providerMetadata?: {
655
680
  readonly [x: string]: {
656
681
  readonly [x: string]: unknown;
657
682
  };
658
683
  } | undefined;
659
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
660
684
  })[];
661
685
  export declare const text: (value: string, id: string) => (({
662
686
  readonly type: "compaction";
@@ -827,6 +851,14 @@ export declare const text: (value: string, id: string) => (({
827
851
  };
828
852
  } | undefined;
829
853
  readonly namespace?: string | undefined;
854
+ } | {
855
+ readonly type: "media";
856
+ readonly media: import("./media.js").Asset;
857
+ readonly providerMetadata?: {
858
+ readonly [x: string]: {
859
+ readonly [x: string]: unknown;
860
+ };
861
+ } | undefined;
830
862
  } | {
831
863
  readonly type: "step-finish";
832
864
  readonly reason: {
@@ -855,12 +887,12 @@ export declare const text: (value: string, id: string) => (({
855
887
  } | {
856
888
  readonly type: "provider-error";
857
889
  readonly message: string;
890
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
858
891
  readonly providerMetadata?: {
859
892
  readonly [x: string]: {
860
893
  readonly [x: string]: unknown;
861
894
  };
862
895
  } | undefined;
863
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
864
896
  })[];
865
897
  export declare const textWithUsage: (value: string, id: string, inputTokens: number) => (({
866
898
  readonly type: "compaction";
@@ -1031,6 +1063,14 @@ export declare const textWithUsage: (value: string, id: string, inputTokens: num
1031
1063
  };
1032
1064
  } | undefined;
1033
1065
  readonly namespace?: string | undefined;
1066
+ } | {
1067
+ readonly type: "media";
1068
+ readonly media: import("./media.js").Asset;
1069
+ readonly providerMetadata?: {
1070
+ readonly [x: string]: {
1071
+ readonly [x: string]: unknown;
1072
+ };
1073
+ } | undefined;
1034
1074
  } | {
1035
1075
  readonly type: "step-finish";
1036
1076
  readonly reason: {
@@ -1059,12 +1099,12 @@ export declare const textWithUsage: (value: string, id: string, inputTokens: num
1059
1099
  } | {
1060
1100
  readonly type: "provider-error";
1061
1101
  readonly message: string;
1102
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1062
1103
  readonly providerMetadata?: {
1063
1104
  readonly [x: string]: {
1064
1105
  readonly [x: string]: unknown;
1065
1106
  };
1066
1107
  } | undefined;
1067
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1068
1108
  })[];
1069
1109
  export declare const tool: (id: string, name: string, input: unknown) => (({
1070
1110
  readonly type: "compaction";
@@ -1235,6 +1275,14 @@ export declare const tool: (id: string, name: string, input: unknown) => (({
1235
1275
  };
1236
1276
  } | undefined;
1237
1277
  readonly namespace?: string | undefined;
1278
+ } | {
1279
+ readonly type: "media";
1280
+ readonly media: import("./media.js").Asset;
1281
+ readonly providerMetadata?: {
1282
+ readonly [x: string]: {
1283
+ readonly [x: string]: unknown;
1284
+ };
1285
+ } | undefined;
1238
1286
  } | {
1239
1287
  readonly type: "step-finish";
1240
1288
  readonly reason: {
@@ -1263,12 +1311,12 @@ export declare const tool: (id: string, name: string, input: unknown) => (({
1263
1311
  } | {
1264
1312
  readonly type: "provider-error";
1265
1313
  readonly message: string;
1314
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1266
1315
  readonly providerMetadata?: {
1267
1316
  readonly [x: string]: {
1268
1317
  readonly [x: string]: unknown;
1269
1318
  };
1270
1319
  } | undefined;
1271
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1272
1320
  })[];
1273
1321
  export declare const failAfter: (error: AIError, ...events: readonly LLMEvent[]) => Stream.Stream<({
1274
1322
  readonly type: "compaction";
@@ -1439,6 +1487,14 @@ export declare const failAfter: (error: AIError, ...events: readonly LLMEvent[])
1439
1487
  };
1440
1488
  } | undefined;
1441
1489
  readonly namespace?: string | undefined;
1490
+ } | {
1491
+ readonly type: "media";
1492
+ readonly media: import("./media.js").Asset;
1493
+ readonly providerMetadata?: {
1494
+ readonly [x: string]: {
1495
+ readonly [x: string]: unknown;
1496
+ };
1497
+ } | undefined;
1442
1498
  } | {
1443
1499
  readonly type: "step-finish";
1444
1500
  readonly reason: {
@@ -1467,12 +1523,12 @@ export declare const failAfter: (error: AIError, ...events: readonly LLMEvent[])
1467
1523
  } | {
1468
1524
  readonly type: "provider-error";
1469
1525
  readonly message: string;
1526
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1470
1527
  readonly providerMetadata?: {
1471
1528
  readonly [x: string]: {
1472
1529
  readonly [x: string]: unknown;
1473
1530
  };
1474
1531
  } | undefined;
1475
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1476
1532
  }, AIError, never>;
1477
1533
  export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Stream<({
1478
1534
  readonly type: "compaction";
@@ -1643,6 +1699,14 @@ export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Strea
1643
1699
  };
1644
1700
  } | undefined;
1645
1701
  readonly namespace?: string | undefined;
1702
+ } | {
1703
+ readonly type: "media";
1704
+ readonly media: import("./media.js").Asset;
1705
+ readonly providerMetadata?: {
1706
+ readonly [x: string]: {
1707
+ readonly [x: string]: unknown;
1708
+ };
1709
+ } | undefined;
1646
1710
  } | {
1647
1711
  readonly type: "step-finish";
1648
1712
  readonly reason: {
@@ -1671,12 +1735,12 @@ export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Strea
1671
1735
  } | {
1672
1736
  readonly type: "provider-error";
1673
1737
  readonly message: string;
1738
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1674
1739
  readonly providerMetadata?: {
1675
1740
  readonly [x: string]: {
1676
1741
  readonly [x: string]: unknown;
1677
1742
  };
1678
1743
  } | undefined;
1679
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
1680
1744
  }, never, never>;
1681
1745
  /** Provides one shared implementation under the normal client and test-control tags. */
1682
1746
  export declare const testLayer: (options?: LayerOptions) => Layer.Layer<import("./route/client.js").Service | Test, never, never>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sniff a media type from leading magic bytes. Covers the containers media routes commonly return; anything else is
3
+ * `undefined` so callers can fall back to a provider-declared type or `application/octet-stream`.
4
+ */
5
+ export declare const detectMediaType: (bytes: Uint8Array) => string | undefined;
6
+ export declare const extensionMediaType: (path: string) => string | undefined;
@@ -0,0 +1,49 @@
1
+ const ascii = (bytes, start, end) => String.fromCharCode(...bytes.subarray(start, end));
2
+ const startsWith = (bytes, prefix) => bytes.length >= prefix.length && prefix.every((value, index) => bytes[index] === value);
3
+ /**
4
+ * Sniff a media type from leading magic bytes. Covers the containers media routes commonly return; anything else is
5
+ * `undefined` so callers can fall back to a provider-declared type or `application/octet-stream`.
6
+ */
7
+ export const detectMediaType = (bytes) => {
8
+ if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
9
+ return "image/png";
10
+ if (startsWith(bytes, [0xff, 0xd8, 0xff]))
11
+ return "image/jpeg";
12
+ if (startsWith(bytes, [0x47, 0x49, 0x46, 0x38]))
13
+ return "image/gif";
14
+ if (bytes.length >= 12 && ascii(bytes, 0, 4) === "RIFF") {
15
+ const riffType = ascii(bytes, 8, 12);
16
+ if (riffType === "WEBP")
17
+ return "image/webp";
18
+ if (riffType === "WAVE")
19
+ return "audio/wav";
20
+ }
21
+ if (startsWith(bytes, [0x25, 0x50, 0x44, 0x46]))
22
+ return "application/pdf";
23
+ if (bytes.length >= 12 && ascii(bytes, 4, 8) === "ftyp")
24
+ return "video/mp4";
25
+ if (startsWith(bytes, [0x1a, 0x45, 0xdf, 0xa3]))
26
+ return "video/webm";
27
+ if (startsWith(bytes, [0x49, 0x44, 0x33]) || startsWith(bytes, [0xff, 0xfb]) || startsWith(bytes, [0xff, 0xf3]))
28
+ return "audio/mpeg";
29
+ if (startsWith(bytes, [0x4f, 0x67, 0x67, 0x53]))
30
+ return "audio/ogg";
31
+ return undefined;
32
+ };
33
+ const EXTENSIONS = {
34
+ png: "image/png",
35
+ jpg: "image/jpeg",
36
+ jpeg: "image/jpeg",
37
+ gif: "image/gif",
38
+ webp: "image/webp",
39
+ pdf: "application/pdf",
40
+ mp4: "video/mp4",
41
+ webm: "video/webm",
42
+ mp3: "audio/mpeg",
43
+ wav: "audio/wav",
44
+ ogg: "audio/ogg",
45
+ txt: "text/plain",
46
+ md: "text/markdown",
47
+ csv: "text/csv",
48
+ };
49
+ export const extensionMediaType = (path) => EXTENSIONS[path.slice(path.lastIndexOf(".") + 1).toLowerCase()];
@@ -1,10 +1,12 @@
1
+ import { Media } from "../media.js";
1
2
  import { isRecord } from "./record.js";
2
3
  export const sanitizeSurrogates = (value) => {
3
4
  if (typeof value === "string")
4
5
  return value.toWellFormed();
5
6
  if (Array.isArray(value))
6
7
  return value.map(sanitizeSurrogates);
7
- if (value instanceof Uint8Array || value instanceof Error)
8
+ // Media assets carry binary or base64 payloads and a lazy byte cache; flattening them into a record would drop both.
9
+ if (value instanceof Uint8Array || value instanceof Error || value instanceof Media.Asset)
8
10
  return value;
9
11
  if (isRecord(value))
10
12
  return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key.toWellFormed(), sanitizeSurrogates(entry)]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "name": "@opencode/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,6 +18,10 @@
18
18
  "import": "./dist/index.js",
19
19
  "types": "./dist/index.d.ts"
20
20
  },
21
+ "./promise": {
22
+ "import": "./dist/promise.js",
23
+ "types": "./dist/promise.d.ts"
24
+ },
21
25
  "./testing": {
22
26
  "import": "./dist/testing.js",
23
27
  "types": "./dist/testing.d.ts"
@@ -30,7 +34,7 @@
30
34
  "devDependencies": {
31
35
  "@clack/prompts": "1.0.0-alpha.1",
32
36
  "@effect/platform-node": "4.0.0-rc.112",
33
- "@opencode/http-recorder": "2.0.13",
37
+ "@opencode/http-recorder": "2.0.15",
34
38
  "@tsconfig/bun": "1.0.9",
35
39
  "@types/bun": "1.4.0",
36
40
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -40,7 +44,7 @@
40
44
  "@aws-sdk/credential-providers": "3.1057.0",
41
45
  "@smithy/eventstream-codec": "4.2.14",
42
46
  "@smithy/util-utf8": "4.2.2",
43
- "@opencode/schema": "2.0.13",
47
+ "@opencode/schema": "2.0.15",
44
48
  "aws4fetch": "1.0.20",
45
49
  "effect": "4.0.0-rc.112",
46
50
  "google-auth-library": "10.5.0"
@@ -1,21 +0,0 @@
1
- import { Effect } from "effect";
2
- import type { ImageInput } from "../../image.js";
3
- import { AIError } from "../../schema/index.js";
4
- export declare const dataUrl: (input: Extract<ImageInput, {
5
- readonly type: "bytes";
6
- }>) => string;
7
- export declare const decodeDataUrl: (url: string) => Effect.Effect<{
8
- readonly mediaType: string;
9
- readonly data: Uint8Array;
10
- } | undefined, AIError>;
11
- export declare const invalidImageInput: (message: string, cause?: unknown) => AIError;
12
- export declare const ImageInputs: {
13
- readonly dataUrl: (input: Extract<ImageInput, {
14
- readonly type: "bytes";
15
- }>) => string;
16
- readonly decodeDataUrl: (url: string) => Effect.Effect<{
17
- readonly mediaType: string;
18
- readonly data: Uint8Array;
19
- } | undefined, AIError>;
20
- readonly invalid: (message: string, cause?: unknown) => AIError;
21
- };
@@ -1,20 +0,0 @@
1
- import { Effect, Encoding } from "effect";
2
- import { InvalidRequestError, AIError } from "../../schema/index.js";
3
- const invalid = (message, cause) => new AIError({
4
- reason: new InvalidRequestError({ message, cause }),
5
- });
6
- export const dataUrl = (input) => `data:${input.mediaType};base64,${Encoding.encodeBase64(input.data)}`;
7
- export const decodeDataUrl = (url) => {
8
- if (!url.startsWith("data:"))
9
- return Effect.undefined;
10
- const match = /^data:([^;,]+);base64,(.*)$/s.exec(url);
11
- if (!match)
12
- return Effect.fail(invalid("Image data URLs must contain a MIME type and base64 data"));
13
- return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(Effect.mapError((cause) => invalid("Image data URL contains invalid base64 data", cause)), Effect.map((data) => ({ mediaType: match[1], data })));
14
- };
15
- export const invalidImageInput = invalid;
16
- export const ImageInputs = {
17
- dataUrl,
18
- decodeDataUrl,
19
- invalid: invalidImageInput,
20
- };