Repository navigation
computer: Let isolate code read module declarations from cloudflare:computer - #240
Draft
mattzcarey wants to merge 3 commits into
Draft
mattzcarey wants to merge 3 commits into
mattzcarey wants to merge 3 commits into
Conversation
A host module's code runs in the Durable Object, so the isolate only
holds stubs that forward each call. Code had nothing to inspect: the
exec description gave one line of prose per module, printing a stub
showed the forwarding code, and printing a module namespace threw,
because console output was formatted with String(). Models guessed
call shapes instead, and ws:git made the guesses look like success:
cli("status") ran git with no arguments and returned its help text
with exit code 0.
Module factories now carry TypeScript declarations in a `types`
property, keyed by the name each one declares, and short `examples`.
The prebuilt modules declare all their exports. ws:tools writes each
tool's declaration from its description and JSON Schema input, which
forAISDKTools and forPiTools now pass through on each binding.
The JavaScript backend renders the declarations once per connection
and installs `cf:computer`, a built-in module like node:fs.
`docs(target)` returns a module's summary, declarations, and examples,
and `docs()` lists every module. `types(target)` returns only the
declarations. The target is a specifier, a namespace from
`import * as`, or one host export. The cf:* namespace is reserved, so
configured modules can no longer use it.
Each host stub is named after its export and prints its declaration
with a `{ [host code] }` body, as V8 prints a built-in with
`{ [native code] }`. console output follows Node.js formatting, so
objects, maps, and errors print legibly, and a namespace prints its
module's declaration.
ws:git exports now take exactly one options object. A string, an
array, or an argv starting with "git" is refused with the shape the
call expects.
馃 Changeset detectedLatest commit: b677dc7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| } | ||
|
|
||
| function mentions(text: string, name: string): boolean { | ||
| const escaped = name.replace(/[$]/g, "\\$"); |
| import { call } from ${JSON.stringify(capabilitiesImport)}; | ||
| ${names.map((name, index) => `const fn${index} = (...args) => call(${namespace}, ${JSON.stringify(name)}, args);`).join("\n")} | ||
| import { hostExport } from ${JSON.stringify(introspectionImport)}; | ||
| ${names.map((name, index) => `const fn${index} = hostExport(${module}, ${JSON.stringify(name)}, (...args) => call(${namespace}, ${JSON.stringify(name)}, args));`).join("\n")} |
| import { call } from ${JSON.stringify(capabilitiesImport)}; | ||
| ${names.map((name, index) => `const fn${index} = (...args) => call(${namespace}, ${JSON.stringify(name)}, args);`).join("\n")} | ||
| import { hostExport } from ${JSON.stringify(introspectionImport)}; | ||
| ${names.map((name, index) => `const fn${index} = hostExport(${module}, ${JSON.stringify(name)}, (...args) => call(${namespace}, ${JSON.stringify(name)}, args));`).join("\n")} |
aron-cf
reviewed
Oct 15, 2026
| "@cloudflare/computer": minor | ||
| --- | ||
|
|
||
| JavaScript backend code can import `cf:computer` for each module's TypeScript declarations, and `console.log` prints a host export's declaration. Configured modules can no longer use `cf:*` specifiers, and `ws:git` rejects calls that pass anything other than one options object; see [`cf:computer`](https://github.com/cloudflare/computer/blob/main/docs/17_isolate_javascript.md#cfcomputer). |
Collaborator
There was a problem hiding this comment.
So excited for this 馃コ
I'm not sure about the module name. I don't think it should be ws: prefix (which I also think should become computer: or ext: or something that implies it's an extension. Alternatives ws:debug, ws:runtime, ws:help...
Also I think now I see it, we should just have one function. help(<thing>) that returns docs, types, source...
I could also be convinced that help should just be a global:
import * as git from "ws:git";
export default () => ({
modules: help().docs, // every module, one line each
git: help(git), // ws:git's interface, summary, declarations, and examples, as markdown
cli: help(git.cli), // one export's interface, declaration, with the types it names
byName: help("ws:git"), // a specifier works too, even for a module that failed to import
});Models given only a module's name rarely read its declarations before
calling it. They guess, and learn from the first error, so errors are
where the declarations need to be found.
A call to an export a host module doesn't have fails with a bare
"is not a function". The run now names the docs() call that lists the
module's exports. An import of a module that isn't configured, such as
node:child_process to run git, names docs(). ws:git refuses an option
it doesn't take, such as log({ cwd }), and lists the ones it does,
where before the option was ignored and the call ran at the backend
root. The cf:computer line in the description asks for a module's
docs before its first call.
The natural first probe, `import * as git from "ws:git";
console.log(git)`, failed before it ran, because the module has no
default export. A module with no exports is now a script: its imports
stay at the top and the rest becomes the body of the default export,
so top-level await, I/O, and return work. A module with named exports
but no default still fails.
Output from the top level of a module was dropped, because the stream
write is refused while the module evaluates. It is now held until the
import settles.
commit: |
The built-in introspection module now lives under the cloudflare: scheme, beside the runtime's own cloudflare:workers and cloudflare:sockets. The whole cloudflare: namespace is reserved for built-in modules: no configured module may use it, and importing any other cloudflare: module, such as cloudflare:workers, fails before the Worker is created.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Code running on the JavaScript backend can now look up how to call any module it can import. A built-in
cloudflare:computermodule returns each module's TypeScript declarations and examples, and printing a host export shows its declaration.Models were guessing how to call host modules. While moving the agents harnesses to Computer, the pi harness needed several tries to get
ws:git'sclisignature right:(...args) => call("host/ws:git", "cli", args), andconsole.log(namespace)threw, because console output went throughString()cli("status")andcli(["status"])ran git with no arguments, so they returned its help text with exit code 0How it works:
types(TypeScript declarations keyed by name) andexamples. The prebuilt modules declare all their exports, andws:toolswrites each tool's declaration from thedescriptionandinputSchemathatforAISDKToolsandforPiToolsnow pass through.{ [host code] }in place of{ [native code] }. Console output follows Node.js formatting.ws:gitrefuses anything other than one options object of the options it takes, or anargvstarting with"git", with the shape it expects. Calling an export a host module lacks, or importing a module that isn't configured, names thedocs(...)call to make.import * as git from "ws:git"; console.log(git)works as a first probe, as do top-levelawait, I/O, andreturn. Top-levelconsole.logoutput, which was dropped before, is kept.cloudflare:*specifiers are reserved, so configured modules can no longer use them.Tests ran (
npm testinpackages/computer). The new end-to-end cases run in workerd under both the legacy and new module registries. The pi harness example also ran against this branch with its hand-writtenws:gitdeclarations removed, so the model knew only thatws:gitandcloudflare:computerexist. In four runs of two git tasks on the final code, it read the declarations by its fourth call at the latest, by printing the namespace or callingdocs, and finished every task. Wrong guesses made before that were caught by the errors above.