67 lines
2.9 KiB
JavaScript
67 lines
2.9 KiB
JavaScript
/**
|
||
* v0.4.0 冒烟:业务工具注册 / 手动直调(D2 双模式路径)/ tool_call 拦截契约
|
||
* 说明:审批拦截逻辑与 src/main/pi/approval.ts 保持一致(此处内联同构实现,
|
||
* 因冒烟脚本无法直接加载 TS 源文件;拦截契约由 pi 的 ExtensionHandler 类型保证)。
|
||
*/
|
||
import { createAgentSessionFromServices, createAgentSessionServices, defineTool, getAgentDir, SessionManager } from '@earendil-works/pi-coding-agent'
|
||
import { Type } from '@earendil-works/pi-ai'
|
||
import { homedir } from 'node:os'
|
||
|
||
const cwd = homedir()
|
||
const agentDir = getAgentDir()
|
||
|
||
// 与 src/main/tools/demo-tools.ts 同构的工具定义
|
||
const hotwordLookup = defineTool({
|
||
name: 'hotword_lookup',
|
||
label: '热点词查询(演示)',
|
||
description: 'demo',
|
||
parameters: Type.Object({ query: Type.String(), limit: Type.Optional(Type.Number()) }),
|
||
async execute(_id, params) {
|
||
return {
|
||
content: [{ type: 'text', text: `热点词查询结果(演示数据源,query="${params.query}"):ok` }],
|
||
details: { source: 'demo' }
|
||
}
|
||
}
|
||
})
|
||
|
||
// ---- 1) 工具注册检查(customTools 路径) ----
|
||
const services = await createAgentSessionServices({ cwd, agentDir })
|
||
const { session } = await createAgentSessionFromServices({
|
||
services,
|
||
sessionManager: SessionManager.inMemory(cwd),
|
||
customTools: [hotwordLookup]
|
||
})
|
||
const all = session.getAllTools()
|
||
console.log('[smoke4] registered tools:', all.map((t) => t.name).join(', '))
|
||
console.log('[smoke4] hotword_lookup registered:', all.some((t) => t.name === 'hotword_lookup'))
|
||
|
||
// ---- 2) 手动直调(双模式共用能力的证明,不经 LLM) ----
|
||
const result = await hotwordLookup.execute('smoke-call', { query: '宠物' }, undefined, undefined, {})
|
||
console.log('[smoke4] manual execute text:', JSON.stringify(result.content[0]?.text?.slice(0, 60)))
|
||
|
||
// ---- 3) tool_call 拦截契约(模拟 ExtensionAPI 捕获 handler) ----
|
||
const handlers = {}
|
||
const fakePi = { on: (event, handler) => { handlers[event] = handler } }
|
||
const needsApproval = new Set(['price_check', 'bash'])
|
||
let decision = false
|
||
fakePi.on('tool_call', async (event) => {
|
||
if (!needsApproval.has(event.toolName)) return
|
||
const approved = decision
|
||
if (!approved) return { block: true, reason: '用户拒绝了本次工具调用审批' }
|
||
return
|
||
})
|
||
|
||
const passthrough = await handlers['tool_call']({ type: 'tool_call', toolName: 'read', input: { path: 'x' } })
|
||
console.log('[smoke4] read(不在名单)放行:', passthrough === undefined)
|
||
|
||
decision = false
|
||
const denied = await handlers['tool_call']({ type: 'tool_call', toolName: 'price_check', input: { product_id: 'P1', target_price: 9.9 } })
|
||
console.log('[smoke4] price_check 拒绝 -> block:', JSON.stringify(denied))
|
||
|
||
decision = true
|
||
const approvedRes = await handlers['tool_call']({ type: 'tool_call', toolName: 'bash', input: { command: 'ls' } })
|
||
console.log('[smoke4] bash 批准 -> 放行:', approvedRes === undefined)
|
||
|
||
console.log('[smoke4] done')
|
||
process.exit(0)
|