Repository navigation
Add @kody/codemode-utils and rename generated UI utils alias #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
20efa02
4de9420
bd52ce1
92be1cb
c8d945f
f18bec1
9535fe7
249b568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,11 @@ | ||
| import { DynamicWorkerExecutor, type ExecuteResult } from '@cloudflare/codemode' | ||
| import { | ||
| type ExecuteResult, | ||
| type ResolvedProvider, | ||
| ToolDispatcher, | ||
| normalizeCode, | ||
| sanitizeToolName, | ||
| } from '@cloudflare/codemode' | ||
| import * as acorn from 'acorn' | ||
| import { exports as workerExports } from 'cloudflare:workers' | ||
| type WorkerLoopbackExports = Exclude<typeof workerExports, undefined> | ||
| import { type FetchGatewayProps } from '#mcp/fetch-gateway.ts' | ||
|
|
@@ -10,6 +17,10 @@ import { | |
| parseHostApprovalRequiredMessage, | ||
| parseMissingSecretMessage, | ||
| } from '#mcp/secrets/errors.ts' | ||
| import { | ||
| buildCodemodeUtilsModuleSource, | ||
| codemodeUtilsModuleSpecifier, | ||
| } from '#mcp/tools/codemode-utils.ts' | ||
|
|
||
| const charsPerToken = 4 | ||
| const maxTokens = 6_000 | ||
|
|
@@ -26,13 +37,231 @@ export function createExecuteExecutor(input: { | |
| 'CodemodeFetchGateway export is required for execute-time fetch.', | ||
| ) | ||
| } | ||
| return new DynamicWorkerExecutor({ | ||
| loader: input.env.LOADER, | ||
| timeout: 90_000, | ||
| globalOutbound: loopbackExports.CodemodeFetchGateway({ | ||
| props: input.gatewayProps, | ||
| }), | ||
| }) | ||
| return { | ||
| execute(code: string, providersOrFns: Array<ResolvedProvider>) { | ||
| return executeWithWorkerLoader({ | ||
| loader: input.env.LOADER, | ||
| timeoutMs: 90_000, | ||
| code, | ||
| providers: providersOrFns, | ||
| globalOutbound: loopbackExports.CodemodeFetchGateway({ | ||
| props: input.gatewayProps, | ||
| }), | ||
| }) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| function buildProviderProxySource(provider: ResolvedProvider) { | ||
| if (provider.positionalArgs) { | ||
| return `new Proxy({}, { | ||
| get: (_, toolName) => async (...args) => { | ||
| const resJson = await __dispatchers.${provider.name}.call(String(toolName), JSON.stringify(args)); | ||
| const data = JSON.parse(resJson); | ||
| if (data.error) throw new Error(data.error); | ||
| return data.result; | ||
| } | ||
| })` | ||
| } | ||
| return `new Proxy({}, { | ||
| get: (_, toolName) => async (args) => { | ||
| const resJson = await __dispatchers.${provider.name}.call(String(toolName), JSON.stringify(args ?? {})); | ||
| const data = JSON.parse(resJson); | ||
| if (data.error) throw new Error(data.error); | ||
| return data.result; | ||
| } | ||
| })` | ||
| } | ||
|
|
||
| function buildUserEntryModuleSource(code: string) { | ||
| const trimmed = code.trim() | ||
| const looksLikeModule = | ||
| /^\s*import\s/m.test(trimmed) || /^\s*export\s/m.test(trimmed) | ||
| if (looksLikeModule) { | ||
| return wrapImportedExecuteCode(trimmed) | ||
| } | ||
| return `export default ${normalizeCode(code)};` | ||
| } | ||
|
|
||
| function wrapImportedExecuteCode(source: string) { | ||
| try { | ||
| const parsed = acorn.parse(source, { | ||
| ecmaVersion: 'latest', | ||
| sourceType: 'module', | ||
| allowReturnOutsideFunction: true, | ||
| }) | ||
| if ( | ||
| parsed.body.some((node) => node.type === 'ExportDefaultDeclaration') || | ||
| parsed.body.some( | ||
| (node) => | ||
| node.type === 'ExportNamedDeclaration' || | ||
| node.type === 'ExportAllDeclaration', | ||
| ) | ||
| ) { | ||
| return source | ||
| } | ||
| const importNodes = parsed.body.filter( | ||
| (node) => node.type === 'ImportDeclaration', | ||
| ) | ||
| const bodyNodes = parsed.body.filter( | ||
| (node) => node.type !== 'ImportDeclaration', | ||
| ) | ||
| const imports = importNodes | ||
| .map((node) => source.slice(node.start, node.end)) | ||
| .join('\n') | ||
| const body = bodyNodes | ||
| .map((node, index) => { | ||
| if ( | ||
| index === bodyNodes.length - 1 && | ||
| node.type === 'ExpressionStatement' && | ||
| !node.directive | ||
| ) { | ||
| return `return ${source.slice(node.start, node.end)}` | ||
| } | ||
| return source.slice(node.start, node.end) | ||
| }) | ||
| .join('\n') | ||
| return [ | ||
| imports, | ||
| imports.length > 0 ? '' : undefined, | ||
| 'export default async () => {', | ||
| body, | ||
| '}', | ||
| ] | ||
| .filter((part) => typeof part === 'string') | ||
| .join('\n') | ||
| } catch { | ||
| return source | ||
| } | ||
| } | ||
|
|
||
| function buildUserCodeRunnerModuleSource() { | ||
| return [ | ||
| 'import * as __kodyUserModule from "user-entry.js";', | ||
| '', | ||
| 'export async function __kodyRunUserCode() {', | ||
| ' const __kodyExecuteFunction = __kodyUserModule.default;', | ||
| ' if (typeof __kodyExecuteFunction !== "function") {', | ||
| ' throw new Error("Execute code must evaluate to an async function.");', | ||
| ' }', | ||
| ' return await __kodyExecuteFunction();', | ||
| '}', | ||
| ].join('\n') | ||
| } | ||
|
|
||
| async function executeWithWorkerLoader(input: { | ||
| loader: WorkerLoader | ||
| timeoutMs: number | ||
| code: string | ||
| providers: Array<ResolvedProvider> | ||
| globalOutbound: Fetcher | ||
| }): Promise<ExecuteResult> { | ||
| const reservedNames = new Set(['__dispatchers', '__logs', '__providers']) | ||
| const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/ | ||
| const seenNames = new Set<string>() | ||
| for (const provider of input.providers) { | ||
| if (reservedNames.has(provider.name)) { | ||
| return { | ||
| result: undefined, | ||
| error: `Provider name "${provider.name}" is reserved`, | ||
| } | ||
| } | ||
| if (!validIdentifier.test(provider.name)) { | ||
| return { | ||
| result: undefined, | ||
| error: `Provider name "${provider.name}" is not a valid JavaScript identifier`, | ||
| } | ||
| } | ||
| if (seenNames.has(provider.name)) { | ||
| return { | ||
| result: undefined, | ||
| error: `Duplicate provider name "${provider.name}"`, | ||
| } | ||
| } | ||
| seenNames.add(provider.name) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "1) Current provider-name validation and dynamic map writes:"
sed -n '159,227p' packages/worker/src/mcp/executor.ts
echo
echo "2) Trace provider name usage/definition points:"
rg -n -C3 --type=ts '\bResolvedProvider\b|provider\.name|name:\s*string'Repository: kentcdodds/kody Length of output: 50372 🏁 Script executed: # Verify the exact locations and check for other plain object usages
grep -n "Object.create\|const.*=.*{}" packages/worker/src/mcp/executor.ts | head -20Repository: kentcdodds/kody Length of output: 273 Harden provider-name handling against prototype-key collisions. Provider names like Proposed fix- const reservedNames = new Set(['__dispatchers', '__logs', '__providers'])
+ const reservedNames = new Set([
+ '__dispatchers',
+ '__logs',
+ '__providers',
+ '__proto__',
+ 'prototype',
+ 'constructor',
+ ])
@@
- ' const __providers = {};',
+ ' const __providers = Object.create(null);',
@@
- const dispatchers = {} as Record<string, ToolDispatcher>
+ const dispatchers: Record<string, ToolDispatcher> = Object.create(null)Also applies to: 194-199, 215-223 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| const executorModuleSource = [ | ||
| 'import { WorkerEntrypoint } from "cloudflare:workers";', | ||
| 'import { __kodyRunUserCode } from "user-code.js";', | ||
| '', | ||
| 'export default class CodeExecutor extends WorkerEntrypoint {', | ||
| ' async evaluate(__dispatchers = {}) {', | ||
| ' const __logs = [];', | ||
| ' console.log = (...a) => { __logs.push(a.map(String).join(" ")); };', | ||
| ' console.warn = (...a) => { __logs.push("[warn] " + a.map(String).join(" ")); };', | ||
| ' console.error = (...a) => { __logs.push("[error] " + a.map(String).join(" ")); };', | ||
| ' const __providers = {};', | ||
| ...input.providers.flatMap((provider) => [ | ||
| ` const ${provider.name} = ${buildProviderProxySource(provider)};`, | ||
| ` __providers.${provider.name} = ${provider.name};`, | ||
| ]), | ||
| ' globalThis.__kodyProviders = __providers;', | ||
|
cursor[bot] marked this conversation as resolved.
Resolved
|
||
| ' let __timeoutId;', | ||
| ' const __timeoutPromise = new Promise((_, reject) => {', | ||
| ` __timeoutId = setTimeout(() => reject(new Error("Execution timed out")), ${input.timeoutMs});`, | ||
| ' });', | ||
| ' try {', | ||
| ' const result = await Promise.race([', | ||
| ' __kodyRunUserCode(),', | ||
| ' __timeoutPromise,', | ||
| ' ]);', | ||
|
cursor[bot] marked this conversation as resolved.
Resolved
|
||
| ' return { result, logs: __logs };', | ||
| ' } catch (err) {', | ||
| ' return { result: undefined, error: err instanceof Error ? err.message : String(err), logs: __logs };', | ||
| ' } finally {', | ||
| ' if (__timeoutId !== undefined) {', | ||
| ' clearTimeout(__timeoutId);', | ||
| ' }', | ||
| ' delete globalThis.__kodyProviders;', | ||
| ' }', | ||
| ' }', | ||
| '}', | ||
| ].join('\n') | ||
|
|
||
| const dispatchers = {} as Record<string, ToolDispatcher> | ||
| for (const provider of input.providers) { | ||
| const sanitizedFns = Object.fromEntries( | ||
| Object.entries(provider.fns).map(([name, fn]) => [ | ||
| sanitizeToolName(name), | ||
| fn, | ||
| ]), | ||
| ) | ||
| dispatchers[provider.name] = new ToolDispatcher( | ||
| sanitizedFns, | ||
| provider.positionalArgs, | ||
| ) | ||
| } | ||
|
|
||
| const entrypoint = input.loader | ||
| .get(`codemode-${crypto.randomUUID()}`, () => ({ | ||
| compatibilityDate: '2025-06-01', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if there are other compatibility dates used in the codebase for reference
rg -n "compatibilityDate" --type ts -C1Repository: kentcdodds/kody Length of output: 552 🏁 Script executed: #!/bin/bash
# Get full context around line 216 in executor.ts
sed -n '200,230p' packages/worker/src/mcp/executor.tsRepository: kentcdodds/kody Length of output: 944 🏁 Script executed: #!/bin/bash
# Search for any comments or documentation about compatibility dates
rg -i "compatibility" packages/worker/src/ -A 2 -B 2 --type ts | head -50Repository: kentcdodds/kody Length of output: 1329 🏁 Script executed: #!/bin/bash
# Look for any config files that might set or reference compatibility dates
fd -e json -e toml -e yaml -e yml packages/worker | xargs grep -l "2025" 2>/dev/null || echo "No matches"Repository: kentcdodds/kody Length of output: 452 🌐 Web query:
💡 Result: The compatibility date for Cloudflare Workers uses the YYYY-MM-DD date format (e.g., "2022-04-05"). The date "2025-06-01" follows this format exactly and is a valid past date as of today (2026-03-29). Official documentation consistently shows examples in this format, and Wrangler validates dates, rejecting invalid ones (e.g., future dates like "2024-11-05" in 2024 caused issues). Compatibility dates must be in the past or present; the runtime supports all historical dates from 2021-11-02 onward indefinitely. Flags enable at specific dates up to 2026-04-01, confirming support for 2025 dates. Use the latest date like "2026-03-29" for new projects to opt into all changes. Citations:
Update compatibility date to current version. The compatibility date 🤖 Prompt for AI Agents |
||
| compatibilityFlags: ['nodejs_compat'], | ||
| mainModule: 'executor.js', | ||
| modules: { | ||
| [`${codemodeUtilsModuleSpecifier}`]: { | ||
| js: buildCodemodeUtilsModuleSource(), | ||
| }, | ||
| 'user-entry.js': buildUserEntryModuleSource(input.code), | ||
| 'user-code.js': buildUserCodeRunnerModuleSource(), | ||
| 'executor.js': executorModuleSource, | ||
| }, | ||
| globalOutbound: input.globalOutbound, | ||
| })) | ||
| .getEntrypoint() | ||
|
|
||
| const response = (await entrypoint.evaluate(dispatchers)) as ExecuteResult | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| if (response.error) { | ||
| return { | ||
| result: undefined, | ||
| error: response.error, | ||
| logs: response.logs, | ||
| } | ||
| } | ||
| return { | ||
| result: response.result, | ||
| logs: response.logs, | ||
| } | ||
| } | ||
|
|
||
| export type ExecutionErrorDetails = | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.