Skip to content

Commit 2fcef66

Browse files
committed
Update code mode
1 parent 53cea4a commit 2fcef66

File tree

3 files changed

+37
-15
lines changed

3 files changed

+37
-15
lines changed

packages/code-mode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@utcp/code-mode",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"description": "Code execution mode for UTCP - enables executing TypeScript code chains with tool access",
55
"main": "dist/index.cjs",
66
"module": "dist/index.js",

packages/code-mode/src/code_mode_utcp_client.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ Remember: Always discover and understand available tools before attempting to us
8181
return codeModeClient;
8282
}
8383

84+
/**
85+
* Sanitizes an identifier to be a valid TypeScript identifier.
86+
* Replaces any non-alphanumeric character (except underscore) with underscore
87+
* and ensures the first character is not a number.
88+
*
89+
* @param name The name to sanitize
90+
* @returns Sanitized identifier
91+
*/
92+
private sanitizeIdentifier(name: string): string {
93+
return name
94+
.replace(/[^a-zA-Z0-9_]/g, '_')
95+
.replace(/^[0-9]/, '_$&');
96+
}
97+
8498
/**
8599
* Converts a Tool object into a TypeScript function interface string.
86100
* This generates the function signature that can be used in TypeScript code.
@@ -99,15 +113,16 @@ Remember: Always discover and understand available tools before attempting to us
99113

100114
if (tool.name.includes('.')) {
101115
const [manualName, ...toolParts] = tool.name.split('.');
102-
const toolName = toolParts.join('_');
103-
accessPattern = `${manualName}.${toolName}`;
116+
const sanitizedManualName = this.sanitizeIdentifier(manualName);
117+
const toolName = toolParts.map(part => this.sanitizeIdentifier(part)).join('_');
118+
accessPattern = `${sanitizedManualName}.${toolName}`;
104119

105120
// Generate interfaces within namespace
106121
const inputInterfaceContent = this.jsonSchemaToObjectContent(tool.inputs);
107122
const outputInterfaceContent = this.jsonSchemaToObjectContent(tool.outputs);
108123

109124
interfaceContent = `
110-
namespace ${manualName} {
125+
namespace ${sanitizedManualName} {
111126
interface ${toolName}Input {
112127
${inputInterfaceContent}
113128
}
@@ -118,9 +133,10 @@ ${outputInterfaceContent}
118133
}`;
119134
} else {
120135
// No manual namespace, generate flat interfaces
121-
accessPattern = tool.name;
122-
const inputType = this.jsonSchemaToTypeScript(tool.inputs, `${tool.name}Input`);
123-
const outputType = this.jsonSchemaToTypeScript(tool.outputs, `${tool.name}Output`);
136+
const sanitizedToolName = this.sanitizeIdentifier(tool.name);
137+
accessPattern = sanitizedToolName;
138+
const inputType = this.jsonSchemaToTypeScript(tool.inputs, `${sanitizedToolName}Input`);
139+
const outputType = this.jsonSchemaToTypeScript(tool.outputs, `${sanitizedToolName}Output`);
124140
interfaceContent = `${inputType}\n\n${outputType}`;
125141
}
126142
const interfaceString = `
@@ -180,7 +196,7 @@ ${interfaces.join('\n\n')}`;
180196
const result = await this.runWithTimeout(wrappedCode, vmContext, timeout);
181197
return { result, logs };
182198
} catch (error) {
183-
throw new Error(`Code execution failed: ${error instanceof Error ? error.message : String(error)}`);
199+
return { result: null, logs: [...logs, `[ERROR] Code execution failed: ${error instanceof Error ? error.message : String(error)}`] };
184200
}
185201
}
186202

@@ -276,15 +292,16 @@ ${interfaces.join('\n\n')}`;
276292
for (const tool of tools) {
277293
if (tool.name.includes('.')) {
278294
const [manualName, ...toolParts] = tool.name.split('.');
279-
const toolName = toolParts.join('_'); // Join remaining parts with underscore
295+
const sanitizedManualName = this.sanitizeIdentifier(manualName);
296+
const toolName = toolParts.map(part => this.sanitizeIdentifier(part)).join('_');
280297

281298
// Create manual namespace object if it doesn't exist
282-
if (!context[manualName]) {
283-
context[manualName] = {};
299+
if (!context[sanitizedManualName]) {
300+
context[sanitizedManualName] = {};
284301
}
285302

286303
// Add the tool function to the manual namespace
287-
context[manualName][toolName] = async (args: Record<string, any>) => {
304+
context[sanitizedManualName][toolName] = async (args: Record<string, any>) => {
288305
try {
289306
return await this.callTool(tool.name, args);
290307
} catch (error) {
@@ -293,7 +310,8 @@ ${interfaces.join('\n\n')}`;
293310
};
294311
} else {
295312
// If no dot, add directly to root context (no manual name)
296-
context[tool.name] = async (args: Record<string, any>) => {
313+
const sanitizedToolName = this.sanitizeIdentifier(tool.name);
314+
context[sanitizedToolName] = async (args: Record<string, any>) => {
297315
try {
298316
return await this.callTool(tool.name, args);
299317
} catch (error) {

packages/code-mode/tests/code_mode_utcp_client.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,9 @@ describe('CodeModeUtcpClient', () => {
460460
return { completed: true };
461461
`;
462462

463-
await expect(client.callToolChain(code, 1000)).rejects.toThrow();
463+
const result = await client.callToolChain(code, 1000);
464+
expect(result.result).toBeNull();
465+
expect(result.logs.some(log => log.includes('Code execution failed'))).toBe(true);
464466
});
465467

466468
test('should handle code syntax errors', async () => {
@@ -469,7 +471,9 @@ describe('CodeModeUtcpClient', () => {
469471
return result;
470472
`;
471473

472-
await expect(client.callToolChain(invalidCode)).rejects.toThrow();
474+
const result = await client.callToolChain(invalidCode);
475+
expect(result.result).toBeNull();
476+
expect(result.logs.some(log => log.includes('Code execution failed'))).toBe(true);
473477
});
474478

475479
test('should have access to basic JavaScript globals', async () => {

0 commit comments

Comments
 (0)