mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-26 06:55:12 +00:00
268 lines
8.7 KiB
TypeScript
268 lines
8.7 KiB
TypeScript
import path from "node:path";
|
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
import * as PiCodingAgent from "@earendil-works/pi-coding-agent";
|
|
import type {
|
|
AuthStorage as PiAuthStorage,
|
|
ModelRegistry as PiModelRegistry,
|
|
} from "@earendil-works/pi-coding-agent";
|
|
import { normalizeModelCompat } from "../plugins/provider-model-compat.js";
|
|
import {
|
|
applyProviderResolvedModelCompatWithPlugins,
|
|
applyProviderResolvedTransportWithPlugin,
|
|
normalizeProviderResolvedModelWithPlugin,
|
|
} from "../plugins/provider-runtime.js";
|
|
import { isRecord } from "../utils.js";
|
|
import type { PiCredentialMap } from "./pi-auth-credentials.js";
|
|
import {
|
|
resolvePiCredentialsForDiscovery,
|
|
scrubLegacyStaticAuthJsonEntriesForDiscovery,
|
|
type DiscoverAuthStorageOptions,
|
|
} from "./pi-auth-discovery.js";
|
|
import { normalizeProviderId } from "./provider-id.js";
|
|
|
|
const PiAuthStorageClass = PiCodingAgent.AuthStorage;
|
|
const PiModelRegistryClass = PiCodingAgent.ModelRegistry;
|
|
|
|
export { PiAuthStorageClass as AuthStorage, PiModelRegistryClass as ModelRegistry };
|
|
|
|
type ProviderRuntimeModelLike = Model<Api> & {
|
|
contextTokens?: number;
|
|
};
|
|
|
|
type DiscoveredProviderRuntimeModelLike = Omit<ProviderRuntimeModelLike, "api"> & {
|
|
api?: string | null;
|
|
};
|
|
|
|
type DiscoverModelsOptions = {
|
|
providerFilter?: string;
|
|
normalizeModels?: boolean;
|
|
};
|
|
|
|
type InMemoryAuthStorageBackendLike = {
|
|
withLock<T>(
|
|
update: (current: string) => {
|
|
result: T;
|
|
next?: string;
|
|
},
|
|
): T;
|
|
};
|
|
|
|
function createInMemoryAuthStorageBackend(
|
|
initialData: PiCredentialMap,
|
|
): InMemoryAuthStorageBackendLike {
|
|
let snapshot = JSON.stringify(initialData, null, 2);
|
|
return {
|
|
withLock<T>(
|
|
update: (current: string) => {
|
|
result: T;
|
|
next?: string;
|
|
},
|
|
): T {
|
|
const { result, next } = update(snapshot);
|
|
if (typeof next === "string") {
|
|
snapshot = next;
|
|
}
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
|
|
export function normalizeDiscoveredPiModel<T>(value: T, agentDir: string): T {
|
|
if (!isRecord(value)) {
|
|
return value;
|
|
}
|
|
if (
|
|
typeof value.id !== "string" ||
|
|
typeof value.name !== "string" ||
|
|
typeof value.provider !== "string"
|
|
) {
|
|
return value;
|
|
}
|
|
const model = value as unknown as DiscoveredProviderRuntimeModelLike;
|
|
const pluginNormalized =
|
|
normalizeProviderResolvedModelWithPlugin({
|
|
provider: model.provider,
|
|
context: {
|
|
provider: model.provider,
|
|
modelId: model.id,
|
|
model: model as unknown as ProviderRuntimeModelLike,
|
|
agentDir,
|
|
},
|
|
}) ?? model;
|
|
const compatNormalized =
|
|
applyProviderResolvedModelCompatWithPlugins({
|
|
provider: model.provider,
|
|
context: {
|
|
provider: model.provider,
|
|
modelId: model.id,
|
|
model: pluginNormalized as unknown as ProviderRuntimeModelLike,
|
|
agentDir,
|
|
},
|
|
}) ?? pluginNormalized;
|
|
const transportNormalized =
|
|
applyProviderResolvedTransportWithPlugin({
|
|
provider: model.provider,
|
|
context: {
|
|
provider: model.provider,
|
|
modelId: model.id,
|
|
model: compatNormalized as unknown as ProviderRuntimeModelLike,
|
|
agentDir,
|
|
},
|
|
}) ?? compatNormalized;
|
|
if (
|
|
!isRecord(transportNormalized) ||
|
|
typeof transportNormalized.id !== "string" ||
|
|
typeof transportNormalized.name !== "string" ||
|
|
typeof transportNormalized.provider !== "string" ||
|
|
typeof transportNormalized.api !== "string"
|
|
) {
|
|
return value;
|
|
}
|
|
return normalizeModelCompat(transportNormalized as Model<Api>) as T;
|
|
}
|
|
|
|
type PiModelRegistryClassLike = {
|
|
create?: (authStorage: PiAuthStorage, modelsJsonPath: string) => PiModelRegistry;
|
|
new (authStorage: PiAuthStorage, modelsJsonPath: string): PiModelRegistry;
|
|
};
|
|
|
|
function instantiatePiModelRegistry(
|
|
authStorage: PiAuthStorage,
|
|
modelsJsonPath: string,
|
|
): PiModelRegistry {
|
|
const Registry = PiModelRegistryClass as unknown as PiModelRegistryClassLike;
|
|
if (typeof Registry.create === "function") {
|
|
return Registry.create(authStorage, modelsJsonPath);
|
|
}
|
|
return new Registry(authStorage, modelsJsonPath);
|
|
}
|
|
|
|
function createOpenClawModelRegistry(
|
|
authStorage: PiAuthStorage,
|
|
modelsJsonPath: string,
|
|
agentDir: string,
|
|
options?: DiscoverModelsOptions,
|
|
): PiModelRegistry {
|
|
const registry = instantiatePiModelRegistry(authStorage, modelsJsonPath);
|
|
const getAll = registry.getAll.bind(registry);
|
|
const getAvailable = registry.getAvailable.bind(registry);
|
|
const find = registry.find.bind(registry);
|
|
const refresh = registry.refresh.bind(registry);
|
|
const providerFilter = options?.providerFilter ? normalizeProviderId(options.providerFilter) : "";
|
|
const matchesProviderFilter = (entry: Model<Api>) =>
|
|
!providerFilter || normalizeProviderId(entry.provider) === providerFilter;
|
|
const shouldNormalize = options?.normalizeModels !== false;
|
|
const findCache = new Map<string, Model<Api> | undefined>();
|
|
const normalizeEntry = (entry: Model<Api>) =>
|
|
shouldNormalize ? normalizeDiscoveredPiModel(entry, agentDir) : entry;
|
|
|
|
registry.getAll = () => {
|
|
const entries = getAll().filter((entry: Model<Api>) => matchesProviderFilter(entry));
|
|
return shouldNormalize
|
|
? entries.map((entry: Model<Api>) => normalizeDiscoveredPiModel(entry, agentDir))
|
|
: entries;
|
|
};
|
|
registry.getAvailable = () => {
|
|
const entries = getAvailable().filter((entry: Model<Api>) => matchesProviderFilter(entry));
|
|
return shouldNormalize
|
|
? entries.map((entry: Model<Api>) => normalizeDiscoveredPiModel(entry, agentDir))
|
|
: entries;
|
|
};
|
|
registry.find = (provider: string, modelId: string) => {
|
|
const normalizedProvider = normalizeProviderId(provider);
|
|
const key = `${normalizedProvider}\0${modelId}`;
|
|
if (findCache.has(key)) {
|
|
return findCache.get(key);
|
|
}
|
|
const fallbackEntry = find(provider, modelId);
|
|
const resolved = fallbackEntry ? normalizeEntry(fallbackEntry) : undefined;
|
|
findCache.set(key, resolved);
|
|
return resolved;
|
|
};
|
|
registry.refresh = () => {
|
|
findCache.clear();
|
|
return refresh();
|
|
};
|
|
|
|
return registry;
|
|
}
|
|
|
|
function createAuthStorage(AuthStorageLike: unknown, path: string, creds: PiCredentialMap) {
|
|
const withInMemory = AuthStorageLike as { inMemory?: (data?: unknown) => unknown };
|
|
if (typeof withInMemory.inMemory === "function") {
|
|
return withInMemory.inMemory(creds) as PiAuthStorage;
|
|
}
|
|
|
|
const withFromStorage = AuthStorageLike as {
|
|
fromStorage?: (storage: unknown) => unknown;
|
|
};
|
|
if (typeof withFromStorage.fromStorage === "function") {
|
|
const backendCtor = (
|
|
PiCodingAgent as { InMemoryAuthStorageBackend?: new () => InMemoryAuthStorageBackendLike }
|
|
).InMemoryAuthStorageBackend;
|
|
const backend =
|
|
typeof backendCtor === "function"
|
|
? new backendCtor()
|
|
: createInMemoryAuthStorageBackend(creds);
|
|
backend.withLock(() => ({
|
|
result: undefined,
|
|
next: JSON.stringify(creds, null, 2),
|
|
}));
|
|
return withFromStorage.fromStorage(backend) as PiAuthStorage;
|
|
}
|
|
|
|
const withFactory = AuthStorageLike as { create?: (path: string) => unknown };
|
|
const withRuntimeOverride = (
|
|
typeof withFactory.create === "function"
|
|
? withFactory.create(path)
|
|
: new (AuthStorageLike as { new (path: string): unknown })(path)
|
|
) as PiAuthStorage & {
|
|
setRuntimeApiKey?: (provider: string, apiKey: string) => void; // pragma: allowlist secret
|
|
};
|
|
const hasRuntimeApiKeyOverride = typeof withRuntimeOverride.setRuntimeApiKey === "function"; // pragma: allowlist secret
|
|
if (hasRuntimeApiKeyOverride) {
|
|
for (const [provider, credential] of Object.entries(creds)) {
|
|
if (credential.type === "api_key") {
|
|
withRuntimeOverride.setRuntimeApiKey(provider, credential.key);
|
|
continue;
|
|
}
|
|
withRuntimeOverride.setRuntimeApiKey(provider, credential.access);
|
|
}
|
|
}
|
|
return withRuntimeOverride;
|
|
}
|
|
|
|
// Compatibility helpers for pi-coding-agent 0.50+ (discover* helpers removed).
|
|
export function discoverAuthStorage(
|
|
agentDir: string,
|
|
options?: DiscoverAuthStorageOptions,
|
|
): PiAuthStorage {
|
|
const credentials =
|
|
options?.skipCredentials === true ? {} : resolvePiCredentialsForDiscovery(agentDir, options);
|
|
const authPath = path.join(agentDir, "auth.json");
|
|
if (options?.readOnly !== true) {
|
|
scrubLegacyStaticAuthJsonEntriesForDiscovery(authPath);
|
|
}
|
|
return createAuthStorage(PiAuthStorageClass, authPath, credentials);
|
|
}
|
|
|
|
export function discoverModels(
|
|
authStorage: PiAuthStorage,
|
|
agentDir: string,
|
|
options?: DiscoverModelsOptions,
|
|
): PiModelRegistry {
|
|
return createOpenClawModelRegistry(
|
|
authStorage,
|
|
path.join(agentDir, "models.json"),
|
|
agentDir,
|
|
options,
|
|
);
|
|
}
|
|
|
|
export {
|
|
addEnvBackedPiCredentials,
|
|
resolvePiCredentialsForDiscovery,
|
|
scrubLegacyStaticAuthJsonEntriesForDiscovery,
|
|
type DiscoverAuthStorageOptions,
|
|
} from "./pi-auth-discovery.js";
|