@opencode/ai 0.0.0-dev-19993 → 0.0.0-dev-19996
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 +11 -1
- package/dist/experimental/evaluation-client.d.ts +1 -1
- package/dist/experimental/evaluation-client.js +39 -3
- package/dist/experimental/evaluation.d.ts +1 -1
- package/dist/experimental/system-one.d.ts +3 -3
- package/dist/experimental/system-one.js +40 -51
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/openrouter.d.ts +19 -0
- package/dist/providers/openrouter.js +13 -1
- package/dist/providers/vercel-ai-gateway.d.ts +41 -0
- package/dist/providers/vercel-ai-gateway.js +85 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -66,7 +66,17 @@ console.log(response.answers.refund.probability)
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`TypeSafeAI` reads `TYPESAFE_API_KEY`. `OpenCodeZen` exposes the same selector and reads
|
|
69
|
-
`OPENCODE_API_KEY`.
|
|
69
|
+
`OPENCODE_API_KEY`. OpenRouter and Vercel AI Gateway use the same provider shape:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { OpenRouter, VercelAIGateway } from "@opencode/ai/providers"
|
|
73
|
+
|
|
74
|
+
OpenRouter.configure().experimental.evaluation("typesafe/jev-1.13")
|
|
75
|
+
VercelAIGateway.configure().experimental.evaluation("typesafe-ai/jev")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
OpenRouter reads `OPENROUTER_API_KEY`. Vercel reads `AI_GATEWAY_API_KEY`, then `VERCEL_OIDC_TOKEN`.
|
|
79
|
+
The common API uses `boolean`; System One routes lower it to native `noul`.
|
|
70
80
|
Choice and score confidence plus score legends remain available in provider metadata, and the
|
|
71
81
|
provider's rounded probabilities are returned unchanged.
|
|
72
82
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { RequestExecutor } from "../route/executor.js";
|
|
3
|
-
import {
|
|
3
|
+
import { AIError } from "../schema/index.js";
|
|
4
4
|
import { type EvaluationOptions, type EvaluationQuestions, type EvaluationRequestFor, type EvaluationResponseFor } from "./evaluation.js";
|
|
5
5
|
export type Execute = RequestExecutor.Interface["execute"];
|
|
6
6
|
export interface Interface {
|
|
@@ -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;
|
|
@@ -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 };
|
|
@@ -28,6 +28,7 @@ export * as OpenCodeZen from "./opencode-zen.js";
|
|
|
28
28
|
export * as OpenRouter from "./openrouter.js";
|
|
29
29
|
export * as TogetherAI from "./togetherai.js";
|
|
30
30
|
export * as TypeSafeAI from "./typesafe-ai.js";
|
|
31
|
+
export * as VercelAIGateway from "./vercel-ai-gateway.js";
|
|
31
32
|
export * as XAI from "./xai.js";
|
|
32
33
|
export * as ZAI from "./zai.js";
|
|
33
34
|
export * as ZAICodingPlan from "./zai-coding-plan.js";
|
package/dist/providers/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export * as OpenCodeZen from "./opencode-zen.js";
|
|
|
28
28
|
export * as OpenRouter from "./openrouter.js";
|
|
29
29
|
export * as TogetherAI from "./togetherai.js";
|
|
30
30
|
export * as TypeSafeAI from "./typesafe-ai.js";
|
|
31
|
+
export * as VercelAIGateway from "./vercel-ai-gateway.js";
|
|
31
32
|
export * as XAI from "./xai.js";
|
|
32
33
|
export * as ZAI from "./zai.js";
|
|
33
34
|
export * as ZAICodingPlan from "./zai-coding-plan.js";
|
|
@@ -73,6 +73,13 @@ export interface OpenRouterOptions {
|
|
|
73
73
|
}>;
|
|
74
74
|
}
|
|
75
75
|
export type OpenRouterProviderOptionsInput = OpenRouterOptions;
|
|
76
|
+
export interface OpenRouterEvaluationOptions {
|
|
77
|
+
readonly [key: string]: unknown;
|
|
78
|
+
readonly provider?: OpenRouterProviderRouting;
|
|
79
|
+
readonly session_id?: string;
|
|
80
|
+
readonly trace?: Readonly<Record<string, unknown>>;
|
|
81
|
+
readonly user?: string;
|
|
82
|
+
}
|
|
76
83
|
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
|
|
77
84
|
readonly baseURL?: string;
|
|
78
85
|
readonly providerOptions?: OpenRouterProviderOptionsInput;
|
|
@@ -548,16 +555,28 @@ export declare const routes: Route<{
|
|
|
548
555
|
export declare const configure: (input?: LanguageModelOptions) => {
|
|
549
556
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
550
557
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenRouterOptions, import("../route/client.js").CompactionOperations | undefined>;
|
|
558
|
+
experimental: {
|
|
559
|
+
evaluation: (modelID: string | ModelID) => import("../experimental.js").EvaluationModel<OpenRouterEvaluationOptions>;
|
|
560
|
+
};
|
|
551
561
|
configure: (input?: LanguageModelOptions) => /*elided*/ any;
|
|
552
562
|
};
|
|
553
563
|
export declare const provider: {
|
|
554
564
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
555
565
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenRouterOptions, import("../route/client.js").CompactionOperations | undefined>;
|
|
566
|
+
experimental: {
|
|
567
|
+
evaluation: (modelID: string | ModelID) => import("../experimental.js").EvaluationModel<OpenRouterEvaluationOptions>;
|
|
568
|
+
};
|
|
556
569
|
configure: (input?: LanguageModelOptions) => {
|
|
557
570
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
558
571
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenRouterOptions, import("../route/client.js").CompactionOperations | undefined>;
|
|
572
|
+
experimental: {
|
|
573
|
+
evaluation: (modelID: string | ModelID) => import("../experimental.js").EvaluationModel<OpenRouterEvaluationOptions>;
|
|
574
|
+
};
|
|
559
575
|
configure: /*elided*/ any;
|
|
560
576
|
};
|
|
561
577
|
};
|
|
578
|
+
export declare const experimental: {
|
|
579
|
+
evaluation: (modelID: string | ModelID) => import("../experimental.js").EvaluationModel<OpenRouterEvaluationOptions>;
|
|
580
|
+
};
|
|
562
581
|
export declare const model: ProviderPackage.Definition<Settings, OpenRouterProviderOptionsInput>["model"];
|
|
563
582
|
export {};
|
|
@@ -3,7 +3,8 @@ import { Route } from "../route/client.js";
|
|
|
3
3
|
import { Endpoint } from "../route/endpoint.js";
|
|
4
4
|
import { Protocol } from "../route/protocol.js";
|
|
5
5
|
import { AuthOptions } from "../route/auth-options.js";
|
|
6
|
-
import { ProviderID } from "../schema/index.js";
|
|
6
|
+
import { HttpOptions, ProviderID } from "../schema/index.js";
|
|
7
|
+
import { SystemOne } from "../experimental/system-one.js";
|
|
7
8
|
import { OpenAIChat } from "../protocols/openai-chat.js";
|
|
8
9
|
import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js";
|
|
9
10
|
import { isRecord } from "../protocols/shared.js";
|
|
@@ -98,13 +99,24 @@ const configuredRoute = (input) => {
|
|
|
98
99
|
};
|
|
99
100
|
export const configure = (input = {}) => {
|
|
100
101
|
const route = configuredRoute(input);
|
|
102
|
+
const evaluation = (modelID) => SystemOne.model({
|
|
103
|
+
id: modelID,
|
|
104
|
+
provider: id,
|
|
105
|
+
providerMetadataKey: "openrouter",
|
|
106
|
+
auth: AuthOptions.bearer(input, "OPENROUTER_API_KEY"),
|
|
107
|
+
baseURL: input.baseURL ?? baseURL,
|
|
108
|
+
headers: input.headers,
|
|
109
|
+
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
|
110
|
+
});
|
|
101
111
|
return {
|
|
102
112
|
id,
|
|
103
113
|
model: (modelID) => route.model({ id: modelID, compatibility: { supportsPromptCacheKey: true } }),
|
|
114
|
+
experimental: { evaluation },
|
|
104
115
|
configure,
|
|
105
116
|
};
|
|
106
117
|
};
|
|
107
118
|
export const provider = configure();
|
|
119
|
+
export const experimental = provider.experimental;
|
|
108
120
|
export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOptions }) => configure({
|
|
109
121
|
apiKey,
|
|
110
122
|
baseURL,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EvaluationModel } from "../experimental/evaluation.js";
|
|
2
|
+
import { type ProviderAuthOption } from "../route/auth-options.js";
|
|
3
|
+
import { HttpOptions, ModelID } from "../schema/index.js";
|
|
4
|
+
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
5
|
+
export interface EvaluationOptions {
|
|
6
|
+
readonly [key: string]: unknown;
|
|
7
|
+
readonly gateway?: Readonly<{
|
|
8
|
+
readonly [key: string]: unknown;
|
|
9
|
+
readonly zeroDataRetention?: boolean;
|
|
10
|
+
readonly only?: ReadonlyArray<string>;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export type Options = ProviderAuthOption<"optional"> & {
|
|
14
|
+
readonly baseURL?: string;
|
|
15
|
+
readonly headers?: Record<string, string>;
|
|
16
|
+
readonly http?: HttpOptions.Input;
|
|
17
|
+
};
|
|
18
|
+
export declare const configure: (input?: Options) => {
|
|
19
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
20
|
+
experimental: {
|
|
21
|
+
evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
|
|
22
|
+
};
|
|
23
|
+
configure: (input?: Options) => /*elided*/ any;
|
|
24
|
+
};
|
|
25
|
+
export declare const provider: {
|
|
26
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
27
|
+
experimental: {
|
|
28
|
+
evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
|
|
29
|
+
};
|
|
30
|
+
configure: (input?: Options) => {
|
|
31
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
32
|
+
experimental: {
|
|
33
|
+
evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
|
|
34
|
+
};
|
|
35
|
+
configure: /*elided*/ any;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export declare const experimental: {
|
|
39
|
+
evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
|
|
40
|
+
};
|
|
41
|
+
export * as VercelAIGateway from "./vercel-ai-gateway.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
import { EvaluationAnswer, EvaluationInput, EvaluationModel, EvaluationQuestion, EvaluationResponse, EvaluationRounding, } from "../experimental/evaluation.js";
|
|
4
|
+
import { Auth } from "../route/auth.js";
|
|
5
|
+
import { AuthOptions } from "../route/auth-options.js";
|
|
6
|
+
import { AIError, HttpContext, HttpOptions, InvalidProviderOutputError, InvalidRequestError, ModelID, ProviderID, ProviderMetadata, Usage, } from "../schema/index.js";
|
|
7
|
+
export const id = ProviderID.make("vercel-ai-gateway");
|
|
8
|
+
const baseURL = "https://ai-gateway.vercel.sh/v1";
|
|
9
|
+
const Request = Schema.StructWithRest(Schema.Struct({
|
|
10
|
+
model: Schema.String,
|
|
11
|
+
state: EvaluationInput,
|
|
12
|
+
questions: Schema.Record(Schema.String, EvaluationQuestion),
|
|
13
|
+
providerOptions: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
14
|
+
}), [Schema.Record(Schema.String, Schema.Any)]);
|
|
15
|
+
const Response = Schema.Struct({
|
|
16
|
+
model: Schema.optional(Schema.String),
|
|
17
|
+
answers: Schema.Record(Schema.String, EvaluationAnswer),
|
|
18
|
+
usage: Schema.optional(Schema.Struct({
|
|
19
|
+
inputTokens: Schema.optional(Schema.Number),
|
|
20
|
+
outputTokens: Schema.optional(Schema.Number),
|
|
21
|
+
})),
|
|
22
|
+
rounding: Schema.optional(EvaluationRounding),
|
|
23
|
+
providerMetadata: Schema.optional(ProviderMetadata),
|
|
24
|
+
});
|
|
25
|
+
export const configure = (input = {}) => {
|
|
26
|
+
const evaluation = (modelID) => EvaluationModel.make({
|
|
27
|
+
id: modelID,
|
|
28
|
+
provider: id,
|
|
29
|
+
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
|
30
|
+
route: {
|
|
31
|
+
id: "vercel-evaluation",
|
|
32
|
+
evaluate: (req, send) => Effect.gen(function* () {
|
|
33
|
+
const url = new URL(`${(input.baseURL ?? baseURL).replace(/\/$/, "")}/evaluate`);
|
|
34
|
+
Object.entries(req.http?.query ?? {}).forEach(([key, value]) => url.searchParams.set(key, value));
|
|
35
|
+
const body = yield* Schema.encodeUnknownEffect(Schema.fromJsonString(Request))({
|
|
36
|
+
...req.http?.body,
|
|
37
|
+
model: req.model.id,
|
|
38
|
+
state: req.state,
|
|
39
|
+
questions: req.questions,
|
|
40
|
+
providerOptions: req.options,
|
|
41
|
+
}).pipe(Effect.mapError((cause) => new AIError({ reason: new InvalidRequestError({ message: cause.message, cause }) })));
|
|
42
|
+
const headers = yield* Auth.toEffect(AuthOptions.bearer(input, ["AI_GATEWAY_API_KEY", "VERCEL_OIDC_TOKEN"]))({
|
|
43
|
+
request: req,
|
|
44
|
+
method: "POST",
|
|
45
|
+
url: url.toString(),
|
|
46
|
+
body,
|
|
47
|
+
headers: Headers.fromInput({ ...input.headers, ...req.http?.headers }),
|
|
48
|
+
});
|
|
49
|
+
const res = yield* send(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(body, "application/json")));
|
|
50
|
+
const http = new HttpContext({ url: res.request.url, status: res.status, headers: res.headers });
|
|
51
|
+
const fail = (message, cause, body) => new AIError({
|
|
52
|
+
reason: new InvalidProviderOutputError({
|
|
53
|
+
route: "vercel-evaluation",
|
|
54
|
+
message,
|
|
55
|
+
body,
|
|
56
|
+
http,
|
|
57
|
+
cause,
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
const text = yield* res.text.pipe(Effect.mapError((cause) => fail("Failed to read the Vercel AI Gateway evaluation response", cause)));
|
|
61
|
+
const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Response))(text).pipe(Effect.mapError((cause) => fail("Vercel AI Gateway returned an invalid evaluation response", cause, text)));
|
|
62
|
+
return new EvaluationResponse({
|
|
63
|
+
model: ModelID.make(data.model ?? req.model.id),
|
|
64
|
+
answers: data.answers,
|
|
65
|
+
usage: data.usage
|
|
66
|
+
? new Usage({
|
|
67
|
+
inputTokens: data.usage.inputTokens,
|
|
68
|
+
outputTokens: data.usage.outputTokens,
|
|
69
|
+
totalTokens: data.usage.inputTokens === undefined && data.usage.outputTokens === undefined
|
|
70
|
+
? undefined
|
|
71
|
+
: (data.usage.inputTokens ?? 0) + (data.usage.outputTokens ?? 0),
|
|
72
|
+
providerMetadata: { gateway: data.usage },
|
|
73
|
+
})
|
|
74
|
+
: undefined,
|
|
75
|
+
rounding: data.rounding,
|
|
76
|
+
providerMetadata: data.providerMetadata,
|
|
77
|
+
});
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return { id, experimental: { evaluation }, configure };
|
|
82
|
+
};
|
|
83
|
+
export const provider = configure();
|
|
84
|
+
export const experimental = provider.experimental;
|
|
85
|
+
export * as VercelAIGateway from "./vercel-ai-gateway.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-19996",
|
|
4
4
|
"name": "@opencode/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
32
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
33
|
-
"@opencode/http-recorder": "0.0.0-dev-
|
|
33
|
+
"@opencode/http-recorder": "0.0.0-dev-19996",
|
|
34
34
|
"@tsconfig/bun": "1.0.9",
|
|
35
35
|
"@types/bun": "1.4.0",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@aws-sdk/credential-providers": "3.1057.0",
|
|
41
41
|
"@smithy/eventstream-codec": "4.2.14",
|
|
42
42
|
"@smithy/util-utf8": "4.2.2",
|
|
43
|
-
"@opencode/schema": "0.0.0-dev-
|
|
43
|
+
"@opencode/schema": "0.0.0-dev-19996",
|
|
44
44
|
"aws4fetch": "1.0.20",
|
|
45
45
|
"effect": "4.0.0-rc.112",
|
|
46
46
|
"google-auth-library": "10.5.0"
|