@taico/worker 0.0.1
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 +63 -0
- package/dist/Coordinator.d.ts +10 -0
- package/dist/Coordinator.js +183 -0
- package/dist/SocketIOTasksTransport.d.ts +68 -0
- package/dist/SocketIOTasksTransport.js +183 -0
- package/dist/Taico.d.ts +10 -0
- package/dist/Taico.js +69 -0
- package/dist/dev.d.ts +1 -0
- package/dist/dev.js +29 -0
- package/dist/formatters/ADKMessageFormatter.d.ts +4 -0
- package/dist/formatters/ADKMessageFormatter.js +32 -0
- package/dist/formatters/ClaudeMessageFormatter.d.ts +11 -0
- package/dist/formatters/ClaudeMessageFormatter.js +97 -0
- package/dist/formatters/OpencodeMessageFormatter.d.ts +8 -0
- package/dist/formatters/OpencodeMessageFormatter.js +56 -0
- package/dist/helpers/config.d.ts +5 -0
- package/dist/helpers/config.js +21 -0
- package/dist/helpers/ensureRepo.d.ts +1 -0
- package/dist/helpers/ensureRepo.js +27 -0
- package/dist/helpers/prepareWorkspace.d.ts +1 -0
- package/dist/helpers/prepareWorkspace.js +23 -0
- package/dist/helpers/sessionStore.d.ts +2 -0
- package/dist/helpers/sessionStore.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/interfaces/AgentRunResult.d.ts +5 -0
- package/dist/interfaces/AgentRunResult.js +2 -0
- package/dist/runners/ADKAgentRunner.d.ts +13 -0
- package/dist/runners/ADKAgentRunner.js +65 -0
- package/dist/runners/AgentRunner.d.ts +43 -0
- package/dist/runners/AgentRunner.js +1 -0
- package/dist/runners/BaseAgentRunner.d.ts +13 -0
- package/dist/runners/BaseAgentRunner.js +27 -0
- package/dist/runners/ClaudeAgentRunner.d.ts +11 -0
- package/dist/runners/ClaudeAgentRunner.js +68 -0
- package/dist/runners/GitHubCopilotAgentRunner.d.ts +12 -0
- package/dist/runners/GitHubCopilotAgentRunner.js +81 -0
- package/dist/runners/OpenCodeAgentRunner.d.ts +25 -0
- package/dist/runners/OpenCodeAgentRunner.js +163 -0
- package/package.json +55 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export class ClaudeMessageFormatter {
|
|
2
|
+
format(message) {
|
|
3
|
+
switch (message.type) {
|
|
4
|
+
case 'assistant':
|
|
5
|
+
return this.formatAssistant(message);
|
|
6
|
+
case 'user':
|
|
7
|
+
return this.formatUser(message);
|
|
8
|
+
case 'result':
|
|
9
|
+
return this.formatResult(message);
|
|
10
|
+
case 'system':
|
|
11
|
+
return this.formatSystem(message);
|
|
12
|
+
case 'stream_event':
|
|
13
|
+
return this.formatStreamEvent(message);
|
|
14
|
+
case 'tool_progress':
|
|
15
|
+
return this.formatToolProgress(message);
|
|
16
|
+
case 'auth_status':
|
|
17
|
+
return this.formatAuthStatus(message);
|
|
18
|
+
default:
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ---------------- private helpers ----------------
|
|
23
|
+
formatAssistant(message) {
|
|
24
|
+
const parts = [];
|
|
25
|
+
const content = message.message?.content;
|
|
26
|
+
if (!Array.isArray(content))
|
|
27
|
+
return null;
|
|
28
|
+
for (const c of content) {
|
|
29
|
+
if (c.type === 'tool_use') {
|
|
30
|
+
parts.push(`🔧 Tool call: ${c.name}`);
|
|
31
|
+
}
|
|
32
|
+
else if (c.type === 'text') {
|
|
33
|
+
parts.push(`💬 Assistant: ${c.text}`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
parts.push(`💬 Assistant (${c.type})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return parts.length ? parts.join('\n') : null;
|
|
40
|
+
}
|
|
41
|
+
formatUser(message) {
|
|
42
|
+
const parts = [];
|
|
43
|
+
const content = message.message?.content;
|
|
44
|
+
if (!Array.isArray(content))
|
|
45
|
+
return null;
|
|
46
|
+
for (const c of content) {
|
|
47
|
+
if (c.type === 'tool_result') {
|
|
48
|
+
// skip or log if you want
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
else if (c.type === 'text') {
|
|
52
|
+
parts.push(`👤 User: ${c.text}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
parts.push(`👤 User (${c.type})`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return parts.length ? parts.join('\n') : null;
|
|
59
|
+
}
|
|
60
|
+
formatResult(message) {
|
|
61
|
+
if (message.subtype === 'success' &&
|
|
62
|
+
typeof message.result === 'string') {
|
|
63
|
+
return [
|
|
64
|
+
`--- Agent turn complete ---`,
|
|
65
|
+
message.result,
|
|
66
|
+
`---------------------------`,
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
return `✅ Agent result received`;
|
|
70
|
+
}
|
|
71
|
+
formatSystem(message) {
|
|
72
|
+
if (message.subtype === 'init') {
|
|
73
|
+
return [
|
|
74
|
+
`🧠 Claude initialized`,
|
|
75
|
+
`- Permissions: ${message.permissionMode}`,
|
|
76
|
+
`- Tools: ${message.tools.length}`,
|
|
77
|
+
`- MCP Servers: ${message.mcp_servers.length}`,
|
|
78
|
+
`- Slash commands: ${message.slash_commands.length}`,
|
|
79
|
+
`- Skills: ${message.skills.length}`,
|
|
80
|
+
`- Plugins: ${message.plugins.length}`,
|
|
81
|
+
`- Agents: ${message.agents?.length || 0}`,
|
|
82
|
+
].join('\n');
|
|
83
|
+
}
|
|
84
|
+
return `⚙️ System message`;
|
|
85
|
+
}
|
|
86
|
+
formatStreamEvent(_) {
|
|
87
|
+
// usually too noisy — ignore by default
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
formatToolProgress(_) {
|
|
91
|
+
// ignore for MVP
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
formatAuthStatus(_) {
|
|
95
|
+
return `🔐 Auth status updated`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AssistantMessage, Part, Event } from "@opencode-ai/sdk";
|
|
2
|
+
export declare function opencodePartToText(part: Part): string;
|
|
3
|
+
export declare class OpencodeAsyncMessageFormatter {
|
|
4
|
+
format(event: Event): string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare class OpencodeSyncMessageFormatter {
|
|
7
|
+
format(info: AssistantMessage, parts: Array<Part>): Array<string>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function opencodePartToText(part) {
|
|
2
|
+
switch (part.type) {
|
|
3
|
+
case 'text':
|
|
4
|
+
return `💬 Assistant: ${part.text}`;
|
|
5
|
+
case 'subtask':
|
|
6
|
+
return `💬 Assistant: Creating subtask: ${part.description}`;
|
|
7
|
+
case 'reasoning':
|
|
8
|
+
return `💬 Assistant: Thinking...`;
|
|
9
|
+
case 'file':
|
|
10
|
+
return `📂 File ${part.filename || ''}`;
|
|
11
|
+
case 'tool':
|
|
12
|
+
return `🔧 Tool call: ${part.tool}`;
|
|
13
|
+
case 'step-start':
|
|
14
|
+
return `💬 Assistant: Starting step`;
|
|
15
|
+
case 'step-finish':
|
|
16
|
+
return `💬 Assistant: Finish step: ${part.reason}`;
|
|
17
|
+
case 'snapshot':
|
|
18
|
+
return `📸 Snapshot: ${part.snapshot}`;
|
|
19
|
+
case 'patch':
|
|
20
|
+
return `🔧 Patching files: ${part.files}`;
|
|
21
|
+
case 'agent':
|
|
22
|
+
return `🤖 Agent: ${part.name}`;
|
|
23
|
+
case 'retry':
|
|
24
|
+
return `🔄 Retrying error due to error '${part.error.name}'. Attempt ${part.attempt}...`;
|
|
25
|
+
case 'compaction':
|
|
26
|
+
return `🚜 Compacting`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class OpencodeAsyncMessageFormatter {
|
|
30
|
+
// Only listen to part update (this is where tool calls and messages happen)
|
|
31
|
+
format(event) {
|
|
32
|
+
if (event.type !== 'message.part.updated') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// Ignore deltas
|
|
36
|
+
if (event.properties.delta) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// Parse part into text
|
|
40
|
+
const part = event.properties.part;
|
|
41
|
+
const message = opencodePartToText(part);
|
|
42
|
+
return message;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class OpencodeSyncMessageFormatter {
|
|
46
|
+
format(info, parts) {
|
|
47
|
+
const messages = [];
|
|
48
|
+
// Parse info
|
|
49
|
+
if (info.error) {
|
|
50
|
+
messages.push(`Error ${info.error.name}: ${JSON.stringify(info.error.data)}`);
|
|
51
|
+
}
|
|
52
|
+
// Parse parts
|
|
53
|
+
const partMessages = parts.map(opencodePartToText);
|
|
54
|
+
return [...messages, ...partMessages];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const ACCESS_TOKEN = process.env.ACCESS_TOKEN || '';
|
|
2
|
+
export const BASE_URL = process.env.BASE_URL || '';
|
|
3
|
+
export const WORK_DIR = process.env.WORK_DIR || '';
|
|
4
|
+
export const AGENT_SLUG = process.env.AGENT_SLUG || '';
|
|
5
|
+
export const RUN_ID_HEADER = 'x-taico-run-id';
|
|
6
|
+
if (!ACCESS_TOKEN) {
|
|
7
|
+
console.error('env ACCESS_TOKEN not available');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
if (!BASE_URL) {
|
|
11
|
+
console.error('env BASE_URL not available');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
if (!WORK_DIR) {
|
|
15
|
+
console.error('env WORK_DIR not available');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
if (!AGENT_SLUG) {
|
|
19
|
+
console.error('env AGENT_SLUG not available');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureRepo(repo: string, dir: string): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// git.ts
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
function sh(cmd) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
exec(cmd, (err, stdout, stderr) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
reject(new Error(stderr || stdout || err.message));
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
resolve();
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function ensureRepo(repo, dir) {
|
|
18
|
+
const gitDir = join(dir, ".git");
|
|
19
|
+
if (!existsSync(gitDir)) {
|
|
20
|
+
console.log(`[git] cloning ${repo}`);
|
|
21
|
+
await sh(`git clone ${repo} ${dir}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(`[git] repo exists, fetching`);
|
|
25
|
+
await sh(`git -C ${dir} fetch --prune`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function prepareWorkspace(taskId: string, agentId: string, repoUrl?: string | null): Promise<string>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// workspace.ts
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { ensureRepo } from "./ensureRepo.js";
|
|
5
|
+
import { WORK_DIR } from "./config.js";
|
|
6
|
+
const BASE_DIR = WORK_DIR;
|
|
7
|
+
export async function prepareWorkspace(taskId, agentId, repoUrl) {
|
|
8
|
+
console.log(`prepping workspace for agent '${agentId}' to work on task '${taskId}'`);
|
|
9
|
+
let workDir = join(BASE_DIR, taskId, agentId);
|
|
10
|
+
// Ensure base directory exists
|
|
11
|
+
mkdirSync(workDir, { recursive: true });
|
|
12
|
+
// If no repoUrl provided, just return the base workDir
|
|
13
|
+
if (!repoUrl) {
|
|
14
|
+
console.log(`no repo url provided, using base workDir: ${workDir}`);
|
|
15
|
+
return workDir;
|
|
16
|
+
}
|
|
17
|
+
// If repoUrl provided, update workDir to point to the repo path
|
|
18
|
+
workDir = join(workDir, "repo");
|
|
19
|
+
console.log(`using repo: ${repoUrl}`);
|
|
20
|
+
// Ensure repo exists at the workDir location
|
|
21
|
+
await ensureRepo(repoUrl, workDir);
|
|
22
|
+
return workDir;
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const DB_PATH = join(new URL(".", import.meta.url).pathname, "sessions.json");
|
|
4
|
+
// const DB_PATH = "sessions.json";
|
|
5
|
+
function loadDB() {
|
|
6
|
+
if (!existsSync(DB_PATH))
|
|
7
|
+
return [];
|
|
8
|
+
const raw = readFileSync(DB_PATH, "utf-8").trim();
|
|
9
|
+
if (!raw)
|
|
10
|
+
return []; // empty file -> empty db
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// If it's corrupted/partial, don't crash the whole app.
|
|
17
|
+
// Optional self-heal:
|
|
18
|
+
// writeFileSync(DB_PATH, "[]\n");
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function writeDB(db) {
|
|
23
|
+
writeFileSync(DB_PATH, JSON.stringify(db, null, 2));
|
|
24
|
+
}
|
|
25
|
+
export function getSession(agentId, taskId) {
|
|
26
|
+
const db = loadDB();
|
|
27
|
+
const entry = db.find((e) => e.agentId === agentId && e.taskId === taskId);
|
|
28
|
+
return entry?.sessionId ?? null;
|
|
29
|
+
}
|
|
30
|
+
export function setSession(agentId, taskId, sessionId) {
|
|
31
|
+
const db = loadDB();
|
|
32
|
+
db.push({ agentId, taskId, sessionId });
|
|
33
|
+
writeDB(db);
|
|
34
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
2
|
+
import { AgentModelConfig, AgentRunContext } from "./AgentRunner.js";
|
|
3
|
+
export declare class ADKAgentRunner extends BaseAgentRunner {
|
|
4
|
+
readonly kind = "adk";
|
|
5
|
+
private formatter;
|
|
6
|
+
private modelId;
|
|
7
|
+
private sessionService;
|
|
8
|
+
constructor(modelConfig?: AgentModelConfig);
|
|
9
|
+
protected runInternal(ctx: AgentRunContext, emit: (msg: string) => Promise<void>, setSession: (id: string) => Promise<void>, onError?: (error: {
|
|
10
|
+
message: string;
|
|
11
|
+
rawMessage?: any;
|
|
12
|
+
}) => void | Promise<void>): Promise<string>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ADKAgentRunner.ts
|
|
2
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
3
|
+
import { LlmAgent, Runner, InMemorySessionService, MCPToolset } from "@google/adk";
|
|
4
|
+
import { ADKMessageFormatter } from "../formatters/ADKMessageFormatter.js";
|
|
5
|
+
import { ACCESS_TOKEN, BASE_URL, RUN_ID_HEADER } from "../helpers/config.js";
|
|
6
|
+
export class ADKAgentRunner extends BaseAgentRunner {
|
|
7
|
+
kind = 'adk';
|
|
8
|
+
formatter = new ADKMessageFormatter();
|
|
9
|
+
modelId;
|
|
10
|
+
sessionService = new InMemorySessionService();
|
|
11
|
+
constructor(modelConfig = {}) {
|
|
12
|
+
super();
|
|
13
|
+
this.modelId = modelConfig.modelId ?? 'gemini-2.5-flash';
|
|
14
|
+
}
|
|
15
|
+
async runInternal(ctx, emit, setSession, onError) {
|
|
16
|
+
let finalResult = '';
|
|
17
|
+
// Init a session
|
|
18
|
+
const session = await this.sessionService.createSession({
|
|
19
|
+
appName: 'app-123',
|
|
20
|
+
sessionId: 'session-123',
|
|
21
|
+
userId: 'user-123',
|
|
22
|
+
});
|
|
23
|
+
const agent = new LlmAgent({
|
|
24
|
+
name: 'agent',
|
|
25
|
+
model: this.modelId,
|
|
26
|
+
description: '',
|
|
27
|
+
instruction: '',
|
|
28
|
+
tools: [
|
|
29
|
+
new MCPToolset({
|
|
30
|
+
type: 'StreamableHTTPConnectionParams',
|
|
31
|
+
url: `${BASE_URL}/api/v1/tasks/tasks/mcp`,
|
|
32
|
+
header: {
|
|
33
|
+
Authorization: `Bearer ${ACCESS_TOKEN}`,
|
|
34
|
+
[RUN_ID_HEADER]: ctx.runId,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
const runner = new Runner({
|
|
40
|
+
appName: 'app-123',
|
|
41
|
+
agent: agent,
|
|
42
|
+
sessionService: this.sessionService,
|
|
43
|
+
});
|
|
44
|
+
const stream = runner.runAsync({
|
|
45
|
+
userId: session.userId,
|
|
46
|
+
sessionId: session.id,
|
|
47
|
+
newMessage: {
|
|
48
|
+
parts: [
|
|
49
|
+
{
|
|
50
|
+
text: ctx.prompt,
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
role: 'user',
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
for await (const msg of stream) {
|
|
57
|
+
// map → string
|
|
58
|
+
const messages = this.formatter.format(msg);
|
|
59
|
+
messages.forEach(async (message) => {
|
|
60
|
+
await emit(message);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return finalResult;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type Model = {
|
|
2
|
+
providerId: string;
|
|
3
|
+
modelId: string;
|
|
4
|
+
};
|
|
5
|
+
export type AgentModelConfig = {
|
|
6
|
+
providerId?: string | null;
|
|
7
|
+
modelId?: string | null;
|
|
8
|
+
};
|
|
9
|
+
export type AgentRunContext = {
|
|
10
|
+
/** Task / job identity */
|
|
11
|
+
taskId: string;
|
|
12
|
+
/** Initial input */
|
|
13
|
+
prompt: string;
|
|
14
|
+
/** Working directory or sandbox */
|
|
15
|
+
cwd: string;
|
|
16
|
+
/** Resume a previous run if supported */
|
|
17
|
+
resume?: string;
|
|
18
|
+
/** Arbitrary agent-specific config */
|
|
19
|
+
options?: Record<string, any>;
|
|
20
|
+
/** For backend traceability */
|
|
21
|
+
runId: string;
|
|
22
|
+
model?: Model;
|
|
23
|
+
};
|
|
24
|
+
export type AgentRunCallbacks = {
|
|
25
|
+
/** Called once when a session/run ID is known */
|
|
26
|
+
onSession?: (sessionId: string) => void | Promise<void>;
|
|
27
|
+
/** Called for every human-readable event */
|
|
28
|
+
onEvent?: (message: string) => void | Promise<void>;
|
|
29
|
+
/** Called when an error occurs (e.g., quota limit, API errors) */
|
|
30
|
+
onError?: (error: {
|
|
31
|
+
message: string;
|
|
32
|
+
rawMessage?: any;
|
|
33
|
+
}) => void | Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
export type AgentRunResult = {
|
|
36
|
+
sessionId: string | null;
|
|
37
|
+
events: string[];
|
|
38
|
+
result: string;
|
|
39
|
+
};
|
|
40
|
+
export interface AgentRunner {
|
|
41
|
+
readonly kind: string;
|
|
42
|
+
run(ctx: AgentRunContext, cb?: AgentRunCallbacks): Promise<AgentRunResult>;
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AgentRunResult, AgentRunCallbacks, AgentRunContext, AgentRunner } from "./AgentRunner.js";
|
|
2
|
+
export declare abstract class BaseAgentRunner implements AgentRunner {
|
|
3
|
+
abstract readonly kind: string;
|
|
4
|
+
run(ctx: AgentRunContext, cb?: AgentRunCallbacks): Promise<AgentRunResult>;
|
|
5
|
+
/**
|
|
6
|
+
* Implemented per agent.
|
|
7
|
+
* Must return final result string.
|
|
8
|
+
*/
|
|
9
|
+
protected abstract runInternal(ctx: AgentRunContext, emit: (msg: string) => Promise<void>, setSession: (id: string) => Promise<void>, onError?: (error: {
|
|
10
|
+
message: string;
|
|
11
|
+
rawMessage?: any;
|
|
12
|
+
}) => void | Promise<void>): Promise<string>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class BaseAgentRunner {
|
|
2
|
+
async run(ctx, cb = {}) {
|
|
3
|
+
const events = [];
|
|
4
|
+
let sessionId = null;
|
|
5
|
+
let result = '';
|
|
6
|
+
const emit = async (msg) => {
|
|
7
|
+
events.push(msg);
|
|
8
|
+
if (cb.onEvent)
|
|
9
|
+
await cb.onEvent(msg);
|
|
10
|
+
};
|
|
11
|
+
const setSession = async (id) => {
|
|
12
|
+
if (sessionId == null) {
|
|
13
|
+
sessionId = id;
|
|
14
|
+
if (cb.onSession)
|
|
15
|
+
await cb.onSession(id);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const onError = cb.onError;
|
|
19
|
+
try {
|
|
20
|
+
result = await this.runInternal(ctx, emit, setSession, onError);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
await emit(`❌ Agent error: ${err?.message ?? String(err)}`);
|
|
24
|
+
}
|
|
25
|
+
return { sessionId, events, result };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
2
|
+
import { AgentModelConfig, AgentRunContext } from "./AgentRunner.js";
|
|
3
|
+
export declare class ClaudeAgentRunner extends BaseAgentRunner {
|
|
4
|
+
readonly kind = "claude";
|
|
5
|
+
private formatter;
|
|
6
|
+
constructor(_modelConfig?: AgentModelConfig);
|
|
7
|
+
protected runInternal(ctx: AgentRunContext, emit: (msg: string) => Promise<void>, setSession: (id: string) => Promise<void>, onError?: (error: {
|
|
8
|
+
message: string;
|
|
9
|
+
rawMessage?: any;
|
|
10
|
+
}) => void | Promise<void>): Promise<string>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ClaudeAgentRunner.ts
|
|
2
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
3
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
4
|
+
import { ClaudeMessageFormatter } from "../formatters/ClaudeMessageFormatter.js";
|
|
5
|
+
import { ACCESS_TOKEN, BASE_URL, RUN_ID_HEADER } from "../helpers/config.js";
|
|
6
|
+
export class ClaudeAgentRunner extends BaseAgentRunner {
|
|
7
|
+
kind = 'claude';
|
|
8
|
+
formatter = new ClaudeMessageFormatter();
|
|
9
|
+
constructor(_modelConfig = {}) {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
async runInternal(ctx, emit, setSession, onError) {
|
|
13
|
+
let finalResult = '';
|
|
14
|
+
const stream = query({
|
|
15
|
+
prompt: ctx.prompt,
|
|
16
|
+
options: {
|
|
17
|
+
cwd: ctx.cwd,
|
|
18
|
+
// resume: ctx.resume,
|
|
19
|
+
persistSession: true,
|
|
20
|
+
settingSources: ['user', 'project', 'local'],
|
|
21
|
+
...(ctx.options ?? {}),
|
|
22
|
+
mcpServers: {
|
|
23
|
+
tasks: {
|
|
24
|
+
type: "http",
|
|
25
|
+
url: `${BASE_URL}/api/v1/tasks/tasks/mcp`,
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${ACCESS_TOKEN}`,
|
|
28
|
+
[RUN_ID_HEADER]: ctx.runId,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
allowedTools: [
|
|
33
|
+
'mcp__tasks__*',
|
|
34
|
+
'SlashCommand',
|
|
35
|
+
'Bash',
|
|
36
|
+
'Read',
|
|
37
|
+
'Write',
|
|
38
|
+
'Edit',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
for await (const msg of stream) {
|
|
43
|
+
// session capture
|
|
44
|
+
if (msg?.type === 'system' &&
|
|
45
|
+
msg?.subtype === 'init' &&
|
|
46
|
+
typeof msg.session_id === 'string') {
|
|
47
|
+
await setSession(msg.session_id);
|
|
48
|
+
}
|
|
49
|
+
// map → string
|
|
50
|
+
const text = this.formatter.format(msg);
|
|
51
|
+
if (text)
|
|
52
|
+
await emit(text);
|
|
53
|
+
if (msg.type === 'result' && msg.subtype === 'success') {
|
|
54
|
+
// Check if this is an error result (e.g., quota limit)
|
|
55
|
+
if (msg.is_error === true) {
|
|
56
|
+
if (onError) {
|
|
57
|
+
await onError({
|
|
58
|
+
message: typeof msg.result === 'string' ? msg.result : 'Unknown error',
|
|
59
|
+
rawMessage: msg,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
finalResult = msg.result;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return finalResult;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
2
|
+
import { AgentModelConfig, AgentRunContext } from "./AgentRunner.js";
|
|
3
|
+
export declare class GitHubCopilotAgentRunner extends BaseAgentRunner {
|
|
4
|
+
readonly kind = "githubcopilot";
|
|
5
|
+
private client;
|
|
6
|
+
private model;
|
|
7
|
+
constructor(modelConfig?: AgentModelConfig);
|
|
8
|
+
protected runInternal(ctx: AgentRunContext, emit: (msg: string) => Promise<void>, setSession: (id: string) => Promise<void>, onError?: (error: {
|
|
9
|
+
message: string;
|
|
10
|
+
rawMessage?: any;
|
|
11
|
+
}) => void | Promise<void>): Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
2
|
+
import { ACCESS_TOKEN, BASE_URL, RUN_ID_HEADER } from "../helpers/config.js";
|
|
3
|
+
import { CopilotClient } from "@github/copilot-sdk";
|
|
4
|
+
export class GitHubCopilotAgentRunner extends BaseAgentRunner {
|
|
5
|
+
kind = 'githubcopilot';
|
|
6
|
+
client = null;
|
|
7
|
+
model;
|
|
8
|
+
constructor(modelConfig = {}) {
|
|
9
|
+
super();
|
|
10
|
+
this.model = modelConfig.modelId ?? 'gpt-5.2-codex';
|
|
11
|
+
}
|
|
12
|
+
async runInternal(ctx, emit, setSession, onError) {
|
|
13
|
+
return new Promise(async (resolve, reject) => {
|
|
14
|
+
try {
|
|
15
|
+
// Init client
|
|
16
|
+
this.client = new CopilotClient({
|
|
17
|
+
cwd: ctx.cwd,
|
|
18
|
+
});
|
|
19
|
+
await this.client.start();
|
|
20
|
+
const taskMcpServer = {
|
|
21
|
+
type: "http",
|
|
22
|
+
url: `${BASE_URL}/api/v1/tasks/tasks/mcp`,
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${ACCESS_TOKEN}`,
|
|
25
|
+
[RUN_ID_HEADER]: ctx.runId,
|
|
26
|
+
},
|
|
27
|
+
tools: ["*"],
|
|
28
|
+
};
|
|
29
|
+
// Create a session for this work
|
|
30
|
+
const session = await this.client.createSession({
|
|
31
|
+
model: this.model,
|
|
32
|
+
mcpServers: {
|
|
33
|
+
tasks: taskMcpServer,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
if (session?.sessionId) {
|
|
37
|
+
await setSession(session.sessionId);
|
|
38
|
+
}
|
|
39
|
+
console.log(`created session ${session.sessionId} in ${session.workspacePath}`);
|
|
40
|
+
let lastAssistantMessage = '';
|
|
41
|
+
// Subscribe to events
|
|
42
|
+
session.on('session.idle', async () => {
|
|
43
|
+
console.log('session.idle');
|
|
44
|
+
if (this.client) {
|
|
45
|
+
await this.client.stop();
|
|
46
|
+
}
|
|
47
|
+
resolve(lastAssistantMessage);
|
|
48
|
+
});
|
|
49
|
+
session.on('assistant.reasoning', (reasoning) => {
|
|
50
|
+
void emit(`💬 Thinking... ${reasoning.data.content}`);
|
|
51
|
+
});
|
|
52
|
+
session.on('assistant.message', (message) => {
|
|
53
|
+
lastAssistantMessage = message.data.content ?? '';
|
|
54
|
+
void emit(`💬 Assistant: ${message.data.content}`);
|
|
55
|
+
});
|
|
56
|
+
session.on('tool.execution_start', (toolCall) => {
|
|
57
|
+
void emit(`🔧 Tool call: ${toolCall.data.toolName}`);
|
|
58
|
+
});
|
|
59
|
+
session.on('tool.execution_complete', () => {
|
|
60
|
+
void emit(`🔧 Tool response`);
|
|
61
|
+
});
|
|
62
|
+
// Fire up the prompt
|
|
63
|
+
await session.send({
|
|
64
|
+
prompt: ctx.prompt,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (this.client) {
|
|
69
|
+
await this.client.stop();
|
|
70
|
+
}
|
|
71
|
+
if (onError) {
|
|
72
|
+
await onError({
|
|
73
|
+
message: err?.message ?? String(err),
|
|
74
|
+
rawMessage: err,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
reject(err);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|