Extend provider adapter runtime contract

This commit is contained in:
ZacharyZhang-NY committed 2026-05-23 19:34:10 -04:00
1 parent 0388e1003b
commit 203a48060e
3 files changed
+78 -27

No files matched your search

@@ -1,4 +1,5 @@
import { PROVIDER_AUTH_TYPE, type ProviderAuthType } from "../../shared/domain";
import { listProviderModels, type ProviderModelEntry } from "../../shared/provider-models";
export interface ProviderExecutionInput {
authProfile: ProviderAuthProfile;
@@ -21,10 +22,24 @@ export interface ProviderHealthResult {
checkedAt: string;
}
export interface ProviderContext {
authProfile: ProviderAuthProfile;
model: string | null;
}
export interface ProviderRateLimitState {
requestLimit: number | null;
remainingRequests: number | null;
resetAt: string | null;
rawStateJson: string;
}
export interface ProviderAdapter {
providerKey: string;
authType: ProviderAuthType;
parseAuthProfile(secret: string): ProviderAuthProfile;
listModels(ctx: ProviderContext): Promise<ProviderModelEntry[]>;
getRateLimitState(ctx: ProviderContext): Promise<ProviderRateLimitState | null>;
checkHealth(input: ProviderHealthInput): Promise<ProviderHealthResult>;
createResponse?: (input: ProviderExecutionInput) => Promise<ProviderExecutionResult>;
}
@@ -48,6 +63,8 @@ export const providerAdapters: ProviderAdapterRegistry = {
providerKey: "openai",
authType: PROVIDER_AUTH_TYPE.API_KEY,
parseAuthProfile: parseApiKey,
listModels: () => listStaticModels("openai"),
getRateLimitState: providerRateLimitUnknown,
checkHealth: (input) => checkOpenAiHealth(input.authProfile, "https://api.openai.com/v1/models"),
async createResponse(input) {
const profile = requireApiKeyProfile(input.authProfile, "OpenAI");
@@ -66,6 +83,8 @@ export const providerAdapters: ProviderAdapterRegistry = {
providerKey: "anthropic",
authType: PROVIDER_AUTH_TYPE.API_KEY,
parseAuthProfile: parseApiKey,
listModels: () => listStaticModels("anthropic"),
getRateLimitState: providerRateLimitUnknown,
async checkHealth(input) {
const profile = requireApiKeyProfile(input.authProfile, "Anthropic");
await getJson("https://api.anthropic.com/v1/models", {
@@ -108,6 +127,8 @@ export const providerAdapters: ProviderAdapterRegistry = {
providerKey: "azure-openai",
authType: PROVIDER_AUTH_TYPE.CLOUD,
parseAuthProfile: parseAzureProfile,
listModels: () => listStaticModels("azure-openai"),
getRateLimitState: providerRateLimitUnknown,
async checkHealth(input) {
const profile = requireAzureProfile(input.authProfile);
await getJson(`${profile.baseUrl}/openai/deployments?api-version=2024-10-21`, { "api-key": profile.apiKey });
@@ -118,6 +139,8 @@ export const providerAdapters: ProviderAdapterRegistry = {
providerKey: "aws-bedrock",
authType: PROVIDER_AUTH_TYPE.CLOUD,
parseAuthProfile: parseBedrockProfile,
listModels: () => listStaticModels("aws-bedrock"),
getRateLimitState: providerRateLimitUnknown,
checkHealth: async (input) => {
requireBedrockProfile(input.authProfile);
return healthy();
@@ -127,6 +150,8 @@ export const providerAdapters: ProviderAdapterRegistry = {
providerKey: "vertex-ai",
authType: PROVIDER_AUTH_TYPE.CLOUD,
parseAuthProfile: parseVertexProfile,
listModels: () => listStaticModels("vertex-ai"),
getRateLimitState: providerRateLimitUnknown,
checkHealth: async (input) => {
requireVertexProfile(input.authProfile);
return healthy();
@@ -220,6 +245,8 @@ function openAiCompatibleAdapter({
providerKey,
authType: PROVIDER_AUTH_TYPE.API_KEY,
parseAuthProfile: parseApiKey,
listModels: () => listStaticModels(providerKey),
getRateLimitState: providerRateLimitUnknown,
checkHealth: (input) => checkOpenAiHealth(input.authProfile, healthUrl),
async createResponse(input) {
const profile = requireApiKeyProfile(input.authProfile, label);
@@ -250,6 +277,8 @@ function browserTokenAdapter(providerKey: string, authType: ProviderAuthType): P
providerKey,
authType,
parseAuthProfile: authType === PROVIDER_AUTH_TYPE.ACCOUNT_SUBSCRIPTION ? parseAccountToken : parseAccessToken,
listModels: () => listStaticModels(providerKey),
getRateLimitState: providerRateLimitUnknown,
checkHealth: async (input) => {
if (authType === PROVIDER_AUTH_TYPE.ACCOUNT_SUBSCRIPTION) requireAccountTokenProfile(input.authProfile);
else requireAccessTokenProfile(input.authProfile);
@@ -373,3 +402,11 @@ function requireVertexProfile(profile: ProviderAuthProfile): Extract<ProviderAut
function healthy(): ProviderHealthResult {
return { checkedAt: new Date().toISOString() };
}
function listStaticModels(providerKey: string): Promise<ProviderModelEntry[]> {
return Promise.resolve(listProviderModels(providerKey));
}
async function providerRateLimitUnknown(): Promise<ProviderRateLimitState | null> {
return null;
}
@@ -242,10 +242,15 @@ export class SdlcService {
const catalogKeys = PROVIDER_CATALOG.map((provider) => provider.key);
const adapterKeys = Object.keys(providerAdapters);
const missing = catalogKeys.filter((key) => !adapterKeys.includes(key));
const runtimeContractReady = PROVIDER_CATALOG.every((provider) => {
const adapter = providerAdapters[provider.key];
return typeof adapter.listModels === "function" && typeof adapter.getRateLimitState === "function";
});
const schemaVersion = count(this.db, "select coalesce(max(version), 0) as count from schema_migrations");
return [
{ key: "provider_adapter_coverage", passed: missing.length === 0, value: `${adapterKeys.length}/${catalogKeys.length}` },
{ key: "schema_version", passed: schemaVersion >= 20, value: schemaVersion },
{ key: "provider_runtime_contract", passed: runtimeContractReady, value: runtimeContractReady },
{ key: "schema_version", passed: schemaVersion >= 21, value: schemaVersion },
];
}