Skip to content

Commit 811669b

Browse files
committed
Add console logs to code mode
1 parent 6d28446 commit 811669b

File tree

4 files changed

+151
-37
lines changed

4 files changed

+151
-37
lines changed

packages/code-mode/README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ await client.registerManual({
4646
});
4747

4848
// Now execute TypeScript code that uses the tools
49-
const result = await client.callToolChain(`
49+
const { result, logs } = await client.callToolChain(`
5050
// Call the add tool using hierarchical access
5151
const sum1 = await math_tools.add({ a: 5, b: 3 });
5252
console.log('First sum:', sum1.result);
@@ -64,10 +64,41 @@ const result = await client.callToolChain(`
6464
`);
6565

6666
console.log('Final result:', result); // 18
67+
console.log('Console output:', logs); // ['First sum: 8', 'Second sum: 18', 'Add tool interface: ...']
6768
```
6869

6970
## Advanced Usage
7071

72+
### Console Output Capture
73+
74+
All console output is automatically captured and returned alongside execution results:
75+
76+
```typescript
77+
const { result, logs } = await client.callToolChain(`
78+
console.log('Starting calculation...');
79+
console.error('This will show as [ERROR]');
80+
console.warn('This will show as [WARN]');
81+
82+
const sum1 = await math_tools.add({ a: 5, b: 3 });
83+
console.log('First sum:', sum1.result);
84+
85+
const sum2 = await math_tools.add({ a: sum1.result, b: 10 });
86+
console.log('Final sum:', sum2.result);
87+
88+
return sum2.result;
89+
`);
90+
91+
console.log('Result:', result); // 18
92+
console.log('Captured logs:');
93+
logs.forEach((log, i) => console.log(`${i + 1}: ${log}`));
94+
// Output:
95+
// 1: Starting calculation...
96+
// 2: [ERROR] This will show as [ERROR]
97+
// 3: [WARN] This will show as [WARN]
98+
// 4: First sum: 8
99+
// 5: Final sum: 18
100+
```
101+
71102
### Getting TypeScript Interfaces
72103

73104
You can generate TypeScript interfaces for all your tools to get better IDE support:
@@ -231,13 +262,13 @@ Extends `UtcpClient` with additional code execution capabilities.
231262

232263
#### Methods
233264

234-
##### `callToolChain(code: string, timeout?: number): Promise<any>`
265+
##### `callToolChain(code: string, timeout?: number): Promise<{result: any, logs: string[]}>`
235266

236-
Executes TypeScript code with access to all registered tools.
267+
Executes TypeScript code with access to all registered tools and captures console output.
237268

238269
- **code**: TypeScript code to execute
239270
- **timeout**: Optional timeout in milliseconds (default: 30000)
240-
- **Returns**: The result of the code execution
271+
- **Returns**: Object containing both the execution result and captured console logs (`console.log`, `console.error`, `console.warn`, `console.info`)
241272

242273
##### `toolToTypeScriptInterface(tool: Tool): string`
243274

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.3",
3+
"version": "1.0.4",
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: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ You have access to a CodeModeUtcpClient that allows you to execute TypeScript co
3535
- Use \`await manual.tool({ param: value })\` syntax for all tool calls
3636
- Tools are async functions that return promises
3737
- You have access to standard JavaScript globals: \`console\`, \`JSON\`, \`Math\`, \`Date\`, etc.
38+
- All console output (\`console.log\`, \`console.error\`, etc.) is automatically captured and returned
3839
- Build properly structured input objects based on interface definitions
3940
- Handle errors appropriately with try/catch blocks
4041
- Chain tool calls by using results from previous calls
41-
- return the results you need
42-
- the code you write will be wrapped in an async function and executed in a vm context
4342
4443
### 4. Best Practices
4544
- **Discover first, code second**: Always explore available tools before writing execution code
@@ -152,18 +151,19 @@ ${interfaces.join('\n\n')}`;
152151
}
153152

154153
/**
155-
* Executes TypeScript code with access to all registered tools as functions.
156-
* The code runs in a secure VM context where each tool is available as a TypeScript function.
154+
* Executes TypeScript code with access to registered tools and captures console output.
155+
* The code can call tools directly as functions and has access to standard JavaScript globals.
157156
*
158-
* @param code TypeScript code to execute
157+
* @param code TypeScript code to execute
159158
* @param timeout Optional timeout in milliseconds (default: 30000)
160-
* @returns The result of the code execution
159+
* @returns Object containing both the execution result and captured console logs
161160
*/
162-
public async callToolChain(code: string, timeout: number = 30000): Promise<any> {
161+
public async callToolChain(code: string, timeout: number = 30000): Promise<{result: any, logs: string[]}> {
163162
const tools = await this.getTools();
164163

165-
// Create the execution context with tool functions
166-
const context = await this.createExecutionContext(tools);
164+
// Create the execution context with tool functions and log capture
165+
const logs: string[] = [];
166+
const context = await this.createExecutionContext(tools, logs);
167167

168168
try {
169169
// Create VM context
@@ -178,7 +178,7 @@ ${interfaces.join('\n\n')}`;
178178

179179
// Execute with timeout
180180
const result = await this.runWithTimeout(wrappedCode, vmContext, timeout);
181-
return result;
181+
return { result, logs };
182182
} catch (error) {
183183
throw new Error(`Code execution failed: ${error instanceof Error ? error.message : String(error)}`);
184184
}
@@ -219,16 +219,41 @@ ${interfaces.join('\n\n')}`;
219219
}
220220

221221
/**
222-
* Creates the execution context for the VM with tool functions and type definitions.
223-
* Each tool becomes a callable function in the execution context.
222+
* Creates the execution context for running TypeScript code.
223+
* This context includes tool functions and basic JavaScript globals.
224224
*
225225
* @param tools Array of tools to make available
226+
* @param logs Optional array to capture console.log output
226227
* @returns Execution context object
227228
*/
228-
private async createExecutionContext(tools: Tool[]): Promise<Record<string, any>> {
229+
private async createExecutionContext(tools: Tool[], logs?: string[]): Promise<Record<string, any>> {
230+
// Create console object (either capturing logs or using standard console)
231+
const consoleObj = logs ? {
232+
log: (...args: any[]) => {
233+
logs.push(args.map(arg =>
234+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
235+
).join(' '));
236+
},
237+
error: (...args: any[]) => {
238+
logs.push('[ERROR] ' + args.map(arg =>
239+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
240+
).join(' '));
241+
},
242+
warn: (...args: any[]) => {
243+
logs.push('[WARN] ' + args.map(arg =>
244+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
245+
).join(' '));
246+
},
247+
info: (...args: any[]) => {
248+
logs.push('[INFO] ' + args.map(arg =>
249+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
250+
).join(' '));
251+
}
252+
} : console;
253+
229254
const context: Record<string, any> = {
230255
// Add basic utilities
231-
console,
256+
console: consoleObj,
232257
JSON,
233258
Promise,
234259
Array,

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

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,13 @@ describe('CodeModeUtcpClient', () => {
299299

300300
test('should execute simple code with basic operations', async () => {
301301
const code = `
302-
const x = 10;
303-
const y = 20;
304-
return { sum: x + y, product: x * y };
302+
const x = 5;
303+
const y = 10;
304+
return x + y;
305305
`;
306306

307-
const result = await client.callToolChain(code);
308-
expect(result).toEqual({ sum: 30, product: 200 });
307+
const { result } = await client.callToolChain(code);
308+
expect(result).toBe(15);
309309
});
310310

311311
test('should execute code that calls a simple tool', async () => {
@@ -317,7 +317,7 @@ describe('CodeModeUtcpClient', () => {
317317
return result;
318318
`;
319319

320-
const result = await client.callToolChain(code);
320+
const { result } = await client.callToolChain(code);
321321
expect(result.result).toBe(40);
322322
expect(result.operation).toBe('addition');
323323

@@ -343,7 +343,7 @@ describe('CodeModeUtcpClient', () => {
343343
};
344344
`;
345345

346-
const result = await client.callToolChain(code);
346+
const { result } = await client.callToolChain(code);
347347
expect(result.math.result).toBe(15);
348348
expect(result.greeting.greeting).toBe("Good day, Alice");
349349
expect(result.greeting.isFormal).toBe(true);
@@ -374,7 +374,7 @@ describe('CodeModeUtcpClient', () => {
374374
return result;
375375
`;
376376

377-
const result = await client.callToolChain(code);
377+
const { result } = await client.callToolChain(code);
378378
expect(result.processedData.processed).toBe(true);
379379
expect(result.processedData.users).toBeDefined();
380380
expect(result.metadata.itemCount).toBe(1);
@@ -400,7 +400,7 @@ describe('CodeModeUtcpClient', () => {
400400
};
401401
`;
402402

403-
const result = await client.callToolChain(code);
403+
const { result } = await client.callToolChain(code);
404404
expect(result.statistics.sum).toBe(25);
405405
expect(result.statistics.count).toBe(6);
406406
expect(result.statistics.average).toBe(25/6);
@@ -422,7 +422,7 @@ describe('CodeModeUtcpClient', () => {
422422
};
423423
`;
424424

425-
const result = await client.callToolChain(code);
425+
const { result } = await client.callToolChain(code);
426426
expect(result.timeData.timestamp).toBeDefined();
427427
expect(result.timeData.iso).toBeDefined();
428428
expect(result.isRecent).toBe(true);
@@ -445,7 +445,7 @@ describe('CodeModeUtcpClient', () => {
445445
}
446446
`;
447447

448-
const result = await client.callToolChain(code);
448+
const { result } = await client.callToolChain(code);
449449
expect(result.error).toBe(true);
450450
expect(result.caught).toBe(true);
451451
expect(result.message).toContain("Test error message");
@@ -483,7 +483,7 @@ describe('CodeModeUtcpClient', () => {
483483
};
484484
`;
485485

486-
const result = await client.callToolChain(code);
486+
const { result } = await client.callToolChain(code);
487487
expect(result.mathPi).toBe(Math.PI);
488488
expect(typeof result.dateNow).toBe('number');
489489
expect(result.arrayMethods).toBe(true);
@@ -502,7 +502,7 @@ describe('CodeModeUtcpClient', () => {
502502
};
503503
`;
504504

505-
const result = await client.callToolChain(code);
505+
const { result } = await client.callToolChain(code);
506506
expect(result.hasInterfaces).toBe(true);
507507
expect(result.interfacesContainNamespace).toBe(true);
508508
expect(result.canGetSpecificInterface).toBe(true);
@@ -558,11 +558,11 @@ describe('CodeModeUtcpClient', () => {
558558
const result = await client.callToolChain(code, 15000);
559559

560560
// Verify the chain worked correctly
561-
expect(result.steps.arrayProcessing.sum).toBe(50);
562-
expect(result.steps.addition.result).toBe(150);
563-
expect(result.steps.greeting.greeting).toBe("Hey CodeMode!");
564-
expect(result.steps.finalProcessing.processedData.processed).toBe(true);
565-
expect(result.summary.chainCompleted).toBe(true);
561+
expect(result.result.steps.arrayProcessing.sum).toBe(50);
562+
expect(result.result.steps.addition.result).toBe(150);
563+
expect(result.result.steps.greeting.greeting).toBe("Hey CodeMode!");
564+
expect(result.result.steps.finalProcessing.processedData.processed).toBe(true);
565+
expect(result.result.summary.chainCompleted).toBe(true);
566566

567567
// Verify all tools were called in the correct order
568568
expect(testResults.sumArrayCalled).toBeDefined();
@@ -576,6 +576,64 @@ describe('CodeModeUtcpClient', () => {
576576
expect(testResults.greetCalled.name).toBe("CodeMode");
577577
expect(testResults.greetCalled.formal).toBe(false);
578578
});
579+
580+
test('should provide agent prompt template', () => {
581+
const promptTemplate = CodeModeUtcpClient.AGENT_PROMPT_TEMPLATE;
582+
583+
expect(typeof promptTemplate).toBe('string');
584+
expect(promptTemplate.length).toBeGreaterThan(0);
585+
expect(promptTemplate).toContain('Tool Discovery Phase');
586+
expect(promptTemplate).toContain('Interface Introspection');
587+
expect(promptTemplate).toContain('Code Execution Guidelines');
588+
expect(promptTemplate).toContain('await manual.tool');
589+
expect(promptTemplate).toContain('__interfaces');
590+
expect(promptTemplate).toContain('__getToolInterface');
591+
expect(promptTemplate).toContain('Discover first, code second');
592+
});
593+
594+
test('should capture console.log output with callToolChain', async () => {
595+
const code = `
596+
console.log('First log message');
597+
console.log('Number:', 42);
598+
console.log('Object:', { name: 'test', value: 123 });
599+
600+
const addResult = await test_tools.add({ a: 10, b: 20 });
601+
console.log('Addition result:', addResult);
602+
603+
return addResult.result;
604+
`;
605+
606+
const { result, logs } = await client.callToolChain(code);
607+
608+
expect(result).toBe(30);
609+
expect(logs).toHaveLength(4);
610+
expect(logs[0]).toBe('First log message');
611+
expect(logs[1]).toBe('Number: 42');
612+
expect(logs[2]).toContain('"name": "test"');
613+
expect(logs[2]).toContain('"value": 123');
614+
expect(logs[3]).toContain('Addition result:');
615+
expect(logs[3]).toContain('"result": 30');
616+
});
617+
618+
test('should capture console error and warn with callToolChain', async () => {
619+
const code = `
620+
console.log('Regular log');
621+
console.error('This is an error');
622+
console.warn('This is a warning');
623+
console.info('This is info');
624+
625+
return 'done';
626+
`;
627+
628+
const { result, logs } = await client.callToolChain(code);
629+
630+
expect(result).toBe('done');
631+
expect(logs).toHaveLength(4);
632+
expect(logs[0]).toBe('Regular log');
633+
expect(logs[1]).toBe('[ERROR] This is an error');
634+
expect(logs[2]).toBe('[WARN] This is a warning');
635+
expect(logs[3]).toBe('[INFO] This is info');
636+
});
579637
});
580638

581639
// Export for potential manual testing

0 commit comments

Comments
 (0)