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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"build:plugin": "bun build src/source/v1.js --outfile=src/index.js --target=bun --format=esm --external=@opencode-ai/plugin/tool",
"build:plugin:npm": "npm install --no-save --package-lock=false bun@1.3.14 && bun build src/source/v1.js --outfile=src/index.js --target=bun --format=esm --external=@opencode-ai/plugin/tool",
"prepack": "npm run build:plugin:npm && node --check src/index.js",
"check": "node --check src/source/v1.js && node --check src/source/core/args.js && node --check src/source/core/state.js && node --check src/source/core/jobs.js && node --check src/source/core/process.js && node --check src/source/opencode/sdk.js && node --check src/source/opencode/session-context.js && node --check src/source/legacy-v1.js && node --check src/index.js && node --check scripts/install-node.mjs && node --check scripts/loopd.mjs && node --check scripts/install-test.mjs && node --check scripts/loopd-test.mjs && node --check scripts/smoke-test.mjs && node --check scripts/comprehensive-watchdog.mjs && node --check scripts/comprehensive-test.mjs && node --check scripts/host-loop-canary.mjs",
"test": "node scripts/install-test.mjs && node scripts/loopd-test.mjs && node scripts/smoke-test.mjs && node scripts/comprehensive-watchdog.mjs",
"check": "node --check src/source/v1.js && node --check src/source/core/args.js && node --check src/source/core/state.js && node --check src/source/core/jobs.js && node --check src/source/core/process.js && node --check src/source/opencode/sdk.js && node --check src/source/opencode/session-context.js && node --check src/source/opencode2/contract.js && node --check src/source/opencode2/experimental.js && node --check src/source/opencode2/plugin.js && node --check src/source/legacy-v1.js && node --check src/index.js && node --check scripts/install-node.mjs && node --check scripts/loopd.mjs && node --check scripts/install-test.mjs && node --check scripts/loopd-test.mjs && node --check scripts/smoke-test.mjs && node --check scripts/comprehensive-watchdog.mjs && node --check scripts/comprehensive-test.mjs && node --check scripts/opencode2-contract-test.mjs && node --check scripts/host-loop-canary.mjs",
"test": "node scripts/opencode2-contract-test.mjs && node scripts/install-test.mjs && node scripts/loopd-test.mjs && node scripts/smoke-test.mjs && node scripts/comprehensive-watchdog.mjs",
"canary:host": "node scripts/host-loop-canary.mjs",
"install:global": "node scripts/install-node.mjs",
"pack:zip": "node scripts/make-zip.mjs"
Expand Down
45 changes: 45 additions & 0 deletions scripts/opencode2-contract-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import assert from "node:assert/strict"

import OpenCode2LoopExperimental, { OPENCODE2_PLUGIN_ID } from "../src/source/opencode2/plugin.js"
import { missingOpenCode2Contract, openCode2Contract } from "../src/source/opencode2/contract.js"

const completeContext = {
command: { transform: async () => ({ dispose: async () => {} }) },
session: {
hook: async () => ({ dispose: async () => {} }),
prompt: async () => {},
},
tool: {
transform: async () => ({ dispose: async () => {} }),
hook: async () => ({ dispose: async () => {} }),
},
event: { subscribe: async () => {} },
}

assert.equal(OPENCODE2_PLUGIN_ID, "bybrawe.opencode-loop.v2")
assert.equal(OpenCode2LoopExperimental.id, OPENCODE2_PLUGIN_ID)
assert.deepEqual(openCode2Contract(completeContext), {
command: true,
session: true,
prompt: true,
tools: true,
toolHooks: true,
events: true,
})
assert.deepEqual(missingOpenCode2Contract(completeContext), [])
await OpenCode2LoopExperimental.setup(completeContext)

const incompleteContext = {
command: { transform: async () => {} },
session: { hook: async () => {} },
tool: { transform: async () => {} },
event: {},
}

assert.deepEqual(missingOpenCode2Contract(incompleteContext), ["prompt", "toolHooks", "events"])
await assert.rejects(
() => OpenCode2LoopExperimental.setup(incompleteContext),
/V2 host contract unavailable: prompt, toolHooks, events/,
)

console.log("OpenCode Loop V2 contract test passed")
16 changes: 16 additions & 0 deletions src/source/opencode2/contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function openCode2Contract(ctx) {
return {
command: typeof ctx?.command?.transform === "function",
session: typeof ctx?.session?.hook === "function",
prompt: typeof ctx?.session?.prompt === "function",
tools: typeof ctx?.tool?.transform === "function",
toolHooks: typeof ctx?.tool?.hook === "function",
events: typeof ctx?.event?.subscribe === "function",
}
}

export function missingOpenCode2Contract(ctx) {
return Object.entries(openCode2Contract(ctx))
.filter(([, available]) => !available)
.map(([name]) => name)
}
1 change: 1 addition & 0 deletions src/source/opencode2/experimental.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const OPENCODE2_EXPERIMENTAL = true
15 changes: 15 additions & 0 deletions src/source/opencode2/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { missingOpenCode2Contract } from "./contract.js"

export const OPENCODE2_PLUGIN_ID = "bybrawe.opencode-loop.v2"

export const OpenCode2LoopExperimental = {
id: OPENCODE2_PLUGIN_ID,
setup: async (ctx) => {
const missing = missingOpenCode2Contract(ctx)
if (missing.length) {
throw new Error(`OpenCode Loop V2 host contract unavailable: ${missing.join(", ")}`)
}
},
}

export default OpenCode2LoopExperimental