codemode: support dotted provider namespaces and dynamic remote providers#3
codemode: support dotted provider namespaces and dynamic remote providers#3jonastemplestein wants to merge 12 commits intomainfrom
Conversation
|
Addressed the review comments. Two follow-ups landed:
This keeps the current PR minimal while being honest that the API shape has limits, and it explicitly points toward the more principled nested-client direction for later. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6d110bd. Configure here.
| typeBlocks.push(types); | ||
| const resolved: ResolvedProvider = { name, fns: extractFns(filtered) }; | ||
| if (provider.positionalArgs) resolved.positionalArgs = true; | ||
| if (staticProvider.positionalArgs) resolved.positionalArgs = true; |
There was a problem hiding this comment.
Duplicate declare const for sibling dotted providers
Medium Severity
When two or more static providers share the same root via dotted names (e.g., "mcp.serverA" and "mcp.serverB"), each provider independently generates its own declare const mcp: { ... } type block containing only its own tools. These are concatenated into the LLM prompt, producing duplicate declare const mcp declarations—invalid TypeScript that can't be merged. The model may only see one provider's tools, causing it to miss the other's. The runtime correctly handles this via globalThis and ??=, but the type-generation loop doesn't merge declaration trees across providers that resolve to the same root identifier.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 6d110bd. Configure here.
|
Added a small follow-up for pnpm git-subdirectory installs:
This lets consumers install {
"dependencies": {
"@cloudflare/codemode": "github:iterate/agents#<ref>&path:/packages/codemode"
}
}so the package builds its Validated with local |
|
Added the remaining package-local build tooling needed for pnpm git-subdirectory installs.
{
"oxfmt": "^0.46.0",
"tsdown": "^0.21.9",
"tsx": "^4.21.0",
"typescript": "^6.0.3"
}That means this should now work as a direct pnpm dependency from the fork: {
"dependencies": {
"@cloudflare/codemode": "github:iterate/agents#<ref>&path:/packages/codemode"
}
}without relying on the monorepo root devDependencies being present during Validated again with local package |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |


Why this exists
The main goal of this follow-up is to make remote tool hosts usable from codemode with as little codemode surgery as possible — especially:
The key constraint was: do not redesign codemode around a whole new remote-provider abstraction if a much smaller runtime-first change can unlock the use case.
In other words, this PR tries to answer:
What changed
This PR adds two small but important capabilities:
Together they allow code like this inside codemode-generated sandbox code:
where codemode does not need a static local
tools: Record<string, Tool>for every callable leaf.Instead, the provider can forward the attempted call to a remote host at runtime.
New API, up front
1) Dedicated dynamic entrypoint
I intentionally moved this off the root codemode entrypoint so the mainstream/static codemode API stays conservative, and the runtime-first escape hatch is opt-in.
2) Dynamic provider authoring
That is the primary intended shape.
Example: Durable Object provider
This is the motivating case I had in mind while shaping the PR.
Then codemode-generated code can do:
At runtime codemode forwards:
"files.read"+[{ path: "README.md" }]"issues.list"+[{ state: "open" }]into the DO instance.
That is the whole point: remote/DO-backed providers can be used without teaching codemode a whole new discovery or transport framework first.
Design summary
A. Dotted tool names are already path-like
The earlier dotted-tool-path work made codemode understand that tool names like:
files.readinternal.sample.pingshould behave like nested sandbox access and nested ambient type declarations.
This PR extends the same idea one level higher to provider names.
So now a provider name like:
name: "mcp.someServer"means sandbox code can access:
B. Dynamic providers trade enumeration for runtime dispatch
Static providers still look like:
Dynamic providers instead look like:
This is the explicit “trust me, try it at runtime” escape hatch.
If sandbox code attempts:
codemode forwards:
"foo.bar"[1, 2]to the provider’s
callTool()hook.C.
typesremains model-facing prompt materialI originally explored async/lazy
types, but after review that version turned out to be too invasive and not worth the complexity.So the final compromise is:
typesThat keeps codemode’s eager description assembly intact and avoids large refactors in
tool.ts/tanstack-ai.ts.Why this is intentionally minimal
I specifically did not add:
All of those may be reasonable later, but they are not required to unblock the real use case.
The smallest useful thing is:
That is enough to make a remote server or Durable Object instance usable as a codemode provider today.
Detailed implementation notes
1) Executor runtime now supports dotted provider namespaces
DynamicWorkerExecutornow builds sandbox globals recursively for dotted provider names.So a provider named:
creates nested proxy access rather than a single flat identifier.
2) Runtime dispatch supports dynamic providers
ResolvedProvidercan now carry either:fns)callTool(name, args)hookToolDispatcherchecks static functions first, then falls back to the dynamic provider hook.3) Ambient declarations support dotted provider namespaces
Type-generation paths now emit nested declarations for dotted provider namespaces so the prompt-side model view matches runtime access.
Example output shape:
4) Prefix conflicts still work
The previous dotted-tool-path work also introduced
$callwhen a name is both:That behavior is preserved.
5) Dedicated entrypoint for dynamic behavior
dynamicTools()now lives at:instead of the root package export.
This keeps the root package surface narrower and makes the dynamic/runtime-first behavior feel intentionally opt-in.
Review-driven changes / corrections
This PR changed shape significantly during review.
The original version tried to do too much, especially around async/lazy
types. Review feedback was right that this created both:I explicitly corrected the following:
Fixed: discarded prompt description bug
The async-doc experiment accidentally computed the final description too late and discarded it, leaving the raw
{{types}}placeholder visible to the model.That design was removed.
Fixed: dotted namespace emit bug
Nested provider declaration output was briefly dropping intermediate segments like
someServer. The declaration tree helpers and callers were corrected so emitted prompt declarations now match runtime access.Fixed: dead runtime conditional
A leftover no-op conditional in the executor proxy path was removed.
Reduced scope substantially
I removed the async/lazy prompt-doc machinery entirely and went back to the smallest shape that actually serves the remote/DO provider use case.
Files of interest
Primary implementation files:
packages/codemode/src/executor.tspackages/codemode/src/dynamic-tools.tspackages/codemode/src/dynamic.tspackages/codemode/src/resolve.tspackages/codemode/src/tool.tspackages/codemode/src/tanstack-ai.tspackages/codemode/src/tool-types.tspackages/codemode/src/json-schema-types.tspackages/codemode/src/type-tree.tspackages/codemode/src/utils.tsTests:
packages/codemode/src/tests/dynamic-tools.test.tspackages/codemode/src/tests/executor.test.tspackages/codemode/src/tests/tool-types.test.tspackages/codemode/src/tests/utils.test.tsPackaging / exports:
packages/codemode/package.jsonpackages/codemode/scripts/build.tsBefore / after mental model
Before
Codemode was best at providers that could eagerly produce:
That is awkward for remote systems where the real implementation lives elsewhere, especially per-instance systems like Durable Objects.
After
Codemode still fully supports the static shape, but now also supports:
That is enough to bridge codemode into remote tool servers with very little new machinery.
Scope / non-goals
This PR is not trying to solve every remote-tools problem.
It does not provide:
Those are intentionally left to the caller / provider implementation.
This PR only provides the minimal codemode runtime surface necessary so those systems can be plugged in cleanly.
Validation
Note
Medium Risk
Updates core execution and type-generation paths to support dotted provider namespaces and dynamic runtime dispatch, which could affect tool routing and sandbox proxy behavior. Risk is mitigated by added validation for namespace conflicts and expanded test coverage for new execution paths.
Overview
Adds an opt-in
@cloudflare/codemode/dynamicentrypoint with adynamicTools()helper for providers that resolve tool calls at runtime via acallTool(name, args)hook instead of a statictoolsrecord.Extends
DynamicWorkerExecutorto support dotted provider namespaces (e.g.mcp.someServer.*) by creating nested sandbox globals, validating/normalizing provider paths, rejecting prefix-conflicting namespaces, and dispatching calls by provider path key.ToolDispatchernow parses arguments uniformly and falls back to a provider-levelcallToolwhen no static tool match exists.Updates AI/TanStack integrations and type generators to handle dynamic providers and dotted provider namespaces, including new
insertDeclTree()logic so emitted ambient declarations match nested provider paths. Packaging/build is updated to ship the new entrypoint and standalone build config, and tests/snapshots are extended to cover dotted namespaces and dynamic dispatch behavior.Reviewed by Cursor Bugbot for commit 7c0735e. Bugbot is set up for automated code reviews on this repo. Configure here.