This guide is for people who work on the plugin itself. If you only want to use the plugin, read the README.
OpenCode can run the plugin from TypeScript source through its embedded Bun runtime.
Clone the repository:
git clone https://github.com/tensorlakeai/opencode-tensorlake-plugin ~/opencode-tensorlake-pluginPoint OpenCode at the plugin entry file in ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"file:///Users/your-username/opencode-tensorlake-plugin/.opencode/plugin/index.ts"
]
}Two rules for local paths:
- The
file://prefix is required. Without it, OpenCode treats the value as an npm package name. - The path must point to the
.tsentry file (.opencode/plugin/index.ts), not the repository root.
opencode-tensorlake-plugin/
├── package.json
├── tsconfig.json # type-check config (no emit)
├── tsconfig.lib.json # build config (emits to dist/)
└── .opencode/
└── plugin/
├── index.ts # re-exports default plugin
└── tensorlake/
├── index.ts # plugin factory
├── tools.ts # assembles all tools
├── core/
│ ├── client.ts # Tensorlake SDK client wrapper
│ ├── credentials.ts # API key resolution (auth.json + env)
│ ├── filesystem.ts # optional persistent filesystem attach
│ ├── git-bootstrap.ts # optional hosted git repo credentials
│ ├── glob-match.ts # glob pattern to RegExp compiler
│ ├── logger.ts # file-based logger with rotation
│ ├── session-manager.ts # binds sessions to sandboxes (Sandbox.getOrCreate)
│ ├── session-tree.ts # maps subagent sessions to their root
│ ├── shell.ts # shell quoting helper
│ ├── toast.ts # TUI toast queue
│ └── types.ts # shared type definitions
├── tools/
│ ├── apply-patch.ts
│ ├── bash.ts
│ ├── read.ts
│ ├── write.ts
│ ├── edit.ts
│ ├── multiedit.ts
│ ├── ls.ts
│ ├── glob.ts
│ └── grep.ts
└── plugins/
├── auth.ts # registers Tensorlake in opencode auth login
├── custom-tools.ts # wires tools into plugin return value
├── session-events.ts # handles session created/deleted events
└── system-transform.ts # injects sandbox context into system prompt
npm run type-check
npm run buildThe build emits to dist/ with declaration files. tsconfig.lib.json sets rootDir to .opencode/plugin, so the output mirrors that structure. The main field in package.json points to dist/index.js.
npm testRuns test/*.test.ts with the Node test runner (via tsx). No Tensorlake key is needed. The tests cover the logic that does not need a sandbox:
apply-patch— patch parsing, hunk matching, and the tool's all-or-nothing commit. The tool test runs the real shell scripts in a temp directory and checks that a failed step restores every file.glob-match— glob to RegExp, and the literal prefix used to narrow the search.session-tree— subagent to root resolution, cached lookups, cycles, failures.sandbox-name— the deterministic sandbox name a session binds to.options—filesystem/gitReporesolution and env precedence;shellQuote.
Everything below needs a real sandbox and a Tensorlake key. Run it before a release.
- Make sure
TENSORLAKE_API_KEYis not exported and notensorlakeentry exists in~/.local/share/opencode/auth.json. - Start OpenCode and ask the model to run a command. The tool call fails with a "Tensorlake login required" toast.
- Run
opencode auth loginand select Tensorlake. A hint shows where to get a key (press Enter to continue), then paste a project API key at the masked prompt. The prompt does not validate the key; a non-project key triggers a "Check your Tensorlake API key" warning toast on the first tool call. - Retry the tool call in the same session — no restart needed. The sandbox is created.
-
Log in once, then start OpenCode in a project directory.
-
No sandbox exists yet — sandboxes are created on the first intercepted tool call, not at startup. Confirm the plugin loaded:
tail -f ~/.local/share/opencode/log/tensorlake.logOn startup you see one line:
[...] [INFO] OpenCode started with Tensorlake pluginIf the file does not exist, the plugin never loaded — see Plugin not loading.
-
Ask the model to run a command. The first tool call provisions the sandbox; a "Sandbox created" toast appears, and the log shows:
[...] [INFO] Sandbox sandbox-xyz (opencode-abc123) created for session abc123 in 2300ms
Run: echo "hello from sandbox" && uname -a
The output shows Linux kernel information from the sandbox VM, not your local machine.
Write the text "Hello Tensorlake" to /tmp/workspace/test.txt, then read it back.
The model calls write then read, both routed to the sandbox via the SDK. The response echoes Hello Tensorlake.
List the files in /tmp/workspace
This triggers the ls tool, which calls sandbox.listDirectory() via the SDK.
Delete the OpenCode session from the session list. The plugin handles session.deleted and terminates the sandbox. Confirm in the log:
[...] [INFO] Sandbox opencode-abc123 deleted
- In a session with a sandbox, ask the model to use the
tasktool (for example,Use a subagent task to create /tmp/workspace/from-subagent.txt). - After the task completes, ask the parent to read the file. It sees the subagent's write — both ran in the same sandbox.
- The log shows the subagent session resolved to the root session, and no second
created for sessionline appears.
- Create a filesystem:
tl fs create test-fs. - Add
{ "filesystem": "test-fs" }to the plugin options (or exportTENSORLAKE_FILESYSTEM=test-fs) and restart OpenCode. - Ask the model to write a file in
/tmp/workspace, then delete the session. - Start a new session and read the file back. It survived the sandbox deletion.
- Negative test: set a name that does not exist. The first tool call fails with a "Filesystem attach failed" toast, and no command runs against ephemeral storage.
- Add
{ "gitRepo": "test-repo" }to the plugin options (or exportTENSORLAKE_GIT_REPO=test-repo) and restart OpenCode. - Ask the model to clone the repo, commit a file, and push. The system prompt gives it the clone URL; the push uses the scoped credential the plugin stored in the sandbox.
- Confirm the commit landed:
tlor the Tensorlake console shows it, or clone the repo elsewhere. - Confirm isolation:
cat ~/.git-credentialsin the sandbox shows only the Tensorlake host line, never your own credentials.
- Create a sandbox (run any command), then quit OpenCode (Ctrl+C). The log shows the sandboxes being suspended.
- Start OpenCode, open the same session, and run a command. A "Sandbox resumed" toast appears and the files from before the restart are still there.
Check the OpenCode server log:
ls -lt ~/.local/share/opencode/log/*.log | head -3
cat ~/.local/share/opencode/log/<latest>.log | grep -i "plugin\|error\|tensorlake"- "Plugin export is not a function" — the path in
opencode.jsonpoints to the repository root instead of the.tsentry file. Make sure it ends with.opencode/plugin/index.ts. - "404 failed to install plugin" — for npm installs, verify the package name is
tensorlake-opencode. For local installs, thefile://prefix is likely missing, so OpenCode tries to fetch your local path from npm.
Note: OpenCode installs the npm package into its own cache (~/.cache/opencode/packages/), not your project or global node_modules. Listing it in opencode.json is enough; a manual npm install elsewhere has no effect.
If OpenCode loads but commands run locally, the plugin tools are not registered. Check the server log for a second round of tool registrations after the built-ins:
service=tool.registry status=started bash ← built-in
...
service=tool.registry status=started bash ← plugin override (should appear)
If the second block is absent, the plugin loaded but failed to return its hooks. Check tensorlake.log for startup errors.
The resource env vars (TENSORLAKE_CPUS, TENSORLAKE_MEMORY_MB, TENSORLAKE_DISK_MB, TENSORLAKE_IMAGE) are documented in the README. The code defaults live in TensorlakeClient.getOrCreateSandbox in .opencode/plugin/tensorlake/core/client.ts:
const cpus = parseFloat(process.env.TENSORLAKE_CPUS ?? '2')
const memoryMb = parseInt(process.env.TENSORLAKE_MEMORY_MB ?? '4096', 10)
const diskMb = parseInt(process.env.TENSORLAKE_DISK_MB ?? '10240', 10)To register a custom image:
tl sbx image create Dockerfile --registered-name my-custom-image- Create a file in
.opencode/plugin/tensorlake/tools/that follows the pattern of the existing tools. - Import and register it in
.opencode/plugin/tensorlake/tools.ts.
Each tool factory receives (sessionManager, pluginCtx) and returns an object with description, args (a Zod schema map), and execute(args, ctx).