mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
fix(cliproxy): handle all invalid prefixes and reverse-order collisions
forceValidChars() now ensures leading char is letter or underscore (catches dot, hyphen, colon, slash — not just digits). registerTools() always registers in mapping so collisions are detected regardless of tool registration order.
This commit is contained in:
@@ -300,5 +300,36 @@ describe('Tool Name Mapper', () => {
|
||||
assert.ok(collisions[0].originals.includes('x__y__y'));
|
||||
assert.ok(collisions[0].originals.includes('x__y'));
|
||||
});
|
||||
|
||||
it('detects collision when unchanged tool appears before sanitized one', () => {
|
||||
const mapper = new ToolNameMapper();
|
||||
const tools = [
|
||||
{ name: 'abc__def' }, // unchanged, but registered in mapping
|
||||
{ name: 'abc__def__def' }, // sanitizes to 'abc__def', collides
|
||||
];
|
||||
|
||||
const result = mapper.registerTools(tools);
|
||||
|
||||
assert.strictEqual(result[0].name, 'abc__def');
|
||||
assert.strictEqual(result[1].name, 'abc__def_2');
|
||||
assert.strictEqual(mapper.hasCollisions(), true);
|
||||
});
|
||||
|
||||
it('restores correctly for reverse-order collision', () => {
|
||||
const mapper = new ToolNameMapper();
|
||||
const tools = [{ name: 'abc__def' }, { name: 'abc__def__def' }];
|
||||
|
||||
mapper.registerTools(tools);
|
||||
|
||||
const content = [
|
||||
{ type: 'tool_use', id: '1', name: 'abc__def', input: {} },
|
||||
{ type: 'tool_use', id: '2', name: 'abc__def_2', input: {} },
|
||||
];
|
||||
|
||||
const restored = mapper.restoreToolUse(content);
|
||||
|
||||
assert.strictEqual(restored[0].name, 'abc__def');
|
||||
assert.strictEqual(restored[1].name, 'abc__def__def');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -203,5 +203,29 @@ describe('Tool Name Sanitizer', () => {
|
||||
assert.ok(isValidToolName(result.sanitized), `Expected valid name, got: ${result.sanitized}`);
|
||||
assert.ok(result.sanitized.length <= 64);
|
||||
});
|
||||
|
||||
it('forces valid prefix for dot-starting name', () => {
|
||||
const result = sanitizeToolName('.tool');
|
||||
|
||||
assert.ok(result.sanitized.startsWith('_'), `Expected _ prefix, got: ${result.sanitized}`);
|
||||
assert.ok(isValidToolName(result.sanitized));
|
||||
assert.strictEqual(result.changed, true);
|
||||
});
|
||||
|
||||
it('forces valid prefix for hyphen-starting name', () => {
|
||||
const result = sanitizeToolName('-tool');
|
||||
|
||||
assert.ok(result.sanitized.startsWith('_'), `Expected _ prefix, got: ${result.sanitized}`);
|
||||
assert.ok(isValidToolName(result.sanitized));
|
||||
assert.strictEqual(result.changed, true);
|
||||
});
|
||||
|
||||
it('forces valid prefix for colon-starting name', () => {
|
||||
const result = sanitizeToolName(':tool');
|
||||
|
||||
assert.ok(result.sanitized.startsWith('_'), `Expected _ prefix, got: ${result.sanitized}`);
|
||||
assert.ok(isValidToolName(result.sanitized));
|
||||
assert.strictEqual(result.changed, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,9 +107,12 @@ export class ToolNameMapper {
|
||||
finalName = disambiguated;
|
||||
}
|
||||
|
||||
// Store mapping if name changed or was disambiguated
|
||||
// Always register in mapping so later tools can detect collisions
|
||||
// even when this tool's name was unchanged
|
||||
this.mapping.set(finalName, tool.name);
|
||||
|
||||
// Record change only if name actually changed
|
||||
if (finalName !== tool.name) {
|
||||
this.mapping.set(finalName, tool.name);
|
||||
this.changes.push({
|
||||
original: tool.name,
|
||||
sanitized: finalName,
|
||||
|
||||
@@ -103,7 +103,7 @@ export function smartTruncate(name: string, maxLen: number = GEMINI_MAX_TOOL_NAM
|
||||
/**
|
||||
* Force a string to comply with Gemini tool name character rules.
|
||||
* Replaces invalid characters with underscores and ensures the name
|
||||
* starts with a letter or underscore.
|
||||
* starts with a letter or underscore (not a digit, dot, colon, etc.).
|
||||
*/
|
||||
function forceValidChars(name: string): string {
|
||||
// Replace any character not in [a-zA-Z0-9_.:/-] with underscore
|
||||
@@ -112,8 +112,8 @@ function forceValidChars(name: string): string {
|
||||
// Collapse multiple consecutive underscores from replacement
|
||||
fixed = fixed.replace(/_+/g, '_');
|
||||
|
||||
// Ensure starts with letter or underscore
|
||||
if (fixed.length > 0 && /^[0-9]/.test(fixed)) {
|
||||
// Ensure starts with letter or underscore (not digit, dot, colon, hyphen, slash)
|
||||
if (fixed.length > 0 && !/^[a-zA-Z_]/.test(fixed)) {
|
||||
fixed = '_' + fixed;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user