Skip to content

Commit 0bc0b02

Browse files
committed
add url constructor
1 parent c08a534 commit 0bc0b02

File tree

7 files changed

+250
-69
lines changed

7 files changed

+250
-69
lines changed

contesto/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## 2025-08-15 18:43
4+
5+
- Convert tool names to camelCase when injecting into isolated environment and generating TypeScript types
6+
- Updated tests to use inline snapshots for tool descriptions and real-world examples
7+
- Added camelcase package for consistent naming convention
8+
9+
## 2025-08-15 18:40
10+
11+
- Updated `createInterpreterTool` to use Zod v4's built-in `toJSONSchema` function instead of custom implementation
12+
- Fixed TypeScript type generation using json-schema-to-typescript-lite for tool input schemas
13+
- Fixed async function signature by making `createInterpreterTool` return a Promise
14+
315
## 2025-08-10 12:00
416

517
- Added `onExecute` callback to `createRenderFormTool` configuration

contesto/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@types/js-yaml": "^4.0.9",
4646
"@uiw/react-color": "^2.6.0",
4747
"ai": "5.0.14",
48+
"camelcase": "^8.0.0",
4849
"class-variance-authority": "^0.7.1",
4950
"clsx": "^2.1.1",
5051
"cookie": "^1.0.2",
@@ -57,6 +58,7 @@
5758
"js-yaml": "^4.1.0",
5859
"json-schema": "^0.4.0",
5960
"json-schema-library": "^10.1.2",
61+
"json-schema-to-typescript-lite": "^15.0.0",
6062
"lucide-react": "^0.525.0",
6163
"match-sorter": "^8.0.3",
6264
"mdast": "^3.0.0",

contesto/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export * from './lib/schema-path-utils.js'
2424
export * from './lib/zod.js'
2525
export * from './components/edit-preview.js'
2626

27-
// Interpreter tool
28-
export * from './lib/interpreter-tool.js'
27+
2928

3029
export { jsxDedent }

contesto/src/lib/ai-cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function createAiCacheMiddleware({
6464
},
6565
}
6666
}
67-
console.log(`skipping cache for ai request with model ${model.modelId}`)
67+
console.log(`not using cache for ai request with model ${model.modelId}`)
6868

6969
const result = await doGenerate()
7070

@@ -112,7 +112,7 @@ export function createAiCacheMiddleware({
112112
rawCall: { rawPrompt: null, rawSettings: {} },
113113
}
114114
}
115-
console.log(`skipping cache for ai request with model ${model.modelId}`)
115+
console.log(`not using cache for ai request with model ${model.modelId}`)
116116

117117
// If not cached, proceed with streaming
118118
const { stream, ...rest } = await doStream()

contesto/src/lib/interpreter-tool.test.ts

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { z } from 'zod'
55

66
describe('createInterpreterTool', () => {
77
test('executes simple code and captures console.log', async () => {
8-
const tool = createInterpreterTool()
8+
const tool = await createInterpreterTool()
99

1010
const startTime = Date.now()
1111
const result = await tool.execute!({
@@ -34,7 +34,7 @@ describe('createInterpreterTool', () => {
3434
})
3535

3636
test('handles errors gracefully', async () => {
37-
const tool = createInterpreterTool()
37+
const tool = await createInterpreterTool()
3838

3939
const result = await tool.execute!({
4040
title: 'Error test',
@@ -51,7 +51,7 @@ describe('createInterpreterTool', () => {
5151
})
5252

5353
test('handles JSON objects in console.log', async () => {
54-
const tool = createInterpreterTool()
54+
const tool = await createInterpreterTool()
5555

5656
const result = await tool.execute!({
5757
title: 'JSON logging',
@@ -79,7 +79,7 @@ describe('createInterpreterTool', () => {
7979
})
8080

8181
test('enforces timeout', async () => {
82-
const tool = createInterpreterTool()
82+
const tool = await createInterpreterTool()
8383

8484
const result = await tool.execute!({
8585
title: 'Timeout test',
@@ -95,7 +95,7 @@ describe('createInterpreterTool', () => {
9595
})
9696

9797
test('handles async code', async () => {
98-
const tool = createInterpreterTool()
98+
const tool = await createInterpreterTool()
9999

100100
const result = await tool.execute!({
101101
title: 'Async test',
@@ -117,7 +117,7 @@ describe('createInterpreterTool', () => {
117117
})
118118

119119
test('respects custom timeout', async () => {
120-
const tool = createInterpreterTool()
120+
const tool = await createInterpreterTool()
121121

122122
const startTime = Date.now()
123123
const result = await tool.execute!({
@@ -138,7 +138,7 @@ describe('createInterpreterTool', () => {
138138
})
139139

140140
test('returns "no console logs" when no output', async () => {
141-
const tool = createInterpreterTool()
141+
const tool = await createInterpreterTool()
142142

143143
const result = await tool.execute!({
144144
title: 'No output test',
@@ -154,7 +154,7 @@ describe('createInterpreterTool', () => {
154154
})
155155

156156
test('shows stack trace for errors thrown in functions', async () => {
157-
const tool = createInterpreterTool()
157+
const tool = await createInterpreterTool()
158158

159159
const result = await tool.execute!({
160160
title: 'Error with stack trace',
@@ -182,10 +182,10 @@ describe('createInterpreterTool', () => {
182182
Error: Something went wrong in doSomething
183183
Stack trace:
184184
Error: Something went wrong in doSomething
185-
at doSomething (<isolated-vm>:33:27)
186-
at main (<isolated-vm>:38:21)
187-
at <isolated-vm>:42:17
188-
at <isolated-vm>:44:23"
185+
at doSomething (<isolated-vm>:44:27)
186+
at main (<isolated-vm>:49:21)
187+
at <isolated-vm>:53:17
188+
at <isolated-vm>:55:23"
189189
`)
190190
})
191191

@@ -211,7 +211,7 @@ describe('createInterpreterTool', () => {
211211
}
212212
})
213213

214-
const interpreterTool = createInterpreterTool({
214+
const interpreterTool = await createInterpreterTool({
215215
tools: {
216216
add: addTool,
217217
greet: greetTool,
@@ -253,7 +253,7 @@ describe('createInterpreterTool', () => {
253253
execute: async ({ x, y }) => x * y
254254
})
255255

256-
const interpreterTool = createInterpreterTool({
256+
const interpreterTool = await createInterpreterTool({
257257
tools: {
258258
multiply: mathTool,
259259
}
@@ -274,7 +274,7 @@ describe('createInterpreterTool', () => {
274274
})
275275

276276
test('supports various console methods', async () => {
277-
const tool = createInterpreterTool()
277+
const tool = await createInterpreterTool()
278278

279279
const result = await tool.execute!({
280280
title: 'Console methods test',
@@ -317,7 +317,7 @@ describe('createInterpreterTool', () => {
317317
}),
318318
})
319319

320-
const interpreterTool = createInterpreterTool({
320+
const interpreterTool = await createInterpreterTool({
321321
tools: {
322322
schemaOnly: schemaTool,
323323
}
@@ -337,6 +337,84 @@ describe('createInterpreterTool', () => {
337337
`)
338338
})
339339

340+
test('includes available tools in description with TypeScript types', async () => {
341+
const mockFetch = tool({
342+
description: 'Fetch data',
343+
inputSchema: z.object({ url: z.string() }),
344+
execute: async ({ url }) => `Data from ${url}`
345+
})
346+
347+
const mockEditor = tool({
348+
description: 'Edit files',
349+
inputSchema: z.object({
350+
path: z.string(),
351+
content: z.string().optional()
352+
}),
353+
execute: async ({ path, content }) => `Edited ${path}`
354+
})
355+
356+
const schemaOnly = tool({
357+
description: 'Schema only',
358+
inputSchema: z.object({ value: z.string() }),
359+
outputSchema: z.object({ result: z.string() })
360+
})
361+
362+
const interpreterTool = await createInterpreterTool({
363+
tools: {
364+
'fetch-data': mockFetch,
365+
'edit_file': mockEditor,
366+
'schema-only': schemaOnly
367+
}
368+
})
369+
370+
expect(interpreterTool.description).toMatchInlineSnapshot(`
371+
"Execute JavaScript code in an isolated sandbox environment with console.log capture
372+
373+
Available tools object type:
374+
375+
interface Tools {
376+
fetchData: (args: {
377+
url: string
378+
}) => Promise<any>;
379+
editFile: (args: {
380+
path: string
381+
content?: string
382+
}) => Promise<any>;
383+
}
384+
"
385+
`)
386+
})
387+
388+
test('supports URL constructor', async () => {
389+
const tool = await createInterpreterTool()
390+
391+
const result = await tool.execute!({
392+
title: 'URL test',
393+
code: `
394+
const url = new URL('https://example.com/path?query=value#hash')
395+
console.log('URL object:', JSON.stringify(url))
396+
console.log('Protocol:', url.protocol)
397+
console.log('Hostname:', url.hostname)
398+
console.log('Pathname:', url.pathname)
399+
console.log('Search:', url.search)
400+
console.log('Hash:', url.hash)
401+
402+
const relative = new URL('/api/users', 'https://api.example.com')
403+
console.log('Full URL:', relative.href)
404+
`
405+
}, {} as any) as string
406+
407+
expect(result).toMatchInlineSnapshot(`
408+
"URL object: {"href":"https://example.com/path?query=value#hash","protocol":"https:","hostname":"example.com","host":"example.com","port":"","pathname":"/path","search":"?query=value","searchParams":{"query":"value"},"hash":"#hash","origin":"https://example.com","username":"","password":""}
409+
Protocol: https:
410+
Hostname: example.com
411+
Pathname: /path
412+
Search: ?query=value
413+
Hash: #hash
414+
Full URL: https://api.example.com/api/users"
415+
`)
416+
})
417+
340418
test('real-world example with fetch and editor tools', async () => {
341419
const files: Record<string, string> = {}
342420

@@ -395,10 +473,10 @@ describe('createInterpreterTool', () => {
395473
}
396474
})
397475

398-
const interpreterTool = createInterpreterTool({
476+
const interpreterTool = await createInterpreterTool({
399477
tools: {
400-
fetch: fetchTool,
401-
editor: strReplaceEditor
478+
'fetch-tool': fetchTool,
479+
'editor_tool': strReplaceEditor
402480
}
403481
})
404482

@@ -416,7 +494,7 @@ describe('createInterpreterTool', () => {
416494
417495
const results = await Promise.all(
418496
urls.map(async url => {
419-
const content = await tools.fetch({ url })
497+
const content = await tools.fetchTool({ url })
420498
const path = url.split('//')[1].split('/').slice(1).join('/')
421499
return { path, content }
422500
})
@@ -426,7 +504,7 @@ describe('createInterpreterTool', () => {
426504
427505
const writeResults = await Promise.all(
428506
results.map(({ path, content }) =>
429-
tools.editor({
507+
tools.editorTool({
430508
command: 'create',
431509
path: path,
432510
file_text: content
@@ -436,7 +514,7 @@ describe('createInterpreterTool', () => {
436514
437515
console.log('Created', writeResults.length, 'files')
438516
439-
const userFile = await tools.editor({
517+
const userFile = await tools.editorTool({
440518
command: 'view',
441519
path: 'users/1.md'
442520
})
@@ -445,10 +523,15 @@ describe('createInterpreterTool', () => {
445523
`
446524
}, {} as any) as string
447525

448-
expect(result).toMatch(/Fetching 4 URLs/)
449-
expect(result).toMatch(/Fetched all data/)
450-
expect(result).toMatch(/Created 4 files/)
451-
expect(result).toMatch(/User 1 file: # User 1/)
526+
expect(result).toMatchInlineSnapshot(`
527+
"Fetching 4 URLs...
528+
Fetched all data
529+
Created 4 files
530+
User 1 file: # User 1
531+
532+
- ID: 1
533+
- ..."
534+
`)
452535

453536
expect(files['users/1.md']).toContain('# User 1')
454537
expect(files['users/2.md']).toContain('# User 2')

0 commit comments

Comments
 (0)