-
Notifications
You must be signed in to change notification settings - Fork 391
feat(providers): add canonical free provider directory #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import type { CodexAccountMode, OcxProviderConfig } from "../types"; | ||
| import { PROVIDER_REGISTRY, type ProviderRegistryEntry } from "./registry"; | ||
| import type { ProviderAccessGroup } from "./free-directory"; | ||
|
|
||
| export interface DerivedKeyLoginProvider { | ||
| label: string; | ||
|
|
@@ -67,6 +68,15 @@ export interface DerivedProviderPreset { | |
| baseUrlChoices?: Array<{ id: string; label: string; baseUrl?: string }>; | ||
| /** Immutable canonical provider config seed for the reserved canonical `openai` forward preset. */ | ||
| provider?: OcxProviderConfig; | ||
| accessGroups?: readonly ProviderAccessGroup[]; | ||
| supportLevel?: "supported" | "experimental" | "reference"; | ||
| verification?: "official" | "primary" | "unverified"; | ||
| documentationUrl?: string; | ||
| modelsUrl?: string; | ||
| discovery?: "live" | "static" | "hybrid" | "unsupported"; | ||
| lastVerified?: string; | ||
| models?: string[]; | ||
| liveModels?: boolean; | ||
| } | ||
|
|
||
| export function listRegistryEntries(): readonly ProviderRegistryEntry[] { | ||
|
|
@@ -125,7 +135,7 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon | |
| export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> { | ||
| const out: Record<string, DerivedKeyLoginProvider> = {}; | ||
| for (const entry of PROVIDER_REGISTRY) { | ||
| if (entry.authKind !== "key") continue; | ||
| if (entry.authKind !== "key" || entry.supportLevel === "reference" || entry.directoryOnly) continue; | ||
| if (!entry.dashboardUrl) throw new Error(`Registry key provider missing dashboardUrl: ${entry.id}`); | ||
| out[entry.id] = { | ||
| label: entry.label, | ||
|
|
@@ -165,7 +175,7 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> { | |
| } | ||
|
|
||
| export function deriveInitProviders(): DerivedInitProvider[] { | ||
| return PROVIDER_REGISTRY.map(entry => ({ | ||
| return PROVIDER_REGISTRY.filter(entry => entry.supportLevel !== "reference" && !entry.directoryOnly).map(entry => ({ | ||
| id: entry.id, | ||
| label: formatInitLabel(entry), | ||
| adapter: entry.adapter, | ||
|
|
@@ -192,7 +202,7 @@ export function deriveOAuthIds(): string[] { | |
|
|
||
| export function deriveProviderPresets(): DerivedProviderPreset[] { | ||
| const presets = PROVIDER_REGISTRY | ||
| .filter(entry => entry.featured || entry.authKind === "key" || entry.dashboardPreset) | ||
| .filter(entry => entry.featured || entry.authKind === "key" || entry.dashboardPreset || entry.accessGroups?.length) | ||
| .map(entryToPreset); | ||
| return [...dedupePresets(presets), customPreset()]; | ||
| } | ||
|
|
@@ -231,6 +241,9 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig | |
| if (prov.freeTier === undefined && seed.freeTier !== undefined) prov.freeTier = seed.freeTier; | ||
| if (prov.modelSuffixBracketStrip === undefined && seed.modelSuffixBracketStrip !== undefined) prov.modelSuffixBracketStrip = seed.modelSuffixBracketStrip; | ||
| if (!prov.headers && seed.headers) prov.headers = { ...seed.headers }; | ||
| if (prov.googleMode === undefined && seed.googleMode !== undefined) prov.googleMode = seed.googleMode; | ||
| if (prov.project === undefined && seed.project !== undefined) prov.project = seed.project; | ||
| if (prov.location === undefined && seed.location !== undefined) prov.location = seed.location; | ||
| } | ||
|
|
||
| export function deriveFeaturedProviderIds(): string[] { | ||
|
|
@@ -270,6 +283,15 @@ function entryToPreset(entry: ProviderRegistryEntry): DerivedProviderPreset { | |
| ...(entry.keyOptional ? { keyOptional: true } : {}), | ||
| ...(entry.freeTier ? { freeTier: true } : {}), | ||
| ...(entry.baseUrlChoices ? { baseUrlChoices: entry.baseUrlChoices.map(c => ({ ...c })) } : {}), | ||
| ...(entry.accessGroups ? { accessGroups: [...entry.accessGroups] } : {}), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Publishing Useful? React with 👍 / 👎. |
||
| ...(entry.supportLevel ? { supportLevel: entry.supportLevel } : {}), | ||
| ...(entry.verification ? { verification: entry.verification } : {}), | ||
| ...(entry.documentationUrl ? { documentationUrl: entry.documentationUrl } : {}), | ||
| ...(entry.modelsUrl ? { modelsUrl: entry.modelsUrl } : {}), | ||
| ...(entry.discovery ? { discovery: entry.discovery } : {}), | ||
| ...(entry.lastVerified ? { lastVerified: entry.lastVerified } : {}), | ||
| ...(entry.models ? { models: [...entry.models] } : {}), | ||
| ...(entry.liveModels !== undefined ? { liveModels: entry.liveModels } : {}), | ||
| }; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reference-only directory entries leak into
deriveProviderPresets().The filter
entry.featured || entry.authKind === "key" || entry.dashboardPreset || entry.accessGroups?.lengthadmits any entry with a non-emptyaccessGroups. Since everyFREE_PROVIDER_DIRECTORYentry (persrc/providers/free-directory.tsLines 145-151) always has at least one access group by construction — including pure reference entries like"agy","blackbox","coze","duckduckgo-web"that havebaseUrl: "", nodashboardUrl, andsupportLevel: "reference"— this filter (and thedashboardPresetclause, sinceregistry.tssetsdashboardPreset: trueunconditionally for all directory-only rows at Lines 1045-1049) lets them pass through into the connectable preset list produced byentryToPreset.This contradicts the intended behavior for this cohort ("excludes reference or directory-only entries from connectable provider outputs") and the explicit design note in
free-directory.tsthat consumer-web/session providers must stay reference-only and never be surfaced as something a user can connect to.🐛 Proposed fix
export function deriveProviderPresets(): DerivedProviderPreset[] { const presets = PROVIDER_REGISTRY - .filter(entry => entry.featured || entry.authKind === "key" || entry.dashboardPreset || entry.accessGroups?.length) + .filter(entry => entry.supportLevel !== "reference" && (entry.featured || entry.authKind === "key" || entry.dashboardPreset || entry.accessGroups?.length)) .map(entryToPreset); return [...dedupePresets(presets), customPreset()]; }📝 Committable suggestion
🤖 Prompt for AI Agents