mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
fix(cliproxy): tighten session affinity review issues
This commit is contained in:
@@ -173,12 +173,18 @@ export async function handleRoutingAffinityStatus(): Promise<void> {
|
||||
export async function handleRoutingAffinitySet(args: string[]): Promise<void> {
|
||||
const requested = normalizeCliproxySessionAffinityEnabled(args[0]);
|
||||
const extractedTtl = extractOption(args.slice(1), ['--ttl']);
|
||||
const remainingArgs = extractedTtl.remainingArgs.filter((token) => token.trim().length > 0);
|
||||
const ttl: string | undefined =
|
||||
extractedTtl.found && !extractedTtl.missingValue
|
||||
? (normalizeCliproxySessionAffinityTtl(extractedTtl.value) ?? undefined)
|
||||
: undefined;
|
||||
|
||||
if (requested === null || extractedTtl.missingValue || (extractedTtl.found && !ttl)) {
|
||||
if (
|
||||
requested === null ||
|
||||
extractedTtl.missingValue ||
|
||||
(extractedTtl.found && !ttl) ||
|
||||
remainingArgs.length > 0
|
||||
) {
|
||||
await initUI();
|
||||
console.log('');
|
||||
console.log(
|
||||
|
||||
@@ -168,11 +168,25 @@ function getSuggestionsForCommand(tokensBeforeCurrent: string[]): CompletionSugg
|
||||
'-h',
|
||||
]);
|
||||
if (subcommand === 'routing') {
|
||||
const routingSubcommand = tokensBeforeCurrent[2];
|
||||
const routingAffinityMode = tokensBeforeCurrent[3];
|
||||
if (lastToken === 'set') {
|
||||
return completeSubcommands(['round-robin', 'fill-first']);
|
||||
}
|
||||
if (lastToken === 'affinity') {
|
||||
return completeSubcommands(['on', 'off', '--ttl']);
|
||||
if (routingSubcommand === 'affinity') {
|
||||
if (!routingAffinityMode || lastToken === 'affinity') {
|
||||
return completeSubcommands(['on', 'off']);
|
||||
}
|
||||
if (
|
||||
(routingAffinityMode === 'on' || routingAffinityMode === 'off') &&
|
||||
!tokensBeforeCurrent.includes('--ttl')
|
||||
) {
|
||||
return completeSubcommands([], ['--ttl']);
|
||||
}
|
||||
if (lastToken === '--ttl') {
|
||||
return [];
|
||||
}
|
||||
return completeSubcommands([]);
|
||||
}
|
||||
return completeSubcommands(['set', 'explain', 'affinity']);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ const CONFIG_YAML = 'config.yaml';
|
||||
const CONFIG_JSON = 'config.json';
|
||||
const CONFIG_LOCK = 'config.yaml.lock';
|
||||
const LOCK_STALE_MS = 5000; // Lock is stale after 5 seconds
|
||||
const GO_DURATION_SEGMENT = String.raw`(?:\d+(?:\.\d+)?(?:ns|us|µs|μs|ms|s|m|h))`;
|
||||
const GO_DURATION_PATTERN = new RegExp(`^${GO_DURATION_SEGMENT}+$`);
|
||||
|
||||
function normalizeBrowserDevtoolsPort(value: number | undefined): number {
|
||||
if (!Number.isFinite(value)) {
|
||||
@@ -112,6 +114,19 @@ function canonicalizeBrowserConfig(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSessionAffinityTtl(value: unknown, fallback: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || !GO_DURATION_PATTERN.test(trimmed)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to unified config.yaml
|
||||
*/
|
||||
@@ -444,11 +459,10 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
||||
typeof partial.cliproxy?.routing?.session_affinity === 'boolean'
|
||||
? partial.cliproxy.routing.session_affinity
|
||||
: defaults.cliproxy.routing?.session_affinity,
|
||||
session_affinity_ttl:
|
||||
typeof partial.cliproxy?.routing?.session_affinity_ttl === 'string' &&
|
||||
partial.cliproxy.routing.session_affinity_ttl.trim()
|
||||
? partial.cliproxy.routing.session_affinity_ttl.trim()
|
||||
: defaults.cliproxy.routing?.session_affinity_ttl,
|
||||
session_affinity_ttl: normalizeSessionAffinityTtl(
|
||||
partial.cliproxy?.routing?.session_affinity_ttl,
|
||||
defaults.cliproxy.routing?.session_affinity_ttl ?? '1h'
|
||||
),
|
||||
},
|
||||
},
|
||||
proxy: {
|
||||
|
||||
@@ -128,6 +128,22 @@ describe('completion backend', () => {
|
||||
expect(values).toContain('my-codex');
|
||||
});
|
||||
|
||||
test('suggests the correct routing affinity completion shape', () => {
|
||||
expect(suggestionValues(['cliproxy', 'routing'])).toEqual(
|
||||
expect.arrayContaining(['set', 'explain', 'affinity'])
|
||||
);
|
||||
expect(suggestionValues(['cliproxy', 'routing', 'affinity'])).toEqual(
|
||||
expect.arrayContaining(['on', 'off'])
|
||||
);
|
||||
expect(suggestionValues(['cliproxy', 'routing', 'affinity', 'on'])).toEqual(
|
||||
expect.arrayContaining(['--ttl'])
|
||||
);
|
||||
expect(suggestionValues(['cliproxy', 'routing', 'affinity', 'off'])).toEqual(
|
||||
expect.arrayContaining(['--ttl'])
|
||||
);
|
||||
expect(suggestionValues(['cliproxy', 'routing', 'affinity', '--ttl'])).not.toContain('--ttl');
|
||||
});
|
||||
|
||||
test('suggests env format values after the format flag', () => {
|
||||
const values = suggestionValues(['env', '--format']);
|
||||
expect(values).toEqual(
|
||||
|
||||
@@ -285,6 +285,41 @@ describe('continuity-inheritance-config', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cliproxy session-affinity ttl normalization', () => {
|
||||
it('normalizes invalid stored ttl values back to the safe default', () => {
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-affinity-ttl-home-'));
|
||||
const ccsDir = path.join(tempHome, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.yaml'),
|
||||
[
|
||||
'version: 8',
|
||||
'cliproxy:',
|
||||
' routing:',
|
||||
' strategy: round-robin',
|
||||
' session_affinity: true',
|
||||
' session_affinity_ttl: forever',
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
process.env.CCS_HOME = tempHome;
|
||||
try {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
expect(config.cliproxy.routing.session_affinity_ttl).toBe('1h');
|
||||
} finally {
|
||||
if (originalCcsHome === undefined) {
|
||||
delete process.env.CCS_HOME;
|
||||
} else {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
}
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('official-channels-config', () => {
|
||||
it('keeps explicit channels.selected empty even when legacy discord_channels.enabled is true', () => {
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
|
||||
@@ -90,6 +90,7 @@ export function useUpdateCliproxySessionAffinity() {
|
||||
mutationFn: (data: { enabled: boolean; ttl?: string }) =>
|
||||
api.cliproxy.updateSessionAffinity(data),
|
||||
onSuccess: (result: CliproxySessionAffinityApplyResult) => {
|
||||
queryClient.setQueryData(['cliproxy-session-affinity'], result);
|
||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-session-affinity'] });
|
||||
const label = result.enabled ? 'enabled' : 'disabled';
|
||||
toast.success(result.message || `Session affinity ${label}.`);
|
||||
|
||||
Reference in New Issue
Block a user