import { createProvider, openAICompletionsApi, type Model, type Provider, } from "@earendil-works/pi-ai/compat"; import type { ApiKeyCredential, OAuthCredential, ProviderAuthInteraction, } from "@earendil-works/pi-ai"; import { PROVIDER_NAME, ENV_API_KEY, ENV_MODELS_MODE, resolveApiBase } from "./env.js"; import { resolveModels, type ModelConfig } from "./models.js"; import { login, refreshToken, getApiKey } from "./oauth.js"; import { interactionToOAuthCallbacks, oauthCredentialToApiKey, resolveAmbientAccessToken, } from "./runtime-auth.js"; export const PROVIDER_ID = PROVIDER_NAME; function modelsModeIncludesAll(env: NodeJS.ProcessEnv = process.env): boolean { return env[ENV_MODELS_MODE]?.trim().toLowerCase() === "all"; } function toPiModel(model: ModelConfig, baseUrl: string): Model<"openai-completions"> { return { id: model.id, name: model.name, api: "openai-completions", provider: PROVIDER_ID, baseUrl, reasoning: model.reasoning, thinkingLevelMap: { ...model.thinkingLevelMap }, input: [...model.input], cost: { ...model.cost }, compat: { ...model.compat }, contextWindow: model.contextWindow, maxTokens: model.maxTokens, }; } export function createClineProvider( models: readonly ModelConfig[], env: NodeJS.ProcessEnv = process.env, ): Provider<"openai-completions"> { const apiBase = resolveApiBase(env); const baseUrl = `${apiBase}/api/v1`; const piModels = models.map((model) => toPiModel(model, baseUrl)); const includeAll = modelsModeIncludesAll(env); return createProvider({ id: PROVIDER_ID, name: "Cline", baseUrl, auth: { apiKey: { name: "Cline API key", async login(interaction: ProviderAuthInteraction): Promise { const credentials = await login(interactionToOAuthCallbacks(interaction)); return { type: "api_key", key: getApiKey(credentials) }; }, async resolve({ credential, signal }) { const stored = credential ? oauthCredentialToApiKey(credential) : undefined; if (stored) { return { auth: { apiKey: stored }, source: "stored Cline credential" }; } const ambient = await resolveAmbientAccessToken(signal); if (!ambient) return undefined; return { auth: { apiKey: ambient.token }, source: ambient.source }; }, }, oauth: { name: "Cline", loginLabel: "Sign in with Cline CLI or API key", async login(interaction: ProviderAuthInteraction): Promise { const credentials = await login(interactionToOAuthCallbacks(interaction)); return { type: "oauth", ...credentials }; }, async refresh(credential: OAuthCredential, signal: AbortSignal): Promise { signal.throwIfAborted(); const refreshed = await refreshToken(credential); return { type: "oauth", ...refreshed }; }, async toAuth(credential: OAuthCredential) { return { apiKey: getApiKey(credential) }; }, }, }, models: piModels, async fetchModels({ signal, credential }) { signal.throwIfAborted(); const stored = credential ? oauthCredentialToApiKey(credential) : undefined; const apiKey = stored ?? (await resolveAmbientAccessToken(signal))?.token; const remote = await resolveModels(apiKey, { apiBase, includeAll }); return remote.map((model) => toPiModel(model, baseUrl)); }, api: openAICompletionsApi(), }); } export async function loadModelCatalog(apiKey?: string): Promise { return resolveModels(apiKey, { apiBase: resolveApiBase(), includeAll: modelsModeIncludesAll(), }); } export { ENV_API_KEY, ENV_MODELS_MODE }; /** @deprecated Renamed to {@link createClineProvider}. */ export const createClineFreeProvider = createClineProvider;