diff --git a/packages/console/app/src/lib/inference-proxy.ts b/packages/console/app/src/lib/inference-proxy.ts index 1eade3ff8fe..6c3f8aebe5c 100644 --- a/packages/console/app/src/lib/inference-proxy.ts +++ b/packages/console/app/src/lib/inference-proxy.ts @@ -8,9 +8,11 @@ const paths: Record = { "POST /zen/v1/chat/completions": "/openai/v1/chat/completions", "POST /zen/v1/responses": "/openai/v1/responses", "POST /zen/v1/messages": "/anthropic/v1/messages", + "POST /zen/systemone/v1/systemone": "/systemone/v1/systemone", "POST /zen/go/v1/chat/completions": "/go/openai/v1/chat/completions", "POST /zen/go/v1/responses": "/go/openai/v1/responses", "POST /zen/go/v1/messages": "/go/anthropic/v1/messages", + "POST /zen/go/systemone/v1/systemone": "/go/systemone/v1/systemone", "GET /zen/v1/models": "/v1/models", "GET /zen/go/v1/models": "/go/v1/models", "GET /zen/go/v1/usage": "/go/v1/usage", diff --git a/packages/console/app/src/routes/zen/go/systemone/v1/systemone.ts b/packages/console/app/src/routes/zen/go/systemone/v1/systemone.ts new file mode 100644 index 00000000000..4b9954fb2a7 --- /dev/null +++ b/packages/console/app/src/routes/zen/go/systemone/v1/systemone.ts @@ -0,0 +1,13 @@ +import type { APIEvent } from "@solidjs/start/server" +import { handler } from "~/routes/zen/util/handler" + +export function POST(input: APIEvent) { + return handler(input, { + format: "systemone", + modelList: "lite", + parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1], + parseModel: (url: string, body: any) => body.model, + parseVariant: () => undefined, + parseIsStream: () => false, + }) +} diff --git a/packages/console/app/src/routes/zen/systemone/v1/systemone.ts b/packages/console/app/src/routes/zen/systemone/v1/systemone.ts new file mode 100644 index 00000000000..c29b8471664 --- /dev/null +++ b/packages/console/app/src/routes/zen/systemone/v1/systemone.ts @@ -0,0 +1,13 @@ +import type { APIEvent } from "@solidjs/start/server" +import { handler } from "~/routes/zen/util/handler" + +export function POST(input: APIEvent) { + return handler(input, { + format: "systemone", + modelList: "full", + parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1], + parseModel: (url: string, body: any) => body.model, + parseVariant: () => undefined, + parseIsStream: () => false, + }) +} diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 414eb09fa66..269821e0e7e 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -33,6 +33,7 @@ import { anthropicHelper } from "./provider/anthropic" import { googleHelper } from "./provider/google" import { openaiHelper } from "./provider/openai" import { oaCompatHelper } from "./provider/openai-compatible" +import { systemoneHelper } from "./provider/systemone" import { createRateLimiter as createIpRateLimiter } from "./ipRateLimiter" import { createRateLimiter as createKeyRateLimiter } from "./keyRateLimiter" import { createTrialLimiter } from "./trialLimiter" @@ -685,6 +686,7 @@ export async function handler( if (format === "anthropic") return anthropicHelper(opts) if (format === "google") return googleHelper(opts) if (format === "openai") return openaiHelper(opts) + if (format === "systemone") return systemoneHelper(opts) return oaCompatHelper(opts) })(), } diff --git a/packages/console/app/src/routes/zen/util/provider/systemone.ts b/packages/console/app/src/routes/zen/util/provider/systemone.ts new file mode 100644 index 00000000000..7d0cd90ed1e --- /dev/null +++ b/packages/console/app/src/routes/zen/util/provider/systemone.ts @@ -0,0 +1,30 @@ +import { ProviderHelper } from "./provider" + +type Usage = { + input_tokens?: number + output_tokens?: number +} + +export const systemoneHelper: ProviderHelper = () => ({ + format: "systemone", + modifyUrl: (providerApi: string) => providerApi.replace(/\/$/, "") + "/systemone", + modifyHeaders: (headers: Headers, apiKey: string, stickyId: string) => { + headers.set("authorization", `Bearer ${apiKey}`) + headers.set("x-session-affinity", stickyId) + }, + modifyBody: (body: Record) => body, + createBinaryStreamDecoder: () => undefined, + createUsageParser: () => ({ + parse: (_chunk: string) => {}, + retrieve: () => undefined, + }), + extractUsage: (response: any) => response.usage, + normalizeUsage: (usage: Usage) => ({ + inputTokens: usage.input_tokens ?? 0, + outputTokens: usage.output_tokens ?? 0, + reasoningTokens: undefined, + cacheReadTokens: undefined, + cacheWrite5mTokens: undefined, + cacheWrite1hTokens: undefined, + }), +}) diff --git a/packages/console/app/test/providerUsage.test.ts b/packages/console/app/test/providerUsage.test.ts index 8b618042ae6..2726610c926 100644 --- a/packages/console/app/test/providerUsage.test.ts +++ b/packages/console/app/test/providerUsage.test.ts @@ -5,15 +5,27 @@ import { anthropicHelper } from "../src/routes/zen/util/provider/anthropic" import { googleHelper } from "../src/routes/zen/util/provider/google" import { oaCompatHelper } from "../src/routes/zen/util/provider/openai-compatible" import { openaiHelper } from "../src/routes/zen/util/provider/openai" +import { systemoneHelper } from "../src/routes/zen/util/provider/systemone" const providers = { anthropic: anthropicHelper({ reqModel: "claude-haiku-4-5", providerModel: "claude-haiku-4-5" }), google: googleHelper({ reqModel: "gemini-3-flash", providerModel: "gemini-3-flash" }), openai: openaiHelper({ reqModel: "gpt-5", providerModel: "gpt-5" }), "oa-compat": oaCompatHelper({ reqModel: "gpt-5-nano", providerModel: "gpt-5-nano" }), + systemone: systemoneHelper({ reqModel: "jev-1.13", providerModel: "jev-latest" }), } satisfies Record> describe("provider usage extraction", () => { + test("prepares SystemOne requests", () => { + const headers = new Headers() + providers.systemone.modifyHeaders(headers, "secret", "session") + + expect(providers.systemone.modifyUrl("https://api.typesafe.ai/v1/")) + .toBe("https://api.typesafe.ai/v1/systemone") + expect(headers.get("authorization")).toBe("Bearer secret") + expect(headers.get("x-session-affinity")).toBe("session") + }) + test("extracts Google non-stream usage metadata", () => { const usage = providers.google.extractUsage({ usageMetadata: { @@ -66,6 +78,21 @@ describe("provider usage extraction", () => { }) }) + test("extracts SystemOne usage", () => { + expect( + providers.systemone.normalizeUsage( + providers.systemone.extractUsage({ usage: { input_tokens: 312, output_tokens: 48 } }), + ), + ).toEqual({ + inputTokens: 312, + outputTokens: 48, + reasoningTokens: undefined, + cacheReadTokens: undefined, + cacheWrite5mTokens: undefined, + cacheWrite1hTokens: undefined, + }) + }) + test("parses OpenAI stream cache write usage", () => { const usageParser = providers.openai.createUsageParser() usageParser.parse( diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index d18ccc2b5fb..4305521b4af 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -8,7 +8,7 @@ import { Actor } from "./actor" import { Resource } from "@opencode-ai/console-resource" export namespace ZenData { - const FormatSchema = z.enum(["anthropic", "google", "openai", "oa-compat"]) + const FormatSchema = z.enum(["anthropic", "google", "openai", "oa-compat", "systemone"]) export type Format = z.infer const ModelCostSchema = z.object({ diff --git a/packages/console/core/test/model.test.ts b/packages/console/core/test/model.test.ts index bf4204d167e..3c653b72a5d 100644 --- a/packages/console/core/test/model.test.ts +++ b/packages/console/core/test/model.test.ts @@ -47,3 +47,18 @@ describe("ZenData cost200K threshold", () => { expect(entry(data).cost200K?.threshold).toBe(272_000) }) }) + +test("accepts the SystemOne provider format", () => { + expect( + ZenData.validate({ + ...base, + providers: { + systemone: { + api: "https://api.typesafe.ai/v1", + apiKey: "test", + format: "systemone", + }, + }, + }).providers.systemone.format, + ).toBe("systemone") +})