Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/cli/commands/interactive.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import chalk from "chalk";
import { logger } from "../../shared/logger";
import { showMainMenu, showGoodbyeScreen, pressAnyKey } from "../tui";
import { authCommand, loadConfig } from "./auth";
import { configCommand } from "./config";
Expand All @@ -11,9 +10,6 @@ import { startCommand } from "./start";
export async function interactiveMode(): Promise<void> {
let running = true;

// Initialize logger early so logs dir is created on first run
logger.debug("TxtCode interactive mode started");

while (running) {
console.clear();

Expand Down
4 changes: 0 additions & 4 deletions src/cli/commands/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,6 @@ export function logsCommand(session: string | undefined, options: LogsOptions) {
console.log(` ${chalk.white(`[${i + 1}]`)} ${ts} ${chalk.gray(`(${sizeStr})`)}${label}`);
}

console.log("");
console.log(chalk.gray(" txtcode logs <number> View a session"));
console.log(chalk.gray(" txtcode logs -f Follow the latest session"));
console.log(chalk.gray(" txtcode logs --clear Delete all logs"));
console.log("");
return;
}
Expand Down
50 changes: 37 additions & 13 deletions src/cli/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,31 +148,55 @@ export async function startCommand(_options: { daemon?: boolean }) {
process.on("SIGINT", shutdownHandler);
process.on("SIGTERM", shutdownHandler);

// Set up Enter key listener to stop the agent
const waitForEnter = new Promise<void>((resolve) => {
process.stdin.setRawMode?.(false);
process.stdin.resume();
process.stdin.once("data", () => resolve());
});

try {
let bot: { start(): Promise<void> };

if (config.platform === "whatsapp") {
const bot = new WhatsAppBot(agent);
await bot.start();
bot = new WhatsAppBot(agent);
} else if (config.platform === "telegram") {
const bot = new TelegramBot(agent);
await bot.start();
bot = new TelegramBot(agent);
} else if (config.platform === "discord") {
const bot = new DiscordBot(agent);
await bot.start();
bot = new DiscordBot(agent);
} else if (config.platform === "slack") {
const bot = new SlackBot(agent);
await bot.start();
bot = new SlackBot(agent);
} else if (config.platform === "teams") {
const bot = new TeamsBot(agent);
await bot.start();
bot = new TeamsBot(agent);
} else if (config.platform === "signal") {
const bot = new SignalBot(agent);
await bot.start();
bot = new SignalBot(agent);
} else {
logger.error("Invalid platform specified");
process.exit(1);
}

// Start bot without blocking — race with Enter key
bot.start().catch((error) => {
logger.error("Failed to start agent", error);
process.exit(1);
});

// Show message after a short delay to let the bot print its startup logs
setTimeout(() => {
console.log(chalk.gray("\nPress Enter to stop the agent...\n"));
}, 2000);

// Wait for user to press Enter
await waitForEnter;

logger.debug("User requested stop via Enter key");
process.stdin.pause();
await agent.shutdown();
// Return to main menu instead of exiting
return;
} catch (error) {
logger.error("Failed to start agent", error);
process.exit(1);
// Return to main menu instead of exiting
return;
}
}
26 changes: 26 additions & 0 deletions src/core/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,25 @@ export class Router {
return "[WARN] AI model not configured. Run: txtcode config";
}

logger.debug(`[Router] Chat → provider=${this.provider}, model=${this.model}`);
const startTime = Date.now();

try {
const result = await this._routeToProvider(instruction);
logger.debug(
`[Router] Chat complete → provider=${this.provider}, time=${Date.now() - startTime}ms, response=${result.length} chars`,
);
return result;
} catch (error) {
logger.error(
`[Router] Chat failed → provider=${this.provider}, time=${Date.now() - startTime}ms`,
error,
);
throw error;
}
}

private async _routeToProvider(instruction: string): Promise<string> {
switch (this.provider) {
case "anthropic":
return await processWithAnthropic(instruction, this.apiKey, this.model, this.toolRegistry);
Expand Down Expand Up @@ -287,6 +306,9 @@ export class Router {
this.currentAbortController = new AbortController();
const signal = this.currentAbortController.signal;

logger.debug(`[Router] Code → adapter=${this.currentAdapterName}`);
const startTime = Date.now();

try {
this.contextManager.addEntry("user", instruction);

Expand All @@ -307,6 +329,10 @@ export class Router {

this.contextManager.addEntry("assistant", result);

logger.debug(
`[Router] Code complete → adapter=${this.currentAdapterName}, time=${Date.now() - startTime}ms, response=${result.length} chars`,
);

return result;
} finally {
this.currentAbortController = null;
Expand Down
16 changes: 16 additions & 0 deletions src/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
ToolUnion,
ToolUseBlock,
} from "@anthropic-ai/sdk/resources/messages/messages";
import { logger } from "../shared/logger";
import { ToolRegistry } from "../tools/registry";

const MAX_ITERATIONS = 10;
Expand All @@ -28,6 +29,9 @@ export async function processWithAnthropic(
model: string,
toolRegistry?: ToolRegistry,
): Promise<string> {
const startTime = Date.now();
logger.debug(`[Anthropic] Request → model=${model}, prompt=${instruction.length} chars`);

try {
const anthropic = new Anthropic({ apiKey });

Expand All @@ -38,6 +42,7 @@ export async function processWithAnthropic(
const messages: MessageParam[] = [{ role: "user", content: instruction }];

for (let i = 0; i < MAX_ITERATIONS; i++) {
const iterStart = Date.now();
const response = await anthropic.messages.create({
model,
max_tokens: 4096,
Expand All @@ -46,6 +51,12 @@ export async function processWithAnthropic(
...(tools ? { tools } : {}),
});

logger.debug(
`[Anthropic] Response ← iteration=${i + 1}, stop=${response.stop_reason}, ` +
`tokens=${response.usage.input_tokens}in/${response.usage.output_tokens}out, ` +
`time=${Date.now() - iterStart}ms`,
);

const textParts = response.content
.filter((block: ContentBlock): block is TextBlock => block.type === "text")
.map((block: TextBlock) => block.text);
Expand All @@ -55,9 +66,12 @@ export async function processWithAnthropic(
);

if (toolCalls.length === 0 || !toolRegistry) {
logger.debug(`[Anthropic] Done in ${Date.now() - startTime}ms (${i + 1} iteration(s))`);
return textParts.join("\n") || "No response from Claude";
}

logger.debug(`[Anthropic] Tool calls: ${toolCalls.map((t) => t.name).join(", ")}`);

messages.push({ role: "assistant", content: response.content });

const toolResults: ToolResultBlockParam[] = [];
Expand All @@ -76,8 +90,10 @@ export async function processWithAnthropic(
messages.push({ role: "user", content: toolResults });
}

logger.warn(`[Anthropic] Reached max ${MAX_ITERATIONS} iterations`);
return "Reached maximum tool iterations.";
} catch (error: unknown) {
logger.error(`[Anthropic] API error after ${Date.now() - startTime}ms`, error);
throw new Error(
`Anthropic API error: ${error instanceof Error ? error.message : "Unknown error"}`,
{ cause: error },
Expand Down
17 changes: 17 additions & 0 deletions src/providers/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type Tool as GeminiTool,
GoogleGenerativeAI,
} from "@google/generative-ai";
import { logger } from "../shared/logger";
import { ToolRegistry } from "../tools/registry";

const MAX_ITERATIONS = 10;
Expand All @@ -24,6 +25,9 @@ export async function processWithGemini(
model: string,
toolRegistry?: ToolRegistry,
): Promise<string> {
const startTime = Date.now();
logger.debug(`[Gemini] Request → model=${model}, prompt=${instruction.length} chars`);

try {
const genAI = new GoogleGenerativeAI(apiKey);

Expand All @@ -38,16 +42,26 @@ export async function processWithGemini(
});

const chat = genModel.startChat();
let iterStart = Date.now();
let result = await chat.sendMessage(instruction);

for (let i = 0; i < MAX_ITERATIONS; i++) {
const response = result.response;
const calls = response.functionCalls();

logger.debug(
`[Gemini] Response ← iteration=${i + 1}, ` +
`toolCalls=${calls?.length ?? 0}, ` +
`time=${Date.now() - iterStart}ms`,
);

if (!calls || calls.length === 0 || !toolRegistry) {
logger.debug(`[Gemini] Done in ${Date.now() - startTime}ms (${i + 1} iteration(s))`);
return response.text();
}

logger.debug(`[Gemini] Tool calls: ${calls.map((c) => c.name).join(", ")}`);

const toolResults: FunctionResponsePart[] = [];
for (const call of calls) {
const execResult = await toolRegistry.execute(
Expand All @@ -62,11 +76,14 @@ export async function processWithGemini(
});
}

iterStart = Date.now();
result = await chat.sendMessage(toolResults);
}

logger.warn(`[Gemini] Reached max ${MAX_ITERATIONS} iterations`);
return "Reached maximum tool iterations.";
} catch (error: unknown) {
logger.error(`[Gemini] API error after ${Date.now() - startTime}ms`, error);
throw new Error(
`Gemini API error: ${error instanceof Error ? error.message : "Unknown error"}`,
{ cause: error },
Expand Down
18 changes: 18 additions & 0 deletions src/providers/huggingface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
ChatCompletionMessageParam,
ChatCompletionTool,
} from "openai/resources/chat/completions/completions";
import { logger } from "../shared/logger";
import { ToolRegistry } from "../tools/registry";

const MAX_ITERATIONS = 10;
Expand All @@ -25,6 +26,9 @@ export async function processWithHuggingFace(
model: string,
toolRegistry?: ToolRegistry,
): Promise<string> {
const startTime = Date.now();
logger.debug(`[HuggingFace] Request → model=${model}, prompt=${instruction.length} chars`);

try {
// HuggingFace Inference Providers use OpenAI-compatible API
const client = new OpenAI({
Expand All @@ -42,6 +46,7 @@ export async function processWithHuggingFace(
];

for (let i = 0; i < MAX_ITERATIONS; i++) {
const iterStart = Date.now();
const completion = await client.chat.completions.create({
model,
messages,
Expand All @@ -52,10 +57,21 @@ export async function processWithHuggingFace(
const choice = completion.choices[0];
const assistantMsg = choice.message;

logger.debug(
`[HuggingFace] Response ← iteration=${i + 1}, finish=${choice.finish_reason}, ` +
`tokens=${completion.usage?.prompt_tokens ?? "?"}in/${completion.usage?.completion_tokens ?? "?"}out, ` +
`time=${Date.now() - iterStart}ms`,
);

if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0 || !toolRegistry) {
logger.debug(`[HuggingFace] Done in ${Date.now() - startTime}ms (${i + 1} iteration(s))`);
return assistantMsg.content || "No response from HuggingFace";
}

logger.debug(
`[HuggingFace] Tool calls: ${assistantMsg.tool_calls.map((t) => ("function" in t ? t.function.name : t.type)).join(", ")}`,
);

messages.push(assistantMsg);

for (const toolCall of assistantMsg.tool_calls) {
Expand All @@ -72,8 +88,10 @@ export async function processWithHuggingFace(
}
}

logger.warn(`[HuggingFace] Reached max ${MAX_ITERATIONS} iterations`);
return "Reached maximum tool iterations.";
} catch (error: unknown) {
logger.error(`[HuggingFace] API error after ${Date.now() - startTime}ms`, error);
throw new Error(
`HuggingFace API error: ${error instanceof Error ? error.message : "Unknown error"}`,
{ cause: error },
Expand Down
16 changes: 16 additions & 0 deletions src/providers/minimax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
ToolUnion,
ToolUseBlock,
} from "@anthropic-ai/sdk/resources/messages/messages";
import { logger } from "../shared/logger";
import { ToolRegistry } from "../tools/registry";

const MAX_ITERATIONS = 10;
Expand All @@ -29,6 +30,9 @@ export async function processWithMiniMax(
model: string,
toolRegistry?: ToolRegistry,
): Promise<string> {
const startTime = Date.now();
logger.debug(`[MiniMax] Request → model=${model}, prompt=${instruction.length} chars`);

try {
// MiniMax uses Anthropic-compatible API
const client = new Anthropic({
Expand All @@ -43,6 +47,7 @@ export async function processWithMiniMax(
const messages: MessageParam[] = [{ role: "user", content: instruction }];

for (let i = 0; i < MAX_ITERATIONS; i++) {
const iterStart = Date.now();
const response = await client.messages.create({
model,
max_tokens: 4096,
Expand All @@ -51,6 +56,12 @@ export async function processWithMiniMax(
...(tools ? { tools } : {}),
});

logger.debug(
`[MiniMax] Response ← iteration=${i + 1}, stop=${response.stop_reason}, ` +
`tokens=${response.usage.input_tokens}in/${response.usage.output_tokens}out, ` +
`time=${Date.now() - iterStart}ms`,
);

const textParts = response.content
.filter((block: ContentBlock): block is TextBlock => block.type === "text")
.map((block: TextBlock) => block.text);
Expand All @@ -60,9 +71,12 @@ export async function processWithMiniMax(
);

if (toolCalls.length === 0 || !toolRegistry) {
logger.debug(`[MiniMax] Done in ${Date.now() - startTime}ms (${i + 1} iteration(s))`);
return textParts.join("\n") || "No response from MiniMax";
}

logger.debug(`[MiniMax] Tool calls: ${toolCalls.map((t) => t.name).join(", ")}`);

messages.push({ role: "assistant", content: response.content });

const toolResults: ToolResultBlockParam[] = [];
Expand All @@ -81,8 +95,10 @@ export async function processWithMiniMax(
messages.push({ role: "user", content: toolResults });
}

logger.warn(`[MiniMax] Reached max ${MAX_ITERATIONS} iterations`);
return "Reached maximum tool iterations.";
} catch (error: unknown) {
logger.error(`[MiniMax] API error after ${Date.now() - startTime}ms`, error);
throw new Error(
`MiniMax API error: ${error instanceof Error ? error.message : "Unknown error"}`,
{ cause: error },
Expand Down
Loading