mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 20:13:02 +00:00
fix: propagate fable model mapping across runtime paths
This commit is contained in:
1 parent
b27a007229
commit
f7241d0874
5 files changed
+55
-18
No files matched your search
@@ -10,22 +10,16 @@ import {
|
||||
} from '../../cliproxy/ai-providers/model-id-normalizer';
|
||||
import { mapExternalProviderName } from '../../cliproxy/provider-capabilities';
|
||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||
import { ANTHROPIC_MODEL_ENV_KEYS } from '../../shared/extended-context-utils';
|
||||
import type { ProfileValidationIssue, ProfileValidationSummary } from './profile-types';
|
||||
|
||||
const MODEL_ENV_KEYS = [
|
||||
'ANTHROPIC_MODEL',
|
||||
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
||||
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
||||
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
||||
] as const;
|
||||
|
||||
const ALLOWED_ANTHROPIC_ENV_KEYS = new Set<string>([
|
||||
'ANTHROPIC_BASE_URL',
|
||||
'ANTHROPIC_AUTH_TOKEN',
|
||||
'ANTHROPIC_API_KEY',
|
||||
// Written by profile-writer when --extra-models is supplied
|
||||
'ANTHROPIC_EXTRA_MODELS',
|
||||
...MODEL_ENV_KEYS,
|
||||
...ANTHROPIC_MODEL_ENV_KEYS,
|
||||
]);
|
||||
|
||||
function resolveProviderFromBaseUrl(baseUrl: string): CLIProxyProvider | null {
|
||||
@@ -118,7 +112,7 @@ export function validateApiProfileSettingsPayload(settings: unknown): ProfileVal
|
||||
}
|
||||
|
||||
const provider = resolveProviderFromBaseUrl(baseUrl);
|
||||
for (const modelKey of MODEL_ENV_KEYS) {
|
||||
for (const modelKey of ANTHROPIC_MODEL_ENV_KEYS) {
|
||||
const value = envObj[modelKey];
|
||||
if (typeof value !== 'string' || value.trim().length === 0) continue;
|
||||
const denyReason = getDeniedModelIdReasonForProvider(value, provider);
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('buildLaunchSettingsOverlay', () => {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/codex',
|
||||
ANTHROPIC_MODEL: 'gpt-5.5',
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL: 'gpt-5.4-mini',
|
||||
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
|
||||
},
|
||||
});
|
||||
@@ -44,12 +45,14 @@ describe('buildLaunchSettingsOverlay', () => {
|
||||
const { settings, changed } = buildLaunchSettingsOverlay(settingsPath, {
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:50118/api/provider/codex',
|
||||
ANTHROPIC_MODEL: 'gpt-5.5-high',
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL: 'gpt-5.4-mini-medium',
|
||||
} as NodeJS.ProcessEnv);
|
||||
|
||||
expect(changed).toBe(true);
|
||||
const env = settings.env as Record<string, string>;
|
||||
expect(env.ANTHROPIC_BASE_URL).toBe('http://127.0.0.1:50118/api/provider/codex');
|
||||
expect(env.ANTHROPIC_MODEL).toBe('gpt-5.5-high');
|
||||
expect(env.ANTHROPIC_DEFAULT_FABLE_MODEL).toBe('gpt-5.4-mini-medium');
|
||||
// Untouched keys are preserved.
|
||||
expect(env.ANTHROPIC_AUTH_TOKEN).toBe('ccs-internal-managed');
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
|
||||
import SharedManager from '../management/shared-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/config-loader-facade';
|
||||
import { ANTHROPIC_MODEL_ENV_KEYS as SHARED_ANTHROPIC_MODEL_ENV_KEYS } from '../shared/extended-context-utils';
|
||||
|
||||
/**
|
||||
* Strip ANTHROPIC_* env vars from an environment object.
|
||||
@@ -40,17 +41,13 @@ export const ANTHROPIC_ROUTING_ENV_KEYS = [
|
||||
'ANTHROPIC_API_KEY',
|
||||
];
|
||||
const ANTHROPIC_ROUTING_ENV_KEY_SET = new Set(ANTHROPIC_ROUTING_ENV_KEYS);
|
||||
// NOTE: This is the intentional routing-overlay SUPERSET of model env keys
|
||||
// (includes ANTHROPIC_SMALL_FAST_MODEL). A separate 4-key `ANTHROPIC_MODEL_ENV_KEYS`
|
||||
// exists in src/shared/extended-context-utils.ts (re-exported as MODEL_ENV_VAR_KEYS).
|
||||
// The two are NOT interchangeable — import deliberately by purpose. See issue #1609.
|
||||
// NOTE: This is the intentional routing-overlay SUPERSET of shared model env keys
|
||||
// (includes legacy ANTHROPIC_SMALL_FAST_MODEL). Keep the shared list authoritative
|
||||
// for current tier mappings while preserving the legacy runtime alias. See issue #1609.
|
||||
export const ANTHROPIC_MODEL_ENV_KEYS = [
|
||||
'ANTHROPIC_MODEL',
|
||||
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
||||
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
||||
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
||||
...SHARED_ANTHROPIC_MODEL_ENV_KEYS,
|
||||
'ANTHROPIC_SMALL_FAST_MODEL',
|
||||
];
|
||||
] as const;
|
||||
const TMUX_SYNC_ENV_KEYS = [
|
||||
'CLAUDE_CONFIG_DIR',
|
||||
'CCS_PROFILE_TYPE',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
importApiProfileBundle,
|
||||
registerApiProfileOrphans,
|
||||
} from '../../../src/api/services/profile-lifecycle-service';
|
||||
import { validateApiProfileSettingsPayload } from '../../../src/api/services/profile-lifecycle-validation';
|
||||
import { createApiProfile } from '../../../src/api/services/profile-writer';
|
||||
import {
|
||||
loadConfigSafe,
|
||||
@@ -69,6 +70,38 @@ describe('profile lifecycle service', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts ANTHROPIC_DEFAULT_FABLE_MODEL as a supported model mapping', () => {
|
||||
const validation = validateApiProfileSettingsPayload({
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/codex',
|
||||
ANTHROPIC_AUTH_TOKEN: 'token',
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL: 'gpt-5.4-mini',
|
||||
},
|
||||
});
|
||||
|
||||
expect(validation.valid).toBe(true);
|
||||
expect(validation.issues).toEqual([]);
|
||||
});
|
||||
|
||||
it('enforces the provider denylist for ANTHROPIC_DEFAULT_FABLE_MODEL', () => {
|
||||
const validation = validateApiProfileSettingsPayload({
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/agy',
|
||||
ANTHROPIC_AUTH_TOKEN: 'token',
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-sonnet-4.5',
|
||||
},
|
||||
});
|
||||
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
level: 'error',
|
||||
code: 'model_denylisted',
|
||||
field: 'env.ANTHROPIC_DEFAULT_FABLE_MODEL',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('discovers grandfathered xai/grok orphans while skipping other reserved names', async () => {
|
||||
const ccsDir = path.join(tempHome, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
|
||||
@@ -264,6 +264,7 @@ describe('CLAUDECODE environment stripping', () => {
|
||||
delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
||||
delete process.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
||||
delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
||||
delete process.env.ANTHROPIC_DEFAULT_FABLE_MODEL;
|
||||
delete process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
||||
delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS;
|
||||
|
||||
@@ -543,6 +544,7 @@ describe('CLAUDECODE environment stripping', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'parent-api-key';
|
||||
process.env.ANTHROPIC_MODEL = 'gpt-5.4';
|
||||
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = 'gpt-5.4';
|
||||
process.env.ANTHROPIC_DEFAULT_FABLE_MODEL = 'gpt-5.4-mini';
|
||||
|
||||
execClaude('claude', ['--help'], {
|
||||
CCS_PROFILE_TYPE: 'settings',
|
||||
@@ -570,10 +572,18 @@ describe('CLAUDECODE environment stripping', () => {
|
||||
call.args[1] === 'ANTHROPIC_MODEL' &&
|
||||
call.args[2] === 'gpt-5.4'
|
||||
);
|
||||
const fableModelCall = spawnSyncCalls.find(
|
||||
(call) =>
|
||||
call.command === 'tmux' &&
|
||||
call.args[0] === 'setenv' &&
|
||||
call.args[1] === 'ANTHROPIC_DEFAULT_FABLE_MODEL' &&
|
||||
call.args[2] === 'gpt-5.4-mini'
|
||||
);
|
||||
|
||||
expect(unsetBaseUrlCall).toBeDefined();
|
||||
expect(unsetAuthTokenCall).toBeDefined();
|
||||
expect(modelCall).toBeDefined();
|
||||
expect(fableModelCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('headless executor spawn path strips CLAUDECODE before spawn', async () => {
|
||||
|
||||
Reference in new issue
Block a user