forked from cloudflare/agents
-
Notifications
You must be signed in to change notification settings - Fork 0
codemode: support dotted provider namespaces and dynamic remote providers #2
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
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
e72ec16
codemode: add dynamic tool providers
jonastemplestein 219f611
codemode: reduce dynamic provider scope
jonastemplestein 33f6693
codemode: move dynamic tools to dedicated entrypoint
jonastemplestein 0c454cc
codemode: save json schema type tree cleanup before rebase
jonastemplestein File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@cloudflare/codemode": patch | ||
| --- | ||
|
|
||
| Add a `dynamicTools()` helper for runtime-resolved codemode providers, support dotted provider paths, and allow provider prompt documentation (`types`) to be loaded asynchronously. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import type { ToolProvider, ToolProviderTypes } from "./executor"; | ||
|
|
||
| /** | ||
| * A dynamic tool provider is the escape hatch for integrations that do not | ||
| * know their full tool surface ahead of time, or deliberately do not want to | ||
| * pay the cost of discovering it up front. | ||
| * | ||
| * Instead of contributing a static `tools` record, a dynamic provider accepts | ||
| * arbitrary dotted subpaths at runtime and decides for itself whether they are | ||
| * valid. In other words, if sandbox code evaluates | ||
| * `mcp.someServer.files.read({ path: "/tmp/x" })`, codemode forwards the | ||
| * trailing path `"files.read"` and the raw argument array to `callTool()`. | ||
| * | ||
| * The `types` field name is inherited from existing codemode providers, but for | ||
| * dynamic providers it should be read much more literally as *LLM-facing | ||
| * documentation*. Codemode does not parse, typecheck, or validate this text as | ||
| * TypeScript. The string is inserted verbatim into the prompt block shown to | ||
| * the model. That means it can contain declaration-like examples, prose API | ||
| * notes, usage conventions, or any other guidance that helps the model produce | ||
| * sensible calls. | ||
| * | ||
| * We intentionally keep this field synchronous in the minimal dynamic-provider | ||
| * design. Tool descriptions are assembled eagerly by the surrounding codemode | ||
| * integrations, and trying to sneak lazy/async prompt material through that | ||
| * path made the implementation much more invasive than the runtime feature | ||
| * justified. If we ever want remote discovery-backed docs, that should likely | ||
| * be a separate API with its own explicit lifecycle. | ||
| */ | ||
| export interface DynamicToolsOptions { | ||
| /** | ||
| * Namespace path exposed in sandbox code. | ||
| * | ||
| * Dotted names are allowed. For example, `name: "mcp.someServer"` makes the | ||
| * provider reachable as `mcp.someServer.*` in generated code. | ||
| */ | ||
| name?: string; | ||
|
|
||
| /** | ||
| * Runtime handler for tool calls under this namespace. | ||
| * | ||
| * Codemode forwards the full dotted subpath below `name` verbatim. If the | ||
| * model attempts `mcp.someServer.foo.bar(1, 2)`, this function receives | ||
| * `name === "foo.bar"` and `args === [1, 2]`. | ||
| */ | ||
| callTool: (name: string, args: unknown[]) => Promise<unknown>; | ||
|
|
||
| /** | ||
| * Optional model-facing documentation inserted into the codemode prompt. | ||
| * | ||
| * Despite the legacy field name, this does not need to be valid TypeScript. | ||
| * It is best thought of as provider documentation for the LLM: declaration | ||
| * snippets, examples, conventions, caveats, etc. | ||
| */ | ||
| types?: ToolProviderTypes; | ||
|
|
||
| /** | ||
| * Marks this provider as preferring positional-argument examples. | ||
| * | ||
| * The runtime hook always receives the raw argument array regardless; this | ||
| * flag only affects how surrounding codemode integrations think about the | ||
| * provider surface. | ||
| */ | ||
| positionalArgs?: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Construct a codemode provider whose tool surface is resolved dynamically at | ||
| * runtime instead of from a static `tools` record. | ||
| * | ||
| * The helper exists so we can keep the public API crisp and intentional. Users | ||
| * opt into the dynamic behavior explicitly instead of smuggling it through the | ||
| * generic `ToolProvider` shape. | ||
| */ | ||
| export function dynamicTools(options: DynamicToolsOptions): ToolProvider { | ||
| return { | ||
| name: options.name, | ||
| callTool: options.callTool, | ||
| types: options.types, | ||
| positionalArgs: options.positionalArgs | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { dynamicTools, type DynamicToolsOptions } from "./dynamic-tools"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Single-segment provider proxy shadows all longer-prefixed providers sharing the same root
When a single-segment provider (e.g.,
name: "mcp") and a multi-segment provider sharing the same root (e.g.,name: "mcp.server") are both registered, the single-segment provider's recursive Proxy intercepts all property accesses via itsgettrap, making the multi-segment provider's proxy permanently unreachable.Detailed walkthrough of the failure
The generated sandbox code for
name: "mcp"doesmcp = (() => { /* proxy A */ })(), creating a Proxy with agettrap that captures every property access. The code forname: "mcp.server"then doesmcp.server = (() => { /* proxy B */ })()— thissetfalls through to proxy A's target (a function object) since there's nosettrap, but when sandbox code later accessesmcp.server.tool(), proxy A'sgettrap intercepts"server", returningmake(["server"])which routes to the"mcp"dispatcher, not the"mcp.server"dispatcher.A plausible scenario:
The
"codemode.extra"provider's tools are silently unreachable.The validation loop at
executor.ts:276-298checks for exact duplicateproviderKeys but not for prefix conflicts. A provider with key"mcp"and another with"mcp.server"both pass validation, producing silently broken runtime behavior.Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.