@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,25 @@
|
|
|
1
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
2
|
+
import { AgentModelConfig, AgentRunContext } from "./AgentRunner.js";
|
|
3
|
+
export declare class OpencodeAgentRunner extends BaseAgentRunner {
|
|
4
|
+
readonly kind = "opencode";
|
|
5
|
+
private static chdirLock;
|
|
6
|
+
private formatter;
|
|
7
|
+
private client;
|
|
8
|
+
private abortController;
|
|
9
|
+
private close;
|
|
10
|
+
private model;
|
|
11
|
+
constructor(modelConfig?: AgentModelConfig);
|
|
12
|
+
private static readonly CHDIR_TIMEOUT_MS;
|
|
13
|
+
initBullshit({ runId, cwd }: {
|
|
14
|
+
runId: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
private shutdown;
|
|
18
|
+
init({ runId }: {
|
|
19
|
+
runId: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
protected runInternal(ctx: AgentRunContext, emit: (msg: string) => Promise<void>, setSession: (id: string) => Promise<void>, onError?: (error: {
|
|
22
|
+
message: string;
|
|
23
|
+
rawMessage?: any;
|
|
24
|
+
}) => void | Promise<void>): Promise<string>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// ClaudeAgentRunner.ts
|
|
2
|
+
import { BaseAgentRunner } from "./BaseAgentRunner.js";
|
|
3
|
+
import { createOpencode } from "@opencode-ai/sdk";
|
|
4
|
+
import { OpencodeAsyncMessageFormatter } from "../formatters/OpencodeMessageFormatter.js";
|
|
5
|
+
import { ACCESS_TOKEN, BASE_URL, RUN_ID_HEADER } from "../helpers/config.js";
|
|
6
|
+
export class OpencodeAgentRunner extends BaseAgentRunner {
|
|
7
|
+
kind = 'opencode';
|
|
8
|
+
// Mutex for process.chdir: serializes all instances so only one
|
|
9
|
+
// changes the working directory at a time.
|
|
10
|
+
static chdirLock = Promise.resolve();
|
|
11
|
+
formatter = new OpencodeAsyncMessageFormatter();
|
|
12
|
+
client = null;
|
|
13
|
+
abortController = new AbortController();
|
|
14
|
+
close = () => { };
|
|
15
|
+
model;
|
|
16
|
+
constructor(modelConfig = {}) {
|
|
17
|
+
super();
|
|
18
|
+
const hasCustomModel = Boolean(modelConfig.providerId && modelConfig.modelId);
|
|
19
|
+
this.model = {
|
|
20
|
+
providerId: hasCustomModel ? modelConfig.providerId : 'openai',
|
|
21
|
+
modelId: hasCustomModel ? modelConfig.modelId : 'gpt-5.2-codex',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
static CHDIR_TIMEOUT_MS = 60_000; // 1 min — if we wait longer, something is stuck
|
|
25
|
+
async initBullshit({ runId, cwd }) {
|
|
26
|
+
// Disgusting hack to start the server in the working directory
|
|
27
|
+
// because Opencode has a bug where running a session in a different
|
|
28
|
+
// folder breaks realtime events (???)
|
|
29
|
+
//
|
|
30
|
+
// We serialize via a promise chain so parallel runners don't stomp
|
|
31
|
+
// on each other's cwd. A timeout prevents waiting forever if a
|
|
32
|
+
// previous init hangs.
|
|
33
|
+
let release;
|
|
34
|
+
const acquired = new Promise(resolve => { release = resolve; });
|
|
35
|
+
const prev = OpencodeAgentRunner.chdirLock;
|
|
36
|
+
OpencodeAgentRunner.chdirLock = acquired;
|
|
37
|
+
// Wait for the previous holder, but not forever.
|
|
38
|
+
await Promise.race([
|
|
39
|
+
prev,
|
|
40
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`initBullshit: timed out after ${OpencodeAgentRunner.CHDIR_TIMEOUT_MS}ms waiting for chdir lock — a previous init is likely stuck`)), OpencodeAgentRunner.CHDIR_TIMEOUT_MS)),
|
|
41
|
+
]);
|
|
42
|
+
const originalCwd = process.cwd();
|
|
43
|
+
process.chdir(cwd);
|
|
44
|
+
try {
|
|
45
|
+
await this.init({ runId });
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
process.chdir(originalCwd);
|
|
49
|
+
release();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
shutdown() {
|
|
53
|
+
// Belt and suspenders: abort signal + close().
|
|
54
|
+
// close() alone is broken (https://github.com/anomalyco/opencode/issues/3841)
|
|
55
|
+
// but we keep it for when they fix it.
|
|
56
|
+
this.abortController.abort();
|
|
57
|
+
this.close();
|
|
58
|
+
}
|
|
59
|
+
async init({ runId }) {
|
|
60
|
+
console.log('Starting Opencode client');
|
|
61
|
+
let lastError;
|
|
62
|
+
const PORT_START = 4000;
|
|
63
|
+
const PORT_END = 4100;
|
|
64
|
+
for (let port = PORT_START; port < PORT_END; port++) {
|
|
65
|
+
try {
|
|
66
|
+
console.log(`Trying port ${port}...`);
|
|
67
|
+
this.abortController = new AbortController();
|
|
68
|
+
const opencode = await createOpencode({
|
|
69
|
+
port,
|
|
70
|
+
timeout: 20 * 3600,
|
|
71
|
+
signal: this.abortController.signal,
|
|
72
|
+
config: {
|
|
73
|
+
mcp: {
|
|
74
|
+
tasks: {
|
|
75
|
+
type: "remote",
|
|
76
|
+
url: `${BASE_URL}/api/v1/tasks/tasks/mcp`,
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${ACCESS_TOKEN}`,
|
|
79
|
+
[RUN_ID_HEADER]: runId,
|
|
80
|
+
},
|
|
81
|
+
enabled: true,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
this.client = opencode.client;
|
|
87
|
+
this.close = opencode.server.close;
|
|
88
|
+
console.log(`Opencode started on port ${port}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.warn(`Port ${port} failed, trying next...`);
|
|
93
|
+
lastError = err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Failed to start Opencode on ports ${PORT_START}–${PORT_END}: ${String(lastError)}`);
|
|
97
|
+
}
|
|
98
|
+
async runInternal(ctx, emit, setSession, onError) {
|
|
99
|
+
// Start client
|
|
100
|
+
await this.initBullshit({ runId: ctx.runId, cwd: ctx.cwd });
|
|
101
|
+
// await this.init({ runId: ctx.runId });
|
|
102
|
+
if (!this.client) {
|
|
103
|
+
throw new Error("Failed to create Opencode client");
|
|
104
|
+
}
|
|
105
|
+
// Create a session for this work
|
|
106
|
+
const { data: session } = await this.client.session.create({
|
|
107
|
+
body: {
|
|
108
|
+
title: `Session ${new Date().toLocaleString()}`,
|
|
109
|
+
},
|
|
110
|
+
query: {
|
|
111
|
+
directory: ctx.cwd,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
if (!session) {
|
|
115
|
+
this.shutdown();
|
|
116
|
+
throw new Error("Failed to create Opencode session");
|
|
117
|
+
}
|
|
118
|
+
console.log(`created session ${session.id} in ${session.directory}`);
|
|
119
|
+
// Session is created in the right dir ✅
|
|
120
|
+
const events = await this.client.event.subscribe(); // SSE stream
|
|
121
|
+
const prompt = {
|
|
122
|
+
type: 'text',
|
|
123
|
+
text: ctx.prompt,
|
|
124
|
+
};
|
|
125
|
+
let finalResult = '';
|
|
126
|
+
this.client.session.promptAsync({
|
|
127
|
+
path: {
|
|
128
|
+
id: session.id,
|
|
129
|
+
},
|
|
130
|
+
// NOTE: adding a directory breaks realtime events 🤷🏻♂️: https://github.com/anomalyco/opencode/issues/11522
|
|
131
|
+
// NOTE: Good news is we don't need it because the session carries the dir.
|
|
132
|
+
// NOTE: Acutally we need to. I've implemented a horrible hack to CD before starting the server.
|
|
133
|
+
// query: {
|
|
134
|
+
// directory: ctx.cwd,
|
|
135
|
+
// },
|
|
136
|
+
body: {
|
|
137
|
+
model: {
|
|
138
|
+
providerID: this.model.providerId,
|
|
139
|
+
modelID: this.model.modelId,
|
|
140
|
+
},
|
|
141
|
+
parts: [prompt],
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
for await (const event of events.stream) {
|
|
146
|
+
// Detect end of session
|
|
147
|
+
if (event.type == 'session.idle') {
|
|
148
|
+
console.log('session.idle');
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
const message = this.formatter.format(event);
|
|
152
|
+
if (message) {
|
|
153
|
+
emit(message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(error);
|
|
159
|
+
}
|
|
160
|
+
this.shutdown();
|
|
161
|
+
return finalResult;
|
|
162
|
+
}
|
|
163
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@taico/worker",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"worker": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"start": "tsx src/index.ts",
|
|
26
|
+
"start:claude": "dotenv -e .env.claude npm run start",
|
|
27
|
+
"start:codex": "dotenv -e .env.codex npm run start",
|
|
28
|
+
"start:reviewer": "dotenv -e .env.reviewer npm run start",
|
|
29
|
+
"start:qwen": "dotenv -e .env.qwen npm run start",
|
|
30
|
+
"start:gemini": "dotenv -e .env.gemini npm run start",
|
|
31
|
+
"start:all": "concurrently -n claude,codex,reviewer,qwen,gemini -c auto \"npm run start:claude\" \"npm run start:codex\" \"npm run start:reviewer\" \"npm run start:qwen\" \"npm run start:gemini\"",
|
|
32
|
+
"dev": "dotenv -e .env.gemini tsx src/dev.ts",
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"pack": "npm run build && npm pack",
|
|
35
|
+
"release": "npm run build && npm publish --access public"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24.0.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@anthropic-ai/claude-agent-sdk": "^0.1.76",
|
|
42
|
+
"@github/copilot-sdk": "0.1.22",
|
|
43
|
+
"@google/adk": "^0.2.4",
|
|
44
|
+
"@opencode-ai/sdk": "^1.1.28",
|
|
45
|
+
"@taico/client": "^0.0.1",
|
|
46
|
+
"@taico/events": "^0.0.2",
|
|
47
|
+
"socket.io-client": "^4.8.3"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.10.7",
|
|
51
|
+
"dotenv-cli": "^11.0.0",
|
|
52
|
+
"tsx": "4.21.0",
|
|
53
|
+
"typescript": "^5.7.3"
|
|
54
|
+
}
|
|
55
|
+
}
|