Files
openclaw/extensions/longcat/doctor-contract-api.ts
ffyuuu efe5d8d76e fix(longcat): correct cache pricing and add provider icon (#121369)
* longcat: align baseUrl with docs, fix cacheWrite pricing, add brand icon

- Point baseUrl at the documented https://api.longcat.chat/openai/v1 path
  instead of the undocumented unversioned alias route.
- Fix cacheWrite pricing to 0: the LongCat pricing page has no separate
  cache-write charge (matches deepseek/moonshot/zai catalog conventions).
- Update the pricing page link to the current docs path.
- Add the official LongCat brand icon (provenance recorded in
  ATTRIBUTION.md) and display-name mapping so the Control UI shows proper
  branding instead of the letter-badge fallback.
- Migrate the persisted legacy default baseUrl via a plugin-owned doctor
  configRepair contract: onboarding persists
  models.providers.longcat.baseUrl and the runtime reads the stored value,
  so the contract rewrites exactly the former default to /openai/v1,
  preserves custom endpoints, and warns via a legacy-config rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(longcat): correct provider metadata

* fix(longcat): migrate persisted stock pricing

* style(longcat): format doctor repair

* fix(longcat): preserve doctor migration after compat cleanup

* fix(longcat): avoid map spread in doctor repair

---------

Co-authored-by: yuzehui02 <yuzehui02@meituan.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-10 20:15:18 -07:00

105 lines
3.6 KiB
TypeScript

// LongCat doctor contract repairs the historical stock model price persisted
// by onboarding. Match the complete stock row so operator-customized models
// and prices are never rewritten when the vendor catalog changes.
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations";
const MODELS_PATH = ["models", "providers", "longcat", "models"];
const LEGACY_CACHE_WRITE_PRICE = 0.75;
function isStringArray(value: unknown, expected: readonly string[]): boolean {
return (
Array.isArray(value) &&
value.length === expected.length &&
value.every((entry, index) => entry === expected[index])
);
}
function isLegacyStockLongCatModel(value: unknown): boolean {
const model = asObjectRecord(value);
const cost = asObjectRecord(model?.cost);
const compatValue = model?.compat;
const compat = asObjectRecord(compatValue);
// Core Doctor removes catalog-owned compat fields before plugin repairs run.
// Accept that normalized shape, but preserve rows with any divergent compat override.
const hasHistoricalCompat =
compatValue === undefined ||
Boolean(
compat &&
compat.supportsStore === false &&
compat.supportsDeveloperRole === false &&
compat.supportsReasoningEffort === false &&
compat.supportsUsageInStreaming === false &&
compat.supportsStrictMode === false &&
compat.maxTokensField === "max_tokens" &&
compat.requiresReasoningContentOnAssistantMessages === true &&
compat.thinkingFormat === "deepseek" &&
Object.keys(compat).length === 8,
);
return Boolean(
model &&
model.id === "LongCat-2.0" &&
model.name === "LongCat 2.0" &&
model.reasoning === true &&
isStringArray(model.input, ["text"]) &&
model.contextWindow === 1_048_576 &&
model.maxTokens === 131_072 &&
cost?.input === 0.75 &&
cost.output === 2.95 &&
cost.cacheRead === 0.015 &&
cost.cacheWrite === LEGACY_CACHE_WRITE_PRICE &&
hasHistoricalCompat,
);
}
function hasLegacyStockLongCatModel(value: unknown): boolean {
return Array.isArray(value) && value.some(isLegacyStockLongCatModel);
}
export const legacyConfigRules = [
{
path: MODELS_PATH,
message:
'models.providers.longcat.models contains the historical stock LongCat-2.0 cache-write price; run "openclaw doctor --fix" to update it without changing customized rows.',
match: hasLegacyStockLongCatModel,
},
];
export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): {
config: OpenClawConfig;
changes: string[];
} {
const models = asObjectRecord(cfg.models);
const providers = asObjectRecord(models?.providers);
const provider = asObjectRecord(providers?.longcat);
const configuredModels = provider?.models;
if (!hasLegacyStockLongCatModel(configuredModels) || !Array.isArray(configuredModels)) {
return { config: cfg, changes: [] };
}
const nextModels = configuredModels.map((model) => {
if (!isLegacyStockLongCatModel(model)) {
return model;
}
const row = asObjectRecord(model) ?? {};
const cost = asObjectRecord(row.cost) ?? {};
return Object.assign({}, row, {
cost: Object.assign({}, cost, { cacheWrite: 0 }),
});
});
return {
config: {
...cfg,
models: {
...models,
providers: {
...providers,
longcat: { ...provider, models: nextModels },
},
} as unknown as OpenClawConfig["models"],
},
changes: ["Updated the historical stock LongCat-2.0 cache-write price from $0.75 to $0."],
};
}