581 lines
20 KiB
TypeScript
581 lines
20 KiB
TypeScript
import type {
|
|
ModelSummary,
|
|
ProviderAccount,
|
|
ProviderModelEntry,
|
|
ProviderProtocol,
|
|
ProviderSecret,
|
|
} from '../../shared/providers/types';
|
|
import {
|
|
getProviderBackendConfig,
|
|
getProviderDefaultModel,
|
|
getProviderDefinition,
|
|
} from '../../shared/providers/registry';
|
|
import type { ProductModelRef } from '../contracts';
|
|
import { atomicWriteJson } from '../../coding-projects/atomic-json';
|
|
import {
|
|
NIANCODE_USER_MODEL_ACCOUNT_ID,
|
|
normalizeImportedUserModelId,
|
|
selectUserModelRuntimeAccounts,
|
|
} from '../../../shared/user-model-config';
|
|
import {
|
|
getImportedModelProfile,
|
|
thinkingLevelMapForImportedModelCapability,
|
|
} from '../../../shared/imported-model-profile';
|
|
|
|
const PI_ENV_PREFIX = 'MAKELORE_PI';
|
|
const WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE = 'works_square_ai_gateway';
|
|
const WORKS_SQUARE_AI_GATEWAY_PROXY_CREDENTIAL_MODE = 'works_square_ai_gateway_proxy';
|
|
const providerCatalogWriteTails = new Map<string, Promise<void>>();
|
|
|
|
export const PI_PROVIDER_APIS = [
|
|
'openai-completions',
|
|
'openai-responses',
|
|
'anthropic-messages',
|
|
'google-generative-ai',
|
|
] as const;
|
|
|
|
export type PiProviderApi = (typeof PI_PROVIDER_APIS)[number];
|
|
|
|
export interface PiProviderModelDescriptor {
|
|
id: string;
|
|
name: string;
|
|
input: Array<'text' | 'image'>;
|
|
reasoning: boolean;
|
|
contextWindow?: number;
|
|
maxOutputTokens?: number;
|
|
compat?: {
|
|
thinkingFormat?: 'deepseek' | 'openrouter' | 'qwen';
|
|
sessionAffinityFormat?: 'openrouter';
|
|
supportsDeveloperRole?: boolean;
|
|
supportsReasoningEffort?: boolean;
|
|
supportsStore?: boolean;
|
|
requiresReasoningContentOnAssistantMessages?: boolean;
|
|
};
|
|
thinkingLevelMap?: Partial<Record<ProductModelRef['thinkingLevel'], string | null>>;
|
|
}
|
|
|
|
export interface PiProviderDescriptor {
|
|
accountId: string;
|
|
runtimeProviderId: string;
|
|
api: PiProviderApi;
|
|
baseUrl?: string;
|
|
headers: Record<string, string>;
|
|
apiKeyEnv?: string;
|
|
models: PiProviderModelDescriptor[];
|
|
}
|
|
|
|
export interface PiModelsFile {
|
|
providers: Record<string, {
|
|
baseUrl?: string;
|
|
api: PiProviderApi;
|
|
apiKey?: string;
|
|
headers?: Record<string, string>;
|
|
models: Array<{
|
|
id: string;
|
|
name: string;
|
|
reasoning: boolean;
|
|
input: Array<'text' | 'image'>;
|
|
contextWindow?: number;
|
|
maxTokens?: number;
|
|
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
compat?: PiProviderModelDescriptor['compat'];
|
|
thinkingLevelMap?: PiProviderModelDescriptor['thinkingLevelMap'];
|
|
}>;
|
|
}>;
|
|
}
|
|
|
|
export interface PiProviderCatalogSummary {
|
|
providerCount: number;
|
|
providers: Array<{
|
|
runtimeProviderId: string;
|
|
api: PiProviderApi;
|
|
baseUrl?: string;
|
|
modelIds: string[];
|
|
imageInputModelIds: string[];
|
|
headerNames: string[];
|
|
hasCredentialReference: boolean;
|
|
}>;
|
|
}
|
|
|
|
export interface PiProviderCatalogResult {
|
|
descriptors: PiProviderDescriptor[];
|
|
modelsFile: PiModelsFile;
|
|
summary: PiProviderCatalogSummary;
|
|
}
|
|
|
|
export interface BuildPiProviderCatalogOptions {
|
|
accounts: readonly ProviderAccount[];
|
|
modelSummaries?: readonly ModelSummary[];
|
|
}
|
|
|
|
export async function buildPiProviderCatalogFromProviderService(
|
|
modelSummaries: readonly ModelSummary[] = [],
|
|
): Promise<PiProviderCatalogResult> {
|
|
const { getProviderService } = await import('../../services/providers/provider-service');
|
|
return buildPiProviderCatalog({
|
|
accounts: await getProviderService().listAccounts(),
|
|
modelSummaries,
|
|
});
|
|
}
|
|
|
|
export interface PiProviderSelection {
|
|
accountId: string;
|
|
runtimeProviderId: string;
|
|
modelId: string;
|
|
thinkingLevel: ProductModelRef['thinkingLevel'];
|
|
input: Array<'text' | 'image'>;
|
|
contextWindow?: number;
|
|
maxOutputTokens?: number;
|
|
}
|
|
|
|
export interface BuildPiWorkerCredentialProjectionOptions {
|
|
account: ProviderAccount;
|
|
descriptor: PiProviderDescriptor;
|
|
resolveCredential: (account: ProviderAccount) => Promise<string | null>;
|
|
localProxyCredential?: string;
|
|
}
|
|
|
|
export interface PiWorkerCredentialProjection {
|
|
env: Record<string, string>;
|
|
sensitiveValues: string[];
|
|
}
|
|
|
|
export interface PiWorkerCredentialProjectionSummary {
|
|
envKeys: string[];
|
|
sensitiveValueCount: number;
|
|
}
|
|
|
|
export class PiProviderConfigError extends Error {
|
|
constructor(
|
|
public readonly code: 'PROVIDER_INVALID' | 'PROVIDER_AUTH_REQUIRED' | 'MODEL_UNAVAILABLE',
|
|
message: string,
|
|
) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
export function credentialValueForProviderSecret(secret: ProviderSecret | null): string | null {
|
|
if (!secret) return null;
|
|
if (secret.type === 'api_key') return secret.apiKey.trim() || null;
|
|
if (secret.type === 'oauth') return secret.accessToken.trim() || null;
|
|
return secret.apiKey?.trim() || null;
|
|
}
|
|
|
|
export async function resolvePiProviderCredentialFromSecretStore(
|
|
account: ProviderAccount,
|
|
): Promise<string | null> {
|
|
const { getProviderSecret } = await import('../../services/secrets/secret-store');
|
|
return credentialValueForProviderSecret(await getProviderSecret(account.id));
|
|
}
|
|
|
|
function accountHex(accountId: string): string {
|
|
const normalized = accountId.trim();
|
|
if (!normalized) throw new PiProviderConfigError('PROVIDER_INVALID', 'Provider account id is required');
|
|
return Buffer.from(normalized, 'utf8').toString('hex');
|
|
}
|
|
|
|
export function resolvePiRuntimeProviderId(accountId: string): string {
|
|
return `makelore-account-${accountHex(accountId)}`;
|
|
}
|
|
|
|
function apiKeyEnvForAccount(accountId: string): string {
|
|
return `${PI_ENV_PREFIX}_ACCOUNT_${accountHex(accountId).toUpperCase()}_API_KEY`;
|
|
}
|
|
|
|
function headerEnvForAccount(accountId: string, headerName: string): string {
|
|
const nameHex = Buffer.from(headerName.toLowerCase(), 'utf8').toString('hex').toUpperCase();
|
|
return `${PI_ENV_PREFIX}_ACCOUNT_${accountHex(accountId).toUpperCase()}_HEADER_${nameHex}`;
|
|
}
|
|
|
|
function normalizeBaseUrl(value: string | undefined): string | undefined {
|
|
const normalized = value?.trim().replace(/\/+$/, '');
|
|
return normalized || undefined;
|
|
}
|
|
|
|
function normalizeWorksGatewayBaseUrl(value: string | undefined): string | undefined {
|
|
const normalized = normalizeBaseUrl(value);
|
|
if (!normalized) return undefined;
|
|
try {
|
|
const parsed = new URL(normalized);
|
|
const pathname = parsed.pathname.replace(/\/+$/, '');
|
|
if (!pathname || pathname === '/') parsed.pathname = '/v1';
|
|
return parsed.toString().replace(/\/+$/, '');
|
|
} catch {
|
|
return normalized;
|
|
}
|
|
}
|
|
|
|
function defaultBaseUrlForVendor(vendorId: string): string | undefined {
|
|
if (vendorId === 'anthropic') return 'https://api.anthropic.com';
|
|
if (vendorId === 'google') return 'https://generativelanguage.googleapis.com/v1beta';
|
|
return getProviderBackendConfig(vendorId)?.baseUrl
|
|
?? getProviderDefinition(vendorId)?.defaultBaseUrl;
|
|
}
|
|
|
|
function baseUrlForAccount(account: ProviderAccount): string | undefined {
|
|
const baseUrl = account.baseUrl
|
|
?? account.metadata?.worksSquareOneApiBaseUrl
|
|
?? defaultBaseUrlForVendor(account.vendorId);
|
|
return account.metadata?.worksSquareCredentialMode === WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE
|
|
? normalizeWorksGatewayBaseUrl(baseUrl)
|
|
: normalizeBaseUrl(baseUrl);
|
|
}
|
|
|
|
function protocolForAccount(account: ProviderAccount): ProviderProtocol | 'google-generative-ai' | undefined {
|
|
if (account.apiProtocol) return account.apiProtocol;
|
|
const configured = getProviderBackendConfig(account.vendorId)?.api;
|
|
if (configured) return configured;
|
|
if (account.vendorId === 'anthropic') return 'anthropic-messages';
|
|
if (account.vendorId === 'google') return 'google-generative-ai';
|
|
if (account.vendorId === 'openai') return 'openai-responses';
|
|
if (account.vendorId === 'openrouter') return 'openrouter';
|
|
return undefined;
|
|
}
|
|
|
|
function mapProtocol(protocol: ReturnType<typeof protocolForAccount>): {
|
|
api: PiProviderApi;
|
|
compat?: PiProviderModelDescriptor['compat'];
|
|
} {
|
|
if (protocol === 'openrouter') {
|
|
return {
|
|
api: 'openai-completions',
|
|
compat: {
|
|
thinkingFormat: 'openrouter',
|
|
sessionAffinityFormat: 'openrouter',
|
|
},
|
|
};
|
|
}
|
|
if (protocol && PI_PROVIDER_APIS.includes(protocol as PiProviderApi)) {
|
|
return { api: protocol as PiProviderApi };
|
|
}
|
|
throw new PiProviderConfigError('PROVIDER_INVALID', 'Provider protocol is not supported by Pi');
|
|
}
|
|
|
|
function normalizeModelId(rawModelId: string | undefined, account: ProviderAccount): string | undefined {
|
|
const modelId = rawModelId?.trim();
|
|
if (!modelId) return undefined;
|
|
const runtimePrefix = `${resolvePiRuntimeProviderId(account.id)}/`;
|
|
const unqualified = modelId.startsWith(runtimePrefix)
|
|
? modelId.slice(runtimePrefix.length)
|
|
: modelId;
|
|
return account.id === NIANCODE_USER_MODEL_ACCOUNT_ID
|
|
? normalizeImportedUserModelId(unqualified)
|
|
: unqualified;
|
|
}
|
|
|
|
function backendModelEntries(account: ProviderAccount): Map<string, ProviderModelEntry> {
|
|
return new Map((getProviderBackendConfig(account.vendorId)?.models ?? []).map((model) => [model.id, model]));
|
|
}
|
|
|
|
function modelIdsForAccount(account: ProviderAccount): string[] {
|
|
const seen = new Set<string>();
|
|
const result: string[] = [];
|
|
const configuredModels = getProviderBackendConfig(account.vendorId)?.models ?? [];
|
|
for (const rawModelId of [
|
|
account.model ?? getProviderDefaultModel(account.vendorId),
|
|
...(account.fallbackModels ?? []),
|
|
...(account.metadata?.customModels ?? []),
|
|
...configuredModels.map((model) => model.id),
|
|
]) {
|
|
const modelId = normalizeModelId(rawModelId, account);
|
|
if (!modelId || seen.has(modelId)) continue;
|
|
seen.add(modelId);
|
|
result.push(modelId);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function finitePositiveInteger(value: unknown): number | undefined {
|
|
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
|
|
? value
|
|
: undefined;
|
|
}
|
|
|
|
function modelDescriptor(
|
|
account: ProviderAccount,
|
|
modelId: string,
|
|
summaries: readonly ModelSummary[],
|
|
compat: PiProviderModelDescriptor['compat'],
|
|
backendModels: Map<string, ProviderModelEntry>,
|
|
enforcedCompat: PiProviderModelDescriptor['compat'],
|
|
): PiProviderModelDescriptor {
|
|
const summary = summaries.find((candidate) => (
|
|
candidate.id === modelId
|
|
&& (candidate.accountId === account.id || (!candidate.accountId && candidate.vendorId === account.vendorId))
|
|
));
|
|
const backend = backendModels.get(modelId);
|
|
const profile = getImportedModelProfile(modelId);
|
|
const serverCapability = account.metadata?.worksSquareModelCapabilities?.[modelId];
|
|
const backendInput = Array.isArray(backend?.input)
|
|
? backend.input.filter((input): input is 'text' | 'image' => input === 'text' || input === 'image')
|
|
: [];
|
|
const supportsImage = Boolean(
|
|
summary?.supportsVision
|
|
|| profile?.modalities.input.includes('image')
|
|
|| backendInput.includes('image'),
|
|
);
|
|
const contextWindow = finitePositiveInteger(summary?.contextWindow)
|
|
?? finitePositiveInteger(profile?.limit?.context)
|
|
?? finitePositiveInteger(backend?.contextWindow);
|
|
const maxOutputTokens = finitePositiveInteger(profile?.limit?.output)
|
|
?? finitePositiveInteger(backend?.maxTokens);
|
|
return {
|
|
id: modelId,
|
|
name: summary?.name || (typeof backend?.name === 'string' && backend.name.trim()) || modelId,
|
|
input: supportsImage ? ['text', 'image'] : ['text'],
|
|
reasoning: serverCapability
|
|
? serverCapability.reasoningEfforts.length > 0
|
|
: summary?.supportsReasoning === true
|
|
|| profile?.pi?.reasoning === true
|
|
|| backend?.reasoning === true,
|
|
...(contextWindow ? { contextWindow } : {}),
|
|
...(maxOutputTokens ? { maxOutputTokens } : {}),
|
|
...(compat || profile?.pi?.compat || enforcedCompat || serverCapability
|
|
? {
|
|
compat: {
|
|
...compat,
|
|
...profile?.pi?.compat,
|
|
...enforcedCompat,
|
|
...(serverCapability ? { supportsReasoningEffort: true } : {}),
|
|
},
|
|
}
|
|
: {}),
|
|
...(serverCapability
|
|
? { thinkingLevelMap: thinkingLevelMapForImportedModelCapability(serverCapability) }
|
|
: profile?.pi?.thinkingLevelMap
|
|
? { thinkingLevelMap: { ...profile.pi.thinkingLevelMap } }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
function rawHeadersForAccount(account: ProviderAccount): Record<string, string> {
|
|
const headers: Record<string, string> = {};
|
|
for (const [name, value] of Object.entries({
|
|
...(getProviderBackendConfig(account.vendorId)?.headers ?? {}),
|
|
...(account.headers ?? {}),
|
|
})) {
|
|
const duplicate = Object.keys(headers).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
if (duplicate) delete headers[duplicate];
|
|
headers[name] = value;
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
function headerNamesForAccount(account: ProviderAccount): string[] {
|
|
const names = Object.keys(rawHeadersForAccount(account));
|
|
if (
|
|
account.metadata?.worksSquareCredentialMode === WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE
|
|
&& !names.some((name) => name.toLowerCase() === 'authorization')
|
|
) {
|
|
names.push('Authorization');
|
|
}
|
|
return names.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase()));
|
|
}
|
|
|
|
function descriptorForAccount(
|
|
account: ProviderAccount,
|
|
summaries: readonly ModelSummary[],
|
|
): PiProviderDescriptor {
|
|
const runtimeProviderId = resolvePiRuntimeProviderId(account.id);
|
|
const mapping = mapProtocol(protocolForAccount(account));
|
|
const baseUrl = baseUrlForAccount(account);
|
|
if (!baseUrl) {
|
|
throw new PiProviderConfigError(
|
|
'PROVIDER_INVALID',
|
|
`Provider account ${account.id} has no runtime base URL`,
|
|
);
|
|
}
|
|
const apiKeyEnv = apiKeyEnvForAccount(account.id);
|
|
const credentialMode = account.metadata?.worksSquareCredentialMode;
|
|
const enforcedCompat: PiProviderModelDescriptor['compat'] = mapping.api === 'openai-completions'
|
|
&& (credentialMode === WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE
|
|
|| credentialMode === WORKS_SQUARE_AI_GATEWAY_PROXY_CREDENTIAL_MODE)
|
|
? { supportsDeveloperRole: false }
|
|
: undefined;
|
|
const models = modelIdsForAccount(account).map((modelId) => modelDescriptor(
|
|
account,
|
|
modelId,
|
|
summaries,
|
|
mapping.compat,
|
|
backendModelEntries(account),
|
|
enforcedCompat,
|
|
));
|
|
if (models.length === 0) {
|
|
throw new PiProviderConfigError(
|
|
'PROVIDER_INVALID',
|
|
`Provider account ${account.id} has no configured model`,
|
|
);
|
|
}
|
|
const headers = Object.fromEntries(headerNamesForAccount(account).map((headerName) => [
|
|
headerName,
|
|
`$${headerEnvForAccount(account.id, headerName)}`,
|
|
]));
|
|
return {
|
|
accountId: account.id,
|
|
runtimeProviderId,
|
|
api: mapping.api,
|
|
baseUrl,
|
|
headers,
|
|
apiKeyEnv,
|
|
models,
|
|
};
|
|
}
|
|
|
|
function modelsFileForDescriptors(descriptors: readonly PiProviderDescriptor[]): PiModelsFile {
|
|
return {
|
|
providers: Object.fromEntries(descriptors.map((descriptor) => [
|
|
descriptor.runtimeProviderId,
|
|
{
|
|
...(descriptor.baseUrl ? { baseUrl: descriptor.baseUrl } : {}),
|
|
api: descriptor.api,
|
|
...(descriptor.apiKeyEnv ? { apiKey: `$${descriptor.apiKeyEnv}` } : {}),
|
|
...(Object.keys(descriptor.headers).length > 0 ? { headers: { ...descriptor.headers } } : {}),
|
|
models: descriptor.models.map((model) => ({
|
|
id: model.id,
|
|
name: model.name,
|
|
reasoning: model.reasoning,
|
|
input: [...model.input],
|
|
...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
|
|
...(model.maxOutputTokens ? { maxTokens: model.maxOutputTokens } : {}),
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
...(model.compat ? { compat: { ...model.compat } } : {}),
|
|
...(model.thinkingLevelMap
|
|
? { thinkingLevelMap: { ...model.thinkingLevelMap } }
|
|
: {}),
|
|
})),
|
|
},
|
|
])),
|
|
};
|
|
}
|
|
|
|
export function buildPiProviderCatalog(
|
|
options: BuildPiProviderCatalogOptions,
|
|
): PiProviderCatalogResult {
|
|
const accounts = selectUserModelRuntimeAccounts([...options.accounts]).filter((account) => account.enabled);
|
|
const accountIds = new Set<string>();
|
|
const descriptors = accounts.map((account) => {
|
|
if (accountIds.has(account.id)) {
|
|
throw new PiProviderConfigError('PROVIDER_INVALID', `Duplicate Provider account id: ${account.id}`);
|
|
}
|
|
accountIds.add(account.id);
|
|
return descriptorForAccount(account, options.modelSummaries ?? []);
|
|
});
|
|
return {
|
|
descriptors,
|
|
modelsFile: modelsFileForDescriptors(descriptors),
|
|
summary: {
|
|
providerCount: descriptors.length,
|
|
providers: descriptors.map((descriptor) => ({
|
|
runtimeProviderId: descriptor.runtimeProviderId,
|
|
api: descriptor.api,
|
|
...(descriptor.baseUrl ? { baseUrl: descriptor.baseUrl } : {}),
|
|
modelIds: descriptor.models.map((model) => model.id),
|
|
imageInputModelIds: descriptor.models
|
|
.filter((model) => model.input.includes('image'))
|
|
.map((model) => model.id),
|
|
headerNames: Object.keys(descriptor.headers).sort(),
|
|
hasCredentialReference: Boolean(descriptor.apiKeyEnv),
|
|
})),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function selectPiProviderModel(
|
|
catalog: PiProviderCatalogResult,
|
|
modelRef: ProductModelRef,
|
|
): PiProviderSelection {
|
|
const descriptor = catalog.descriptors.find((candidate) => candidate.accountId === modelRef.accountId);
|
|
const model = descriptor?.models.find((candidate) => candidate.id === modelRef.modelId);
|
|
if (!descriptor || !model) {
|
|
throw new PiProviderConfigError(
|
|
'MODEL_UNAVAILABLE',
|
|
'The selected Provider account or model is unavailable',
|
|
);
|
|
}
|
|
return {
|
|
accountId: descriptor.accountId,
|
|
runtimeProviderId: descriptor.runtimeProviderId,
|
|
modelId: model.id,
|
|
thinkingLevel: modelRef.thinkingLevel,
|
|
input: [...model.input],
|
|
...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
|
|
...(model.maxOutputTokens ? { maxOutputTokens: model.maxOutputTokens } : {}),
|
|
};
|
|
}
|
|
|
|
export async function writePiProviderCatalog(
|
|
filePath: string,
|
|
catalog: PiProviderCatalogResult,
|
|
modelRef?: ProductModelRef,
|
|
): Promise<void> {
|
|
if (modelRef) selectPiProviderModel(catalog, modelRef);
|
|
const previous = providerCatalogWriteTails.get(filePath) ?? Promise.resolve();
|
|
const write = previous
|
|
.catch(() => undefined)
|
|
.then(async () => await atomicWriteJson(filePath, catalog.modelsFile));
|
|
providerCatalogWriteTails.set(filePath, write);
|
|
try {
|
|
await write;
|
|
} finally {
|
|
if (providerCatalogWriteTails.get(filePath) === write) {
|
|
providerCatalogWriteTails.delete(filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
function replaceCredentialEnvReferences(value: string, credential: string | null): string {
|
|
if (!value.includes('{env:')) return value;
|
|
if (!credential) {
|
|
throw new PiProviderConfigError('PROVIDER_AUTH_REQUIRED', 'Provider credential is unavailable');
|
|
}
|
|
return value.replace(/\{env:[^}]+\}/g, credential);
|
|
}
|
|
|
|
export async function buildPiWorkerCredentialProjection(
|
|
options: BuildPiWorkerCredentialProjectionOptions,
|
|
): Promise<PiWorkerCredentialProjection> {
|
|
if (options.account.id !== options.descriptor.accountId) {
|
|
throw new PiProviderConfigError('PROVIDER_INVALID', 'Provider account does not match descriptor');
|
|
}
|
|
const useLocalProxy = options.account.metadata?.worksSquareCredentialMode
|
|
=== WORKS_SQUARE_AI_GATEWAY_PROXY_CREDENTIAL_MODE;
|
|
const resolved = useLocalProxy
|
|
? options.localProxyCredential?.trim() || null
|
|
: await options.resolveCredential(options.account);
|
|
const credential = resolved?.trim()
|
|
|| (options.account.authMode === 'local' ? 'local-provider' : null);
|
|
if (!credential) {
|
|
throw new PiProviderConfigError('PROVIDER_AUTH_REQUIRED', 'Provider credential is unavailable');
|
|
}
|
|
const env: Record<string, string> = {};
|
|
if (options.descriptor.apiKeyEnv) env[options.descriptor.apiKeyEnv] = credential;
|
|
const accountHeaders = rawHeadersForAccount(options.account);
|
|
for (const headerName of Object.keys(options.descriptor.headers)) {
|
|
const sourceEntry = Object.entries(accountHeaders).find(([name]) => (
|
|
name.toLowerCase() === headerName.toLowerCase()
|
|
));
|
|
let value = sourceEntry?.[1];
|
|
if (!value && headerName.toLowerCase() === 'authorization'
|
|
&& options.account.metadata?.worksSquareCredentialMode === WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE) {
|
|
value = `Bearer ${credential}`;
|
|
}
|
|
if (value === undefined) {
|
|
throw new PiProviderConfigError('PROVIDER_INVALID', `Provider header is unavailable: ${headerName}`);
|
|
}
|
|
env[headerEnvForAccount(options.account.id, headerName)] = replaceCredentialEnvReferences(value, credential);
|
|
}
|
|
return {
|
|
env,
|
|
sensitiveValues: [...new Set(Object.values(env).filter(Boolean))],
|
|
};
|
|
}
|
|
|
|
export function summarizePiWorkerCredentialProjection(
|
|
projection: PiWorkerCredentialProjection,
|
|
): PiWorkerCredentialProjectionSummary {
|
|
return {
|
|
envKeys: Object.keys(projection.env).sort(),
|
|
sensitiveValueCount: projection.sensitiveValues.length,
|
|
};
|
|
}
|