From 236afb365f183f7cb08f318a73c4af2aa6a32489 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 14 Aug 2026 13:13:43 -0300 Subject: [PATCH] feat(providers): declare imageToText serviceKind on major vision providers (#10275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(providers): declare imageToText serviceKind on major vision providers The /dashboard/media-providers/imageToText category was empty by design: imageToText has no backing registry and no catalog entry declared it. Declare serviceKinds: ["llm", "imageToText"] on the 7 major vision-capable providers (openai, anthropic, gemini, openrouter, mistral, xai, groq) so the category lists them and the Modality Bridge ?tab=vision shortcut becomes reachable from their provider detail pages. "llm" is declared alongside because ProviderCard treats an EMPTY serviceKinds as "regular LLM provider" — declaring only imageToText would silently hide the inline Test button and the playground default (guarded by the new test). Refs #9760 * chore(quality): rebaseline gateways.ts file-size for imageToText serviceKinds The two serviceKinds declarations (openrouter here, chutes in #10291) add exactly two data lines to the provider catalog. Frozen 1250 -> 1252 with the justification recorded in the baseline key. * chore(quality): rebaseline deadExports for the OCR/image-to-text series --------- Co-authored-by: Xiangzhe --- config/quality/file-size-baseline.json | 5 ++- .../providers/apikey/frontier-labs.ts | 6 +++ .../constants/providers/apikey/gateways.ts | 1 + tests/unit/imagetotext-service-kinds.test.ts | 42 +++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 tests/unit/imagetotext-service-kinds.test.ts diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index bbdc9d16f0..b8cbd65265 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -442,7 +442,7 @@ "src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1062, "src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051, "src/shared/components/ModelSelectModal.tsx": 1138, - "src/shared/constants/providers/apikey/gateways.ts": 1250, + "src/shared/constants/providers/apikey/gateways.ts": 1255, "open-sse/vendor/codex-chatgpt-web/bridge.ts": 1387, "_rebaseline_2026_08_11_v3850_merge_storm_provider_registry": "DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legitima acima do cap; gateways.ts = god-file de catalogo de providers que cresceu com PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o proprio PR #9421 quebrou o arquivo); bridge.ts (PR #8949) = ponte Chromium vendored; proxyFetch.ts 1207->1220 = drift herdado de merges. Owner autorizou rebaseline com anotacao (2026-08-11).", "src/lib/modelCapabilities.ts": 1006, @@ -609,5 +609,6 @@ "_rebaseline_2026_08_06_9006_reconcile_onto_tip_2": "PR #9006 (fix/vertex-claude-catalog-dispatch) second rebase-onto-tip reconciliation. Same two files as _rebaseline_2026_08_04_9006_reconcile_onto_tip below, further inherited drift, still not this PR's own growth (verified via git diff against the fresh upstream/release/v3.8.50 merge-base — this PR's own commits still touch neither file): open-sse/executors/base.ts 1623->1640 (+17) and src/sse/handlers/chat.ts 1847->1881 (+34), both measured post-merge via split(\"\\n\").length. More already-merged release/v3.8.50 PRs grew these files without updating their baseline entries (same root cause as every other entry in this chain: fast-gates PR->release does not run check:file-size). No offending branch left to fix.", "_rebaseline_2026_08_12_v3850_basereds_round3": "Base-reds round 3 (#9985, 2026-08-12): ModelSelectModal.tsx 1135->1138 = base drift from the #10198 SWR/build repair (flagged as non-blocking drift by Release-Green run 31634993212, rebaselined here so the PR queue's Fast Quality Gates stop failing on inherited drift); gateways.ts 1215->1250 = base drift from the 08-12 merges (#10131 regolo/naga-ac repair, #9210 void-ai+helixmind) plus this PR restoring the chatanywhere metadata entry that round 2 dropped along with its duplicate (wave3 audited entry, +16 lines; same god-file no-split rationale as the 2026-08-11 annotation). Owner-authorized sweep (/sweep-reds).", "_rebaseline_2026_08_12_proxyfetch_redaction": "Base-reds round 3 (#9985): proxyFetch.ts 1220->1239 (+19) = redactProxyDetailsInMessage() helper closing the credential leak #10032 reintroduced (raw proxy URL with user:password appended to the propagated error, Hard Rule #12); irreducible security fix at the existing error-surface chokepoint. Covered by tests/unit/tls-proxy-context.test.ts (strengthened leak guards).", - "_rebaseline_2026_08_12_modelcapabilities_snapshot_routing": "Base-reds round 3 (#9985): modelCapabilities.ts crossed the new-file cap at 1006 (+~10) when the context/max-input-token override lookups were routed through the #9199 bulk snapshot (fixing 323 per-model SQLite reads per catalog prepare — auto-combo-context-advertising guard); cohesive change at the existing resolution chokepoints, not extractable. Covered by tests/unit/auto-combo-context-advertising.test.ts + model-capability-resolution-snapshot-9199.test.ts." + "_rebaseline_2026_08_12_modelcapabilities_snapshot_routing": "Base-reds round 3 (#9985): modelCapabilities.ts crossed the new-file cap at 1006 (+~10) when the context/max-input-token override lookups were routed through the #9199 bulk snapshot (fixing 323 per-model SQLite reads per catalog prepare — auto-combo-context-advertising guard); cohesive change at the existing resolution chokepoints, not extractable. Covered by tests/unit/auto-combo-context-advertising.test.ts + model-capability-resolution-snapshot-9199.test.ts.", + "_rebaseline_2026_08_14_imagetotext_servicekinds": "Image-to-Text category (#10275/#10291): gateways.ts grew 1250→1255 by data lines only — the serviceKinds: [\"llm\", \"imageToText\"] declarations on the openrouter and chutes catalog entries, plus the 3-line comment recording why chutes needs no static dots.ocr entry (passthroughModels discovery). No new logic or branching; the file is a provider catalog of declarative metadata. Splitting a catalog for five lines would be worse than the growth (semantic-families rule)." } diff --git a/src/shared/constants/providers/apikey/frontier-labs.ts b/src/shared/constants/providers/apikey/frontier-labs.ts index 60139bde84..cd1f98975a 100644 --- a/src/shared/constants/providers/apikey/frontier-labs.ts +++ b/src/shared/constants/providers/apikey/frontier-labs.ts @@ -11,6 +11,7 @@ export const APIKEY_PROVIDERS_FRONTIER = { color: "#10A37F", textIcon: "OA", website: "https://platform.openai.com", + serviceKinds: ["llm", "imageToText"], }, reka: { id: "reka", @@ -52,6 +53,7 @@ export const APIKEY_PROVIDERS_FRONTIER = { color: "#D97757", textIcon: "AN", website: "https://platform.claude.com", + serviceKinds: ["llm", "imageToText"], }, gemini: { id: "gemini", @@ -64,6 +66,7 @@ export const APIKEY_PROVIDERS_FRONTIER = { hasFree: true, freeNote: "Free tier available through Google AI Studio; current per-model quotas and regional limits apply", + serviceKinds: ["llm", "imageToText"], }, groq: { id: "groq", @@ -75,6 +78,7 @@ export const APIKEY_PROVIDERS_FRONTIER = { website: "https://groq.com", hasFree: true, freeNote: "Free tier: 30 RPM / 14.4K RPD — no credit card", + serviceKinds: ["llm", "imageToText"], }, blackbox: { id: "blackbox", @@ -96,6 +100,7 @@ export const APIKEY_PROVIDERS_FRONTIER = { color: "#1DA1F2", textIcon: "XA", website: "https://x.ai", + serviceKinds: ["llm", "imageToText"], }, mistral: { id: "mistral", @@ -107,6 +112,7 @@ export const APIKEY_PROVIDERS_FRONTIER = { website: "https://mistral.ai", hasFree: true, freeNote: "Free Experiment tier: rate-limited access to all models, no credit card required", + serviceKinds: ["llm", "imageToText"], }, perplexity: { id: "perplexity", diff --git a/src/shared/constants/providers/apikey/gateways.ts b/src/shared/constants/providers/apikey/gateways.ts index 860117943d..15b8fb3edc 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -82,6 +82,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = { website: "https://openrouter.ai", hasFree: true, freeNote: "Free models at $0/token with :free suffix - 20 RPM / 200 RPD", + serviceKinds: ["llm", "imageToText"], }, requesty: { id: "requesty", diff --git a/tests/unit/imagetotext-service-kinds.test.ts b/tests/unit/imagetotext-service-kinds.test.ts new file mode 100644 index 0000000000..9a486586a0 --- /dev/null +++ b/tests/unit/imagetotext-service-kinds.test.ts @@ -0,0 +1,42 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { AI_PROVIDERS } from "../../src/shared/constants/providers.ts"; +import { resolveProviderServiceKinds } from "../../open-sse/config/mediaServiceKinds.ts"; + +/** + * The Image-to-Text category (/dashboard/media-providers/imageToText) fills from + * providers whose resolved serviceKinds include "imageToText". It has no backing + * registry, so major vision-capable providers must declare it explicitly. + */ +const IMAGE_TO_TEXT_PROVIDERS = [ + "openai", + "anthropic", + "gemini", + "openrouter", + "mistral", + "xai", + "groq", +] as const; + +test("major vision providers declare the imageToText serviceKind", () => { + for (const id of IMAGE_TO_TEXT_PROVIDERS) { + const provider = AI_PROVIDERS[id] as { serviceKinds?: string[] } | undefined; + assert.ok(provider, `provider "${id}" missing from AI_PROVIDERS`); + const kinds = resolveProviderServiceKinds(id, provider.serviceKinds); + assert.ok(kinds.includes("imageToText"), `"${id}" must resolve the imageToText serviceKind`); + } +}); + +test("declaring imageToText keeps the llm kind (inline Test button + playground default)", () => { + // ProviderCard treats an EMPTY serviceKinds as "regular LLM provider"; once a + // provider declares any kind, "llm" must be declared too or the Test button + // and the playground default silently disappear. + for (const id of IMAGE_TO_TEXT_PROVIDERS) { + const provider = AI_PROVIDERS[id] as { serviceKinds?: string[] }; + assert.ok( + (provider.serviceKinds ?? []).includes("llm"), + `"${id}" declares serviceKinds without "llm" — this hides the inline Test button` + ); + } +});