@roj-ai/cli 0.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/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +28 -0
- package/dist/commands/answer.d.ts +3 -0
- package/dist/commands/answer.d.ts.map +1 -0
- package/dist/commands/answer.js +15 -0
- package/dist/commands/debug-format.d.ts +12 -0
- package/dist/commands/debug-format.d.ts.map +1 -0
- package/dist/commands/debug-format.js +262 -0
- package/dist/commands/debug.d.ts +14 -0
- package/dist/commands/debug.d.ts.map +1 -0
- package/dist/commands/debug.js +187 -0
- package/dist/commands/messages.d.ts +4 -0
- package/dist/commands/messages.d.ts.map +1 -0
- package/dist/commands/messages.js +77 -0
- package/dist/commands/presets.d.ts +3 -0
- package/dist/commands/presets.d.ts.map +1 -0
- package/dist/commands/presets.js +14 -0
- package/dist/commands/sessions.d.ts +6 -0
- package/dist/commands/sessions.d.ts.map +1 -0
- package/dist/commands/sessions.js +50 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +277 -0
- package/dist/repl/formatter.d.ts +16 -0
- package/dist/repl/formatter.d.ts.map +1 -0
- package/dist/repl/formatter.js +72 -0
- package/dist/repl/repl.d.ts +3 -0
- package/dist/repl/repl.d.ts.map +1 -0
- package/dist/repl/repl.js +212 -0
- package/dist/repl/session-picker.d.ts +6 -0
- package/dist/repl/session-picker.d.ts.map +1 -0
- package/dist/repl/session-picker.js +60 -0
- package/dist/unwrap.d.ts +4 -0
- package/dist/unwrap.d.ts.map +1 -0
- package/dist/unwrap.js +6 -0
- package/package.json +27 -0
- package/src/client.ts +41 -0
- package/src/commands/answer.ts +27 -0
- package/src/commands/debug-format.ts +299 -0
- package/src/commands/debug.ts +347 -0
- package/src/commands/messages.ts +92 -0
- package/src/commands/presets.ts +22 -0
- package/src/commands/sessions.ts +73 -0
- package/src/main.ts +319 -0
- package/src/repl/formatter.ts +84 -0
- package/src/repl/repl.ts +246 -0
- package/src/repl/session-picker.ts +81 -0
- package/src/unwrap.ts +8 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { SessionId } from '@roj-ai/shared';
|
|
2
|
+
import type { RpcClient } from '@roj-ai/shared/rpc';
|
|
3
|
+
import * as readline from 'node:readline';
|
|
4
|
+
export declare function pickSession(client: RpcClient, rl: readline.Interface): Promise<SessionId>;
|
|
5
|
+
export declare function createNewSession(client: RpcClient, rl: readline.Interface): Promise<SessionId>;
|
|
6
|
+
//# sourceMappingURL=session-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-picker.d.ts","sourceRoot":"","sources":["../../src/repl/session-picker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAiBzC,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CA8B/F;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CA4BpG"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SessionId } from '@roj-ai/shared';
|
|
2
|
+
import { unwrap } from '../unwrap.js';
|
|
3
|
+
const DIM = '\x1b[2m';
|
|
4
|
+
const BOLD = '\x1b[1m';
|
|
5
|
+
const RESET = '\x1b[0m';
|
|
6
|
+
function ask(rl, question) {
|
|
7
|
+
return new Promise(resolve => {
|
|
8
|
+
rl.question(question, resolve);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function isSessionMetadata(data) {
|
|
12
|
+
return typeof data === 'object' && data !== null && 'sessionId' in data && 'presetId' in data;
|
|
13
|
+
}
|
|
14
|
+
export async function pickSession(client, rl) {
|
|
15
|
+
const { sessions: rawSessions } = unwrap(await client.call('sessions.list', { status: 'active', limit: 20, order: 'desc' }));
|
|
16
|
+
const sessions = rawSessions.filter(isSessionMetadata);
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(`${BOLD}Active sessions:${RESET}`);
|
|
19
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
20
|
+
const s = sessions[i];
|
|
21
|
+
const date = new Date(s.createdAt).toLocaleString();
|
|
22
|
+
const name = s.name ? ` - ${s.name}` : '';
|
|
23
|
+
console.log(` ${BOLD}${i + 1})${RESET} ${s.sessionId.slice(0, 8)}... ${DIM}[${s.presetId}]${RESET}${name} ${DIM}${date}${RESET}`);
|
|
24
|
+
}
|
|
25
|
+
console.log(` ${BOLD}n)${RESET} Create new session`);
|
|
26
|
+
console.log();
|
|
27
|
+
const choice = await ask(rl, 'Select session: ');
|
|
28
|
+
if (choice.toLowerCase() === 'n') {
|
|
29
|
+
return createNewSession(client, rl);
|
|
30
|
+
}
|
|
31
|
+
const index = parseInt(choice, 10) - 1;
|
|
32
|
+
if (index >= 0 && index < sessions.length) {
|
|
33
|
+
return sessions[index].sessionId;
|
|
34
|
+
}
|
|
35
|
+
console.log('Invalid choice, try again.');
|
|
36
|
+
return pickSession(client, rl);
|
|
37
|
+
}
|
|
38
|
+
export async function createNewSession(client, rl) {
|
|
39
|
+
const { presets } = unwrap(await client.call('presets.list', {}));
|
|
40
|
+
if (presets.length === 0) {
|
|
41
|
+
throw new Error('No presets available on the server.');
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(`${BOLD}Available presets:${RESET}`);
|
|
45
|
+
for (let i = 0; i < presets.length; i++) {
|
|
46
|
+
const p = presets[i];
|
|
47
|
+
const desc = p.description ? ` - ${p.description}` : '';
|
|
48
|
+
console.log(` ${BOLD}${i + 1})${RESET} ${p.name}${DIM}${desc}${RESET}`);
|
|
49
|
+
}
|
|
50
|
+
console.log();
|
|
51
|
+
const choice = await ask(rl, 'Select preset: ');
|
|
52
|
+
const index = parseInt(choice, 10) - 1;
|
|
53
|
+
if (index < 0 || index >= presets.length) {
|
|
54
|
+
console.log('Invalid choice, try again.');
|
|
55
|
+
return createNewSession(client, rl);
|
|
56
|
+
}
|
|
57
|
+
const { sessionId } = unwrap(await client.call('sessions.create', { presetId: presets[index].id }));
|
|
58
|
+
console.log(`Session created: ${sessionId}`);
|
|
59
|
+
return SessionId(sessionId);
|
|
60
|
+
}
|
package/dist/unwrap.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwrap.d.ts","sourceRoot":"","sources":["../src/unwrap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,CAG5D"}
|
package/dist/unwrap.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@roj-ai/cli",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/main.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"src",
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run src/main.ts",
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"type-check": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@roj-ai/sdk": "^0.0.2",
|
|
20
|
+
"@roj-ai/shared": "^0.0.2",
|
|
21
|
+
"@roj-ai/transport": "^0.0.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/bun": "latest",
|
|
25
|
+
"typescript": "^5.7.2"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RpcClient } from '@roj-ai/shared/rpc'
|
|
2
|
+
import type { ProtocolDef, RawMessageListener } from '@roj-ai/transport'
|
|
3
|
+
import { bunWebSocketFactory } from '@roj-ai/transport/bun'
|
|
4
|
+
import { ClientConnection } from '@roj-ai/transport/client'
|
|
5
|
+
|
|
6
|
+
export function createCliClient(baseUrl: string): RpcClient {
|
|
7
|
+
return new RpcClient(baseUrl)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type WsConnection = ClientConnection<ProtocolDef, ProtocolDef>
|
|
11
|
+
|
|
12
|
+
export type NotificationHandlers = Record<string, (payload: unknown) => void>
|
|
13
|
+
|
|
14
|
+
export function connectWebSocket(
|
|
15
|
+
baseUrl: string,
|
|
16
|
+
sessionId: string,
|
|
17
|
+
handlers: NotificationHandlers,
|
|
18
|
+
): WsConnection {
|
|
19
|
+
const url = new URL('/ws/spa', baseUrl)
|
|
20
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
21
|
+
url.searchParams.set('sessionId', sessionId)
|
|
22
|
+
|
|
23
|
+
const connection = new ClientConnection<ProtocolDef, ProtocolDef>({
|
|
24
|
+
url: url.toString(),
|
|
25
|
+
wsFactory: bunWebSocketFactory,
|
|
26
|
+
reconnect: {
|
|
27
|
+
baseDelayMs: 1000,
|
|
28
|
+
maxDelayMs: 10000,
|
|
29
|
+
maxAttempts: 10,
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const listener: RawMessageListener = (type, payload) => {
|
|
34
|
+
const handler = handlers[type]
|
|
35
|
+
if (handler) {
|
|
36
|
+
handler(payload)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
connection.setRawMessageListener(listener)
|
|
40
|
+
return connection
|
|
41
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AgentId, ChatMessageId } from '@roj-ai/shared'
|
|
2
|
+
import type { RpcClient } from '@roj-ai/shared/rpc'
|
|
3
|
+
import { unwrap } from '../unwrap.js'
|
|
4
|
+
|
|
5
|
+
export async function answerCommand(
|
|
6
|
+
client: RpcClient,
|
|
7
|
+
sessionId: string,
|
|
8
|
+
questionId: string,
|
|
9
|
+
answer: string,
|
|
10
|
+
json: boolean,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const session = unwrap(await client.call('sessions.get', { sessionId }))
|
|
13
|
+
if (!session.entryAgentId) {
|
|
14
|
+
console.error('No entry agent found for session.')
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
const result = unwrap(
|
|
18
|
+
await client.call('user-chat.answerQuestion', { sessionId, agentId: AgentId(session.entryAgentId), questionId: ChatMessageId(questionId), answer }),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if (json) {
|
|
22
|
+
console.log(JSON.stringify(result))
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('Answer submitted.')
|
|
27
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { LLMCallLogEntry } from '@roj-ai/sdk'
|
|
2
|
+
import type { DomainEvent } from '@roj-ai/sdk'
|
|
3
|
+
import type {
|
|
4
|
+
AgentTreeNode,
|
|
5
|
+
ConversationMessageView,
|
|
6
|
+
GetAgentDetailResponse,
|
|
7
|
+
GetMetricsResponse,
|
|
8
|
+
GlobalMailboxMessage,
|
|
9
|
+
TimelineItem,
|
|
10
|
+
} from '@roj-ai/shared'
|
|
11
|
+
|
|
12
|
+
// ANSI color codes
|
|
13
|
+
const RESET = '\x1b[0m'
|
|
14
|
+
const BOLD = '\x1b[1m'
|
|
15
|
+
const DIM = '\x1b[2m'
|
|
16
|
+
const CYAN = '\x1b[36m'
|
|
17
|
+
const GREEN = '\x1b[32m'
|
|
18
|
+
const YELLOW = '\x1b[33m'
|
|
19
|
+
const RED = '\x1b[31m'
|
|
20
|
+
const MAGENTA = '\x1b[35m'
|
|
21
|
+
|
|
22
|
+
export function truncate(str: string, maxLen: number): string {
|
|
23
|
+
if (str.length <= maxLen) return str
|
|
24
|
+
return str.slice(0, maxLen - 1) + '\u2026'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatDuration(ms: number | undefined): string {
|
|
28
|
+
if (ms === undefined) return '-'
|
|
29
|
+
if (ms < 1000) return `${ms}ms`
|
|
30
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatTimestamp(ts: number): string {
|
|
34
|
+
return new Date(ts).toLocaleTimeString()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function statusColor(status: string): string {
|
|
38
|
+
switch (status) {
|
|
39
|
+
case 'idle':
|
|
40
|
+
case 'success':
|
|
41
|
+
return GREEN
|
|
42
|
+
case 'thinking':
|
|
43
|
+
case 'responding':
|
|
44
|
+
case 'running':
|
|
45
|
+
return YELLOW
|
|
46
|
+
case 'error':
|
|
47
|
+
return RED
|
|
48
|
+
default:
|
|
49
|
+
return ''
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Agent Tree
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
export function formatAgentTree(nodes: AgentTreeNode[]): string {
|
|
58
|
+
const lines: string[] = []
|
|
59
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
60
|
+
const isLast = i === nodes.length - 1
|
|
61
|
+
formatTreeNode(lines, nodes[i], '', isLast)
|
|
62
|
+
}
|
|
63
|
+
return lines.join('\n')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatCost(cost: number): string {
|
|
67
|
+
if (cost >= 1) return `$${cost.toFixed(2)}`
|
|
68
|
+
return `$${cost.toFixed(4)}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatTreeNode(lines: string[], node: AgentTreeNode, prefix: string, isLast: boolean): void {
|
|
72
|
+
const connector = isLast ? '\u2514\u2500\u2500 ' : '\u251c\u2500\u2500 '
|
|
73
|
+
const childPrefix = prefix + (isLast ? ' ' : '\u2502 ')
|
|
74
|
+
const color = statusColor(node.status)
|
|
75
|
+
|
|
76
|
+
// Line 1: name + status
|
|
77
|
+
lines.push(`${prefix}${connector}${BOLD}${node.definitionName}${RESET} ${color}[${node.status}]${RESET}`)
|
|
78
|
+
|
|
79
|
+
// Line 2: cost + badges + id
|
|
80
|
+
const details: string[] = []
|
|
81
|
+
if (node.cost > 0) details.push(`${GREEN}${formatCost(node.cost)}${RESET}`)
|
|
82
|
+
if (node.isExecuting) details.push(`${YELLOW}executing${RESET}`)
|
|
83
|
+
if (node.mailboxCount > 0) details.push(`${CYAN}${node.mailboxCount} msgs${RESET}`)
|
|
84
|
+
if (node.pendingToolCalls > 0) details.push(`${MAGENTA}${node.pendingToolCalls} tools${RESET}`)
|
|
85
|
+
const detailStr = details.length > 0 ? details.join(` ${DIM}\u00b7${RESET} `) + ' ' : ''
|
|
86
|
+
lines.push(`${childPrefix}${detailStr}${DIM}${node.id}${RESET}`)
|
|
87
|
+
|
|
88
|
+
// Children
|
|
89
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
90
|
+
const childIsLast = i === node.children.length - 1
|
|
91
|
+
formatTreeNode(lines, node.children[i], childPrefix, childIsLast)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Agent Detail
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
export function formatAgentDetail(detail: GetAgentDetailResponse): string {
|
|
100
|
+
const lines: string[] = []
|
|
101
|
+
const color = statusColor(detail.status)
|
|
102
|
+
|
|
103
|
+
// Header
|
|
104
|
+
lines.push(`${BOLD}Agent: ${detail.definitionName}${RESET}`)
|
|
105
|
+
lines.push(` ID: ${detail.id}`)
|
|
106
|
+
lines.push(` Status: ${color}${detail.status}${RESET}`)
|
|
107
|
+
lines.push(` Parent: ${detail.parentId ?? 'none'}`)
|
|
108
|
+
|
|
109
|
+
// Mailbox
|
|
110
|
+
lines.push('')
|
|
111
|
+
lines.push(`${BOLD}Mailbox${RESET} (${detail.mailbox.length} messages)`)
|
|
112
|
+
if (detail.mailbox.length === 0) {
|
|
113
|
+
lines.push(` ${DIM}(empty)${RESET}`)
|
|
114
|
+
} else {
|
|
115
|
+
for (const msg of detail.mailbox) {
|
|
116
|
+
const consumed = msg.consumed ? `${DIM}[consumed]${RESET}` : `${YELLOW}[pending]${RESET}`
|
|
117
|
+
lines.push(` ${msg.from} ${consumed}: ${truncate(msg.content, 80)}`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Conversation
|
|
122
|
+
lines.push('')
|
|
123
|
+
lines.push(`${BOLD}Conversation${RESET} (${detail.conversationHistory.length} messages)`)
|
|
124
|
+
if (detail.conversationHistory.length === 0) {
|
|
125
|
+
lines.push(` ${DIM}(empty)${RESET}`)
|
|
126
|
+
} else {
|
|
127
|
+
for (const msg of detail.conversationHistory) {
|
|
128
|
+
lines.push(formatConversationMessage(msg))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Pending Tool Calls
|
|
133
|
+
if (detail.pendingToolCalls.length > 0) {
|
|
134
|
+
lines.push('')
|
|
135
|
+
lines.push(`${BOLD}Pending Tool Calls${RESET} (${detail.pendingToolCalls.length})`)
|
|
136
|
+
for (const tc of detail.pendingToolCalls) {
|
|
137
|
+
lines.push(` ${MAGENTA}${tc.name}${RESET} [${tc.status}] ${DIM}${tc.id}${RESET}`)
|
|
138
|
+
if (tc.input) {
|
|
139
|
+
lines.push(` Input: ${truncate(JSON.stringify(tc.input), 100)}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return lines.join('\n')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function formatConversationMessage(msg: ConversationMessageView): string {
|
|
148
|
+
switch (msg.role) {
|
|
149
|
+
case 'user':
|
|
150
|
+
return ` ${GREEN}user:${RESET} ${truncate(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content), 100)}`
|
|
151
|
+
case 'assistant': {
|
|
152
|
+
const content = msg.content ? truncate(msg.content, 100) : ''
|
|
153
|
+
const toolsInfo = msg.toolCalls?.length ? ` ${DIM}[${msg.toolCalls.length} tool calls]${RESET}` : ''
|
|
154
|
+
return ` ${CYAN}assistant:${RESET} ${content}${toolsInfo}`
|
|
155
|
+
}
|
|
156
|
+
case 'tool': {
|
|
157
|
+
const errTag = msg.isError ? `${RED}[error]${RESET} ` : ''
|
|
158
|
+
return ` ${MAGENTA}tool(${msg.toolCallId}):${RESET} ${errTag}${truncate(msg.content, 100)}`
|
|
159
|
+
}
|
|
160
|
+
case 'system':
|
|
161
|
+
return ` ${DIM}system: ${truncate(msg.content, 100)}${RESET}`
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ============================================================================
|
|
166
|
+
// Timeline
|
|
167
|
+
// ============================================================================
|
|
168
|
+
|
|
169
|
+
export function formatTimelineItem(item: TimelineItem): string {
|
|
170
|
+
const status = `${statusColor(item.status)}${item.status}${RESET}`
|
|
171
|
+
const duration = formatDuration(item.durationMs)
|
|
172
|
+
const time = formatTimestamp(item.startedAt)
|
|
173
|
+
|
|
174
|
+
switch (item.type) {
|
|
175
|
+
case 'llm': {
|
|
176
|
+
const model = item.model ?? '-'
|
|
177
|
+
const tokens = item.promptTokens !== undefined ? `${item.promptTokens}/${item.completionTokens}` : '-'
|
|
178
|
+
return `${time} ${CYAN}llm${RESET} ${item.agentName.padEnd(20)} ${status.padEnd(20)} ${duration.padStart(8)} ${model} ${tokens}`
|
|
179
|
+
}
|
|
180
|
+
case 'tool': {
|
|
181
|
+
const toolName = item.toolName ?? '-'
|
|
182
|
+
return `${time} ${MAGENTA}tool${RESET} ${item.agentName.padEnd(20)} ${status.padEnd(20)} ${duration.padStart(8)} ${toolName}`
|
|
183
|
+
}
|
|
184
|
+
case 'compaction': {
|
|
185
|
+
const info = item.originalTokens !== undefined
|
|
186
|
+
? `${item.originalTokens} -> ${item.compactedTokens} tokens, ${item.messagesRemoved} msgs removed`
|
|
187
|
+
: ''
|
|
188
|
+
return `${time} ${YELLOW}compact${RESET} ${item.agentName.padEnd(20)} ${status.padEnd(20)} ${DIM}${info}${RESET}`
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Mailbox
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
export function formatMailboxMessage(msg: GlobalMailboxMessage): string {
|
|
198
|
+
const status = msg.consumed ? `${DIM}[consumed]${RESET}` : `${YELLOW}[pending]${RESET}`
|
|
199
|
+
const time = formatTimestamp(msg.timestamp)
|
|
200
|
+
return `${time} ${msg.fromAgentName} -> ${msg.toAgentName} ${status} ${truncate(msg.content, 60)}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Metrics
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
export function formatMetrics(metrics: GetMetricsResponse): string {
|
|
208
|
+
const lines: string[] = []
|
|
209
|
+
lines.push(`${BOLD}Session Metrics${RESET}`)
|
|
210
|
+
lines.push(` Total tokens: ${metrics.totalTokens.toLocaleString()}`)
|
|
211
|
+
lines.push(` Prompt tokens: ${metrics.promptTokens.toLocaleString()}`)
|
|
212
|
+
lines.push(` Completion tokens: ${metrics.completionTokens.toLocaleString()}`)
|
|
213
|
+
if (metrics.totalCost !== undefined) {
|
|
214
|
+
lines.push(` Cost: $${metrics.totalCost.toFixed(4)}`)
|
|
215
|
+
}
|
|
216
|
+
lines.push(` LLM calls: ${metrics.llmCalls}`)
|
|
217
|
+
lines.push(` Tool calls: ${metrics.toolCalls}`)
|
|
218
|
+
lines.push(` Agent count: ${metrics.agentCount}`)
|
|
219
|
+
lines.push(` Duration: ${formatDuration(metrics.durationMs)}`)
|
|
220
|
+
return lines.join('\n')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// LLM Call Detail
|
|
225
|
+
// ============================================================================
|
|
226
|
+
|
|
227
|
+
export function formatLLMCallDetail(call: LLMCallLogEntry): string {
|
|
228
|
+
const lines: string[] = []
|
|
229
|
+
|
|
230
|
+
lines.push(`${BOLD}LLM Call: ${call.id}${RESET}`)
|
|
231
|
+
lines.push(` Agent: ${call.agentId}`)
|
|
232
|
+
lines.push(` Status: ${statusColor(call.status)}${call.status}${RESET}`)
|
|
233
|
+
lines.push(` Created: ${new Date(call.createdAt).toLocaleString()}`)
|
|
234
|
+
if (call.durationMs !== undefined) {
|
|
235
|
+
lines.push(` Duration: ${formatDuration(call.durationMs)}`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Request
|
|
239
|
+
lines.push('')
|
|
240
|
+
lines.push(`${BOLD}Request${RESET}`)
|
|
241
|
+
lines.push(` Model: ${call.request.model}`)
|
|
242
|
+
lines.push(` Messages: ${call.request.messages.length}`)
|
|
243
|
+
lines.push(` Tools: ${call.request.toolsCount}`)
|
|
244
|
+
if (call.request.systemPrompt) {
|
|
245
|
+
lines.push(` System: ${truncate(call.request.systemPrompt, 100)}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Response
|
|
249
|
+
if (call.response) {
|
|
250
|
+
lines.push('')
|
|
251
|
+
lines.push(`${BOLD}Response${RESET}`)
|
|
252
|
+
lines.push(` Finish: ${call.response.finishReason}`)
|
|
253
|
+
if (call.response.content) {
|
|
254
|
+
lines.push(` Content: ${truncate(call.response.content, 200)}`)
|
|
255
|
+
}
|
|
256
|
+
if (call.response.toolCalls.length > 0) {
|
|
257
|
+
lines.push(` Tool calls: ${call.response.toolCalls.length}`)
|
|
258
|
+
for (const tc of call.response.toolCalls) {
|
|
259
|
+
lines.push(` ${MAGENTA}${tc.name}${RESET} ${DIM}${tc.id}${RESET}`)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Metrics
|
|
265
|
+
if (call.metrics) {
|
|
266
|
+
lines.push('')
|
|
267
|
+
lines.push(`${BOLD}Metrics${RESET}`)
|
|
268
|
+
lines.push(` Prompt: ${call.metrics.promptTokens.toLocaleString()} tokens`)
|
|
269
|
+
lines.push(` Completion: ${call.metrics.completionTokens.toLocaleString()} tokens`)
|
|
270
|
+
lines.push(` Total: ${call.metrics.totalTokens.toLocaleString()} tokens`)
|
|
271
|
+
if (call.metrics.cost !== undefined) {
|
|
272
|
+
lines.push(` Cost: $${call.metrics.cost.toFixed(4)}`)
|
|
273
|
+
}
|
|
274
|
+
lines.push(` Latency: ${formatDuration(call.metrics.latencyMs)}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Error
|
|
278
|
+
if (call.error) {
|
|
279
|
+
lines.push('')
|
|
280
|
+
lines.push(`${RED}${BOLD}Error${RESET}`)
|
|
281
|
+
lines.push(` ${RED}${call.error.type}: ${call.error.message}${RESET}`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return lines.join('\n')
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// Event Row
|
|
289
|
+
// ============================================================================
|
|
290
|
+
|
|
291
|
+
export function formatEventRow(event: DomainEvent, index: number): string[] {
|
|
292
|
+
const agentId = 'agentId' in event ? String(event.agentId) : '-'
|
|
293
|
+
return [
|
|
294
|
+
String(index),
|
|
295
|
+
event.type,
|
|
296
|
+
truncate(agentId, 24),
|
|
297
|
+
formatTimestamp(event.timestamp),
|
|
298
|
+
]
|
|
299
|
+
}
|