mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat(plugins): match shared models across vendor-namespaced catalog ids (#115210)
Aggregators republish first-party models under a namespaced id and varying case, so the shared-model check could not group novita/moonshotai/kimi-k3 with moonshot/kimi-k3. Matching now ignores one leading namespace segment and case, which catches that whole class automatically instead of needing an upstreamModel marker per row. Declares the tier on the 18 rows this surfaces across baseten, deepinfra, gmi, novita, nvidia, and together. All are "capable", matching what the runtime already applied for an absent flag, so behavior is unchanged.
This commit is contained in:
committed by
GitHub
parent
c3fba2bb20
commit
695db84cd1
@@ -1014,7 +1014,7 @@ Suppression fields:
|
||||
| `when.baseUrlHosts` | `string[]` | Optional list of effective provider base URL hosts required before the suppression applies. |
|
||||
| `when.providerConfigApiIn` | `string[]` | Optional list of exact provider-config `api` values required before the suppression applies. |
|
||||
|
||||
`upstreamModel` marks a row that serves the same upstream model as a row in another bundled catalog under a different id, for example a subscription endpoint next to the vendor's API endpoint. It is authoring metadata: normalization drops it, and a contract test uses it to keep capability flags such as `compat.codeMode` from drifting between catalogs that ship the same model. Rows that already share the same model id need no marker. See [Code mode](/tools/code-mode#models-shipped-by-more-than-one-provider).
|
||||
`upstreamModel` marks a row that serves the same upstream model as a row in another bundled catalog under a different name, for example a subscription endpoint next to the vendor's API endpoint. It is authoring metadata: normalization drops it, and a contract test uses it to keep capability flags such as `compat.codeMode` from drifting between catalogs that ship the same model. Most rows need no marker, because matching ignores a leading vendor namespace and casing: `moonshotai/kimi-k3` and `zai-org/GLM-5.2` already match the first-party `kimi-k3` and `glm-5.2` rows. Reach for `upstreamModel` only when the vendor's own names genuinely differ. See [Code mode](/tools/code-mode#models-shipped-by-more-than-one-provider).
|
||||
|
||||
Do not put runtime-only data in `modelCatalog`. Use `static` only when manifest rows are complete enough for provider-filtered list and picker surfaces to skip registry/runtime discovery. Use `refreshable` when manifest rows are useful listable seeds or supplements but a refresh/cache can add more rows later; refreshable rows are not authoritative by themselves. Use `runtime` when OpenClaw must load provider runtime to know the list.
|
||||
|
||||
|
||||
+15
-9
@@ -318,15 +318,21 @@ model. Because `"auto"` resolves the tier from whichever catalog served the run,
|
||||
two catalogs describing the same upstream model must not disagree by accident.
|
||||
|
||||
Every catalog row for a shared model therefore states its tier explicitly once
|
||||
any sibling row states one. When the shared model is listed under different ids,
|
||||
the non-canonical row points at the canonical one with `upstreamModel`, as the
|
||||
`kimi` catalog does for `moonshot/kimi-k3`. Reseller catalogs such as
|
||||
`github-copilot`, `opencode`, `opencode-go`, `ollama-cloud`, `qianfan`,
|
||||
`venice`, and `volcengine-plan` currently declare `"capable"` for the models
|
||||
first-party catalogs flag `"preferred"`: the preferred tier came from
|
||||
evaluations on the first-party endpoints, and those runs have not been repeated
|
||||
per reseller. Promoting one of those rows is a deliberate, evidence-backed
|
||||
change rather than an oversight.
|
||||
any sibling row states one. Rows are matched on the vendor's own name for the
|
||||
weights, so a catalog that republishes a model under a namespaced id or
|
||||
different casing is matched automatically: `novita/moonshotai/kimi-k3`,
|
||||
`nvidia/z-ai/glm-5.2`, and `together/deepseek-ai/DeepSeek-V4-Pro` all group with
|
||||
the first-party rows without anyone declaring anything. Only genuinely different
|
||||
names need the manifest's `upstreamModel` marker, as the `kimi` catalog uses for
|
||||
`moonshot/kimi-k3`.
|
||||
|
||||
Reseller and aggregator catalogs such as `baseten`, `deepinfra`,
|
||||
`github-copilot`, `gmi`, `novita`, `nvidia`, `ollama-cloud`, `opencode`,
|
||||
`opencode-go`, `qianfan`, `together`, `venice`, and `volcengine-plan` currently
|
||||
declare `"capable"` for the models first-party catalogs flag `"preferred"`: the
|
||||
preferred tier came from evaluations on the first-party endpoints, and those
|
||||
runs have not been repeated per reseller. Promoting one of those rows is a
|
||||
deliberate, evidence-backed change rather than an oversight.
|
||||
|
||||
For OpenAI models, the flag matters only when the run resolves to the OpenClaw
|
||||
embedded agent runtime. Default OpenAI routing uses the Codex-style harness
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
"output": 3.48,
|
||||
"cacheRead": 0.145,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72,6 +75,9 @@
|
||||
"output": 4.4,
|
||||
"cacheRead": 0.14,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true
|
||||
"supportsUsageInStreaming": true,
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -65,7 +66,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true
|
||||
"supportsUsageInStreaming": true,
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -82,7 +84,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true
|
||||
"supportsUsageInStreaming": true,
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -205,7 +208,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true
|
||||
"supportsUsageInStreaming": true,
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
"output": 1.357,
|
||||
"cacheRead": 0.056,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -161,6 +164,9 @@
|
||||
"output": 10,
|
||||
"cacheRead": 0.2,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -191,6 +197,9 @@
|
||||
"output": 30,
|
||||
"cacheRead": 0.5,
|
||||
"cacheWrite": 6.25
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -85,6 +85,9 @@
|
||||
"output": 15,
|
||||
"cacheRead": 0.3,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -127,6 +130,9 @@
|
||||
"output": 4.4,
|
||||
"cacheRead": 0.26,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -141,6 +147,9 @@
|
||||
"output": 3.2,
|
||||
"cacheRead": 0.135,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -155,6 +164,9 @@
|
||||
"output": 0.28,
|
||||
"cacheRead": 0.028,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -86,7 +86,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
"requiresStringContent": true,
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -145,7 +146,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
"requiresStringContent": true,
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -206,7 +208,8 @@
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
"requiresStringContent": true,
|
||||
"codeMode": "capable"
|
||||
},
|
||||
"status": "deprecated",
|
||||
"statusReason": "Still available by exact reference; use z-ai/glm-5.2 for new NVIDIA setups.",
|
||||
|
||||
@@ -85,6 +85,9 @@
|
||||
"output": 3.48,
|
||||
"cacheRead": 0.2,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -99,6 +102,9 @@
|
||||
"output": 4.4,
|
||||
"cacheRead": 0.26,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"codeMode": "capable"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7,15 +7,17 @@ import { listGitTrackedFiles } from "../../test-utils/repo-files.js";
|
||||
const repoRoot = path.resolve(import.meta.dirname, "../../..");
|
||||
const MANIFEST_BASENAME = "openclaw.plugin.json";
|
||||
const CODE_MODE_TIER_LITERAL = /codeMode:\s*"(?:preferred|capable)"/;
|
||||
// Catalogs still built in plugin source instead of `modelCatalog` manifest rows.
|
||||
// The shared-model checks below read manifests, so these two stay invisible to
|
||||
// them; moving their rows into the manifest closes the gap.
|
||||
// Catalogs still built in plugin source instead of `modelCatalog` manifest rows,
|
||||
// so the manifest scan below cannot see their tiers. Moving them is not free:
|
||||
// `google` rows would newly feed model visibility and pre-discovery thinking
|
||||
// metadata through `loadManifestModelCatalog`, and `minimax` resolves cost per
|
||||
// provider surface, so its rows cannot live in one manifest catalog.
|
||||
const UNCONVERTED_SOURCE_CATALOG_PLUGINS = ["google", "minimax"];
|
||||
|
||||
type CatalogEntry = {
|
||||
/** `provider/model` ref used in failure output. */
|
||||
ref: string;
|
||||
/** Shared-model group: the declared upstream model id, else the row's own id. */
|
||||
/** Shared-model group: the normalized upstream model id, else the row's own. */
|
||||
groupKey: string;
|
||||
/** Declared upstream ref, present only on non-canonical rows. */
|
||||
upstreamModel?: string;
|
||||
@@ -43,6 +45,18 @@ function readBundledManifests(): Array<Record<string, unknown>> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a catalog model id to the vendor's own name for the weights.
|
||||
* Aggregators republish first-party models under a namespaced id and varying
|
||||
* case (`moonshotai/kimi-k3`, `zai-org/GLM-5.2`), so dropping one leading
|
||||
* namespace segment groups those with the first-party row automatically instead
|
||||
* of waiting for someone to declare `upstreamModel` on each one.
|
||||
*/
|
||||
function normalizeSharedModelId(modelId: string): string {
|
||||
const separator = modelId.indexOf("/");
|
||||
return (separator === -1 ? modelId : modelId.slice(separator + 1)).toLowerCase();
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
@@ -76,7 +90,9 @@ function collectCatalogEntries(): CatalogEntry[] {
|
||||
const codeMode = readRecord(model?.compat)?.codeMode;
|
||||
entries.push({
|
||||
ref: `${providerId}/${id}`,
|
||||
groupKey: upstreamModel ? upstreamModel.slice(upstreamModel.indexOf("/") + 1) : id,
|
||||
groupKey: normalizeSharedModelId(
|
||||
upstreamModel ? upstreamModel.slice(upstreamModel.indexOf("/") + 1) : id,
|
||||
),
|
||||
...(upstreamModel ? { upstreamModel } : {}),
|
||||
...(typeof codeMode === "string" ? { codeMode } : {}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user