-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathopencode.ts
More file actions
127 lines (111 loc) · 3.53 KB
/
opencode.ts
File metadata and controls
127 lines (111 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import process from "node:process";
import detectPort from "detect-port";
import {
createOpencode,
type Config as OpencodeConfig,
} from "@opencode-ai/sdk";
import type { Agent } from "./index.js";
// Set OpenCode config before server starts to ensure timeout is applied
const opencodeConfig = {
permission: {
edit: "allow",
bash: "allow",
webfetch: "allow",
external_directory: "allow",
},
share: "auto",
provider: {
opencode: {
options: {
timeout: false, // disable timeout for OpenCode provider requests
},
},
},
} satisfies OpencodeConfig;
// CRITICAL: Set via environment variable BEFORE importing/creating anything
// The SDK reads this when spawning the server process
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify(opencodeConfig);
const opencode = await createOpencode({
port: await detectPort(4096),
timeout: 1_500_000, // 25 minutes timeout for server startup
config: opencodeConfig,
});
const sessionCache = new Map<string, string>();
function sessionKey(model: string, cwd: string): string {
return `${cwd}::${model}`;
}
const opencodeAgent: Agent.Definition = {
async run(model, prompt, options) {
options.logger.log(`opencode --model ${model} ${prompt}`);
const cacheKey = sessionKey(model, options.cwd);
options.logger.log(`Creating session...`);
let sessionID = sessionCache.get(cacheKey);
if (!sessionID) {
const { data: session } = await opencode.client.session.create({
query: { directory: options.cwd },
throwOnError: true,
});
sessionID = session.id;
sessionCache.set(cacheKey, sessionID);
}
options.logger.log(`Sharing session ${sessionID}...`);
try {
const { data, error } = await opencode.client.session.share({
path: { id: sessionID! },
query: { directory: options.cwd },
});
if (error) throw error;
const shareUrl = data.share?.url;
options.logger.log(`Share URL: ${shareUrl}`);
} catch (e) {
options.logger.error(
`Failed to enable sharing for session ${sessionID}:`,
e,
);
}
options.logger.log(`Prompting session ${sessionID}...`);
const [providerID, modelID] = model.split("/");
const actions: string[] = [];
const usage = {
input: 0,
output: 0,
cost: 0,
};
try {
const { data, error } = await opencode.client.session.prompt({
path: { id: sessionID! },
query: { directory: options.cwd },
body: {
model: {
providerID,
modelID,
},
parts: [{ type: "text", text: prompt }],
},
});
if (error) throw error;
options.logger.debug(`Data: ${JSON.stringify(data)}`);
const info = data.info;
if (info) actions.push(JSON.stringify(info));
usage.input = info?.tokens?.input ?? 0;
usage.output = info?.tokens?.output ?? 0;
usage.cost = info?.cost ?? 0;
options.logger.debug(`Usage: ${JSON.stringify(usage)}`);
if (!data.parts?.length)
throw new Error(
options.logger.format("Response did not include assistant parts."),
);
data.parts.forEach((part) => actions.push(JSON.stringify(part)));
options.logger.debug(`Actions: ${JSON.stringify(actions)}`);
} catch (error: any) {
sessionCache.delete(cacheKey);
options.logger.error("Error in opencode agent: ", error);
throw error;
}
return { actions, usage };
},
cleanup() {
opencode.server.close();
},
};
export default opencodeAgent;