mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
8028a3a5b2
* feat(anthropic): gate live model discovery on contract coverage Live catalog discovery cloned a template for any newly discovered Claude id, so a future model generation would be selectable while request shaping treated it as pre-4.6 and 400d. Add an opt-in acceptUnknownModel gate to the shared live catalog seam and have the Anthropic plugin accept a discovered model only when Anthropic's advertised capabilities agree with the contracts we would apply. Manifest-published ids bypass the gate and keep their metadata. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(plugins): document the live-discovery admission hook Record acceptUnknownModel in the provider-plugin SDK reference: when to use it, that manifest-published ids bypass it, and the fail-closed guidance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
/**
|
|
* Contract gate for live-discovered Anthropic models the manifest does not
|
|
* publish. Claude request shaping is selected by model-version predicates, so a
|
|
* model those predicates do not recognize is shaped like a pre-4.6 model:
|
|
* manual `budget_tokens` thinking plus caller sampling parameters. Current
|
|
* Claude models reject both, so surfacing an unrecognized model would offer a
|
|
* selectable entry whose every request 400s.
|
|
*
|
|
* Rather than hand-maintaining a family allowlist that rots on each launch,
|
|
* accept a discovered model only when Anthropic's advertised capabilities agree
|
|
* with what our contracts would apply. Disagreement means our shaping is stale
|
|
* for that model, so it stays hidden until the contracts are updated.
|
|
*/
|
|
import {
|
|
supportsClaudeAdaptiveThinking,
|
|
supportsClaudeNativeMaxEffort,
|
|
supportsClaudeNativeXhighEffort,
|
|
} from "openclaw/plugin-sdk/provider-model-shared";
|
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
|
|
function readCapabilityFlag(root: unknown, path: readonly string[]): boolean | undefined {
|
|
let current: unknown = root;
|
|
for (const key of path) {
|
|
if (!isRecord(current)) {
|
|
return undefined;
|
|
}
|
|
current = current[key];
|
|
}
|
|
return typeof current === "boolean" ? current : undefined;
|
|
}
|
|
|
|
/**
|
|
* Anthropic publishes a capability tree on `/v1/models`. Each entry pairs a
|
|
* capability we can read from that tree with the contract predicate that must
|
|
* agree, so a mismatch on any axis rejects the model.
|
|
*/
|
|
const CLAUDE_CONTRACT_CAPABILITY_CHECKS = [
|
|
{
|
|
path: ["thinking", "types", "adaptive", "supported"],
|
|
matches: supportsClaudeAdaptiveThinking,
|
|
},
|
|
{ path: ["effort", "xhigh", "supported"], matches: supportsClaudeNativeXhighEffort },
|
|
{ path: ["effort", "max", "supported"], matches: supportsClaudeNativeMaxEffort },
|
|
] as const;
|
|
|
|
/**
|
|
* Return whether a live-discovered Claude model can be shaped by the current
|
|
* contracts. Fails closed: a model without a readable capability tree is
|
|
* rejected, because we cannot prove our shaping matches it.
|
|
*/
|
|
export function acceptsAnthropicLiveModelContract(params: {
|
|
id: string;
|
|
record: Record<string, unknown>;
|
|
}): boolean {
|
|
const capabilities = params.record.capabilities;
|
|
if (!isRecord(capabilities)) {
|
|
return false;
|
|
}
|
|
const ref = { id: params.id };
|
|
return CLAUDE_CONTRACT_CAPABILITY_CHECKS.every((check) => {
|
|
const advertised = readCapabilityFlag(capabilities, check.path);
|
|
return advertised !== undefined && advertised === check.matches(ref);
|
|
});
|
|
}
|