feat(ai): provider factories, per-provider catalogs, createProvider (phase 3)
Auth helpers in src/auth/helpers.ts: envApiKeyAuth() (stored key wins, then env vars in order, with secret-prompt login) and lazyOAuth() (flow loads on first use through bundler-opaque dynamic imports in utils/oauth/load.ts; the OAuthAuth flow exports land in phase 4). There is no OAuth factory toggle: providers that support OAuth always attach it, advertising costs nothing until login/refresh runs. createProvider() in models.ts builds providers from parts: single API implementation or a map dispatched on model.api (mixed-API providers like opencode and github-copilot); unknown api yields a stream error. generate-models.ts now emits one providers/<id>.models.ts catalog per provider (35 files, biome-excluded like models.generated.ts) and models.generated.ts becomes a generated aggregator, so importing one provider factory pulls one catalog. Typed getModel globals unchanged. One factory per built-in provider under src/providers/: envApiKeyAuth for standard providers, OAuth for anthropic/openai-codex/github-copilot, ambient ApiKeyAuth for amazon-bedrock (AWS env/profile/IAM) and google-vertex (explicit key or ADC+project+location). providers/all.ts: builtinProviders(), builtinModels(), getBuiltin* re-exports. fauxProvider() factory returns a real Provider for tests; legacy registerFauxProvider() unchanged.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { registerApiProvider, unregisterApiProviders } from "../api-registry.ts";
|
||||
import { createProvider, type Provider } from "../models.ts";
|
||||
import type {
|
||||
AssistantMessage,
|
||||
AssistantMessageEventStream,
|
||||
@@ -125,6 +126,18 @@ export interface FauxProviderRegistration {
|
||||
unregister: () => void;
|
||||
}
|
||||
|
||||
export interface FauxProviderHandle {
|
||||
provider: Provider;
|
||||
api: string;
|
||||
models: [Model<string>, ...Model<string>[]];
|
||||
getModel(): Model<string>;
|
||||
getModel(modelId: string): Model<string> | undefined;
|
||||
state: { callCount: number };
|
||||
setResponses: (responses: FauxResponseStep[]) => void;
|
||||
appendResponses: (responses: FauxResponseStep[]) => void;
|
||||
getPendingResponseCount: () => number;
|
||||
}
|
||||
|
||||
function estimateTokens(text: string): number {
|
||||
return Math.ceil(text.length / 4);
|
||||
}
|
||||
@@ -388,10 +401,9 @@ async function streamWithDeltas(
|
||||
stream.end(message);
|
||||
}
|
||||
|
||||
export function registerFauxProvider(options: RegisterFauxProviderOptions = {}): FauxProviderRegistration {
|
||||
function createFauxCore(options: RegisterFauxProviderOptions) {
|
||||
const api = options.api ?? randomId(DEFAULT_API);
|
||||
const provider = options.provider ?? DEFAULT_PROVIDER;
|
||||
const sourceId = randomId("faux-provider");
|
||||
const minTokenSize = Math.max(
|
||||
1,
|
||||
Math.min(options.tokenSize?.min ?? DEFAULT_MIN_TOKEN_SIZE, options.tokenSize?.max ?? DEFAULT_MAX_TOKEN_SIZE),
|
||||
@@ -467,8 +479,6 @@ export function registerFauxProvider(options: RegisterFauxProviderOptions = {}):
|
||||
const streamSimple: StreamFunction<string, SimpleStreamOptions> = (streamModel, context, streamOptions) =>
|
||||
stream(streamModel, context, streamOptions);
|
||||
|
||||
registerApiProvider({ api, stream, streamSimple }, sourceId);
|
||||
|
||||
function getModel(): Model<string>;
|
||||
function getModel(requestedModelId: string): Model<string> | undefined;
|
||||
function getModel(requestedModelId?: string): Model<string> | undefined {
|
||||
@@ -480,20 +490,69 @@ export function registerFauxProvider(options: RegisterFauxProviderOptions = {}):
|
||||
|
||||
return {
|
||||
api,
|
||||
provider,
|
||||
models,
|
||||
stream,
|
||||
streamSimple,
|
||||
getModel,
|
||||
state,
|
||||
setResponses(responses) {
|
||||
setResponses(responses: FauxResponseStep[]) {
|
||||
pendingResponses = [...responses];
|
||||
},
|
||||
appendResponses(responses) {
|
||||
appendResponses(responses: FauxResponseStep[]) {
|
||||
pendingResponses.push(...responses);
|
||||
},
|
||||
getPendingResponseCount() {
|
||||
return pendingResponses.length;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Registers the faux api into the legacy global api-registry. */
|
||||
export function registerFauxProvider(options: RegisterFauxProviderOptions = {}): FauxProviderRegistration {
|
||||
const core = createFauxCore(options);
|
||||
const sourceId = randomId("faux-provider");
|
||||
registerApiProvider({ api: core.api, stream: core.stream, streamSimple: core.streamSimple }, sourceId);
|
||||
return {
|
||||
api: core.api,
|
||||
models: core.models,
|
||||
getModel: core.getModel,
|
||||
state: core.state,
|
||||
setResponses: core.setResponses,
|
||||
appendResponses: core.appendResponses,
|
||||
getPendingResponseCount: core.getPendingResponseCount,
|
||||
unregister() {
|
||||
unregisterApiProviders(sourceId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Faux provider for tests built on explicit `Models` collections:
|
||||
*
|
||||
* ```ts
|
||||
* const faux = fauxProvider();
|
||||
* const models = createModels();
|
||||
* models.setProvider(faux.provider);
|
||||
* faux.setResponses([fauxAssistantMessage("hi")]);
|
||||
* ```
|
||||
*/
|
||||
export function fauxProvider(options: RegisterFauxProviderOptions = {}): FauxProviderHandle {
|
||||
const core = createFauxCore(options);
|
||||
const provider = createProvider({
|
||||
id: core.provider,
|
||||
auth: { apiKey: { name: "Faux", resolve: async () => ({ auth: {} }) } },
|
||||
models: core.models,
|
||||
api: { stream: core.stream, streamSimple: core.streamSimple },
|
||||
});
|
||||
return {
|
||||
provider,
|
||||
api: core.api,
|
||||
models: core.models,
|
||||
getModel: core.getModel,
|
||||
state: core.state,
|
||||
setResponses: core.setResponses,
|
||||
appendResponses: core.appendResponses,
|
||||
getPendingResponseCount: core.getPendingResponseCount,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user