@radaros/core 0.1.0

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.
Files changed (48) hide show
  1. package/dist/index.d.ts +887 -0
  2. package/dist/index.js +3462 -0
  3. package/package.json +64 -0
  4. package/src/agent/agent.ts +314 -0
  5. package/src/agent/llm-loop.ts +263 -0
  6. package/src/agent/run-context.ts +35 -0
  7. package/src/agent/types.ts +77 -0
  8. package/src/events/event-bus.ts +45 -0
  9. package/src/events/types.ts +16 -0
  10. package/src/guardrails/types.ts +5 -0
  11. package/src/hooks/types.ts +6 -0
  12. package/src/index.ts +111 -0
  13. package/src/knowledge/knowledge-base.ts +146 -0
  14. package/src/logger/logger.ts +232 -0
  15. package/src/memory/memory.ts +87 -0
  16. package/src/memory/types.ts +13 -0
  17. package/src/models/provider.ts +22 -0
  18. package/src/models/providers/anthropic.ts +330 -0
  19. package/src/models/providers/google.ts +361 -0
  20. package/src/models/providers/ollama.ts +211 -0
  21. package/src/models/providers/openai.ts +323 -0
  22. package/src/models/registry.ts +90 -0
  23. package/src/models/types.ts +112 -0
  24. package/src/session/session-manager.ts +75 -0
  25. package/src/session/types.ts +10 -0
  26. package/src/storage/driver.ts +10 -0
  27. package/src/storage/in-memory.ts +44 -0
  28. package/src/storage/mongodb.ts +70 -0
  29. package/src/storage/postgres.ts +81 -0
  30. package/src/storage/sqlite.ts +81 -0
  31. package/src/team/modes.ts +1 -0
  32. package/src/team/team.ts +323 -0
  33. package/src/team/types.ts +26 -0
  34. package/src/tools/define-tool.ts +20 -0
  35. package/src/tools/tool-executor.ts +131 -0
  36. package/src/tools/types.ts +27 -0
  37. package/src/vector/base.ts +44 -0
  38. package/src/vector/embeddings/google.ts +64 -0
  39. package/src/vector/embeddings/openai.ts +66 -0
  40. package/src/vector/in-memory.ts +115 -0
  41. package/src/vector/mongodb.ts +241 -0
  42. package/src/vector/pgvector.ts +169 -0
  43. package/src/vector/qdrant.ts +203 -0
  44. package/src/vector/types.ts +55 -0
  45. package/src/workflow/step-runner.ts +303 -0
  46. package/src/workflow/types.ts +55 -0
  47. package/src/workflow/workflow.ts +68 -0
  48. package/tsconfig.json +8 -0
@@ -0,0 +1,77 @@
1
+ import type { z } from "zod";
2
+ import type { ModelProvider } from "../models/provider.js";
3
+ import type { ToolDef, ToolCallResult } from "../tools/types.js";
4
+ import type { Memory } from "../memory/memory.js";
5
+ import type { StorageDriver } from "../storage/driver.js";
6
+ import type { EventBus } from "../events/event-bus.js";
7
+ import type { TokenUsage, StreamChunk, MessageContent } from "../models/types.js";
8
+ import type { RunContext } from "./run-context.js";
9
+ import type { LogLevel } from "../logger/logger.js";
10
+
11
+ export interface AgentConfig {
12
+ name: string;
13
+ model: ModelProvider;
14
+ tools?: ToolDef[];
15
+ instructions?: string | ((ctx: RunContext) => string);
16
+ memory?: Memory;
17
+ storage?: StorageDriver;
18
+ sessionId?: string;
19
+ userId?: string;
20
+ addHistoryToMessages?: boolean;
21
+ numHistoryRuns?: number;
22
+ maxToolRoundtrips?: number;
23
+ temperature?: number;
24
+ structuredOutput?: z.ZodSchema;
25
+ hooks?: AgentHooks;
26
+ guardrails?: {
27
+ input?: InputGuardrail[];
28
+ output?: OutputGuardrail[];
29
+ };
30
+ eventBus?: EventBus;
31
+ /** Logging level. Set to "debug" for tool call details, "info" for summaries, "silent" to disable. Default: "silent". */
32
+ logLevel?: LogLevel;
33
+ }
34
+
35
+ export interface RunOpts {
36
+ sessionId?: string;
37
+ userId?: string;
38
+ metadata?: Record<string, unknown>;
39
+ /** Per-request API key override passed to the model provider. */
40
+ apiKey?: string;
41
+ }
42
+
43
+ export interface RunOutput {
44
+ text: string;
45
+ toolCalls: ToolCallResult[];
46
+ usage: TokenUsage;
47
+ /** Parsed structured output if structuredOutput schema is set. */
48
+ structured?: unknown;
49
+ durationMs?: number;
50
+ }
51
+
52
+ export interface AgentHooks {
53
+ beforeRun?: (ctx: RunContext) => Promise<void>;
54
+ afterRun?: (ctx: RunContext, output: RunOutput) => Promise<void>;
55
+ onToolCall?: (
56
+ ctx: RunContext,
57
+ toolName: string,
58
+ args: unknown
59
+ ) => Promise<void>;
60
+ onError?: (ctx: RunContext, error: Error) => Promise<void>;
61
+ }
62
+
63
+ export type GuardrailResult =
64
+ | { pass: true }
65
+ | { pass: false; reason: string };
66
+
67
+ export interface InputGuardrail {
68
+ name: string;
69
+ validate: (input: MessageContent, ctx: RunContext) => Promise<GuardrailResult>;
70
+ }
71
+
72
+ export interface OutputGuardrail {
73
+ name: string;
74
+ validate: (output: RunOutput, ctx: RunContext) => Promise<GuardrailResult>;
75
+ }
76
+
77
+ export type { StreamChunk };
@@ -0,0 +1,45 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { AgentEventMap } from "./types.js";
3
+
4
+ type EventKey = keyof AgentEventMap;
5
+
6
+ export class EventBus {
7
+ private emitter = new EventEmitter();
8
+
9
+ constructor() {
10
+ this.emitter.setMaxListeners(100);
11
+ }
12
+
13
+ on<K extends EventKey>(
14
+ event: K,
15
+ handler: (data: AgentEventMap[K]) => void
16
+ ): this {
17
+ this.emitter.on(event, handler as (...args: unknown[]) => void);
18
+ return this;
19
+ }
20
+
21
+ once<K extends EventKey>(
22
+ event: K,
23
+ handler: (data: AgentEventMap[K]) => void
24
+ ): this {
25
+ this.emitter.once(event, handler as (...args: unknown[]) => void);
26
+ return this;
27
+ }
28
+
29
+ off<K extends EventKey>(
30
+ event: K,
31
+ handler: (data: AgentEventMap[K]) => void
32
+ ): this {
33
+ this.emitter.off(event, handler as (...args: unknown[]) => void);
34
+ return this;
35
+ }
36
+
37
+ emit<K extends EventKey>(event: K, data: AgentEventMap[K]): boolean {
38
+ return this.emitter.emit(event, data);
39
+ }
40
+
41
+ removeAllListeners(event?: EventKey): this {
42
+ this.emitter.removeAllListeners(event);
43
+ return this;
44
+ }
45
+ }
@@ -0,0 +1,16 @@
1
+ import type { RunOutput } from "../agent/types.js";
2
+
3
+ export type AgentEventMap = {
4
+ "run.start": { runId: string; agentName: string; input: string };
5
+ "run.complete": { runId: string; output: RunOutput };
6
+ "run.error": { runId: string; error: Error };
7
+ "run.stream.chunk": { runId: string; chunk: string };
8
+ "tool.call": { runId: string; toolName: string; args: unknown };
9
+ "tool.result": { runId: string; toolName: string; result: unknown };
10
+ "team.delegate": { runId: string; memberId: string; task: string };
11
+ "workflow.step": {
12
+ runId: string;
13
+ stepName: string;
14
+ status: "start" | "done" | "error";
15
+ };
16
+ };
@@ -0,0 +1,5 @@
1
+ export type {
2
+ InputGuardrail,
3
+ OutputGuardrail,
4
+ GuardrailResult,
5
+ } from "../agent/types.js";
@@ -0,0 +1,6 @@
1
+ export type {
2
+ AgentHooks,
3
+ InputGuardrail,
4
+ OutputGuardrail,
5
+ GuardrailResult,
6
+ } from "../agent/types.js";
package/src/index.ts ADDED
@@ -0,0 +1,111 @@
1
+ // Agent
2
+ export { Agent } from "./agent/agent.js";
3
+ export { RunContext } from "./agent/run-context.js";
4
+ export { LLMLoop } from "./agent/llm-loop.js";
5
+ export type {
6
+ AgentConfig,
7
+ RunOpts,
8
+ RunOutput,
9
+ AgentHooks,
10
+ InputGuardrail,
11
+ OutputGuardrail,
12
+ GuardrailResult,
13
+ } from "./agent/types.js";
14
+
15
+ // Team
16
+ export { Team } from "./team/team.js";
17
+ export { TeamMode } from "./team/types.js";
18
+ export type { TeamConfig } from "./team/types.js";
19
+
20
+ // Workflow
21
+ export { Workflow } from "./workflow/workflow.js";
22
+ export type {
23
+ WorkflowConfig,
24
+ WorkflowResult,
25
+ StepDef,
26
+ StepResult,
27
+ AgentStep,
28
+ FunctionStep,
29
+ ConditionStep,
30
+ ParallelStep,
31
+ } from "./workflow/types.js";
32
+
33
+ // Models
34
+ export type { ModelProvider } from "./models/provider.js";
35
+ export type {
36
+ ChatMessage,
37
+ MessageRole,
38
+ MessageContent,
39
+ ContentPart,
40
+ TextPart,
41
+ ImagePart,
42
+ AudioPart,
43
+ FilePart,
44
+ ToolCall,
45
+ ToolDefinition,
46
+ TokenUsage,
47
+ ModelResponse,
48
+ StreamChunk,
49
+ ModelConfig,
50
+ } from "./models/types.js";
51
+ export { getTextContent, isMultiModal } from "./models/types.js";
52
+ export { ModelRegistry, registry, openai, anthropic, google, ollama } from "./models/registry.js";
53
+ export { OpenAIProvider } from "./models/providers/openai.js";
54
+ export { AnthropicProvider } from "./models/providers/anthropic.js";
55
+ export { GoogleProvider } from "./models/providers/google.js";
56
+ export { OllamaProvider } from "./models/providers/ollama.js";
57
+
58
+ // Tools
59
+ export { defineTool } from "./tools/define-tool.js";
60
+ export { ToolExecutor } from "./tools/tool-executor.js";
61
+ export type { ToolDef, ToolResult, ToolCallResult, Artifact } from "./tools/types.js";
62
+
63
+ // Storage
64
+ export type { StorageDriver } from "./storage/driver.js";
65
+ export { InMemoryStorage } from "./storage/in-memory.js";
66
+ export { SqliteStorage } from "./storage/sqlite.js";
67
+ export { PostgresStorage } from "./storage/postgres.js";
68
+ export { MongoDBStorage } from "./storage/mongodb.js";
69
+
70
+ // Vector Stores
71
+ export type {
72
+ VectorStore,
73
+ VectorDocument,
74
+ VectorSearchResult,
75
+ VectorSearchOptions,
76
+ EmbeddingProvider,
77
+ } from "./vector/types.js";
78
+ export { BaseVectorStore } from "./vector/base.js";
79
+ export { InMemoryVectorStore } from "./vector/in-memory.js";
80
+ export { PgVectorStore } from "./vector/pgvector.js";
81
+ export type { PgVectorConfig } from "./vector/pgvector.js";
82
+ export { QdrantVectorStore } from "./vector/qdrant.js";
83
+ export type { QdrantConfig } from "./vector/qdrant.js";
84
+ export { MongoDBVectorStore } from "./vector/mongodb.js";
85
+ export type { MongoDBVectorConfig } from "./vector/mongodb.js";
86
+
87
+ // Embedding Providers
88
+ export { OpenAIEmbedding } from "./vector/embeddings/openai.js";
89
+ export type { OpenAIEmbeddingConfig } from "./vector/embeddings/openai.js";
90
+ export { GoogleEmbedding } from "./vector/embeddings/google.js";
91
+ export type { GoogleEmbeddingConfig } from "./vector/embeddings/google.js";
92
+
93
+ // Knowledge Base
94
+ export { KnowledgeBase } from "./knowledge/knowledge-base.js";
95
+ export type { KnowledgeBaseConfig, KnowledgeBaseToolConfig } from "./knowledge/knowledge-base.js";
96
+
97
+ // Session
98
+ export { SessionManager } from "./session/session-manager.js";
99
+ export type { Session } from "./session/types.js";
100
+
101
+ // Memory
102
+ export { Memory } from "./memory/memory.js";
103
+ export type { MemoryConfig, MemoryEntry } from "./memory/types.js";
104
+
105
+ // Events
106
+ export { EventBus } from "./events/event-bus.js";
107
+ export type { AgentEventMap } from "./events/types.js";
108
+
109
+ // Logger
110
+ export { Logger } from "./logger/logger.js";
111
+ export type { LogLevel, LoggerConfig } from "./logger/logger.js";
@@ -0,0 +1,146 @@
1
+ import { z } from "zod";
2
+ import type { ToolDef } from "../tools/types.js";
3
+ import type {
4
+ VectorStore,
5
+ VectorDocument,
6
+ VectorSearchResult,
7
+ VectorSearchOptions,
8
+ } from "../vector/types.js";
9
+
10
+ export interface KnowledgeBaseConfig {
11
+ /** Display name used in tool description auto-generation. */
12
+ name: string;
13
+ /** The underlying vector store (any backend). */
14
+ vectorStore: VectorStore;
15
+ /** Collection/index name inside the vector store. */
16
+ collection?: string;
17
+ }
18
+
19
+ export interface KnowledgeBaseToolConfig {
20
+ /** Tool name exposed to the LLM. Defaults to `search_<collection>`. */
21
+ toolName?: string;
22
+ /** Custom tool description. A sensible default is generated from the KB name. */
23
+ description?: string;
24
+ /** Number of results to return per search. Default 5. */
25
+ topK?: number;
26
+ /** Minimum similarity score to include. */
27
+ minScore?: number;
28
+ /** Metadata filter applied to every search. */
29
+ filter?: Record<string, unknown>;
30
+ /** Custom formatter for search results. Defaults to numbered list with scores. */
31
+ formatResults?: (results: VectorSearchResult[]) => string;
32
+ }
33
+
34
+ export class KnowledgeBase {
35
+ readonly name: string;
36
+ readonly collection: string;
37
+ private store: VectorStore;
38
+ private initialized = false;
39
+
40
+ constructor(config: KnowledgeBaseConfig) {
41
+ this.name = config.name;
42
+ this.store = config.vectorStore;
43
+ this.collection = config.collection ?? config.name.toLowerCase().replace(/\s+/g, "_");
44
+ }
45
+
46
+ async initialize(): Promise<void> {
47
+ if (this.initialized) return;
48
+ await this.store.initialize();
49
+ this.initialized = true;
50
+ }
51
+
52
+ async add(doc: VectorDocument): Promise<void> {
53
+ await this.ensureInit();
54
+ await this.store.upsert(this.collection, doc);
55
+ }
56
+
57
+ async addDocuments(docs: VectorDocument[]): Promise<void> {
58
+ await this.ensureInit();
59
+ await this.store.upsertBatch(this.collection, docs);
60
+ }
61
+
62
+ async search(
63
+ query: string,
64
+ options?: VectorSearchOptions
65
+ ): Promise<VectorSearchResult[]> {
66
+ await this.ensureInit();
67
+ return this.store.search(this.collection, query, options);
68
+ }
69
+
70
+ async get(id: string): Promise<VectorDocument | null> {
71
+ await this.ensureInit();
72
+ return this.store.get(this.collection, id);
73
+ }
74
+
75
+ async delete(id: string): Promise<void> {
76
+ await this.ensureInit();
77
+ await this.store.delete(this.collection, id);
78
+ }
79
+
80
+ async clear(): Promise<void> {
81
+ await this.store.dropCollection(this.collection);
82
+ }
83
+
84
+ async close(): Promise<void> {
85
+ await this.store.close();
86
+ }
87
+
88
+ /**
89
+ * Returns a ToolDef that an Agent can use to search this knowledge base.
90
+ * Plug the result directly into `Agent({ tools: [kb.asTool()] })`.
91
+ */
92
+ asTool(config: KnowledgeBaseToolConfig = {}): ToolDef {
93
+ const topK = config.topK ?? 5;
94
+ const minScore = config.minScore;
95
+ const filter = config.filter;
96
+ const toolName =
97
+ config.toolName ?? `search_${this.collection}`;
98
+ const description =
99
+ config.description ??
100
+ `Search the "${this.name}" knowledge base for relevant information. Use this before answering questions related to ${this.name}.`;
101
+
102
+ const formatResults =
103
+ config.formatResults ?? defaultFormatResults;
104
+
105
+ const kb = this;
106
+
107
+ return {
108
+ name: toolName,
109
+ description,
110
+ parameters: z.object({
111
+ query: z.string().describe("Search query to find relevant documents"),
112
+ }),
113
+ execute: async (args: Record<string, unknown>) => {
114
+ const results = await kb.search(args.query as string, {
115
+ topK,
116
+ minScore,
117
+ filter,
118
+ });
119
+
120
+ if (results.length === 0) {
121
+ return "No relevant documents found in the knowledge base.";
122
+ }
123
+
124
+ return formatResults(results);
125
+ },
126
+ };
127
+ }
128
+
129
+ private async ensureInit(): Promise<void> {
130
+ if (!this.initialized) await this.initialize();
131
+ }
132
+ }
133
+
134
+ function defaultFormatResults(results: VectorSearchResult[]): string {
135
+ const lines = results.map((r, i) => {
136
+ const meta = r.metadata
137
+ ? Object.entries(r.metadata)
138
+ .filter(([, v]) => v !== undefined)
139
+ .map(([k, v]) => `${k}: ${v}`)
140
+ .join(", ")
141
+ : "";
142
+ const metaStr = meta ? ` | ${meta}` : "";
143
+ return `[${i + 1}] (score: ${r.score.toFixed(3)}${metaStr})\n${r.content}`;
144
+ });
145
+ return `Found ${results.length} relevant document(s):\n\n${lines.join("\n\n")}`;
146
+ }
@@ -0,0 +1,232 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
2
+
3
+ const LEVEL_ORDER: Record<LogLevel, number> = {
4
+ debug: 0,
5
+ info: 1,
6
+ warn: 2,
7
+ error: 3,
8
+ silent: 4,
9
+ };
10
+
11
+ const C = {
12
+ reset: "\x1b[0m",
13
+ bold: "\x1b[1m",
14
+ dim: "\x1b[2m",
15
+ italic: "\x1b[3m",
16
+
17
+ black: "\x1b[30m",
18
+ red: "\x1b[31m",
19
+ green: "\x1b[32m",
20
+ yellow: "\x1b[33m",
21
+ blue: "\x1b[34m",
22
+ magenta: "\x1b[35m",
23
+ cyan: "\x1b[36m",
24
+ white: "\x1b[37m",
25
+
26
+ bgBlack: "\x1b[40m",
27
+ bgRed: "\x1b[41m",
28
+ bgGreen: "\x1b[42m",
29
+ bgYellow: "\x1b[43m",
30
+ bgBlue: "\x1b[44m",
31
+ bgMagenta: "\x1b[45m",
32
+ bgCyan: "\x1b[46m",
33
+
34
+ gray: "\x1b[90m",
35
+ brightGreen: "\x1b[92m",
36
+ brightYellow: "\x1b[93m",
37
+ brightBlue: "\x1b[94m",
38
+ brightMagenta: "\x1b[95m",
39
+ brightCyan: "\x1b[96m",
40
+ };
41
+
42
+ function noColor(str: string): string {
43
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
44
+ }
45
+
46
+ export interface LoggerConfig {
47
+ level?: LogLevel;
48
+ color?: boolean;
49
+ prefix?: string;
50
+ }
51
+
52
+ export class Logger {
53
+ private level: LogLevel;
54
+ private color: boolean;
55
+ private prefix: string;
56
+
57
+ constructor(config: LoggerConfig = {}) {
58
+ this.level = config.level ?? "info";
59
+ this.color = config.color ?? process.stdout.isTTY !== false;
60
+ this.prefix = config.prefix ?? "radaros";
61
+ }
62
+
63
+ private c(code: string, text: string): string {
64
+ return this.color ? `${code}${text}${C.reset}` : text;
65
+ }
66
+
67
+ private shouldLog(level: LogLevel): boolean {
68
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[this.level];
69
+ }
70
+
71
+ private tag(level: LogLevel): string {
72
+ switch (level) {
73
+ case "debug":
74
+ return this.c(C.gray, "DBG");
75
+ case "info":
76
+ return this.c(C.brightCyan, "INF");
77
+ case "warn":
78
+ return this.c(C.brightYellow, "WRN");
79
+ case "error":
80
+ return this.c(C.red, "ERR");
81
+ default:
82
+ return "";
83
+ }
84
+ }
85
+
86
+ private timestamp(): string {
87
+ const now = new Date();
88
+ const ts = now.toISOString().slice(11, 23);
89
+ return this.c(C.dim, ts);
90
+ }
91
+
92
+ private log(level: LogLevel, msg: string, data?: Record<string, unknown>): void {
93
+ if (!this.shouldLog(level)) return;
94
+ const parts = [
95
+ this.timestamp(),
96
+ this.tag(level),
97
+ this.c(C.dim, `[${this.prefix}]`),
98
+ msg,
99
+ ];
100
+ if (data && Object.keys(data).length > 0) {
101
+ const formatted = Object.entries(data)
102
+ .map(([k, v]) => `${this.c(C.dim, k + "=")}${this.formatValue(v)}`)
103
+ .join(" ");
104
+ parts.push(formatted);
105
+ }
106
+ console.log(parts.join(" "));
107
+ }
108
+
109
+ private formatValue(v: unknown): string {
110
+ if (typeof v === "number") return this.c(C.brightGreen, String(v));
111
+ if (typeof v === "string") return this.c(C.yellow, `"${v}"`);
112
+ if (typeof v === "boolean") return this.c(C.magenta, String(v));
113
+ return String(v);
114
+ }
115
+
116
+ debug(msg: string, data?: Record<string, unknown>) {
117
+ this.log("debug", msg, data);
118
+ }
119
+
120
+ info(msg: string, data?: Record<string, unknown>) {
121
+ this.log("info", msg, data);
122
+ }
123
+
124
+ warn(msg: string, data?: Record<string, unknown>) {
125
+ this.log("warn", msg, data);
126
+ }
127
+
128
+ error(msg: string, data?: Record<string, unknown>) {
129
+ this.log("error", msg, data);
130
+ }
131
+
132
+ // ── Formatted agent output helpers ────────────────────────────────────
133
+
134
+ private readonly boxWidth = 80;
135
+
136
+ private pipe(): string {
137
+ return this.c(C.brightCyan, "│");
138
+ }
139
+
140
+ private printBoxLine(label: string, value: string, labelColor = C.dim, valueColor = C.white): void {
141
+ const lines = value.split("\n");
142
+ const prefix = `${this.pipe()} ${this.c(labelColor, label)}`;
143
+ console.log(`${prefix}${this.c(valueColor, lines[0])}`);
144
+ const pad = " ".repeat(noColor(label).length);
145
+ for (let i = 1; i < lines.length; i++) {
146
+ console.log(`${this.pipe()} ${pad}${this.c(valueColor, lines[i])}`);
147
+ }
148
+ }
149
+
150
+ agentStart(agentName: string, input: string): void {
151
+ if (!this.shouldLog("info")) return;
152
+ const title = ` Agent: ${agentName} `;
153
+ const lineLen = Math.max(0, this.boxWidth - title.length - 2);
154
+ console.log("");
155
+ console.log(
156
+ this.c(C.bold + C.brightCyan, "┌─") +
157
+ this.c(C.bold + C.brightCyan, title) +
158
+ this.c(C.dim, "─".repeat(lineLen))
159
+ );
160
+ this.printBoxLine("Input: ", input);
161
+ console.log(this.pipe());
162
+ }
163
+
164
+ toolCall(toolName: string, args: Record<string, unknown>): void {
165
+ if (!this.shouldLog("debug")) return;
166
+ const argsStr = JSON.stringify(args, null, 2);
167
+ console.log(
168
+ `${this.pipe()} ${this.c(C.brightMagenta, "⚡")} ${this.c(C.magenta, toolName)}`
169
+ );
170
+ if (argsStr !== "{}" && argsStr !== "[]") {
171
+ const truncated = argsStr.length > 200 ? argsStr.slice(0, 200) + "…" : argsStr;
172
+ for (const line of truncated.split("\n")) {
173
+ console.log(`${this.pipe()} ${this.c(C.dim, line)}`);
174
+ }
175
+ }
176
+ }
177
+
178
+ toolResult(toolName: string, result: string): void {
179
+ if (!this.shouldLog("debug")) return;
180
+ const truncated = result.length > 300 ? result.slice(0, 300) + "…" : result;
181
+ console.log(
182
+ `${this.pipe()} ${this.c(C.green, "✓")} ${this.c(C.dim, toolName + " →")}`
183
+ );
184
+ for (const line of truncated.split("\n")) {
185
+ console.log(`${this.pipe()} ${this.c(C.gray, line)}`);
186
+ }
187
+ console.log(this.pipe());
188
+ }
189
+
190
+ agentEnd(
191
+ agentName: string,
192
+ output: string,
193
+ usage: { promptTokens: number; completionTokens: number; totalTokens: number },
194
+ durationMs: number
195
+ ): void {
196
+ if (!this.shouldLog("info")) return;
197
+
198
+ console.log(this.pipe());
199
+ this.printBoxLine("Output: ", output);
200
+ console.log(this.pipe());
201
+
202
+ const tokensLine =
203
+ this.c(C.dim, "Tokens: ") +
204
+ this.c(C.brightGreen, `↑ ${usage.promptTokens}`) +
205
+ this.c(C.dim, " ") +
206
+ this.c(C.brightGreen, `↓ ${usage.completionTokens}`) +
207
+ this.c(C.dim, " ") +
208
+ this.c(C.bold + C.brightGreen, `Σ ${usage.totalTokens}`);
209
+
210
+ const duration =
211
+ this.c(C.dim, "Duration: ") +
212
+ this.c(C.yellow, this.formatDuration(durationMs));
213
+
214
+ console.log(`${this.pipe()} ${tokensLine}`);
215
+ console.log(`${this.pipe()} ${duration}`);
216
+ console.log(
217
+ this.c(C.bold + C.brightCyan, "└") +
218
+ this.c(C.dim, "─".repeat(this.boxWidth - 1))
219
+ );
220
+ }
221
+
222
+ separator(): void {
223
+ if (!this.shouldLog("info")) return;
224
+ console.log(this.c(C.dim, "─".repeat(this.boxWidth)));
225
+ }
226
+
227
+ private formatDuration(ms: number): string {
228
+ if (ms < 1000) return `${ms}ms`;
229
+ const secs = (ms / 1000).toFixed(1);
230
+ return `${secs}s`;
231
+ }
232
+ }