diff --git a/apis/cloudflare/package.json b/apis/cloudflare/package.json
index 8289c4ae..b5812a52 100644
--- a/apis/cloudflare/package.json
+++ b/apis/cloudflare/package.json
@@ -26,6 +26,6 @@
"@opentelemetry/resources": "^2.0.0",
"@opentelemetry/sdk-metrics": "^2.1.0",
"dotenv": "^16.3.1",
- "zod": "3.25.34"
+ "zod": "4.2.1"
}
}
diff --git a/apis/vercel/next-env.d.ts b/apis/vercel/next-env.d.ts
index fd36f949..725dd6f2 100644
--- a/apis/vercel/next-env.d.ts
+++ b/apis/vercel/next-env.d.ts
@@ -3,4 +3,4 @@
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/basic-features/typescript for more information.
+// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
diff --git a/package.json b/package.json
index 9d752ccd..1f4d4a97 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,6 @@
},
"packageManager": "pnpm@8.15.5",
"resolutions": {
- "zod": "3.25.34"
+ "zod": "4.2.1"
}
}
diff --git a/packages/proxy/package.json b/packages/proxy/package.json
index 8d4e1ef0..0dd82400 100644
--- a/packages/proxy/package.json
+++ b/packages/proxy/package.json
@@ -120,6 +120,6 @@
"openai": "4.104.0",
"openapi-json-schema": "^2.0.0",
"uuid": "^9.0.1",
- "zod": "^3.25.34"
+ "zod": "4.2.1"
}
}
diff --git a/packages/proxy/schema/audio.ts b/packages/proxy/schema/audio.ts
index 15c636f7..74bd1aa7 100644
--- a/packages/proxy/schema/audio.ts
+++ b/packages/proxy/schema/audio.ts
@@ -25,8 +25,8 @@ export const pcmAudioFormatSchema = z
.discriminatedUnion("name", [
z.object({
name: z.literal("pcm"),
- byte_order: z.enum(["little", "big"]).default("little"),
- number_encoding: z.enum(["int", "float"]).default("int"),
+ byte_order: z.enum(["little", "big"]).prefault("little"),
+ number_encoding: z.enum(["int", "float"]).prefault("int"),
bits_per_sample: z.union([z.literal(8), z.literal(16)]),
}),
z.object({
diff --git a/packages/proxy/schema/index.ts b/packages/proxy/schema/index.ts
index ee93455e..f7bafea5 100644
--- a/packages/proxy/schema/index.ts
+++ b/packages/proxy/schema/index.ts
@@ -690,14 +690,14 @@ export const anthropicSupportedMediaTypes = [
export const anthropicTextBlockSchema = z.object({
type: z.literal("text").optional(),
- text: z.string().default(""),
+ text: z.string().prefault(""),
});
export const anthropicImageBlockSchema = z.object({
type: z.literal("image").optional(),
source: z.object({
type: z.enum(["base64"]).optional(),
media_type: z.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
- data: z.string().default(""),
+ data: z.string().prefault(""),
}),
});
const anthropicContentBlockSchema = z.union([
@@ -706,7 +706,7 @@ const anthropicContentBlockSchema = z.union([
]);
const anthropicContentBlocksSchema = z.array(anthropicContentBlockSchema);
const anthropicContentSchema = z.union([
- z.string().default(""),
+ z.string().prefault(""),
anthropicContentBlocksSchema,
]);
diff --git a/packages/proxy/schema/models.test.ts b/packages/proxy/schema/models.test.ts
index d6d22400..de148633 100644
--- a/packages/proxy/schema/models.test.ts
+++ b/packages/proxy/schema/models.test.ts
@@ -5,7 +5,7 @@ import { ModelSchema } from "./models";
import { z } from "zod";
it("parse model list", () => {
- const models = z.record(z.unknown()).parse(raw_models);
+ const models = z.record(z.string(), z.unknown()).parse(raw_models);
for (const [key, value] of Object.entries(models)) {
const result = ModelSchema.safeParse(value);
if (!result.success) {
diff --git a/packages/proxy/schema/models.ts b/packages/proxy/schema/models.ts
index 1e0e8e08..c84936e4 100644
--- a/packages/proxy/schema/models.ts
+++ b/packages/proxy/schema/models.ts
@@ -87,14 +87,19 @@ export const ModelSchema = z.object({
export type ModelSpec = z.infer;
import modelListJson from "./model_list.json";
-const modelListJsonTyped = z.record(ModelSchema).parse(modelListJson);
+const modelListJsonTyped = z
+ .record(z.string(), ModelSchema)
+ .parse(modelListJson);
// Because this file can be included and bundled in various ways, it's important to
// really inject these variables into the global scope, rather than let the bundler
// have its way with them.
declare global {
+ // eslint-disable-next-line no-var
var _proxy_availableModels: { [name: string]: ModelSpec } | undefined;
+ // eslint-disable-next-line no-var
var _proxy_cachedModels: { [name: string]: ModelSpec } | null;
+ // eslint-disable-next-line no-var
var _proxy_cacheTimestamp: number | null;
}
@@ -158,6 +163,7 @@ async function loadModelsFromControlPlane(
throw new Error(`Failed to fetch models: ${response.statusText}`);
}
const data = await response.json();
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
globalThis._proxy_cachedModels = data as { [name: string]: ModelSpec };
globalThis._proxy_cacheTimestamp = Date.now();
} catch (error) {
diff --git a/packages/proxy/schema/openai-realtime.ts b/packages/proxy/schema/openai-realtime.ts
index d0a3f745..e6c8d202 100644
--- a/packages/proxy/schema/openai-realtime.ts
+++ b/packages/proxy/schema/openai-realtime.ts
@@ -29,7 +29,7 @@ export const toolDefinitionTypeSchema = z.object({
type: z.literal("function").optional(),
name: z.string(),
description: z.string(),
- parameters: z.record(z.unknown()),
+ parameters: z.record(z.string(), z.unknown()),
});
export const sessionResourceTypeSchema = z.object({
diff --git a/packages/proxy/schema/secrets.ts b/packages/proxy/schema/secrets.ts
index 4ab582b8..2d8d9cf8 100644
--- a/packages/proxy/schema/secrets.ts
+++ b/packages/proxy/schema/secrets.ts
@@ -1,30 +1,26 @@
import { z } from "zod";
import { ModelSchema } from "./models";
-export const BaseMetadataSchema = z
- .object({
- models: z.array(z.string()).nullish(),
- customModels: z.record(ModelSchema).nullish(),
- excludeDefaultModels: z.boolean().nullish(),
- additionalHeaders: z.record(z.string(), z.string()).nullish(),
- supportsStreaming: z.boolean().default(true),
- })
- .strict();
-
-export const AzureMetadataSchema = BaseMetadataSchema.merge(
- z.object({
- api_base: z.string().url(),
- api_version: z.string().default("2023-07-01-preview"),
- deployment: z.string().nullish(),
- auth_type: z.enum(["api_key", "entra_api"]).default("api_key"),
- no_named_deployment: z
- .boolean()
- .default(false)
- .describe(
- "If true, the deployment name will not be used in the request path.",
- ),
- }),
-).strict();
+export const BaseMetadataSchema = z.strictObject({
+ models: z.array(z.string()).nullish(),
+ customModels: z.record(z.string(), ModelSchema).nullish(),
+ excludeDefaultModels: z.boolean().nullish(),
+ additionalHeaders: z.record(z.string(), z.string()).nullish(),
+ supportsStreaming: z.boolean().prefault(true),
+});
+
+export const AzureMetadataSchema = BaseMetadataSchema.extend({
+ api_base: z.url(),
+ api_version: z.string().prefault("2023-07-01-preview"),
+ deployment: z.string().nullish(),
+ auth_type: z.enum(["api_key", "entra_api"]).prefault("api_key"),
+ no_named_deployment: z
+ .boolean()
+ .prefault(false)
+ .describe(
+ "If true, the deployment name will not be used in the request path.",
+ ),
+});
export const AzureEntraSecretSchema = z.object({
client_id: z.string().min(1, "Client ID cannot be empty"),
@@ -34,30 +30,24 @@ export const AzureEntraSecretSchema = z.object({
});
export type AzureEntraSecret = z.infer;
-export const BedrockMetadataSchema = BaseMetadataSchema.merge(
- z.object({
- region: z.string().min(1, "Region cannot be empty"),
- access_key: z.string().min(1, "Access key cannot be empty"),
- session_token: z.string().nullish(),
- api_base: z.union([z.string().url(), z.string().length(0)]).nullish(),
- }),
-).strict();
+export const BedrockMetadataSchema = BaseMetadataSchema.extend({
+ region: z.string().min(1, "Region cannot be empty"),
+ access_key: z.string().min(1, "Access key cannot be empty"),
+ session_token: z.string().nullish(),
+ api_base: z.union([z.url(), z.string().length(0)]).nullish(),
+});
export type BedrockMetadata = z.infer;
-export const VertexMetadataSchema = BaseMetadataSchema.merge(
- z.object({
- project: z.string().min(1, "Project cannot be empty"),
- authType: z.enum(["access_token", "service_account_key"]),
- api_base: z.union([z.string().url(), z.string().length(0)]).nullish(),
- }),
-).strict();
+export const VertexMetadataSchema = BaseMetadataSchema.extend({
+ project: z.string().min(1, "Project cannot be empty"),
+ authType: z.enum(["access_token", "service_account_key"]),
+ api_base: z.union([z.url(), z.string().length(0)]).nullish(),
+});
-export const DatabricksMetadataSchema = BaseMetadataSchema.merge(
- z.object({
- api_base: z.string().url(),
- auth_type: z.enum(["pat", "service_principal_oauth"]).default("pat"),
- }),
-).strict();
+export const DatabricksMetadataSchema = BaseMetadataSchema.extend({
+ api_base: z.url(),
+ auth_type: z.enum(["pat", "service_principal_oauth"]).prefault("pat"),
+});
export const DatabricksOAuthSecretSchema = z.object({
client_id: z.string().min(1, "Client ID cannot be empty"),
@@ -65,90 +55,66 @@ export const DatabricksOAuthSecretSchema = z.object({
});
export type DatabricksOAuthSecret = z.infer;
-export const OpenAIMetadataSchema = BaseMetadataSchema.merge(
- z.object({
- api_base: z.union([
- z.string().url().optional(),
- z.string().length(0),
- z.null(),
- ]),
- organization_id: z.string().nullish(),
- }),
-).strict();
+export const OpenAIMetadataSchema = BaseMetadataSchema.extend({
+ api_base: z.union([z.url().optional(), z.string().length(0), z.null()]),
+ organization_id: z.string().nullish(),
+});
-export const MistralMetadataSchema = BaseMetadataSchema.merge(
- z.object({
- api_base: z.union([z.string().url(), z.string().length(0)]).nullish(),
- }),
-).strict();
+export const MistralMetadataSchema = BaseMetadataSchema.extend({
+ api_base: z.union([z.url(), z.string().length(0)]).nullish(),
+});
-const APISecretBaseSchema = z
- .object({
- id: z.string().uuid().nullish(),
- org_name: z.string().nullish(),
- name: z.string().nullish(),
- secret: z.string(),
- metadata: z.record(z.unknown()).nullish(),
- })
- .strict();
+const APISecretBaseSchema = z.strictObject({
+ id: z.string().uuid().nullish(),
+ org_name: z.string().nullish(),
+ name: z.string().nullish(),
+ secret: z.string(),
+ metadata: z.record(z.string(), z.unknown()).nullish(),
+});
export const APISecretSchema = z.union([
- APISecretBaseSchema.merge(
- z.object({
- type: z.enum([
- "perplexity",
- "anthropic",
- "google",
- "replicate",
- "together",
- "baseten",
- "ollama",
- "groq",
- "lepton",
- "fireworks",
- "cerebras",
- "xAI",
- "js",
- ]),
- metadata: BaseMetadataSchema.nullish(),
- }),
- ),
- APISecretBaseSchema.merge(
- z.object({
- type: z.literal("openai"),
- metadata: OpenAIMetadataSchema.nullish(),
- }),
- ),
- APISecretBaseSchema.merge(
- z.object({
- type: z.literal("azure"),
- metadata: AzureMetadataSchema.nullish(),
- }),
- ),
- APISecretBaseSchema.merge(
- z.object({
- type: z.literal("bedrock"),
- metadata: BedrockMetadataSchema.nullish(),
- }),
- ),
- APISecretBaseSchema.merge(
- z.object({
- type: z.literal("vertex"),
- metadata: VertexMetadataSchema.nullish(),
- }),
- ),
- APISecretBaseSchema.merge(
- z.object({
- type: z.literal("databricks"),
- metadata: DatabricksMetadataSchema.nullish(),
- }),
- ),
- APISecretBaseSchema.merge(
- z.object({
- type: z.literal("mistral"),
- metadata: MistralMetadataSchema.nullish(),
- }),
- ),
+ APISecretBaseSchema.extend({
+ type: z.enum([
+ "perplexity",
+ "anthropic",
+ "google",
+ "replicate",
+ "together",
+ "baseten",
+ "ollama",
+ "groq",
+ "lepton",
+ "fireworks",
+ "cerebras",
+ "xAI",
+ "js",
+ ]),
+ metadata: BaseMetadataSchema.nullish(),
+ }),
+ APISecretBaseSchema.extend({
+ type: z.literal("openai"),
+ metadata: OpenAIMetadataSchema.nullish(),
+ }),
+ APISecretBaseSchema.extend({
+ type: z.literal("azure"),
+ metadata: AzureMetadataSchema.nullish(),
+ }),
+ APISecretBaseSchema.extend({
+ type: z.literal("bedrock"),
+ metadata: BedrockMetadataSchema.nullish(),
+ }),
+ APISecretBaseSchema.extend({
+ type: z.literal("vertex"),
+ metadata: VertexMetadataSchema.nullish(),
+ }),
+ APISecretBaseSchema.extend({
+ type: z.literal("databricks"),
+ metadata: DatabricksMetadataSchema.nullish(),
+ }),
+ APISecretBaseSchema.extend({
+ type: z.literal("mistral"),
+ metadata: MistralMetadataSchema.nullish(),
+ }),
]);
export type APISecret = z.infer;
@@ -157,10 +123,10 @@ export const proxyLoggingParamSchema = z
.object({
parent: z.string().optional(),
project_name: z.string().optional(),
- compress_audio: z.boolean().default(true),
+ compress_audio: z.boolean().prefault(true),
})
.refine((data) => data.parent || data.project_name, {
- message: "Either 'parent' or 'project_name' must be provided",
+ error: "Either 'parent' or 'project_name' must be provided",
})
.describe(
"If present, proxy will log requests to the given Braintrust project or parent span.",
@@ -179,7 +145,7 @@ export const credentialsRequestSchema = z
ttl_seconds: z
.number()
.max(60 * 60 * 24)
- .default(60 * 10)
+ .prefault(60 * 10)
.describe("TTL of the temporary credential. 10 minutes by default."),
logging: proxyLoggingParamSchema.nullish(),
})
diff --git a/packages/proxy/scripts/generate_types.ts b/packages/proxy/scripts/generate_types.ts
index c6448c37..f8ab625b 100644
--- a/packages/proxy/scripts/generate_types.ts
+++ b/packages/proxy/scripts/generate_types.ts
@@ -5,6 +5,7 @@ import {
} from "openapi-zod-client";
import * as fs from "fs/promises";
import path from "node:path";
+import * as ts from "typescript";
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
const OPENAPI_SPEC_PATH = path.join(SCRIPT_DIR, "../generated_types.json");
@@ -17,6 +18,7 @@ const OUTPUT_PATH = path.join(SCRIPT_DIR, "../src/generated_types.ts");
async function main() {
const openApiDoc = JSON.parse(await fs.readFile(OPENAPI_SPEC_PATH, "utf-8"));
const handlebars = getHandlebars();
+ // only outputs as zod v3, support for v4 still in progress.
await generateZodClientFromOpenAPI({
openApiDoc,
templatePath: TEMPLATE_PATH,
@@ -28,11 +30,302 @@ async function main() {
additionalPropertiesDefaultValue: false,
},
});
+ // Read generated code. Optionally skip post-processing when debugging raw output.
+ let code = await fs.readFile(OUTPUT_PATH, "utf8");
+
+ // If SKIP_CODMOD=1 is set in the environment, write the generated file as-is
+ // (with the generated banner) and exit. Useful to inspect upstream generator output
+ // before applying our v3->v4 codemod.
+ if (process.env.SKIP_CODMOD === "1") {
+ const internalGitSha = openApiDoc.info["x-internal-git-sha"] || "UNKNOWN";
+ const banner = `// Auto-generated file (internal git SHA ${internalGitSha}) -- do not modify\n\n`;
+ await fs.writeFile(OUTPUT_PATH, banner + code);
+ return;
+ }
+
+ const fixGenerated = (s: string) => {
+ // Robust AST transform using TypeScript Transformer API. This reliably
+ // rewrites single-arg `z.record(valueSchema)` into `z.record(z.string(), valueSchema)`
+ // and unwraps parenthesized expressions. If anything goes wrong, fall back to the cleaned string.
+ try {
+ const sf = ts.createSourceFile(
+ "generated_types.ts",
+ s,
+ ts.ScriptTarget.Latest,
+ /*setParentNodes*/ true,
+ ts.ScriptKind.TS,
+ );
+
+ const isZCall = (expr: ts.Expression, name: string) =>
+ ts.isPropertyAccessExpression(expr) &&
+ ts.isIdentifier(expr.expression) &&
+ expr.expression.text === "z" &&
+ expr.name.text === name;
+
+ const makeZCallExpr = (name: string) =>
+ ts.factory.createCallExpression(
+ ts.factory.createPropertyAccessExpression(
+ ts.factory.createIdentifier("z"),
+ name,
+ ),
+ undefined,
+ [],
+ );
+
+ const transformer: ts.TransformerFactory = (context) => {
+ const visit: ts.Visitor = (node) => {
+ if (ts.isCallExpression(node)) {
+ const expr = node.expression;
+
+ // Handle z.record(...) general case
+ if (isZCall(expr, "record")) {
+ const args = node.arguments.slice();
+ if (args.length === 1) {
+ // single-arg -> insert z.string() as key schema
+ const valueArg = ts.isParenthesizedExpression(args[0])
+ ? args[0].expression
+ : args[0];
+ return ts.factory.updateCallExpression(
+ node,
+ node.expression,
+ node.typeArguments,
+ [makeZCallExpr("string"), valueArg],
+ );
+ }
+
+ // If call already has 2+ args, just unwrap parentheses on second arg
+ if (args.length >= 2) {
+ const second = args[1];
+ const newSecond = ts.isParenthesizedExpression(second)
+ ? second.expression
+ : second;
+ const newArgs = [args[0], newSecond, ...args.slice(2)];
+ return ts.factory.updateCallExpression(
+ node,
+ node.expression,
+ node.typeArguments,
+ newArgs,
+ );
+ }
+ }
+ }
+ return ts.visitEachChild(node, visit, context);
+ };
+ return (node) => ts.visitNode(node, visit) as ts.SourceFile;
+ };
+
+ const result = ts.transform(sf, [transformer]);
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Transformer API returns Node[]; we expect a SourceFile here
+ const transformed = result.transformed[0] as ts.SourceFile;
+ const printer = ts.createPrinter({ removeComments: false });
+ const printed = printer.printFile(transformed);
+ result.dispose();
+ return printed;
+ } catch {
+ return s;
+ }
+ };
+
+ // Token-level repair: balance parentheses for `z.record(...)` calls, insert missing
+ // `z.unknown()` value argument when the call only had a single key schema, and
+ // remove duplicated trailing ')' tokens following the call.
+ const repairRecordCalls = (src: string) => {
+ const needle = "z.record(";
+ let out = "";
+ let i = 0;
+ while (true) {
+ const p = src.indexOf(needle, i);
+ if (p === -1) {
+ out += src.slice(i);
+ break;
+ }
+ out += src.slice(i, p);
+ let k = p + needle.length;
+ let depth = 1;
+ let inSingle = false;
+ let inDouble = false;
+ let inBack = false;
+ let escaped = false;
+ let end = -1;
+ for (; k < src.length; k++) {
+ const ch = src[k];
+ if (escaped) {
+ escaped = false;
+ continue;
+ }
+ if (ch === "\\") {
+ escaped = true;
+ continue;
+ }
+ if (inSingle) {
+ if (ch === "'") {
+ inSingle = false;
+ }
+ continue;
+ }
+ if (inDouble) {
+ if (ch === '"') {
+ inDouble = false;
+ }
+ continue;
+ }
+ if (inBack) {
+ if (ch === "`") {
+ inBack = false;
+ }
+ continue;
+ }
+ if (ch === "'") {
+ inSingle = true;
+ continue;
+ }
+ if (ch === '"') {
+ inDouble = true;
+ continue;
+ }
+ if (ch === "`") {
+ inBack = true;
+ continue;
+ }
+ if (ch === "(") {
+ depth++;
+ continue;
+ }
+ if (ch === ")") {
+ depth--;
+ if (depth === 0) {
+ end = k;
+ break;
+ }
+ continue;
+ }
+ }
+ if (end === -1) {
+ // malformed remainder; append rest and break
+ out += src.slice(p);
+ break;
+ }
+
+ const inner = src.slice(p + needle.length, end);
+
+ // determine if there is a top-level comma in `inner`
+ let hasTopComma = false;
+ let pd = 0,
+ bd = 0,
+ cd = 0; // paren, bracket, brace depth for detection
+ inSingle = inDouble = inBack = escaped = false;
+ for (let m = 0; m < inner.length; m++) {
+ const ch = inner[m];
+ if (escaped) {
+ escaped = false;
+ continue;
+ }
+ if (ch === "\\") {
+ escaped = true;
+ continue;
+ }
+ if (inSingle) {
+ if (ch === "'") inSingle = false;
+ continue;
+ }
+ if (inDouble) {
+ if (ch === '"') inDouble = false;
+ continue;
+ }
+ if (inBack) {
+ if (ch === "`") inBack = false;
+ continue;
+ }
+ if (ch === "'") {
+ inSingle = true;
+ continue;
+ }
+ if (ch === '"') {
+ inDouble = true;
+ continue;
+ }
+ if (ch === "`") {
+ inBack = true;
+ continue;
+ }
+ if (ch === "(") {
+ pd++;
+ continue;
+ }
+ if (ch === ")") {
+ if (pd > 0) pd--;
+ continue;
+ }
+ if (ch === "[") {
+ bd++;
+ continue;
+ }
+ if (ch === "]") {
+ if (bd > 0) bd--;
+ continue;
+ }
+ if (ch === "{") {
+ cd++;
+ continue;
+ }
+ if (ch === "}") {
+ if (cd > 0) cd--;
+ continue;
+ }
+ if (ch === "," && pd === 0 && bd === 0 && cd === 0) {
+ hasTopComma = true;
+ break;
+ }
+ }
+
+ let replacedCall = null;
+ if (!hasTopComma) {
+ // single-arg form: insert z.unknown() as the second arg
+ replacedCall = `${needle}${inner.trim()}, z.unknown())`;
+ } else {
+ // keep original text for call
+ replacedCall = src.slice(p, end + 1);
+ }
+
+ // append the replacement
+ out += replacedCall;
+
+ // advance k to after end, and collapse any immediately following duplicated ')' tokens down to one
+ let j = end + 1;
+ // count consecutive ) characters
+ let closeCount = 0;
+ while (j < src.length && src[j] === ")") {
+ closeCount++;
+ j++;
+ }
+ if (closeCount > 0) {
+ // we already emitted one ')' as part of replacement; if extra ) found, skip them
+ // (effectively collapsing duplicates)
+ }
+
+ i = j;
+ }
+ return out;
+ };
+
+ code = fixGenerated(code);
+ // Apply token-level repairs for z.record(...) cases
+ code = repairRecordCalls(code);
- const code = await fs.readFile(OUTPUT_PATH, "utf8");
const internalGitSha = openApiDoc.info["x-internal-git-sha"] || "UNKNOWN";
const banner = `// Auto-generated file (internal git SHA ${internalGitSha}) -- do not modify\n\n`;
await fs.writeFile(OUTPUT_PATH, banner + code);
+
+ // Format the generated file with prettier
+ const { execSync } = await import("node:child_process");
+ try {
+ execSync(`./node_modules/.bin/prettier --write "${OUTPUT_PATH}"`, {
+ cwd: path.join(SCRIPT_DIR, "../../.."),
+ stdio: "inherit",
+ });
+ } catch (error) {
+ console.warn("Warning: Could not format generated file with prettier");
+ }
}
main();
diff --git a/packages/proxy/scripts/sync_models.ts b/packages/proxy/scripts/sync_models.ts
index f0fd5b77..6303ce05 100644
--- a/packages/proxy/scripts/sync_models.ts
+++ b/packages/proxy/scripts/sync_models.ts
@@ -75,7 +75,7 @@ const liteLLMModelDetailSchema = z
})
.passthrough();
-const liteLLMModelListSchema = z.record(liteLLMModelDetailSchema);
+const liteLLMModelListSchema = z.record(z.string(), liteLLMModelDetailSchema);
type LiteLLMModelDetail = z.infer;
type LiteLLMModelList = z.infer;
@@ -114,7 +114,7 @@ async function fetchRemoteModels(url: string): Promise {
if (error instanceof z.ZodError) {
console.error(
"Zod validation errors in remote data:",
- error.errors,
+ error.issues,
);
reject(
new Error(
@@ -142,12 +142,12 @@ async function readLocalModels(filePath: string): Promise {
const fileContent = await fs.promises.readFile(filePath, "utf-8");
const localData = JSON.parse(fileContent);
// Validate local data with the imported ModelSchema
- return z.record(ModelSchema).parse(localData);
+ return z.record(z.string(), ModelSchema).parse(localData);
} catch (error) {
if (error instanceof z.ZodError) {
console.error(
"Zod validation errors in local model_list.json:",
- error.errors,
+ error.issues,
);
throw new Error("Local model_list.json failed Zod validation.");
}
diff --git a/packages/proxy/src/generated_types.ts b/packages/proxy/src/generated_types.ts
index 8b8a40bf..9004c0f3 100644
--- a/packages/proxy/src/generated_types.ts
+++ b/packages/proxy/src/generated_types.ts
@@ -1,7 +1,6 @@
// Auto-generated file (internal git SHA e6490ffb3d42f5dbc59d748bb5016f6d8f11cac2) -- do not modify
import { z } from "zod";
-
export const AclObjectType = z.union([
z.enum([
"organization",
@@ -539,7 +538,7 @@ export const ExperimentEvent = z.object({
expected: z.unknown().optional(),
error: z.unknown().optional(),
scores: z
- .union([z.record(z.union([z.number(), z.null()])), z.null()])
+ .union([z.record(z.string(), z.union([z.number(), z.null()])), z.null()])
.optional(),
metadata: z
.union([
@@ -551,7 +550,7 @@ export const ExperimentEvent = z.object({
])
.optional(),
tags: z.union([z.array(z.string()), z.null()]).optional(),
- metrics: z.union([z.record(z.number()), z.null()]).optional(),
+ metrics: z.union([z.record(z.string(), z.number()), z.null()]).optional(),
context: z
.union([
z
@@ -687,7 +686,7 @@ export const PromptParserNullish = z.union([
z.object({
type: z.literal("llm_classifier"),
use_cot: z.boolean(),
- choice_scores: z.record(z.number().gte(0).lte(1)),
+ choice_scores: z.record(z.string(), z.number().gte(0).lte(1)),
}),
z.null(),
]);
@@ -809,8 +808,8 @@ export const GraphEdge = z.object({
export type GraphEdgeType = z.infer;
export const GraphData = z.object({
type: z.literal("graph"),
- nodes: z.record(GraphNode),
- edges: z.record(GraphEdge),
+ nodes: z.record(z.string(), GraphNode),
+ edges: z.record(z.string(), GraphEdge),
});
export type GraphDataType = z.infer;
export const FunctionData = z.union([
@@ -1162,7 +1161,7 @@ export const ProjectLogsEvent = z.object({
expected: z.unknown().optional(),
error: z.unknown().optional(),
scores: z
- .union([z.record(z.union([z.number(), z.null()])), z.null()])
+ .union([z.record(z.string(), z.union([z.number(), z.null()])), z.null()])
.optional(),
metadata: z
.union([
@@ -1174,7 +1173,7 @@ export const ProjectLogsEvent = z.object({
])
.optional(),
tags: z.union([z.array(z.string()), z.null()]).optional(),
- metrics: z.union([z.record(z.number()), z.null()]).optional(),
+ metrics: z.union([z.record(z.string(), z.number()), z.null()]).optional(),
context: z
.union([
z
@@ -1216,7 +1215,7 @@ export const ProjectScoreCategory = z.object({
export type ProjectScoreCategoryType = z.infer;
export const ProjectScoreCategories = z.union([
z.array(ProjectScoreCategory),
- z.record(z.number()),
+ z.record(z.string(), z.number()),
z.array(z.string()),
z.null(),
]);
@@ -1459,7 +1458,7 @@ export const ViewOptions = z.union([
frameStart: z.union([z.string(), z.null()]),
frameEnd: z.union([z.string(), z.null()]),
tzUTC: z.union([z.boolean(), z.null()]),
- chartVisibility: z.union([z.record(z.boolean()), z.null()]),
+ chartVisibility: z.union([z.record(z.string(), z.boolean()), z.null()]),
projectId: z.union([z.string(), z.null()]),
type: z.union([z.enum(["project", "experiment"]), z.null()]),
groupBy: z.union([z.string(), z.null()]),
@@ -1468,9 +1467,9 @@ export const ViewOptions = z.union([
}),
z
.object({
- columnVisibility: z.union([z.record(z.boolean()), z.null()]),
+ columnVisibility: z.union([z.record(z.string(), z.boolean()), z.null()]),
columnOrder: z.union([z.array(z.string()), z.null()]),
- columnSizing: z.union([z.record(z.number()), z.null()]),
+ columnSizing: z.union([z.record(z.string(), z.number()), z.null()]),
grouping: z.union([z.string(), z.null()]),
rowHeight: z.union([z.string(), z.null()]),
tallGroupRows: z.union([z.boolean(), z.null()]),
diff --git a/packages/proxy/src/providers/anthropic.ts b/packages/proxy/src/providers/anthropic.ts
index 9aa32d52..174a0764 100644
--- a/packages/proxy/src/providers/anthropic.ts
+++ b/packages/proxy/src/providers/anthropic.ts
@@ -140,7 +140,7 @@ export const anthropicStreamEventSchema = z.discriminatedUnion("type", [
type: z.literal("tool_use"),
id: z.string(),
name: z.string(),
- input: z.record(z.unknown()),
+ input: z.record(z.string(), z.unknown()),
}),
]),
}),
diff --git a/packages/proxy/src/providers/google.ts b/packages/proxy/src/providers/google.ts
index a6b9f923..7a4ae1c7 100644
--- a/packages/proxy/src/providers/google.ts
+++ b/packages/proxy/src/providers/google.ts
@@ -188,27 +188,27 @@ export async function openAIMessagesToGoogleMessages(
return sortedContent;
}
-const finishReason = finishReasonSchema.Enum;
+// finishReasonSchema.Enum (v3) is not available in Zod v4; switch on string literals instead
function translateFinishReason(
reason?: FinishReason | null,
): OpenAIChatCompletionChoice["finish_reason"] | null {
// "length" | "stop" | "tool_calls" | "content_filter" | "function_call"
switch (reason) {
- case finishReason.MAX_TOKENS:
+ case "MAX_TOKENS":
return "length";
- case finishReason.SAFETY:
- case finishReason.PROHIBITED_CONTENT:
- case finishReason.SPII:
- case finishReason.BLOCKLIST:
+ case "SAFETY":
+ case "PROHIBITED_CONTENT":
+ case "SPII":
+ case "BLOCKLIST":
return "content_filter";
- case finishReason.STOP:
+ case "STOP":
return "stop";
- case finishReason.RECITATION:
- case finishReason.LANGUAGE:
- case finishReason.OTHER:
- case finishReason.FINISH_REASON_UNSPECIFIED:
- case finishReason.MALFORMED_FUNCTION_CALL:
+ case "RECITATION":
+ case "LANGUAGE":
+ case "OTHER":
+ case "FINISH_REASON_UNSPECIFIED":
+ case "MALFORMED_FUNCTION_CALL":
return "content_filter";
case undefined:
default:
@@ -367,13 +367,10 @@ function convertGeminiPartsToOpenAIContent(
}
}
- // If only text content (single part), return as string for backwards compatibility
- if (
- !hasNonTextContent &&
- contentParts.length === 1 &&
- contentParts[0].type === "text"
- ) {
- return contentParts[0].text ?? "";
+ // If only text content (no images), return as string for backwards compatibility
+ // Concatenate all text parts into a single string
+ if (!hasNonTextContent && contentParts.every((p) => p.type === "text")) {
+ return contentParts.map((p) => p.text ?? "").join("");
}
// If no content parts, return empty string
diff --git a/packages/proxy/types/anthropic.ts b/packages/proxy/types/anthropic.ts
index 2f9b405b..f2f672df 100644
--- a/packages/proxy/types/anthropic.ts
+++ b/packages/proxy/types/anthropic.ts
@@ -51,7 +51,7 @@ const anthropicToolUseContentPartSchema = z.object({
type: z.literal("tool_use"),
id: z.string(),
name: z.string(),
- input: z.record(z.any()),
+ input: z.record(z.string(), z.unknown()),
cache_control: cacheControlSchema.optional(),
});
@@ -59,7 +59,7 @@ const anthropicServerToolUseContentPartSchema = z.object({
type: z.literal("server_tool_use"),
id: z.string(),
name: z.enum(["web_search", "code_execution"]),
- input: z.record(z.any()),
+ input: z.record(z.string(), z.unknown()),
cache_control: cacheControlSchema.optional(),
});
@@ -129,7 +129,7 @@ const anthropicMCPToolUseContentPartSchema = z.object({
type: z.literal("mcp_tool_use"),
id: z.string(),
name: z.string(),
- input: z.record(z.any()),
+ input: z.record(z.string(), z.unknown()),
server_name: z.string(),
cache_control: cacheControlSchema.nullish(),
});
@@ -145,7 +145,7 @@ const anthropicMCPToolResultContentPartSchema = z.object({
type: z.literal("text"),
text: z.string(),
// This is a simplification of the strict citation schema
- citations: z.array(z.record(z.any())).nullish(),
+ citations: z.array(z.record(z.string(), z.unknown())).nullish(),
cache_control: cacheControlSchema.nullish(),
}),
),
diff --git a/packages/proxy/types/google.ts b/packages/proxy/types/google.ts
index 33795fc2..578d7951 100644
--- a/packages/proxy/types/google.ts
+++ b/packages/proxy/types/google.ts
@@ -101,14 +101,14 @@ const fileDataSchema = z.object({
const functionCallSchema = z.object({
id: z.string().nullish(),
- args: z.record(z.unknown()).nullish(),
+ args: z.record(z.string(), z.unknown()).nullish(),
name: z.string().nullish(),
});
const functionResponseSchema = z.object({
id: z.string().nullish(),
name: z.string().nullish(),
- response: z.record(z.unknown()).nullish(),
+ response: z.record(z.string(), z.unknown()).nullish(),
});
const blobSchema = z.object({
@@ -495,7 +495,7 @@ const schemaSchema: z.ZodSchema = z.object({
minimum: z.number().nullish(),
nullable: z.boolean().nullish(),
pattern: z.string().nullish(),
- properties: z.lazy(() => z.record(schemaSchema)).nullish(),
+ properties: z.lazy(() => z.record(z.string(), schemaSchema)).nullish(),
propertyOrdering: z.array(z.string()).nullish(),
required: z.array(z.string()).nullish(),
title: z.string().nullish(),
@@ -627,7 +627,7 @@ const generateContentConfigSchema = z.object({
safetySettings: z.array(safetySettingSchema).nullish(),
tools: toolListUnionSchema.nullish(),
toolConfig: toolConfigSchema.nullish(),
- labels: z.record(z.string()).nullish(),
+ labels: z.record(z.string(), z.string()).nullish(),
cachedContent: z.string().nullish(),
responseModalities: z.array(z.string()).nullish(),
mediaResolution: mediaResolutionSchema.nullish(),
diff --git a/packages/proxy/utils/index.ts b/packages/proxy/utils/index.ts
index ebcf2380..8d8775ce 100644
--- a/packages/proxy/utils/index.ts
+++ b/packages/proxy/utils/index.ts
@@ -18,10 +18,12 @@ export function getCurrentUnixTimestamp(): number {
}
export const effortToBudgetMultiplier = {
+ none: 0,
minimal: 0,
low: 0.2,
medium: 0.5,
high: 0.8,
+ xhigh: 1.0,
} as const;
export const getBudgetMultiplier = (
diff --git a/packages/proxy/utils/tempCredentials.test.ts b/packages/proxy/utils/tempCredentials.test.ts
index d441b532..d38c842e 100644
--- a/packages/proxy/utils/tempCredentials.test.ts
+++ b/packages/proxy/utils/tempCredentials.test.ts
@@ -162,7 +162,7 @@ test("verifyTempCredentials wrong payload type", async () => {
});
await expect(
verifyTempCredentials({ jwt: jwtWrongSchema, cacheGet }),
- ).rejects.toThrow("invalid_literal");
+ ).rejects.toThrow("invalid_value");
// Non object.
const jwtWrongType = jwtSign("not an object", "auth token", {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8ee12257..12475bfb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,7 +5,7 @@ settings:
excludeLinksFromLockfile: false
overrides:
- zod: 3.25.34
+ zod: 4.2.1
importers:
@@ -49,16 +49,16 @@ importers:
version: 2.1.0(@opentelemetry/api@1.9.0)
braintrust:
specifier: ^0.3.7
- version: 0.3.7(zod@3.25.34)
+ version: 0.3.7(zod@4.2.1)
dotenv:
specifier: ^16.3.1
version: 16.3.1
openai:
specifier: ^6.3.0
- version: 6.3.0(zod@3.25.34)
+ version: 6.3.0(zod@4.2.1)
zod:
- specifier: 3.25.34
- version: 3.25.34
+ specifier: 4.2.1
+ version: 4.2.1
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20250810.0
@@ -116,7 +116,7 @@ importers:
version: 4.19.2
openai:
specifier: ^4.104.0
- version: 4.104.0(zod@3.25.34)
+ version: 4.104.0(zod@4.2.1)
redis:
specifier: ^4.6.8
version: 4.6.8
@@ -256,7 +256,7 @@ importers:
version: 9.0.2
openai:
specifier: 4.104.0
- version: 4.104.0(zod@3.25.34)
+ version: 4.104.0(zod@4.2.1)
openapi-json-schema:
specifier: ^2.0.0
version: 2.0.0
@@ -264,8 +264,8 @@ importers:
specifier: ^9.0.1
version: 9.0.1
zod:
- specifier: 3.25.34
- version: 3.25.34
+ specifier: 4.2.1
+ version: 4.2.1
devDependencies:
'@types/content-disposition':
specifier: ^0.5.8
@@ -317,7 +317,7 @@ importers:
version: 17.7.2
zod-to-json-schema:
specifier: ^3.24.6
- version: 3.24.6(zod@3.25.34)
+ version: 3.24.6(zod@4.2.1)
packages:
@@ -4561,7 +4561,7 @@ packages:
dependencies:
'@vue/compiler-ssr': 3.5.22
'@vue/shared': 3.5.22
- vue: 3.5.22(typescript@5.5.4)
+ vue: 3.5.22(typescript@5.3.3)
dev: false
/@vue/shared@3.5.13:
@@ -4572,14 +4572,14 @@ packages:
resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==}
dev: false
- /@zodios/core@10.9.6(axios@1.13.1)(zod@3.25.34):
+ /@zodios/core@10.9.6(axios@1.13.1)(zod@4.2.1):
resolution: {integrity: sha512-aH4rOdb3AcezN7ws8vDgBfGboZMk2JGGzEq/DtW65MhnRxyTGRuLJRWVQ/2KxDgWvV2F5oTkAS+5pnjKbl0n+A==}
peerDependencies:
axios: ^0.x || ^1.0.0
- zod: 3.25.34
+ zod: 4.2.1
dependencies:
axios: 1.13.1
- zod: 3.25.34
+ zod: 4.2.1
dev: true
/abbrev@1.1.1:
@@ -5086,11 +5086,11 @@ packages:
fill-range: 7.1.1
dev: true
- /braintrust@0.3.7(zod@3.25.34):
+ /braintrust@0.3.7(zod@4.2.1):
resolution: {integrity: sha512-P6nJLgM98IOiBvAsgfUn9dbrLhmTfvED/+2ouacImTgs9adlZoGrIib9BkRAm+keSTfKLBPFqMmUi7QZ4ClemQ==}
hasBin: true
peerDependencies:
- zod: 3.25.34
+ zod: 4.2.1
dependencies:
'@ai-sdk/provider': 1.1.3
'@next/env': 14.2.3
@@ -5112,8 +5112,8 @@ packages:
slugify: 1.6.6
source-map: 0.7.4
uuid: 9.0.1
- zod: 3.25.34
- zod-to-json-schema: 3.23.5(zod@3.25.34)
+ zod: 4.2.1
+ zod-to-json-schema: 3.23.5(zod@4.2.1)
transitivePeerDependencies:
- '@aws-sdk/credential-provider-web-identity'
- supports-color
@@ -8070,7 +8070,7 @@ packages:
workerd: 1.20251008.0
ws: 8.18.0
youch: 4.1.0-beta.10
- zod: 3.25.34
+ zod: 4.2.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -8474,12 +8474,12 @@ packages:
mimic-fn: 2.1.0
dev: true
- /openai@4.104.0(zod@3.25.34):
+ /openai@4.104.0(zod@4.2.1):
resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==}
hasBin: true
peerDependencies:
ws: ^8.18.0
- zod: 3.25.34
+ zod: 4.2.1
peerDependenciesMeta:
ws:
optional: true
@@ -8493,24 +8493,24 @@ packages:
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0
- zod: 3.25.34
+ zod: 4.2.1
transitivePeerDependencies:
- encoding
dev: false
- /openai@6.3.0(zod@3.25.34):
+ /openai@6.3.0(zod@4.2.1):
resolution: {integrity: sha512-E6vOGtZvdcb4yXQ5jXvDlUG599OhIkb/GjBLZXS+qk0HF+PJReIldEc9hM8Ft81vn+N6dRdFRb7BZNK8bbvXrw==}
hasBin: true
peerDependencies:
ws: ^8.18.0
- zod: 3.25.34
+ zod: 4.2.1
peerDependenciesMeta:
ws:
optional: true
zod:
optional: true
dependencies:
- zod: 3.25.34
+ zod: 4.2.1
dev: false
/openapi-json-schema@2.0.0:
@@ -8527,7 +8527,7 @@ packages:
dependencies:
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
'@liuli-util/fs-extra': 0.1.0
- '@zodios/core': 10.9.6(axios@1.13.1)(zod@3.25.34)
+ '@zodios/core': 10.9.6(axios@1.13.1)(zod@4.2.1)
axios: 1.13.1
cac: 6.7.14
handlebars: 4.7.8
@@ -8538,7 +8538,7 @@ packages:
tanu: 0.1.13
ts-pattern: 5.8.0
whence: 2.1.0
- zod: 3.25.34
+ zod: 4.2.1
transitivePeerDependencies:
- debug
- react
@@ -10002,7 +10002,7 @@ packages:
peerDependencies:
vue: '>=3.2.26 < 4'
dependencies:
- vue: 3.5.22(typescript@5.5.4)
+ vue: 3.5.22(typescript@5.3.3)
dev: false
/tailwindcss@3.2.7(postcss@8.4.38):
@@ -11236,21 +11236,21 @@ packages:
youch-core: 0.3.3
dev: true
- /zod-to-json-schema@3.23.5(zod@3.25.34):
+ /zod-to-json-schema@3.23.5(zod@4.2.1):
resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==}
peerDependencies:
- zod: 3.25.34
+ zod: 4.2.1
dependencies:
- zod: 3.25.34
+ zod: 4.2.1
dev: false
- /zod-to-json-schema@3.24.6(zod@3.25.34):
+ /zod-to-json-schema@3.24.6(zod@4.2.1):
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
peerDependencies:
- zod: 3.25.34
+ zod: 4.2.1
dependencies:
- zod: 3.25.34
+ zod: 4.2.1
dev: true
- /zod@3.25.34:
- resolution: {integrity: sha512-lZHvSc2PpWdcfpHlyB33HA9nqP16GpC9IpiG4lYq9jZCJVLZNnWd6Y1cj79bcLSBKTkxepfpjckPv5Y5VOPlwA==}
+ /zod@4.2.1:
+ resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==}