@epic-cloudcontrol/daemon 0.2.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.
- package/README.md +150 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +525 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +38 -0
- package/dist/mcp-server.d.ts +26 -0
- package/dist/mcp-server.js +522 -0
- package/dist/model-router.d.ts +40 -0
- package/dist/model-router.js +146 -0
- package/dist/models/claude-code.d.ts +15 -0
- package/dist/models/claude-code.js +140 -0
- package/dist/models/claude.d.ts +34 -0
- package/dist/models/claude.js +121 -0
- package/dist/models/cli-adapter.d.ts +48 -0
- package/dist/models/cli-adapter.js +218 -0
- package/dist/models/ollama.d.ts +25 -0
- package/dist/models/ollama.js +139 -0
- package/dist/multi-profile.d.ts +6 -0
- package/dist/multi-profile.js +137 -0
- package/dist/profile.d.ts +27 -0
- package/dist/profile.js +97 -0
- package/dist/retry.d.ts +17 -0
- package/dist/retry.js +45 -0
- package/dist/sandbox.d.ts +53 -0
- package/dist/sandbox.js +216 -0
- package/dist/service-manager.d.ts +13 -0
- package/dist/service-manager.js +262 -0
- package/dist/task-executor.d.ts +47 -0
- package/dist/task-executor.js +195 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +17 -0
- package/package.json +36 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ClaudeAdapter } from "./models/claude.js";
|
|
2
|
+
import { OllamaAdapter } from "./models/ollama.js";
|
|
3
|
+
import { CliAdapter, KNOWN_CLIS, parseCliModels } from "./models/cli-adapter.js";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
function isCommandInstalled(command) {
|
|
6
|
+
try {
|
|
7
|
+
execSync(`which ${command}`, { stdio: "ignore" });
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* ModelRouter manages a roster of available AI models and selects
|
|
16
|
+
* the best one for each task based on the task's modelHint.
|
|
17
|
+
*
|
|
18
|
+
* Models are detected from:
|
|
19
|
+
* 1. ANTHROPIC_API_KEY → Claude API (sonnet + haiku)
|
|
20
|
+
* 2. Installed CLIs → auto-detected from KNOWN_CLIS + CLOUDCONTROL_CLI_MODELS env
|
|
21
|
+
* 3. CLOUDCONTROL_OLLAMA_MODELS → Ollama local models
|
|
22
|
+
*/
|
|
23
|
+
export class ModelRouter {
|
|
24
|
+
models = [];
|
|
25
|
+
defaultModel;
|
|
26
|
+
constructor() {
|
|
27
|
+
this.defaultModel = "";
|
|
28
|
+
this.detectAvailableModels();
|
|
29
|
+
}
|
|
30
|
+
detectAvailableModels() {
|
|
31
|
+
// 1. Claude API models (require ANTHROPIC_API_KEY)
|
|
32
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
33
|
+
const hasApiKey = apiKey && apiKey !== "your-anthropic-api-key-here" && apiKey.length > 0;
|
|
34
|
+
if (hasApiKey) {
|
|
35
|
+
this.models.push({
|
|
36
|
+
name: "claude-sonnet",
|
|
37
|
+
adapter: new ClaudeAdapter("claude-sonnet-4-20250514"),
|
|
38
|
+
traits: ["smartest", "code", "vision"],
|
|
39
|
+
available: true,
|
|
40
|
+
});
|
|
41
|
+
this.models.push({
|
|
42
|
+
name: "claude-haiku",
|
|
43
|
+
adapter: new ClaudeAdapter("claude-haiku-4-5-20251001"),
|
|
44
|
+
traits: ["cheapest", "fastest"],
|
|
45
|
+
available: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// 2. Auto-detect installed CLIs from KNOWN_CLIS
|
|
49
|
+
const cliTraits = {
|
|
50
|
+
"claude-code": ["smartest", "code", "local"],
|
|
51
|
+
gemini: ["smartest", "code", "local"],
|
|
52
|
+
codex: ["code", "local"],
|
|
53
|
+
aider: ["code", "local"],
|
|
54
|
+
goose: ["code", "local"],
|
|
55
|
+
};
|
|
56
|
+
for (const [name, config] of Object.entries(KNOWN_CLIS)) {
|
|
57
|
+
if (isCommandInstalled(config.command)) {
|
|
58
|
+
this.models.push({
|
|
59
|
+
name,
|
|
60
|
+
adapter: new CliAdapter({ name, ...config }),
|
|
61
|
+
traits: cliTraits[name] || ["code", "local"],
|
|
62
|
+
available: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 3. Custom CLI models from env var
|
|
67
|
+
// Format: "name:command:args;name2:command2:args"
|
|
68
|
+
// Or just names from KNOWN_CLIS: "gemini;aider"
|
|
69
|
+
const customClis = process.env.CLOUDCONTROL_CLI_MODELS;
|
|
70
|
+
if (customClis) {
|
|
71
|
+
const configs = parseCliModels(customClis);
|
|
72
|
+
for (const config of configs) {
|
|
73
|
+
// Skip if already registered (auto-detected above)
|
|
74
|
+
if (this.models.some((m) => m.name === config.name))
|
|
75
|
+
continue;
|
|
76
|
+
this.models.push({
|
|
77
|
+
name: config.name,
|
|
78
|
+
adapter: new CliAdapter(config),
|
|
79
|
+
traits: ["code", "local"],
|
|
80
|
+
available: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 4. Ollama models
|
|
85
|
+
const ollamaUrl = process.env.OLLAMA_URL || "http://localhost:11434";
|
|
86
|
+
const ollamaModels = process.env.CLOUDCONTROL_OLLAMA_MODELS?.split(",") || [];
|
|
87
|
+
for (const model of ollamaModels) {
|
|
88
|
+
const name = model.trim();
|
|
89
|
+
if (!name)
|
|
90
|
+
continue;
|
|
91
|
+
this.models.push({
|
|
92
|
+
name: `ollama/${name}`,
|
|
93
|
+
adapter: new OllamaAdapter(name, ollamaUrl),
|
|
94
|
+
traits: ["cheapest", "local"],
|
|
95
|
+
available: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Set default: first available local model, or cheapest API model
|
|
99
|
+
const localModel = this.models.find((m) => m.traits.includes("local"));
|
|
100
|
+
const cheapApi = this.models.find((m) => m.name === "claude-haiku");
|
|
101
|
+
this.defaultModel = localModel?.name || cheapApi?.name || this.models[0]?.name || "";
|
|
102
|
+
console.log(`[router] Available models: ${this.models.map((m) => m.name).join(", ") || "none"}`);
|
|
103
|
+
console.log(`[router] Default model: ${this.defaultModel || "none"}`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Select the best model for a task based on its modelHint.
|
|
107
|
+
*/
|
|
108
|
+
select(modelHint) {
|
|
109
|
+
if (!modelHint || modelHint === "auto") {
|
|
110
|
+
return this.getByName(this.defaultModel);
|
|
111
|
+
}
|
|
112
|
+
// Specific model name — exact match
|
|
113
|
+
const exact = this.models.find((m) => m.name === modelHint && m.available);
|
|
114
|
+
if (exact) {
|
|
115
|
+
return { adapter: exact.adapter, name: exact.name };
|
|
116
|
+
}
|
|
117
|
+
// Trait-based hint — find best match
|
|
118
|
+
const trait = modelHint;
|
|
119
|
+
const candidates = this.models.filter((m) => m.available && m.traits.includes(trait));
|
|
120
|
+
if (candidates.length > 0) {
|
|
121
|
+
return { adapter: candidates[0].adapter, name: candidates[0].name };
|
|
122
|
+
}
|
|
123
|
+
console.log(`[router] No model matches hint "${modelHint}", using default "${this.defaultModel}"`);
|
|
124
|
+
return this.getByName(this.defaultModel);
|
|
125
|
+
}
|
|
126
|
+
getByName(name) {
|
|
127
|
+
const model = this.models.find((m) => m.name === name && m.available);
|
|
128
|
+
if (model)
|
|
129
|
+
return { adapter: model.adapter, name: model.name };
|
|
130
|
+
const fallback = this.models.find((m) => m.available);
|
|
131
|
+
if (fallback) {
|
|
132
|
+
console.log(`[router] Default "${name}" not available, falling back to "${fallback.name}"`);
|
|
133
|
+
return { adapter: fallback.adapter, name: fallback.name };
|
|
134
|
+
}
|
|
135
|
+
throw new Error("No AI models available. Install a CLI (claude, gemini, etc.), set ANTHROPIC_API_KEY, or configure Ollama.");
|
|
136
|
+
}
|
|
137
|
+
getDefault() {
|
|
138
|
+
return this.defaultModel || "none";
|
|
139
|
+
}
|
|
140
|
+
listModels() {
|
|
141
|
+
return this.models
|
|
142
|
+
.filter((m) => m.available)
|
|
143
|
+
.map((m) => ({ name: m.name, traits: m.traits }));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=model-router.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ExecutionResult } from "./claude.js";
|
|
2
|
+
import { type SandboxConfig } from "../sandbox.js";
|
|
3
|
+
export declare class ClaudeCodeAdapter {
|
|
4
|
+
private sandboxConfig;
|
|
5
|
+
constructor(sandboxConfig?: SandboxConfig);
|
|
6
|
+
execute(task: {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string | null;
|
|
9
|
+
taskType?: string | null;
|
|
10
|
+
context?: Record<string, unknown> | null;
|
|
11
|
+
processHint?: string | null;
|
|
12
|
+
humanContext?: string | null;
|
|
13
|
+
}): Promise<ExecutionResult>;
|
|
14
|
+
private runClaude;
|
|
15
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { buildSandboxPrompt, getDefaultSandboxConfig, filterEnvironment, truncateOutput, } from "../sandbox.js";
|
|
3
|
+
export class ClaudeCodeAdapter {
|
|
4
|
+
sandboxConfig;
|
|
5
|
+
constructor(sandboxConfig) {
|
|
6
|
+
this.sandboxConfig = sandboxConfig || getDefaultSandboxConfig();
|
|
7
|
+
}
|
|
8
|
+
async execute(task) {
|
|
9
|
+
const dialogue = [];
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
// Build prompt with sandbox restrictions
|
|
12
|
+
const sandboxRules = buildSandboxPrompt(this.sandboxConfig);
|
|
13
|
+
let prompt = `## Task: ${task.title}\n`;
|
|
14
|
+
if (task.description)
|
|
15
|
+
prompt += `\n${task.description}\n`;
|
|
16
|
+
if (task.taskType)
|
|
17
|
+
prompt += `\nTask type: ${task.taskType}\n`;
|
|
18
|
+
if (task.processHint)
|
|
19
|
+
prompt += `\nProcess hint: ${task.processHint}\n`;
|
|
20
|
+
if (task.context) {
|
|
21
|
+
prompt += `\nContext:\n\`\`\`json\n${JSON.stringify(task.context, null, 2)}\n\`\`\`\n`;
|
|
22
|
+
}
|
|
23
|
+
if (task.humanContext) {
|
|
24
|
+
prompt += `\nHuman feedback from previous attempt:\n${task.humanContext}\n`;
|
|
25
|
+
}
|
|
26
|
+
prompt += `\nWhen you encounter something you cannot complete (CAPTCHA, phone verification, account creation, ambiguous decision), say: [HUMAN_REQUIRED]: <reason>\n`;
|
|
27
|
+
prompt += `\nWhen done, end with: [RESULT]: <brief summary>\n`;
|
|
28
|
+
prompt += sandboxRules;
|
|
29
|
+
dialogue.push({
|
|
30
|
+
role: "user",
|
|
31
|
+
content: prompt,
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const output = await this.runClaude(prompt);
|
|
36
|
+
dialogue.push({
|
|
37
|
+
role: "assistant",
|
|
38
|
+
content: output,
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
});
|
|
41
|
+
// Check for human required
|
|
42
|
+
const humanMatch = output.match(/\[HUMAN_REQUIRED\]:\s*(.*)/s);
|
|
43
|
+
if (humanMatch) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
dialogue,
|
|
47
|
+
result: { raw_response: output },
|
|
48
|
+
metadata: {
|
|
49
|
+
model: "claude-code",
|
|
50
|
+
tokens_used: 0,
|
|
51
|
+
duration_ms: Date.now() - startTime,
|
|
52
|
+
},
|
|
53
|
+
humanRequired: {
|
|
54
|
+
reason: humanMatch[1].trim(),
|
|
55
|
+
context: output,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const resultMatch = output.match(/\[RESULT\]:\s*(.*)/s);
|
|
60
|
+
const resultSummary = resultMatch
|
|
61
|
+
? resultMatch[1].trim()
|
|
62
|
+
: output.slice(0, 500);
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
dialogue,
|
|
66
|
+
result: { summary: resultSummary, raw_response: output },
|
|
67
|
+
metadata: {
|
|
68
|
+
model: "claude-code",
|
|
69
|
+
tokens_used: 0,
|
|
70
|
+
duration_ms: Date.now() - startTime,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const errMsg = error instanceof Error ? error.message : "Unknown error";
|
|
76
|
+
dialogue.push({
|
|
77
|
+
role: "system",
|
|
78
|
+
content: `Execution error: ${errMsg}`,
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
dialogue,
|
|
84
|
+
result: { error: errMsg },
|
|
85
|
+
metadata: {
|
|
86
|
+
model: "claude-code",
|
|
87
|
+
tokens_used: 0,
|
|
88
|
+
duration_ms: Date.now() - startTime,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
runClaude(prompt) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
// Filter environment — only pass allowed vars, strip secrets
|
|
96
|
+
const filteredEnv = filterEnvironment(process.env, this.sandboxConfig.allowedEnvVars);
|
|
97
|
+
const proc = spawn("claude", ["-p", prompt], {
|
|
98
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
99
|
+
env: filteredEnv,
|
|
100
|
+
});
|
|
101
|
+
let stdout = "";
|
|
102
|
+
let stderr = "";
|
|
103
|
+
const maxOutput = this.sandboxConfig.maxOutputBytes;
|
|
104
|
+
proc.stdout.on("data", (data) => {
|
|
105
|
+
stdout += data.toString();
|
|
106
|
+
// Kill process if output exceeds limit
|
|
107
|
+
if (Buffer.byteLength(stdout, "utf-8") > maxOutput * 1.1) {
|
|
108
|
+
proc.kill();
|
|
109
|
+
reject(new Error("Output exceeded maximum size limit"));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
proc.stderr.on("data", (data) => {
|
|
113
|
+
stderr += data.toString();
|
|
114
|
+
});
|
|
115
|
+
proc.on("close", (code) => {
|
|
116
|
+
const truncatedOutput = truncateOutput(stdout.trim(), maxOutput);
|
|
117
|
+
if (code === 0) {
|
|
118
|
+
resolve(truncatedOutput);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
reject(new Error(`claude exited with code ${code}: ${stderr.slice(0, 500)}`));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
proc.on("error", (err) => {
|
|
125
|
+
if (err.code === "ENOENT") {
|
|
126
|
+
reject(new Error("Claude Code CLI not found. Install it or set ANTHROPIC_API_KEY to use the API adapter."));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
reject(err);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// 5 minute timeout
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
proc.kill();
|
|
135
|
+
reject(new Error("Claude Code execution timed out (5m)"));
|
|
136
|
+
}, 5 * 60 * 1000);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type SandboxConfig } from "../sandbox.js";
|
|
2
|
+
export interface ChatMessage {
|
|
3
|
+
role: "user" | "assistant" | "system";
|
|
4
|
+
content: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ExecutionResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
dialogue: ChatMessage[];
|
|
10
|
+
result: Record<string, unknown>;
|
|
11
|
+
metadata: {
|
|
12
|
+
model: string;
|
|
13
|
+
tokens_used: number;
|
|
14
|
+
duration_ms: number;
|
|
15
|
+
};
|
|
16
|
+
humanRequired?: {
|
|
17
|
+
reason: string;
|
|
18
|
+
context: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare class ClaudeAdapter {
|
|
22
|
+
private client;
|
|
23
|
+
private model;
|
|
24
|
+
private sandboxConfig;
|
|
25
|
+
constructor(model?: string, sandboxConfig?: SandboxConfig);
|
|
26
|
+
execute(task: {
|
|
27
|
+
title: string;
|
|
28
|
+
description?: string | null;
|
|
29
|
+
taskType?: string | null;
|
|
30
|
+
context?: Record<string, unknown> | null;
|
|
31
|
+
processHint?: string | null;
|
|
32
|
+
humanContext?: string | null;
|
|
33
|
+
}): Promise<ExecutionResult>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { buildSandboxPrompt, getDefaultSandboxConfig } from "../sandbox.js";
|
|
3
|
+
export class ClaudeAdapter {
|
|
4
|
+
client;
|
|
5
|
+
model;
|
|
6
|
+
sandboxConfig;
|
|
7
|
+
constructor(model = "claude-sonnet-4-20250514", sandboxConfig) {
|
|
8
|
+
this.client = new Anthropic();
|
|
9
|
+
this.model = model;
|
|
10
|
+
this.sandboxConfig = sandboxConfig || getDefaultSandboxConfig();
|
|
11
|
+
}
|
|
12
|
+
async execute(task) {
|
|
13
|
+
const dialogue = [];
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
let totalTokens = 0;
|
|
16
|
+
// Build system prompt with sandbox restrictions
|
|
17
|
+
const sandboxRules = buildSandboxPrompt(this.sandboxConfig);
|
|
18
|
+
const systemPrompt = `You are a CloudControl task executor. You complete tasks efficiently and report results clearly.
|
|
19
|
+
|
|
20
|
+
When you encounter something you cannot complete (CAPTCHA, phone verification, account creation, ambiguous decision), respond with:
|
|
21
|
+
[HUMAN_REQUIRED]: <reason>
|
|
22
|
+
|
|
23
|
+
Always end your final response with:
|
|
24
|
+
[RESULT]: <brief summary of what was accomplished>
|
|
25
|
+
${sandboxRules}`;
|
|
26
|
+
// Build user message with full context
|
|
27
|
+
let userMessage = `## Task: ${task.title}\n`;
|
|
28
|
+
if (task.description)
|
|
29
|
+
userMessage += `\n${task.description}\n`;
|
|
30
|
+
if (task.taskType)
|
|
31
|
+
userMessage += `\nTask type: ${task.taskType}\n`;
|
|
32
|
+
if (task.processHint)
|
|
33
|
+
userMessage += `\nProcess hint: ${task.processHint}\n`;
|
|
34
|
+
if (task.context) {
|
|
35
|
+
userMessage += `\nContext:\n\`\`\`json\n${JSON.stringify(task.context, null, 2)}\n\`\`\`\n`;
|
|
36
|
+
}
|
|
37
|
+
if (task.humanContext) {
|
|
38
|
+
userMessage += `\nHuman feedback from previous attempt:\n${task.humanContext}\n`;
|
|
39
|
+
}
|
|
40
|
+
dialogue.push({
|
|
41
|
+
role: "user",
|
|
42
|
+
content: userMessage,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
const response = await this.client.messages.create({
|
|
47
|
+
model: this.model,
|
|
48
|
+
max_tokens: 4096,
|
|
49
|
+
system: systemPrompt,
|
|
50
|
+
messages: [{ role: "user", content: userMessage }],
|
|
51
|
+
});
|
|
52
|
+
const assistantContent = response.content
|
|
53
|
+
.filter((c) => c.type === "text")
|
|
54
|
+
.map((c) => c.text)
|
|
55
|
+
.join("\n");
|
|
56
|
+
totalTokens =
|
|
57
|
+
(response.usage?.input_tokens || 0) +
|
|
58
|
+
(response.usage?.output_tokens || 0);
|
|
59
|
+
dialogue.push({
|
|
60
|
+
role: "assistant",
|
|
61
|
+
content: assistantContent,
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
});
|
|
64
|
+
// Check for human required
|
|
65
|
+
const humanMatch = assistantContent.match(/\[HUMAN_REQUIRED\]:\s*(.*)/s);
|
|
66
|
+
if (humanMatch) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
dialogue,
|
|
70
|
+
result: { raw_response: assistantContent },
|
|
71
|
+
metadata: {
|
|
72
|
+
model: this.model,
|
|
73
|
+
tokens_used: totalTokens,
|
|
74
|
+
duration_ms: Date.now() - startTime,
|
|
75
|
+
},
|
|
76
|
+
humanRequired: {
|
|
77
|
+
reason: humanMatch[1].trim(),
|
|
78
|
+
context: assistantContent,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Extract result
|
|
83
|
+
const resultMatch = assistantContent.match(/\[RESULT\]:\s*(.*)/s);
|
|
84
|
+
const resultSummary = resultMatch
|
|
85
|
+
? resultMatch[1].trim()
|
|
86
|
+
: assistantContent.slice(0, 500);
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
dialogue,
|
|
90
|
+
result: {
|
|
91
|
+
summary: resultSummary,
|
|
92
|
+
raw_response: assistantContent,
|
|
93
|
+
},
|
|
94
|
+
metadata: {
|
|
95
|
+
model: this.model,
|
|
96
|
+
tokens_used: totalTokens,
|
|
97
|
+
duration_ms: Date.now() - startTime,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const errMsg = error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
dialogue.push({
|
|
104
|
+
role: "system",
|
|
105
|
+
content: `Execution error: ${errMsg}`,
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
dialogue,
|
|
111
|
+
result: { error: errMsg },
|
|
112
|
+
metadata: {
|
|
113
|
+
model: this.model,
|
|
114
|
+
tokens_used: totalTokens,
|
|
115
|
+
duration_ms: Date.now() - startTime,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ExecutionResult } from "./claude.js";
|
|
2
|
+
import { type SandboxConfig } from "../sandbox.js";
|
|
3
|
+
export interface CliModelConfig {
|
|
4
|
+
/** Display name (e.g., "gemini", "claude-code", "aider") */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Binary to execute (e.g., "claude", "gemini", "aider") */
|
|
7
|
+
command: string;
|
|
8
|
+
/** How to pass the prompt. Use {prompt} as placeholder. */
|
|
9
|
+
args: string[];
|
|
10
|
+
/** Environment variables to set for this CLI (merged with filtered env) */
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
/** Timeout in ms (default: 5 minutes) */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Predefined CLI configurations for known tools.
|
|
17
|
+
* Add new CLIs here — no other code changes needed.
|
|
18
|
+
*/
|
|
19
|
+
export declare const KNOWN_CLIS: Record<string, Omit<CliModelConfig, "name">>;
|
|
20
|
+
/**
|
|
21
|
+
* Generic CLI adapter — works with any AI CLI tool that accepts a prompt
|
|
22
|
+
* and writes output to stdout. Configure via CliModelConfig or use a
|
|
23
|
+
* predefined config from KNOWN_CLIS.
|
|
24
|
+
*/
|
|
25
|
+
export declare class CliAdapter {
|
|
26
|
+
private config;
|
|
27
|
+
private sandboxConfig;
|
|
28
|
+
constructor(config: CliModelConfig, sandboxConfig?: SandboxConfig);
|
|
29
|
+
execute(task: {
|
|
30
|
+
title: string;
|
|
31
|
+
description?: string | null;
|
|
32
|
+
taskType?: string | null;
|
|
33
|
+
context?: Record<string, unknown> | null;
|
|
34
|
+
processHint?: string | null;
|
|
35
|
+
humanContext?: string | null;
|
|
36
|
+
}): Promise<ExecutionResult>;
|
|
37
|
+
private taskId?;
|
|
38
|
+
setTaskId(taskId: string): void;
|
|
39
|
+
private runCli;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse CLI model definitions from an env var string.
|
|
43
|
+
* Format: "name:command:arg1,arg2;name2:command2:arg1,arg2"
|
|
44
|
+
* Example: "gemini:gemini:-p;aider:aider:--message,--yes-always,--no-git"
|
|
45
|
+
*
|
|
46
|
+
* If only a name is given (e.g., "gemini"), looks it up in KNOWN_CLIS.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseCliModels(envValue: string): CliModelConfig[];
|