Compare commits
No commits in common. "26cc61d26f3558b6998d825fecc2b8665bacf183" and "1778231a2dab90849ed71603777d353044749855" have entirely different histories.
26cc61d26f
...
1778231a2d
267
index.ts
267
index.ts
|
|
@ -1,8 +1,6 @@
|
|||
import fs from "fs"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import * as acorn from "acorn"
|
||||
import * as acornWalk from "acorn-walk"
|
||||
import { MatrixBotClient, type MatrixEventContext } from "./matrix-client.js"
|
||||
import { SessionManager } from "./session-manager.js"
|
||||
import { loadConfig } from "./config-loader.js"
|
||||
|
|
@ -133,222 +131,6 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
|||
|
||||
const sessionsBySessionID = new Map<string, SessionState>()
|
||||
|
||||
// =============================================================================
|
||||
// Parse tool calls from CodeMode JS script using AST (same as OpenCode)
|
||||
// CodeMode creates nested tools: tools.<server>.<tool>(...)
|
||||
// e.g. tools.duckduckgo.search({ query: "..." })
|
||||
// =============================================================================
|
||||
|
||||
interface ChildToolInfo {
|
||||
name: string
|
||||
args: string
|
||||
}
|
||||
|
||||
// Minimal AST node type (acorn doesn't export AST namespace in usable way)
|
||||
type AstNode = { type: string; [key: string]: any }
|
||||
|
||||
/**
|
||||
* Parse the CodeMode JS script and extract all tool calls.
|
||||
* Uses acorn AST parser + walk — same approach as OpenCode's CodeMode interpreter.
|
||||
* Does NOT execute the code, only parses and analyzes the AST.
|
||||
*/
|
||||
function parseCodeModeTools(code: string): ChildToolInfo[] {
|
||||
const results: ChildToolInfo[] = []
|
||||
const seen = new Set<string>()
|
||||
|
||||
// Parse the code into an AST using acorn (same parser as CodeMode)
|
||||
const ast = acorn.parse(code, {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "script",
|
||||
allowReturnOutsideFunction: true,
|
||||
allowAwaitOutsideFunction: true,
|
||||
locations: true,
|
||||
}) as AstNode
|
||||
|
||||
// Walk the AST and find all CallExpression nodes for tools.xxx.yyy(...)
|
||||
acornWalk.simple(ast as any, {
|
||||
CallExpression(node) {
|
||||
// Check if callee is tools.<server>.<tool>(...)
|
||||
const toolCall = extractToolCall(node)
|
||||
if (!toolCall) return
|
||||
|
||||
const { path, args } = toolCall
|
||||
const name = path.join("_")
|
||||
if (seen.has(name)) return
|
||||
seen.add(name)
|
||||
|
||||
// Evaluate the arguments to get their string representation
|
||||
const argsStr = evaluateArgs(args)
|
||||
results.push({ name, args: argsStr })
|
||||
},
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a CallExpression is a tool call: tools.<server>.<tool>(...)
|
||||
* Returns { path, args } or null.
|
||||
*/
|
||||
function extractToolCall(node: AstNode): { path: string[]; args: AstNode[] } | null {
|
||||
const callee = node.callee as AstNode
|
||||
|
||||
// Must be a MemberExpression: tools.xxx.yyy
|
||||
if (callee.type !== "MemberExpression") return null
|
||||
if (callee.computed) return null
|
||||
|
||||
const prop = callee.property as AstNode
|
||||
if (prop.type !== "Identifier") return null
|
||||
const toolName = prop.name
|
||||
|
||||
// Object must be tools.xxx (nested MemberExpression) or just "tools"
|
||||
let obj: AstNode = callee.object
|
||||
const path: string[] = []
|
||||
|
||||
// Walk up the nested MemberExpression chain
|
||||
while (obj.type === "MemberExpression" && !obj.computed) {
|
||||
const innerProp = obj.property as AstNode
|
||||
if (innerProp.type !== "Identifier") return null
|
||||
path.unshift(innerProp.name)
|
||||
obj = obj.object
|
||||
}
|
||||
|
||||
// The base must be the identifier "tools"
|
||||
if (obj.type !== "Identifier" || obj.name !== "tools") return null
|
||||
|
||||
path.unshift(toolName)
|
||||
return { path, args: (node.arguments ?? []) as AstNode[] }
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate arguments to a string representation.
|
||||
* Handles literals, identifiers, objects, arrays, and template literals.
|
||||
* Matches how CodeMode's interpreter evaluates expression values.
|
||||
*/
|
||||
function evaluateArgs(args: AstNode[]): string {
|
||||
if (!args || args.length === 0) return ""
|
||||
|
||||
const values: string[] = []
|
||||
for (const arg of args) {
|
||||
values.push(evaluateExpression(arg))
|
||||
}
|
||||
|
||||
return values.join(", ")
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate an AST expression node to its string value.
|
||||
* Handles the expression types used in CodeMode tool calls.
|
||||
*/
|
||||
function evaluateExpression(node: AstNode): string {
|
||||
switch (node.type) {
|
||||
// Literals
|
||||
case "Literal":
|
||||
return String(node.value)
|
||||
|
||||
// String/number/boolean/null/regexp literals
|
||||
case "Identifier": {
|
||||
const name = node.name
|
||||
// Known globals with known values
|
||||
if (name === "undefined") return "undefined"
|
||||
if (name === "true") return "true"
|
||||
if (name === "false") return "false"
|
||||
if (name === "null") return "null"
|
||||
// For variables, show the variable name
|
||||
return name
|
||||
}
|
||||
|
||||
// Object literals: { key: value, ... }
|
||||
case "ObjectExpression": {
|
||||
const parts: string[] = []
|
||||
for (const prop of node.properties ?? []) {
|
||||
if (prop.type === "Property") {
|
||||
const key = prop.key.type === "Identifier" ? prop.key.name : String((prop.key as AstNode).value)
|
||||
const value = evaluateExpression(prop.value)
|
||||
parts.push(`${key}: ${value}`)
|
||||
}
|
||||
}
|
||||
return `{ ${parts.join(", ")} }`
|
||||
}
|
||||
|
||||
// Array literals: [value1, value2, ...]
|
||||
case "ArrayExpression": {
|
||||
const elements = (node.elements ?? []).map((el: AstNode | null) =>
|
||||
el ? evaluateExpression(el) : "",
|
||||
)
|
||||
return `[${elements.join(", ")}]`
|
||||
}
|
||||
|
||||
// Template literals: `text ${expr} text`
|
||||
case "TemplateLiteral": {
|
||||
let result = "`"
|
||||
const quasis = node.quasis ?? []
|
||||
const expressions = node.expressions ?? []
|
||||
for (let i = 0; i < quasis.length; i++) {
|
||||
result += quasis[i]?.value?.raw ?? ""
|
||||
if (expressions[i]) {
|
||||
result += "${" + evaluateExpression(expressions[i]) + "}"
|
||||
}
|
||||
}
|
||||
result += "`"
|
||||
return result
|
||||
}
|
||||
|
||||
// Binary expressions: a + b, a === b, etc.
|
||||
case "BinaryExpression": {
|
||||
const left = evaluateExpression(node.left)
|
||||
const right = evaluateExpression(node.right)
|
||||
return `${left} ${node.operator} ${right}`
|
||||
}
|
||||
|
||||
// Conditional: a ? b : c
|
||||
case "ConditionalExpression": {
|
||||
return `${evaluateExpression(node.test)} ? ${evaluateExpression(node.consequent)} : ${evaluateExpression(node.alternate)}`
|
||||
}
|
||||
|
||||
// Function expressions (arrow functions, etc.)
|
||||
case "ArrowFunctionExpression":
|
||||
case "FunctionExpression":
|
||||
return "function"
|
||||
|
||||
// Call expressions: other().method()
|
||||
case "CallExpression": {
|
||||
if (node.callee?.type === "MemberExpression") {
|
||||
const member = node.callee as AstNode
|
||||
if (member.object?.type === "Identifier") {
|
||||
return `${(member.object as AstNode).name}.${(member.property as AstNode)?.name}(${evaluateArgs(node.arguments ?? [])})`
|
||||
}
|
||||
}
|
||||
return "call"
|
||||
}
|
||||
|
||||
// Unary expressions: !a, -b, typeof c
|
||||
case "UnaryExpression": {
|
||||
const arg = evaluateExpression(node.argument)
|
||||
return `${node.operator}${arg}`
|
||||
}
|
||||
|
||||
// Logical expressions: a && b, a || b
|
||||
case "LogicalExpression": {
|
||||
return `${evaluateExpression(node.left)} ${node.operator} ${evaluateExpression(node.right)}`
|
||||
}
|
||||
|
||||
// Await expressions
|
||||
case "AwaitExpression": {
|
||||
return `await ${evaluateExpression(node.argument)}`
|
||||
}
|
||||
|
||||
// Spread: ...arr
|
||||
case "SpreadElement": {
|
||||
return `...${evaluateExpression(node.argument)}`
|
||||
}
|
||||
|
||||
// Default: show "(expr)"
|
||||
default:
|
||||
return `(expr:${node.type})`
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Event subscription — collects step data and sends messages per step
|
||||
// =============================================================================
|
||||
|
|
@ -426,26 +208,6 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
|||
const tool = step.tools.find(t => t.toolID === toolID)
|
||||
if (tool) tool.args = args
|
||||
}
|
||||
// Parse CodeMode execute tool — extract child tool calls from JS code
|
||||
if (toolID && step && args) {
|
||||
let parsedArgs: any
|
||||
try { parsedArgs = JSON.parse(args) } catch { continue }
|
||||
const code = parsedArgs?.code || ""
|
||||
if (code) {
|
||||
const childTools = parseCodeModeTools(code)
|
||||
for (const child of childTools) {
|
||||
if (!step.tools.find(t => t.name === child.name)) {
|
||||
step.tools.push({
|
||||
toolID: `${toolID}/${child.name}`,
|
||||
name: child.name,
|
||||
args: child.args,
|
||||
result: "",
|
||||
failed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -570,34 +332,6 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
|||
|
||||
// Tool calls
|
||||
if (step.tools.length > 0) {
|
||||
// Check if any tool is an execute with child tool calls
|
||||
const executeTool = step.tools.find(
|
||||
(t) => t.name === "execute" && t.toolID.includes("/"),
|
||||
)
|
||||
const childTools = executeTool
|
||||
? step.tools.filter((t) => t.toolID.startsWith(executeTool.toolID + "/"))
|
||||
: []
|
||||
|
||||
if (childTools.length > 0) {
|
||||
// Execute has child tool calls — show children, then execute result
|
||||
for (const child of childTools) {
|
||||
parts.push(`<details>\n<summary>🔧 ${child.name}</summary>\n\n`)
|
||||
parts.push(`<b>Args:</b>\n\`\`\`\n${child.args}\n\`\`\`\n\n`)
|
||||
if (child.failed) {
|
||||
parts.push(`<b>Failed:</b> ${child.result}\n\n`)
|
||||
} else if (child.result) {
|
||||
parts.push(`<b>Result:</b>\n\`\`\`\n${child.result}\n\`\`\`\n\n`)
|
||||
}
|
||||
parts.push(`</details>`)
|
||||
}
|
||||
// Execute result as a separate spoiler (like a regular tool result)
|
||||
if (executeTool?.result) {
|
||||
parts.push(`<details>\n<summary>🔧 execute</summary>\n\n`)
|
||||
parts.push(`<b>Result:</b>\n\`\`\`\n${executeTool.result}\n\`\`\`\n\n`)
|
||||
parts.push(`</details>`)
|
||||
}
|
||||
} else {
|
||||
// No child tool calls — show tools normally (including execute as-is)
|
||||
for (const tool of step.tools) {
|
||||
parts.push(`<details>\n<summary>🔧 ${tool.name}</summary>\n\n`)
|
||||
parts.push(`<b>Args:</b>\n\`\`\`\n${tool.args}\n\`\`\`\n\n`)
|
||||
|
|
@ -609,7 +343,6 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
|||
parts.push(`</details>`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator before text
|
||||
if (step.text) {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
{
|
||||
"name": "matrix-plugin",
|
||||
"name": "opencode-matrix-plugin",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "matrix-plugin",
|
||||
"name": "opencode-matrix-plugin",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
|
||||
"@opencode/plugin": "^2.0.16",
|
||||
"acorn": "^8.18.0",
|
||||
"acorn-walk": "^8.3.5",
|
||||
"marked": "^15.0.0",
|
||||
"matrix-bot-sdk": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -1662,12 +1660,12 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
|
||||
"integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
"undici-types": "~8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
|
|
@ -1764,30 +1762,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
||||
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
|
|
@ -5558,9 +5532,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
||||
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -10,13 +10,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "^2.0.16",
|
||||
"acorn": "^8.18.0",
|
||||
"acorn-walk": "^8.3.5",
|
||||
"marked": "^15.0.0",
|
||||
"matrix-bot-sdk": "^0.8.0"
|
||||
"matrix-bot-sdk": "^0.8.0",
|
||||
"marked": "^15.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
"typescript": "^5.7.0",
|
||||
"@types/node": "^20.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue