Files
openclaw/extensions/deepinfra/doctor-contract-api.ts
Peter Steinberger 8cb53c7b55 perf(doctor): keep bundled doctor contract closures dependency-light (#120698)
* perf(doctor): keep bundled doctor contract closures dependency-light

Doctor contract enumeration cold-loads each plugin's doctor-contract-api
closure via jiti, so a static value import of openclaw/plugin-sdk/runtime-doctor
pulled the state-db/kysely graph (~4.3s per closure) into
listPluginDoctorLegacyConfigRules / listPluginDoctorStateMigrationEntries.

- migrate all light doctor-contract closures (66 files) to the
  dependency-light openclaw/plugin-sdk/runtime-doctor-migrations subpath
- voice-call: load detect/repairOpenClawStateDatabaseSchema* lazily inside
  the migration bodies; keep only a type-only static runtime-doctor import
- matrix: split pure credential record shapes/normalizers into
  credentials-state.ts so the doctor closure no longer imports the sync
  plugin-state store through credentials-read
- guard: doctor-contract-closure-guard.test.ts now forbids static value
  imports of runtime-doctor in closures alongside agent-runtime

* fix(matrix): keep credential revocation record type module-local

Knip production scan flags the export as consumer-less; the type is only
referenced by the exported union and revocation guard signature.
2026-08-08 17:51:31 -07:00

109 lines
4.5 KiB
TypeScript

// DeepInfra doctor contract: migrates legacy video endpoint config to the
// canonical `models.providers.deepinfra.baseUrl`. Runtime reads only the
// canonical key; `openclaw doctor --fix` repairs shipped `nativeBaseUrl` and
// `/v1/inference` values here so no request-time compat remap is needed.
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const PROVIDER_PATH = "models.providers.deepinfra";
const NATIVE_INFERENCE_PATH = "/v1/inference";
const OPENAI_COMPAT_PATH = "/v1/openai";
const CANONICAL_BASE_URL: string = manifest.modelCatalog.providers.deepinfra.baseUrl;
const FIX_HINT = `Run "openclaw doctor --fix" (api.deepinfra.com endpoints migrate automatically; custom hosts must set ${PROVIDER_PATH}.baseUrl to an OpenAI-compatible videos endpoint manually).`;
export const legacyConfigRules = [
{
path: ["models", "providers", "deepinfra", "nativeBaseUrl"],
message: `${PROVIDER_PATH}.nativeBaseUrl is legacy; video generation uses the OpenAI-compatible ${PROVIDER_PATH}.baseUrl. ${FIX_HINT}`,
},
{
path: ["models", "providers", "deepinfra", "baseUrl"],
message: `${PROVIDER_PATH}.baseUrl targets the retired native ${NATIVE_INFERENCE_PATH} surface; use an ${OPENAI_COMPAT_PATH} base. ${FIX_HINT}`,
match: (value: unknown) => typeof value === "string" && value.includes(NATIVE_INFERENCE_PATH),
},
];
// Only DeepInfra's own host provably serves both the native and the OpenAI
// video surfaces; rewriting a custom host's URL cannot make it speak the
// OpenAI videos protocol, so custom endpoints are never auto-converted.
function isDeepInfraApiHost(value: string): boolean {
try {
return new URL(value).host === "api.deepinfra.com";
} catch {
return false;
}
}
function normalizeBaseUrlValue(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim().replace(/\/+$/u, "");
return trimmed ? trimmed : undefined;
}
export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): {
config: OpenClawConfig;
changes: string[];
} {
const models = asObjectRecord(cfg.models);
const providers = asObjectRecord(models?.providers);
const provider = asObjectRecord(providers?.deepinfra);
if (!provider) {
return { config: cfg, changes: [] };
}
const changes: string[] = [];
const next: Record<string, unknown> = { ...provider };
// Change messages never echo configured URL values: a legacy URL may carry
// userinfo or query tokens, and doctor output lands in terminals/CI logs.
if (Object.hasOwn(next, "nativeBaseUrl")) {
const legacyNative = normalizeBaseUrlValue(next.nativeBaseUrl);
delete next.nativeBaseUrl;
if (normalizeBaseUrlValue(next.baseUrl)) {
changes.push(`${PROVIDER_PATH}.nativeBaseUrl: removed (baseUrl is already configured)`);
} else if (legacyNative && !isDeepInfraApiHost(legacyNative)) {
// The native video route is retired, so a custom native endpoint cannot
// be preserved; provider entries require baseUrl, so fall back to the
// canonical endpoint and tell the operator what manual step remains.
next.baseUrl = CANONICAL_BASE_URL;
changes.push(
`${PROVIDER_PATH}.nativeBaseUrl: removed retired custom native endpoint; using ${CANONICAL_BASE_URL} - set ${PROVIDER_PATH}.baseUrl manually if your host serves an OpenAI-compatible videos API`,
);
} else {
const migrated = legacyNative?.includes(NATIVE_INFERENCE_PATH)
? legacyNative.replace(NATIVE_INFERENCE_PATH, OPENAI_COMPAT_PATH)
: CANONICAL_BASE_URL;
next.baseUrl = migrated;
changes.push(
`${PROVIDER_PATH}.nativeBaseUrl -> ${PROVIDER_PATH}.baseUrl (OpenAI-compatible ${OPENAI_COMPAT_PATH} endpoint)`,
);
}
}
if (
typeof next.baseUrl === "string" &&
next.baseUrl.includes(NATIVE_INFERENCE_PATH) &&
isDeepInfraApiHost(next.baseUrl)
) {
next.baseUrl = next.baseUrl.replace(NATIVE_INFERENCE_PATH, OPENAI_COMPAT_PATH);
changes.push(`${PROVIDER_PATH}.baseUrl: ${NATIVE_INFERENCE_PATH} -> ${OPENAI_COMPAT_PATH}`);
}
if (changes.length === 0) {
return { config: cfg, changes: [] };
}
return {
config: {
...cfg,
models: {
...models,
providers: { ...providers, deepinfra: next },
} as unknown as OpenClawConfig["models"],
},
changes,
};
}