Skip to content
Draft
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/remove-think-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/computer": minor
---

Remove the deprecated `useThink` compatibility layer. Think integrations must use `workspace.fs` and `@cloudflare/computer/tools` directly.
55 changes: 16 additions & 39 deletions packages/computer/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import { SQLiteTestStorage } from "@cloudflare/dofs/testing";
import { describe, expect, it } from "vitest";

import { getWorkspace, type WorkspaceClient } from "./client.js";
import { getWorkspace } from "./client.js";
import { WORKSPACE, type WorkspaceStubHost } from "./with-workspace.js";
import { type ThinkWorkspaceCompatibility, Workspace } from "./workspace.js";
import { Workspace } from "./workspace.js";

interface ExecCall {
command: string;
Expand Down Expand Up @@ -87,7 +87,6 @@ function fakeRemote(): {
let disposed = false;
const stub = {
fs: { marker: "fs" },
useThink: false,
git: { marker: "git" },
assets: undefined,
artifacts: { marker: "artifacts" },
Expand All @@ -104,14 +103,20 @@ function fakeRemote(): {
};
}

function fakeBrokenRemote(): {
function fakeLegacyFlagRemote(): {
host: WorkspaceStubHost;
disposed: () => boolean;
} {
const { runtime } = fakeRuntime(true);
let disposed = false;
const stub = {
fs: { marker: "fs" },
runtime,
git: { marker: "git" },
assets: undefined,
artifacts: { marker: "artifacts" },
get useThink(): boolean {
throw new Error("compatibility lookup failed");
throw new Error("legacy compatibility flag was read");
},
[Symbol.dispose]() {
disposed = true;
Expand Down Expand Up @@ -177,29 +182,15 @@ describe("getWorkspace — remote dispatch", () => {
expect(disposed()).toBe(true);
});

it("disposes the remote stub when client initialization fails", async () => {
const { host, disposed } = fakeBrokenRemote();
it("does not inspect the removed remote useThink flag", async () => {
const { host, disposed } = fakeLegacyFlagRemote();

await expect(getWorkspace(host)).rejects.toThrow("compatibility lookup failed");
const ws = await getWorkspace(host);
expect(ws.fs).toEqual({ marker: "fs" });
expect(disposed()).toBe(false);
ws[Symbol.dispose]();
expect(disposed()).toBe(true);
});

it("adds Think compatibility when the remote Workspace enables it", async () => {
const workspace = new Workspace({
storage: new SQLiteTestStorage(),
useThink: true,
});
await workspace.fs.writeFile("/notes.txt", "hello");
const host: WorkspaceStubHost = {
__getWorkspaceStub: () => Promise.resolve(workspace.stub()),
};

const client = (await getWorkspace(host)) as WorkspaceClient & ThinkWorkspaceCompatibility;

expect(client).toHaveProperty("readFile");
await expect(client.readFile("/notes.txt")).resolves.toBe("hello");
await expect(client.readFile("/missing.txt")).resolves.toBeNull();
});
});

describe("getWorkspace — local dispatch", () => {
Expand All @@ -216,20 +207,6 @@ describe("getWorkspace — local dispatch", () => {
const ws = await getWorkspace(host);
expect(() => ws[Symbol.dispose]()).not.toThrow();
});

it("adds Think compatibility when the local Workspace enables it", async () => {
const workspace = new Workspace({
storage: new SQLiteTestStorage(),
useThink: true,
});
const host = { [WORKSPACE]: workspace };
const { runtime } = fakeRuntime();
Object.defineProperty(workspace, "runtime", { get: () => runtime });

const client = (await getWorkspace(host)) as WorkspaceClient & ThinkWorkspaceCompatibility;

expect(client).toHaveProperty("readFile");
});
});

describe("client runtime.exec — tagged template form", () => {
Expand Down
12 changes: 2 additions & 10 deletions packages/computer/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,7 @@ import { decodeRuntimeEvents } from "./runtime/wire.js";
import { type ShellValue, sh } from "./sh.js";
import type { ExecEncoding } from "./shell.js";
import { WORKSPACE, type WorkspaceStubHost } from "./with-workspace.js";
import {
createThinkCompatibility,
type ThinkWorkspaceCompatibility,
Workspace,
} from "./workspace.js";
import { Workspace } from "./workspace.js";

// The remote runtime handle stub: a result / stream / kill surface
// carried across Workers RPC.
Expand Down Expand Up @@ -315,7 +311,7 @@ function withExecutionId(
// `fs`, `git`, `artifacts`, and `assets` are the underlying surface's
// members, passed through. The filesystem stub mirrors the local
// filesystem, so it also serves as the common client type.
export interface WorkspaceClient extends Partial<ThinkWorkspaceCompatibility> {
export interface WorkspaceClient {
readonly fs: WorkspaceFilesystem;
readonly runtime: WorkspaceRuntimeClient;
// biome-ignore lint/suspicious/noExplicitAny: git type differs local vs remote
Expand All @@ -332,7 +328,6 @@ function makeClient(
surface: any,
rehydrate: (handle: unknown, metadata?: RuntimeHandleMetadata) => unknown,
dispose: () => void,
useThink: boolean,
): WorkspaceClient {
const runtime = makeRuntimeClient(
surface.runtime as UnderlyingRuntime,
Expand All @@ -354,7 +349,6 @@ function makeClient(
},
[Symbol.dispose]: dispose,
};
if (useThink) Object.assign(client, createThinkCompatibility(client.fs));
return client;
}

Expand Down Expand Up @@ -383,7 +377,6 @@ export async function getWorkspace(handle: WorkspaceHandle): Promise<WorkspaceCl
// Local handle is already a host ExecHandle — pass it through.
(h) => h,
() => {},
local.useThink,
);
}
// Remote path: fetch the stub over RPC and delegate to it. Handle
Expand All @@ -397,7 +390,6 @@ export async function getWorkspace(handle: WorkspaceHandle): Promise<WorkspaceCl
() => {
(stub as { [Symbol.dispose]?: () => void })[Symbol.dispose]?.();
},
await stub.useThink,
);
} catch (error) {
(stub as { [Symbol.dispose]?: () => void })[Symbol.dispose]?.();
Expand Down
1 change: 0 additions & 1 deletion packages/computer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export {
type SyncRetryIntent,
type SyncRetryOptions,
type SyncRetryScheduler,
type ThinkWorkspaceCompatibility,
Workspace,
type WorkspaceGitFactory,
type WorkspaceOptions,
Expand Down
6 changes: 0 additions & 6 deletions packages/computer/src/stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,6 @@ export class WorkspaceStub extends RpcTarget {
readonly #git: WorkspaceGitStub;
readonly #assets: WorkspaceAssetsStub | undefined;
readonly #artifacts: WorkspaceArtifactsStub;
readonly #useThink: boolean;

constructor(ws: Workspace) {
super();
Expand All @@ -608,7 +607,6 @@ export class WorkspaceStub extends RpcTarget {
this.#git = new WorkspaceGitStub(ws);
this.#assets = ws.assets === undefined ? undefined : new WorkspaceAssetsStub(ws);
this.#artifacts = new WorkspaceArtifactsStub(ws.artifacts);
this.#useThink = ws.useThink;
trackStub(this);
}

Expand All @@ -631,10 +629,6 @@ export class WorkspaceStub extends RpcTarget {
return this.#fs;
}

get useThink(): boolean {
return this.#useThink;
}

get runtime(): WorkspaceRuntimeStub {
return this.#runtime;
}
Expand Down
58 changes: 6 additions & 52 deletions packages/computer/src/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,12 @@ import type { BackendHandle, WorkspaceBackend } from "./backend.js";
import { createGitClient } from "./git/index.js";
import type { WorkspaceModuleBackend } from "./runtime/types.js";
import { WorkspaceTransportError } from "./transport-failure.js";
import { type ThinkWorkspaceCompatibility, Workspace } from "./workspace.js";
import { Workspace } from "./workspace.js";

function makeStorage(): SQLiteTestStorage {
return new SQLiteTestStorage();
}

function expectThinkWorkspace(
ws: Workspace,
): asserts ws is Workspace & ThinkWorkspaceCompatibility {
expect(ws).toHaveProperty("readFile");
expect(ws).toHaveProperty("readFileBytes");
expect(ws).toHaveProperty("writeFile");
expect(ws).toHaveProperty("readDir");
expect(ws).toHaveProperty("glob");
expect(ws).toHaveProperty("mkdir");
expect(ws).toHaveProperty("rm");
expect(ws).toHaveProperty("stat");
}

// In-process fakes. We never spawn anything from the package
// code; the backend's only contract is "produce a SyncRPC
// stub that computerd would speak". A plain object is enough.
Expand Down Expand Up @@ -1463,49 +1450,15 @@ describe("Workspace transport-failure invalidation", () => {
]);
});
});
describe("Workspace Think compatibility", () => {
it("adds Think-compatible filesystem methods when useThink is true", async () => {
describe("Workspace compatibility surface", () => {
it("does not accept useThink or add root-level filesystem methods", () => {
const ws = new Workspace({
storage: makeStorage(),
// @ts-expect-error useThink was removed with the compatibility layer.
useThink: true,
now: () => 1_700_000_000_000,
});

expectThinkWorkspace(ws);

await ws.mkdir("/workspace/notes", { recursive: true });
await ws.writeFile("/workspace/notes/a.txt", "hello");
await ws.writeFile("/workspace/notes/b.md", "# title");

expect(await ws.readFile("/workspace/notes/a.txt")).toBe("hello");
expect(await ws.readFile("/workspace/missing.txt")).toBeNull();
expect(new TextDecoder().decode(await ws.readFileBytes("/workspace/notes/a.txt"))).toBe(
"hello",
);
expect(await ws.readFileBytes("/workspace/missing.txt")).toBeNull();

await expect(ws.stat("/workspace/notes/a.txt")).resolves.toMatchObject({
path: "/workspace/notes/a.txt",
name: "a.txt",
type: "file",
size: 5,
});
await expect(ws.stat("/workspace/missing.txt")).resolves.toBeNull();

await expect(ws.readDir("/workspace/notes", { limit: 1, offset: 1 })).resolves.toEqual([
expect.objectContaining({ path: "/workspace/notes/b.md", name: "b.md", type: "file" }),
]);
await expect(ws.glob("/workspace/notes/**/*.txt")).resolves.toEqual([
expect.objectContaining({ path: "/workspace/notes/a.txt", name: "a.txt", type: "file" }),
]);

await ws.rm("/workspace/notes/a.txt", { force: true });
expect(await ws.readFile("/workspace/notes/a.txt")).toBeNull();
});

it("does not add Think compatibility methods by default", () => {
const ws = new Workspace({ storage: makeStorage() });

expect(ws).not.toHaveProperty("useThink");
expect(ws).not.toHaveProperty("readFile");
expect(ws).not.toHaveProperty("readFileBytes");
expect(ws).not.toHaveProperty("writeFile");
Expand All @@ -1514,5 +1467,6 @@ describe("Workspace Think compatibility", () => {
expect(ws).not.toHaveProperty("mkdir");
expect(ws).not.toHaveProperty("rm");
expect(ws).not.toHaveProperty("stat");
expect(ws.stub()).not.toHaveProperty("useThink");
});
});
Loading
Loading