mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(coding-agent): allow configuring Anthropic fallback models
Allow models.json overrides to replace or disable server-side fallback metadata. Fixes #9294
This commit is contained in:
parent
60e7e76bd7
commit
b03a367a4f
4 changed files with 77 additions and 19 deletions
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
- Added `ctx.modelRegistry.stream()` and `streamSimple()` for extension model calls through configured providers with resolved authentication ([#8964](https://github.com/earendil-works/pi/issues/8964)).
|
||||
- Added per-model `reserveTokens` and `keepRecentTokens` settings through `compaction.modelOverrides`, with ordinary compaction settings as fallback ([#8133](https://github.com/earendil-works/pi-mono/issues/8133)).
|
||||
- Added `compat.allowedFallbackModels` configuration for overriding or disabling Anthropic server-side fallback models ([#9294](https://github.com/earendil-works/pi/issues/9294)).
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -435,6 +435,7 @@ Built-in Anthropic models enable `supportsStrictTools` in their model metadata.
|
|||
| `supportsMidConvoEffort` | Whether the exact Claude model transport supports per-turn effort system messages and thinking binding controls. Pi persists native effort levels and always sends `drop_block` when enabled. Default: `false`. |
|
||||
| `allowEmptySignature` | Whether to replay empty thinking signatures as `signature: ""` instead of converting thinking to text. Default: `false`. |
|
||||
| `supportsStrictTools` | Whether the provider accepts strict JSON-schema tool definitions. Default: `false`; built-in Anthropic models enable it in generated metadata. |
|
||||
| `allowedFallbackModels` | Up to three server-side fallback models, each with `provider`, `model`, and complete `cost` metadata. An empty array disables fallback. |
|
||||
|
||||
## OpenAI Compatibility
|
||||
|
||||
|
|
|
|||
|
|
@ -125,25 +125,6 @@ const OpenAIResponsesCompatSchema = Type.Object({
|
|||
supportsMaxOutputTokens: Type.Optional(Type.Boolean()),
|
||||
});
|
||||
|
||||
const AnthropicMessagesCompatSchema = Type.Object({
|
||||
supportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),
|
||||
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
||||
sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
|
||||
supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
|
||||
supportsTemperature: Type.Optional(Type.Boolean()),
|
||||
forceAdaptiveThinking: Type.Optional(Type.Boolean()),
|
||||
allowEmptySignature: Type.Optional(Type.Boolean()),
|
||||
supportsStrictTools: Type.Optional(Type.Boolean()),
|
||||
supportsMidConvoEffort: Type.Optional(Type.Boolean()),
|
||||
supportsToolReferences: Type.Optional(Type.Boolean()),
|
||||
});
|
||||
|
||||
const ProviderCompatSchema = Type.Union([
|
||||
OpenAICompletionsCompatSchema,
|
||||
OpenAIResponsesCompatSchema,
|
||||
AnthropicMessagesCompatSchema,
|
||||
]);
|
||||
|
||||
const ModelCostRatesSchema = {
|
||||
input: Type.Number(),
|
||||
output: Type.Number(),
|
||||
|
|
@ -159,6 +140,35 @@ const ModelCostSchema = Type.Object({
|
|||
tiers: Type.Optional(Type.Array(ModelCostTierSchema)),
|
||||
});
|
||||
|
||||
const AnthropicMessagesCompatSchema = Type.Object({
|
||||
supportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),
|
||||
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
||||
sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
|
||||
supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
|
||||
supportsTemperature: Type.Optional(Type.Boolean()),
|
||||
forceAdaptiveThinking: Type.Optional(Type.Boolean()),
|
||||
allowEmptySignature: Type.Optional(Type.Boolean()),
|
||||
supportsStrictTools: Type.Optional(Type.Boolean()),
|
||||
supportsMidConvoEffort: Type.Optional(Type.Boolean()),
|
||||
supportsToolReferences: Type.Optional(Type.Boolean()),
|
||||
allowedFallbackModels: Type.Optional(
|
||||
Type.Array(
|
||||
Type.Object({
|
||||
provider: Type.String({ minLength: 1 }),
|
||||
model: Type.String({ minLength: 1 }),
|
||||
cost: ModelCostSchema,
|
||||
}),
|
||||
{ maxItems: 3 },
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
const ProviderCompatSchema = Type.Union([
|
||||
OpenAICompletionsCompatSchema,
|
||||
OpenAIResponsesCompatSchema,
|
||||
AnthropicMessagesCompatSchema,
|
||||
]);
|
||||
|
||||
const ModelDefinitionSchema = Type.Object({
|
||||
id: Type.String({ minLength: 1 }),
|
||||
name: Type.Optional(Type.String({ minLength: 1 })),
|
||||
|
|
|
|||
|
|
@ -714,6 +714,52 @@ describe("ModelRegistry", () => {
|
|||
expect(opus?.name).not.toBe("Custom Sonnet Name");
|
||||
});
|
||||
|
||||
test("Anthropic model override replaces allowed fallback metadata", async () => {
|
||||
const allowedFallbackModels = [
|
||||
{
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-5",
|
||||
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
},
|
||||
{
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-8",
|
||||
cost: { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 },
|
||||
},
|
||||
];
|
||||
writeRawModelsJson({
|
||||
anthropic: {
|
||||
modelOverrides: {
|
||||
"claude-fable-5": {
|
||||
compat: { allowedFallbackModels },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const registry = await createModelRegistry(authStorage, modelsJsonPath);
|
||||
const compat = registry.find("anthropic", "claude-fable-5")?.compat as AnthropicMessagesCompat | undefined;
|
||||
|
||||
expect(registry.getError()).toBeUndefined();
|
||||
expect(compat?.allowedFallbackModels).toEqual(allowedFallbackModels);
|
||||
});
|
||||
|
||||
test("empty allowed fallback model override disables server-side fallback", async () => {
|
||||
writeRawModelsJson({
|
||||
anthropic: {
|
||||
modelOverrides: {
|
||||
"claude-fable-5": { compat: { allowedFallbackModels: [] } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const registry = await createModelRegistry(authStorage, modelsJsonPath);
|
||||
const compat = registry.find("anthropic", "claude-fable-5")?.compat as AnthropicMessagesCompat | undefined;
|
||||
|
||||
expect(registry.getError()).toBeUndefined();
|
||||
expect(compat?.allowedFallbackModels).toEqual([]);
|
||||
});
|
||||
|
||||
test("custom model and model override carry sampling params", async () => {
|
||||
writeRawModelsJson({
|
||||
openrouter: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue