@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.
Files changed (40) hide show
  1. package/README.md +63 -0
  2. package/dist/Coordinator.d.ts +10 -0
  3. package/dist/Coordinator.js +183 -0
  4. package/dist/SocketIOTasksTransport.d.ts +68 -0
  5. package/dist/SocketIOTasksTransport.js +183 -0
  6. package/dist/Taico.d.ts +10 -0
  7. package/dist/Taico.js +69 -0
  8. package/dist/dev.d.ts +1 -0
  9. package/dist/dev.js +29 -0
  10. package/dist/formatters/ADKMessageFormatter.d.ts +4 -0
  11. package/dist/formatters/ADKMessageFormatter.js +32 -0
  12. package/dist/formatters/ClaudeMessageFormatter.d.ts +11 -0
  13. package/dist/formatters/ClaudeMessageFormatter.js +97 -0
  14. package/dist/formatters/OpencodeMessageFormatter.d.ts +8 -0
  15. package/dist/formatters/OpencodeMessageFormatter.js +56 -0
  16. package/dist/helpers/config.d.ts +5 -0
  17. package/dist/helpers/config.js +21 -0
  18. package/dist/helpers/ensureRepo.d.ts +1 -0
  19. package/dist/helpers/ensureRepo.js +27 -0
  20. package/dist/helpers/prepareWorkspace.d.ts +1 -0
  21. package/dist/helpers/prepareWorkspace.js +23 -0
  22. package/dist/helpers/sessionStore.d.ts +2 -0
  23. package/dist/helpers/sessionStore.js +34 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +5 -0
  26. package/dist/interfaces/AgentRunResult.d.ts +5 -0
  27. package/dist/interfaces/AgentRunResult.js +2 -0
  28. package/dist/runners/ADKAgentRunner.d.ts +13 -0
  29. package/dist/runners/ADKAgentRunner.js +65 -0
  30. package/dist/runners/AgentRunner.d.ts +43 -0
  31. package/dist/runners/AgentRunner.js +1 -0
  32. package/dist/runners/BaseAgentRunner.d.ts +13 -0
  33. package/dist/runners/BaseAgentRunner.js +27 -0
  34. package/dist/runners/ClaudeAgentRunner.d.ts +11 -0
  35. package/dist/runners/ClaudeAgentRunner.js +68 -0
  36. package/dist/runners/GitHubCopilotAgentRunner.d.ts +12 -0
  37. package/dist/runners/GitHubCopilotAgentRunner.js +81 -0
  38. package/dist/runners/OpenCodeAgentRunner.d.ts +25 -0
  39. package/dist/runners/OpenCodeAgentRunner.js +163 -0
  40. 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,5 @@
1
+ export declare const ACCESS_TOKEN: string;
2
+ export declare const BASE_URL: string;
3
+ export declare const WORK_DIR: string;
4
+ export declare const AGENT_SLUG: string;
5
+ export declare const RUN_ID_HEADER = "x-taico-run-id";
@@ -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,2 @@
1
+ export declare function getSession(agentId: string, taskId: string): string | null;
2
+ export declare function setSession(agentId: string, taskId: string, sessionId: string): void;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { Coordinator } from "./Coordinator.js";
4
+ const coordinator = new Coordinator();
5
+ await coordinator.start();
@@ -0,0 +1,5 @@
1
+ export interface AgentRunResult {
2
+ sessionId: string | null;
3
+ events: string[];
4
+ result: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ ;
2
+ export {};
@@ -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
+ }