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
11 changes: 3 additions & 8 deletions packages/server/src/agents/engines/aider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { unlink, writeFile } from "node:fs/promises";
import type { Subprocess } from "bun";
import { parseAcpMessage } from "../acp-parser";
import type { AgentEngine, AgentEvent, EngineOptions } from "../engine";
import { getLiteLLMBaseUrl, listLiteLLMModels } from "../litellm-client";
import { streamProcess } from "../stream-process";
Expand Down Expand Up @@ -47,7 +48,7 @@ export class AiderEngine implements AgentEngine {
const promptFile = `/tmp/vibe-aider-prompt-${options.runId ?? Date.now()}.txt`;
await writeFile(promptFile, prompt, "utf8");

const args = ["aider", "--yes-always", "--no-auto-commits"];
const args = ["aider", "acp"];
if (options.model) args.push("--model", options.model);
args.push("--message", `@${promptFile}`);

Expand Down Expand Up @@ -76,13 +77,7 @@ export class AiderEngine implements AgentEngine {
if (options.runId) this.processes.set(options.runId, proc);

yield* withHeartbeat(
streamProcess(
proc,
(line) => {
return [{ type: "log", stream: "stdout", content: line }];
},
options.signal
),
streamProcess(proc, (line) => parseAcpMessage(line), options.signal),
getHeartbeatIntervalMs(),
options.signal
);
Expand Down
31 changes: 3 additions & 28 deletions packages/server/src/agents/engines/claude-code.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { unlink, writeFile } from "node:fs/promises";
import type { Subprocess } from "bun";
import { parseAcpMessage } from "../acp-parser";
import type { AgentEngine, AgentEvent, EngineOptions } from "../engine";
import { getLiteLLMBaseUrl, listLiteLLMModels } from "../litellm-client";
import { streamProcess } from "../stream-process";
Expand Down Expand Up @@ -47,7 +48,7 @@ export class ClaudeCodeEngine implements AgentEngine {
const promptFile = `/tmp/vibe-claude-code-prompt-${options.runId ?? Date.now()}.txt`;
await writeFile(promptFile, prompt, "utf8");

const args = ["claude", "--print", "--verbose", "--output-format", "stream-json"];
const args = ["claude", "acp"];
if (options.model) args.push("--model", options.model);
args.push("-p", `@${promptFile}`);

Expand All @@ -70,33 +71,7 @@ export class ClaudeCodeEngine implements AgentEngine {
if (options.runId) this.processes.set(options.runId, proc);

yield* withHeartbeat(
streamProcess(
proc,
(line) => {
try {
const parsed = JSON.parse(line);
if (parsed.type === "assistant" && parsed.content) {
const events: AgentEvent[] = [];
for (const block of parsed.content) {
if (block.type === "text") {
events.push({ type: "log", stream: "stdout", content: block.text });
} else if (block.type === "tool_use") {
events.push({
type: "log",
stream: "system",
content: `[tool] ${block.name}: ${JSON.stringify(block.input).slice(0, 200)}`,
});
}
}
return events;
}
return [];
} catch {
return [{ type: "log", stream: "stdout", content: line }];
}
},
options.signal
),
streamProcess(proc, (line) => parseAcpMessage(line), options.signal),
getHeartbeatIntervalMs(),
options.signal
);
Expand Down
123 changes: 3 additions & 120 deletions packages/server/src/agents/engines/copilot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { execSync } from "node:child_process";
import { join } from "node:path";
import type { Subprocess } from "bun";
import { parseAcpMessage } from "../acp-parser";
import type { AgentEngine, AgentEvent, EngineOptions } from "../engine";
import { streamProcess } from "../stream-process";
import { getHeartbeatIntervalMs, withHeartbeat } from "./heartbeat";
Expand Down Expand Up @@ -51,115 +52,6 @@ export class CopilotEngine implements AgentEngine {
return env;
}

private parseLine(line: string): AgentEvent[] {
// Try to parse stream-json / JSONL output
let parsed: unknown;
try {
parsed = JSON.parse(line);
} catch {
// Not JSON — emit as raw log
return [{ type: "log", stream: "stdout", content: line }];
}

if (!parsed || typeof parsed !== "object") {
return [{ type: "log", stream: "stdout", content: line }];
}

const obj = parsed as Record<string, unknown>;

// Error event
if (obj.error || obj.type === "error") {
const errMsg =
typeof obj.error === "string"
? obj.error
: typeof obj.message === "string"
? obj.message
: JSON.stringify(obj.error ?? obj);
return [
{ type: "log", stream: "stderr", content: `[copilot] ${errMsg}` },
{ type: "error", content: errMsg },
];
}

// Tool use
if (obj.type === "tool_use" || obj.type === "tool_call") {
const name: string =
typeof obj.name === "string"
? obj.name
: typeof obj.function === "object" && obj.function !== null
? String((obj.function as Record<string, unknown>).name ?? "tool")
: "tool";
const toolId: string =
typeof obj.id === "string" ? obj.id : typeof obj.call_id === "string" ? obj.call_id : "";
const parameters = (obj.parameters ?? obj.input ?? obj.args) as
| Record<string, unknown>
| undefined;
return [
{
type: "tool_use",
toolUse: { toolId, toolName: name, parameters: parameters ?? undefined },
},
{
type: "log",
stream: "system",
content: `[tool] ${name}${toolId ? ` (${toolId})` : ""}`,
},
];
}

// Tool result
if (obj.type === "tool_result" || obj.type === "tool_execution_complete") {
const toolId =
typeof obj.tool_call_id === "string"
? obj.tool_call_id
: typeof obj.call_id === "string"
? obj.call_id
: typeof obj.id === "string"
? obj.id
: "";
const output =
typeof obj.output === "string"
? obj.output
: typeof obj.result === "string"
? obj.result
: JSON.stringify(obj.output ?? obj.result ?? "");
const status = obj.error ? "error" : "success";
return [
{
type: "tool_result",
toolResult: { toolId, output, status },
},
{
type: "log",
stream: "system",
content: `[tool result] ${status}${toolId ? ` (${toolId})` : ""}`,
},
];
}

// Text / message content
const text =
typeof obj.text === "string"
? obj.text
: typeof obj.content === "string"
? obj.content
: Array.isArray(obj.content)
? obj.content
.filter((b: unknown) => typeof (b as Record<string, unknown>)?.text === "string")
.map((b: unknown) => (b as Record<string, string>).text)
.join("")
: typeof obj.message === "string"
? obj.message
: null;

if (text) {
return [{ type: "log", stream: "stdout", content: text }];
}

// Generic debug log for unrecognised events
return [{ type: "log", stream: "system", content: line }];
}

async isAvailable(): Promise<boolean> {
try {
const bin = this.getCopilotBinPath();
Expand Down Expand Up @@ -216,16 +108,7 @@ export class CopilotEngine implements AgentEngine {
content: `[copilot] bin=${bin} model=${options.model ?? "default"}`,
};

const args = [
bin,
"--allow-all",
"--output-format",
"json",
"--add-dir",
workdir,
"-p",
prompt,
];
const args = [bin, "acp", "-p", prompt];
if (options.model) args.push("--model", options.model);
if (options.resumeSessionId) args.push("--resume", options.resumeSessionId);

Expand All @@ -240,7 +123,7 @@ export class CopilotEngine implements AgentEngine {
if (options.runId) this.processes.set(options.runId, proc);

yield* withHeartbeat(
streamProcess(proc, (line) => this.parseLine(line), options.signal),
streamProcess(proc, (line) => parseAcpMessage(line), options.signal),
getHeartbeatIntervalMs(),
options.signal
);
Expand Down
11 changes: 3 additions & 8 deletions packages/server/src/agents/engines/cursor-agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { unlink, writeFile } from "node:fs/promises";
import type { Subprocess } from "bun";
import { parseAcpMessage } from "../acp-parser";
import type { AgentEngine, AgentEvent, EngineOptions } from "../engine";
import { getLiteLLMBaseUrl, listLiteLLMModels } from "../litellm-client";
import { streamProcess } from "../stream-process";
Expand Down Expand Up @@ -46,7 +47,7 @@ export class CursorAgentEngine implements AgentEngine {
const promptFile = `/tmp/vibe-cursor-prompt-${options.runId ?? Date.now()}.txt`;
await writeFile(promptFile, prompt, "utf8");

const args = ["cursor-agent"];
const args = ["cursor-agent", "acp"];
if (options.model) args.push("--model", options.model);
args.push("--message", `@${promptFile}`);

Expand All @@ -73,13 +74,7 @@ export class CursorAgentEngine implements AgentEngine {
if (options.runId) this.processes.set(options.runId, proc);

yield* withHeartbeat(
streamProcess(
proc,
(line) => {
return [{ type: "log", stream: "stdout", content: line }];
},
options.signal
),
streamProcess(proc, (line) => parseAcpMessage(line), options.signal),
getHeartbeatIntervalMs(),
options.signal
);
Expand Down
Loading
Loading