From 173b96b887c81eb5460eed23c42ec7b2f7d724ca Mon Sep 17 00:00:00 2001 From: es697 Date: Mon, 13 Jul 2026 17:06:03 +0400 Subject: [PATCH] add AnyAPI thin-endpoint sync provider - add conservative AnyAPI sync provider using /v1/models - require ANYAPI_API_KEY for sync runs and wire workflow env - register provider in aggregators - preserve existing authored AnyAPI TOMLs byte-for-byte - skip creating new models from the thin ID-only endpoint - never delete hand-authored models missing from API response - align existing AnyAPI file paths with current API ids where needed - move rationale comments into leading blocks so sync preserves them --- .github/workflows/sync-models.yml | 1 + packages/core/src/sync/index.ts | 5 +- packages/core/src/sync/providers/anyapi.ts | 67 +++++++++++++++++++ ...e-haiku-4-5.toml => claude-haiku-4.5.toml} | 2 +- ...ude-opus-4-6.toml => claude-opus-4.6.toml} | 2 +- ...ude-opus-4-7.toml => claude-opus-4.7.toml} | 2 +- ...sonnet-4-5.toml => claude-sonnet-4.5.toml} | 2 +- ...sonnet-4-6.toml => claude-sonnet-4.6.toml} | 2 +- .../models/google/gemini-3-pro-image.toml | 2 + .../models/google/gemini-3-pro-preview.toml | 2 - .../anyapi/models/openai/gpt-5-mini.toml | 2 +- providers/anyapi/models/openai/gpt-5.1.toml | 2 +- providers/anyapi/models/openai/gpt-5.2.toml | 2 +- providers/anyapi/models/openai/gpt-5.4.toml | 2 +- providers/anyapi/models/openai/gpt-5.toml | 2 +- providers/anyapi/models/openai/o3-mini.toml | 2 +- providers/anyapi/models/openai/o3.toml | 2 +- providers/anyapi/models/openai/o4-mini.toml | 2 +- .../anyapi/models/{xai => x-ai}/grok-4.3.toml | 0 19 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 packages/core/src/sync/providers/anyapi.ts rename providers/anyapi/models/anthropic/{claude-haiku-4-5.toml => claude-haiku-4.5.toml} (100%) rename providers/anyapi/models/anthropic/{claude-opus-4-6.toml => claude-opus-4.6.toml} (100%) rename providers/anyapi/models/anthropic/{claude-opus-4-7.toml => claude-opus-4.7.toml} (100%) rename providers/anyapi/models/anthropic/{claude-sonnet-4-5.toml => claude-sonnet-4.5.toml} (100%) rename providers/anyapi/models/anthropic/{claude-sonnet-4-6.toml => claude-sonnet-4.6.toml} (100%) create mode 100644 providers/anyapi/models/google/gemini-3-pro-image.toml delete mode 100644 providers/anyapi/models/google/gemini-3-pro-preview.toml rename providers/anyapi/models/{xai => x-ai}/grok-4.3.toml (100%) diff --git a/.github/workflows/sync-models.yml b/.github/workflows/sync-models.yml index 5edb83859a..594dc74790 100644 --- a/.github/workflows/sync-models.yml +++ b/.github/workflows/sync-models.yml @@ -64,6 +64,7 @@ jobs: run: bun models:sync ${{ matrix.provider }} env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + ANYAPI_API_KEY: ${{ secrets.ANYAPI_API_KEY }} BASETEN_API_KEY: ${{ secrets.BASETEN_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DIGITALOCEAN_API_TOKEN: ${{ secrets.DIGITALOCEAN_API_TOKEN }} diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 3b8c8fce97..4ff6430a07 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { AuthoredModel, AuthoredModelShape, ModelMetadata } from "../schema.js"; import { ambient } from "./providers/ambient.js"; import { anthropic } from "./providers/anthropic.js"; +import { anyapi } from "./providers/anyapi.js"; import { baseten } from "./providers/baseten.js"; import { chutes } from "./providers/chutes.js"; import { cloudflareWorkersAi } from "./providers/cloudflare-workers-ai.js"; @@ -94,6 +95,7 @@ export interface SyncResult { export const providers: { ambient: SyncProvider; + anyapi: SyncProvider; anthropic: SyncProvider; baseten: SyncProvider; chutes: SyncProvider; @@ -116,6 +118,7 @@ export const providers: { xai: SyncProvider; } = { ambient, + anyapi, anthropic, baseten, chutes, @@ -139,7 +142,7 @@ export const providers: { }; export const groups = { - aggregators: ["crossmodel", "empiriolabs", "huggingface", "kilo", "llmgateway", "openrouter", "vercel"], + aggregators: ["anyapi", "crossmodel", "empiriolabs", "huggingface", "kilo", "llmgateway", "openrouter", "vercel"], cloudflare: ["cloudflare-workers-ai"], direct: ["ambient", "anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "openai", "ovhcloud", "pioneer", "venice", "wandb", "xai"], } as const; diff --git a/packages/core/src/sync/providers/anyapi.ts b/packages/core/src/sync/providers/anyapi.ts new file mode 100644 index 0000000000..449d9abbf6 --- /dev/null +++ b/packages/core/src/sync/providers/anyapi.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; + +import { AuthoredModel } from "../../schema.js"; +import type { ExistingModel, SyncProvider, SyncedModel } from "../index.js"; + +const API_ENDPOINT = "https://api.anyapi.ai/v1/models"; + +const AnyAPIModel = z.object({ + id: z.string(), +}); + +const AnyAPIResponse = z.object({ + data: z.array(AnyAPIModel.passthrough()), +}).passthrough(); + +export type AnyAPIModel = z.infer; + +function preserveAuthoredModel(id: string, authored: ExistingModel): SyncedModel { + if (authored.base_model !== undefined) return authored as SyncedModel; + + const parsed = AuthoredModel.safeParse({ id, ...authored }); + if (!parsed.success) { + parsed.error.cause = { provider: "anyapi", model: id }; + throw parsed.error; + } + const { id: _id, ...model } = parsed.data; + return model; +} + +export const anyapi = { + id: "anyapi", + name: "AnyAPI", + modelsDir: "providers/anyapi/models", + skipCreates: true, + deleteMissing: false, + sourceID(model) { + return model.id; + }, + skippedNotice(ids) { + if (ids.length === 0) return []; + return [ + `${ids.length} AnyAPI models returned by the API were not created because the /v1/models endpoint is not authoritative beyond model IDs. Existing models are still updated while new models require hand-authored metadata.`, + `Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`, + ]; + }, + async fetchModels() { + const key = process.env.ANYAPI_API_KEY; + if (!key) { + throw new Error("ANYAPI_API_KEY environment variable is required"); + } + const response = await fetch(API_ENDPOINT, { + headers: { Authorization: "Bearer " + key }, + }); + if (!response.ok) { + throw new Error("AnyAPI request failed: " + response.status + " " + response.statusText); + } + return response.json(); + }, + parseModels(raw) { + return AnyAPIResponse.parse(raw).data; + }, + translateModel(model, context) { + const authored = context.authored(model.id); + if (authored === undefined) return undefined; + return { id: model.id, model: preserveAuthoredModel(model.id, authored) }; + }, +} satisfies SyncProvider; diff --git a/providers/anyapi/models/anthropic/claude-haiku-4-5.toml b/providers/anyapi/models/anthropic/claude-haiku-4.5.toml similarity index 100% rename from providers/anyapi/models/anthropic/claude-haiku-4-5.toml rename to providers/anyapi/models/anthropic/claude-haiku-4.5.toml index 5a88477f60..596248114d 100644 --- a/providers/anyapi/models/anthropic/claude-haiku-4-5.toml +++ b/providers/anyapi/models/anthropic/claude-haiku-4.5.toml @@ -1,6 +1,6 @@ -base_model = "anthropic/claude-haiku-4-5" # AnyAPI accepts thinking.type="enabled" with budget_tokens, but documents no # model-specific 1,024..63,999 bounds or off mapping; these remain upstream facts. # https://docs.anyapi.ai/guides/parameters # https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking +base_model = "anthropic/claude-haiku-4-5" reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["low", "medium", "high"] }, { type = "budget_tokens", min = 1_024, max = 63_999 }] diff --git a/providers/anyapi/models/anthropic/claude-opus-4-6.toml b/providers/anyapi/models/anthropic/claude-opus-4.6.toml similarity index 100% rename from providers/anyapi/models/anthropic/claude-opus-4-6.toml rename to providers/anyapi/models/anthropic/claude-opus-4.6.toml index 277f164315..69e2f2b444 100644 --- a/providers/anyapi/models/anthropic/claude-opus-4-6.toml +++ b/providers/anyapi/models/anthropic/claude-opus-4.6.toml @@ -1,8 +1,8 @@ -base_model = "anthropic/claude-opus-4-6" # AnyAPI accepts thinking.type="enabled" with budget_tokens, but documents no # model-specific 1,024..127,999 bounds or off mapping; these remain upstream facts. # https://docs.anyapi.ai/guides/parameters # https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking +base_model = "anthropic/claude-opus-4-6" reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["low", "medium", "high"] }, { type = "budget_tokens", min = 1_024, max = 127_999 }] [experimental.modes.fast] diff --git a/providers/anyapi/models/anthropic/claude-opus-4-7.toml b/providers/anyapi/models/anthropic/claude-opus-4.7.toml similarity index 100% rename from providers/anyapi/models/anthropic/claude-opus-4-7.toml rename to providers/anyapi/models/anthropic/claude-opus-4.7.toml index 3b19885ae9..40d665d645 100644 --- a/providers/anyapi/models/anthropic/claude-opus-4-7.toml +++ b/providers/anyapi/models/anthropic/claude-opus-4.7.toml @@ -1,7 +1,7 @@ -base_model = "anthropic/claude-opus-4-7" # AnyAPI documents generic effort and thinking fields, but no Opus 4.7-specific # mapping or budget bounds; accepted schema does not prove effective control. # https://docs.anyapi.ai/guides/parameters +base_model = "anthropic/claude-opus-4-7" reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["low", "medium", "high"] }] [experimental.modes.fast] diff --git a/providers/anyapi/models/anthropic/claude-sonnet-4-5.toml b/providers/anyapi/models/anthropic/claude-sonnet-4.5.toml similarity index 100% rename from providers/anyapi/models/anthropic/claude-sonnet-4-5.toml rename to providers/anyapi/models/anthropic/claude-sonnet-4.5.toml index a43b659197..45458f1d98 100644 --- a/providers/anyapi/models/anthropic/claude-sonnet-4-5.toml +++ b/providers/anyapi/models/anthropic/claude-sonnet-4.5.toml @@ -1,6 +1,6 @@ -base_model = "anthropic/claude-sonnet-4-5" # AnyAPI accepts thinking.type="enabled" with budget_tokens, but documents no # model-specific 1,024..63,999 bounds or off mapping; these remain upstream facts. # https://docs.anyapi.ai/guides/parameters # https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking +base_model = "anthropic/claude-sonnet-4-5" reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["low", "medium", "high"] }, { type = "budget_tokens", min = 1_024, max = 63_999 }] diff --git a/providers/anyapi/models/anthropic/claude-sonnet-4-6.toml b/providers/anyapi/models/anthropic/claude-sonnet-4.6.toml similarity index 100% rename from providers/anyapi/models/anthropic/claude-sonnet-4-6.toml rename to providers/anyapi/models/anthropic/claude-sonnet-4.6.toml index 69d77c806c..25199187c2 100644 --- a/providers/anyapi/models/anthropic/claude-sonnet-4-6.toml +++ b/providers/anyapi/models/anthropic/claude-sonnet-4.6.toml @@ -1,6 +1,6 @@ -base_model = "anthropic/claude-sonnet-4-6" # AnyAPI accepts thinking.type="enabled" with budget_tokens, but documents no # model-specific 1,024..63,999 bounds or off mapping; these remain upstream facts. # https://docs.anyapi.ai/guides/parameters # https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking +base_model = "anthropic/claude-sonnet-4-6" reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["low", "medium", "high"] }, { type = "budget_tokens", min = 1_024, max = 63_999 }] diff --git a/providers/anyapi/models/google/gemini-3-pro-image.toml b/providers/anyapi/models/google/gemini-3-pro-image.toml new file mode 100644 index 0000000000..307995b245 --- /dev/null +++ b/providers/anyapi/models/google/gemini-3-pro-image.toml @@ -0,0 +1,2 @@ +base_model = "google/gemini-3-pro-image" +reasoning_options = [] diff --git a/providers/anyapi/models/google/gemini-3-pro-preview.toml b/providers/anyapi/models/google/gemini-3-pro-preview.toml deleted file mode 100644 index 40275fd404..0000000000 --- a/providers/anyapi/models/google/gemini-3-pro-preview.toml +++ /dev/null @@ -1,2 +0,0 @@ -base_model = "google/gemini-3-pro-preview" -reasoning_options = [] diff --git a/providers/anyapi/models/openai/gpt-5-mini.toml b/providers/anyapi/models/openai/gpt-5-mini.toml index bb74733f76..8411c9e510 100644 --- a/providers/anyapi/models/openai/gpt-5-mini.toml +++ b/providers/anyapi/models/openai/gpt-5-mini.toml @@ -1,5 +1,5 @@ -base_model = "openai/gpt-5-mini" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/gpt-5-mini" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/openai/gpt-5.1.toml b/providers/anyapi/models/openai/gpt-5.1.toml index 03df71fe4a..e8ecc0e766 100644 --- a/providers/anyapi/models/openai/gpt-5.1.toml +++ b/providers/anyapi/models/openai/gpt-5.1.toml @@ -1,5 +1,5 @@ -base_model = "openai/gpt-5.1" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/gpt-5.1" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/openai/gpt-5.2.toml b/providers/anyapi/models/openai/gpt-5.2.toml index e64028af6d..1958ddd79e 100644 --- a/providers/anyapi/models/openai/gpt-5.2.toml +++ b/providers/anyapi/models/openai/gpt-5.2.toml @@ -1,5 +1,5 @@ -base_model = "openai/gpt-5.2" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/gpt-5.2" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/openai/gpt-5.4.toml b/providers/anyapi/models/openai/gpt-5.4.toml index b5c9108b19..6643beb169 100644 --- a/providers/anyapi/models/openai/gpt-5.4.toml +++ b/providers/anyapi/models/openai/gpt-5.4.toml @@ -1,7 +1,7 @@ -base_model = "openai/gpt-5.4" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/gpt-5.4" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] [experimental.modes.fast] diff --git a/providers/anyapi/models/openai/gpt-5.toml b/providers/anyapi/models/openai/gpt-5.toml index 8e862c0d44..4046a1f8d0 100644 --- a/providers/anyapi/models/openai/gpt-5.toml +++ b/providers/anyapi/models/openai/gpt-5.toml @@ -1,5 +1,5 @@ -base_model = "openai/gpt-5" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/gpt-5" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/openai/o3-mini.toml b/providers/anyapi/models/openai/o3-mini.toml index 9da963506c..26514036ad 100644 --- a/providers/anyapi/models/openai/o3-mini.toml +++ b/providers/anyapi/models/openai/o3-mini.toml @@ -1,5 +1,5 @@ -base_model = "openai/o3-mini" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/o3-mini" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/openai/o3.toml b/providers/anyapi/models/openai/o3.toml index 294b5e1f75..9af0e8d365 100644 --- a/providers/anyapi/models/openai/o3.toml +++ b/providers/anyapi/models/openai/o3.toml @@ -1,5 +1,5 @@ -base_model = "openai/o3" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/o3" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/openai/o4-mini.toml b/providers/anyapi/models/openai/o4-mini.toml index 27cfe456d9..88979464f2 100644 --- a/providers/anyapi/models/openai/o4-mini.toml +++ b/providers/anyapi/models/openai/o4-mini.toml @@ -1,5 +1,5 @@ -base_model = "openai/o4-mini" # AnyAPI accepts low/medium/high generically but documents no model-specific # effectiveness; unsupported parameters may be silently ignored. # https://docs.anyapi.ai/guides/parameters +base_model = "openai/o4-mini" reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] diff --git a/providers/anyapi/models/xai/grok-4.3.toml b/providers/anyapi/models/x-ai/grok-4.3.toml similarity index 100% rename from providers/anyapi/models/xai/grok-4.3.toml rename to providers/anyapi/models/x-ai/grok-4.3.toml