@ai-sdk/harness-opencode 1.0.108 → 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/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 === "anthropic" || provider === "openai") {
212
+ if (isDirectProvider(provider)) {
170
213
  return provider;
171
214
  }
172
215
  if (model?.includes("/")) {
173
216
  const [modelProvider] = model.split("/");
174
- if (modelProvider === "anthropic" || modelProvider === "openai") {
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 pickOpenAI({ processEnv: authenticationEnvironment });
191
- }
192
- if (selectedProvider === "anthropic" && auth === "anthropic") {
193
- return pickAnthropic({ processEnv: authenticationEnvironment });
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 selectedProvider === "openai" ? pickOpenAI({ processEnv: authenticationEnvironment }) : pickAnthropic({ processEnv: authenticationEnvironment });
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.108" : "0.0.0-test";
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 HarnessCapabilityUnsupportedError({
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 authenticationMode = resolveOpenCodeAuthenticationMode({
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 requestTransformations = createOpenCodeRequestTransformations({
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: settings.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
- ...forwardedAuthEnvironment,
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: settings.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 HarnessCapabilityUnsupportedError({
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 HarnessCapabilityUnsupportedError({
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 HarnessCapabilityUnsupportedError({
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 HarnessCapabilityUnsupportedError({
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 safeParseJSON({
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 HarnessCapabilityUnsupportedError({
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 HarnessCapabilityUnsupportedError({
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 HarnessCapabilityUnsupportedError({
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 HarnessCapabilityUnsupportedError({
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
  });