Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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` |
Expand Down
84 changes: 83 additions & 1 deletion packages/ai/scripts/generate-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -1327,6 +1344,70 @@ async function fetchAiGatewayModels(): Promise<Model<any>[]> {
}
}

async function fetchRequestyModels(): Promise<Model<any>[]> {
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<any>[] = [];

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<Api>[] {
const variants = [
{
Expand Down Expand Up @@ -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"),
Expand Down
1 change: 1 addition & 0 deletions packages/ai/src/env-api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions packages/ai/src/models.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/ai/src/providers/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -119,6 +120,7 @@ export function builtinProviders(): Provider[] {
qwenTokenPlanCnProvider(),
qwenTokenPlanIndividualProvider(),
radiusProvider(),
requestyProvider(),
togetherProvider(),
vercelAIGatewayProvider(),
xaiProvider(),
Expand Down
8 changes: 8 additions & 0 deletions packages/ai/src/providers/requesty.models.ts
Original file line number Diff line number Diff line change
@@ -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<typeof values, "requesty"> =
flattenModelCatalog("requesty", values);
18 changes: 18 additions & 0 deletions packages/ai/src/providers/requesty.ts
Original file line number Diff line number Diff line change
@@ -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(),
},
});
}
1 change: 1 addition & 0 deletions packages/ai/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type KnownProvider =
| "cerebras"
| "openrouter"
| "vercel-ai-gateway"
| "requesty"
| "zai"
| "zai-coding-cn"
| "mistral"
Expand Down
24 changes: 24 additions & 0 deletions packages/ai/test/providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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());
Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/src/core/model-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
"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",
Expand Down
Loading