返回 oh-my-ppt
agent-tool-logger.ts
根目录 / src / main / utils / agent-tool-logger.ts
1 import log from 'electron-log/main.js'
2 import {
3 PRODUCT_SKILLS_ROUTE,
4 REQUIRED_PRODUCT_SKILL_NAMES,
5 type RequiredProductSkillName
6 } from '../product-skills/contract'
7
8 const getRecord = (value: unknown): Record<string, unknown> | null =>
9 value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : null
10
11 const readField = (record: Record<string, unknown>, key: string): unknown => {
12 const direct = record[key]
13 if (direct !== undefined) return direct
14 const kwargs = getRecord(record.kwargs)
15 return kwargs?.[key]
16 }
17
18 export const previewStr = (value: unknown, max = 240): string => {
19 const source =
20 typeof value === 'string'
21 ? value
22 : (() => {
23 try {
24 return JSON.stringify(value)
25 } catch {
26 return String(value)
27 }
28 })()
29 const compact = source.replace(/\s+/g, ' ').trim()
30 return compact.length > max ? `${compact.slice(0, max)}...` : compact
31 }
32
33 const parseToolArgs = (value: unknown): Record<string, unknown> | null => {
34 if (typeof value === 'string') {
35 const source = value.trim()
36 if (!source) return null
37 try {
38 return getRecord(JSON.parse(source))
39 } catch {
40 return null
41 }
42 }
43 return getRecord(value)
44 }
45
46 const readToolPathArg = (args: Record<string, unknown> | null): string => {
47 if (!args) return ''
48 const value =
49 args.path ??
50 args.file_path ??
51 args.filePath ??
52 args.absolute_path ??
53 args.absolutePath
54 return typeof value === 'string' ? value.trim() : ''
55 }
56
57 const resolveProductSkillName = (filePath: string): RequiredProductSkillName | null => {
58 const normalized = filePath.replace(/\\/g, '/')
59 if (!normalized.startsWith(PRODUCT_SKILLS_ROUTE)) return null
60 for (const skillName of REQUIRED_PRODUCT_SKILL_NAMES) {
61 if (normalized.includes(`/${skillName}/`)) return skillName
62 }
63 return null
64 }
65
66 export function logAgentToolEvents(
67 data: unknown,
68 seen: Set<string>,
69 options: { tag: string; source: 'updates' | 'messages' }
70 ): void {
71 const { tag, source } = options
72
73 const visitMessage = (msg: unknown): void => {
74 const record = getRecord(msg)
75 if (!record) return
76
77 const toolCallsSources = [
78 readField(record, 'tool_calls'),
79 readField(record, 'tool_call_chunks'),
80 getRecord(readField(record, 'additional_kwargs'))?.tool_calls
81 ]
82 for (const calls of toolCallsSources) {
83 if (!Array.isArray(calls)) continue
84 for (const call of calls) {
85 const callRecord = getRecord(call)
86 if (!callRecord) continue
87 const fnRecord = getRecord(callRecord.function)
88 const rawArgs = callRecord.args ?? callRecord.arguments ?? fnRecord?.arguments ?? ''
89 const name = String(callRecord.name ?? fnRecord?.name ?? '').trim()
90 const id = String(callRecord.id ?? callRecord.tool_call_id ?? '').trim()
91 if (!name && !id) continue
92 const argsText = previewStr(rawArgs)
93 const key = `call:${id}:${name}:${argsText}`
94 if (seen.has(key)) continue
95 seen.add(key)
96 log.info(`[${tag}] tool_call`, {
97 source,
98 toolName: name || null,
99 toolCallId: id || null,
100 argsLength: typeof rawArgs === 'string' ? rawArgs.length : 0,
101 argsPreview: argsText
102 })
103
104 if (name === 'read_file') {
105 const filePath = readToolPathArg(parseToolArgs(rawArgs))
106 const skillName = filePath ? resolveProductSkillName(filePath) : null
107 if (skillName) {
108 log.info(`[${tag}] product_skill_read_file`, {
109 source,
110 toolName: name,
111 toolCallId: id || null,
112 skillName,
113 path: filePath
114 })
115 }
116 }
117 }
118 }
119
120 const messageType = String(readField(record, 'type') ?? readField(record, 'role') ?? '')
121 const toolCallId = String(readField(record, 'tool_call_id') ?? '').trim()
122 const toolName = String(readField(record, 'name') ?? '').trim()
123 if (toolCallId || messageType === 'tool') {
124 const content = readField(record, 'content')
125 const contentLen = typeof content === 'string' ? content.length : 0
126 const key = `result:${toolCallId}:${toolName}:${contentLen}`
127 if (!seen.has(key)) {
128 seen.add(key)
129 log.info(`[${tag}] tool_result`, {
130 source,
131 toolName: toolName || null,
132 toolCallId: toolCallId || null,
133 contentLength: contentLen
134 })
135 }
136 }
137 }
138
139 const visit = (value: unknown): void => {
140 if (Array.isArray(value)) {
141 value.forEach(visit)
142 return
143 }
144 const record = getRecord(value)
145 if (!record) return
146 if (
147 readField(record, 'tool_calls') !== undefined ||
148 readField(record, 'tool_call_chunks') !== undefined ||
149 readField(record, 'tool_call_id') !== undefined ||
150 readField(record, 'role') === 'tool' ||
151 readField(record, 'type') === 'tool'
152 ) {
153 visitMessage(record)
154 }
155 for (const nested of Object.values(record)) {
156 if (nested && typeof nested === 'object') visit(nested)
157 }
158 }
159
160 visit(data)
161 }
162
162 lines TYPESCRIPT