fix: smarter CodeMode parse — skip execute, show children with extracted args
Проблемы исправлены: - execute с детьми больше не показывается дубликатом - Args извлекаются из кода балансирующим парсером скобок - Показываются только дочерние инструменты с их аргументами из кода - execute без детей работает как раньше
This commit is contained in:
parent
6dc6cf41c4
commit
a829e0b114
128
index.ts
128
index.ts
|
|
@ -137,18 +137,77 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
||||||
// e.g. tools.duckduckgo.search({ query: "..." })
|
// e.g. tools.duckduckgo.search({ query: "..." })
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
function parseCodeModeTools(code: string): string[] {
|
interface ChildToolInfo {
|
||||||
const tools: string[] = []
|
name: string
|
||||||
// Match tools.<word>.<word>( — nested tool calls in CodeMode
|
args: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCodeModeTools(code: string): ChildToolInfo[] {
|
||||||
|
const results: ChildToolInfo[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
const regex = /tools\.(\w+)\.(\w+)\s*\(/g
|
const regex = /tools\.(\w+)\.(\w+)\s*\(/g
|
||||||
let match
|
let match
|
||||||
|
|
||||||
while ((match = regex.exec(code)) !== null) {
|
while ((match = regex.exec(code)) !== null) {
|
||||||
const toolName = `${match[1]}_${match[2]}`
|
const toolName = `${match[1]}_${match[2]}`
|
||||||
if (!tools.includes(toolName)) {
|
if (seen.has(toolName)) continue
|
||||||
tools.push(toolName)
|
seen.add(toolName)
|
||||||
|
|
||||||
|
// Extract the full argument block starting from the (
|
||||||
|
const openParenPos = match.index + match[0].indexOf("(")
|
||||||
|
const args = extractArgumentBlock(code, openParenPos)
|
||||||
|
results.push({ name: toolName, args })
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract argument block from code starting at the opening parenthesis.
|
||||||
|
* Handles nested parentheses, brackets, and braces.
|
||||||
|
* e.g. for tools.duckduckgo.fetch_content({ url: url, max_length: 5000 })
|
||||||
|
* returns "{ url: url, max_length: 5000 }"
|
||||||
|
*/
|
||||||
|
function extractArgumentBlock(code: string, openParenPos: number): string {
|
||||||
|
let depth = 0
|
||||||
|
let start = openParenPos
|
||||||
|
let end = openParenPos
|
||||||
|
|
||||||
|
// Find the opening character: (, {, [, or "
|
||||||
|
while (start < code.length) {
|
||||||
|
const ch = code[start]
|
||||||
|
if (ch === "(" || ch === "{" || ch === "[" || ch === '"') break
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start >= code.length) return "(variable)"
|
||||||
|
|
||||||
|
const openCh = code[start]
|
||||||
|
const closeCh = openCh === "(" ? ")" : openCh === "{" ? "}" : openCh === "[" ? "]" : '"'
|
||||||
|
|
||||||
|
for (let i = start; i < code.length; i++) {
|
||||||
|
const ch = code[i]
|
||||||
|
if (ch === openCh) depth++
|
||||||
|
else if (ch === closeCh) {
|
||||||
|
depth--
|
||||||
|
if (depth === 0) {
|
||||||
|
end = i + 1
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tools
|
// Skip string literals to avoid counting quotes inside strings
|
||||||
|
if (ch === '"' || ch === "'") {
|
||||||
|
let j = i + 1
|
||||||
|
while (j < code.length && code[j] !== ch) {
|
||||||
|
if (code[j] === "\\") j++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = code.slice(start, end).trim()
|
||||||
|
return result || "(variable)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -235,9 +294,15 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
||||||
const code = parsedArgs?.code || ""
|
const code = parsedArgs?.code || ""
|
||||||
if (code) {
|
if (code) {
|
||||||
const childTools = parseCodeModeTools(code)
|
const childTools = parseCodeModeTools(code)
|
||||||
for (const name of childTools) {
|
for (const child of childTools) {
|
||||||
if (!step.tools.find(t => t.name === name)) {
|
if (!step.tools.find(t => t.name === child.name)) {
|
||||||
step.tools.push({ toolID: `${toolID}/${name}`, name, args: "", result: "", failed: false })
|
step.tools.push({
|
||||||
|
toolID: `${toolID}/${child.name}`,
|
||||||
|
name: child.name,
|
||||||
|
args: child.args,
|
||||||
|
result: "",
|
||||||
|
failed: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -366,19 +431,19 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
||||||
|
|
||||||
// Tool calls
|
// Tool calls
|
||||||
if (step.tools.length > 0) {
|
if (step.tools.length > 0) {
|
||||||
for (const tool of step.tools) {
|
// Check if any tool is an execute with child tool calls
|
||||||
// Check if this is an execute tool with child tool calls
|
const executeTool = step.tools.find(
|
||||||
// Child tools have toolID like "parentID/toolName"
|
(t) => t.name === "execute" && t.toolID.includes("/"),
|
||||||
const isExecuteWithChildren = tool.name === "execute" && tool.toolID.includes("/")
|
)
|
||||||
const childTools = isExecuteWithChildren ? step.tools.filter(t => t.toolID.startsWith(tool.toolID + "/")) : []
|
const childTools = executeTool
|
||||||
|
? step.tools.filter((t) => t.toolID.startsWith(executeTool.toolID + "/"))
|
||||||
|
: []
|
||||||
|
|
||||||
if (isExecuteWithChildren && childTools.length > 0) {
|
if (childTools.length > 0) {
|
||||||
// Expand execute → individual spoilers for each child tool
|
// Execute has child tool calls — show only the children, skip execute
|
||||||
for (const child of childTools) {
|
for (const child of childTools) {
|
||||||
// Extract args from the code for this child tool
|
|
||||||
const childArgs = extractChildToolArgs(tool.args, child.name)
|
|
||||||
parts.push(`<details>\n<summary>🔧 ${child.name}</summary>\n\n`)
|
parts.push(`<details>\n<summary>🔧 ${child.name}</summary>\n\n`)
|
||||||
parts.push(`<b>Args:</b>\n\`\`\`\n${childArgs}\n\`\`\`\n\n`)
|
parts.push(`<b>Args:</b>\n\`\`\`\n${child.args}\n\`\`\`\n\n`)
|
||||||
if (child.failed) {
|
if (child.failed) {
|
||||||
parts.push(`<b>Failed:</b> ${child.result}\n\n`)
|
parts.push(`<b>Failed:</b> ${child.result}\n\n`)
|
||||||
} else if (child.result) {
|
} else if (child.result) {
|
||||||
|
|
@ -387,7 +452,8 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
||||||
parts.push(`</details>`)
|
parts.push(`</details>`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal tool (or execute without children)
|
// 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(`<details>\n<summary>🔧 ${tool.name}</summary>\n\n`)
|
||||||
parts.push(`<b>Args:</b>\n\`\`\`\n${tool.args}\n\`\`\`\n\n`)
|
parts.push(`<b>Args:</b>\n\`\`\`\n${tool.args}\n\`\`\`\n\n`)
|
||||||
if (tool.failed) {
|
if (tool.failed) {
|
||||||
|
|
@ -409,28 +475,6 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
|
||||||
return parts.join("\n")
|
return parts.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract args for a child tool from the execute code
|
|
||||||
function extractChildToolArgs(code: string, toolName: string): string {
|
|
||||||
try {
|
|
||||||
const args = JSON.parse(code)
|
|
||||||
const codeStr = args?.code || ""
|
|
||||||
// Match tools.<server>.<toolName>(...) and extract the arguments
|
|
||||||
const regex = new RegExp(`tools\\.\\w+\\.${escapeRegex(toolName.replace("_", "."))}\\s*\\(([^)]*)\\)`, "g")
|
|
||||||
const matches: string[] = []
|
|
||||||
let match
|
|
||||||
while ((match = regex.exec(codeStr)) !== null) {
|
|
||||||
matches.push(match[1].trim())
|
|
||||||
}
|
|
||||||
return matches.join("\n") || "(parsed from code)"
|
|
||||||
} catch {
|
|
||||||
return "(parsed from code)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeRegex(str: string): string {
|
|
||||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Send message to Matrix (reuses existing sendReply/sendNotice)
|
// Send message to Matrix (reuses existing sendReply/sendNotice)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue