Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/opencode-2-2-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agents": patch
---

`agents/harness/opencode` and `agents/models/opencode` now need opencode 2.2.3 or later (peer range `^2.2.3`): `agents/models/opencode` supplies its provider through OpenCode's `sdk` hook instead of borrowing `@ai-sdk/vercel`, which OpenCode stopped bundling in 2.2.1. One-way door: opencode 2.2.0 and later move file attachments and file tool results into a blob table that releases before 2.2.0 cannot read. See [the OpenCode harness example](https://github.com/cloudflare/agents/blob/main/examples/next/harnesses/opencode/README.md).
4 changes: 2 additions & 2 deletions examples/next/harnesses/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"dependencies": {
"@cloudflare/computer": "^0.8.0",
"@cloudflare/kumo": "^2.6.0",
"@opencode/plugin": "2.0.24",
"@opencode/sdk": "2.0.24",
"@opencode/plugin": "2.2.3",
"@opencode/sdk": "2.2.3",
"@phosphor-icons/react": "^2.1.10",
"@streamdown/code": "^1.1.1",
"agents": "*",
Expand Down
2 changes: 1 addition & 1 deletion examples/next/harnesses/opencode/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default defineConfig({
name: "empty-httpapi-scalar",
enforce: "pre",
load: (id) =>
id.includes("/effect/dist/unstable/httpapi/internal/httpApiScalar.js")
id.includes("/effect/dist/http-api/internal/httpApiScalar.js")
? 'export const javascript = "";'
: null
},
Expand Down
8 changes: 4 additions & 4 deletions packages/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
"@modelcontextprotocol/conformance-v2": "npm:@modelcontextprotocol/conformance@0.2.0-alpha.12",
"@modelcontextprotocol/sdk": "1.32.2",
"@modelcontextprotocol/server": "2.4.0",
"@opencode/plugin": "2.0.24",
"@opencode/sdk": "2.0.24",
"@opencode/plugin": "2.2.3",
"@opencode/sdk": "2.2.3",
"@tanstack/ai": "0.38.0",
"@types/react": "^19.2.17",
"@valibot/to-json-schema": "1.3.0",
Expand All @@ -81,8 +81,8 @@
"@modelcontextprotocol/client": "2.4.0",
"@modelcontextprotocol/sdk": "1.32.2",
"@modelcontextprotocol/server": "2.4.0",
"@opencode/plugin": "^2.0.24",
"@opencode/sdk": "^2.0.24",
"@opencode/plugin": "^2.2.3",
"@opencode/sdk": "^2.2.3",
"@tanstack/ai": ">=0.10.2 <2.0.0",
"@x402/core": "^2.0.0",
"@x402/evm": "^2.0.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/agents/src/harness/opencode/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ export class OpenCodeHarness extends LifecycleCapability {
case "queued":
case "running":
await this.#call(() =>
opencode.sessions.wait(
opencode.experimental.session.wait(
{ sessionID: session },
signal ? { signal } : undefined
)
Expand Down Expand Up @@ -667,7 +667,7 @@ export class OpenCodeHarness extends LifecycleCapability {
const timer = setTimeout(() => budget.abort(), this.#waitBudgetMs);
try {
// Cancelling the wait never cancels OpenCode's work.
await opencode.sessions.wait(
await opencode.experimental.session.wait(
{ sessionID: session },
{ signal: budget.signal }
);
Expand Down Expand Up @@ -1239,7 +1239,7 @@ export class OpenCodeSession {
const release = this.#harness.lease();
try {
const opencode = await this.#harness.opencode();
yield* opencode.sessions.log(
yield* opencode.experimental.session.log(
{
sessionID: this.id,
...(options.after === undefined ? {} : { after: options.after }),
Expand Down
18 changes: 17 additions & 1 deletion packages/agents/src/harness/opencode/tests/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,26 @@ export default defineConfig({
name: "empty-httpapi-scalar",
enforce: "pre",
load: (id) =>
id.includes("/effect/dist/unstable/httpapi/internal/httpApiScalar.js")
id.includes("/effect/dist/http-api/internal/httpApiScalar.js")
? 'export const javascript = "";'
: null
},
{
// OpenCode inlines models.dev's catalog as one 8 MB string, too large
// for the message that carries a module into the test runtime. These
// tests turn catalog fetches off and use only the Cloudflare provider,
// so an empty catalog stands in.
name: "empty-models-dev-snapshot",
enforce: "pre",
transform: (code, id) =>
id.includes("/@opencode/core/dist/") &&
code.includes("// src/models-dev/snapshot.txt")
? code.replace(
/(\/\/ src\/models-dev\/snapshot\.txt\nvar snapshot_default = )'.*';/,
"$1'{}';"
)
: null
},
stripNodeModulesSourceMapReferences(),
cloudflareTest({
wrangler: { configPath: path.join(testsDir, "wrangler.jsonc") }
Expand Down
4 changes: 3 additions & 1 deletion packages/agents/src/harness/opencode/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export type OpenCodeEvent =
* carries its sequence number in `durable.seq`.
*/
export type OpenCodeLogEvent =
ReturnType<OpenCode["sessions"]["log"]> extends AsyncIterable<infer E>
ReturnType<OpenCode["experimental"]["session"]["log"]> extends AsyncIterable<
infer E
>
? E
: never;

Expand Down
28 changes: 16 additions & 12 deletions packages/agents/src/models/opencode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,12 @@ export const CLOUDFLARE_PROVIDER_ID = "cloudflare";
/**
* The AI SDK package OpenCode is told the provider uses. OpenCode builds an
* AI SDK model in two hook steps: `sdk` (load the package) and `language`
* (make the model). Its `sdk` step can only load packages it bundles; any
* other is handed to an npm installer that cannot run in workerd, and an
* external plugin's `sdk` hook runs after that installer. So the provider
* names `@ai-sdk/vercel`, which OpenCode bundles and loads in-process, and
* this plugin's `language` hook replaces the model it would make. The
* Vercel client is built but never called: its base URL does not resolve.
* (make the model). No package by this name exists: this plugin's `sdk` hook
* answers for the provider before OpenCode's npm loader would try to install
* it (OpenCode 2.2.3 and later run that loader after plugin hooks), and its
* `language` hook makes the model.
*/
const CARRIER_PACKAGE = "aisdk:@ai-sdk/vercel";
const UNREACHABLE_BASE_URL = "https://workers-ai.binding.invalid/v1";
const CARRIER_PACKAGE = "aisdk:agents/models/opencode";

/**
* A model as OpenCode references one: pass it to `OpenCodeHarness`'s
Expand Down Expand Up @@ -136,13 +133,19 @@ export function createAI(settings: AISettings): AI {
id: providerID,
name: "Cloudflare",
activation: "enabled",
package: CARRIER_PACKAGE,
settings: { baseURL: UNREACHABLE_BASE_URL, apiKey: "binding" }
package: CARRIER_PACKAGE
},
models: [...models].map(([id, options]) => info(id, options))
});
});
const hook = await ctx.aisdk.hook(
const sdk = await ctx.aisdk.hook(
"sdk",
(event) => {
event.sdk = { provider: CLOUDFLARE_PROVIDER_ID };
},
{ providerID }
);
const language = await ctx.aisdk.hook(
"language",
(event) => {
const id = event.model.modelID ?? event.model.id;
Expand All @@ -156,7 +159,8 @@ export function createAI(settings: AISettings): AI {
reloads.add(reload);
return async () => {
reloads.delete(reload);
await hook.dispose();
await language.dispose();
await sdk.dispose();
await transform.dispose();
};
}
Expand Down
Loading