@ai-sdk/harness-opencode 1.0.109 → 1.0.110
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 +14 -0
- package/dist/index.js +697 -34
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/opencode-auth.ts +152 -11
- package/src/opencode-harness.ts +102 -14
- package/src/opencode-subscription.ts +727 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "path";
|
|
|
3
3
|
import {
|
|
4
4
|
commonTool,
|
|
5
5
|
HARNESS_V1_BUILTIN_TOOLS,
|
|
6
|
-
HarnessCapabilityUnsupportedError,
|
|
6
|
+
HarnessCapabilityUnsupportedError as HarnessCapabilityUnsupportedError2,
|
|
7
7
|
harnessV1DiagnosticFromBridgeFrame
|
|
8
8
|
} from "@ai-sdk/harness";
|
|
9
9
|
import {
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
writeSkills
|
|
29
29
|
} from "@ai-sdk/harness/utils";
|
|
30
30
|
import {
|
|
31
|
-
safeParseJSON,
|
|
31
|
+
safeParseJSON as safeParseJSON2,
|
|
32
32
|
tool
|
|
33
33
|
} from "@ai-sdk/provider-utils";
|
|
34
34
|
import { WebSocket } from "ws";
|
|
@@ -84,11 +84,19 @@ import {
|
|
|
84
84
|
getAiGatewayAuthFromEnv,
|
|
85
85
|
isHarnessAuthenticationEnvironment
|
|
86
86
|
} from "@ai-sdk/harness/utils";
|
|
87
|
+
var OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE = "AI_SDK_OPENCODE_NATIVE_ACCESS_TOKEN";
|
|
87
88
|
var OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES = [
|
|
88
89
|
"AI_GATEWAY_API_KEY",
|
|
89
90
|
"OPENAI_API_KEY",
|
|
90
91
|
"ANTHROPIC_API_KEY",
|
|
91
|
-
"ANTHROPIC_AUTH_TOKEN"
|
|
92
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
93
|
+
"XAI_API_KEY",
|
|
94
|
+
"GITHUB_TOKEN",
|
|
95
|
+
"GITHUB_COPILOT_TOKEN",
|
|
96
|
+
"POE_API_KEY",
|
|
97
|
+
"OPENCODE_API_KEY",
|
|
98
|
+
"GITLAB_TOKEN",
|
|
99
|
+
OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE
|
|
92
100
|
];
|
|
93
101
|
function createOpenCodeRequestTransformations({
|
|
94
102
|
env: environment,
|
|
@@ -160,18 +168,53 @@ function createOpenCodeRequestTransformations({
|
|
|
160
168
|
}
|
|
161
169
|
return transformations;
|
|
162
170
|
}
|
|
171
|
+
case "xai":
|
|
172
|
+
return createBearerTransformation({
|
|
173
|
+
environment,
|
|
174
|
+
sandboxEnvironment,
|
|
175
|
+
environmentVariableName: "XAI_API_KEY",
|
|
176
|
+
matchUrl: environment.XAI_BASE_URL ?? "https://api.x.ai/v1"
|
|
177
|
+
});
|
|
178
|
+
case "github-copilot":
|
|
179
|
+
return createBearerTransformation({
|
|
180
|
+
environment,
|
|
181
|
+
sandboxEnvironment,
|
|
182
|
+
environmentVariableName: environment.GITHUB_COPILOT_TOKEN != null ? "GITHUB_COPILOT_TOKEN" : "GITHUB_TOKEN",
|
|
183
|
+
matchUrl: "https://api.githubcopilot.com"
|
|
184
|
+
});
|
|
185
|
+
case "poe":
|
|
186
|
+
return createBearerTransformation({
|
|
187
|
+
environment,
|
|
188
|
+
sandboxEnvironment,
|
|
189
|
+
environmentVariableName: "POE_API_KEY",
|
|
190
|
+
matchUrl: "https://api.poe.com"
|
|
191
|
+
});
|
|
192
|
+
case "opencode-go":
|
|
193
|
+
return createBearerTransformation({
|
|
194
|
+
environment,
|
|
195
|
+
sandboxEnvironment,
|
|
196
|
+
environmentVariableName: "OPENCODE_API_KEY",
|
|
197
|
+
matchUrl: "https://opencode.ai/zen/go/v1"
|
|
198
|
+
});
|
|
199
|
+
case "gitlab":
|
|
200
|
+
return createBearerTransformation({
|
|
201
|
+
environment,
|
|
202
|
+
sandboxEnvironment,
|
|
203
|
+
environmentVariableName: "GITLAB_TOKEN",
|
|
204
|
+
matchUrl: environment.GITLAB_INSTANCE_URL ?? "https://gitlab.com"
|
|
205
|
+
});
|
|
163
206
|
}
|
|
164
207
|
}
|
|
165
208
|
function resolveOpenCodeProvider({
|
|
166
209
|
model,
|
|
167
210
|
provider
|
|
168
211
|
}) {
|
|
169
|
-
if (provider
|
|
212
|
+
if (isDirectProvider(provider)) {
|
|
170
213
|
return provider;
|
|
171
214
|
}
|
|
172
215
|
if (model?.includes("/")) {
|
|
173
216
|
const [modelProvider] = model.split("/");
|
|
174
|
-
if (modelProvider
|
|
217
|
+
if (isDirectProvider(modelProvider)) {
|
|
175
218
|
return modelProvider;
|
|
176
219
|
}
|
|
177
220
|
}
|
|
@@ -186,11 +229,11 @@ function resolveOpenCodeEnv({
|
|
|
186
229
|
const suppliedEnvironment = isHarnessAuthenticationEnvironment(auth);
|
|
187
230
|
const authenticationEnvironment = suppliedEnvironment ? auth : processEnv;
|
|
188
231
|
const selectedProvider = resolveOpenCodeProvider({ model, provider });
|
|
189
|
-
if (selectedProvider === "openai" && auth === "openai") {
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
232
|
+
if (selectedProvider === "openai" && auth === "openai" || selectedProvider === "anthropic" && auth === "anthropic") {
|
|
233
|
+
return pickDirectProvider({
|
|
234
|
+
provider: selectedProvider,
|
|
235
|
+
processEnv: authenticationEnvironment
|
|
236
|
+
});
|
|
194
237
|
}
|
|
195
238
|
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({
|
|
196
239
|
env: authenticationEnvironment
|
|
@@ -198,7 +241,10 @@ function resolveOpenCodeEnv({
|
|
|
198
241
|
if (auth === "ai-gateway" || gatewayAuthFromEnv.apiKey) {
|
|
199
242
|
return pickGateway({ gatewayAuthFromEnv });
|
|
200
243
|
}
|
|
201
|
-
return
|
|
244
|
+
return pickDirectProvider({
|
|
245
|
+
provider: selectedProvider,
|
|
246
|
+
processEnv: authenticationEnvironment
|
|
247
|
+
});
|
|
202
248
|
}
|
|
203
249
|
function resolveOpenCodeAuthenticationMode({
|
|
204
250
|
auth,
|
|
@@ -250,6 +296,51 @@ function pickAnthropic({
|
|
|
250
296
|
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
|
|
251
297
|
return env;
|
|
252
298
|
}
|
|
299
|
+
function pickDirectProvider({
|
|
300
|
+
provider,
|
|
301
|
+
processEnv
|
|
302
|
+
}) {
|
|
303
|
+
if (provider === "openai") return pickOpenAI({ processEnv });
|
|
304
|
+
if (provider === "anthropic") return pickAnthropic({ processEnv });
|
|
305
|
+
const names = 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"];
|
|
306
|
+
return Object.fromEntries(
|
|
307
|
+
names.flatMap(
|
|
308
|
+
(name) => processEnv[name] == null ? [] : [[name, processEnv[name]]]
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
function hasOpenCodeCredential({
|
|
313
|
+
environment,
|
|
314
|
+
authenticationMode
|
|
315
|
+
}) {
|
|
316
|
+
if (authenticationMode === "ai-gateway") {
|
|
317
|
+
return environment.AI_GATEWAY_API_KEY != null;
|
|
318
|
+
}
|
|
319
|
+
return Object.keys(environment).some(
|
|
320
|
+
(name) => name.endsWith("_API_KEY") || name.endsWith("_TOKEN") || name === "ANTHROPIC_AUTH_TOKEN"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
function createBearerTransformation({
|
|
324
|
+
environment,
|
|
325
|
+
sandboxEnvironment,
|
|
326
|
+
environmentVariableName,
|
|
327
|
+
matchUrl
|
|
328
|
+
}) {
|
|
329
|
+
const credential = environment[environmentVariableName];
|
|
330
|
+
const sandboxCredential = sandboxEnvironment[environmentVariableName];
|
|
331
|
+
return credential == null || sandboxCredential == null ? [] : [
|
|
332
|
+
createCredentialRequestTransformation({
|
|
333
|
+
matchUrl,
|
|
334
|
+
matchHeaders: {
|
|
335
|
+
Authorization: `Bearer ${sandboxCredential}`
|
|
336
|
+
},
|
|
337
|
+
transformHeaders: { Authorization: `Bearer ${credential}` }
|
|
338
|
+
})
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
function isDirectProvider(value) {
|
|
342
|
+
return value === "anthropic" || value === "openai" || value === "xai" || value === "github-copilot" || value === "poe" || value === "opencode-go" || value === "gitlab";
|
|
343
|
+
}
|
|
253
344
|
function pickGateway({
|
|
254
345
|
gatewayAuthFromEnv
|
|
255
346
|
}) {
|
|
@@ -265,6 +356,533 @@ function toOpenCodeGatewayBaseUrl(baseUrl) {
|
|
|
265
356
|
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
266
357
|
}
|
|
267
358
|
|
|
359
|
+
// src/opencode-subscription.ts
|
|
360
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
361
|
+
import { homedir } from "os";
|
|
362
|
+
import { dirname, join } from "path";
|
|
363
|
+
import {
|
|
364
|
+
HarnessCapabilityUnsupportedError
|
|
365
|
+
} from "@ai-sdk/harness";
|
|
366
|
+
import {
|
|
367
|
+
createCredentialRequestTransformation as createCredentialRequestTransformation2,
|
|
368
|
+
isAccessTokenExpiringSoon,
|
|
369
|
+
isHarnessAuthenticationEnvironment as isHarnessAuthenticationEnvironment2,
|
|
370
|
+
isWindows,
|
|
371
|
+
parseJwtPayload,
|
|
372
|
+
refreshOAuthAccessToken
|
|
373
|
+
} from "@ai-sdk/harness/utils";
|
|
374
|
+
import { isRecord, safeParseJSON } from "@ai-sdk/provider-utils";
|
|
375
|
+
var OPENAI_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
376
|
+
var XAI_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
377
|
+
var GITLAB_CLIENT_ID = "1d89f9fdb23ee96d4e603201f6861dab6e143c5c3c00469a018a2d94bdc03d4e";
|
|
378
|
+
var GITLAB_AI_GATEWAY_URL = "https://cloud.gitlab.com";
|
|
379
|
+
var GITLAB_ANTHROPIC_PROVIDER_ID = "ai-sdk-gitlab-anthropic";
|
|
380
|
+
var GITLAB_OPENAI_CHAT_PROVIDER_ID = "ai-sdk-gitlab-openai-chat";
|
|
381
|
+
var GITLAB_OPENAI_RESPONSES_PROVIDER_ID = "ai-sdk-gitlab-openai-responses";
|
|
382
|
+
var OPEN_CODE_GITLAB_MODELS = {
|
|
383
|
+
"duo-chat-fable-5": {
|
|
384
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
385
|
+
modelId: "claude-fable-5"
|
|
386
|
+
},
|
|
387
|
+
"duo-chat-opus-5": {
|
|
388
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
389
|
+
modelId: "claude-opus-5"
|
|
390
|
+
},
|
|
391
|
+
"duo-chat-opus-4-8": {
|
|
392
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
393
|
+
modelId: "claude-opus-4-8"
|
|
394
|
+
},
|
|
395
|
+
"duo-chat-opus-4-7": {
|
|
396
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
397
|
+
modelId: "claude-opus-4-7"
|
|
398
|
+
},
|
|
399
|
+
"duo-chat-opus-4-6": {
|
|
400
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
401
|
+
modelId: "claude-opus-4-6"
|
|
402
|
+
},
|
|
403
|
+
"duo-chat-sonnet-5": {
|
|
404
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
405
|
+
modelId: "claude-sonnet-5"
|
|
406
|
+
},
|
|
407
|
+
"duo-chat-sonnet-4-6": {
|
|
408
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
409
|
+
modelId: "claude-sonnet-4-6"
|
|
410
|
+
},
|
|
411
|
+
"duo-chat-opus-4-5": {
|
|
412
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
413
|
+
modelId: "claude-opus-4-5-20251101"
|
|
414
|
+
},
|
|
415
|
+
"duo-chat-sonnet-4-5": {
|
|
416
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
417
|
+
modelId: "claude-sonnet-4-5-20250929"
|
|
418
|
+
},
|
|
419
|
+
"duo-chat-haiku-4-5": {
|
|
420
|
+
providerId: GITLAB_ANTHROPIC_PROVIDER_ID,
|
|
421
|
+
modelId: "claude-haiku-4-5-20251001"
|
|
422
|
+
},
|
|
423
|
+
"duo-chat-gpt-5-1": {
|
|
424
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
425
|
+
modelId: "gpt-5.1-2025-11-13"
|
|
426
|
+
},
|
|
427
|
+
"duo-chat-gpt-5-2": {
|
|
428
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
429
|
+
modelId: "gpt-5.2-2025-12-11"
|
|
430
|
+
},
|
|
431
|
+
"duo-chat-gpt-5-4": {
|
|
432
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
433
|
+
modelId: "gpt-5.4-2026-03-05"
|
|
434
|
+
},
|
|
435
|
+
"duo-chat-gpt-5-5": {
|
|
436
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
437
|
+
modelId: "gpt-5.5-2026-04-23"
|
|
438
|
+
},
|
|
439
|
+
"duo-chat-gpt-5-mini": {
|
|
440
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
441
|
+
modelId: "gpt-5-mini-2025-08-07"
|
|
442
|
+
},
|
|
443
|
+
"duo-chat-gpt-5-4-mini": {
|
|
444
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
445
|
+
modelId: "gpt-5.4-mini"
|
|
446
|
+
},
|
|
447
|
+
"duo-chat-gpt-5-4-nano": {
|
|
448
|
+
providerId: GITLAB_OPENAI_CHAT_PROVIDER_ID,
|
|
449
|
+
modelId: "gpt-5.4-nano"
|
|
450
|
+
},
|
|
451
|
+
"duo-chat-gpt-5-6-sol": {
|
|
452
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
453
|
+
modelId: "gpt-5.6-sol"
|
|
454
|
+
},
|
|
455
|
+
"duo-chat-gpt-5-6-terra": {
|
|
456
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
457
|
+
modelId: "gpt-5.6-terra"
|
|
458
|
+
},
|
|
459
|
+
"duo-chat-gpt-5-6-luna": {
|
|
460
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
461
|
+
modelId: "gpt-5.6-luna"
|
|
462
|
+
},
|
|
463
|
+
"duo-chat-gpt-5-codex": {
|
|
464
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
465
|
+
modelId: "gpt-5-codex"
|
|
466
|
+
},
|
|
467
|
+
"duo-chat-gpt-5-2-codex": {
|
|
468
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
469
|
+
modelId: "gpt-5.2-codex"
|
|
470
|
+
},
|
|
471
|
+
"duo-chat-gpt-5-3-codex": {
|
|
472
|
+
providerId: GITLAB_OPENAI_RESPONSES_PROVIDER_ID,
|
|
473
|
+
modelId: "gpt-5.3-codex"
|
|
474
|
+
},
|
|
475
|
+
"duo-workflow": { providerId: "workflow", modelId: "default" },
|
|
476
|
+
"duo-workflow-default": { providerId: "workflow", modelId: "default" },
|
|
477
|
+
"duo-workflow-sonnet-4-5": {
|
|
478
|
+
providerId: "workflow",
|
|
479
|
+
modelId: "anthropic/claude-sonnet-4-5-20250929"
|
|
480
|
+
},
|
|
481
|
+
"duo-workflow-sonnet-5": {
|
|
482
|
+
providerId: "workflow",
|
|
483
|
+
modelId: "claude_sonnet_5"
|
|
484
|
+
},
|
|
485
|
+
"duo-workflow-opus-5": {
|
|
486
|
+
providerId: "workflow",
|
|
487
|
+
modelId: "claude_opus_5"
|
|
488
|
+
},
|
|
489
|
+
"duo-workflow-sonnet-4-6": {
|
|
490
|
+
providerId: "workflow",
|
|
491
|
+
modelId: "claude_sonnet_4_6"
|
|
492
|
+
},
|
|
493
|
+
"duo-workflow-opus-4-5": {
|
|
494
|
+
providerId: "workflow",
|
|
495
|
+
modelId: "anthropic/claude-opus-4-5-20251101"
|
|
496
|
+
},
|
|
497
|
+
"duo-workflow-haiku-4-5": {
|
|
498
|
+
providerId: "workflow",
|
|
499
|
+
modelId: "claude_haiku_4_5_20251001"
|
|
500
|
+
},
|
|
501
|
+
"duo-workflow-opus-4-6": {
|
|
502
|
+
providerId: "workflow",
|
|
503
|
+
modelId: "claude_opus_4_6_20260205"
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
async function resolveOpenCodeAuthentication({
|
|
507
|
+
auth,
|
|
508
|
+
model,
|
|
509
|
+
provider,
|
|
510
|
+
processEnv = process.env,
|
|
511
|
+
readSubscription = readOpenCodeSubscription
|
|
512
|
+
}) {
|
|
513
|
+
const environment = resolveOpenCodeEnv({
|
|
514
|
+
auth,
|
|
515
|
+
model,
|
|
516
|
+
provider,
|
|
517
|
+
processEnv
|
|
518
|
+
});
|
|
519
|
+
const authenticationMode = resolveOpenCodeAuthenticationMode({
|
|
520
|
+
auth,
|
|
521
|
+
model,
|
|
522
|
+
provider,
|
|
523
|
+
processEnv
|
|
524
|
+
});
|
|
525
|
+
if (isHarnessAuthenticationEnvironment2(auth) || auth === "ai-gateway" || authenticationMode === "ai-gateway" || hasOpenCodeCredential({ environment, authenticationMode })) {
|
|
526
|
+
return { environment, authenticationMode };
|
|
527
|
+
}
|
|
528
|
+
const subscription = await readSubscription({
|
|
529
|
+
providerId: authenticationMode,
|
|
530
|
+
env: processEnv
|
|
531
|
+
});
|
|
532
|
+
return subscription == null ? { environment, authenticationMode } : {
|
|
533
|
+
environment: {
|
|
534
|
+
...environment,
|
|
535
|
+
[OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE]: subscription.accessToken
|
|
536
|
+
},
|
|
537
|
+
authenticationMode,
|
|
538
|
+
subscription
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
async function readOpenCodeSubscription({
|
|
542
|
+
providerId,
|
|
543
|
+
env = process.env,
|
|
544
|
+
homeDirectory = homedir(),
|
|
545
|
+
platform = process.platform,
|
|
546
|
+
fetch
|
|
547
|
+
}) {
|
|
548
|
+
if (!isSubscriptionProvider(providerId)) return void 0;
|
|
549
|
+
const stored = await readStore({ env, homeDirectory, platform });
|
|
550
|
+
if (stored == null) return void 0;
|
|
551
|
+
const candidate = stored.value[providerId];
|
|
552
|
+
if (!isRecord(candidate)) return void 0;
|
|
553
|
+
if (candidate.type === "api") {
|
|
554
|
+
return typeof candidate.key === "string" && candidate.key.length > 0 ? { providerId, accessToken: candidate.key } : void 0;
|
|
555
|
+
}
|
|
556
|
+
if (candidate.type !== "oauth") return void 0;
|
|
557
|
+
if (providerId === "opencode-go") return void 0;
|
|
558
|
+
const access = candidate.access;
|
|
559
|
+
const refresh = candidate.refresh;
|
|
560
|
+
const expires = candidate.expires;
|
|
561
|
+
if (typeof access !== "string" || typeof refresh !== "string" || typeof expires !== "number") {
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
564
|
+
if (providerId === "github-copilot") {
|
|
565
|
+
return {
|
|
566
|
+
providerId,
|
|
567
|
+
accessToken: refresh,
|
|
568
|
+
...typeof candidate.enterpriseUrl === "string" ? { enterpriseUrl: candidate.enterpriseUrl } : {}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
if (providerId === "poe") {
|
|
572
|
+
if (isAccessTokenExpiringSoon({ expiresAt: expires })) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
"OpenCode Poe subscription API key is expiring soon. Run OpenCode login again."
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
return { providerId, accessToken: access };
|
|
578
|
+
}
|
|
579
|
+
let accessToken = access;
|
|
580
|
+
let accountId = typeof candidate.accountId === "string" ? candidate.accountId : providerId === "openai" ? await extractOpenAIAccountId(access) : void 0;
|
|
581
|
+
if (isAccessTokenExpiringSoon({ expiresAt: expires })) {
|
|
582
|
+
const refreshSettings = resolveRefreshSettings({
|
|
583
|
+
providerId,
|
|
584
|
+
candidate,
|
|
585
|
+
env
|
|
586
|
+
});
|
|
587
|
+
const refreshed = await refreshOAuthAccessToken({
|
|
588
|
+
...refreshSettings,
|
|
589
|
+
refreshToken: refresh,
|
|
590
|
+
...fetch == null ? {} : { fetch }
|
|
591
|
+
});
|
|
592
|
+
accessToken = refreshed.accessToken;
|
|
593
|
+
if (providerId === "openai") {
|
|
594
|
+
accountId = await extractOpenAIAccountId(refreshed.accessToken) ?? accountId;
|
|
595
|
+
}
|
|
596
|
+
stored.value[providerId] = {
|
|
597
|
+
...candidate,
|
|
598
|
+
access: refreshed.accessToken,
|
|
599
|
+
refresh: refreshed.refreshToken ?? refresh,
|
|
600
|
+
expires: refreshed.expiresAt,
|
|
601
|
+
...accountId == null ? {} : { accountId }
|
|
602
|
+
};
|
|
603
|
+
await stored.write?.(stored.value);
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
providerId,
|
|
607
|
+
accessToken,
|
|
608
|
+
...accountId == null ? {} : { accountId },
|
|
609
|
+
...typeof candidate.enterpriseUrl === "string" ? { enterpriseUrl: candidate.enterpriseUrl } : {}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
async function requestOpenCodeGitLabDirectAccess({
|
|
613
|
+
accessToken,
|
|
614
|
+
instanceUrl = "https://gitlab.com",
|
|
615
|
+
aiGatewayUrl = GITLAB_AI_GATEWAY_URL,
|
|
616
|
+
fetch: fetchImplementation = globalThis.fetch
|
|
617
|
+
}) {
|
|
618
|
+
const response = await fetchImplementation(
|
|
619
|
+
`${instanceUrl.replace(/\/+$/, "")}/api/v4/ai/third_party_agents/direct_access`,
|
|
620
|
+
{
|
|
621
|
+
method: "POST",
|
|
622
|
+
headers: {
|
|
623
|
+
Authorization: `Bearer ${accessToken}`,
|
|
624
|
+
"Content-Type": "application/json"
|
|
625
|
+
},
|
|
626
|
+
body: JSON.stringify({
|
|
627
|
+
feature_flags: { DuoAgentPlatformNext: true }
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
if (!response.ok) {
|
|
632
|
+
throw new Error(
|
|
633
|
+
`OpenCode GitLab direct access request failed with status ${response.status}.`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
const parsed = await safeParseJSON({ text: await response.text() });
|
|
637
|
+
if (!parsed.success || !isRecord(parsed.value)) {
|
|
638
|
+
throw new Error("OpenCode GitLab direct access returned invalid JSON.");
|
|
639
|
+
}
|
|
640
|
+
const token = parsed.value.token;
|
|
641
|
+
const headers = parsed.value.headers;
|
|
642
|
+
if (typeof token !== "string" || token.length === 0 || !isRecord(headers) || Object.values(headers).some((value) => typeof value !== "string")) {
|
|
643
|
+
throw new Error(
|
|
644
|
+
"OpenCode GitLab direct access returned invalid credentials."
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
accessToken: token,
|
|
649
|
+
headers,
|
|
650
|
+
aiGatewayUrl: aiGatewayUrl.replace(/\/+$/, "")
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function createOpenCodeGitLabSubscriptionRequestTransformations({
|
|
654
|
+
directAccess,
|
|
655
|
+
sandboxAccessToken
|
|
656
|
+
}) {
|
|
657
|
+
const headers = Object.fromEntries(
|
|
658
|
+
Object.entries(directAccess.headers).filter(
|
|
659
|
+
([name]) => name.toLowerCase() !== "authorization" && name.toLowerCase() !== "x-api-key"
|
|
660
|
+
)
|
|
661
|
+
);
|
|
662
|
+
const transformHeaders = {
|
|
663
|
+
...headers,
|
|
664
|
+
Authorization: `Bearer ${directAccess.accessToken}`
|
|
665
|
+
};
|
|
666
|
+
return [
|
|
667
|
+
`${directAccess.aiGatewayUrl}/ai/v1/proxy/anthropic`,
|
|
668
|
+
`${directAccess.aiGatewayUrl}/ai/v1/proxy/openai/v1`
|
|
669
|
+
].map(
|
|
670
|
+
(matchUrl) => createCredentialRequestTransformation2({
|
|
671
|
+
matchUrl,
|
|
672
|
+
matchHeaders: {
|
|
673
|
+
Authorization: `Bearer ${sandboxAccessToken}`
|
|
674
|
+
},
|
|
675
|
+
transformHeaders
|
|
676
|
+
})
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
function createOpenCodeGitLabSubscriptionConfig({
|
|
680
|
+
openCodeConfig,
|
|
681
|
+
sandboxAccessToken,
|
|
682
|
+
aiGatewayUrl,
|
|
683
|
+
headers
|
|
684
|
+
}) {
|
|
685
|
+
const providerOptions = headers == null ? {} : { headers };
|
|
686
|
+
const providers = {
|
|
687
|
+
[GITLAB_ANTHROPIC_PROVIDER_ID]: {
|
|
688
|
+
name: "GitLab Anthropic",
|
|
689
|
+
npm: "@ai-sdk/anthropic",
|
|
690
|
+
api: `${aiGatewayUrl}/ai/v1/proxy/anthropic/v1`,
|
|
691
|
+
options: { authToken: sandboxAccessToken, ...providerOptions },
|
|
692
|
+
models: createOpenCodeGitLabModels(GITLAB_ANTHROPIC_PROVIDER_ID)
|
|
693
|
+
},
|
|
694
|
+
[GITLAB_OPENAI_CHAT_PROVIDER_ID]: {
|
|
695
|
+
name: "GitLab OpenAI Chat",
|
|
696
|
+
npm: "@ai-sdk/openai-compatible",
|
|
697
|
+
api: `${aiGatewayUrl}/ai/v1/proxy/openai/v1`,
|
|
698
|
+
options: { apiKey: sandboxAccessToken, ...providerOptions },
|
|
699
|
+
models: createOpenCodeGitLabModels(GITLAB_OPENAI_CHAT_PROVIDER_ID)
|
|
700
|
+
},
|
|
701
|
+
[GITLAB_OPENAI_RESPONSES_PROVIDER_ID]: {
|
|
702
|
+
name: "GitLab OpenAI Responses",
|
|
703
|
+
npm: "@ai-sdk/openai",
|
|
704
|
+
api: `${aiGatewayUrl}/ai/v1/proxy/openai/v1`,
|
|
705
|
+
options: { apiKey: sandboxAccessToken, ...providerOptions },
|
|
706
|
+
models: createOpenCodeGitLabModels(GITLAB_OPENAI_RESPONSES_PROVIDER_ID)
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const configuredProviders = isRecord(openCodeConfig?.provider) ? openCodeConfig.provider : {};
|
|
710
|
+
return {
|
|
711
|
+
...openCodeConfig,
|
|
712
|
+
provider: { ...configuredProviders, ...providers }
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function resolveOpenCodeGitLabSubscriptionModel({
|
|
716
|
+
model,
|
|
717
|
+
provider
|
|
718
|
+
}) {
|
|
719
|
+
if (model == null) {
|
|
720
|
+
if (provider === "gitlab") {
|
|
721
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
722
|
+
harnessId: "opencode",
|
|
723
|
+
message: "Harness 'opencode' requires an explicit GitLab model when native GitLab subscription credentials are brokered."
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
return void 0;
|
|
727
|
+
}
|
|
728
|
+
const modelProvider = model.includes("/") ? model.split("/")[0] : provider;
|
|
729
|
+
if (modelProvider !== "gitlab") return model;
|
|
730
|
+
const modelId = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
|
|
731
|
+
const mapping = OPEN_CODE_GITLAB_MODELS[modelId];
|
|
732
|
+
if (mapping == null) {
|
|
733
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
734
|
+
harnessId: "opencode",
|
|
735
|
+
message: `Harness 'opencode' cannot broker native GitLab subscription credentials for unknown model '${modelId}'.`
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
if (mapping.providerId === "workflow") {
|
|
739
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
740
|
+
harnessId: "opencode",
|
|
741
|
+
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.`
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
return `${mapping.providerId}/${modelId}`;
|
|
745
|
+
}
|
|
746
|
+
function createOpenCodeGitLabModels(providerId) {
|
|
747
|
+
return Object.fromEntries(
|
|
748
|
+
Object.entries(OPEN_CODE_GITLAB_MODELS).flatMap(
|
|
749
|
+
([id, model]) => model.providerId === providerId ? [[id, { id: model.modelId, name: id }]] : []
|
|
750
|
+
)
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
function createOpenCodeSubscriptionAuthContent({
|
|
754
|
+
authentication,
|
|
755
|
+
accessToken
|
|
756
|
+
}) {
|
|
757
|
+
const { providerId } = authentication;
|
|
758
|
+
const value = providerId === "opencode-go" ? { type: "api", key: accessToken } : {
|
|
759
|
+
type: "oauth",
|
|
760
|
+
access: accessToken,
|
|
761
|
+
refresh: providerId === "github-copilot" ? accessToken : "host-managed",
|
|
762
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
763
|
+
...authentication.accountId == null ? {} : { accountId: authentication.accountId },
|
|
764
|
+
...authentication.enterpriseUrl == null ? {} : { enterpriseUrl: authentication.enterpriseUrl }
|
|
765
|
+
};
|
|
766
|
+
return JSON.stringify({ [providerId]: value });
|
|
767
|
+
}
|
|
768
|
+
function createOpenCodeSubscriptionRequestTransformations({
|
|
769
|
+
authentication,
|
|
770
|
+
sandboxAccessToken
|
|
771
|
+
}) {
|
|
772
|
+
const matchUrl = resolveRequestUrl(authentication);
|
|
773
|
+
const transformHeaders = {
|
|
774
|
+
Authorization: `Bearer ${authentication.accessToken}`,
|
|
775
|
+
...authentication.providerId === "openai" && authentication.accountId ? { "ChatGPT-Account-Id": authentication.accountId } : {}
|
|
776
|
+
};
|
|
777
|
+
const transformations = [
|
|
778
|
+
createCredentialRequestTransformation2({
|
|
779
|
+
matchUrl,
|
|
780
|
+
matchHeaders: {
|
|
781
|
+
Authorization: `Bearer ${sandboxAccessToken}`
|
|
782
|
+
},
|
|
783
|
+
transformHeaders
|
|
784
|
+
})
|
|
785
|
+
];
|
|
786
|
+
if (authentication.providerId === "poe" || authentication.providerId === "opencode-go") {
|
|
787
|
+
transformations.push(
|
|
788
|
+
createCredentialRequestTransformation2({
|
|
789
|
+
matchUrl,
|
|
790
|
+
matchHeaders: { "x-api-key": sandboxAccessToken },
|
|
791
|
+
transformHeaders: { "x-api-key": authentication.accessToken }
|
|
792
|
+
})
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
return transformations;
|
|
796
|
+
}
|
|
797
|
+
function resolveRefreshSettings({
|
|
798
|
+
providerId,
|
|
799
|
+
candidate,
|
|
800
|
+
env
|
|
801
|
+
}) {
|
|
802
|
+
if (providerId === "openai") {
|
|
803
|
+
return {
|
|
804
|
+
tokenUrl: "https://auth.openai.com/oauth/token",
|
|
805
|
+
clientId: OPENAI_CLIENT_ID
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (providerId === "xai") {
|
|
809
|
+
return {
|
|
810
|
+
tokenUrl: "https://auth.x.ai/oauth2/token",
|
|
811
|
+
clientId: XAI_CLIENT_ID
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
const instanceUrl = (typeof candidate.enterpriseUrl === "string" ? candidate.enterpriseUrl : env.GITLAB_INSTANCE_URL) ?? "https://gitlab.com";
|
|
815
|
+
return {
|
|
816
|
+
tokenUrl: `${instanceUrl.replace(/\/+$/, "")}/oauth/token`,
|
|
817
|
+
clientId: env.GITLAB_OAUTH_CLIENT_ID ?? GITLAB_CLIENT_ID
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function resolveRequestUrl(authentication) {
|
|
821
|
+
switch (authentication.providerId) {
|
|
822
|
+
case "openai":
|
|
823
|
+
return "https://chatgpt.com/backend-api/codex";
|
|
824
|
+
case "xai":
|
|
825
|
+
return "https://api.x.ai/v1";
|
|
826
|
+
case "github-copilot":
|
|
827
|
+
return authentication.enterpriseUrl == null ? "https://api.githubcopilot.com" : `https://copilot-api.${normalizeDomain(authentication.enterpriseUrl)}`;
|
|
828
|
+
case "poe":
|
|
829
|
+
return "https://api.poe.com";
|
|
830
|
+
case "opencode-go":
|
|
831
|
+
return "https://opencode.ai/zen/go/v1";
|
|
832
|
+
case "gitlab":
|
|
833
|
+
return authentication.enterpriseUrl ?? "https://gitlab.com";
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function normalizeDomain(value) {
|
|
837
|
+
try {
|
|
838
|
+
return new URL(value.includes("://") ? value : `https://${value}`).host;
|
|
839
|
+
} catch {
|
|
840
|
+
return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
async function extractOpenAIAccountId(accessToken) {
|
|
844
|
+
const payload = await parseJwtPayload({ token: accessToken });
|
|
845
|
+
const auth = payload?.["https://api.openai.com/auth"];
|
|
846
|
+
if (!isRecord(auth)) return void 0;
|
|
847
|
+
const accountId = auth.chatgpt_account_id;
|
|
848
|
+
return typeof accountId === "string" ? accountId : void 0;
|
|
849
|
+
}
|
|
850
|
+
async function readStore({
|
|
851
|
+
env,
|
|
852
|
+
homeDirectory,
|
|
853
|
+
platform
|
|
854
|
+
}) {
|
|
855
|
+
if (env.OPENCODE_AUTH_CONTENT != null) {
|
|
856
|
+
const parsed2 = await safeParseJSON({ text: env.OPENCODE_AUTH_CONTENT });
|
|
857
|
+
return parsed2.success && isRecord(parsed2.value) ? { value: parsed2.value } : void 0;
|
|
858
|
+
}
|
|
859
|
+
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");
|
|
860
|
+
const text = await readFile(authPath, "utf8").catch(() => void 0);
|
|
861
|
+
if (text == null) return void 0;
|
|
862
|
+
const parsed = await safeParseJSON({ text });
|
|
863
|
+
if (!parsed.success || !isRecord(parsed.value)) return void 0;
|
|
864
|
+
return {
|
|
865
|
+
value: parsed.value,
|
|
866
|
+
write: (value) => writeStore({ authPath, value })
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
async function writeStore({
|
|
870
|
+
authPath,
|
|
871
|
+
value
|
|
872
|
+
}) {
|
|
873
|
+
await mkdir(dirname(authPath), { recursive: true });
|
|
874
|
+
const temporaryPath = `${authPath}.${process.pid}.tmp`;
|
|
875
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}
|
|
876
|
+
`, {
|
|
877
|
+
mode: 384
|
|
878
|
+
});
|
|
879
|
+
await chmod(temporaryPath, 384);
|
|
880
|
+
await rename(temporaryPath, authPath);
|
|
881
|
+
}
|
|
882
|
+
function isSubscriptionProvider(value) {
|
|
883
|
+
return value === "openai" || value === "xai" || value === "github-copilot" || value === "poe" || value === "opencode-go" || value === "gitlab";
|
|
884
|
+
}
|
|
885
|
+
|
|
268
886
|
// src/opencode-bridge-protocol.ts
|
|
269
887
|
import {
|
|
270
888
|
harnessV1BridgeInboundCommandSchemas,
|
|
@@ -291,7 +909,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
291
909
|
]);
|
|
292
910
|
|
|
293
911
|
// src/version.ts
|
|
294
|
-
var VERSION = true ? "1.0.
|
|
912
|
+
var VERSION = true ? "1.0.110" : "0.0.0-test";
|
|
295
913
|
|
|
296
914
|
// src/opencode-harness.ts
|
|
297
915
|
var OPENCODE_CLIENT_APP = `ai-sdk/harness-opencode/${VERSION}`;
|
|
@@ -433,7 +1051,7 @@ function createOpenCode(settings = {}) {
|
|
|
433
1051
|
portEndpoint: settings.portEndpoint
|
|
434
1052
|
});
|
|
435
1053
|
if (settings.mintBridgeToken != null && sandboxId == null) {
|
|
436
|
-
throw new
|
|
1054
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
437
1055
|
harnessId: "opencode",
|
|
438
1056
|
message: "The OpenCode harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id."
|
|
439
1057
|
});
|
|
@@ -448,14 +1066,15 @@ function createOpenCode(settings = {}) {
|
|
|
448
1066
|
const resumeData = isResume && typeof lifecycleState?.data === "object" ? lifecycleState.data : void 0;
|
|
449
1067
|
const resumeSessionId = typeof resumeData?.openCodeSessionId === "string" && resumeData.openCodeSessionId.length > 0 ? resumeData.openCodeSessionId : void 0;
|
|
450
1068
|
const coords = resumeData?.bridge;
|
|
451
|
-
const
|
|
452
|
-
auth: settings.auth,
|
|
453
|
-
provider: settings.provider
|
|
454
|
-
});
|
|
455
|
-
const resolvedAuthEnvironment = resolveOpenCodeEnv({
|
|
1069
|
+
const authentication = await resolveOpenCodeAuthentication({
|
|
456
1070
|
auth: settings.auth,
|
|
457
1071
|
provider: settings.provider
|
|
458
1072
|
});
|
|
1073
|
+
const authenticationMode = authentication.authenticationMode;
|
|
1074
|
+
const resolvedAuthEnvironment = authentication.environment;
|
|
1075
|
+
let resolvedOpenCodeConfig = settings.openCodeConfig;
|
|
1076
|
+
let transformModel;
|
|
1077
|
+
let gitLabSubscriptionBrokered = false;
|
|
459
1078
|
let sandboxAuthEnvironment = resolvedAuthEnvironment;
|
|
460
1079
|
let sandboxCredentialEnvironment;
|
|
461
1080
|
let credentialsBrokered = false;
|
|
@@ -469,11 +1088,40 @@ function createOpenCode(settings = {}) {
|
|
|
469
1088
|
...resolvedAuthEnvironment,
|
|
470
1089
|
...sandboxCredentialEnvironment
|
|
471
1090
|
};
|
|
472
|
-
const
|
|
1091
|
+
const transformationSources = {
|
|
473
1092
|
env: resolvedAuthEnvironment,
|
|
474
1093
|
sandboxEnv: sandboxAuthEnvironment,
|
|
475
1094
|
auth: authenticationMode
|
|
476
|
-
}
|
|
1095
|
+
};
|
|
1096
|
+
const sandboxSubscriptionAccessToken = sandboxAuthEnvironment[OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE];
|
|
1097
|
+
let requestTransformations;
|
|
1098
|
+
if (authentication.subscription?.providerId === "gitlab" && sandboxSubscriptionAccessToken != null) {
|
|
1099
|
+
const directAccess = await requestOpenCodeGitLabDirectAccess({
|
|
1100
|
+
accessToken: authentication.subscription.accessToken,
|
|
1101
|
+
...authentication.subscription.enterpriseUrl == null ? {} : { instanceUrl: authentication.subscription.enterpriseUrl },
|
|
1102
|
+
...process.env.GITLAB_AI_GATEWAY_URL == null ? {} : { aiGatewayUrl: process.env.GITLAB_AI_GATEWAY_URL }
|
|
1103
|
+
});
|
|
1104
|
+
requestTransformations = createOpenCodeGitLabSubscriptionRequestTransformations({
|
|
1105
|
+
directAccess,
|
|
1106
|
+
sandboxAccessToken: sandboxSubscriptionAccessToken
|
|
1107
|
+
});
|
|
1108
|
+
resolvedOpenCodeConfig = createOpenCodeGitLabSubscriptionConfig({
|
|
1109
|
+
openCodeConfig: settings.openCodeConfig,
|
|
1110
|
+
sandboxAccessToken: sandboxSubscriptionAccessToken,
|
|
1111
|
+
aiGatewayUrl: directAccess.aiGatewayUrl,
|
|
1112
|
+
...startOpts.headers == null ? {} : { headers: startOpts.headers }
|
|
1113
|
+
});
|
|
1114
|
+
transformModel = (model) => resolveOpenCodeGitLabSubscriptionModel({
|
|
1115
|
+
model,
|
|
1116
|
+
provider: settings.provider
|
|
1117
|
+
});
|
|
1118
|
+
gitLabSubscriptionBrokered = true;
|
|
1119
|
+
} else {
|
|
1120
|
+
requestTransformations = authentication.subscription == null || sandboxSubscriptionAccessToken == null ? createOpenCodeRequestTransformations(transformationSources) : createOpenCodeSubscriptionRequestTransformations({
|
|
1121
|
+
authentication: authentication.subscription,
|
|
1122
|
+
sandboxAccessToken: sandboxSubscriptionAccessToken
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
477
1125
|
if (requestTransformations.length > 0) {
|
|
478
1126
|
await sandboxSession.addRequestTransformations(
|
|
479
1127
|
requestTransformations
|
|
@@ -537,7 +1185,7 @@ function createOpenCode(settings = {}) {
|
|
|
537
1185
|
proc: void 0,
|
|
538
1186
|
provider: settings.provider,
|
|
539
1187
|
reasoningVariant: settings.reasoningVariant,
|
|
540
|
-
openCodeConfig:
|
|
1188
|
+
openCodeConfig: resolvedOpenCodeConfig,
|
|
541
1189
|
mcpServers: settings.mcpServers,
|
|
542
1190
|
headers: startOpts.headers,
|
|
543
1191
|
openCodeSessionId: resumeSessionId,
|
|
@@ -553,6 +1201,7 @@ function createOpenCode(settings = {}) {
|
|
|
553
1201
|
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
554
1202
|
sandbox: toolSafeSandboxSession,
|
|
555
1203
|
sandboxHomeDir,
|
|
1204
|
+
transformModel,
|
|
556
1205
|
supportsUserMessageResponses: () => supportsUserMessageResponses2
|
|
557
1206
|
});
|
|
558
1207
|
} catch {
|
|
@@ -587,8 +1236,20 @@ function createOpenCode(settings = {}) {
|
|
|
587
1236
|
credentialEnvironmentVariables: OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES
|
|
588
1237
|
});
|
|
589
1238
|
}
|
|
1239
|
+
const subscriptionAccessToken = forwardedAuthEnvironment[OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE];
|
|
1240
|
+
const forwardableAuthEnvironment = Object.fromEntries(
|
|
1241
|
+
Object.entries(forwardedAuthEnvironment).filter(
|
|
1242
|
+
([name]) => name !== OPENCODE_SUBSCRIPTION_ACCESS_TOKEN_ENVIRONMENT_VARIABLE
|
|
1243
|
+
)
|
|
1244
|
+
);
|
|
590
1245
|
const env = {
|
|
591
|
-
...
|
|
1246
|
+
...forwardableAuthEnvironment,
|
|
1247
|
+
...gitLabSubscriptionBrokered || authentication.subscription == null || subscriptionAccessToken == null ? {} : {
|
|
1248
|
+
OPENCODE_AUTH_CONTENT: createOpenCodeSubscriptionAuthContent({
|
|
1249
|
+
authentication: authentication.subscription,
|
|
1250
|
+
accessToken: subscriptionAccessToken
|
|
1251
|
+
})
|
|
1252
|
+
},
|
|
592
1253
|
AI_SDK_HARNESS_CLIENT_APP: OPENCODE_CLIENT_APP,
|
|
593
1254
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
594
1255
|
BRIDGE_WS_PORT: String(port),
|
|
@@ -670,7 +1331,7 @@ function createOpenCode(settings = {}) {
|
|
|
670
1331
|
proc,
|
|
671
1332
|
provider: settings.provider,
|
|
672
1333
|
reasoningVariant: settings.reasoningVariant,
|
|
673
|
-
openCodeConfig:
|
|
1334
|
+
openCodeConfig: resolvedOpenCodeConfig,
|
|
674
1335
|
mcpServers: settings.mcpServers,
|
|
675
1336
|
headers: startOpts.headers,
|
|
676
1337
|
openCodeSessionId: resumeSessionId,
|
|
@@ -686,6 +1347,7 @@ function createOpenCode(settings = {}) {
|
|
|
686
1347
|
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
687
1348
|
sandbox: toolSafeSandboxSession,
|
|
688
1349
|
sandboxHomeDir,
|
|
1350
|
+
transformModel,
|
|
689
1351
|
supportsUserMessageResponses: () => supportsUserMessageResponses
|
|
690
1352
|
});
|
|
691
1353
|
}
|
|
@@ -699,7 +1361,7 @@ function resolveBridgePort({
|
|
|
699
1361
|
if ("ports" in sandboxSession && sandboxSession.ports.length > 0) {
|
|
700
1362
|
return sandboxSession.ports[0];
|
|
701
1363
|
}
|
|
702
|
-
throw new
|
|
1364
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
703
1365
|
harnessId: "opencode",
|
|
704
1366
|
message: "The OpenCode harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createOpenCode({ port })`."
|
|
705
1367
|
});
|
|
@@ -711,13 +1373,13 @@ function validateBasicSandboxSettings({
|
|
|
711
1373
|
}) {
|
|
712
1374
|
if ("getPortEndpoint" in sandboxSession) return;
|
|
713
1375
|
if (port == null) {
|
|
714
|
-
throw new
|
|
1376
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
715
1377
|
harnessId: "opencode",
|
|
716
1378
|
message: "The OpenCode harness requires an explicit `port` when using a basic sandbox session."
|
|
717
1379
|
});
|
|
718
1380
|
}
|
|
719
1381
|
if (portEndpoint == null) {
|
|
720
|
-
throw new
|
|
1382
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
721
1383
|
harnessId: "opencode",
|
|
722
1384
|
message: "The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
723
1385
|
});
|
|
@@ -732,7 +1394,7 @@ async function resolveBridgeEndpoint({
|
|
|
732
1394
|
if ("getPortEndpoint" in sandboxSession) {
|
|
733
1395
|
return sandboxSession.getPortEndpoint({ port, protocol: "ws" });
|
|
734
1396
|
}
|
|
735
|
-
throw new
|
|
1397
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
736
1398
|
harnessId: "opencode",
|
|
737
1399
|
message: "The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session."
|
|
738
1400
|
});
|
|
@@ -775,7 +1437,7 @@ function openWebSocket({
|
|
|
775
1437
|
};
|
|
776
1438
|
const onMessage = (raw) => {
|
|
777
1439
|
void (async () => {
|
|
778
|
-
const parsed = await
|
|
1440
|
+
const parsed = await safeParseJSON2({
|
|
779
1441
|
text: webSocketMessageToString(raw)
|
|
780
1442
|
});
|
|
781
1443
|
if (!parsed.success) return;
|
|
@@ -842,6 +1504,7 @@ function createSession({
|
|
|
842
1504
|
builtinToolFiltering,
|
|
843
1505
|
sandbox,
|
|
844
1506
|
sandboxHomeDir,
|
|
1507
|
+
transformModel,
|
|
845
1508
|
supportsUserMessageResponses
|
|
846
1509
|
}) {
|
|
847
1510
|
let stopped = false;
|
|
@@ -986,7 +1649,7 @@ function createSession({
|
|
|
986
1649
|
};
|
|
987
1650
|
};
|
|
988
1651
|
const startBase = (turnModel) => ({
|
|
989
|
-
model: turnModel,
|
|
1652
|
+
model: transformModel == null ? turnModel : transformModel(turnModel),
|
|
990
1653
|
provider,
|
|
991
1654
|
...reasoningVariant ? { variant: reasoningVariant } : {},
|
|
992
1655
|
...openCodeConfig == null ? {} : { openCodeConfig },
|
|
@@ -999,7 +1662,7 @@ function createSession({
|
|
|
999
1662
|
});
|
|
1000
1663
|
const prepareTurn = async (opts) => {
|
|
1001
1664
|
if (opts.responseFormat?.type === "json" && opts.responseFormat.schema == null) {
|
|
1002
|
-
throw new
|
|
1665
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1003
1666
|
message: "Harness 'opencode' requires a JSON schema for structured output.",
|
|
1004
1667
|
harnessId: "opencode"
|
|
1005
1668
|
});
|
|
@@ -1068,20 +1731,20 @@ function createSession({
|
|
|
1068
1731
|
},
|
|
1069
1732
|
doCompact: async (customInstructions) => {
|
|
1070
1733
|
if (customInstructions?.trim()) {
|
|
1071
|
-
throw new
|
|
1734
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1072
1735
|
harnessId: "opencode",
|
|
1073
1736
|
message: "Harness 'opencode' supports native manual compaction, but OpenCode does not expose custom compaction instructions through the supported API."
|
|
1074
1737
|
});
|
|
1075
1738
|
}
|
|
1076
1739
|
if (activeTurn) {
|
|
1077
|
-
throw new
|
|
1740
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1078
1741
|
harnessId: "opencode",
|
|
1079
1742
|
message: "Harness 'opencode' supports manual compaction between turns; compacting during an active turn is not supported by the bridge transport."
|
|
1080
1743
|
});
|
|
1081
1744
|
}
|
|
1082
1745
|
await runCompactOperation({
|
|
1083
1746
|
channel,
|
|
1084
|
-
model: selectedModel,
|
|
1747
|
+
model: transformModel == null ? selectedModel : transformModel(selectedModel),
|
|
1085
1748
|
provider,
|
|
1086
1749
|
permissionMode,
|
|
1087
1750
|
debug,
|
|
@@ -1290,7 +1953,7 @@ function extractUserText(prompt) {
|
|
|
1290
1953
|
const parts = [];
|
|
1291
1954
|
for (const part of content) {
|
|
1292
1955
|
if (part.type !== "text") {
|
|
1293
|
-
throw new
|
|
1956
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
1294
1957
|
harnessId: "opencode",
|
|
1295
1958
|
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.`
|
|
1296
1959
|
});
|