Spaces:
Runtime error
Runtime error
| import { z } from "zod"; | |
| import { | |
| ACCOUNT_FALLBACK_STRATEGY_VALUES, | |
| ROUTING_STRATEGY_VALUES, | |
| } from "@/shared/constants/routingStrategies"; | |
| import { SUPPORTED_BATCH_ENDPOINTS } from "@/shared/constants/batchEndpoints"; | |
| import { MAX_REQUEST_BODY_LIMIT_MB, MIN_REQUEST_BODY_LIMIT_MB } from "@/shared/constants/bodySize"; | |
| import { COMBO_CONFIG_MODES } from "@/shared/constants/comboConfigMode"; | |
| import { providerAllowsOptionalApiKey } from "@/shared/constants/providers"; | |
| import { HIDEABLE_SIDEBAR_ITEM_IDS } from "@/shared/constants/sidebarVisibility"; | |
| import { | |
| isForbiddenUpstreamHeaderName, | |
| isForbiddenCustomHeaderName, | |
| } from "@/shared/constants/upstreamHeaders"; | |
| import { MAX_TIMER_TIMEOUT_MS } from "@/shared/utils/runtimeTimeouts"; | |
| import { modelIdSchema, nonEmptyStringSchema } from "./misc.ts"; | |
| export const embeddingTokenArraySchema = z | |
| .array(z.number().int().min(0)) | |
| .min(1, "input token array must contain at least one item"); | |
| export const embeddingInputSchema = z.union([ | |
| nonEmptyStringSchema, | |
| z.array(nonEmptyStringSchema).min(1, "input must contain at least one item"), | |
| embeddingTokenArraySchema, | |
| z.array(embeddingTokenArraySchema).min(1, "input must contain at least one item"), | |
| ]); | |
| export const chatMessageSchema = z | |
| .object({ | |
| role: z.string().trim().min(1, "messages[].role is required"), | |
| content: z.union([nonEmptyStringSchema, z.array(z.unknown()).min(1), z.null()]).optional(), | |
| }) | |
| .catchall(z.unknown()); | |
| export const countTokensMessageSchema = z | |
| .object({ | |
| content: z.union([ | |
| nonEmptyStringSchema, | |
| z | |
| .array( | |
| z | |
| .object({ | |
| type: z.string().optional(), | |
| text: z.string().optional(), | |
| }) | |
| .catchall(z.unknown()) | |
| ) | |
| .min(1, "messages[].content must contain at least one item"), | |
| ]), | |
| }) | |
| .catchall(z.unknown()); | |
| export const v1EmbeddingsSchema = z | |
| .object({ | |
| model: modelIdSchema, | |
| input: embeddingInputSchema, | |
| dimensions: z.coerce.number().int().positive().optional(), | |
| encoding_format: z.enum(["float", "base64"]).optional(), | |
| }) | |
| .catchall(z.unknown()); | |
| export const v1ImageGenerationSchema = z | |
| .object({ | |
| model: modelIdSchema, | |
| prompt: nonEmptyStringSchema.optional(), | |
| }) | |
| .catchall(z.unknown()); | |
| export const v1AudioSpeechSchema = z | |
| .object({ | |
| model: modelIdSchema, | |
| input: nonEmptyStringSchema, | |
| }) | |
| .catchall(z.unknown()); | |
| export const v1ModerationSchema = z | |
| .object({ | |
| model: modelIdSchema.optional(), | |
| input: z.unknown().refine((value) => { | |
| if (value === undefined || value === null) return false; | |
| if (typeof value === "string") return value.trim().length > 0; | |
| if (Array.isArray(value)) return value.length > 0; | |
| return true; | |
| }, "Input is required"), | |
| }) | |
| .catchall(z.unknown()); | |
| export const v1RerankSchema = z | |
| .object({ | |
| model: modelIdSchema, | |
| query: nonEmptyStringSchema, | |
| documents: z.array(z.unknown()).min(1, "documents must contain at least one item"), | |
| }) | |
| .catchall(z.unknown()); | |
| export const providerChatCompletionSchema = z | |
| .object({ | |
| model: modelIdSchema, | |
| messages: z.array(chatMessageSchema).min(1).optional(), | |
| input: z.union([nonEmptyStringSchema, z.array(z.unknown()).min(1)]).optional(), | |
| prompt: nonEmptyStringSchema.optional(), | |
| }) | |
| .catchall(z.unknown()) | |
| .superRefine((value, ctx) => { | |
| if (value.messages === undefined && value.input === undefined && value.prompt === undefined) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "messages, input or prompt is required", | |
| path: [], | |
| }); | |
| } | |
| }); | |
| export const v1CountTokensSchema = z | |
| .object({ | |
| messages: z.array(countTokensMessageSchema).min(1, "messages must contain at least one item"), | |
| }) | |
| .catchall(z.unknown()); | |
| // ββ Search Schemas βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Unified search request/response schemas. Final contract β all fields optional | |
| // with defaults. New features add implementations, not new fields. | |
| // Multi-query deferred to POST /v1/search/batch (separate PRD). | |
| export const v1SearchSchema = z | |
| .object({ | |
| // Core | |
| query: z | |
| .string() | |
| .trim() | |
| .min(1, "Query is required") | |
| .max(500, "Query must be 500 characters or fewer"), | |
| provider: z | |
| .enum([ | |
| "serper-search", | |
| "brave-search", | |
| "perplexity-search", | |
| "exa-search", | |
| "tavily-search", | |
| "google-pse-search", | |
| "linkup-search", | |
| "ollama-search", | |
| "searchapi-search", | |
| "youcom-search", | |
| "searxng-search", | |
| "zai-search", | |
| "duckduckgo-free", | |
| ]) | |
| .optional(), | |
| max_results: z.coerce.number().int().min(1).max(100).default(5), | |
| search_type: z.enum(["web", "news"]).default("web"), | |
| offset: z.coerce.number().int().min(0).default(0), | |
| // Locale | |
| country: z.string().max(2).toUpperCase().optional(), | |
| language: z.string().min(2).max(5).optional(), | |
| time_range: z.enum(["any", "day", "week", "month", "year"]).optional(), | |
| // Content control | |
| content: z | |
| .object({ | |
| snippet: z.boolean().default(true), | |
| full_page: z.boolean().default(false), | |
| format: z.enum(["text", "markdown"]).default("text"), | |
| max_characters: z.coerce.number().int().min(100).max(100000).optional(), | |
| }) | |
| .optional(), | |
| // Filters | |
| filters: z | |
| .object({ | |
| include_domains: z.array(z.string().max(253)).max(20).optional(), | |
| exclude_domains: z.array(z.string().max(253)).max(20).optional(), | |
| safe_search: z.enum(["off", "moderate", "strict"]).optional(), | |
| }) | |
| .optional(), | |
| // Answer synthesis (Phase 2 β returns null until implemented) | |
| synthesis: z | |
| .object({ | |
| strategy: z.enum(["none", "auto", "provider", "internal"]).default("none"), | |
| model: z.string().optional(), | |
| max_tokens: z.coerce.number().int().min(1).max(4000).optional(), | |
| }) | |
| .optional(), | |
| // Provider-specific passthrough | |
| provider_options: z.record(z.string(), z.unknown()).optional(), | |
| // Strict mode β reject if provider doesn't support a requested filter | |
| strict_filters: z.boolean().default(false), | |
| }) | |
| .catchall(z.unknown()); | |
| export const searchResultSchema = z.object({ | |
| title: z.string(), | |
| url: z.string(), | |
| display_url: z.string().optional(), | |
| snippet: z.string(), | |
| position: z.number().int().positive(), | |
| score: z.number().min(0).max(1).nullable().optional(), | |
| published_at: z.string().nullable().optional(), | |
| favicon_url: z.string().nullable().optional(), | |
| content: z | |
| .object({ | |
| format: z.enum(["text", "markdown"]).optional(), | |
| text: z.string().optional(), | |
| length: z.number().int().optional(), | |
| }) | |
| .nullable() | |
| .optional(), | |
| metadata: z | |
| .object({ | |
| author: z.string().nullable().optional(), | |
| language: z.string().nullable().optional(), | |
| source_type: z | |
| .enum(["article", "blog", "forum", "video", "academic", "news", "other"]) | |
| .nullable() | |
| .optional(), | |
| image_url: z.string().nullable().optional(), | |
| }) | |
| .nullable() | |
| .optional(), | |
| citation: z.object({ | |
| provider: z.string(), | |
| retrieved_at: z.string(), | |
| rank: z.number().int().positive(), | |
| }), | |
| provider_raw: z.record(z.string(), z.unknown()).nullable().optional(), | |
| }); | |
| export const v1SearchResponseSchema = z.object({ | |
| id: z.string(), | |
| provider: z.string(), | |
| query: z.string(), | |
| results: z.array(searchResultSchema), | |
| cached: z.boolean(), | |
| answer: z | |
| .object({ | |
| source: z.enum(["none", "provider", "internal"]).optional(), | |
| text: z.string().nullable().optional(), | |
| model: z.string().nullable().optional(), | |
| }) | |
| .nullable() | |
| .optional(), | |
| usage: z.object({ | |
| queries_used: z.number().int().min(0), | |
| search_cost_usd: z.number().min(0), | |
| llm_tokens: z.number().int().min(0).optional(), | |
| }), | |
| metrics: z.object({ | |
| response_time_ms: z.number().int().min(0), | |
| upstream_latency_ms: z.number().int().min(0).optional(), | |
| gateway_latency_ms: z.number().int().min(0).optional(), | |
| total_results_available: z.number().int().nullable(), | |
| }), | |
| errors: z | |
| .array( | |
| z.object({ | |
| provider: z.string(), | |
| code: z.string(), | |
| message: z.string(), | |
| }) | |
| ) | |
| .optional(), | |
| }); | |
| export const v1BatchCreateSchema = z.object({ | |
| input_file_id: z.string().min(1), | |
| endpoint: z.enum(SUPPORTED_BATCH_ENDPOINTS), | |
| completion_window: z.enum(["24h"]), | |
| metadata: z | |
| .record(z.string().max(64), z.string().max(512)) | |
| .refine((m) => Object.keys(m).length <= 16, { message: "metadata may have at most 16 keys" }) | |
| .optional(), | |
| output_expires_after: z | |
| .object({ | |
| anchor: z.enum(["created_at"]), | |
| seconds: z.number().int().min(3600).max(2592000), | |
| }) | |
| .optional(), | |
| }); | |
| // ββ Web Fetch βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export const v1WebFetchSchema = z.object({ | |
| url: z.string().url("url must be a valid URL (http/https)"), | |
| provider: z.enum(["firecrawl", "jina-reader", "tavily-search"]).optional(), | |
| format: z.enum(["markdown", "html", "links", "screenshot"]).default("markdown"), | |
| depth: z.union([z.literal(0), z.literal(1), z.literal(2)]).default(0), | |
| wait_for_selector: z.string().max(256).optional(), | |
| include_metadata: z.boolean().default(false), | |
| }); |