From 8f3774420f7fb5996c9b97fa79132670fade9c83 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:13:25 +0000 Subject: [PATCH] feat(ai,coding-agent): add Requesty as a built-in provider Models are generated from https://router.requesty.ai/v1/models. anthropic/* models use the router's Anthropic Messages endpoint; all other models use OpenAI chat completions. Co-Authored-By: john --- packages/ai/README.md | 2 + packages/ai/scripts/generate-models.ts | 84 ++++++++++++++++++- packages/ai/src/env-api-keys.ts | 1 + packages/ai/src/models.generated.ts | 3 + packages/ai/src/providers/all.ts | 2 + packages/ai/src/providers/requesty.models.ts | 8 ++ packages/ai/src/providers/requesty.ts | 18 ++++ packages/ai/src/types.ts | 1 + packages/ai/test/providers.test.ts | 24 ++++++ packages/coding-agent/README.md | 1 + packages/coding-agent/docs/providers.md | 1 + packages/coding-agent/src/cli/args.ts | 1 + .../coding-agent/src/core/model-resolver.ts | 1 + 13 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 packages/ai/src/providers/requesty.models.ts create mode 100644 packages/ai/src/providers/requesty.ts diff --git a/packages/ai/README.md b/packages/ai/README.md index df43d305733..24789b82541 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -75,6 +75,7 @@ Unified LLM API with provider collections, automatic auth resolution, token and - **xAI** - **OpenRouter** - **Vercel AI Gateway** +- **Requesty** - **ZAI Coding Plan (Global)** (with separate China provider) - **MiniMax** (with separate China provider) - **Together AI** @@ -433,6 +434,7 @@ Built-in providers resolve these env vars (Node.js; in browsers pass `apiKey` ex | Baseten | `BASETEN_API_KEY` | | OpenRouter | `OPENROUTER_API_KEY` | | Vercel AI Gateway | `AI_GATEWAY_API_KEY` | +| Requesty | `REQUESTY_API_KEY` | | ZAI Coding Plan (Global) | `ZAI_API_KEY` | | ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | | MiniMax (Global) | `MINIMAX_API_KEY` | diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index b1dfcf10fb1..a0721a8610b 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -164,6 +164,23 @@ interface AiGatewayModel { }; } +interface RequestyModelListItem { + id: string; + api?: string; + input_price?: number; + output_price?: number; + caching_price?: number; + cached_price?: number; + context_window?: number; + max_output_tokens?: number; + supports_tool_calling?: boolean; + supports_vision?: boolean; + supports_reasoning?: boolean; + supports_role_developer?: boolean; +} + +const REQUESTY_ROUTER_URL = "https://router.requesty.ai"; + const COPILOT_STATIC_HEADERS = { "User-Agent": "GitHubCopilotChat/0.35.0", "Editor-Version": "vscode/1.107.0", @@ -1327,6 +1344,70 @@ async function fetchAiGatewayModels(): Promise[]> { } } +async function fetchRequestyModels(): Promise[]> { + try { + console.log("Fetching models from Requesty API..."); + const response = await fetch(`${REQUESTY_ROUTER_URL}/v1/models`); + if (!response.ok) throw new Error(`Requesty API returned ${response.status}`); + const data = (await response.json()) as { data?: RequestyModelListItem[] }; + const models: Model[] = []; + + for (const model of data.data ?? []) { + if (model.api !== "chat" || !model.supports_tool_calling) continue; + + const input: ("text" | "image")[] = model.supports_vision ? ["text", "image"] : ["text"]; + // Requesty prices are $/token. + const cost = { + input: roundCost((model.input_price ?? 0) * 1_000_000), + output: roundCost((model.output_price ?? 0) * 1_000_000), + cacheRead: roundCost((model.cached_price ?? 0) * 1_000_000), + cacheWrite: roundCost((model.caching_price ?? 0) * 1_000_000), + }; + const contextWindow = model.context_window || 4096; + const maxTokens = model.max_output_tokens || 4096; + + // Anthropic models go through Requesty's native /v1/messages endpoint so thinking blocks, + // cache_control, and signatures pass through unchanged. Everything else uses chat completions. + if (model.id.startsWith("anthropic/")) { + models.push({ + id: model.id, + name: model.id, + api: "anthropic-messages", + baseUrl: REQUESTY_ROUTER_URL, + provider: "requesty", + reasoning: model.supports_reasoning === true, + input, + cost, + contextWindow, + maxTokens, + }); + continue; + } + + models.push({ + id: model.id, + name: model.id, + api: "openai-completions", + baseUrl: `${REQUESTY_ROUTER_URL}/v1`, + provider: "requesty", + reasoning: model.supports_reasoning === true, + input, + cost, + contextWindow, + maxTokens, + ...(model.supports_role_developer ? {} : { compat: { supportsDeveloperRole: false } }), + }); + } + + console.log(`Fetched ${models.length} tool-capable models from Requesty`); + return models; + } catch (error) { + console.error("Failed to fetch Requesty models:", error); + if (generatorOptions.strict) throw error; + return []; + } +} + function processZaiModels(data: ModelsDevCatalog): Model[] { const variants = [ { @@ -2549,9 +2630,10 @@ async function generateModels() { const modelsDevModels = await loadModelsDevData(); const openRouterModels = await fetchOpenRouterModels(); const aiGatewayModels = await fetchAiGatewayModels(); + const requestyModels = await fetchRequestyModels(); // Combine models (models.dev has priority) - const allModels = [...modelsDevModels, ...openRouterModels, ...aiGatewayModels].filter( + const allModels = [...modelsDevModels, ...openRouterModels, ...aiGatewayModels, ...requestyModels].filter( (model) => !(model.provider === "xai" && XAI_BUILTIN_EXCLUDED_MODEL_IDS.has(model.id)) && !((model.provider === "opencode" || model.provider === "opencode-go") && model.id === "gpt-5.3-codex-spark"), diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts index 5b952b7b510..00f5afaaca6 100644 --- a/packages/ai/src/env-api-keys.ts +++ b/packages/ai/src/env-api-keys.ts @@ -93,6 +93,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined { radius: "RADIUS_API_KEY", openrouter: "OPENROUTER_API_KEY", "vercel-ai-gateway": "AI_GATEWAY_API_KEY", + requesty: "REQUESTY_API_KEY", zai: "ZAI_API_KEY", "zai-coding-cn": "ZAI_CODING_CN_API_KEY", mistral: "MISTRAL_API_KEY", diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 12952045922..076312ebc52 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -31,6 +31,7 @@ import { OPENROUTER_MODELS } from "./providers/openrouter.models.ts"; import { QWEN_TOKEN_PLAN_MODELS } from "./providers/qwen-token-plan.models.ts"; import { QWEN_TOKEN_PLAN_CN_MODELS } from "./providers/qwen-token-plan-cn.models.ts"; import { QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS } from "./providers/qwen-token-plan-individual.models.ts"; +import { REQUESTY_MODELS } from "./providers/requesty.models.ts"; import { TOGETHER_MODELS } from "./providers/together.models.ts"; import { VERCEL_AI_GATEWAY_MODELS } from "./providers/vercel-ai-gateway.models.ts"; import { XAI_MODELS } from "./providers/xai.models.ts"; @@ -72,6 +73,7 @@ export const MODELS: { readonly "qwen-token-plan": typeof QWEN_TOKEN_PLAN_MODELS; readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_MODELS; readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS; + readonly "requesty": typeof REQUESTY_MODELS; readonly "together": typeof TOGETHER_MODELS; readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_MODELS; readonly "xai": typeof XAI_MODELS; @@ -112,6 +114,7 @@ export const MODELS: { "qwen-token-plan": QWEN_TOKEN_PLAN_MODELS, "qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_MODELS, "qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS, + "requesty": REQUESTY_MODELS, "together": TOGETHER_MODELS, "vercel-ai-gateway": VERCEL_AI_GATEWAY_MODELS, "xai": XAI_MODELS, diff --git a/packages/ai/src/providers/all.ts b/packages/ai/src/providers/all.ts index 8785e94aed6..66a43c0f2eb 100644 --- a/packages/ai/src/providers/all.ts +++ b/packages/ai/src/providers/all.ts @@ -35,6 +35,7 @@ import { qwenTokenPlanProvider } from "./qwen-token-plan.ts"; import { qwenTokenPlanCnProvider } from "./qwen-token-plan-cn.ts"; import { qwenTokenPlanIndividualProvider } from "./qwen-token-plan-individual.ts"; import { radiusProvider } from "./radius.ts"; +import { requestyProvider } from "./requesty.ts"; import { togetherProvider } from "./together.ts"; import { vercelAIGatewayProvider } from "./vercel-ai-gateway.ts"; import { xaiProvider } from "./xai.ts"; @@ -119,6 +120,7 @@ export function builtinProviders(): Provider[] { qwenTokenPlanCnProvider(), qwenTokenPlanIndividualProvider(), radiusProvider(), + requestyProvider(), togetherProvider(), vercelAIGatewayProvider(), xaiProvider(), diff --git a/packages/ai/src/providers/requesty.models.ts b/packages/ai/src/providers/requesty.models.ts new file mode 100644 index 00000000000..1b3749096bd --- /dev/null +++ b/packages/ai/src/providers/requesty.models.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by scripts/generate-models.ts +// Do not edit manually - run 'npm run generate-models' to update + +import values from "./data/requesty.json" with { type: "json" }; +import { flattenModelCatalog, type ModelCatalog } from "../model-catalog.ts"; + +export const REQUESTY_MODELS: ModelCatalog = + flattenModelCatalog("requesty", values); diff --git a/packages/ai/src/providers/requesty.ts b/packages/ai/src/providers/requesty.ts new file mode 100644 index 00000000000..95e36886e48 --- /dev/null +++ b/packages/ai/src/providers/requesty.ts @@ -0,0 +1,18 @@ +import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.ts"; +import { openAICompletionsApi } from "../api/openai-completions.lazy.ts"; +import { envApiKeyAuth } from "../auth/helpers.ts"; +import { createProvider, type Provider } from "../models.ts"; +import { REQUESTY_MODELS } from "./requesty.models.ts"; + +export function requestyProvider(): Provider<"anthropic-messages" | "openai-completions"> { + return createProvider({ + id: "requesty", + name: "Requesty", + auth: { apiKey: envApiKeyAuth("Requesty API key", ["REQUESTY_API_KEY"]) }, + models: Object.values(REQUESTY_MODELS), + api: { + "anthropic-messages": anthropicMessagesApi(), + "openai-completions": openAICompletionsApi(), + }, + }); +} diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 4dcfd08d5da..69e15b65a54 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -50,6 +50,7 @@ export type KnownProvider = | "cerebras" | "openrouter" | "vercel-ai-gateway" + | "requesty" | "zai" | "zai-coding-cn" | "mistral" diff --git a/packages/ai/test/providers.test.ts b/packages/ai/test/providers.test.ts index e6fa4c4fc33..db2ea4fbca3 100644 --- a/packages/ai/test/providers.test.ts +++ b/packages/ai/test/providers.test.ts @@ -11,6 +11,7 @@ import { cloudflareAIGatewayProvider } from "../src/providers/cloudflare-ai-gate import { cloudflareWorkersAIProvider } from "../src/providers/cloudflare-workers-ai.ts"; import { fauxAssistantMessage, fauxProvider } from "../src/providers/faux.ts"; import { googleVertexProvider } from "../src/providers/google-vertex.ts"; +import { requestyProvider } from "../src/providers/requesty.ts"; import type { Api, DeferredCancelOptions, @@ -276,6 +277,29 @@ describe("builtin providers", () => { ).toMatchObject({ auth: {}, env: { AWS_PROFILE: "work" } }); }); + it("routes Requesty models through the router's Anthropic and OpenAI-compatible endpoints", async () => { + const models = createModels({ authContext: fakeAuthContext({ REQUESTY_API_KEY: "rq-key" }) }); + models.setProvider(requestyProvider()); + + expect(await models.getAuth("requesty")).toEqual({ + auth: { apiKey: "rq-key" }, + source: "REQUESTY_API_KEY", + }); + + const requestyModels = models.getModels("requesty"); + expect(requestyModels.length).toBeGreaterThan(0); + for (const model of requestyModels) { + if (model.id.startsWith("anthropic/")) { + expect(model.api, model.id).toBe("anthropic-messages"); + expect(model.baseUrl, model.id).toBe("https://router.requesty.ai"); + } else { + expect(model.api, model.id).toBe("openai-completions"); + expect(model.baseUrl, model.id).toBe("https://router.requesty.ai/v1"); + } + } + expect(models.getModel("requesty", "anthropic/claude-sonnet-4-6")?.api).toBe("anthropic-messages"); + }); + it("reports bedrock as configured from ambient AWS credentials without an api key", async () => { const models = createModels({ authContext: fakeAuthContext({ AWS_PROFILE: "dev" }) }); models.setProvider(amazonBedrockProvider()); diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 6445443f905..b1544a92df9 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -121,6 +121,7 @@ For each built-in provider, pi maintains a list of tool-capable models. Configur - xAI - OpenRouter - Vercel AI Gateway +- Requesty - ZAI Coding Plan (Global) - ZAI Coding Plan (China) - OpenCode Zen diff --git a/packages/coding-agent/docs/providers.md b/packages/coding-agent/docs/providers.md index 4fea738fadb..79f6b570062 100644 --- a/packages/coding-agent/docs/providers.md +++ b/packages/coding-agent/docs/providers.md @@ -84,6 +84,7 @@ pi | xAI | `XAI_API_KEY` | `xai` | | OpenRouter | `OPENROUTER_API_KEY` | `openrouter` | | Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` | +| Requesty | `REQUESTY_API_KEY` | `requesty` | | ZAI Coding Plan (Global) | `ZAI_API_KEY` | `zai` | | ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` | | OpenCode Zen | `OPENCODE_API_KEY` | `opencode` | diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 8ad5da63e5c..7474fe286f1 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -406,6 +406,7 @@ ${chalk.bold("Environment Variables:")} BASETEN_API_KEY - Baseten API key OPENROUTER_API_KEY - OpenRouter API key AI_GATEWAY_API_KEY - Vercel AI Gateway API key + REQUESTY_API_KEY - Requesty API key ZAI_API_KEY - ZAI Coding Plan API key (Global) ZAI_CODING_CN_API_KEY - ZAI Coding Plan API key (China) MISTRAL_API_KEY - Mistral API key diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 156476c05ff..a96e51dbd51 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -32,6 +32,7 @@ export const defaultModelPerProvider: Record = { "github-copilot": "gpt-5.4", openrouter: "moonshotai/kimi-k2.6", "vercel-ai-gateway": "zai/glm-5.1", + requesty: "anthropic/claude-sonnet-4-6", xai: "grok-4.6", groq: "openai/gpt-oss-120b", cerebras: "gpt-oss-120b",