@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.
- package/README.md +399 -56
- package/dist/experimental/evaluation-client.d.ts +1 -1
- package/dist/experimental/evaluation-client.js +39 -3
- package/dist/experimental/evaluation.d.ts +4 -4
- package/dist/experimental/evaluation.js +2 -2
- package/dist/experimental/system-one.d.ts +3 -3
- package/dist/experimental/system-one.js +40 -51
- package/dist/generation.d.ts +83 -0
- package/dist/generation.js +113 -0
- package/dist/image-client.d.ts +16 -7
- package/dist/image-client.js +29 -13
- package/dist/image.d.ts +1410 -81
- package/dist/image.js +97 -67
- package/dist/index.d.ts +17 -2
- package/dist/index.js +12 -1
- package/dist/llm.d.ts +9 -1
- package/dist/media-model.d.ts +44 -0
- package/dist/media-model.js +49 -0
- package/dist/media.d.ts +213 -0
- package/dist/media.js +227 -0
- package/dist/promise.d.ts +974 -0
- package/dist/promise.js +81 -0
- package/dist/protocols/alibaba-chat.d.ts +12 -0
- package/dist/protocols/alibaba-responses.d.ts +2 -2
- package/dist/protocols/anthropic-messages.js +8 -19
- package/dist/protocols/assemblyai-transcription.d.ts +40 -0
- package/dist/protocols/assemblyai-transcription.js +138 -0
- package/dist/protocols/bedrock-converse.d.ts +4 -4
- package/dist/protocols/bedrock-converse.js +6 -17
- package/dist/protocols/bfl-images.d.ts +32 -0
- package/dist/protocols/bfl-images.js +153 -0
- package/dist/protocols/cartesia-speech.d.ts +127 -0
- package/dist/protocols/cartesia-speech.js +126 -0
- package/dist/protocols/deepgram-speech.d.ts +119 -0
- package/dist/protocols/deepgram-speech.js +92 -0
- package/dist/protocols/deepgram-transcription.d.ts +25 -0
- package/dist/protocols/deepgram-transcription.js +129 -0
- package/dist/protocols/elevenlabs-speech.d.ts +122 -0
- package/dist/protocols/elevenlabs-speech.js +115 -0
- package/dist/protocols/fal-images.d.ts +24 -0
- package/dist/protocols/fal-images.js +114 -0
- package/dist/protocols/fal-video.d.ts +29 -0
- package/dist/protocols/fal-video.js +88 -0
- package/dist/protocols/gemini.d.ts +30 -9
- package/dist/protocols/gemini.js +45 -35
- package/dist/protocols/google-images.d.ts +9 -21
- package/dist/protocols/google-images.js +158 -133
- package/dist/protocols/google-speech.d.ts +130 -0
- package/dist/protocols/google-speech.js +84 -0
- package/dist/protocols/google-transcription.d.ts +173 -0
- package/dist/protocols/google-transcription.js +138 -0
- package/dist/protocols/google-video.d.ts +26 -0
- package/dist/protocols/google-video.js +158 -0
- package/dist/protocols/meta-images.d.ts +7 -12
- package/dist/protocols/meta-images.js +85 -66
- package/dist/protocols/meta-responses.d.ts +4 -4
- package/dist/protocols/meta-responses.js +1 -1
- package/dist/protocols/mistral-chat.js +7 -6
- package/dist/protocols/open-responses.d.ts +17 -9
- package/dist/protocols/open-responses.js +24 -14
- package/dist/protocols/openai-chat.d.ts +118 -1
- package/dist/protocols/openai-chat.js +125 -44
- package/dist/protocols/openai-compatible-chat.d.ts +12 -0
- package/dist/protocols/openai-compatible-responses.d.ts +2 -2
- package/dist/protocols/openai-images.d.ts +128 -18
- package/dist/protocols/openai-images.js +177 -154
- package/dist/protocols/openai-responses.d.ts +15 -15
- package/dist/protocols/openai-responses.js +5 -6
- package/dist/protocols/openai-speech.d.ts +116 -0
- package/dist/protocols/openai-speech.js +98 -0
- package/dist/protocols/openai-transcription.d.ts +207 -0
- package/dist/protocols/openai-transcription.js +190 -0
- package/dist/protocols/replicate-images.d.ts +28 -0
- package/dist/protocols/replicate-images.js +133 -0
- package/dist/protocols/runway-video.d.ts +38 -0
- package/dist/protocols/runway-video.js +146 -0
- package/dist/protocols/shared.d.ts +27 -17
- package/dist/protocols/shared.js +52 -35
- package/dist/protocols/stability-images.d.ts +38 -0
- package/dist/protocols/stability-images.js +148 -0
- package/dist/protocols/utils/bedrock-media.d.ts +2 -3
- package/dist/protocols/utils/bedrock-media.js +4 -4
- package/dist/protocols/utils/fal-queue.d.ts +28 -0
- package/dist/protocols/utils/fal-queue.js +69 -0
- package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
- package/dist/protocols/utils/gemini-generate-content.js +65 -0
- package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
- package/dist/protocols/utils/gemini-json-schema.js +76 -0
- package/dist/protocols/utils/media-input.d.ts +18 -0
- package/dist/protocols/utils/media-input.js +35 -0
- package/dist/protocols/utils/responses-compaction.js +6 -5
- package/dist/protocols/utils/speech-stream.d.ts +49 -0
- package/dist/protocols/utils/speech-stream.js +67 -0
- package/dist/protocols/utils/tool-schema.d.ts +2 -2
- package/dist/protocols/utils/tool-schema.js +40 -17
- package/dist/protocols/utils/tool-stream.d.ts +27 -3
- package/dist/protocols/xai-images.d.ts +9 -15
- package/dist/protocols/xai-images.js +75 -84
- package/dist/protocols/xai-responses.d.ts +2 -2
- package/dist/protocols/xai-video.d.ts +34 -0
- package/dist/protocols/xai-video.js +147 -0
- package/dist/protocols/zai-chat.d.ts +13 -1
- package/dist/protocols/zai-images.d.ts +9 -13
- package/dist/protocols/zai-images.js +59 -57
- package/dist/provider-error.js +3 -0
- package/dist/providers/alibaba.d.ts +14 -2
- package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
- package/dist/providers/amazon-bedrock.d.ts +2 -2
- package/dist/providers/assemblyai.d.ts +25 -0
- package/dist/providers/assemblyai.js +29 -0
- package/dist/providers/azure.d.ts +18 -6
- package/dist/providers/baseten.d.ts +24 -0
- package/dist/providers/black-forest-labs.d.ts +25 -0
- package/dist/providers/black-forest-labs.js +28 -0
- package/dist/providers/cartesia.d.ts +24 -0
- package/dist/providers/cartesia.js +22 -0
- package/dist/providers/cerebras.d.ts +24 -0
- package/dist/providers/cerebras.js +6 -1
- package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
- package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
- package/dist/providers/deepgram.d.ts +29 -0
- package/dist/providers/deepgram.js +31 -0
- package/dist/providers/deepinfra.d.ts +24 -0
- package/dist/providers/deepinfra.js +6 -1
- package/dist/providers/deepseek.d.ts +24 -0
- package/dist/providers/elevenlabs.d.ts +24 -0
- package/dist/providers/elevenlabs.js +28 -0
- package/dist/providers/fal.d.ts +29 -0
- package/dist/providers/fal.js +33 -0
- package/dist/providers/fireworks.d.ts +24 -0
- package/dist/providers/google-vertex-chat.d.ts +12 -0
- package/dist/providers/google-vertex-responses.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts +10 -3
- package/dist/providers/google.d.ts +25 -3
- package/dist/providers/google.js +11 -2
- package/dist/providers/groq.d.ts +24 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +10 -0
- package/dist/providers/meta.d.ts +14 -2
- package/dist/providers/minimax.d.ts +14 -2
- package/dist/providers/moonshot.d.ts +14 -2
- package/dist/providers/moonshot.js +3 -3
- package/dist/providers/openai-compatible-responses.d.ts +2 -2
- package/dist/providers/openai-compatible.d.ts +12 -0
- package/dist/providers/openai.d.ts +25 -3
- package/dist/providers/openai.js +10 -1
- package/dist/providers/openrouter.d.ts +67 -0
- package/dist/providers/openrouter.js +13 -1
- package/dist/providers/replicate.d.ts +25 -0
- package/dist/providers/replicate.js +22 -0
- package/dist/providers/runway.d.ts +24 -0
- package/dist/providers/runway.js +22 -0
- package/dist/providers/stability.d.ts +28 -0
- package/dist/providers/stability.js +23 -0
- package/dist/providers/togetherai.d.ts +24 -0
- package/dist/providers/vercel-ai-gateway.d.ts +41 -0
- package/dist/providers/vercel-ai-gateway.js +85 -0
- package/dist/providers/xai.d.ts +17 -0
- package/dist/providers/xai.js +5 -2
- package/dist/providers/zai-coding-plan.d.ts +15 -3
- package/dist/providers/zai.d.ts +13 -1
- package/dist/route/auth.d.ts +4 -1
- package/dist/route/auth.js +6 -0
- package/dist/route/client.d.ts +9 -1
- package/dist/route/endpoint.d.ts +10 -10
- package/dist/route/executor-service.d.ts +12 -0
- package/dist/route/executor-service.js +3 -0
- package/dist/route/executor.d.ts +4 -9
- package/dist/route/executor.js +3 -3
- package/dist/route/framing.d.ts +5 -1
- package/dist/route/framing.js +9 -0
- package/dist/route/index.d.ts +2 -0
- package/dist/route/index.js +2 -0
- package/dist/route/media-protocol.d.ts +158 -0
- package/dist/route/media-protocol.js +96 -0
- package/dist/route/media.d.ts +97 -0
- package/dist/route/media.js +236 -0
- package/dist/schema/errors.d.ts +13 -3
- package/dist/schema/errors.js +7 -0
- package/dist/schema/events.d.ts +557 -40
- package/dist/schema/events.js +35 -2
- package/dist/schema/messages.d.ts +95 -8
- package/dist/schema/messages.js +8 -6
- package/dist/schema/options.d.ts +6 -3
- package/dist/schema/options.js +6 -2
- package/dist/speech-client.d.ts +21 -0
- package/dist/speech-client.js +25 -0
- package/dist/speech.d.ts +1150 -0
- package/dist/speech.js +119 -0
- package/dist/testing.d.ts +72 -8
- package/dist/transcription-client.d.ts +28 -0
- package/dist/transcription-client.js +44 -0
- package/dist/transcription.d.ts +1504 -0
- package/dist/transcription.js +133 -0
- package/dist/utils/bytes.d.ts +1 -0
- package/dist/utils/bytes.js +10 -0
- package/dist/utils/media-type.d.ts +7 -0
- package/dist/utils/media-type.js +70 -0
- package/dist/utils/sanitize.js +3 -1
- package/dist/video-client.d.ts +28 -0
- package/dist/video-client.js +40 -0
- package/dist/video.d.ts +1359 -0
- package/dist/video.js +119 -0
- package/package.json +7 -3
- package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
- package/dist/protocols/utils/gemini-tool-schema.js +0 -103
- package/dist/protocols/utils/image-input.d.ts +0 -21
- 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
|
|
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:
|
|
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
|
|
229
|
-
export declare function
|
|
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
|
|
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
|
|
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
|
-
|
|
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<
|
|
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<
|
|
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 {
|
|
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
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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*
|
|
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:
|
|
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
|
|
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,
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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))));
|
package/dist/image-client.d.ts
CHANGED
|
@@ -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 {
|
|
4
|
-
import type
|
|
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
|
|
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
|
|
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
|
|
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 {};
|
package/dist/image-client.js
CHANGED
|
@@ -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 {
|
|
4
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
};
|