@raolin2025/claude-code-node 2.8.23 → 2.8.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/cli.js +53 -4
- package/src/core/query-engine.js +17 -152
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raolin2025/claude-code-node",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.25",
|
|
4
4
|
"description": "Node.js AI Code Agent CLI - Zero dependencies, pure JavaScript, security hardened, multi-channel notifications, Telegram & QQ Bot remote programming, rich media upload, multi-account management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/core/index.js",
|
package/src/core/cli.js
CHANGED
|
@@ -254,6 +254,9 @@ Commands:
|
|
|
254
254
|
/help — Show this help
|
|
255
255
|
/model NAME — Switch model
|
|
256
256
|
/models — List available models from current API
|
|
257
|
+
/api-base URL — Switch API base URL at runtime (local session only)
|
|
258
|
+
/api-key KEY — Switch API key at runtime (local session only)
|
|
259
|
+
/api — Show current API base/key/model
|
|
257
260
|
/tools — List available tools
|
|
258
261
|
/session — Show session info
|
|
259
262
|
/sessions — List all sessions
|
|
@@ -284,6 +287,11 @@ const DETAILED_HELP = {
|
|
|
284
287
|
|
|
285
288
|
models: "/models\n Fetch and display all available models from the current API provider.\n Shows a numbered list, then prompts you to select by number or name.\n Requires a configured API key (the one you used to start cc-node).\n Uses the endpoint: <apiBase>/models",
|
|
286
289
|
|
|
290
|
+
apiBase:"/api-base <url>\n Switch the LLM API base URL at runtime (session-only, not persisted).\n Takes effect immediately for the next message.\n After switching, cc-node re-probes the context window.\n Then use /model <name> or /models to pick the new provider's model.\n For local llama.cpp/Ollama (no auth) you can skip /api-key.\n\n Example: /api-base http://127.0.0.1:11434/v1",
|
|
291
|
+
apiKey: "/api-key <key>\n Switch the LLM API key at runtime (session-only, not persisted).\n Takes effect immediately for the next message.\n For local llama.cpp/Ollama (no auth) you can leave it unset.\n\n Example: /api-key sk-xxxxxxxx",
|
|
292
|
+
|
|
293
|
+
api: "/api\n Show the current API configuration: base URL, key (masked), and model.",
|
|
294
|
+
|
|
287
295
|
tools: "/tools\n List all available tools that cc-node can use.\n Shows tool names with their short descriptions.\n\n Tools include: Bash, Read, Edit, Write, Glob, Grep,\n WebFetch, WebSearch, AskUserQuestion, GitTool",
|
|
288
296
|
|
|
289
297
|
session: "/session\n Show current session information:\n - Session ID\n - Session title\n - Number of messages\n - Number of tool call turns",
|
|
@@ -374,9 +382,9 @@ Options:
|
|
|
374
382
|
-v, --verbose Verbose mode
|
|
375
383
|
--no-stream Disable streaming
|
|
376
384
|
--max-messages N Fold history when message count exceeds N (default: 0 = off)
|
|
377
|
-
--small-model
|
|
385
|
+
--small-model Reserved flag (no-op now — tool engine uses plain mode like v2.8.11)
|
|
378
386
|
--max-output-tokens N Override max single-response output tokens (default: computed from window size)
|
|
379
|
-
--small-model-max-turns N
|
|
387
|
+
--small-model-max-turns N Reserved (no-op — tool loop uses maxTurns)
|
|
380
388
|
--with-notify Start built-in channel listener (Telegram)
|
|
381
389
|
(replaces cc-notify daemon — no external script needed)
|
|
382
390
|
-h, --help Show this help
|
|
@@ -425,7 +433,7 @@ export async function main() {
|
|
|
425
433
|
const config = new Config()
|
|
426
434
|
await config.load(process.cwd())
|
|
427
435
|
|
|
428
|
-
|
|
436
|
+
let apiBase = cliArgs.apiBase || config.get('apiBase') || process.env.LLM_API_BASE || ''
|
|
429
437
|
// apiBase 指向自建本地服务(Ollama / llama.cpp / vLLM 等)时允许缺省 apiKey
|
|
430
438
|
const localApiBase = isLocalLlmServer(apiBase)
|
|
431
439
|
let model = cliArgs.model || config.get('model') || ''
|
|
@@ -488,7 +496,7 @@ export async function main() {
|
|
|
488
496
|
const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
|
|
489
497
|
const permissionMode = cliArgs.permissionMode || config.get('permissionMode')
|
|
490
498
|
const maxTurns = cliArgs.maxTurns || config.get('maxTurns')
|
|
491
|
-
|
|
499
|
+
let apiKey = cliArgs.apiKey || config.get('apiKey') || ''
|
|
492
500
|
const verbose = cliArgs.verbose || config.get('verbose')
|
|
493
501
|
|
|
494
502
|
const registry = createDefaultRegistry()
|
|
@@ -749,6 +757,47 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
|
|
|
749
757
|
}
|
|
750
758
|
else console.log(`Model: ${engine.config.model}`)
|
|
751
759
|
break
|
|
760
|
+
case 'api-base':
|
|
761
|
+
// /api-base <url> — 运行时切换 LLM 来源地址(仅本次会话生效)
|
|
762
|
+
if (rest[0]) {
|
|
763
|
+
const newBase = rest.join(' ').trim().replace(/\/+$/, '')
|
|
764
|
+
apiBase = newBase
|
|
765
|
+
engine.config.apiBase = newBase
|
|
766
|
+
console.log(`API Base → ${newBase}`)
|
|
767
|
+
// 切换来源后重新探测上下文窗口(旧来源窗口可能不同)
|
|
768
|
+
const manual = config.get('maxBudgetTokens') || 0
|
|
769
|
+
try {
|
|
770
|
+
const { window: win, source } = await detectContextWindow({ model, apiBase: newBase, apiKey, manualWindow: manual })
|
|
771
|
+
tokenBudget.setWindow(win, source)
|
|
772
|
+
windowSource = source
|
|
773
|
+
console.log(` ↳ Context window → ${formatTokens(win)} (${windowSourceLabel(source)})`)
|
|
774
|
+
} catch (e) {
|
|
775
|
+
console.log(` ⚠️ 窗口探测失败: ${e.message}`)
|
|
776
|
+
}
|
|
777
|
+
// 提示切模型:不同来源的模型名不同
|
|
778
|
+
const localNow = isLocalLlmServer(newBase)
|
|
779
|
+
if (!localNow && !apiKey) {
|
|
780
|
+
console.log(' ⚠️ 新来源需 API Key → 请用 /api-key <key> 设置')
|
|
781
|
+
}
|
|
782
|
+
console.log(' ℹ️ 不同模型的名称不同,请用 /model <名称> 选择新来源的模型,或用 /models 查看')
|
|
783
|
+
}
|
|
784
|
+
else console.log(`API Base: ${engine.config.apiBase}`)
|
|
785
|
+
break
|
|
786
|
+
case 'api-key':
|
|
787
|
+
// /api-key <key> — 运行时切换 LLM API Key(仅本次会话生效)
|
|
788
|
+
if (rest[0]) {
|
|
789
|
+
const newKey = rest.join(' ').trim()
|
|
790
|
+
apiKey = newKey
|
|
791
|
+
engine.config.apiKey = newKey
|
|
792
|
+
console.log(`API Key → ${newKey.slice(0, 4)}...${newKey.slice(-4)}`)
|
|
793
|
+
}
|
|
794
|
+
else console.log(`API Key: ${engine.config.apiKey ? `已设置 (${engine.config.apiKey.slice(0, 4)}...${engine.config.apiKey.slice(-4)})` : '未设置'}`)
|
|
795
|
+
break
|
|
796
|
+
case 'api':
|
|
797
|
+
console.log(`API Base: ${engine.config.apiBase}`)
|
|
798
|
+
console.log(`API Key: ${engine.config.apiKey ? `已设置 (${engine.config.apiKey.slice(0, 4)}...${engine.config.apiKey.slice(-4)})` : '未设置'}`)
|
|
799
|
+
console.log(`Model: ${engine.config.model}`)
|
|
800
|
+
break
|
|
752
801
|
case 'window': {
|
|
753
802
|
const arg = rest[0] ? rest.join(' ').trim() : ''
|
|
754
803
|
if (!arg) {
|
package/src/core/query-engine.js
CHANGED
|
@@ -15,15 +15,6 @@ import crypto from 'crypto'
|
|
|
15
15
|
import { UserMessage, AssistantMessage, ToolCall, ToolResult, SessionState } from '../types/index.js'
|
|
16
16
|
import { parseStream, parseNonStreamResponse } from './streaming.js'
|
|
17
17
|
import { compactMessages, trimToWindow, foldHistoryByCount, trimToolResults } from './compact.js'
|
|
18
|
-
import {
|
|
19
|
-
isSmallModelEnabled,
|
|
20
|
-
buildSmallModelSystemPrompt,
|
|
21
|
-
isFillerResponse,
|
|
22
|
-
buildCombinedGuidance,
|
|
23
|
-
selectRelevantTools,
|
|
24
|
-
RETRY_GUIDANCE,
|
|
25
|
-
extractFrameworkAction,
|
|
26
|
-
} from './small-model.js'
|
|
27
18
|
import { CostTracker } from './cost-tracker.js'
|
|
28
19
|
import { EnhancedPermissionChecker } from '../security/enhanced-permission.js'
|
|
29
20
|
import { isLocalLlmServer, buildAuthHeaders } from '../utils/index.js'
|
|
@@ -97,8 +88,6 @@ export class QueryEngine {
|
|
|
97
88
|
this.tokenBudget = this.config.tokenBudget || null
|
|
98
89
|
// 最近一次 processMessage 是否已通过流式回调/直写把正文输出(供调用方避免重复打印 response)
|
|
99
90
|
this.lastStreamed = false
|
|
100
|
-
// 当前正在处理的用户输入(小模型模式用于按意图精简工具集)
|
|
101
|
-
this.currentUserInput = ''
|
|
102
91
|
}
|
|
103
92
|
|
|
104
93
|
/**
|
|
@@ -114,7 +103,6 @@ export class QueryEngine {
|
|
|
114
103
|
this.state.turnCount++
|
|
115
104
|
this.lastStreamed = false // 本轮是否已流式输出正文
|
|
116
105
|
this.abortController = new AbortController()
|
|
117
|
-
this.currentUserInput = userInput // 记录当前用户指令(小模型模式按意图精简工具)
|
|
118
106
|
const userMsg = new UserMessage(userInput, images)
|
|
119
107
|
this.state.messages.push(userMsg)
|
|
120
108
|
|
|
@@ -226,65 +214,17 @@ export class QueryEngine {
|
|
|
226
214
|
*/
|
|
227
215
|
async _runToolLoop(userMessage) {
|
|
228
216
|
let finalResponse = ''
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
// - 普通模式:用 config.maxTurns
|
|
238
|
-
const toolLoopLimit = smallModel
|
|
239
|
-
? (this.config.smallModelMaxTurns || 8)
|
|
240
|
-
: this.config.maxTurns
|
|
241
|
-
|
|
242
|
-
for (let turn = 0; turn < toolLoopLimit; turn++) {
|
|
217
|
+
|
|
218
|
+
// 朴素工具循环(v2.8.11 风格):
|
|
219
|
+
// 实测结论——小模型在"朴素模式"(16 个工具全上、无引导注入、无代执行、
|
|
220
|
+
// 无工具精简)下才能正常工作。v2.8.13 后我加的意图引导注入、工具精简、
|
|
221
|
+
// 多步计划、框架代执行等"辅助机制"反而让模型乱套(不调用工具、瞎编内容)。
|
|
222
|
+
// 因此这里【不做任何干预】,回归最原始行为:构建请求 → 调 LLM →
|
|
223
|
+
// 有工具调用就执行 → 循环。
|
|
224
|
+
for (let turn = 0; turn < this.config.maxTurns; turn++) {
|
|
243
225
|
// 发送前硬校验:工具结果可能已使上下文超窗,确保 ≤ 窗口(摘要优先 + 滑动窗口裁剪兜底)
|
|
244
226
|
this._ensureFitWindow()
|
|
245
227
|
|
|
246
|
-
// 小模型模式:首轮若识别到意图/多步计划,把引导【追加到首条 system 消息】。
|
|
247
|
-
// 注意:不能 push 一条新的 system 消息到中间——llama.cpp/Jinja 严格要求
|
|
248
|
-
// system 消息必须在对话开头,中间插 system 会报 "System message must be at the beginning" (500)。
|
|
249
|
-
if (smallModel && !intentGuided && this.state.messages.length > 0) {
|
|
250
|
-
const guidance = buildCombinedGuidance(userMessage.content, {
|
|
251
|
-
enable: true,
|
|
252
|
-
cwd: this.config.cwd,
|
|
253
|
-
})
|
|
254
|
-
if (guidance) {
|
|
255
|
-
// 找到第一条 system 消息,把引导拼接进去(保持 system 全在开头)
|
|
256
|
-
const sysIdx = this.state.messages.findIndex(m => m.role === 'system')
|
|
257
|
-
if (sysIdx !== -1) {
|
|
258
|
-
const sysMsg = this.state.messages[sysIdx]
|
|
259
|
-
this.state.messages[sysIdx] = {
|
|
260
|
-
...sysMsg,
|
|
261
|
-
content: (typeof sysMsg.content === 'string' ? sysMsg.content : '') + '\n\n' + guidance,
|
|
262
|
-
}
|
|
263
|
-
} else {
|
|
264
|
-
// 没有 system 消息 → 作为 user 引导插入(跟随在最新 user 之后作为新 user)
|
|
265
|
-
this.state.messages.push({ role: 'user', content: guidance })
|
|
266
|
-
}
|
|
267
|
-
if (this.config.verbose) console.error('[small-model] 已注入引导(并入首条 system):' + guidance.slice(0, 60) + '...')
|
|
268
|
-
}
|
|
269
|
-
intentGuided = true
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// 小模型模式:接近工具循环上限时,注入"总结收尾"引导,防止模型无限循环
|
|
273
|
-
if (smallModel && turn === toolLoopLimit - 1) {
|
|
274
|
-
const stopGuidance = `[系统提示] 你已经完成了大部分工作。现在请【停止调用新工具】,把已经完成的内容整理成一段总结回复给用户。如果确实还有关键步骤未完成,只再调用一次工具完成它,然后立即总结。不要再继续无休止地调用工具。`
|
|
275
|
-
const sysIdx = this.state.messages.findIndex(m => m.role === 'system')
|
|
276
|
-
if (sysIdx !== -1) {
|
|
277
|
-
const sysMsg = this.state.messages[sysIdx]
|
|
278
|
-
this.state.messages[sysIdx] = {
|
|
279
|
-
...sysMsg,
|
|
280
|
-
content: (typeof sysMsg.content === 'string' ? sysMsg.content : '') + '\n\n' + stopGuidance,
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
this.state.messages.push({ role: 'user', content: stopGuidance })
|
|
284
|
-
}
|
|
285
|
-
if (this.config.verbose) console.error(`[small-model] 已到工具循环第 ${turn + 1} 轮(上限 ${toolLoopLimit}),注入收尾引导`)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
228
|
const requestMessages = this._buildRequest(this.state.messages)
|
|
289
229
|
const response = await this._callLLM(requestMessages, this.state.messages)
|
|
290
230
|
|
|
@@ -292,32 +232,8 @@ export class QueryEngine {
|
|
|
292
232
|
throw new Error('操作已取消')
|
|
293
233
|
}
|
|
294
234
|
|
|
295
|
-
// 没有工具调用 →
|
|
235
|
+
// 没有工具调用 → 最终回复
|
|
296
236
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
297
|
-
// 小模型模式:检测敷衍输出(空话/太短/命中占位句)
|
|
298
|
-
if (smallModel && isFillerResponse(response)) {
|
|
299
|
-
// 第一次敷衍 → 追加强引导重试
|
|
300
|
-
if (fillerRetries < MAX_FILLER_RETRIES) {
|
|
301
|
-
fillerRetries++
|
|
302
|
-
if (this.config.verbose) {
|
|
303
|
-
console.error(`[small-model] 检测到敷衍输出(第 ${fillerRetries} 次),追加强引导重试`)
|
|
304
|
-
}
|
|
305
|
-
this.state.messages.push(new AssistantMessage(response.content, [], response.reasoningContent))
|
|
306
|
-
this.state.messages.push({ role: 'user', content: RETRY_GUIDANCE })
|
|
307
|
-
continue
|
|
308
|
-
}
|
|
309
|
-
// 重试后仍敷衍 → 框架代执行,且【直接把结果作为最终答案】返回,
|
|
310
|
-
// 不再 continue 让模型总结——模型已证明它只会敷衍,继续循环只会无限重复。
|
|
311
|
-
// 这样既完成任务,又不会陷入"代执行→敷衍→再代执行"的死循环。
|
|
312
|
-
const execResult = await this._frameworkExecute(userMessage.content)
|
|
313
|
-
if (execResult.done) {
|
|
314
|
-
if (this.config.verbose) console.error(`[small-model] 框架代执行完成,直接返回结果`)
|
|
315
|
-
// 框架已执行并把结果注入对话;直接把真实结果作为最终回复
|
|
316
|
-
finalResponse = execResult.resultText
|
|
317
|
-
break
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
237
|
finalResponse = response.content
|
|
322
238
|
this.state.messages.push(new AssistantMessage(response.content, [], response.reasoningContent))
|
|
323
239
|
break
|
|
@@ -344,9 +260,8 @@ export class QueryEngine {
|
|
|
344
260
|
}
|
|
345
261
|
}
|
|
346
262
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
finalResponse = `[已达到工具循环上限 (${toolLoopLimit}),已停止进一步调用工具。请查看上方工具执行结果,确认任务完成情况。]`
|
|
263
|
+
if (!finalResponse && this.state.turnCount >= this.config.maxTurns) {
|
|
264
|
+
finalResponse = `[达到最大回合数限制 (${this.config.maxTurns}),停止响应]`
|
|
350
265
|
}
|
|
351
266
|
|
|
352
267
|
return {
|
|
@@ -388,12 +303,10 @@ export class QueryEngine {
|
|
|
388
303
|
_buildRequest(messages) {
|
|
389
304
|
const request = []
|
|
390
305
|
|
|
391
|
-
//
|
|
306
|
+
// 系统提示(朴素模式:直接用基础 system prompt,不做 cwd/工具铁律等注入——
|
|
307
|
+
// 实测 v2.8.11 朴素模式能正常工作,注入反而干扰模型)
|
|
392
308
|
if (this.config.systemPrompt) {
|
|
393
|
-
|
|
394
|
-
? buildSmallModelSystemPrompt(this.config.systemPrompt, { cwd: this.config.cwd })
|
|
395
|
-
: this.config.systemPrompt
|
|
396
|
-
request.push({ role: 'system', content: sysContent })
|
|
309
|
+
request.push({ role: 'system', content: this.config.systemPrompt })
|
|
397
310
|
}
|
|
398
311
|
|
|
399
312
|
// 历史消息 — 转换为 OpenAI 兼容格式
|
|
@@ -512,47 +425,6 @@ export class QueryEngine {
|
|
|
512
425
|
return results
|
|
513
426
|
}
|
|
514
427
|
|
|
515
|
-
/**
|
|
516
|
-
* 框架代执行 — 模型敷衍调不起工具时,框架直接替它执行最合理的工具
|
|
517
|
-
*
|
|
518
|
-
* 背景:27B Q3 量化模型工具调用极弱,即使强引导也常只回"研究一下"而不调工具。
|
|
519
|
-
* 此时框架【绕过模型】,根据指令推断该执行什么工具并直接运行,把真实结果
|
|
520
|
-
* 注入对话,再让模型基于结果总结——"一句话调用小模型工作"才能成立。
|
|
521
|
-
*
|
|
522
|
-
* @param {string} userInput — 用户指令
|
|
523
|
-
* @returns {Promise<{ done: boolean, summary: string }>}
|
|
524
|
-
* done=true 表示框架已执行工具(结果已注入 state.messages)
|
|
525
|
-
*/
|
|
526
|
-
async _frameworkExecute(userInput) {
|
|
527
|
-
const action = extractFrameworkAction(userInput, { cwd: this.config.cwd })
|
|
528
|
-
if (!action) return { done: false, summary: '', resultText: '' }
|
|
529
|
-
|
|
530
|
-
// 找到对应的工具
|
|
531
|
-
const tool = this.config.tools.find(t => t.name === action.tool)
|
|
532
|
-
if (!tool) return { done: false, summary: '', resultText: '' }
|
|
533
|
-
|
|
534
|
-
// 执行工具(直接调用 handler)
|
|
535
|
-
try {
|
|
536
|
-
const content = await tool.handler(action.input, { cwd: this.config.cwd, engine: this, readline: this.config.readline })
|
|
537
|
-
|
|
538
|
-
const resultText = typeof content === 'string' ? content : JSON.stringify(content)
|
|
539
|
-
|
|
540
|
-
// 注入框架代执行结果(作为 user 消息,避免 tool 消息无对应 assistant tool_call 报错)
|
|
541
|
-
// 说明:框架代执行不是模型发起的工具调用,不能作为 tool 角色(会缺 assistant tool_call)。
|
|
542
|
-
this.state.messages.push({
|
|
543
|
-
role: 'user',
|
|
544
|
-
content: `[框架代执行结果] 框架已替你执行工具 ${tool.name}(参数 ${JSON.stringify(action.input)}),结果如下:\n\n${resultText.slice(0, 8000)}`,
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
const summary = `框架已代执行 ${tool.name}(${JSON.stringify(action.input)})`
|
|
548
|
-
if (this.config.verbose) console.error(`[small-model] 框架代执行 ${tool.name} 完成`)
|
|
549
|
-
return { done: true, summary, resultText }
|
|
550
|
-
} catch (err) {
|
|
551
|
-
if (this.config.verbose) console.error(`[small-model] 框架代执行失败: ${err.message}`)
|
|
552
|
-
return { done: false, summary: '', resultText: '' }
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
428
|
async _callLLM(messages, contextMessages) {
|
|
557
429
|
const apiKey = this.config.apiKey
|
|
558
430
|
const apiBase = this.config.apiBase
|
|
@@ -580,16 +452,9 @@ export class QueryEngine {
|
|
|
580
452
|
}
|
|
581
453
|
|
|
582
454
|
// 构建工具定义
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const reduced = selectRelevantTools(this.config.tools, this.currentUserInput, { enable: true })
|
|
587
|
-
effectiveTools = reduced
|
|
588
|
-
if (this.config.verbose && reduced.length !== this.config.tools.length) {
|
|
589
|
-
console.error(`[small-model] 按意图精简工具:${this.config.tools.length} → ${reduced.length}(${reduced.map(t => t.name).join(', ')})`)
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const tools = effectiveTools.map(t => ({
|
|
455
|
+
// 朴素模式(v2.8.11 风格):16 个工具全上,不做精简。
|
|
456
|
+
// 实测结论——小模型在工具全给时才能正常调用;按意图精简反而让它不会调用、瞎编。
|
|
457
|
+
const tools = this.config.tools.map(t => ({
|
|
593
458
|
type: 'function',
|
|
594
459
|
function: { name: t.name, description: t.description, parameters: t.parameters },
|
|
595
460
|
}))
|