agent-termo 1.0.1 → 1.0.2
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/README.md +27 -2
- package/dist/agents/index.d.ts +5 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +86 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -168
- package/dist/cli.js.map +4 -4
- package/dist/diff/index.d.ts +9 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +213 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/llm/client.d.ts +39 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +188 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/models.d.ts +19 -0
- package/dist/llm/models.d.ts.map +1 -0
- package/dist/llm/models.js +45 -0
- package/dist/llm/models.js.map +1 -0
- package/dist/llm/tools.d.ts +3 -0
- package/dist/llm/tools.d.ts.map +1 -0
- package/dist/llm/tools.js +204 -0
- package/dist/llm/tools.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +91 -140
- package/dist/main.js.map +4 -4
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +117 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +334 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +119 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/index.d.ts +47 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +321 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/screen.d.ts +59 -0
- package/dist/ui/screen.d.ts.map +1 -0
- package/dist/ui/screen.js +629 -0
- package/dist/ui/screen.js.map +1 -0
- package/dist/utils/ansi.d.ts +62 -0
- package/dist/utils/ansi.d.ts.map +1 -0
- package/dist/utils/ansi.js +116 -0
- package/dist/utils/ansi.js.map +1 -0
- package/dist/utils/config.d.ts +9 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +120 -0
- package/dist/utils/config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/llm/client.ts +24 -0
- package/src/llm/models.ts +55 -0
- package/src/main.ts +201 -75
- package/src/ui/index.ts +256 -389
- package/src/ui/screen.ts +698 -0
- package/src/utils/config.ts +37 -20
package/src/main.ts
CHANGED
|
@@ -8,8 +8,9 @@ import { executeTool, applyFileChange } from './tools/index.js';
|
|
|
8
8
|
import { loadMemory, logToTimeline, logAgentTermoTimeline } from './memory/index.js';
|
|
9
9
|
import { loadAllAgents } from './agents/index.js';
|
|
10
10
|
import { buildSystemPrompt } from './agents/index.js';
|
|
11
|
-
import { resolveProjectRoot } from './utils/config.js';
|
|
11
|
+
import { resolveProjectRoot, saveNimModel } from './utils/config.js';
|
|
12
12
|
import {
|
|
13
|
+
MarkdownPrinter,
|
|
13
14
|
printWelcome,
|
|
14
15
|
printUserMessage,
|
|
15
16
|
printAssistantMessage,
|
|
@@ -26,7 +27,9 @@ import {
|
|
|
26
27
|
stopSpinner,
|
|
27
28
|
clearScreen,
|
|
28
29
|
createInput,
|
|
29
|
-
|
|
30
|
+
initScreen,
|
|
31
|
+
pick,
|
|
32
|
+
setScreenStatus,
|
|
30
33
|
promptAcceptReject,
|
|
31
34
|
} from './ui/index.js';
|
|
32
35
|
import type { ConversationMessage, ToolCall, ProposedFileChange } from './types/index.js';
|
|
@@ -53,6 +56,85 @@ function dedup(arr: ToolCall[]): ToolCall[] {
|
|
|
53
56
|
});
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Get a plain-text assistant response without tool calling: stream first, fall
|
|
61
|
+
* back to a single non-streaming completion. Appends the assistant message to
|
|
62
|
+
* the conversation and prints it. Never throws: if both the stream and the
|
|
63
|
+
* retry fail, an LLM error is printed and no message is appended.
|
|
64
|
+
*/
|
|
65
|
+
async function replyWithText(
|
|
66
|
+
conversation: ConversationMessage[],
|
|
67
|
+
nimClient: NimClient,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
startSpinner('Thinking');
|
|
70
|
+
const md = new MarkdownPrinter(() => stopSpinner());
|
|
71
|
+
try {
|
|
72
|
+
const gen = nimClient.streamComplete(conversation, undefined, (text: string) => md.push(text));
|
|
73
|
+
// The generator streams everything, then yields once at the end; the
|
|
74
|
+
// chunks have already been rendered through the callback.
|
|
75
|
+
await gen.next();
|
|
76
|
+
md.end();
|
|
77
|
+
stopSpinner();
|
|
78
|
+
return;
|
|
79
|
+
} catch {
|
|
80
|
+
md.end();
|
|
81
|
+
stopSpinner();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
startSpinner('Retrying');
|
|
85
|
+
try {
|
|
86
|
+
const result = await nimClient.complete(conversation, undefined);
|
|
87
|
+
stopSpinner();
|
|
88
|
+
const cleanText = (result.content ?? '').trim();
|
|
89
|
+
if (cleanText) {
|
|
90
|
+
conversation.push({
|
|
91
|
+
id: crypto.randomUUID(),
|
|
92
|
+
role: 'assistant',
|
|
93
|
+
content: cleanText,
|
|
94
|
+
timestamp: new Date(),
|
|
95
|
+
});
|
|
96
|
+
printAssistantMessage(cleanText);
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
stopSpinner();
|
|
100
|
+
printError('LLM error: ' + (err instanceof Error ? err.message : String(err)));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse JSON tool calls that a non-tool-calling model may emit as plain text,
|
|
106
|
+
* e.g. {"tool_calls":[{"id":"call_1","name":"file:write","arguments":{...}}]}.
|
|
107
|
+
* Returns an empty array when nothing parseable is found.
|
|
108
|
+
*/
|
|
109
|
+
function parseToolCallsFromText(text: string): ToolCall[] {
|
|
110
|
+
let cleaned = (text ?? '').trim();
|
|
111
|
+
cleaned = cleaned.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/i, '').trim();
|
|
112
|
+
|
|
113
|
+
let obj: { tool_calls?: unknown };
|
|
114
|
+
try {
|
|
115
|
+
obj = JSON.parse(cleaned);
|
|
116
|
+
} catch {
|
|
117
|
+
const start = cleaned.indexOf('{');
|
|
118
|
+
const end = cleaned.lastIndexOf('}');
|
|
119
|
+
if (start === -1 || end === -1 || end <= start) return [];
|
|
120
|
+
try {
|
|
121
|
+
obj = JSON.parse(cleaned.slice(start, end + 1));
|
|
122
|
+
} catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rawCalls = Array.isArray(obj.tool_calls) ? obj.tool_calls : [];
|
|
128
|
+
return rawCalls.map((tc: { id?: string; name?: string; function?: { name?: string; arguments?: unknown } }) => ({
|
|
129
|
+
id: tc.id ?? crypto.randomUUID(),
|
|
130
|
+
name: tc.name ?? (tc.function?.name ?? ''),
|
|
131
|
+
arguments:
|
|
132
|
+
typeof tc.function?.arguments === 'string'
|
|
133
|
+
? (() => { try { return JSON.parse(tc.function.arguments as string); } catch { return { _raw: tc.function.arguments }; } })()
|
|
134
|
+
: (tc.function?.arguments ?? {}),
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
56
138
|
function extractProposedChange(toolName: string, resultOutput: string): ProposedFileChange | undefined {
|
|
57
139
|
const isFileTool = toolName === 'file:write' || toolName === 'file:edit';
|
|
58
140
|
if (!isFileTool) return undefined;
|
|
@@ -116,7 +198,6 @@ async function executeToolWithReview(
|
|
|
116
198
|
projectRoot: string,
|
|
117
199
|
input: InputController,
|
|
118
200
|
): Promise<{ toolResult: { success: boolean; output?: string; error?: string }; change?: ProposedFileChange; applied: boolean }> {
|
|
119
|
-
console.log('');
|
|
120
201
|
printToolCall(toolName, args);
|
|
121
202
|
startSpinner('Running ' + toolName);
|
|
122
203
|
const toolResult = await executeTool(toolName, args, projectRoot);
|
|
@@ -197,36 +278,94 @@ async function runToolLoop(
|
|
|
197
278
|
|
|
198
279
|
// Send conversation (including tool results) back to the LLM
|
|
199
280
|
// The LLM will either produce text or more tool calls
|
|
200
|
-
|
|
201
|
-
|
|
281
|
+
startSpinner('Working');
|
|
282
|
+
let result: { content: string; toolCalls: ToolCall[] };
|
|
202
283
|
try {
|
|
203
284
|
const tools = nimClient.buildToolDefs(TOOL_DEFINITIONS);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
285
|
+
result = await nimClient.complete(conversation, tools);
|
|
286
|
+
} catch {
|
|
287
|
+
// Model doesn't support native tool calling — fall back to plain text
|
|
288
|
+
// and parse any JSON tool calls emitted in the content.
|
|
289
|
+
try {
|
|
290
|
+
const fallback = await nimClient.complete(conversation, undefined);
|
|
291
|
+
result = {
|
|
292
|
+
content: fallback.content,
|
|
293
|
+
toolCalls: parseToolCallsFromText(fallback.content),
|
|
294
|
+
};
|
|
295
|
+
} catch (err2) {
|
|
296
|
+
stopSpinner();
|
|
297
|
+
printError('LLM error: ' + (err2 instanceof Error ? err2.message : String(err2)));
|
|
298
|
+
break;
|
|
218
299
|
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
300
|
+
}
|
|
301
|
+
stopSpinner();
|
|
302
|
+
|
|
303
|
+
finalText = result.content;
|
|
304
|
+
toolCalls = dedup(result.toolCalls);
|
|
305
|
+
|
|
306
|
+
// If the LLM produced text alongside tool calls, save it
|
|
307
|
+
if (finalText && finalText.trim() && finalText.trim() !== 'null') {
|
|
308
|
+
conversation.push({
|
|
309
|
+
id: crypto.randomUUID(),
|
|
310
|
+
role: 'assistant',
|
|
311
|
+
content: finalText.trim(),
|
|
312
|
+
timestamp: new Date(),
|
|
313
|
+
});
|
|
314
|
+
// Narration that comes with more tool calls is shown now; the
|
|
315
|
+
// final answer (no tool calls) is printed by the caller.
|
|
316
|
+
if (toolCalls.length > 0) printAssistantMessage(finalText.trim());
|
|
224
317
|
}
|
|
225
318
|
}
|
|
226
319
|
|
|
227
320
|
return finalText;
|
|
228
321
|
}
|
|
229
322
|
|
|
323
|
+
/**
|
|
324
|
+
* /model — pick from the live NVIDIA NIM catalogue (falls back to a built-in list)
|
|
325
|
+
* /model <model-id> — switch directly, e.g. /model meta/llama-3.3-70b-instruct
|
|
326
|
+
*/
|
|
327
|
+
async function handleModelCommand(arg: string, nimClient: NimClient, projectRoot: string): Promise<void> {
|
|
328
|
+
let selected: string | null = arg || null;
|
|
329
|
+
|
|
330
|
+
if (!selected) {
|
|
331
|
+
startSpinner('Fetching NVIDIA NIM models');
|
|
332
|
+
const { models, live, error } = await nimClient.listModels();
|
|
333
|
+
stopSpinner();
|
|
334
|
+
if (!live) {
|
|
335
|
+
printInfo('Could not fetch the live model list (' + (error ?? 'unknown error') + '). Showing built-in suggestions; you can type any model ID.');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
selected = await pick({
|
|
339
|
+
title: 'Select a model',
|
|
340
|
+
items: models.map((m) => ({ value: m.id, label: m.id, hint: m.note })),
|
|
341
|
+
current: nimClient.getModel(),
|
|
342
|
+
allowCustom: true,
|
|
343
|
+
});
|
|
344
|
+
if (!selected) {
|
|
345
|
+
printInfo('Model unchanged: ' + nimClient.getModel());
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (selected === nimClient.getModel()) {
|
|
351
|
+
printInfo('Already using ' + selected);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
nimClient.setModel(selected);
|
|
356
|
+
setScreenStatus({ model: selected });
|
|
357
|
+
logAgentTermoTimeline('Model switched to ' + selected);
|
|
358
|
+
logToTimeline(projectRoot, 'Model switched to ' + selected);
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
saveNimModel(selected);
|
|
362
|
+
printSuccess('Model set to ' + selected + ' (saved as your default)');
|
|
363
|
+
} catch {
|
|
364
|
+
printSuccess('Model set to ' + selected + ' for this session');
|
|
365
|
+
printInfo('Could not save it as your default.');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
230
369
|
export async function runRepl(projectRoot?: string): Promise<void> {
|
|
231
370
|
projectRoot = resolveProjectRoot(projectRoot);
|
|
232
371
|
|
|
@@ -245,8 +384,8 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
245
384
|
},
|
|
246
385
|
];
|
|
247
386
|
|
|
248
|
-
|
|
249
|
-
|
|
387
|
+
initScreen({ model: nimClient.getModel(), project: projectRoot });
|
|
388
|
+
printWelcome(projectRoot, nimClient.getModel());
|
|
250
389
|
logAgentTermoTimeline('Agent-Termo session started. Model: ' + nimClient.getModel());
|
|
251
390
|
logToTimeline(projectRoot, 'Agent-Termo session started. Model: ' + nimClient.getModel());
|
|
252
391
|
logAgentTermoTimeline('Commands: /help /clear /memory /timeline /model /agents /history');
|
|
@@ -277,6 +416,7 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
277
416
|
|
|
278
417
|
if (userInput.startsWith('/')) {
|
|
279
418
|
const cmd = userInput.toLowerCase().trim();
|
|
419
|
+
printUserMessage(userInput);
|
|
280
420
|
|
|
281
421
|
if (cmd === '/help' || cmd === '/') {
|
|
282
422
|
printHelp();
|
|
@@ -285,7 +425,7 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
285
425
|
if (cmd === '/clear' || cmd === '/cls') {
|
|
286
426
|
conversation.length = 1;
|
|
287
427
|
clearScreen();
|
|
288
|
-
printWelcome(projectRoot);
|
|
428
|
+
printWelcome(projectRoot, nimClient.getModel());
|
|
289
429
|
printSuccess('Conversation cleared.');
|
|
290
430
|
continue mainLoop;
|
|
291
431
|
}
|
|
@@ -297,8 +437,9 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
297
437
|
await printTimelineFile();
|
|
298
438
|
continue mainLoop;
|
|
299
439
|
}
|
|
300
|
-
if (cmd === '/model') {
|
|
301
|
-
|
|
440
|
+
if (cmd === '/model' || cmd.startsWith('/model ')) {
|
|
441
|
+
// Take the argument from the original text: model IDs are case-sensitive.
|
|
442
|
+
await handleModelCommand(userInput.trim().slice('/model'.length).trim(), nimClient, projectRoot);
|
|
302
443
|
continue mainLoop;
|
|
303
444
|
}
|
|
304
445
|
if (cmd === '/agents') {
|
|
@@ -315,10 +456,7 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
315
456
|
break;
|
|
316
457
|
}
|
|
317
458
|
|
|
318
|
-
|
|
319
|
-
printHelp();
|
|
320
|
-
console.log('Unknown command: ' + cmd);
|
|
321
|
-
console.log('Type /help for available commands.\n');
|
|
459
|
+
printError('Unknown command: ' + cmd, 'Type /help for available commands.');
|
|
322
460
|
continue mainLoop;
|
|
323
461
|
}
|
|
324
462
|
|
|
@@ -329,20 +467,18 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
329
467
|
timestamp: new Date(),
|
|
330
468
|
};
|
|
331
469
|
conversation.push(userMsg);
|
|
332
|
-
printUserMessage(
|
|
333
|
-
|
|
334
|
-
moveToFreshLine();
|
|
470
|
+
printUserMessage(userInput);
|
|
335
471
|
|
|
336
472
|
const useTools = mightNeedTools(userInput);
|
|
337
473
|
const tools = useTools ? nimClient.buildToolDefs(TOOL_DEFINITIONS) : undefined;
|
|
338
474
|
|
|
339
475
|
if (useTools) {
|
|
340
|
-
startSpinner('Working
|
|
476
|
+
startSpinner('Working');
|
|
341
477
|
try {
|
|
342
478
|
const result = await nimClient.complete(conversation, tools);
|
|
343
479
|
const fullText = result.content;
|
|
344
480
|
let toolCalls = dedup(result.toolCalls);
|
|
345
|
-
stopSpinner(
|
|
481
|
+
stopSpinner();
|
|
346
482
|
|
|
347
483
|
if (toolCalls.length === 0) {
|
|
348
484
|
// No tool calls — just display the text response
|
|
@@ -354,8 +490,13 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
354
490
|
content: cleanText,
|
|
355
491
|
timestamp: new Date(),
|
|
356
492
|
});
|
|
493
|
+
printAssistantMessage(cleanText);
|
|
357
494
|
}
|
|
358
495
|
} else {
|
|
496
|
+
// Show any narration that came with the first tool calls
|
|
497
|
+
const narration = (fullText ?? '').trim().replace(/^null$/, '').trim();
|
|
498
|
+
if (narration) printAssistantMessage(narration);
|
|
499
|
+
|
|
359
500
|
// Run the tool-call loop: execute tools, feed results back, repeat
|
|
360
501
|
const finalText = await runToolLoop(conversation, projectRoot, toolCalls, input, nimClient);
|
|
361
502
|
if (finalText && finalText.trim() && finalText.trim() !== 'null') {
|
|
@@ -363,41 +504,16 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
363
504
|
}
|
|
364
505
|
}
|
|
365
506
|
} catch (err) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
} else {
|
|
373
|
-
// No tools — stream the response
|
|
374
|
-
startSpinner('Thinking...');
|
|
375
|
-
try {
|
|
376
|
-
const gen = nimClient.streamComplete(conversation, undefined, (text: string) => {
|
|
377
|
-
process.stdout.write(text);
|
|
378
|
-
});
|
|
379
|
-
let receivedContent = false;
|
|
380
|
-
for await (const result of gen) {
|
|
381
|
-
if (result.content && result.content.trim()) {
|
|
382
|
-
receivedContent = true;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (receivedContent) {
|
|
386
|
-
process.stdout.write('\n');
|
|
387
|
-
stopSpinner(true);
|
|
388
|
-
} else {
|
|
389
|
-
process.stdout.write('\n');
|
|
390
|
-
stopSpinner(undefined);
|
|
391
|
-
}
|
|
392
|
-
} catch {
|
|
393
|
-
process.stdout.write('\n');
|
|
394
|
-
stopSpinner(false);
|
|
395
|
-
startSpinner('Retrying...');
|
|
507
|
+
stopSpinner();
|
|
508
|
+
// Tool calling failed (e.g. HTTP 400 because the model does not
|
|
509
|
+
// support the `tools` parameter). Retry without tools and parse
|
|
510
|
+
// any JSON tool calls the model may have emitted as plain text.
|
|
511
|
+
printInfo('Tool calls not supported by this model — retrying without tools.');
|
|
396
512
|
try {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const cleanText = (
|
|
513
|
+
const fallback = await nimClient.complete(conversation, undefined);
|
|
514
|
+
const text = (fallback.content ?? '').trim();
|
|
515
|
+
// Strip any leading/trailing "null" reasoning noise
|
|
516
|
+
const cleanText = text.replace(/^null$/, '').trim();
|
|
401
517
|
if (cleanText) {
|
|
402
518
|
conversation.push({
|
|
403
519
|
id: crypto.randomUUID(),
|
|
@@ -406,21 +522,31 @@ export async function runRepl(projectRoot?: string): Promise<void> {
|
|
|
406
522
|
timestamp: new Date(),
|
|
407
523
|
});
|
|
408
524
|
}
|
|
525
|
+
const narration = cleanText.replace(/^null$/, '').trim();
|
|
526
|
+
if (narration) printAssistantMessage(narration);
|
|
527
|
+
|
|
528
|
+
const toolCalls = dedup(parseToolCallsFromText(fallback.content));
|
|
529
|
+
if (toolCalls.length > 0) {
|
|
530
|
+
const finalText = await runToolLoop(conversation, projectRoot, toolCalls, input, nimClient);
|
|
531
|
+
if (finalText && finalText.trim() && finalText.trim() !== 'null') {
|
|
532
|
+
printAssistantMessage(finalText.trim());
|
|
533
|
+
}
|
|
534
|
+
}
|
|
409
535
|
} catch (err2) {
|
|
410
|
-
process.stdout.write('\n');
|
|
411
|
-
stopSpinner(false);
|
|
412
536
|
printError('LLM error: ' + (err2 instanceof Error ? err2.message : String(err2)));
|
|
413
537
|
conversation.pop();
|
|
414
538
|
continue mainLoop;
|
|
415
539
|
}
|
|
416
540
|
}
|
|
541
|
+
} else {
|
|
542
|
+
await replyWithText(conversation, nimClient);
|
|
417
543
|
}
|
|
418
544
|
|
|
419
545
|
if (conversation.length > 42) {
|
|
420
546
|
conversation.splice(1, conversation.length - 42);
|
|
421
547
|
}
|
|
422
548
|
} catch (err) {
|
|
423
|
-
stopSpinner(
|
|
549
|
+
stopSpinner();
|
|
424
550
|
printError('Session error: ' + (err instanceof Error ? err.message : String(err)));
|
|
425
551
|
}
|
|
426
552
|
}
|