@ai-sdk/harness-opencode 1.0.134 → 1.0.136
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/CHANGELOG.md +21 -0
- package/dist/bridge/host-tool-mcp.mjs +76 -93
- package/dist/bridge/host-tool-mcp.mjs.map +1 -1
- package/dist/bridge/index.mjs +2669 -2952
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +857 -855
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1568 -2003
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/bridge/host-tool-mcp.ts +2 -2
- package/src/index.ts +1 -0
- package/src/opencode-harness.ts +5 -4
package/dist/index.js
CHANGED
|
@@ -1,2064 +1,1629 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
HARNESS_V1_BUILTIN_TOOLS,
|
|
6
|
-
HarnessCapabilityUnsupportedError as HarnessCapabilityUnsupportedError2,
|
|
7
|
-
harnessV1DiagnosticFromBridgeFrame,
|
|
8
|
-
harnessStateDirectoryPath,
|
|
9
|
-
harnessSessionDataDirectoryPath
|
|
10
|
-
} from "@ai-sdk/harness";
|
|
11
|
-
import {
|
|
12
|
-
applyCredentialForwarding,
|
|
13
|
-
classifyDiskLog,
|
|
14
|
-
createSandboxCredentialEnvironment,
|
|
15
|
-
createBridgeToken,
|
|
16
|
-
experimental_createBridgeUserMessageSubmitter,
|
|
17
|
-
createBridgeErrorHandler,
|
|
18
|
-
createBridgeStartupError,
|
|
19
|
-
drainBridgeProcessStream,
|
|
20
|
-
forwardBridgeProcessStream,
|
|
21
|
-
getRestrictedSandboxSession,
|
|
22
|
-
markBridgeStarting,
|
|
23
|
-
resolveSandboxHomeDir,
|
|
24
|
-
SandboxChannel,
|
|
25
|
-
shellQuote,
|
|
26
|
-
warnCredentialBrokeringUnavailable,
|
|
27
|
-
waitForBridgeReady,
|
|
28
|
-
withBridgeToken,
|
|
29
|
-
writeSkills
|
|
30
|
-
} from "@ai-sdk/harness/utils";
|
|
31
|
-
import {
|
|
32
|
-
safeParseJSON as safeParseJSON2,
|
|
33
|
-
tool
|
|
34
|
-
} from "@ai-sdk/provider-utils";
|
|
1
|
+
import path, { dirname, join } from "node:path";
|
|
2
|
+
import { HARNESS_V1_BUILTIN_TOOLS, HarnessCapabilityUnsupportedError, commonTool, harnessSessionDataDirectoryPath, harnessStateDirectoryPath, harnessV1BridgeInboundCommandSchemas, harnessV1BridgeOutboundMessageSchema, harnessV1BridgeStartBaseSchema, harnessV1DiagnosticFromBridgeFrame } from "@ai-sdk/harness";
|
|
3
|
+
import { SandboxChannel, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createBridgeToken, createCredentialRequestTransformation, createReadBridgeAsset, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, forwardBridgeProcessStream, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isAccessTokenExpiringSoon, isHarnessAuthenticationEnvironment, isWindows, markBridgeStarting, parseJwtPayload, refreshOAuthAccessToken, resolveSandboxCredentialEnvironment, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, withBridgeToken, writeSkills } from "@ai-sdk/harness/utils";
|
|
4
|
+
import { isRecord, safeParseJSON, tool } from "@ai-sdk/provider-utils";
|
|
35
5
|
import { WebSocket } from "ws";
|
|
36
|
-
import { z
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
),
|
|
47
|
-
"index.mjs": new URL("./bridge/index.mjs", import.meta.url),
|
|
48
|
-
"host-tool-mcp.mjs": new URL("./bridge/host-tool-mcp.mjs", import.meta.url)
|
|
6
|
+
import { z } from "zod/v4";
|
|
7
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
//#region src/opencode-bootstrap.ts
|
|
10
|
+
const readBridgeAsset = createReadBridgeAsset({
|
|
11
|
+
"package.json": new URL("./bridge/package.json", import.meta.url),
|
|
12
|
+
"pnpm-lock.yaml": new URL("./bridge/pnpm-lock.yaml", import.meta.url),
|
|
13
|
+
"pnpm-workspace.yaml": new URL("./bridge/pnpm-workspace.yaml", import.meta.url),
|
|
14
|
+
"index.mjs": new URL("./bridge/index.mjs", import.meta.url),
|
|
15
|
+
"host-tool-mcp.mjs": new URL("./bridge/host-tool-mcp.mjs", import.meta.url)
|
|
49
16
|
});
|
|
50
|
-
|
|
51
|
-
|
|
17
|
+
const OPENCODE_BOOTSTRAP_DIR = ".harness-bootstrap/opencode";
|
|
18
|
+
let cachedBootstrap;
|
|
52
19
|
async function getOpenCodeBootstrap() {
|
|
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
|
-
|
|
20
|
+
if (cachedBootstrap != null) return cachedBootstrap;
|
|
21
|
+
const [pkg, lock, workspace, bridge, hostToolMcp] = await Promise.all([
|
|
22
|
+
readBridgeAsset("package.json"),
|
|
23
|
+
readBridgeAsset("pnpm-lock.yaml"),
|
|
24
|
+
readBridgeAsset("pnpm-workspace.yaml"),
|
|
25
|
+
readBridgeAsset("index.mjs"),
|
|
26
|
+
readBridgeAsset("host-tool-mcp.mjs")
|
|
27
|
+
]);
|
|
28
|
+
cachedBootstrap = {
|
|
29
|
+
harnessId: "opencode",
|
|
30
|
+
bootstrapDir: OPENCODE_BOOTSTRAP_DIR,
|
|
31
|
+
files: [
|
|
32
|
+
{
|
|
33
|
+
path: `${OPENCODE_BOOTSTRAP_DIR}/package.json`,
|
|
34
|
+
content: pkg
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: `${OPENCODE_BOOTSTRAP_DIR}/pnpm-lock.yaml`,
|
|
38
|
+
content: lock
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: `${OPENCODE_BOOTSTRAP_DIR}/pnpm-workspace.yaml`,
|
|
42
|
+
content: workspace
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
path: `${OPENCODE_BOOTSTRAP_DIR}/bridge.mjs`,
|
|
46
|
+
content: bridge
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: `${OPENCODE_BOOTSTRAP_DIR}/host-tool-mcp.mjs`,
|
|
50
|
+
content: hostToolMcp
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
commands: [{ command: "pnpm install --frozen-lockfile --store-dir .pnpm-store" }, { command: "./node_modules/.bin/opencode --version" }]
|
|
54
|
+
};
|
|
55
|
+
return cachedBootstrap;
|
|
87
56
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"GITHUB_COPILOT_TOKEN",
|
|
105
|
-
"POE_API_KEY",
|
|
106
|
-
"OPENCODE_API_KEY",
|
|
107
|
-
"GITLAB_TOKEN",
|
|
108
|
-
OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/opencode-auth.ts
|
|
59
|
+
const OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE = "AI_SDK_OPENCODE_NATIVE_ACCESS_TOKEN";
|
|
60
|
+
const OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES = [
|
|
61
|
+
"AI_GATEWAY_API_KEY",
|
|
62
|
+
"OPENAI_API_KEY",
|
|
63
|
+
"ANTHROPIC_API_KEY",
|
|
64
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
65
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
66
|
+
"XAI_API_KEY",
|
|
67
|
+
"GITHUB_TOKEN",
|
|
68
|
+
"GITHUB_COPILOT_TOKEN",
|
|
69
|
+
"POE_API_KEY",
|
|
70
|
+
"OPENCODE_API_KEY",
|
|
71
|
+
"GITLAB_TOKEN",
|
|
72
|
+
OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE
|
|
109
73
|
];
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
74
|
+
const NON_GOOGLE_DIRECT_CREDENTIAL_ENVIRONMENT_VARIABLES = [
|
|
75
|
+
"OPENAI_API_KEY",
|
|
76
|
+
"ANTHROPIC_API_KEY",
|
|
77
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
78
|
+
"XAI_API_KEY",
|
|
79
|
+
"GITHUB_TOKEN",
|
|
80
|
+
"GITHUB_COPILOT_TOKEN",
|
|
81
|
+
"POE_API_KEY",
|
|
82
|
+
"OPENCODE_API_KEY",
|
|
83
|
+
"GITLAB_TOKEN"
|
|
120
84
|
];
|
|
121
|
-
function createOpenCodeRequestTransformations({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
return transformations;
|
|
190
|
-
}
|
|
191
|
-
case "google":
|
|
192
|
-
return environment.GOOGLE_GENERATIVE_AI_API_KEY && sandboxEnvironment.GOOGLE_GENERATIVE_AI_API_KEY ? [
|
|
193
|
-
createCredentialRequestTransformation({
|
|
194
|
-
matchUrl: "https://generativelanguage.googleapis.com",
|
|
195
|
-
matchHeaders: {
|
|
196
|
-
"x-goog-api-key": sandboxEnvironment.GOOGLE_GENERATIVE_AI_API_KEY
|
|
197
|
-
},
|
|
198
|
-
transformHeaders: {
|
|
199
|
-
"x-goog-api-key": environment.GOOGLE_GENERATIVE_AI_API_KEY
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
] : [];
|
|
203
|
-
case "xai":
|
|
204
|
-
return createBearerTransformation({
|
|
205
|
-
environment,
|
|
206
|
-
sandboxEnvironment,
|
|
207
|
-
environmentVariableName: "XAI_API_KEY",
|
|
208
|
-
matchUrl: environment.XAI_BASE_URL ?? "https://api.x.ai/v1"
|
|
209
|
-
});
|
|
210
|
-
case "github-copilot":
|
|
211
|
-
return createBearerTransformation({
|
|
212
|
-
environment,
|
|
213
|
-
sandboxEnvironment,
|
|
214
|
-
environmentVariableName: environment.GITHUB_COPILOT_TOKEN != null ? "GITHUB_COPILOT_TOKEN" : "GITHUB_TOKEN",
|
|
215
|
-
matchUrl: "https://api.githubcopilot.com"
|
|
216
|
-
});
|
|
217
|
-
case "poe":
|
|
218
|
-
return createBearerTransformation({
|
|
219
|
-
environment,
|
|
220
|
-
sandboxEnvironment,
|
|
221
|
-
environmentVariableName: "POE_API_KEY",
|
|
222
|
-
matchUrl: "https://api.poe.com"
|
|
223
|
-
});
|
|
224
|
-
case "opencode-go":
|
|
225
|
-
return createBearerTransformation({
|
|
226
|
-
environment,
|
|
227
|
-
sandboxEnvironment,
|
|
228
|
-
environmentVariableName: "OPENCODE_API_KEY",
|
|
229
|
-
matchUrl: "https://opencode.ai/zen/go/v1"
|
|
230
|
-
});
|
|
231
|
-
case "gitlab":
|
|
232
|
-
return createBearerTransformation({
|
|
233
|
-
environment,
|
|
234
|
-
sandboxEnvironment,
|
|
235
|
-
environmentVariableName: "GITLAB_TOKEN",
|
|
236
|
-
matchUrl: environment.GITLAB_INSTANCE_URL ?? "https://gitlab.com"
|
|
237
|
-
});
|
|
238
|
-
}
|
|
85
|
+
function createOpenCodeRequestTransformations({ env: environment, sandboxEnv: sandboxEnvironment, auth: authenticationMode }) {
|
|
86
|
+
switch (authenticationMode) {
|
|
87
|
+
case "ai-gateway": return environment.AI_GATEWAY_API_KEY && sandboxEnvironment.AI_GATEWAY_API_KEY ? [createCredentialRequestTransformation({
|
|
88
|
+
matchUrl: environment.AI_GATEWAY_BASE_URL,
|
|
89
|
+
matchHeaders: { "x-api-key": sandboxEnvironment.AI_GATEWAY_API_KEY },
|
|
90
|
+
transformHeaders: { Authorization: `Bearer ${environment.AI_GATEWAY_API_KEY}` }
|
|
91
|
+
}), createCredentialRequestTransformation({
|
|
92
|
+
matchUrl: environment.AI_GATEWAY_BASE_URL,
|
|
93
|
+
matchHeaders: { Authorization: `Bearer ${sandboxEnvironment.AI_GATEWAY_API_KEY}` },
|
|
94
|
+
transformHeaders: { Authorization: `Bearer ${environment.AI_GATEWAY_API_KEY}` }
|
|
95
|
+
})] : [];
|
|
96
|
+
case "openai": return environment.OPENAI_API_KEY && sandboxEnvironment.OPENAI_API_KEY ? [createCredentialRequestTransformation({
|
|
97
|
+
matchUrl: environment.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
|
|
98
|
+
matchHeaders: { Authorization: `Bearer ${sandboxEnvironment.OPENAI_API_KEY}` },
|
|
99
|
+
transformHeaders: { Authorization: `Bearer ${environment.OPENAI_API_KEY}` }
|
|
100
|
+
})] : [];
|
|
101
|
+
case "anthropic": {
|
|
102
|
+
const matchUrl = environment.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
|
|
103
|
+
const transformations = [];
|
|
104
|
+
if (environment.ANTHROPIC_API_KEY != null && sandboxEnvironment.ANTHROPIC_API_KEY != null) transformations.push(createCredentialRequestTransformation({
|
|
105
|
+
matchUrl,
|
|
106
|
+
matchHeaders: { "x-api-key": sandboxEnvironment.ANTHROPIC_API_KEY },
|
|
107
|
+
transformHeaders: { "x-api-key": environment.ANTHROPIC_API_KEY }
|
|
108
|
+
}));
|
|
109
|
+
if (environment.ANTHROPIC_AUTH_TOKEN != null && sandboxEnvironment.ANTHROPIC_AUTH_TOKEN != null) transformations.push(createCredentialRequestTransformation({
|
|
110
|
+
matchUrl,
|
|
111
|
+
matchHeaders: { Authorization: `Bearer ${sandboxEnvironment.ANTHROPIC_AUTH_TOKEN}` },
|
|
112
|
+
transformHeaders: { Authorization: `Bearer ${environment.ANTHROPIC_AUTH_TOKEN}` }
|
|
113
|
+
}));
|
|
114
|
+
return transformations;
|
|
115
|
+
}
|
|
116
|
+
case "google": return environment.GOOGLE_GENERATIVE_AI_API_KEY && sandboxEnvironment.GOOGLE_GENERATIVE_AI_API_KEY ? [createCredentialRequestTransformation({
|
|
117
|
+
matchUrl: "https://generativelanguage.googleapis.com",
|
|
118
|
+
matchHeaders: { "x-goog-api-key": sandboxEnvironment.GOOGLE_GENERATIVE_AI_API_KEY },
|
|
119
|
+
transformHeaders: { "x-goog-api-key": environment.GOOGLE_GENERATIVE_AI_API_KEY }
|
|
120
|
+
})] : [];
|
|
121
|
+
case "xai": return createBearerTransformation({
|
|
122
|
+
environment,
|
|
123
|
+
sandboxEnvironment,
|
|
124
|
+
environmentVariableName: "XAI_API_KEY",
|
|
125
|
+
matchUrl: environment.XAI_BASE_URL ?? "https://api.x.ai/v1"
|
|
126
|
+
});
|
|
127
|
+
case "github-copilot": return createBearerTransformation({
|
|
128
|
+
environment,
|
|
129
|
+
sandboxEnvironment,
|
|
130
|
+
environmentVariableName: environment.GITHUB_COPILOT_TOKEN != null ? "GITHUB_COPILOT_TOKEN" : "GITHUB_TOKEN",
|
|
131
|
+
matchUrl: "https://api.githubcopilot.com"
|
|
132
|
+
});
|
|
133
|
+
case "poe": return createBearerTransformation({
|
|
134
|
+
environment,
|
|
135
|
+
sandboxEnvironment,
|
|
136
|
+
environmentVariableName: "POE_API_KEY",
|
|
137
|
+
matchUrl: "https://api.poe.com"
|
|
138
|
+
});
|
|
139
|
+
case "opencode-go": return createBearerTransformation({
|
|
140
|
+
environment,
|
|
141
|
+
sandboxEnvironment,
|
|
142
|
+
environmentVariableName: "OPENCODE_API_KEY",
|
|
143
|
+
matchUrl: "https://opencode.ai/zen/go/v1"
|
|
144
|
+
});
|
|
145
|
+
case "gitlab": return createBearerTransformation({
|
|
146
|
+
environment,
|
|
147
|
+
sandboxEnvironment,
|
|
148
|
+
environmentVariableName: "GITLAB_TOKEN",
|
|
149
|
+
matchUrl: environment.GITLAB_INSTANCE_URL ?? "https://gitlab.com"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
239
152
|
}
|
|
240
|
-
function resolveOpenCodeProvider({
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (model?.includes("/")) {
|
|
248
|
-
const [modelProvider] = model.split("/");
|
|
249
|
-
if (isDirectProvider(modelProvider)) {
|
|
250
|
-
return modelProvider;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return "anthropic";
|
|
153
|
+
function resolveOpenCodeProvider({ model, provider }) {
|
|
154
|
+
if (isDirectProvider(provider)) return provider;
|
|
155
|
+
if (model?.includes("/")) {
|
|
156
|
+
const [modelProvider] = model.split("/");
|
|
157
|
+
if (isDirectProvider(modelProvider)) return modelProvider;
|
|
158
|
+
}
|
|
159
|
+
return "anthropic";
|
|
254
160
|
}
|
|
255
|
-
function resolveOpenCodeEnv({
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({
|
|
276
|
-
env: authenticationEnvironment
|
|
277
|
-
});
|
|
278
|
-
if (auth === "ai-gateway" || gatewayAuthFromEnv.apiKey) {
|
|
279
|
-
return pickGateway({ gatewayAuthFromEnv });
|
|
280
|
-
}
|
|
281
|
-
return pickDirectProvider({
|
|
282
|
-
provider: selectedProvider,
|
|
283
|
-
processEnv: authenticationEnvironment
|
|
284
|
-
});
|
|
161
|
+
function resolveOpenCodeEnv({ auth, model, provider, processEnv = process.env }) {
|
|
162
|
+
const authenticationEnvironment = isHarnessAuthenticationEnvironment(auth) ? auth : processEnv;
|
|
163
|
+
const selectedProvider = resolveOpenCodeAuthenticationProvider({
|
|
164
|
+
auth,
|
|
165
|
+
model,
|
|
166
|
+
provider,
|
|
167
|
+
environment: authenticationEnvironment
|
|
168
|
+
});
|
|
169
|
+
if (selectedProvider === "openai" && auth === "openai" || selectedProvider === "anthropic" && auth === "anthropic") return pickDirectProvider({
|
|
170
|
+
provider: selectedProvider,
|
|
171
|
+
processEnv: authenticationEnvironment
|
|
172
|
+
});
|
|
173
|
+
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: authenticationEnvironment });
|
|
174
|
+
if (auth === "ai-gateway" || gatewayAuthFromEnv.apiKey) return pickGateway({ gatewayAuthFromEnv });
|
|
175
|
+
return pickDirectProvider({
|
|
176
|
+
provider: selectedProvider,
|
|
177
|
+
processEnv: authenticationEnvironment
|
|
178
|
+
});
|
|
285
179
|
}
|
|
286
|
-
function resolveOpenCodeAuthenticationMode({
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
environment: processEnv
|
|
305
|
-
});
|
|
306
|
-
if (selectedProvider === "openai" && auth === "openai") {
|
|
307
|
-
return "openai";
|
|
308
|
-
}
|
|
309
|
-
if (selectedProvider === "anthropic" && auth === "anthropic") {
|
|
310
|
-
return "anthropic";
|
|
311
|
-
}
|
|
312
|
-
if (auth === "ai-gateway") {
|
|
313
|
-
return "ai-gateway";
|
|
314
|
-
}
|
|
315
|
-
if (getAiGatewayAuthFromEnv({ env: processEnv }).apiKey) {
|
|
316
|
-
return "ai-gateway";
|
|
317
|
-
}
|
|
318
|
-
return selectedProvider;
|
|
180
|
+
function resolveOpenCodeAuthenticationMode({ auth, model, provider, processEnv = process.env }) {
|
|
181
|
+
if (isHarnessAuthenticationEnvironment(auth)) return getAiGatewayAuthFromEnv({ env: auth }).apiKey ? "ai-gateway" : resolveOpenCodeAuthenticationProvider({
|
|
182
|
+
auth,
|
|
183
|
+
model,
|
|
184
|
+
provider,
|
|
185
|
+
environment: auth
|
|
186
|
+
});
|
|
187
|
+
const selectedProvider = resolveOpenCodeAuthenticationProvider({
|
|
188
|
+
auth,
|
|
189
|
+
model,
|
|
190
|
+
provider,
|
|
191
|
+
environment: processEnv
|
|
192
|
+
});
|
|
193
|
+
if (selectedProvider === "openai" && auth === "openai") return "openai";
|
|
194
|
+
if (selectedProvider === "anthropic" && auth === "anthropic") return "anthropic";
|
|
195
|
+
if (auth === "ai-gateway") return "ai-gateway";
|
|
196
|
+
if (getAiGatewayAuthFromEnv({ env: processEnv }).apiKey) return "ai-gateway";
|
|
197
|
+
return selectedProvider;
|
|
319
198
|
}
|
|
320
|
-
function resolveOpenCodeAuthenticationProvider({
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (model == null && provider == null && (auth === "anthropic" || auth === "openai")) {
|
|
328
|
-
return auth;
|
|
329
|
-
}
|
|
330
|
-
return model == null && provider == null && (environment.GOOGLE_GENERATIVE_AI_API_KEY?.length ?? 0) > 0 && !NON_GOOGLE_DIRECT_CREDENTIAL_ENVIRONMENT_VARIABLES.some(
|
|
331
|
-
(name) => (environment[name]?.length ?? 0) > 0
|
|
332
|
-
) ? "google" : selectedProvider;
|
|
199
|
+
function resolveOpenCodeAuthenticationProvider({ auth, model, provider, environment }) {
|
|
200
|
+
const selectedProvider = resolveOpenCodeProvider({
|
|
201
|
+
model,
|
|
202
|
+
provider
|
|
203
|
+
});
|
|
204
|
+
if (model == null && provider == null && (auth === "anthropic" || auth === "openai")) return auth;
|
|
205
|
+
return model == null && provider == null && (environment.GOOGLE_GENERATIVE_AI_API_KEY?.length ?? 0) > 0 && !NON_GOOGLE_DIRECT_CREDENTIAL_ENVIRONMENT_VARIABLES.some((name) => (environment[name]?.length ?? 0) > 0) ? "google" : selectedProvider;
|
|
333
206
|
}
|
|
334
|
-
function pickOpenAI({
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (project) env.OPENAI_PROJECT = project;
|
|
346
|
-
return env;
|
|
207
|
+
function pickOpenAI({ processEnv }) {
|
|
208
|
+
const env = {};
|
|
209
|
+
const apiKey = processEnv.OPENAI_API_KEY;
|
|
210
|
+
if (apiKey) env.OPENAI_API_KEY = apiKey;
|
|
211
|
+
const baseUrl = processEnv.OPENAI_BASE_URL;
|
|
212
|
+
if (baseUrl) env.OPENAI_BASE_URL = baseUrl;
|
|
213
|
+
const organization = processEnv.OPENAI_ORGANIZATION;
|
|
214
|
+
if (organization) env.OPENAI_ORGANIZATION = organization;
|
|
215
|
+
const project = processEnv.OPENAI_PROJECT;
|
|
216
|
+
if (project) env.OPENAI_PROJECT = project;
|
|
217
|
+
return env;
|
|
347
218
|
}
|
|
348
|
-
function pickAnthropic({
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
|
|
358
|
-
return env;
|
|
219
|
+
function pickAnthropic({ processEnv }) {
|
|
220
|
+
const env = {};
|
|
221
|
+
const apiKey = processEnv.ANTHROPIC_API_KEY;
|
|
222
|
+
if (apiKey) env.ANTHROPIC_API_KEY = apiKey;
|
|
223
|
+
const authToken = processEnv.ANTHROPIC_AUTH_TOKEN;
|
|
224
|
+
if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken;
|
|
225
|
+
const baseUrl = processEnv.ANTHROPIC_BASE_URL;
|
|
226
|
+
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
|
|
227
|
+
return env;
|
|
359
228
|
}
|
|
360
|
-
function pickDirectProvider({
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (provider === "openai") return pickOpenAI({ processEnv });
|
|
365
|
-
if (provider === "anthropic") return pickAnthropic({ processEnv });
|
|
366
|
-
const names = provider === "google" ? ["GOOGLE_GENERATIVE_AI_API_KEY"] : provider === "xai" ? ["XAI_API_KEY", "XAI_BASE_URL"] : provider === "github-copilot" ? ["GITHUB_COPILOT_TOKEN", "GITHUB_TOKEN"] : provider === "poe" ? ["POE_API_KEY"] : provider === "opencode-go" ? ["OPENCODE_API_KEY"] : ["GITLAB_TOKEN", "GITLAB_INSTANCE_URL"];
|
|
367
|
-
return Object.fromEntries(
|
|
368
|
-
names.flatMap(
|
|
369
|
-
(name) => processEnv[name] == null ? [] : [[name, processEnv[name]]]
|
|
370
|
-
)
|
|
371
|
-
);
|
|
229
|
+
function pickDirectProvider({ provider, processEnv }) {
|
|
230
|
+
if (provider === "openai") return pickOpenAI({ processEnv });
|
|
231
|
+
if (provider === "anthropic") return pickAnthropic({ processEnv });
|
|
232
|
+
return Object.fromEntries((provider === "google" ? ["GOOGLE_GENERATIVE_AI_API_KEY"] : provider === "xai" ? ["XAI_API_KEY", "XAI_BASE_URL"] : provider === "github-copilot" ? ["GITHUB_COPILOT_TOKEN", "GITHUB_TOKEN"] : provider === "poe" ? ["POE_API_KEY"] : provider === "opencode-go" ? ["OPENCODE_API_KEY"] : ["GITLAB_TOKEN", "GITLAB_INSTANCE_URL"]).flatMap((name) => processEnv[name] == null ? [] : [[name, processEnv[name]]]));
|
|
372
233
|
}
|
|
373
|
-
function hasOpenCodeCredential({
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}) {
|
|
377
|
-
if (authenticationMode === "ai-gateway") {
|
|
378
|
-
return environment.AI_GATEWAY_API_KEY != null;
|
|
379
|
-
}
|
|
380
|
-
return Object.keys(environment).some(
|
|
381
|
-
(name) => name.endsWith("_API_KEY") || name.endsWith("_TOKEN") || name === "ANTHROPIC_AUTH_TOKEN"
|
|
382
|
-
);
|
|
234
|
+
function hasOpenCodeCredential({ environment, authenticationMode }) {
|
|
235
|
+
if (authenticationMode === "ai-gateway") return environment.AI_GATEWAY_API_KEY != null;
|
|
236
|
+
return Object.keys(environment).some((name) => name.endsWith("_API_KEY") || name.endsWith("_TOKEN") || name === "ANTHROPIC_AUTH_TOKEN");
|
|
383
237
|
}
|
|
384
|
-
function createBearerTransformation({
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return credential == null || sandboxCredential == null ? [] : [
|
|
393
|
-
createCredentialRequestTransformation({
|
|
394
|
-
matchUrl,
|
|
395
|
-
matchHeaders: {
|
|
396
|
-
Authorization: `Bearer ${sandboxCredential}`
|
|
397
|
-
},
|
|
398
|
-
transformHeaders: { Authorization: `Bearer ${credential}` }
|
|
399
|
-
})
|
|
400
|
-
];
|
|
238
|
+
function createBearerTransformation({ environment, sandboxEnvironment, environmentVariableName, matchUrl }) {
|
|
239
|
+
const credential = environment[environmentVariableName];
|
|
240
|
+
const sandboxCredential = sandboxEnvironment[environmentVariableName];
|
|
241
|
+
return credential == null || sandboxCredential == null ? [] : [createCredentialRequestTransformation({
|
|
242
|
+
matchUrl,
|
|
243
|
+
matchHeaders: { Authorization: `Bearer ${sandboxCredential}` },
|
|
244
|
+
transformHeaders: { Authorization: `Bearer ${credential}` }
|
|
245
|
+
})];
|
|
401
246
|
}
|
|
402
247
|
function isDirectProvider(value) {
|
|
403
|
-
|
|
248
|
+
return value === "anthropic" || value === "openai" || value === "google" || value === "xai" || value === "github-copilot" || value === "poe" || value === "opencode-go" || value === "gitlab";
|
|
404
249
|
}
|
|
405
|
-
function pickGateway({
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const baseUrl = toOpenCodeGatewayBaseUrl(gatewayAuthFromEnv.baseUrl);
|
|
412
|
-
env.AI_GATEWAY_BASE_URL = baseUrl;
|
|
413
|
-
return env;
|
|
250
|
+
function pickGateway({ gatewayAuthFromEnv }) {
|
|
251
|
+
const env = {};
|
|
252
|
+
const apiKey = gatewayAuthFromEnv.apiKey;
|
|
253
|
+
if (apiKey) env.AI_GATEWAY_API_KEY = apiKey;
|
|
254
|
+
env.AI_GATEWAY_BASE_URL = toOpenCodeGatewayBaseUrl(gatewayAuthFromEnv.baseUrl);
|
|
255
|
+
return env;
|
|
414
256
|
}
|
|
415
257
|
function toOpenCodeGatewayBaseUrl(baseUrl) {
|
|
416
|
-
|
|
417
|
-
|
|
258
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
259
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
418
260
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
},
|
|
558
|
-
"duo-workflow-haiku-4-5": {
|
|
559
|
-
providerId: "workflow",
|
|
560
|
-
modelId: "claude_haiku_4_5_20251001"
|
|
561
|
-
},
|
|
562
|
-
"duo-workflow-opus-4-6": {
|
|
563
|
-
providerId: "workflow",
|
|
564
|
-
modelId: "claude_opus_4_6_20260205"
|
|
565
|
-
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/opencode-subscription.ts
|
|
263
|
+
const OPENAI_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
264
|
+
const XAI_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
265
|
+
const GITLAB_CLIENT_ID = "1d89f9fdb23ee96d4e603201f6861dab6e143c5c3c00469a018a2d94bdc03d4e";
|
|
266
|
+
const GITLAB_AI_GATEWAY_URL = "https://cloud.gitlab.com";
|
|
267
|
+
const GITLAB_ANTHROPIC_PROVIDER_ID = "ai-sdk-gitlab-anthropic";
|
|
268
|
+
const GITLAB_OPENAI_CHAT_PROVIDER_ID = "ai-sdk-gitlab-openai-chat";
|
|
269
|
+
const GITLAB_OPENAI_RESPONSES_PROVIDER_ID = "ai-sdk-gitlab-openai-responses";
|
|
270
|
+
const OPEN_CODE_GITLAB_MODELS = {
|
|
271
|
+
"duo-chat-fable-5": {
|
|
272
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
273
|
+
modelId: "claude-fable-5"
|
|
274
|
+
},
|
|
275
|
+
"duo-chat-opus-5": {
|
|
276
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
277
|
+
modelId: "claude-opus-5"
|
|
278
|
+
},
|
|
279
|
+
"duo-chat-opus-4-8": {
|
|
280
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
281
|
+
modelId: "claude-opus-4-8"
|
|
282
|
+
},
|
|
283
|
+
"duo-chat-opus-4-7": {
|
|
284
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
285
|
+
modelId: "claude-opus-4-7"
|
|
286
|
+
},
|
|
287
|
+
"duo-chat-opus-4-6": {
|
|
288
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
289
|
+
modelId: "claude-opus-4-6"
|
|
290
|
+
},
|
|
291
|
+
"duo-chat-sonnet-5": {
|
|
292
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
293
|
+
modelId: "claude-sonnet-5"
|
|
294
|
+
},
|
|
295
|
+
"duo-chat-sonnet-4-6": {
|
|
296
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
297
|
+
modelId: "claude-sonnet-4-6"
|
|
298
|
+
},
|
|
299
|
+
"duo-chat-opus-4-5": {
|
|
300
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
301
|
+
modelId: "claude-opus-4-5-20251101"
|
|
302
|
+
},
|
|
303
|
+
"duo-chat-sonnet-4-5": {
|
|
304
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
305
|
+
modelId: "claude-sonnet-4-5-20250929"
|
|
306
|
+
},
|
|
307
|
+
"duo-chat-haiku-4-5": {
|
|
308
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
309
|
+
modelId: "claude-haiku-4-5-20251001"
|
|
310
|
+
},
|
|
311
|
+
"duo-chat-gpt-5-1": {
|
|
312
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
313
|
+
modelId: "gpt-5.1-2025-11-13"
|
|
314
|
+
},
|
|
315
|
+
"duo-chat-gpt-5-2": {
|
|
316
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
317
|
+
modelId: "gpt-5.2-2025-12-11"
|
|
318
|
+
},
|
|
319
|
+
"duo-chat-gpt-5-4": {
|
|
320
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
321
|
+
modelId: "gpt-5.4-2026-03-05"
|
|
322
|
+
},
|
|
323
|
+
"duo-chat-gpt-5-5": {
|
|
324
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
325
|
+
modelId: "gpt-5.5-2026-04-23"
|
|
326
|
+
},
|
|
327
|
+
"duo-chat-gpt-5-mini": {
|
|
328
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
329
|
+
modelId: "gpt-5-mini-2025-08-07"
|
|
330
|
+
},
|
|
331
|
+
"duo-chat-gpt-5-4-mini": {
|
|
332
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
333
|
+
modelId: "gpt-5.4-mini"
|
|
334
|
+
},
|
|
335
|
+
"duo-chat-gpt-5-4-nano": {
|
|
336
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
337
|
+
modelId: "gpt-5.4-nano"
|
|
338
|
+
},
|
|
339
|
+
"duo-chat-gpt-5-6-sol": {
|
|
340
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
341
|
+
modelId: "gpt-5.6-sol"
|
|
342
|
+
},
|
|
343
|
+
"duo-chat-gpt-5-6-terra": {
|
|
344
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
345
|
+
modelId: "gpt-5.6-terra"
|
|
346
|
+
},
|
|
347
|
+
"duo-chat-gpt-5-6-luna": {
|
|
348
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
349
|
+
modelId: "gpt-5.6-luna"
|
|
350
|
+
},
|
|
351
|
+
"duo-chat-gpt-5-codex": {
|
|
352
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
353
|
+
modelId: "gpt-5-codex"
|
|
354
|
+
},
|
|
355
|
+
"duo-chat-gpt-5-2-codex": {
|
|
356
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
357
|
+
modelId: "gpt-5.2-codex"
|
|
358
|
+
},
|
|
359
|
+
"duo-chat-gpt-5-3-codex": {
|
|
360
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
361
|
+
modelId: "gpt-5.3-codex"
|
|
362
|
+
},
|
|
363
|
+
"duo-workflow": {
|
|
364
|
+
providerId: "workflow",
|
|
365
|
+
modelId: "default"
|
|
366
|
+
},
|
|
367
|
+
"duo-workflow-default": {
|
|
368
|
+
providerId: "workflow",
|
|
369
|
+
modelId: "default"
|
|
370
|
+
},
|
|
371
|
+
"duo-workflow-sonnet-4-5": {
|
|
372
|
+
providerId: "workflow",
|
|
373
|
+
modelId: "anthropic/claude-sonnet-4-5-20250929"
|
|
374
|
+
},
|
|
375
|
+
"duo-workflow-sonnet-5": {
|
|
376
|
+
providerId: "workflow",
|
|
377
|
+
modelId: "claude_sonnet_5"
|
|
378
|
+
},
|
|
379
|
+
"duo-workflow-opus-5": {
|
|
380
|
+
providerId: "workflow",
|
|
381
|
+
modelId: "claude_opus_5"
|
|
382
|
+
},
|
|
383
|
+
"duo-workflow-sonnet-4-6": {
|
|
384
|
+
providerId: "workflow",
|
|
385
|
+
modelId: "claude_sonnet_4_6"
|
|
386
|
+
},
|
|
387
|
+
"duo-workflow-opus-4-5": {
|
|
388
|
+
providerId: "workflow",
|
|
389
|
+
modelId: "anthropic/claude-opus-4-5-20251101"
|
|
390
|
+
},
|
|
391
|
+
"duo-workflow-haiku-4-5": {
|
|
392
|
+
providerId: "workflow",
|
|
393
|
+
modelId: "claude_haiku_4_5_20251001"
|
|
394
|
+
},
|
|
395
|
+
"duo-workflow-opus-4-6": {
|
|
396
|
+
providerId: "workflow",
|
|
397
|
+
modelId: "claude_opus_4_6_20260205"
|
|
398
|
+
}
|
|
566
399
|
};
|
|
567
|
-
async function resolveOpenCodeAuthentication({
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
400
|
+
async function resolveOpenCodeAuthentication({ auth, model, provider, processEnv = process.env, readSubscription = readOpenCodeSubscription }) {
|
|
401
|
+
const environment = resolveOpenCodeEnv({
|
|
402
|
+
auth,
|
|
403
|
+
model,
|
|
404
|
+
provider,
|
|
405
|
+
processEnv
|
|
406
|
+
});
|
|
407
|
+
const authenticationMode = resolveOpenCodeAuthenticationMode({
|
|
408
|
+
auth,
|
|
409
|
+
model,
|
|
410
|
+
provider,
|
|
411
|
+
processEnv
|
|
412
|
+
});
|
|
413
|
+
if (isHarnessAuthenticationEnvironment(auth) || auth === "ai-gateway" || authenticationMode === "ai-gateway" || hasOpenCodeCredential({
|
|
414
|
+
environment,
|
|
415
|
+
authenticationMode
|
|
416
|
+
})) return {
|
|
417
|
+
environment,
|
|
418
|
+
authenticationMode
|
|
419
|
+
};
|
|
420
|
+
const subscription = await readSubscription({
|
|
421
|
+
providerId: authenticationMode,
|
|
422
|
+
env: processEnv
|
|
423
|
+
});
|
|
424
|
+
return subscription == null ? {
|
|
425
|
+
environment,
|
|
426
|
+
authenticationMode
|
|
427
|
+
} : {
|
|
428
|
+
environment: {
|
|
429
|
+
...environment,
|
|
430
|
+
[OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE]: subscription.accessToken
|
|
431
|
+
},
|
|
432
|
+
authenticationMode,
|
|
433
|
+
subscription
|
|
434
|
+
};
|
|
601
435
|
}
|
|
602
|
-
async function readOpenCodeSubscription({
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
await stored.write?.(stored.value);
|
|
665
|
-
}
|
|
666
|
-
return {
|
|
667
|
-
providerId,
|
|
668
|
-
accessToken,
|
|
669
|
-
...accountId == null ? {} : { accountId },
|
|
670
|
-
...typeof candidate.enterpriseUrl === "string" ? { enterpriseUrl: candidate.enterpriseUrl } : {}
|
|
671
|
-
};
|
|
436
|
+
async function readOpenCodeSubscription({ providerId, env = process.env, homeDirectory = homedir(), platform = process.platform, fetch }) {
|
|
437
|
+
if (!isSubscriptionProvider(providerId)) return void 0;
|
|
438
|
+
const stored = await readStore({
|
|
439
|
+
env,
|
|
440
|
+
homeDirectory,
|
|
441
|
+
platform
|
|
442
|
+
});
|
|
443
|
+
if (stored == null) return void 0;
|
|
444
|
+
const candidate = stored.value[providerId];
|
|
445
|
+
if (!isRecord(candidate)) return void 0;
|
|
446
|
+
if (candidate.type === "api") return typeof candidate.key === "string" && candidate.key.length > 0 ? {
|
|
447
|
+
providerId,
|
|
448
|
+
accessToken: candidate.key
|
|
449
|
+
} : void 0;
|
|
450
|
+
if (candidate.type !== "oauth") return void 0;
|
|
451
|
+
if (providerId === "opencode-go") return void 0;
|
|
452
|
+
const access = candidate.access;
|
|
453
|
+
const refresh = candidate.refresh;
|
|
454
|
+
const expires = candidate.expires;
|
|
455
|
+
if (typeof access !== "string" || typeof refresh !== "string" || typeof expires !== "number") return;
|
|
456
|
+
if (providerId === "github-copilot") return {
|
|
457
|
+
providerId,
|
|
458
|
+
accessToken: refresh,
|
|
459
|
+
...typeof candidate.enterpriseUrl === "string" ? { enterpriseUrl: candidate.enterpriseUrl } : {}
|
|
460
|
+
};
|
|
461
|
+
if (providerId === "poe") {
|
|
462
|
+
if (isAccessTokenExpiringSoon({ expiresAt: expires })) throw new Error("OpenCode Poe subscription API key is expiring soon. Run OpenCode login again.");
|
|
463
|
+
return {
|
|
464
|
+
providerId,
|
|
465
|
+
accessToken: access
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
let accessToken = access;
|
|
469
|
+
let accountId = typeof candidate.accountId === "string" ? candidate.accountId : providerId === "openai" ? await extractOpenAIAccountId(access) : void 0;
|
|
470
|
+
if (isAccessTokenExpiringSoon({ expiresAt: expires })) {
|
|
471
|
+
const refreshSettings = resolveRefreshSettings({
|
|
472
|
+
providerId,
|
|
473
|
+
candidate,
|
|
474
|
+
env
|
|
475
|
+
});
|
|
476
|
+
const refreshed = await refreshOAuthAccessToken({
|
|
477
|
+
...refreshSettings,
|
|
478
|
+
refreshToken: refresh,
|
|
479
|
+
...fetch == null ? {} : { fetch }
|
|
480
|
+
});
|
|
481
|
+
accessToken = refreshed.accessToken;
|
|
482
|
+
if (providerId === "openai") accountId = await extractOpenAIAccountId(refreshed.accessToken) ?? accountId;
|
|
483
|
+
stored.value[providerId] = {
|
|
484
|
+
...candidate,
|
|
485
|
+
access: refreshed.accessToken,
|
|
486
|
+
refresh: refreshed.refreshToken ?? refresh,
|
|
487
|
+
expires: refreshed.expiresAt,
|
|
488
|
+
...accountId == null ? {} : { accountId }
|
|
489
|
+
};
|
|
490
|
+
await stored.write?.(stored.value);
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
providerId,
|
|
494
|
+
accessToken,
|
|
495
|
+
...accountId == null ? {} : { accountId },
|
|
496
|
+
...typeof candidate.enterpriseUrl === "string" ? { enterpriseUrl: candidate.enterpriseUrl } : {}
|
|
497
|
+
};
|
|
672
498
|
}
|
|
673
|
-
async function requestOpenCodeGitLabDirectAccess({
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
throw new Error(
|
|
694
|
-
`OpenCode GitLab direct access request failed with status ${response.status}.`
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
const parsed = await safeParseJSON({ text: await response.text() });
|
|
698
|
-
if (!parsed.success || !isRecord(parsed.value)) {
|
|
699
|
-
throw new Error("OpenCode GitLab direct access returned invalid JSON.");
|
|
700
|
-
}
|
|
701
|
-
const token = parsed.value.token;
|
|
702
|
-
const headers = parsed.value.headers;
|
|
703
|
-
if (typeof token !== "string" || token.length === 0 || !isRecord(headers) || Object.values(headers).some((value) => typeof value !== "string")) {
|
|
704
|
-
throw new Error(
|
|
705
|
-
"OpenCode GitLab direct access returned invalid credentials."
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
accessToken: token,
|
|
710
|
-
headers,
|
|
711
|
-
aiGatewayUrl: aiGatewayUrl.replace(/\/+$/, "")
|
|
712
|
-
};
|
|
499
|
+
async function requestOpenCodeGitLabDirectAccess({ accessToken, instanceUrl = "https://gitlab.com", aiGatewayUrl = GITLAB_AI_GATEWAY_URL, fetch: fetchImplementation = globalThis.fetch }) {
|
|
500
|
+
const response = await fetchImplementation(`${instanceUrl.replace(/\/+$/, "")}/api/v4/ai/third_party_agents/direct_access`, {
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers: {
|
|
503
|
+
Authorization: `Bearer ${accessToken}`,
|
|
504
|
+
"Content-Type": "application/json"
|
|
505
|
+
},
|
|
506
|
+
body: JSON.stringify({ feature_flags: { DuoAgentPlatformNext: true } })
|
|
507
|
+
});
|
|
508
|
+
if (!response.ok) throw new Error(`OpenCode GitLab direct access request failed with status ${response.status}.`);
|
|
509
|
+
const parsed = await safeParseJSON({ text: await response.text() });
|
|
510
|
+
if (!parsed.success || !isRecord(parsed.value)) throw new Error("OpenCode GitLab direct access returned invalid JSON.");
|
|
511
|
+
const token = parsed.value.token;
|
|
512
|
+
const headers = parsed.value.headers;
|
|
513
|
+
if (typeof token !== "string" || token.length === 0 || !isRecord(headers) || Object.values(headers).some((value) => typeof value !== "string")) throw new Error("OpenCode GitLab direct access returned invalid credentials.");
|
|
514
|
+
return {
|
|
515
|
+
accessToken: token,
|
|
516
|
+
headers,
|
|
517
|
+
aiGatewayUrl: aiGatewayUrl.replace(/\/+$/, "")
|
|
518
|
+
};
|
|
713
519
|
}
|
|
714
|
-
function createOpenCodeGitLabSubscriptionRequestTransformations({
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
...headers,
|
|
725
|
-
Authorization: `Bearer ${directAccess.accessToken}`
|
|
726
|
-
};
|
|
727
|
-
return [
|
|
728
|
-
`${directAccess.aiGatewayUrl}/ai/v1/proxy/anthropic`,
|
|
729
|
-
`${directAccess.aiGatewayUrl}/ai/v1/proxy/openai/v1`
|
|
730
|
-
].map(
|
|
731
|
-
(matchUrl) => createCredentialRequestTransformation2({
|
|
732
|
-
matchUrl,
|
|
733
|
-
matchHeaders: {
|
|
734
|
-
Authorization: `Bearer ${sandboxAccessToken}`
|
|
735
|
-
},
|
|
736
|
-
transformHeaders
|
|
737
|
-
})
|
|
738
|
-
);
|
|
520
|
+
function createOpenCodeGitLabSubscriptionRequestTransformations({ directAccess, sandboxAccessToken }) {
|
|
521
|
+
const transformHeaders = {
|
|
522
|
+
...Object.fromEntries(Object.entries(directAccess.headers).filter(([name]) => name.toLowerCase() !== "authorization" && name.toLowerCase() !== "x-api-key")),
|
|
523
|
+
Authorization: `Bearer ${directAccess.accessToken}`
|
|
524
|
+
};
|
|
525
|
+
return [`${directAccess.aiGatewayUrl}/ai/v1/proxy/anthropic`, `${directAccess.aiGatewayUrl}/ai/v1/proxy/openai/v1`].map((matchUrl) => createCredentialRequestTransformation({
|
|
526
|
+
matchUrl,
|
|
527
|
+
matchHeaders: { Authorization: `Bearer ${sandboxAccessToken}` },
|
|
528
|
+
transformHeaders
|
|
529
|
+
}));
|
|
739
530
|
}
|
|
740
|
-
function createOpenCodeGitLabSubscriptionConfig({
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
531
|
+
function createOpenCodeGitLabSubscriptionConfig({ openCodeConfig, sandboxAccessToken, aiGatewayUrl, headers }) {
|
|
532
|
+
const providerOptions = headers == null ? {} : { headers };
|
|
533
|
+
const providers = {
|
|
534
|
+
[GITLAB_ANTHROPIC_PROVIDER_ID]: {
|
|
535
|
+
name: "GitLab Anthropic",
|
|
536
|
+
npm: "@ai-sdk/anthropic",
|
|
537
|
+
api: `${aiGatewayUrl}/ai/v1/proxy/anthropic/v1`,
|
|
538
|
+
options: {
|
|
539
|
+
authToken: sandboxAccessToken,
|
|
540
|
+
...providerOptions
|
|
541
|
+
},
|
|
542
|
+
models: createOpenCodeGitLabModels(GITLAB_ANTHROPIC_PROVIDER_ID)
|
|
543
|
+
},
|
|
544
|
+
[GITLAB_OPENAI_CHAT_PROVIDER_ID]: {
|
|
545
|
+
name: "GitLab OpenAI Chat",
|
|
546
|
+
npm: "@ai-sdk/openai-compatible",
|
|
547
|
+
api: `${aiGatewayUrl}/ai/v1/proxy/openai/v1`,
|
|
548
|
+
options: {
|
|
549
|
+
apiKey: sandboxAccessToken,
|
|
550
|
+
...providerOptions
|
|
551
|
+
},
|
|
552
|
+
models: createOpenCodeGitLabModels(GITLAB_OPENAI_CHAT_PROVIDER_ID)
|
|
553
|
+
},
|
|
554
|
+
[GITLAB_OPENAI_RESPONSES_PROVIDER_ID]: {
|
|
555
|
+
name: "GitLab OpenAI Responses",
|
|
556
|
+
npm: "@ai-sdk/openai",
|
|
557
|
+
api: `${aiGatewayUrl}/ai/v1/proxy/openai/v1`,
|
|
558
|
+
options: {
|
|
559
|
+
apiKey: sandboxAccessToken,
|
|
560
|
+
...providerOptions
|
|
561
|
+
},
|
|
562
|
+
models: createOpenCodeGitLabModels(GITLAB_OPENAI_RESPONSES_PROVIDER_ID)
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const configuredProviders = isRecord(openCodeConfig?.provider) ? openCodeConfig.provider : {};
|
|
566
|
+
return {
|
|
567
|
+
...openCodeConfig,
|
|
568
|
+
provider: {
|
|
569
|
+
...configuredProviders,
|
|
570
|
+
...providers
|
|
571
|
+
}
|
|
572
|
+
};
|
|
775
573
|
}
|
|
776
|
-
function resolveOpenCodeGitLabSubscriptionModel({
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
message: `Harness 'opencode' cannot broker native GitLab subscription credentials for unknown model '${modelId}'.`
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
if (mapping.providerId === "workflow") {
|
|
800
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
801
|
-
harnessId: "opencode",
|
|
802
|
-
message: `Harness 'opencode' cannot broker native GitLab subscription credentials for workflow model '${modelId}' because it authenticates over WebSocket. Use a sandbox without credential brokering to use the credential-forwarding fallback.`
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
return `${mapping.providerId}/${modelId}`;
|
|
574
|
+
function resolveOpenCodeGitLabSubscriptionModel({ model, provider }) {
|
|
575
|
+
if (model == null) {
|
|
576
|
+
if (provider === "gitlab") throw new HarnessCapabilityUnsupportedError({
|
|
577
|
+
harnessId: "opencode",
|
|
578
|
+
message: "Harness 'opencode' requires an explicit GitLab model when native GitLab subscription credentials are brokered."
|
|
579
|
+
});
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if ((model.includes("/") ? model.split("/")[0] : provider) !== "gitlab") return model;
|
|
583
|
+
const modelId = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
|
|
584
|
+
const mapping = OPEN_CODE_GITLAB_MODELS[modelId];
|
|
585
|
+
if (mapping == null) throw new HarnessCapabilityUnsupportedError({
|
|
586
|
+
harnessId: "opencode",
|
|
587
|
+
message: `Harness 'opencode' cannot broker native GitLab subscription credentials for unknown model '${modelId}'.`
|
|
588
|
+
});
|
|
589
|
+
if (mapping.providerId === "workflow") throw new HarnessCapabilityUnsupportedError({
|
|
590
|
+
harnessId: "opencode",
|
|
591
|
+
message: `Harness 'opencode' cannot broker native GitLab subscription credentials for workflow model '${modelId}' because it authenticates over WebSocket. Use a sandbox without credential brokering to use the credential-forwarding fallback.`
|
|
592
|
+
});
|
|
593
|
+
return `${mapping.providerId}/${modelId}`;
|
|
806
594
|
}
|
|
807
595
|
function createOpenCodeGitLabModels(providerId) {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
);
|
|
596
|
+
return Object.fromEntries(Object.entries(OPEN_CODE_GITLAB_MODELS).flatMap(([id, model]) => model.providerId === providerId ? [[id, {
|
|
597
|
+
id: model.modelId,
|
|
598
|
+
name: id
|
|
599
|
+
}]] : []));
|
|
813
600
|
}
|
|
814
|
-
function createOpenCodeSubscriptionAuthContent({
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
601
|
+
function createOpenCodeSubscriptionAuthContent({ authentication, accessToken }) {
|
|
602
|
+
const { providerId } = authentication;
|
|
603
|
+
const value = providerId === "opencode-go" ? {
|
|
604
|
+
type: "api",
|
|
605
|
+
key: accessToken
|
|
606
|
+
} : {
|
|
607
|
+
type: "oauth",
|
|
608
|
+
access: accessToken,
|
|
609
|
+
refresh: providerId === "github-copilot" ? accessToken : "host-managed",
|
|
610
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
611
|
+
...authentication.accountId == null ? {} : { accountId: authentication.accountId },
|
|
612
|
+
...authentication.enterpriseUrl == null ? {} : { enterpriseUrl: authentication.enterpriseUrl }
|
|
613
|
+
};
|
|
614
|
+
return JSON.stringify({ [providerId]: value });
|
|
828
615
|
}
|
|
829
|
-
function createOpenCodeSubscriptionRequestTransformations({
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
];
|
|
847
|
-
if (authentication.providerId === "poe" || authentication.providerId === "opencode-go") {
|
|
848
|
-
transformations.push(
|
|
849
|
-
createCredentialRequestTransformation2({
|
|
850
|
-
matchUrl,
|
|
851
|
-
matchHeaders: { "x-api-key": sandboxAccessToken },
|
|
852
|
-
transformHeaders: { "x-api-key": authentication.accessToken }
|
|
853
|
-
})
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
return transformations;
|
|
616
|
+
function createOpenCodeSubscriptionRequestTransformations({ authentication, sandboxAccessToken }) {
|
|
617
|
+
const matchUrl = resolveRequestUrl(authentication);
|
|
618
|
+
const transformHeaders = {
|
|
619
|
+
Authorization: `Bearer ${authentication.accessToken}`,
|
|
620
|
+
...authentication.providerId === "openai" && authentication.accountId ? { "ChatGPT-Account-Id": authentication.accountId } : {}
|
|
621
|
+
};
|
|
622
|
+
const transformations = [createCredentialRequestTransformation({
|
|
623
|
+
matchUrl,
|
|
624
|
+
matchHeaders: { Authorization: `Bearer ${sandboxAccessToken}` },
|
|
625
|
+
transformHeaders
|
|
626
|
+
})];
|
|
627
|
+
if (authentication.providerId === "poe" || authentication.providerId === "opencode-go") transformations.push(createCredentialRequestTransformation({
|
|
628
|
+
matchUrl,
|
|
629
|
+
matchHeaders: { "x-api-key": sandboxAccessToken },
|
|
630
|
+
transformHeaders: { "x-api-key": authentication.accessToken }
|
|
631
|
+
}));
|
|
632
|
+
return transformations;
|
|
857
633
|
}
|
|
858
|
-
function resolveRefreshSettings({
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
tokenUrl: "https://auth.x.ai/oauth2/token",
|
|
872
|
-
clientId: XAI_CLIENT_ID
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
const instanceUrl = (typeof candidate.enterpriseUrl === "string" ? candidate.enterpriseUrl : env.GITLAB_INSTANCE_URL) ?? "https://gitlab.com";
|
|
876
|
-
return {
|
|
877
|
-
tokenUrl: `${instanceUrl.replace(/\/+$/, "")}/oauth/token`,
|
|
878
|
-
clientId: env.GITLAB_OAUTH_CLIENT_ID ?? GITLAB_CLIENT_ID
|
|
879
|
-
};
|
|
634
|
+
function resolveRefreshSettings({ providerId, candidate, env }) {
|
|
635
|
+
if (providerId === "openai") return {
|
|
636
|
+
tokenUrl: "https://auth.openai.com/oauth/token",
|
|
637
|
+
clientId: OPENAI_CLIENT_ID
|
|
638
|
+
};
|
|
639
|
+
if (providerId === "xai") return {
|
|
640
|
+
tokenUrl: "https://auth.x.ai/oauth2/token",
|
|
641
|
+
clientId: XAI_CLIENT_ID
|
|
642
|
+
};
|
|
643
|
+
return {
|
|
644
|
+
tokenUrl: `${((typeof candidate.enterpriseUrl === "string" ? candidate.enterpriseUrl : env.GITLAB_INSTANCE_URL) ?? "https://gitlab.com").replace(/\/+$/, "")}/oauth/token`,
|
|
645
|
+
clientId: env.GITLAB_OAUTH_CLIENT_ID ?? GITLAB_CLIENT_ID
|
|
646
|
+
};
|
|
880
647
|
}
|
|
881
648
|
function resolveRequestUrl(authentication) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return "https://api.poe.com";
|
|
891
|
-
case "opencode-go":
|
|
892
|
-
return "https://opencode.ai/zen/go/v1";
|
|
893
|
-
case "gitlab":
|
|
894
|
-
return authentication.enterpriseUrl ?? "https://gitlab.com";
|
|
895
|
-
}
|
|
649
|
+
switch (authentication.providerId) {
|
|
650
|
+
case "openai": return "https://chatgpt.com/backend-api/codex";
|
|
651
|
+
case "xai": return "https://api.x.ai/v1";
|
|
652
|
+
case "github-copilot": return authentication.enterpriseUrl == null ? "https://api.githubcopilot.com" : `https://copilot-api.${normalizeDomain(authentication.enterpriseUrl)}`;
|
|
653
|
+
case "poe": return "https://api.poe.com";
|
|
654
|
+
case "opencode-go": return "https://opencode.ai/zen/go/v1";
|
|
655
|
+
case "gitlab": return authentication.enterpriseUrl ?? "https://gitlab.com";
|
|
656
|
+
}
|
|
896
657
|
}
|
|
897
658
|
function normalizeDomain(value) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
659
|
+
try {
|
|
660
|
+
return new URL(value.includes("://") ? value : `https://${value}`).host;
|
|
661
|
+
} catch {
|
|
662
|
+
return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
663
|
+
}
|
|
903
664
|
}
|
|
904
665
|
async function extractOpenAIAccountId(accessToken) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
return typeof accountId === "string" ? accountId : void 0;
|
|
666
|
+
const auth = (await parseJwtPayload({ token: accessToken }))?.["https://api.openai.com/auth"];
|
|
667
|
+
if (!isRecord(auth)) return void 0;
|
|
668
|
+
const accountId = auth.chatgpt_account_id;
|
|
669
|
+
return typeof accountId === "string" ? accountId : void 0;
|
|
910
670
|
}
|
|
911
|
-
async function readStore({
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
};
|
|
671
|
+
async function readStore({ env, homeDirectory, platform }) {
|
|
672
|
+
if (env.OPENCODE_AUTH_CONTENT != null) {
|
|
673
|
+
const parsed = await safeParseJSON({ text: env.OPENCODE_AUTH_CONTENT });
|
|
674
|
+
return parsed.success && isRecord(parsed.value) ? { value: parsed.value } : void 0;
|
|
675
|
+
}
|
|
676
|
+
const authPath = env.XDG_DATA_HOME != null ? join(env.XDG_DATA_HOME, "opencode", "auth.json") : isWindows(platform) ? join(homeDirectory, ".opencode", "auth.json") : join(homeDirectory, ".local", "share", "opencode", "auth.json");
|
|
677
|
+
const text = await readFile(authPath, "utf8").catch(() => void 0);
|
|
678
|
+
if (text == null) return void 0;
|
|
679
|
+
const parsed = await safeParseJSON({ text });
|
|
680
|
+
if (!parsed.success || !isRecord(parsed.value)) return void 0;
|
|
681
|
+
return {
|
|
682
|
+
value: parsed.value,
|
|
683
|
+
write: (value) => writeStore({
|
|
684
|
+
authPath,
|
|
685
|
+
value
|
|
686
|
+
})
|
|
687
|
+
};
|
|
929
688
|
}
|
|
930
|
-
async function writeStore({
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}
|
|
937
|
-
`, {
|
|
938
|
-
mode: 384
|
|
939
|
-
});
|
|
940
|
-
await chmod(temporaryPath, 384);
|
|
941
|
-
await rename(temporaryPath, authPath);
|
|
689
|
+
async function writeStore({ authPath, value }) {
|
|
690
|
+
await mkdir(dirname(authPath), { recursive: true });
|
|
691
|
+
const temporaryPath = `${authPath}.${process.pid}.tmp`;
|
|
692
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
693
|
+
await chmod(temporaryPath, 384);
|
|
694
|
+
await rename(temporaryPath, authPath);
|
|
942
695
|
}
|
|
943
696
|
function isSubscriptionProvider(value) {
|
|
944
|
-
|
|
697
|
+
return value === "openai" || value === "xai" || value === "github-copilot" || value === "poe" || value === "opencode-go" || value === "gitlab";
|
|
945
698
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
variant: z.string().optional(),
|
|
960
|
-
instructions: z.string().optional(),
|
|
961
|
-
skillsChanged: z.boolean().optional(),
|
|
962
|
-
resumeSessionId: z.string().optional(),
|
|
963
|
-
openCodeConfig: z.record(z.string(), z.unknown()).optional(),
|
|
964
|
-
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
965
|
-
headers: z.record(z.string(), z.string()).optional()
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/opencode-bridge-protocol.ts
|
|
701
|
+
const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;
|
|
702
|
+
const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
703
|
+
operation: z.enum(["prompt", "compact"]).optional(),
|
|
704
|
+
provider: z.string().optional(),
|
|
705
|
+
variant: z.string().optional(),
|
|
706
|
+
instructions: z.string().optional(),
|
|
707
|
+
skillsChanged: z.boolean().optional(),
|
|
708
|
+
resumeSessionId: z.string().optional(),
|
|
709
|
+
openCodeConfig: z.record(z.string(), z.unknown()).optional(),
|
|
710
|
+
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
711
|
+
headers: z.record(z.string(), z.string()).optional()
|
|
966
712
|
});
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}),
|
|
1071
|
-
agent: tool({
|
|
1072
|
-
description: "Run an OpenCode subagent",
|
|
1073
|
-
inputSchema: z2.looseObject({
|
|
1074
|
-
agent: z2.string().optional(),
|
|
1075
|
-
prompt: z2.string().optional(),
|
|
1076
|
-
description: z2.string().optional(),
|
|
1077
|
-
metadata: optionalStringRecord
|
|
1078
|
-
})
|
|
1079
|
-
})
|
|
713
|
+
z.discriminatedUnion("type", [startMessageSchema, ...harnessV1BridgeInboundCommandSchemas]);
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/version.ts
|
|
716
|
+
const VERSION = "1.0.136";
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/opencode-harness.ts
|
|
719
|
+
/**
|
|
720
|
+
* Value to use in User-Agent and `x-client-app` headers.
|
|
721
|
+
*/
|
|
722
|
+
const OPENCODE_CLIENT_APP = `ai-sdk-harness-opencode/${VERSION}`;
|
|
723
|
+
const optionalStringRecord = z.record(z.string(), z.unknown()).optional();
|
|
724
|
+
const OPENCODE_BUILTIN_TOOLS = {
|
|
725
|
+
askUserQuestions: {
|
|
726
|
+
...HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
|
|
727
|
+
nativeName: "question",
|
|
728
|
+
toolUseKind: "readonly"
|
|
729
|
+
},
|
|
730
|
+
read: commonTool("read", {
|
|
731
|
+
nativeName: "view",
|
|
732
|
+
toolUseKind: "readonly",
|
|
733
|
+
description: "Read file contents",
|
|
734
|
+
inputSchema: z.looseObject({
|
|
735
|
+
file_path: z.string().optional(),
|
|
736
|
+
path: z.string().optional()
|
|
737
|
+
})
|
|
738
|
+
}),
|
|
739
|
+
write: commonTool("write", {
|
|
740
|
+
nativeName: "write",
|
|
741
|
+
toolUseKind: "edit",
|
|
742
|
+
description: "Write content to a file",
|
|
743
|
+
inputSchema: z.looseObject({
|
|
744
|
+
file_path: z.string().optional(),
|
|
745
|
+
path: z.string().optional(),
|
|
746
|
+
content: z.string().optional()
|
|
747
|
+
})
|
|
748
|
+
}),
|
|
749
|
+
edit: commonTool("edit", {
|
|
750
|
+
nativeName: "edit",
|
|
751
|
+
toolUseKind: "edit",
|
|
752
|
+
description: "Edit a file by replacing text",
|
|
753
|
+
inputSchema: z.looseObject({
|
|
754
|
+
file_path: z.string().optional(),
|
|
755
|
+
path: z.string().optional(),
|
|
756
|
+
old_string: z.string().optional(),
|
|
757
|
+
new_string: z.string().optional()
|
|
758
|
+
})
|
|
759
|
+
}),
|
|
760
|
+
bash: commonTool("bash", {
|
|
761
|
+
nativeName: "bash",
|
|
762
|
+
toolUseKind: "bash",
|
|
763
|
+
description: "Execute a shell command",
|
|
764
|
+
inputSchema: z.looseObject({ command: z.string().optional() })
|
|
765
|
+
}),
|
|
766
|
+
glob: commonTool("glob", {
|
|
767
|
+
nativeName: "glob",
|
|
768
|
+
toolUseKind: "readonly",
|
|
769
|
+
description: "Find files matching a glob pattern",
|
|
770
|
+
inputSchema: z.looseObject({
|
|
771
|
+
pattern: z.string().optional(),
|
|
772
|
+
path: z.string().optional()
|
|
773
|
+
})
|
|
774
|
+
}),
|
|
775
|
+
grep: commonTool("grep", {
|
|
776
|
+
nativeName: "grep",
|
|
777
|
+
toolUseKind: "readonly",
|
|
778
|
+
description: "Search file contents with regex",
|
|
779
|
+
inputSchema: z.looseObject({
|
|
780
|
+
pattern: z.string().optional(),
|
|
781
|
+
path: z.string().optional()
|
|
782
|
+
})
|
|
783
|
+
}),
|
|
784
|
+
ls: tool({
|
|
785
|
+
description: "List directory contents",
|
|
786
|
+
inputSchema: z.looseObject({ path: z.string().optional() })
|
|
787
|
+
}),
|
|
788
|
+
webfetch: tool({
|
|
789
|
+
description: "Fetch a URL",
|
|
790
|
+
inputSchema: z.looseObject({
|
|
791
|
+
url: z.string().optional(),
|
|
792
|
+
prompt: z.string().optional()
|
|
793
|
+
})
|
|
794
|
+
}),
|
|
795
|
+
skill: tool({
|
|
796
|
+
description: "Load an OpenCode skill by name",
|
|
797
|
+
inputSchema: z.looseObject({ name: z.string().optional() })
|
|
798
|
+
}),
|
|
799
|
+
todowrite: tool({
|
|
800
|
+
description: "Replace the OpenCode session todo list",
|
|
801
|
+
inputSchema: z.looseObject({ todos: z.array(z.looseObject({
|
|
802
|
+
content: z.string().optional(),
|
|
803
|
+
status: z.string().optional(),
|
|
804
|
+
priority: z.string().optional()
|
|
805
|
+
})).optional() })
|
|
806
|
+
}),
|
|
807
|
+
agent: tool({
|
|
808
|
+
description: "Run an OpenCode subagent",
|
|
809
|
+
inputSchema: z.looseObject({
|
|
810
|
+
agent: z.string().optional(),
|
|
811
|
+
prompt: z.string().optional(),
|
|
812
|
+
description: z.string().optional(),
|
|
813
|
+
metadata: optionalStringRecord
|
|
814
|
+
})
|
|
815
|
+
})
|
|
1080
816
|
};
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
817
|
+
const openCodeBridgeCoordsSchema = z.object({
|
|
818
|
+
port: z.number(),
|
|
819
|
+
token: z.string(),
|
|
820
|
+
lastSeenEventId: z.number(),
|
|
821
|
+
sandboxId: z.string().optional()
|
|
1086
822
|
});
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
823
|
+
const openCodeResumeStateSchema = z.object({
|
|
824
|
+
openCodeSessionId: z.string().optional(),
|
|
825
|
+
bridge: openCodeBridgeCoordsSchema.optional(),
|
|
826
|
+
sandboxCredentialEnvironment: z.record(z.string(), z.string()).optional()
|
|
1091
827
|
});
|
|
1092
828
|
function createOpenCode(settings = {}) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
});
|
|
1387
|
-
await channel.open(
|
|
1388
|
-
respawnStrategy === "replay" ? { resume: true } : void 0
|
|
1389
|
-
);
|
|
1390
|
-
return createSession({
|
|
1391
|
-
sessionId: startOpts.sessionId,
|
|
1392
|
-
channel,
|
|
1393
|
-
proc,
|
|
1394
|
-
provider: settings.provider,
|
|
1395
|
-
reasoningVariant: settings.reasoningVariant,
|
|
1396
|
-
openCodeConfig: resolvedOpenCodeConfig,
|
|
1397
|
-
mcpServers: settings.mcpServers,
|
|
1398
|
-
headers: startOpts.headers,
|
|
1399
|
-
openCodeSessionId: resumeSessionId,
|
|
1400
|
-
isResume: respawnStrategy !== void 0,
|
|
1401
|
-
seedResumeSessionOnFirstPrompt: respawnStrategy !== void 0,
|
|
1402
|
-
rerunContinue: respawnStrategy === "rerun",
|
|
1403
|
-
bridgePort: boundPort,
|
|
1404
|
-
bridgeToken: token,
|
|
1405
|
-
sandboxId,
|
|
1406
|
-
sandboxCredentialEnvironment,
|
|
1407
|
-
debug: startOpts.observability?.debug,
|
|
1408
|
-
permissionMode: startOpts.permissionMode,
|
|
1409
|
-
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
1410
|
-
sandbox: toolSafeSandboxSession,
|
|
1411
|
-
sandboxHomeDir,
|
|
1412
|
-
transformModel,
|
|
1413
|
-
supportsUserMessageResponses: () => supportsUserMessageResponses
|
|
1414
|
-
});
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
829
|
+
if (settings.mcpServers != null && Object.prototype.hasOwnProperty.call(settings.mcpServers, "harness-tools")) throw new Error("OpenCode MCP server name \"harness-tools\" is reserved for HarnessAgent tools.");
|
|
830
|
+
return {
|
|
831
|
+
specificationVersion: "harness-v1",
|
|
832
|
+
harnessId: "opencode",
|
|
833
|
+
builtinTools: OPENCODE_BUILTIN_TOOLS,
|
|
834
|
+
supportsBuiltinToolApprovals: true,
|
|
835
|
+
lifecycleStateSchema: openCodeResumeStateSchema,
|
|
836
|
+
getBootstrap: getOpenCodeBootstrap,
|
|
837
|
+
doStart: async (startOpts) => {
|
|
838
|
+
const sandboxSession = startOpts.sandboxSession;
|
|
839
|
+
const toolSafeSandboxSession = getRestrictedSandboxSession(sandboxSession);
|
|
840
|
+
const sandboxId = "id" in sandboxSession ? sandboxSession.id : void 0;
|
|
841
|
+
validateBasicSandboxSettings({
|
|
842
|
+
sandboxSession,
|
|
843
|
+
port: settings.port,
|
|
844
|
+
portEndpoint: settings.portEndpoint
|
|
845
|
+
});
|
|
846
|
+
if (settings.mintBridgeToken != null && sandboxId == null) throw new HarnessCapabilityUnsupportedError({
|
|
847
|
+
harnessId: "opencode",
|
|
848
|
+
message: "The OpenCode harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id."
|
|
849
|
+
});
|
|
850
|
+
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
851
|
+
const isResume = lifecycleState != null;
|
|
852
|
+
const isContinue = startOpts.continueFrom != null;
|
|
853
|
+
const resumeData = isResume && typeof lifecycleState?.data === "object" ? lifecycleState.data : void 0;
|
|
854
|
+
const resumeSessionId = typeof resumeData?.openCodeSessionId === "string" && resumeData.openCodeSessionId.length > 0 ? resumeData.openCodeSessionId : void 0;
|
|
855
|
+
const coords = resumeData?.bridge;
|
|
856
|
+
const authentication = await resolveOpenCodeAuthentication({
|
|
857
|
+
auth: settings.auth,
|
|
858
|
+
provider: settings.provider
|
|
859
|
+
});
|
|
860
|
+
const authenticationMode = authentication.authenticationMode;
|
|
861
|
+
const resolvedAuthEnvironment = authentication.environment;
|
|
862
|
+
let resolvedOpenCodeConfig = settings.openCodeConfig;
|
|
863
|
+
let transformModel;
|
|
864
|
+
let gitLabSubscriptionBrokered = false;
|
|
865
|
+
let sandboxAuthEnvironment = resolvedAuthEnvironment;
|
|
866
|
+
let sandboxCredentialEnvironment;
|
|
867
|
+
let credentialsBrokered = false;
|
|
868
|
+
if ("addRequestTransformations" in sandboxSession && sandboxSession.addRequestTransformations != null) {
|
|
869
|
+
sandboxCredentialEnvironment = await resolveSandboxCredentialEnvironment({
|
|
870
|
+
environment: resolvedAuthEnvironment,
|
|
871
|
+
credentialEnvironmentVariables: OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
872
|
+
credentialForwarding: settings.credentialForwarding,
|
|
873
|
+
previousSandboxCredentialEnvironment: resumeData?.sandboxCredentialEnvironment
|
|
874
|
+
});
|
|
875
|
+
sandboxAuthEnvironment = {
|
|
876
|
+
...resolvedAuthEnvironment,
|
|
877
|
+
...sandboxCredentialEnvironment
|
|
878
|
+
};
|
|
879
|
+
const transformationSources = {
|
|
880
|
+
env: resolvedAuthEnvironment,
|
|
881
|
+
sandboxEnv: sandboxAuthEnvironment,
|
|
882
|
+
auth: authenticationMode
|
|
883
|
+
};
|
|
884
|
+
const sandboxSubscriptionAccessToken = sandboxAuthEnvironment[OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE];
|
|
885
|
+
let requestTransformations;
|
|
886
|
+
if (authentication.subscription?.providerId === "gitlab" && sandboxSubscriptionAccessToken != null) {
|
|
887
|
+
const directAccess = await requestOpenCodeGitLabDirectAccess({
|
|
888
|
+
accessToken: authentication.subscription.accessToken,
|
|
889
|
+
...authentication.subscription.enterpriseUrl == null ? {} : { instanceUrl: authentication.subscription.enterpriseUrl },
|
|
890
|
+
...process.env.GITLAB_AI_GATEWAY_URL == null ? {} : { aiGatewayUrl: process.env.GITLAB_AI_GATEWAY_URL }
|
|
891
|
+
});
|
|
892
|
+
requestTransformations = createOpenCodeGitLabSubscriptionRequestTransformations({
|
|
893
|
+
directAccess,
|
|
894
|
+
sandboxAccessToken: sandboxSubscriptionAccessToken
|
|
895
|
+
});
|
|
896
|
+
resolvedOpenCodeConfig = createOpenCodeGitLabSubscriptionConfig({
|
|
897
|
+
openCodeConfig: settings.openCodeConfig,
|
|
898
|
+
sandboxAccessToken: sandboxSubscriptionAccessToken,
|
|
899
|
+
aiGatewayUrl: directAccess.aiGatewayUrl,
|
|
900
|
+
...startOpts.headers == null ? {} : { headers: startOpts.headers }
|
|
901
|
+
});
|
|
902
|
+
transformModel = (model) => resolveOpenCodeGitLabSubscriptionModel({
|
|
903
|
+
model,
|
|
904
|
+
provider: settings.provider
|
|
905
|
+
});
|
|
906
|
+
gitLabSubscriptionBrokered = true;
|
|
907
|
+
} else requestTransformations = authentication.subscription == null || sandboxSubscriptionAccessToken == null ? createOpenCodeRequestTransformations(transformationSources) : createOpenCodeSubscriptionRequestTransformations({
|
|
908
|
+
authentication: authentication.subscription,
|
|
909
|
+
sandboxAccessToken: sandboxSubscriptionAccessToken
|
|
910
|
+
});
|
|
911
|
+
if (requestTransformations.length > 0) await sandboxSession.addRequestTransformations(requestTransformations);
|
|
912
|
+
credentialsBrokered = true;
|
|
913
|
+
}
|
|
914
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
915
|
+
sandbox: toolSafeSandboxSession,
|
|
916
|
+
abortSignal: startOpts.abortSignal
|
|
917
|
+
});
|
|
918
|
+
const stateDir = harnessStateDirectoryPath({ sandboxHomeDir });
|
|
919
|
+
const bootstrapDir = path.posix.resolve(stateDir, OPENCODE_BOOTSTRAP_DIR);
|
|
920
|
+
const workDir = startOpts.sessionWorkDir;
|
|
921
|
+
const skillsDir = path.posix.join(sandboxHomeDir, ".agents", "skills");
|
|
922
|
+
const bridgeStateDir = `${harnessSessionDataDirectoryPath({
|
|
923
|
+
stateDirectory: stateDir,
|
|
924
|
+
sessionId: startOpts.sessionId
|
|
925
|
+
})}/bridge`;
|
|
926
|
+
const timeoutMs = settings.startupTimeoutMs ?? 12e4;
|
|
927
|
+
const report = startOpts.observability?.report;
|
|
928
|
+
const onDiagnostic = report ? (frame) => report(harnessV1DiagnosticFromBridgeFrame(frame, {
|
|
929
|
+
sessionId: startOpts.sessionId,
|
|
930
|
+
timestamp: Date.now()
|
|
931
|
+
})) : void 0;
|
|
932
|
+
const onBridgeError = createBridgeErrorHandler({
|
|
933
|
+
harnessId: "opencode",
|
|
934
|
+
sessionId: startOpts.sessionId
|
|
935
|
+
});
|
|
936
|
+
if (coords) try {
|
|
937
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
938
|
+
sandboxSession,
|
|
939
|
+
override: settings.portEndpoint,
|
|
940
|
+
port: coords.port
|
|
941
|
+
});
|
|
942
|
+
const attachEndpoint = withBridgeToken({
|
|
943
|
+
endpoint,
|
|
944
|
+
token: coords.token
|
|
945
|
+
});
|
|
946
|
+
let supportsUserMessageResponses = false;
|
|
947
|
+
const attachChannel = new SandboxChannel({
|
|
948
|
+
connect: ({ abortSignal }) => openWebSocket({
|
|
949
|
+
endpoint: attachEndpoint,
|
|
950
|
+
helloTimeoutMs: Math.min(timeoutMs, 5e3),
|
|
951
|
+
abortSignal,
|
|
952
|
+
onHello: (supported) => {
|
|
953
|
+
supportsUserMessageResponses = supported;
|
|
954
|
+
}
|
|
955
|
+
}),
|
|
956
|
+
outboundSchema: outboundMessageSchema,
|
|
957
|
+
initialLastSeenEventId: coords.lastSeenEventId,
|
|
958
|
+
onDiagnostic,
|
|
959
|
+
onBridgeError,
|
|
960
|
+
reconnect: settings.reconnect
|
|
961
|
+
});
|
|
962
|
+
await attachChannel.open(isContinue ? { resume: true } : void 0);
|
|
963
|
+
return createSession({
|
|
964
|
+
sessionId: startOpts.sessionId,
|
|
965
|
+
channel: attachChannel,
|
|
966
|
+
proc: void 0,
|
|
967
|
+
provider: settings.provider,
|
|
968
|
+
reasoningVariant: settings.reasoningVariant,
|
|
969
|
+
openCodeConfig: resolvedOpenCodeConfig,
|
|
970
|
+
mcpServers: settings.mcpServers,
|
|
971
|
+
headers: startOpts.headers,
|
|
972
|
+
openCodeSessionId: resumeSessionId,
|
|
973
|
+
isResume: true,
|
|
974
|
+
seedResumeSessionOnFirstPrompt: false,
|
|
975
|
+
rerunContinue: false,
|
|
976
|
+
bridgePort: coords.port,
|
|
977
|
+
bridgeToken: coords.token,
|
|
978
|
+
sandboxId,
|
|
979
|
+
sandboxCredentialEnvironment,
|
|
980
|
+
debug: startOpts.observability?.debug,
|
|
981
|
+
permissionMode: startOpts.permissionMode,
|
|
982
|
+
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
983
|
+
sandbox: toolSafeSandboxSession,
|
|
984
|
+
sandboxHomeDir,
|
|
985
|
+
transformModel,
|
|
986
|
+
supportsUserMessageResponses: () => supportsUserMessageResponses
|
|
987
|
+
});
|
|
988
|
+
} catch {}
|
|
989
|
+
let respawnStrategy = isResume ? "rerun" : void 0;
|
|
990
|
+
if (coords && isContinue) {
|
|
991
|
+
const logRaw = await Promise.resolve(toolSafeSandboxSession.readTextFile({
|
|
992
|
+
path: `${bridgeStateDir}/event-log.ndjson`,
|
|
993
|
+
abortSignal: startOpts.abortSignal
|
|
994
|
+
})).catch(() => null);
|
|
995
|
+
if (await classifyDiskLog(logRaw) === "replay") respawnStrategy = "replay";
|
|
996
|
+
}
|
|
997
|
+
const port = resolveBridgePort({
|
|
998
|
+
sandboxSession,
|
|
999
|
+
override: settings.port
|
|
1000
|
+
});
|
|
1001
|
+
const token = settings.mintBridgeToken == null ? createBridgeToken() : settings.mintBridgeToken(sandboxId);
|
|
1002
|
+
const forwardedAuthEnvironment = credentialsBrokered ? sandboxAuthEnvironment : await applyCredentialForwarding({
|
|
1003
|
+
environment: sandboxAuthEnvironment,
|
|
1004
|
+
credentialEnvironmentVariables: OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
1005
|
+
credentialForwarding: settings.credentialForwarding
|
|
1006
|
+
});
|
|
1007
|
+
if (!credentialsBrokered) warnCredentialBrokeringUnavailable({
|
|
1008
|
+
environment: resolvedAuthEnvironment,
|
|
1009
|
+
forwardedEnvironment: forwardedAuthEnvironment,
|
|
1010
|
+
credentialEnvironmentVariables: OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES
|
|
1011
|
+
});
|
|
1012
|
+
const subscriptionAccessToken = forwardedAuthEnvironment[OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE];
|
|
1013
|
+
const env = {
|
|
1014
|
+
...Object.fromEntries(Object.entries(forwardedAuthEnvironment).filter(([name]) => name !== OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE)),
|
|
1015
|
+
...gitLabSubscriptionBrokered || authentication.subscription == null || subscriptionAccessToken == null ? {} : { OPENCODE_AUTH_CONTENT: createOpenCodeSubscriptionAuthContent({
|
|
1016
|
+
authentication: authentication.subscription,
|
|
1017
|
+
accessToken: subscriptionAccessToken
|
|
1018
|
+
}) },
|
|
1019
|
+
AI_SDK_HARNESS_CLIENT_APP: OPENCODE_CLIENT_APP,
|
|
1020
|
+
BRIDGE_CHANNEL_TOKEN: token,
|
|
1021
|
+
BRIDGE_WS_PORT: String(port),
|
|
1022
|
+
...respawnStrategy === "replay" ? { BRIDGE_REPLAY_FROM_DISK: "1" } : {}
|
|
1023
|
+
};
|
|
1024
|
+
if (respawnStrategy === void 0) await toolSafeSandboxSession.run({
|
|
1025
|
+
command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
|
|
1026
|
+
abortSignal: startOpts.abortSignal
|
|
1027
|
+
});
|
|
1028
|
+
await markBridgeStarting({
|
|
1029
|
+
sandbox: toolSafeSandboxSession,
|
|
1030
|
+
bridgeStateDir,
|
|
1031
|
+
bridgeType: "opencode",
|
|
1032
|
+
abortSignal: startOpts.abortSignal
|
|
1033
|
+
});
|
|
1034
|
+
const proc = await toolSafeSandboxSession.spawn({
|
|
1035
|
+
command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)} --skills-dir ${shellQuote(skillsDir)}`,
|
|
1036
|
+
env,
|
|
1037
|
+
abortSignal: startOpts.abortSignal
|
|
1038
|
+
});
|
|
1039
|
+
const stderrTail = [];
|
|
1040
|
+
const bridgeStderrDone = forwardBridgeProcessStream({
|
|
1041
|
+
stream: proc.stderr,
|
|
1042
|
+
streamName: "stderr",
|
|
1043
|
+
source: "opencode",
|
|
1044
|
+
collectTail: stderrTail
|
|
1045
|
+
});
|
|
1046
|
+
const { port: boundPort } = await waitForBridgeReady({
|
|
1047
|
+
proc,
|
|
1048
|
+
sandbox: toolSafeSandboxSession,
|
|
1049
|
+
bridgeStateDir,
|
|
1050
|
+
bridgeType: "opencode",
|
|
1051
|
+
timeoutMs,
|
|
1052
|
+
abortSignal: startOpts.abortSignal,
|
|
1053
|
+
createTimeoutError: ({ proc, stdoutTail }) => createBridgeStartupError({
|
|
1054
|
+
message: "OpenCode bridge did not become ready in time.",
|
|
1055
|
+
proc,
|
|
1056
|
+
stdoutTail,
|
|
1057
|
+
stderrTail,
|
|
1058
|
+
stderrDone: bridgeStderrDone
|
|
1059
|
+
}),
|
|
1060
|
+
createExitError: ({ proc, stdoutTail }) => createBridgeStartupError({
|
|
1061
|
+
message: "OpenCode bridge exited before becoming ready.",
|
|
1062
|
+
proc,
|
|
1063
|
+
stdoutTail,
|
|
1064
|
+
stderrTail,
|
|
1065
|
+
stderrDone: bridgeStderrDone
|
|
1066
|
+
})
|
|
1067
|
+
});
|
|
1068
|
+
drainBridgeProcessStream(proc.stdout);
|
|
1069
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
1070
|
+
sandboxSession,
|
|
1071
|
+
override: settings.portEndpoint,
|
|
1072
|
+
port: boundPort
|
|
1073
|
+
});
|
|
1074
|
+
const bridgeEndpoint = withBridgeToken({
|
|
1075
|
+
endpoint,
|
|
1076
|
+
token
|
|
1077
|
+
});
|
|
1078
|
+
let supportsUserMessageResponses = false;
|
|
1079
|
+
const channel = new SandboxChannel({
|
|
1080
|
+
connect: ({ abortSignal }) => openWebSocket({
|
|
1081
|
+
endpoint: bridgeEndpoint,
|
|
1082
|
+
helloTimeoutMs: Math.min(timeoutMs, 5e3),
|
|
1083
|
+
abortSignal,
|
|
1084
|
+
onHello: (supported) => {
|
|
1085
|
+
supportsUserMessageResponses = supported;
|
|
1086
|
+
}
|
|
1087
|
+
}),
|
|
1088
|
+
outboundSchema: outboundMessageSchema,
|
|
1089
|
+
onDiagnostic,
|
|
1090
|
+
onBridgeError,
|
|
1091
|
+
reconnect: settings.reconnect,
|
|
1092
|
+
...respawnStrategy === "replay" ? { initialLastSeenEventId: coords?.lastSeenEventId ?? 0 } : {}
|
|
1093
|
+
});
|
|
1094
|
+
await channel.open(respawnStrategy === "replay" ? { resume: true } : void 0);
|
|
1095
|
+
return createSession({
|
|
1096
|
+
sessionId: startOpts.sessionId,
|
|
1097
|
+
channel,
|
|
1098
|
+
proc,
|
|
1099
|
+
provider: settings.provider,
|
|
1100
|
+
reasoningVariant: settings.reasoningVariant,
|
|
1101
|
+
openCodeConfig: resolvedOpenCodeConfig,
|
|
1102
|
+
mcpServers: settings.mcpServers,
|
|
1103
|
+
headers: startOpts.headers,
|
|
1104
|
+
openCodeSessionId: resumeSessionId,
|
|
1105
|
+
isResume: respawnStrategy !== void 0,
|
|
1106
|
+
seedResumeSessionOnFirstPrompt: respawnStrategy !== void 0,
|
|
1107
|
+
rerunContinue: respawnStrategy === "rerun",
|
|
1108
|
+
bridgePort: boundPort,
|
|
1109
|
+
bridgeToken: token,
|
|
1110
|
+
sandboxId,
|
|
1111
|
+
sandboxCredentialEnvironment,
|
|
1112
|
+
debug: startOpts.observability?.debug,
|
|
1113
|
+
permissionMode: startOpts.permissionMode,
|
|
1114
|
+
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
1115
|
+
sandbox: toolSafeSandboxSession,
|
|
1116
|
+
sandboxHomeDir,
|
|
1117
|
+
transformModel,
|
|
1118
|
+
supportsUserMessageResponses: () => supportsUserMessageResponses
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1417
1122
|
}
|
|
1418
|
-
function resolveBridgePort({
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
}
|
|
1426
|
-
throw new HarnessCapabilityUnsupportedError2({
|
|
1427
|
-
harnessId: "opencode",
|
|
1428
|
-
message: "The OpenCode harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createOpenCode({ port })`."
|
|
1429
|
-
});
|
|
1123
|
+
function resolveBridgePort({ sandboxSession, override }) {
|
|
1124
|
+
if (override !== void 0) return override;
|
|
1125
|
+
if ("ports" in sandboxSession && sandboxSession.ports.length > 0) return sandboxSession.ports[0];
|
|
1126
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
1127
|
+
harnessId: "opencode",
|
|
1128
|
+
message: "The OpenCode harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createOpenCode({ port })`."
|
|
1129
|
+
});
|
|
1430
1130
|
}
|
|
1431
|
-
function validateBasicSandboxSettings({
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
if (portEndpoint == null) {
|
|
1444
|
-
throw new HarnessCapabilityUnsupportedError2({
|
|
1445
|
-
harnessId: "opencode",
|
|
1446
|
-
message: "The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1131
|
+
function validateBasicSandboxSettings({ sandboxSession, port, portEndpoint }) {
|
|
1132
|
+
if ("getPortEndpoint" in sandboxSession) return;
|
|
1133
|
+
if (port == null) throw new HarnessCapabilityUnsupportedError({
|
|
1134
|
+
harnessId: "opencode",
|
|
1135
|
+
message: "The OpenCode harness requires an explicit `port` when using a basic sandbox session."
|
|
1136
|
+
});
|
|
1137
|
+
if (portEndpoint == null) throw new HarnessCapabilityUnsupportedError({
|
|
1138
|
+
harnessId: "opencode",
|
|
1139
|
+
message: "The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
1140
|
+
});
|
|
1449
1141
|
}
|
|
1450
|
-
async function resolveBridgeEndpoint({
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
harnessId: "opencode",
|
|
1461
|
-
message: "The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
1462
|
-
});
|
|
1142
|
+
async function resolveBridgeEndpoint({ sandboxSession, override, port }) {
|
|
1143
|
+
if (override != null) return override;
|
|
1144
|
+
if ("getPortEndpoint" in sandboxSession) return sandboxSession.getPortEndpoint({
|
|
1145
|
+
port,
|
|
1146
|
+
protocol: "ws"
|
|
1147
|
+
});
|
|
1148
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
1149
|
+
harnessId: "opencode",
|
|
1150
|
+
message: "The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
1151
|
+
});
|
|
1463
1152
|
}
|
|
1464
|
-
function openWebSocket({
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
const onClose = () => settle(new Error("OpenCode bridge closed before sending bridge-hello."));
|
|
1540
|
-
const onError = (err) => {
|
|
1541
|
-
settle(err);
|
|
1542
|
-
};
|
|
1543
|
-
onAbort = () => settle(abortReason());
|
|
1544
|
-
ws.on("open", onOpen);
|
|
1545
|
-
ws.on("message", onMessage);
|
|
1546
|
-
ws.on("close", onClose);
|
|
1547
|
-
ws.on("error", onError);
|
|
1548
|
-
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
1549
|
-
if (abortSignal.aborted) {
|
|
1550
|
-
onAbort();
|
|
1551
|
-
return;
|
|
1552
|
-
}
|
|
1553
|
-
helloTimer = setTimeout(
|
|
1554
|
-
() => settle(
|
|
1555
|
-
new Error(
|
|
1556
|
-
`OpenCode bridge did not send bridge-hello within ${helloTimeoutMs}ms.`
|
|
1557
|
-
)
|
|
1558
|
-
),
|
|
1559
|
-
helloTimeoutMs
|
|
1560
|
-
);
|
|
1561
|
-
helloTimer.unref?.();
|
|
1562
|
-
});
|
|
1153
|
+
function openWebSocket({ endpoint, helloTimeoutMs, abortSignal, onHello }) {
|
|
1154
|
+
return new Promise((resolve, reject) => {
|
|
1155
|
+
const abortReason = () => abortSignal.reason ?? /* @__PURE__ */ new Error("WebSocket connection aborted");
|
|
1156
|
+
if (abortSignal.aborted) {
|
|
1157
|
+
reject(abortReason());
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const ws = new WebSocket(endpoint.url, { headers: endpoint.headers == null ? void 0 : { ...endpoint.headers } });
|
|
1161
|
+
let opened = false;
|
|
1162
|
+
let receivedHello = false;
|
|
1163
|
+
let settled = false;
|
|
1164
|
+
let helloTimer;
|
|
1165
|
+
let onAbort;
|
|
1166
|
+
const cleanup = () => {
|
|
1167
|
+
clearTimeout(helloTimer);
|
|
1168
|
+
ws.off("open", onOpen);
|
|
1169
|
+
ws.off("message", onMessage);
|
|
1170
|
+
ws.off("close", onClose);
|
|
1171
|
+
ws.off("error", onError);
|
|
1172
|
+
if (onAbort != null) abortSignal.removeEventListener("abort", onAbort);
|
|
1173
|
+
};
|
|
1174
|
+
const settle = (error) => {
|
|
1175
|
+
if (settled) return;
|
|
1176
|
+
settled = true;
|
|
1177
|
+
cleanup();
|
|
1178
|
+
if (error == null) resolve(ws);
|
|
1179
|
+
else {
|
|
1180
|
+
const suppressError = () => {};
|
|
1181
|
+
ws.once?.("error", suppressError);
|
|
1182
|
+
try {
|
|
1183
|
+
ws.terminate();
|
|
1184
|
+
} catch {
|
|
1185
|
+
try {
|
|
1186
|
+
ws.close();
|
|
1187
|
+
} catch {}
|
|
1188
|
+
}
|
|
1189
|
+
reject(error);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
const tryResolve = () => {
|
|
1193
|
+
if (opened && receivedHello) settle();
|
|
1194
|
+
};
|
|
1195
|
+
const onOpen = () => {
|
|
1196
|
+
opened = true;
|
|
1197
|
+
tryResolve();
|
|
1198
|
+
};
|
|
1199
|
+
const onMessage = (raw) => {
|
|
1200
|
+
(async () => {
|
|
1201
|
+
const parsed = await safeParseJSON({ text: webSocketMessageToString(raw) });
|
|
1202
|
+
if (!parsed.success) return;
|
|
1203
|
+
const value = parsed.value;
|
|
1204
|
+
if (typeof value !== "object" || value == null || Array.isArray(value) || value.type !== "bridge-hello") return;
|
|
1205
|
+
const capabilities = value.capabilities;
|
|
1206
|
+
onHello(capabilities?.experimental_userMessageResponses === true);
|
|
1207
|
+
receivedHello = true;
|
|
1208
|
+
tryResolve();
|
|
1209
|
+
})();
|
|
1210
|
+
};
|
|
1211
|
+
const onClose = () => settle(/* @__PURE__ */ new Error("OpenCode bridge closed before sending bridge-hello."));
|
|
1212
|
+
const onError = (err) => {
|
|
1213
|
+
settle(err);
|
|
1214
|
+
};
|
|
1215
|
+
onAbort = () => settle(abortReason());
|
|
1216
|
+
ws.on("open", onOpen);
|
|
1217
|
+
ws.on("message", onMessage);
|
|
1218
|
+
ws.on("close", onClose);
|
|
1219
|
+
ws.on("error", onError);
|
|
1220
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
1221
|
+
if (abortSignal.aborted) {
|
|
1222
|
+
onAbort();
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
helloTimer = setTimeout(() => settle(/* @__PURE__ */ new Error(`OpenCode bridge did not send bridge-hello within ${helloTimeoutMs}ms.`)), helloTimeoutMs);
|
|
1226
|
+
helloTimer.unref?.();
|
|
1227
|
+
});
|
|
1563
1228
|
}
|
|
1564
1229
|
function webSocketMessageToString(raw) {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
"utf8"
|
|
1572
|
-
);
|
|
1573
|
-
}
|
|
1574
|
-
return String(raw);
|
|
1230
|
+
if (typeof raw === "string") return raw;
|
|
1231
|
+
if (Buffer.isBuffer(raw)) return raw.toString("utf8");
|
|
1232
|
+
if (Array.isArray(raw)) return Buffer.concat(raw).toString("utf8");
|
|
1233
|
+
if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString("utf8");
|
|
1234
|
+
if (ArrayBuffer.isView(raw)) return Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength).toString("utf8");
|
|
1235
|
+
return String(raw);
|
|
1575
1236
|
}
|
|
1576
|
-
function createSession({
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
`OpenCode session ${sessionId} is already stopped; cannot stop.`
|
|
1910
|
-
);
|
|
1911
|
-
}
|
|
1912
|
-
stopped = true;
|
|
1913
|
-
channel.beginClose();
|
|
1914
|
-
const data = channel.isClosed() ? {} : await new Promise((resolve, reject) => {
|
|
1915
|
-
const timer = setTimeout(() => {
|
|
1916
|
-
unsub();
|
|
1917
|
-
reject(
|
|
1918
|
-
new Error(
|
|
1919
|
-
`OpenCode session ${sessionId} did not reply to stop within 5s.`
|
|
1920
|
-
)
|
|
1921
|
-
);
|
|
1922
|
-
}, 5e3);
|
|
1923
|
-
timer.unref?.();
|
|
1924
|
-
const unsub = channel.on("bridge-stop", (msg) => {
|
|
1925
|
-
clearTimeout(timer);
|
|
1926
|
-
unsub();
|
|
1927
|
-
resolve(msg.data);
|
|
1928
|
-
});
|
|
1929
|
-
try {
|
|
1930
|
-
channel.send({ type: "stop" });
|
|
1931
|
-
} catch (err) {
|
|
1932
|
-
clearTimeout(timer);
|
|
1933
|
-
unsub();
|
|
1934
|
-
reject(err);
|
|
1935
|
-
}
|
|
1936
|
-
});
|
|
1937
|
-
let stopTimer;
|
|
1938
|
-
try {
|
|
1939
|
-
if (proc) {
|
|
1940
|
-
await Promise.race([
|
|
1941
|
-
proc.wait(),
|
|
1942
|
-
new Promise((resolve) => {
|
|
1943
|
-
stopTimer = setTimeout(resolve, 5e3);
|
|
1944
|
-
stopTimer.unref?.();
|
|
1945
|
-
})
|
|
1946
|
-
]);
|
|
1947
|
-
}
|
|
1948
|
-
} finally {
|
|
1949
|
-
if (stopTimer) clearTimeout(stopTimer);
|
|
1950
|
-
try {
|
|
1951
|
-
await proc?.kill();
|
|
1952
|
-
} catch {
|
|
1953
|
-
}
|
|
1954
|
-
channel.close();
|
|
1955
|
-
}
|
|
1956
|
-
const lifecycleData = data != null && typeof data === "object" && !Array.isArray(data) ? { ...data } : {};
|
|
1957
|
-
const payload = {
|
|
1958
|
-
type: "resume-session",
|
|
1959
|
-
harnessId: "opencode",
|
|
1960
|
-
specificationVersion: "harness-v1",
|
|
1961
|
-
data: {
|
|
1962
|
-
...lifecycleData,
|
|
1963
|
-
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
|
-
return payload;
|
|
1967
|
-
},
|
|
1968
|
-
doSuspendTurn: async () => {
|
|
1969
|
-
if (stopped) {
|
|
1970
|
-
throw new Error(
|
|
1971
|
-
`OpenCode session ${sessionId} is stopped; cannot suspend.`
|
|
1972
|
-
);
|
|
1973
|
-
}
|
|
1974
|
-
stopped = true;
|
|
1975
|
-
const lastSeenEventId = await channel.suspend();
|
|
1976
|
-
const payload = {
|
|
1977
|
-
type: "continue-turn",
|
|
1978
|
-
harnessId: "opencode",
|
|
1979
|
-
specificationVersion: "harness-v1",
|
|
1980
|
-
data: {
|
|
1981
|
-
...latestOpenCodeSessionId ? { openCodeSessionId: latestOpenCodeSessionId } : {},
|
|
1982
|
-
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
|
|
1983
|
-
bridge: {
|
|
1984
|
-
port: bridgePort,
|
|
1985
|
-
token: bridgeToken,
|
|
1986
|
-
lastSeenEventId,
|
|
1987
|
-
...sandboxId == null ? {} : { sandboxId }
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
};
|
|
1991
|
-
return payload;
|
|
1992
|
-
}
|
|
1993
|
-
};
|
|
1237
|
+
function createSession({ sessionId, channel, proc, provider, reasoningVariant, openCodeConfig, mcpServers, headers, openCodeSessionId, isResume, seedResumeSessionOnFirstPrompt, rerunContinue, bridgePort, bridgeToken, sandboxId, sandboxCredentialEnvironment, debug, permissionMode, builtinToolFiltering, sandbox, sandboxHomeDir, transformModel, supportsUserMessageResponses }) {
|
|
1238
|
+
let stopped = false;
|
|
1239
|
+
let stopPromise;
|
|
1240
|
+
let latestOpenCodeSessionId = openCodeSessionId;
|
|
1241
|
+
let pendingResumeSessionId = seedResumeSessionOnFirstPrompt ? openCodeSessionId : void 0;
|
|
1242
|
+
let selectedModel;
|
|
1243
|
+
let activeTurn = false;
|
|
1244
|
+
const pendingCompactionParts = [];
|
|
1245
|
+
channel.on("bridge-thread", (msg) => {
|
|
1246
|
+
latestOpenCodeSessionId = msg.threadId;
|
|
1247
|
+
});
|
|
1248
|
+
const wireTurn = (turnOpts) => {
|
|
1249
|
+
activeTurn = true;
|
|
1250
|
+
let pendingResolve;
|
|
1251
|
+
let pendingReject;
|
|
1252
|
+
const done = new Promise((resolve, reject) => {
|
|
1253
|
+
pendingResolve = resolve;
|
|
1254
|
+
pendingReject = reject;
|
|
1255
|
+
});
|
|
1256
|
+
const userMessageSubmitter = supportsUserMessageResponses() ? experimental_createBridgeUserMessageSubmitter({
|
|
1257
|
+
send: (message) => channel.send(message),
|
|
1258
|
+
onResponse: (listener) => channel.on("user-message-response", listener),
|
|
1259
|
+
onReconnect: (listener) => channel.onReconnect(listener)
|
|
1260
|
+
}) : void 0;
|
|
1261
|
+
const unsubs = [];
|
|
1262
|
+
const forward = (event) => {
|
|
1263
|
+
try {
|
|
1264
|
+
turnOpts.emit(event);
|
|
1265
|
+
} catch {}
|
|
1266
|
+
};
|
|
1267
|
+
const eventTypes = [
|
|
1268
|
+
"stream-start",
|
|
1269
|
+
"text-start",
|
|
1270
|
+
"text-delta",
|
|
1271
|
+
"text-end",
|
|
1272
|
+
"reasoning-start",
|
|
1273
|
+
"reasoning-delta",
|
|
1274
|
+
"reasoning-end",
|
|
1275
|
+
"tool-call",
|
|
1276
|
+
"tool-approval-request",
|
|
1277
|
+
"tool-result",
|
|
1278
|
+
"file-change",
|
|
1279
|
+
"finish-step",
|
|
1280
|
+
"compaction",
|
|
1281
|
+
"raw"
|
|
1282
|
+
];
|
|
1283
|
+
let isSettled = false;
|
|
1284
|
+
const settleSuccess = () => {
|
|
1285
|
+
if (isSettled) return;
|
|
1286
|
+
isSettled = true;
|
|
1287
|
+
activeTurn = false;
|
|
1288
|
+
userMessageSubmitter?.close();
|
|
1289
|
+
for (const u of unsubs) u();
|
|
1290
|
+
pendingResolve();
|
|
1291
|
+
};
|
|
1292
|
+
const settleError = (err) => {
|
|
1293
|
+
if (isSettled) return;
|
|
1294
|
+
isSettled = true;
|
|
1295
|
+
activeTurn = false;
|
|
1296
|
+
userMessageSubmitter?.close(err);
|
|
1297
|
+
for (const u of unsubs) u();
|
|
1298
|
+
pendingReject(err);
|
|
1299
|
+
};
|
|
1300
|
+
for (const type of eventTypes) unsubs.push(channel.on(type, (msg) => {
|
|
1301
|
+
forward(msg);
|
|
1302
|
+
}));
|
|
1303
|
+
unsubs.push(channel.on("finish", (msg) => {
|
|
1304
|
+
forward(msg);
|
|
1305
|
+
settleSuccess();
|
|
1306
|
+
}));
|
|
1307
|
+
unsubs.push(channel.on("error", (msg) => {
|
|
1308
|
+
forward(msg);
|
|
1309
|
+
settleError(msg.error);
|
|
1310
|
+
}));
|
|
1311
|
+
const onClose = (_code, reason) => {
|
|
1312
|
+
if (isSettled) return;
|
|
1313
|
+
if (reason === "suspended") {
|
|
1314
|
+
settleSuccess();
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
settleError(/* @__PURE__ */ new Error("OpenCode bridge closed before the turn finished."));
|
|
1318
|
+
};
|
|
1319
|
+
channel.onClose(onClose);
|
|
1320
|
+
const onAbort = () => {
|
|
1321
|
+
if (isSettled) return;
|
|
1322
|
+
try {
|
|
1323
|
+
channel.send({ type: "abort" });
|
|
1324
|
+
} catch {}
|
|
1325
|
+
settleError(turnOpts.abortSignal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
1326
|
+
};
|
|
1327
|
+
if (turnOpts.abortSignal) {
|
|
1328
|
+
if (turnOpts.abortSignal.aborted) onAbort();
|
|
1329
|
+
else turnOpts.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
1330
|
+
}
|
|
1331
|
+
while (pendingCompactionParts.length > 0) forward(pendingCompactionParts.shift());
|
|
1332
|
+
return {
|
|
1333
|
+
submitToolResult: async (input) => {
|
|
1334
|
+
channel.send({
|
|
1335
|
+
type: "tool-result",
|
|
1336
|
+
toolCallId: input.toolCallId,
|
|
1337
|
+
output: input.output,
|
|
1338
|
+
isError: input.isError,
|
|
1339
|
+
...input.toolResult !== void 0 ? { toolResult: input.toolResult } : {}
|
|
1340
|
+
});
|
|
1341
|
+
},
|
|
1342
|
+
submitToolApproval: async (input) => {
|
|
1343
|
+
channel.send({
|
|
1344
|
+
type: "tool-approval-response",
|
|
1345
|
+
approvalId: input.approvalId,
|
|
1346
|
+
approved: input.approved,
|
|
1347
|
+
reason: input.reason
|
|
1348
|
+
});
|
|
1349
|
+
},
|
|
1350
|
+
...userMessageSubmitter == null ? {} : { submitUserMessage: async (text) => {
|
|
1351
|
+
await userMessageSubmitter.submit(text);
|
|
1352
|
+
} },
|
|
1353
|
+
done
|
|
1354
|
+
};
|
|
1355
|
+
};
|
|
1356
|
+
const startBase = (turnModel) => ({
|
|
1357
|
+
model: transformModel == null ? turnModel : transformModel(turnModel),
|
|
1358
|
+
provider,
|
|
1359
|
+
...reasoningVariant ? { variant: reasoningVariant } : {},
|
|
1360
|
+
...openCodeConfig == null ? {} : { openCodeConfig },
|
|
1361
|
+
...mcpServers == null ? {} : { mcpServers },
|
|
1362
|
+
...headers == null ? {} : { headers },
|
|
1363
|
+
...permissionMode ? { permissionMode } : {},
|
|
1364
|
+
...builtinToolFiltering ? { builtinToolFiltering } : {},
|
|
1365
|
+
...pendingResumeSessionId ? { resumeSessionId: pendingResumeSessionId } : latestOpenCodeSessionId ? { resumeSessionId: latestOpenCodeSessionId } : {},
|
|
1366
|
+
...debug ? { debug } : {}
|
|
1367
|
+
});
|
|
1368
|
+
const prepareTurn = async (opts) => {
|
|
1369
|
+
if (opts.responseFormat?.type === "json" && opts.responseFormat.schema == null) throw new HarnessCapabilityUnsupportedError({
|
|
1370
|
+
message: "Harness 'opencode' requires a JSON schema for structured output.",
|
|
1371
|
+
harnessId: "opencode"
|
|
1372
|
+
});
|
|
1373
|
+
const skillWriteResult = await writeSkills({
|
|
1374
|
+
sandbox,
|
|
1375
|
+
homePath: sandboxHomeDir,
|
|
1376
|
+
skillsDir: ".agents/skills",
|
|
1377
|
+
skills: opts.skills,
|
|
1378
|
+
abortSignal: opts.abortSignal,
|
|
1379
|
+
invalidSkillNameMessage: ({ name }) => `Invalid OpenCode skill name: ${name}`,
|
|
1380
|
+
invalidSkillFilePathMessage: ({ skillName, filePath }) => `Invalid OpenCode skill file path for ${skillName}: ${filePath}`
|
|
1381
|
+
});
|
|
1382
|
+
return {
|
|
1383
|
+
control: wireTurn({
|
|
1384
|
+
emit: opts.emit,
|
|
1385
|
+
abortSignal: opts.abortSignal
|
|
1386
|
+
}),
|
|
1387
|
+
skillWriteResult
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
return {
|
|
1391
|
+
sessionId,
|
|
1392
|
+
isResume,
|
|
1393
|
+
doPromptTurn: async (promptOpts) => {
|
|
1394
|
+
const { control, skillWriteResult } = await prepareTurn(promptOpts);
|
|
1395
|
+
const turnModel = promptOpts.model ?? selectedModel;
|
|
1396
|
+
if (turnModel) selectedModel = turnModel;
|
|
1397
|
+
channel.send({
|
|
1398
|
+
type: "start",
|
|
1399
|
+
operation: "prompt",
|
|
1400
|
+
prompt: extractUserText(promptOpts.prompt),
|
|
1401
|
+
tools: (promptOpts.tools ?? []).map((t) => ({
|
|
1402
|
+
name: t.name,
|
|
1403
|
+
description: t.description,
|
|
1404
|
+
inputSchema: t.inputSchema
|
|
1405
|
+
})),
|
|
1406
|
+
...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
|
|
1407
|
+
...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
|
|
1408
|
+
skillsChanged: skillWriteResult.changed,
|
|
1409
|
+
...startBase(turnModel)
|
|
1410
|
+
});
|
|
1411
|
+
pendingResumeSessionId = void 0;
|
|
1412
|
+
return control;
|
|
1413
|
+
},
|
|
1414
|
+
doContinueTurn: async (continueOpts) => {
|
|
1415
|
+
const { control, skillWriteResult } = await prepareTurn(continueOpts);
|
|
1416
|
+
if (rerunContinue) {
|
|
1417
|
+
const turnModel = continueOpts.model ?? selectedModel;
|
|
1418
|
+
if (turnModel) selectedModel = turnModel;
|
|
1419
|
+
channel.send({
|
|
1420
|
+
type: "start",
|
|
1421
|
+
operation: "prompt",
|
|
1422
|
+
prompt: "Continue.",
|
|
1423
|
+
tools: (continueOpts.tools ?? []).map((t) => ({
|
|
1424
|
+
name: t.name,
|
|
1425
|
+
description: t.description,
|
|
1426
|
+
inputSchema: t.inputSchema
|
|
1427
|
+
})),
|
|
1428
|
+
...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
|
|
1429
|
+
...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
|
|
1430
|
+
skillsChanged: skillWriteResult.changed,
|
|
1431
|
+
...startBase(turnModel)
|
|
1432
|
+
});
|
|
1433
|
+
pendingResumeSessionId = void 0;
|
|
1434
|
+
}
|
|
1435
|
+
return control;
|
|
1436
|
+
},
|
|
1437
|
+
doCompact: async (customInstructions) => {
|
|
1438
|
+
if (customInstructions?.trim()) throw new HarnessCapabilityUnsupportedError({
|
|
1439
|
+
harnessId: "opencode",
|
|
1440
|
+
message: "Harness 'opencode' supports native manual compaction, but OpenCode does not expose custom compaction instructions through the supported API."
|
|
1441
|
+
});
|
|
1442
|
+
if (activeTurn) throw new HarnessCapabilityUnsupportedError({
|
|
1443
|
+
harnessId: "opencode",
|
|
1444
|
+
message: "Harness 'opencode' supports manual compaction between turns; compacting during an active turn is not supported by the bridge transport."
|
|
1445
|
+
});
|
|
1446
|
+
await runCompactOperation({
|
|
1447
|
+
channel,
|
|
1448
|
+
model: transformModel == null ? selectedModel : transformModel(selectedModel),
|
|
1449
|
+
provider,
|
|
1450
|
+
permissionMode,
|
|
1451
|
+
debug,
|
|
1452
|
+
openCodeConfig,
|
|
1453
|
+
mcpServers,
|
|
1454
|
+
headers,
|
|
1455
|
+
resumeSessionId: latestOpenCodeSessionId,
|
|
1456
|
+
onCompaction: (part) => pendingCompactionParts.push(part)
|
|
1457
|
+
});
|
|
1458
|
+
},
|
|
1459
|
+
doDetach: async () => {
|
|
1460
|
+
if (stopped) throw new Error(`OpenCode session ${sessionId} is already stopped; cannot detach.`);
|
|
1461
|
+
stopped = true;
|
|
1462
|
+
const lastSeenEventId = await channel.suspend();
|
|
1463
|
+
return {
|
|
1464
|
+
type: "resume-session",
|
|
1465
|
+
harnessId: "opencode",
|
|
1466
|
+
specificationVersion: "harness-v1",
|
|
1467
|
+
data: {
|
|
1468
|
+
...latestOpenCodeSessionId ? { openCodeSessionId: latestOpenCodeSessionId } : {},
|
|
1469
|
+
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
|
|
1470
|
+
bridge: {
|
|
1471
|
+
port: bridgePort,
|
|
1472
|
+
token: bridgeToken,
|
|
1473
|
+
lastSeenEventId,
|
|
1474
|
+
...sandboxId == null ? {} : { sandboxId }
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
},
|
|
1479
|
+
doDestroy: async () => {
|
|
1480
|
+
if (stopped) return stopPromise;
|
|
1481
|
+
stopped = true;
|
|
1482
|
+
stopPromise = (async () => {
|
|
1483
|
+
channel.beginClose();
|
|
1484
|
+
try {
|
|
1485
|
+
if (!channel.isClosed()) channel.send({ type: "destroy" });
|
|
1486
|
+
} catch {}
|
|
1487
|
+
let stopTimer;
|
|
1488
|
+
try {
|
|
1489
|
+
if (proc) await Promise.race([proc.wait(), new Promise((resolve) => {
|
|
1490
|
+
stopTimer = setTimeout(resolve, 5e3);
|
|
1491
|
+
stopTimer.unref?.();
|
|
1492
|
+
})]);
|
|
1493
|
+
} finally {
|
|
1494
|
+
if (stopTimer) clearTimeout(stopTimer);
|
|
1495
|
+
try {
|
|
1496
|
+
await proc?.kill();
|
|
1497
|
+
} catch {}
|
|
1498
|
+
channel.close();
|
|
1499
|
+
}
|
|
1500
|
+
})();
|
|
1501
|
+
return stopPromise;
|
|
1502
|
+
},
|
|
1503
|
+
doStop: async () => {
|
|
1504
|
+
if (stopped) throw new Error(`OpenCode session ${sessionId} is already stopped; cannot stop.`);
|
|
1505
|
+
stopped = true;
|
|
1506
|
+
channel.beginClose();
|
|
1507
|
+
const data = channel.isClosed() ? {} : await new Promise((resolve, reject) => {
|
|
1508
|
+
const timer = setTimeout(() => {
|
|
1509
|
+
unsub();
|
|
1510
|
+
reject(/* @__PURE__ */ new Error(`OpenCode session ${sessionId} did not reply to stop within 5s.`));
|
|
1511
|
+
}, 5e3);
|
|
1512
|
+
timer.unref?.();
|
|
1513
|
+
const unsub = channel.on("bridge-stop", (msg) => {
|
|
1514
|
+
clearTimeout(timer);
|
|
1515
|
+
unsub();
|
|
1516
|
+
resolve(msg.data);
|
|
1517
|
+
});
|
|
1518
|
+
try {
|
|
1519
|
+
channel.send({ type: "stop" });
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
clearTimeout(timer);
|
|
1522
|
+
unsub();
|
|
1523
|
+
reject(err);
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
let stopTimer;
|
|
1527
|
+
try {
|
|
1528
|
+
if (proc) await Promise.race([proc.wait(), new Promise((resolve) => {
|
|
1529
|
+
stopTimer = setTimeout(resolve, 5e3);
|
|
1530
|
+
stopTimer.unref?.();
|
|
1531
|
+
})]);
|
|
1532
|
+
} finally {
|
|
1533
|
+
if (stopTimer) clearTimeout(stopTimer);
|
|
1534
|
+
try {
|
|
1535
|
+
await proc?.kill();
|
|
1536
|
+
} catch {}
|
|
1537
|
+
channel.close();
|
|
1538
|
+
}
|
|
1539
|
+
return {
|
|
1540
|
+
type: "resume-session",
|
|
1541
|
+
harnessId: "opencode",
|
|
1542
|
+
specificationVersion: "harness-v1",
|
|
1543
|
+
data: {
|
|
1544
|
+
...data != null && typeof data === "object" && !Array.isArray(data) ? { ...data } : {},
|
|
1545
|
+
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
},
|
|
1549
|
+
doSuspendTurn: async () => {
|
|
1550
|
+
if (stopped) throw new Error(`OpenCode session ${sessionId} is stopped; cannot suspend.`);
|
|
1551
|
+
stopped = true;
|
|
1552
|
+
const lastSeenEventId = await channel.suspend();
|
|
1553
|
+
return {
|
|
1554
|
+
type: "continue-turn",
|
|
1555
|
+
harnessId: "opencode",
|
|
1556
|
+
specificationVersion: "harness-v1",
|
|
1557
|
+
data: {
|
|
1558
|
+
...latestOpenCodeSessionId ? { openCodeSessionId: latestOpenCodeSessionId } : {},
|
|
1559
|
+
...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
|
|
1560
|
+
bridge: {
|
|
1561
|
+
port: bridgePort,
|
|
1562
|
+
token: bridgeToken,
|
|
1563
|
+
lastSeenEventId,
|
|
1564
|
+
...sandboxId == null ? {} : { sandboxId }
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1994
1570
|
}
|
|
1995
|
-
async function runCompactOperation({
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
tools: [],
|
|
2029
|
-
model,
|
|
2030
|
-
provider,
|
|
2031
|
-
...openCodeConfig == null ? {} : { openCodeConfig },
|
|
2032
|
-
...mcpServers == null ? {} : { mcpServers },
|
|
2033
|
-
...headers == null ? {} : { headers },
|
|
2034
|
-
...permissionMode ? { permissionMode } : {},
|
|
2035
|
-
...resumeSessionId ? { resumeSessionId } : {},
|
|
2036
|
-
...debug ? { debug } : {}
|
|
2037
|
-
});
|
|
2038
|
-
await done;
|
|
1571
|
+
async function runCompactOperation({ channel, model, provider, permissionMode, debug, openCodeConfig, mcpServers, headers, resumeSessionId, onCompaction }) {
|
|
1572
|
+
let pendingResolve;
|
|
1573
|
+
let pendingReject;
|
|
1574
|
+
const done = new Promise((resolve, reject) => {
|
|
1575
|
+
pendingResolve = resolve;
|
|
1576
|
+
pendingReject = reject;
|
|
1577
|
+
});
|
|
1578
|
+
const unsubs = [
|
|
1579
|
+
channel.on("compaction", (msg) => onCompaction(msg)),
|
|
1580
|
+
channel.on("finish", () => {
|
|
1581
|
+
for (const u of unsubs) u();
|
|
1582
|
+
pendingResolve();
|
|
1583
|
+
}),
|
|
1584
|
+
channel.on("error", (msg) => {
|
|
1585
|
+
for (const u of unsubs) u();
|
|
1586
|
+
pendingReject(msg.error);
|
|
1587
|
+
})
|
|
1588
|
+
];
|
|
1589
|
+
channel.send({
|
|
1590
|
+
type: "start",
|
|
1591
|
+
operation: "compact",
|
|
1592
|
+
prompt: "",
|
|
1593
|
+
tools: [],
|
|
1594
|
+
model,
|
|
1595
|
+
provider,
|
|
1596
|
+
...openCodeConfig == null ? {} : { openCodeConfig },
|
|
1597
|
+
...mcpServers == null ? {} : { mcpServers },
|
|
1598
|
+
...headers == null ? {} : { headers },
|
|
1599
|
+
...permissionMode ? { permissionMode } : {},
|
|
1600
|
+
...resumeSessionId ? { resumeSessionId } : {},
|
|
1601
|
+
...debug ? { debug } : {}
|
|
1602
|
+
});
|
|
1603
|
+
await done;
|
|
2039
1604
|
}
|
|
2040
1605
|
function extractUserText(prompt) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
}
|
|
2054
|
-
return parts.join("\n\n");
|
|
1606
|
+
if (typeof prompt === "string") return prompt;
|
|
1607
|
+
const { content } = prompt;
|
|
1608
|
+
if (typeof content === "string") return content;
|
|
1609
|
+
const parts = [];
|
|
1610
|
+
for (const part of content) {
|
|
1611
|
+
if (part.type !== "text") throw new HarnessCapabilityUnsupportedError({
|
|
1612
|
+
harnessId: "opencode",
|
|
1613
|
+
message: `The OpenCode harness does not yet support user message parts of type '${part.type}'. Pass a string or a user message whose content contains only text parts.`
|
|
1614
|
+
});
|
|
1615
|
+
parts.push(part.text);
|
|
1616
|
+
}
|
|
1617
|
+
return parts.join("\n\n");
|
|
2055
1618
|
}
|
|
1619
|
+
//#endregion
|
|
1620
|
+
//#region src/index.ts
|
|
1621
|
+
/**
|
|
1622
|
+
* Default `openCode` harness instance with no overrides. Equivalent to
|
|
1623
|
+
* `createOpenCode()`.
|
|
1624
|
+
*/
|
|
1625
|
+
const openCode = createOpenCode();
|
|
1626
|
+
//#endregion
|
|
1627
|
+
export { VERSION, createOpenCode, openCode };
|
|
2056
1628
|
|
|
2057
|
-
// src/index.ts
|
|
2058
|
-
var openCode = createOpenCode();
|
|
2059
|
-
export {
|
|
2060
|
-
VERSION,
|
|
2061
|
-
createOpenCode,
|
|
2062
|
-
openCode
|
|
2063
|
-
};
|
|
2064
1629
|
//# sourceMappingURL=index.js.map
|