@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
@@ -1,6 +1,6 @@
1
1
  import { Context, Effect, Layer } from "effect";
2
2
  import { RequestExecutor } from "../route/executor.js";
3
- import { mergeHttpOptions } from "../schema/index.js";
3
+ import { AIError, InvalidProviderOutputError, mergeHttpOptions } from "../schema/index.js";
4
4
  import { sanitizeSurrogates } from "../utils/sanitize.js";
5
5
  import {} from "./evaluation.js";
6
6
  export class Service extends Context.Service()("@opencode/AI/Experimental/EvaluationClient") {
@@ -9,14 +9,50 @@ export const evaluate = (request) => Effect.flatMap(Service, (client) => client.
9
9
  export const layer = Layer.effect(Service, Effect.gen(function* () {
10
10
  const executor = yield* RequestExecutor.Service;
11
11
  return Service.of({
12
- evaluate: (request) => request.model.route.evaluate({
12
+ evaluate: (request) => request.model.route
13
+ .evaluate({
13
14
  ...sanitizeSurrogates({
14
15
  ...request,
15
16
  model: undefined,
16
17
  http: mergeHttpOptions(request.model.http, request.http),
17
18
  }),
18
19
  model: request.model,
19
- }, executor.execute),
20
+ }, executor.execute)
21
+ .pipe(Effect.flatMap((response) => {
22
+ const questions = Object.entries(request.questions);
23
+ if (questions.length === Object.keys(response.answers).length &&
24
+ questions.every(([id, question]) => {
25
+ const answer = response.answers[id];
26
+ if (question.type === "boolean")
27
+ return answer?.type === "boolean";
28
+ if (question.type === "choice") {
29
+ if (answer?.type !== "choice" || !Object.hasOwn(question.criteria, answer.choice))
30
+ return false;
31
+ if (answer.probabilities === undefined)
32
+ return true;
33
+ const keys = Object.keys(question.criteria);
34
+ const probabilities = answer.probabilities;
35
+ return (Object.keys(probabilities).length === keys.length &&
36
+ keys.every((key) => Object.hasOwn(probabilities, key)));
37
+ }
38
+ if (answer?.type !== "score" || answer.score < 0 || answer.score > question.criteria.length - 1)
39
+ return false;
40
+ if (answer.probabilities === undefined)
41
+ return true;
42
+ const keys = question.criteria.map((_, index) => String(index));
43
+ const probabilities = answer.probabilities;
44
+ return (Object.keys(probabilities).length === keys.length &&
45
+ keys.every((key) => Object.hasOwn(probabilities, key)));
46
+ }))
47
+ return Effect.succeed(response);
48
+ return Effect.fail(new AIError({
49
+ reason: new InvalidProviderOutputError({
50
+ route: request.model.route.id,
51
+ message: "Evaluation answers do not match the requested questions",
52
+ cause: response.answers,
53
+ }),
54
+ }));
55
+ })),
20
56
  });
21
57
  }));
22
58
  export const fetchLayer = layer.pipe(Layer.provide(RequestExecutor.fetchLayer));
@@ -96,7 +96,7 @@ export type AnswersFor<Questions extends EvaluationQuestions> = {
96
96
  export type EvaluationOptions = Record<string, unknown>;
97
97
  export interface EvaluationRoute<Options extends EvaluationOptions = EvaluationOptions> {
98
98
  readonly id: string;
99
- readonly evaluate: <const Questions extends EvaluationQuestions>(request: EvaluationRequestFor<Options, Questions>, execute: Execute) => Effect.Effect<EvaluationResponseFor<Questions>, AIError>;
99
+ readonly evaluate: (request: EvaluationRequestFor<Options>, execute: Execute) => Effect.Effect<EvaluationResponse, AIError>;
100
100
  }
101
101
  export declare class EvaluationModel<Options extends EvaluationOptions = EvaluationOptions> {
102
102
  protected readonly _Options: (options: Options) => Options;
@@ -225,10 +225,10 @@ export type EvaluationResponseFor<Questions extends EvaluationQuestions> = Omit<
225
225
  };
226
226
  export declare function request<const Model extends object, const Questions extends EvaluationQuestions>(input: EvaluationRequestInput<Model, Questions>): EvaluationRequestFor<EvaluationModelOptions<Model>, Questions>;
227
227
  export declare function request(input: EvaluationRequest): EvaluationRequest;
228
- export declare function evaluate<const Model extends object, const Questions extends EvaluationQuestions>(input: EvaluationRequestInput<Model, Questions>): Effect.Effect<EvaluationResponseFor<Questions>, AIError, Service>;
229
- export declare function evaluate(input: EvaluationRequest): Effect.Effect<EvaluationResponse, AIError, Service>;
228
+ export declare function run<const Model extends object, const Questions extends EvaluationQuestions>(input: EvaluationRequestInput<Model, Questions>): Effect.Effect<EvaluationResponseFor<Questions>, AIError, Service>;
229
+ export declare function run(input: EvaluationRequest): Effect.Effect<EvaluationResponse, AIError, Service>;
230
230
  export declare const Evaluation: {
231
231
  readonly request: typeof request;
232
- readonly evaluate: typeof evaluate;
232
+ readonly run: typeof run;
233
233
  };
234
234
  export {};
@@ -97,7 +97,7 @@ export function request(input) {
97
97
  http: input.http === undefined ? undefined : HttpOptions.make(input.http),
98
98
  });
99
99
  }
100
- export function evaluate(input) {
100
+ export function run(input) {
101
101
  return Effect.try({
102
102
  try: () => (input instanceof EvaluationRequest ? input : request(input)),
103
103
  catch: (cause) => new AIError({
@@ -110,5 +110,5 @@ export function evaluate(input) {
110
110
  }
111
111
  export const Evaluation = {
112
112
  request,
113
- evaluate,
113
+ run,
114
114
  };
@@ -1,4 +1,4 @@
1
- import { EvaluationModel } from "./evaluation.js";
1
+ import { EvaluationModel, type EvaluationOptions } from "./evaluation.js";
2
2
  import { type Definition as AuthDefinition } from "../route/auth.js";
3
3
  import { HttpOptions, ModelID } from "../schema/index.js";
4
4
  export interface ModelInput {
@@ -10,7 +10,7 @@ export interface ModelInput {
10
10
  readonly headers?: Record<string, string>;
11
11
  readonly http?: HttpOptions;
12
12
  }
13
- export declare const model: (cfg: ModelInput) => EvaluationModel<import("./evaluation.js").EvaluationOptions>;
13
+ export declare const model: <Options extends EvaluationOptions = EvaluationOptions>(cfg: ModelInput) => EvaluationModel<Options>;
14
14
  export declare const SystemOne: {
15
- readonly model: (cfg: ModelInput) => EvaluationModel<import("./evaluation.js").EvaluationOptions>;
15
+ readonly model: <Options extends EvaluationOptions = EvaluationOptions>(cfg: ModelInput) => EvaluationModel<Options>;
16
16
  };
@@ -1,6 +1,6 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { Headers, HttpClientRequest } from "effect/unstable/http";
3
- import { BooleanAnswer, ChoiceAnswer, ChoiceQuestion, EvaluationInput, EvaluationModel, EvaluationResponse, EvaluationRounding, ScoreAnswer, ScoreQuestion, } from "./evaluation.js";
3
+ import { ChoiceQuestion, EvaluationInput, EvaluationModel, EvaluationResponse, EvaluationRounding, ScoreQuestion, } from "./evaluation.js";
4
4
  import { Auth } from "../route/auth.js";
5
5
  import { AIError, HttpContext, HttpOptions, InvalidProviderOutputError, InvalidRequestError, ModelID, Usage, mergeJsonRecords, } from "../schema/index.js";
6
6
  const Noul = Schema.Struct({
@@ -40,19 +40,29 @@ const Score = Schema.Struct({
40
40
  legend: Schema.optional(Schema.Record(Schema.String, Schema.Json)),
41
41
  confidence: Schema.optional(Probability),
42
42
  });
43
- const NativeUsage = Schema.Struct({
43
+ const Answer = Schema.Union([NoulAnswer, Choice, Score]).pipe(Schema.toTaggedUnion("type"));
44
+ const NativeUsage = Schema.StructWithRest(Schema.Struct({
44
45
  input_tokens: Schema.optional(Schema.Number),
45
46
  output_tokens: Schema.optional(Schema.Number),
47
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
48
+ const Response = Schema.Struct({
49
+ model: Schema.String,
50
+ answers: Schema.Record(Schema.String, Answer),
51
+ usage: Schema.optional(NativeUsage),
52
+ id: Schema.optional(Schema.String),
53
+ provider: Schema.optional(Schema.String),
54
+ provider_metadata: Schema.optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown))),
46
55
  });
47
- const encode = Schema.encodeUnknownEffect(Schema.fromJsonString(Request));
48
- const exact = (x, keys) => Object.keys(x).length === keys.length && keys.every((key) => Object.hasOwn(x, key));
49
- export const model = (cfg) => {
50
- const route = {
56
+ export const model = (cfg) => EvaluationModel.make({
57
+ id: cfg.id,
58
+ provider: cfg.provider,
59
+ http: cfg.http,
60
+ route: {
51
61
  id: "system-one",
52
62
  evaluate: (req, send) => Effect.gen(function* () {
53
63
  const url = new URL(`${cfg.baseURL.replace(/\/$/, "")}/systemone`);
54
64
  Object.entries(req.http?.query ?? {}).forEach(([key, value]) => url.searchParams.set(key, value));
55
- const body = yield* encode({
65
+ const body = yield* Schema.encodeUnknownEffect(Schema.fromJsonString(Request))({
56
66
  ...mergeJsonRecords(req.options, req.http?.body),
57
67
  model: req.model.id,
58
68
  state: req.state,
@@ -67,56 +77,36 @@ export const model = (cfg) => {
67
77
  });
68
78
  const res = yield* send(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(body, "application/json")));
69
79
  const http = new HttpContext({ url: res.request.url, status: res.status, headers: res.headers });
70
- const fail = (message, cause, body) => new AIError({ reason: new InvalidProviderOutputError({ route: route.id, message, body, http, cause }) });
80
+ const fail = (message, cause, body) => new AIError({ reason: new InvalidProviderOutputError({ route: "system-one", message, body, http, cause }) });
71
81
  const text = yield* res.text.pipe(Effect.mapError((cause) => fail("Failed to read the System One response", cause)));
72
- const entries = Object.entries(req.questions);
73
- const output = Schema.Struct({
74
- model: Schema.String,
75
- answers: Schema.Struct(Object.fromEntries(entries.map(([id, question]) => {
76
- if (question.type === "boolean")
77
- return [id, NoulAnswer];
78
- if (question.type === "choice") {
79
- const keys = Object.keys(question.criteria);
80
- return [
81
- id,
82
- Choice.pipe(Schema.refine((x) => Object.hasOwn(question.criteria, x.choice) && exact(x.probabilities, keys), { message: `Question "${id}" returned an invalid choice answer` })),
83
- ];
84
- }
85
- const keys = question.criteria.map((_, index) => String(index));
86
- return [
87
- id,
88
- Score.pipe(Schema.refine((x) => x.score >= 0 && x.score <= question.criteria.length - 1 && exact(x.probabilities, keys), { message: `Question "${id}" returned an invalid score answer` })),
89
- ];
90
- }))),
91
- usage: Schema.optional(NativeUsage),
92
- });
93
- const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(output))(text).pipe(Effect.mapError((cause) => fail("System One returned an invalid response", cause, text)));
82
+ const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Response))(text).pipe(Effect.mapError((cause) => fail("System One returned an invalid response", cause, text)));
94
83
  const confidence = {};
95
84
  const legend = {};
96
- const answers = Object.fromEntries(entries.map(([id, question]) => {
97
- const answer = data.answers[id];
98
- if (question.type === "boolean")
85
+ const answers = Object.fromEntries(Object.entries(data.answers).map(([id, answer]) => {
86
+ if (answer.type === "noul")
87
+ return [id, { type: "boolean", probability: answer.noul }];
88
+ if (answer.type === "choice") {
89
+ if (answer.confidence !== undefined)
90
+ confidence[id] = answer.confidence;
99
91
  return [
100
92
  id,
101
- { type: "boolean", probability: answer.noul },
102
- ];
103
- if (question.type === "choice") {
104
- const value = answer;
105
- if (value.confidence !== undefined)
106
- confidence[id] = value.confidence;
107
- return [
108
- id,
109
- { type: "choice", choice: value.choice, probabilities: value.probabilities },
93
+ {
94
+ type: "choice",
95
+ choice: answer.choice,
96
+ probabilities: answer.probabilities,
97
+ },
110
98
  ];
111
99
  }
112
- const value = answer;
113
- if (value.confidence !== undefined)
114
- confidence[id] = value.confidence;
115
- if (value.legend !== undefined)
116
- legend[id] = value.legend;
117
- return [id, { type: "score", score: value.score, probabilities: value.probabilities }];
100
+ if (answer.confidence !== undefined)
101
+ confidence[id] = answer.confidence;
102
+ if (answer.legend !== undefined)
103
+ legend[id] = answer.legend;
104
+ return [id, { type: "score", score: answer.score, probabilities: answer.probabilities }];
118
105
  }));
119
106
  const meta = {
107
+ ...(data.id === undefined ? {} : { responseId: data.id }),
108
+ ...(data.provider === undefined ? {} : { provider: data.provider }),
109
+ ...data.provider_metadata?.[cfg.providerMetadataKey],
120
110
  ...(Object.keys(confidence).length === 0 ? {} : { confidence }),
121
111
  ...(Object.keys(legend).length === 0 ? {} : { legend }),
122
112
  };
@@ -137,7 +127,6 @@ export const model = (cfg) => {
137
127
  providerMetadata: Object.keys(meta).length === 0 ? undefined : { [cfg.providerMetadataKey]: meta },
138
128
  });
139
129
  }),
140
- };
141
- return EvaluationModel.make({ id: cfg.id, provider: cfg.provider, route, http: cfg.http });
142
- };
130
+ },
131
+ });
143
132
  export const SystemOne = { model };
@@ -0,0 +1,83 @@
1
+ import { Duration, Effect, Schedule, Schema, Stream } from "effect";
2
+ import { AIError } from "./schema/errors.js";
3
+ export declare const Status: Schema.Literals<readonly ["queued", "running", "completed", "failed", "cancelled", "expired"]>;
4
+ export type Status = Schema.Schema.Type<typeof Status>;
5
+ /** Provider-neutral view of one generation observation. */
6
+ export interface Snapshot {
7
+ readonly id: string;
8
+ readonly status: Status;
9
+ /** Normalized 0..1 when the provider reports progress. */
10
+ readonly progress?: number;
11
+ readonly position?: number;
12
+ readonly expiresAt?: number;
13
+ }
14
+ /**
15
+ * Route-owned generation operations for one generation. The media route decodes its serializable token once (from the
16
+ * submission response or a `resume` input) and closes over it, so `Generation` never sees the token's shape.
17
+ */
18
+ export interface Route<Response> {
19
+ readonly status: Effect.Effect<Snapshot, AIError>;
20
+ readonly result: Effect.Effect<Response, AIError>;
21
+ readonly cancel?: Effect.Effect<void, AIError>;
22
+ /** Provider polling hint (e.g. `openai-poll-after-ms`) that overrides the default interval for the next poll. */
23
+ readonly pollHint?: (snapshot: Snapshot) => Duration.Duration | undefined;
24
+ }
25
+ export interface Poll {
26
+ readonly interval?: Duration.Input;
27
+ readonly timeout?: Duration.Input;
28
+ /** Full override of the polling schedule; `interval` and `pollHint` are ignored when supplied. */
29
+ readonly schedule?: Schedule.Schedule<unknown, Snapshot>;
30
+ }
31
+ export interface AwaitOptions {
32
+ readonly poll?: Poll;
33
+ }
34
+ export declare const DEFAULT_POLL_INTERVAL: Duration.Duration;
35
+ export declare const DEFAULT_POLL_TIMEOUT: Duration.Duration;
36
+ export declare const QueuedEvent: Schema.Struct<{
37
+ readonly type: Schema.tag<"generation-queued">;
38
+ readonly id: Schema.String;
39
+ readonly position: Schema.optional<Schema.Number>;
40
+ }>;
41
+ export declare const ProgressEvent: Schema.Struct<{
42
+ readonly type: Schema.tag<"generation-progress">;
43
+ readonly id: Schema.String;
44
+ readonly progress: Schema.optional<Schema.Number>;
45
+ }>;
46
+ export type Observation = Schema.Schema.Type<typeof QueuedEvent> | Schema.Schema.Type<typeof ProgressEvent>;
47
+ export type Event = Observation | {
48
+ readonly type: "generation-finished";
49
+ readonly id: string;
50
+ readonly status: Status;
51
+ };
52
+ export declare class Generation<Response> {
53
+ readonly route: Route<Response>;
54
+ /** Route-owned serializable JSON; pass it to the modality's `resume` from another process. */
55
+ readonly token: unknown;
56
+ readonly id: string;
57
+ readonly status: Status;
58
+ readonly progress?: number;
59
+ readonly position?: number;
60
+ readonly expiresAt?: number;
61
+ constructor(route: Route<Response>,
62
+ /** Route-owned serializable JSON; pass it to the modality's `resume` from another process. */
63
+ token: unknown, snapshot: Snapshot);
64
+ get snapshot(): Snapshot;
65
+ get terminal(): boolean;
66
+ refresh(): Effect.Effect<Generation<Response>, AIError>;
67
+ /** Fetch the result without polling; non-completed terminal generations fail with the provider's terminal body. */
68
+ result(): Effect.Effect<Response, AIError>;
69
+ /** Poll until the generation reaches a terminal status, then fetch the result. Fails with a `Timeout` reason on deadline. */
70
+ await(options?: AwaitOptions): Effect.Effect<Response, AIError>;
71
+ cancel(): Effect.Effect<void, AIError>;
72
+ /**
73
+ * Status observations as a stream, ending after the first terminal observation. Each poll is bounded by the time
74
+ * remaining until `poll.timeout`, so a hung status request fails the stream instead of stalling it. (`Stream.interruptWhen`
75
+ * would express this directly but deadlocks under `TestClock` when the source completes while the timer sleeps.)
76
+ */
77
+ events(options?: AwaitOptions): Stream.Stream<Event, AIError>;
78
+ private event;
79
+ private timeoutError;
80
+ private poll;
81
+ private schedule;
82
+ }
83
+ export declare const resultEvents: <Response, A>(generation: Generation<Response>, expand: (response: Response) => ReadonlyArray<A>, options?: AwaitOptions) => Stream.Stream<Observation | A, AIError>;
@@ -0,0 +1,113 @@
1
+ import { Clock, Duration, Effect, Schedule, Schema, Stream } from "effect";
2
+ import { AIError, TimeoutError } from "./schema/errors.js";
3
+ export const Status = Schema.Literals(["queued", "running", "completed", "failed", "cancelled", "expired"]);
4
+ export const DEFAULT_POLL_INTERVAL = Duration.seconds(5);
5
+ export const DEFAULT_POLL_TIMEOUT = Duration.minutes(10);
6
+ export const QueuedEvent = Schema.Struct({
7
+ type: Schema.tag("generation-queued"),
8
+ id: Schema.String,
9
+ position: Schema.optional(Schema.Number),
10
+ }).annotate({ identifier: "Generation.Event.Queued" });
11
+ export const ProgressEvent = Schema.Struct({
12
+ type: Schema.tag("generation-progress"),
13
+ id: Schema.String,
14
+ progress: Schema.optional(Schema.Number),
15
+ }).annotate({ identifier: "Generation.Event.Progress" });
16
+ const TERMINAL = new Set(["completed", "failed", "cancelled", "expired"]);
17
+ export class Generation {
18
+ route;
19
+ token;
20
+ id;
21
+ status;
22
+ progress;
23
+ position;
24
+ expiresAt;
25
+ constructor(route,
26
+ /** Route-owned serializable JSON; pass it to the modality's `resume` from another process. */
27
+ token, snapshot) {
28
+ this.route = route;
29
+ this.token = token;
30
+ this.id = snapshot.id;
31
+ this.status = snapshot.status;
32
+ this.progress = snapshot.progress;
33
+ this.position = snapshot.position;
34
+ this.expiresAt = snapshot.expiresAt;
35
+ }
36
+ get snapshot() {
37
+ return {
38
+ id: this.id,
39
+ status: this.status,
40
+ progress: this.progress,
41
+ position: this.position,
42
+ expiresAt: this.expiresAt,
43
+ };
44
+ }
45
+ get terminal() {
46
+ return TERMINAL.has(this.status);
47
+ }
48
+ refresh() {
49
+ return this.route.status.pipe(Effect.map((snapshot) => new Generation(this.route, this.token, snapshot)));
50
+ }
51
+ /** Fetch the result without polling; non-completed terminal generations fail with the provider's terminal body. */
52
+ result() {
53
+ return this.route.result;
54
+ }
55
+ /** Poll until the generation reaches a terminal status, then fetch the result. Fails with a `Timeout` reason on deadline. */
56
+ await(options) {
57
+ const timeout = Duration.fromInputUnsafe(options?.poll?.timeout ?? DEFAULT_POLL_TIMEOUT);
58
+ const settled = this.terminal ? Effect.succeed(this) : this.poll(options?.poll);
59
+ return settled.pipe(
60
+ // Non-completed terminal states also go through `result` so the route can surface its provider failure body.
61
+ Effect.flatMap((generation) => generation.result()), Effect.timeoutOrElse({ duration: timeout, orElse: () => this.timeoutError(timeout) }));
62
+ }
63
+ cancel() {
64
+ return this.route.cancel ?? Effect.void;
65
+ }
66
+ /**
67
+ * Status observations as a stream, ending after the first terminal observation. Each poll is bounded by the time
68
+ * remaining until `poll.timeout`, so a hung status request fails the stream instead of stalling it. (`Stream.interruptWhen`
69
+ * would express this directly but deadlocks under `TestClock` when the source completes while the timer sleeps.)
70
+ */
71
+ events(options) {
72
+ if (this.terminal)
73
+ return Stream.make(this.event());
74
+ const timeout = Duration.fromInputUnsafe(options?.poll?.timeout ?? DEFAULT_POLL_TIMEOUT);
75
+ return Stream.unwrap(Clock.currentTimeMillis.pipe(Effect.map((start) => {
76
+ const deadline = start + Duration.toMillis(timeout);
77
+ const refresh = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => this.refresh().pipe(Effect.timeoutOrElse({
78
+ duration: Duration.millis(Math.max(0, deadline - now)),
79
+ orElse: () => this.timeoutError(timeout),
80
+ }))));
81
+ return Stream.fromEffectSchedule(refresh, this.schedule(options?.poll)).pipe(Stream.takeUntil((generation) => generation.terminal), Stream.map((generation) => generation.event()));
82
+ })));
83
+ }
84
+ event() {
85
+ if (this.terminal)
86
+ return { type: "generation-finished", id: this.id, status: this.status };
87
+ if (this.status === "queued")
88
+ return { type: "generation-queued", id: this.id, position: this.position };
89
+ return { type: "generation-progress", id: this.id, progress: this.progress };
90
+ }
91
+ timeoutError(timeout) {
92
+ return new AIError({
93
+ reason: new TimeoutError({
94
+ message: `Generation ${this.id} did not finish within ${Duration.format(timeout)}`,
95
+ timeoutMs: Duration.toMillis(timeout),
96
+ }),
97
+ });
98
+ }
99
+ poll(poll) {
100
+ return this.refresh().pipe(Effect.repeat({ schedule: this.schedule(poll), until: (generation) => generation.terminal }));
101
+ }
102
+ schedule(poll) {
103
+ if (poll?.schedule)
104
+ return poll.schedule.pipe(Schedule.setInputType());
105
+ const interval = poll?.interval ?? DEFAULT_POLL_INTERVAL;
106
+ const pollHint = this.route.pollHint;
107
+ const spaced = Schedule.spaced(interval).pipe(Schedule.setInputType());
108
+ if (!pollHint)
109
+ return spaced;
110
+ return spaced.pipe(Schedule.modifyDelay((metadata) => Effect.succeed(pollHint(metadata.input.snapshot) ?? interval)));
111
+ }
112
+ }
113
+ export const resultEvents = (generation, expand, options) => generation.events(options).pipe(Stream.filter((event) => event.type !== "generation-finished"), Stream.concat(Stream.fromIterableEffect(Effect.map(generation.result(), expand))));
@@ -1,19 +1,28 @@
1
- import { Context, Effect, Layer } from "effect";
1
+ import { Context, Effect, Layer, Stream } from "effect";
2
+ import type { AwaitOptions, Generation } from "./generation.js";
2
3
  import { RequestExecutor } from "./route/executor.js";
3
- import { type AIError } from "./schema/index.js";
4
- import type { ImageOptions, ImageRequestFor, ImageResponse } from "./image.js";
5
- export type Execute = RequestExecutor.Interface["execute"];
4
+ import type { AIError } from "./schema/index.js";
5
+ import { type ImageEvent, type ImageModel, type ImageOptions, type ImageRequestFor, type ImageResponse } from "./image.js";
6
6
  export interface Interface {
7
- readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError>;
7
+ readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>, options?: AwaitOptions) => Effect.Effect<ImageResponse, AIError>;
8
+ readonly stream: <Options extends ImageOptions>(request: ImageRequestFor<Options>, options?: AwaitOptions) => Stream.Stream<ImageEvent, AIError>;
9
+ readonly start: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<Generation<ImageResponse>, AIError>;
10
+ readonly resume: <Options extends ImageOptions>(model: ImageModel<Options>, token: unknown) => Effect.Effect<Generation<ImageResponse>, AIError>;
8
11
  }
9
12
  declare const Service_base: Context.ServiceClass<Service, "@opencode/ImageClient", Interface>;
10
13
  export declare class Service extends Service_base {
11
14
  }
12
- export declare const generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError, Service>;
15
+ export declare const generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>, options?: AwaitOptions) => Effect.Effect<ImageResponse, AIError, Service>;
16
+ export declare const stream: <Options extends ImageOptions>(request: ImageRequestFor<Options>, options?: AwaitOptions) => Stream.Stream<ImageEvent, AIError, Service>;
17
+ export declare const start: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<Generation<ImageResponse>, AIError, Service>;
18
+ export declare const resume: <Options extends ImageOptions>(model: ImageModel<Options>, token: unknown) => Effect.Effect<Generation<ImageResponse>, AIError, Service>;
13
19
  export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
14
20
  export declare const ImageClient: {
15
21
  readonly Service: typeof Service;
16
22
  readonly layer: Layer.Layer<Service, never, RequestExecutor.Service>;
17
- readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError, Service>;
23
+ readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>, options?: AwaitOptions) => Effect.Effect<ImageResponse, AIError, Service>;
24
+ readonly stream: <Options extends ImageOptions>(request: ImageRequestFor<Options>, options?: AwaitOptions) => Stream.Stream<ImageEvent, AIError, Service>;
25
+ readonly start: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<Generation<ImageResponse>, AIError, Service>;
26
+ readonly resume: <Options extends ImageOptions>(model: ImageModel<Options>, token: unknown) => Effect.Effect<Generation<ImageResponse>, AIError, Service>;
18
27
  };
19
28
  export {};
@@ -1,28 +1,44 @@
1
- import { Context, Effect, Layer } from "effect";
1
+ import { Context, Effect, Layer, Stream } from "effect";
2
2
  import { RequestExecutor } from "./route/executor.js";
3
- import { mergeHttpOptions } from "./schema/index.js";
4
- import { sanitizeSurrogates } from "./utils/sanitize.js";
3
+ import { MediaRoute } from "./route/media.js";
4
+ import { responseEvents, } from "./image.js";
5
5
  export class Service extends Context.Service()("@opencode/ImageClient") {
6
6
  }
7
- export const generate = (request) => Effect.gen(function* () {
7
+ export const generate = (request, options) => Effect.gen(function* () {
8
8
  const client = yield* Service;
9
- return yield* client.generate(request);
9
+ return yield* client.generate(request, options);
10
+ });
11
+ export const stream = (request, options) => Stream.unwrap(Effect.gen(function* () {
12
+ const client = yield* Service;
13
+ return client.stream(request, options);
14
+ }));
15
+ export const start = (request) => Effect.gen(function* () {
16
+ const client = yield* Service;
17
+ return yield* client.start(request);
18
+ });
19
+ export const resume = (model, token) => Effect.gen(function* () {
20
+ const client = yield* Service;
21
+ return yield* client.resume(model, token);
10
22
  });
11
23
  export const layer = Layer.effect(Service, Effect.gen(function* () {
12
24
  const executor = yield* RequestExecutor.Service;
25
+ const dispatch = MediaRoute.dispatch({
26
+ modality: "image",
27
+ execute: executor.execute,
28
+ responseEvents,
29
+ });
13
30
  return Service.of({
14
- generate: (request) => request.model.route.generate({
15
- ...sanitizeSurrogates({
16
- ...request,
17
- model: undefined,
18
- http: mergeHttpOptions(request.model.http, request.http),
19
- }),
20
- model: request.model,
21
- }, executor.execute),
31
+ start: (request) => dispatch.start(request.model.route, request),
32
+ resume: (model, token) => dispatch.resume(model.route, model, token),
33
+ generate: (request, options) => dispatch.generate(request.model.route, request, options),
34
+ stream: (request, options) => dispatch.stream(request.model.route, request, options),
22
35
  });
23
36
  }));
24
37
  export const ImageClient = {
25
38
  Service,
26
39
  layer,
27
40
  generate,
41
+ stream,
42
+ start,
43
+ resume,
28
44
  };