swayrouter 1.0.2 → 1.0.4

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dashboard/dist/assets/{CliTools-Ch3gKuNw.js → CliTools-5DLXOz1A.js} +1 -1
  3. package/dashboard/dist/assets/{Combo-BBCldjWi.js → Combo-BCvuw7s2.js} +1 -1
  4. package/dashboard/dist/assets/{ConsoleLog-wywAYcPT.js → ConsoleLog-BwgJ9V8I.js} +4 -4
  5. package/dashboard/dist/assets/{Dashboard-DxzyF41b.js → Dashboard-B-tNs8a1.js} +1 -1
  6. package/dashboard/dist/assets/{Header-C_9Su9BF.js → Header-9KXz4S67.js} +1 -1
  7. package/dashboard/dist/assets/{Keys-D0ufgcsc.js → Keys-DE32cEsn.js} +1 -1
  8. package/dashboard/dist/assets/Login-z7dbRqrR.js +1 -0
  9. package/dashboard/dist/assets/{ManageApiKey-CVXsmW2B.js → ManageApiKey-DF3A1CXL.js} +1 -1
  10. package/dashboard/dist/assets/{OAuthCallback-D_UZYcF6.js → OAuthCallback-I8fzY5zi.js} +1 -1
  11. package/dashboard/dist/assets/Provider-Caq5lvsq.js +4 -0
  12. package/dashboard/dist/assets/{ProviderModelAccordion-BAifkfVR.js → ProviderModelAccordion-GZSOrZZu.js} +1 -1
  13. package/dashboard/dist/assets/{Proxy-CU15VJ--.js → Proxy-DJVhsf4E.js} +1 -1
  14. package/dashboard/dist/assets/QuotaMonitor-BA4GOxkV.js +1 -0
  15. package/dashboard/dist/assets/{Settings-Dt4y2dC0.js → Settings-eEpS37LF.js} +1 -1
  16. package/dashboard/dist/assets/{Setup-DSDUwU7N.js → Setup-D9G2dVUf.js} +1 -1
  17. package/dashboard/dist/assets/{StatusBadge-1nAOyHU2.js → StatusBadge-DClAYX7v.js} +1 -1
  18. package/dashboard/dist/assets/{SwayChat-Bup7up6D.js → SwayChat-DS0GBQKQ.js} +2 -2
  19. package/dashboard/dist/assets/Usage-CRQCq5jc.js +4 -0
  20. package/dashboard/dist/assets/{accordion-DOOPDDgt.js → accordion-BkSdf_dl.js} +1 -1
  21. package/dashboard/dist/assets/{admin-extras-api-CCdu5TFM.js → admin-extras-api-ppLkXcD9.js} +1 -1
  22. package/dashboard/dist/assets/{api-keys-api-ifAC2Ok3.js → api-keys-api-CsdHmg2W.js} +1 -1
  23. package/dashboard/dist/assets/{card-CC0Omjcv.js → card-BOVhvx7W.js} +1 -1
  24. package/dashboard/dist/assets/{checkbox-7SZBrmbN.js → checkbox-DU_0LpXJ.js} +1 -1
  25. package/dashboard/dist/assets/{connections-api-_N54UD3S.js → connections-api-CQHf0Hwe.js} +1 -1
  26. package/dashboard/dist/assets/{copy-button-qBvM6H9z.js → copy-button-nPFbNAXf.js} +1 -1
  27. package/dashboard/dist/assets/{currency-api-SV9CBf5e.js → currency-api-CDXu9UhW.js} +1 -1
  28. package/dashboard/dist/assets/{flip-button-BGgkjtl0.js → flip-button-C_PNv8Iq.js} +1 -1
  29. package/dashboard/dist/assets/{frame-cUiti6Lq.js → frame-BLwA8c3u.js} +1 -1
  30. package/dashboard/dist/assets/index-CN-Y5gO6.css +1 -0
  31. package/dashboard/dist/assets/index-x5tLD3Ye.js +10 -0
  32. package/dashboard/dist/assets/{ripple-button-l8aMiVVG.js → ripple-button-BV_KcZ7W.js} +1 -1
  33. package/dashboard/dist/assets/{segmented-CqmEnQjZ.js → segmented-BBSMiP-0.js} +1 -1
  34. package/dashboard/dist/assets/select-DELc8FMO.js +1 -0
  35. package/dashboard/dist/assets/{skeleton-7uJDiIXe.js → skeleton-CFSSp0e0.js} +1 -1
  36. package/dashboard/dist/assets/{table-D0M60Tu1.js → table-lcAgAODE.js} +1 -1
  37. package/dashboard/dist/assets/{tabs-C6YkuulG.js → tabs-CSBJRjaH.js} +1 -1
  38. package/dashboard/dist/assets/{tunnel-api-DDPsBaDV.js → tunnel-api-CdE1F_Ct.js} +1 -1
  39. package/dashboard/dist/index.html +2 -2
  40. package/package.json +1 -1
  41. package/src/executors/index.js +3 -2
  42. package/src/executors/opencode-go.js +46 -39
  43. package/src/executors/opencode.js +54 -31
  44. package/src/handlers/chatCore.js +3 -2
  45. package/src/providers/registry/index.js +5 -4
  46. package/src/providers/registry/opencode-go.js +16 -22
  47. package/src/providers/registry/opencode-zen.js +34 -0
  48. package/src/providers/registry/opencode.js +16 -12
  49. package/src/routes/api/providers/[id]/models/route.js +28 -0
  50. package/src/routes/api/providers/[id]/test/testUtils.js +15 -6
  51. package/src/routes/api/providers/catalog/route.js +3 -1
  52. package/src/routes/api/providers/suggested-models/filters.js +7 -2
  53. package/src/routes/api/providers/validate/route.js +14 -11
  54. package/src/types/provider.ts +1 -1
  55. package/src/utils/opencode.js +225 -0
  56. package/dashboard/dist/assets/Login-BeKpipS_.js +0 -1
  57. package/dashboard/dist/assets/Provider-BU7JfTqm.js +0 -4
  58. package/dashboard/dist/assets/QuotaMonitor-HzGK7bcc.js +0 -1
  59. package/dashboard/dist/assets/Usage-eWnrPQ_v.js +0 -4
  60. package/dashboard/dist/assets/index-B6j-9zHy.js +0 -10
  61. package/dashboard/dist/assets/index-C-_l0yYz.css +0 -1
  62. package/dashboard/dist/assets/select-D8pUtMXp.js +0 -1
@@ -34,8 +34,9 @@ const executors = {
34
34
  cu: new CursorExecutor(),
35
35
  vertex: new VertexExecutor("vertex"),
36
36
  "vertex-partner": new VertexExecutor("vertex-partner"),
37
- opencode: new OpenCodeExecutor(),
38
- "opencode-go": new OpenCodeGoExecutor(),
37
+ opencode: new OpenCodeExecutor(),
38
+ "opencode-zen": new OpenCodeExecutor("opencode-zen"),
39
+ "opencode-go": new OpenCodeGoExecutor(),
39
40
  "grok-web": new GrokWebExecutor(),
40
41
  "grok-cli": new GrokCliExecutor(),
41
42
  gcli: new GrokCliExecutor(),
@@ -1,47 +1,54 @@
1
1
  import { BaseExecutor } from "./base.js";
2
- import { PROVIDERS } from "../config/providers.js";
3
- import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
4
- import { ANTHROPIC_API_VERSION } from "../providers/shared.js";
5
-
6
- const MESSAGES_FORMAT_MODELS = new Set([
7
- "minimax-m3",
8
- "minimax-m2.7",
9
- "minimax-m2.5",
10
- "qwen3.7-max",
11
- "qwen3.7-plus",
12
- "qwen3.6-plus",
13
- ]);
2
+ import { PROVIDERS } from "../config/providers.js";
3
+ import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
4
+ import { applyOpenCodeFingerprint, buildOpenCodeHeaders } from "../utils/opencode.js";
5
+
6
+ const MESSAGES_FORMAT_MODELS = new Set([
7
+ "minimax-m3",
8
+ "minimax-m2.7",
9
+ "qwen3.7-max",
10
+ "claude-opus-5",
11
+ ]);
12
+ const RESPONSES_FORMAT_MODELS = new Set(["grok-4.5"]);
13
+ const modelId = (model) => typeof model === "string" ? model.split("/").pop() : model;
14
14
 
15
15
  const BASE = "https://opencode.ai/zen/go/v1";
16
16
 
17
- export class OpenCodeGoExecutor extends BaseExecutor {
17
+ export class OpenCodeGoExecutor extends BaseExecutor {
18
18
  constructor() {
19
19
  super("opencode-go", PROVIDERS["opencode-go"]);
20
- }
21
-
22
- buildUrl(model) {
23
- this._lastModel = model;
24
- return MESSAGES_FORMAT_MODELS.has(model)
20
+ }
21
+
22
+ resolveTargetFormat(model, currentFormat) {
23
+ const id = modelId(model);
24
+ if (RESPONSES_FORMAT_MODELS.has(id)) return "openai-responses";
25
+ return MESSAGES_FORMAT_MODELS.has(id) ? "claude" : currentFormat;
26
+ }
27
+
28
+ buildUrl(model) {
29
+ return MESSAGES_FORMAT_MODELS.has(modelId(model))
25
30
  ? `${BASE}/messages`
26
31
  : `${BASE}/chat/completions`;
27
- }
28
-
29
- buildHeaders(credentials, stream = true) {
30
- const key = credentials?.apiKey || credentials?.accessToken;
31
- const headers = { "Content-Type": "application/json" };
32
-
33
- if (MESSAGES_FORMAT_MODELS.has(this._lastModel)) {
34
- headers["x-api-key"] = key;
35
- headers["anthropic-version"] = ANTHROPIC_API_VERSION;
36
- } else {
37
- headers["Authorization"] = `Bearer ${key}`;
38
- }
39
-
40
- if (stream) headers["Accept"] = "text/event-stream";
41
- return headers;
42
- }
43
-
44
- transformRequest(model, body) {
45
- return injectReasoningContent({ provider: this.provider, model, body });
46
- }
47
- }
32
+ }
33
+
34
+ buildHeaders(credentials, stream = true, _url, model) {
35
+ const id = modelId(model);
36
+ const format = RESPONSES_FORMAT_MODELS.has(id)
37
+ ? "responses"
38
+ : MESSAGES_FORMAT_MODELS.has(id)
39
+ ? "claude"
40
+ : "openai";
41
+ return buildOpenCodeHeaders(credentials, stream, { authorization: "", format });
42
+ }
43
+
44
+ transformRequest(model, body) {
45
+ const id = modelId(model);
46
+ const format = RESPONSES_FORMAT_MODELS.has(id)
47
+ ? "responses"
48
+ : MESSAGES_FORMAT_MODELS.has(id)
49
+ ? "claude"
50
+ : "openai";
51
+ const fingerprinted = applyOpenCodeFingerprint({ ...body }, format);
52
+ return injectReasoningContent({ provider: this.provider, model, body: fingerprinted });
53
+ }
54
+ }
@@ -1,31 +1,54 @@
1
- import { BaseExecutor } from "./base.js";
2
- import { PROVIDERS } from "../config/providers.js";
3
- import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
4
-
5
- const MESSAGES_MODELS = new Set();
6
-
7
- export class OpenCodeExecutor extends BaseExecutor {
8
- constructor() {
9
- super("opencode", PROVIDERS.opencode);
10
- }
11
-
12
- transformRequest(model, body) {
13
- return injectReasoningContent({ provider: this.provider, model, body });
14
- }
15
-
16
- buildUrl(model) {
17
- const base = this.config.baseUrl;
18
- return MESSAGES_MODELS.has(model)
19
- ? `${base}/zen/v1/messages`
20
- : `${base}/zen/v1/chat/completions`;
21
- }
22
-
23
- buildHeaders() {
24
- return {
25
- "Content-Type": "application/json",
26
- "Authorization": "Bearer public",
27
- "x-opencode-client": "desktop",
28
- "Accept": "text/event-stream"
29
- };
30
- }
31
- }
1
+ import { BaseExecutor } from "./base.js";
2
+ import { PROVIDERS } from "../config/providers.js";
3
+ import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
4
+ import { applyOpenCodeFingerprint, buildOpenCodeHeaders } from "../utils/opencode.js";
5
+
6
+ const RESPONSES_MODELS = new Set([
7
+ "muse-spark-1.3-contributor-free",
8
+ "muse-spark-1.2-contributor-free",
9
+ ]);
10
+
11
+ const MESSAGES_MODELS = new Set(["claude-opus-5", "qwen3.7-max", "minimax-m3"]);
12
+ const modelId = (model) => typeof model === "string" ? model.split("/").pop() : model;
13
+
14
+ export class OpenCodeExecutor extends BaseExecutor {
15
+ constructor(provider = "opencode") {
16
+ super(provider, PROVIDERS[provider]);
17
+ }
18
+
19
+ resolveTargetFormat(model, currentFormat) {
20
+ const id = modelId(model);
21
+ if (RESPONSES_MODELS.has(id)) return "openai-responses";
22
+ if (MESSAGES_MODELS.has(id)) return "claude";
23
+ return currentFormat;
24
+ }
25
+
26
+ getModelFormat(model) {
27
+ const id = modelId(model);
28
+ if (RESPONSES_MODELS.has(id)) return "responses";
29
+ if (MESSAGES_MODELS.has(id)) return "claude";
30
+ return "openai";
31
+ }
32
+
33
+ transformRequest(model, body) {
34
+ const format = this.getModelFormat(model);
35
+ const fingerprinted = applyOpenCodeFingerprint({ ...body }, format, { forceStream: true });
36
+ return injectReasoningContent({ provider: this.provider, model, body: fingerprinted });
37
+ }
38
+
39
+ buildUrl(model) {
40
+ const id = modelId(model);
41
+ const base = this.config.baseUrl;
42
+ if (RESPONSES_MODELS.has(id)) return `${base}/zen/v1/responses`;
43
+ return MESSAGES_MODELS.has(id)
44
+ ? `${base}/zen/v1/messages`
45
+ : `${base}/zen/v1/chat/completions`;
46
+ }
47
+
48
+ buildHeaders(credentials, stream = true, _url, model) {
49
+ return buildOpenCodeHeaders(credentials, stream, {
50
+ authorization: "public",
51
+ format: this.getModelFormat(model),
52
+ });
53
+ }
54
+ }
@@ -83,7 +83,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
83
83
  const modelTargetFormat = getModelTargetFormat(alias, model);
84
84
 
85
85
  const runtimeTransport = resolveTransport(provider, sourceFormat);
86
- const targetFormat = modelTargetFormat || runtimeTransport?.format || getTargetFormat(provider, credentials);
86
+ const executor = getExecutor(provider);
87
+ const defaultTargetFormat = modelTargetFormat || runtimeTransport?.format || getTargetFormat(provider, credentials);
88
+ const targetFormat = executor.resolveTargetFormat?.(model, defaultTargetFormat) || defaultTargetFormat;
87
89
  if (runtimeTransport && credentials) credentials.runtimeTransport = runtimeTransport;
88
90
 
89
91
  const canonicalRequest = normalizeTranslationRequest({
@@ -308,7 +310,6 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
308
310
  log, provider, model, reqTag
309
311
  });
310
312
 
311
- const executor = getExecutor(provider);
312
313
  const executeOptions = {
313
314
  model, body: translatedBody, stream, credentials,
314
315
  signal: streamController.signal, log, proxyOptions,
@@ -43,9 +43,10 @@ import p41 from "./nvidia.js";
43
43
  import p42 from "./ollama-local.js";
44
44
  import p43 from "./ollama.js";
45
45
  import p44 from "./openai.js";
46
- import p45 from "./opencode-go.js";
47
- import p46 from "./opencode.js";
48
- import p47 from "./openrouter.js";
46
+ import p45 from "./opencode-go.js";
47
+ import p46 from "./opencode.js";
48
+ import p47 from "./opencode-zen.js";
49
+ import p48 from "./openrouter.js";
49
50
  import p50 from "./perplexity.js";
50
51
  import p51 from "./poolside.js";
51
52
  import p52 from "./qoder.js";
@@ -69,7 +70,7 @@ const REGISTRY = [
69
70
  p10, p11, p12, p13, p14, p15, p16, p17, p18, p19,
70
71
  p20, p21, p22, p23, p24, p25, p26, p27, p28, p29,
71
72
  p30, p31, p32, p33, p34, p35, p36, p37, p38, p39,
72
- p40, p41, p42, p43, p44, p45, p46, p47,
73
+ p40, p41, p42, p43, p44, p45, p46, p47, p48,
73
74
  p50, p51, p52, p53, p54, p55, p56, p57, p58, p59,
74
75
  p60, p61, p62, p63, p64, p65, p66,
75
76
  ];
@@ -1,4 +1,6 @@
1
- export default {
1
+ import { OPENCODE_GO_MODELS, OPENCODE_USER_AGENT } from "../../utils/opencode.js";
2
+
3
+ export default {
2
4
  id: "opencode-go",
3
5
  priority: 210,
4
6
  alias: "opencode-go",
@@ -18,24 +20,16 @@ export default {
18
20
  },
19
21
  },
20
22
  category: "apikey",
21
- transport: {
22
- baseUrl: "https://opencode.ai/zen/go/v1/chat/completions",
23
- headers: {},
24
- },
25
- models: [
26
- { id: "glm-5.2", name: "GLM 5.2" },
27
- { id: "glm-5.1", name: "GLM 5.1" },
28
- { id: "kimi-k2.7-code", name: "Kimi K2.7 Code" },
29
- { id: "kimi-k2.6", name: "Kimi K2.6" },
30
- { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro" },
31
- { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" },
32
- { id: "mimo-v2.5", name: "MiMo V2.5" },
33
- { id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro" },
34
- { id: "minimax-m3", name: "MiniMax M3", targetFormat: "claude" },
35
- { id: "minimax-m2.7", name: "MiniMax M2.7", targetFormat: "claude" },
36
- { id: "minimax-m2.5", name: "MiniMax M2.5", targetFormat: "claude" },
37
- { id: "qwen3.7-max", name: "Qwen 3.7 Max", targetFormat: "claude" },
38
- { id: "qwen3.7-plus", name: "Qwen 3.7 Plus", targetFormat: "claude" },
39
- { id: "qwen3.6-plus", name: "Qwen 3.6 Plus", targetFormat: "claude" },
40
- ],
41
- };
23
+ transport: {
24
+ baseUrl: "https://opencode.ai/zen/go/v1/chat/completions",
25
+ headers: {
26
+ "User-Agent": OPENCODE_USER_AGENT,
27
+ "x-opencode-client": "desktop",
28
+ },
29
+ },
30
+ models: OPENCODE_GO_MODELS,
31
+ modelsFetcher: {
32
+ url: "https://opencode.ai/zen/go/v1/models",
33
+ type: "openai",
34
+ },
35
+ };
@@ -0,0 +1,34 @@
1
+ import { OPENCODE_USER_AGENT } from "../../utils/opencode.js";
2
+
3
+ export default {
4
+ id: "opencode-zen",
5
+ priority: 205,
6
+ alias: "ocz",
7
+ uiAlias: "ocz",
8
+ display: {
9
+ name: "OpenCode Zen",
10
+ icon: "terminal",
11
+ color: "#E87040",
12
+ textIcon: "OC",
13
+ website: "https://opencode.ai/auth",
14
+ notice: {
15
+ text: "OpenCode Zen PAYG: connect an API key from your OpenCode workspace.",
16
+ apiKeyUrl: "https://opencode.ai/auth",
17
+ },
18
+ },
19
+ category: "apikey",
20
+ authType: "apikey",
21
+ authModes: ["apikey"],
22
+ transport: {
23
+ baseUrl: "https://opencode.ai",
24
+ headers: {
25
+ "User-Agent": OPENCODE_USER_AGENT,
26
+ "x-opencode-client": "desktop",
27
+ },
28
+ },
29
+ modelsFetcher: {
30
+ url: "https://opencode.ai/zen/v1/models",
31
+ type: "opencode-all",
32
+ },
33
+ passthroughModels: true,
34
+ };
@@ -1,4 +1,6 @@
1
- export default {
1
+ import { OPENCODE_FREE_MODELS, OPENCODE_USER_AGENT } from "../../utils/opencode.js";
2
+
3
+ export default {
2
4
  id: "opencode",
3
5
  priority: 40,
4
6
  hasFree: true,
@@ -13,14 +15,16 @@ export default {
13
15
  category: "none",
14
16
  authType: "none",
15
17
  noAuth: true,
16
- transport: {
17
- baseUrl: "https://opencode.ai",
18
- headers: {
19
- "x-opencode-client": "desktop",
20
- },
21
- noAuth: true,
22
- },
23
- models: [],
24
- modelsFetcher: { url: "https://opencode.ai/zen/v1/models", type: "opencode-free" },
25
- passthroughModels: true,
26
- };
18
+ transport: {
19
+ baseUrl: "https://opencode.ai",
20
+ headers: {
21
+ "x-opencode-client": "desktop",
22
+ "User-Agent": OPENCODE_USER_AGENT,
23
+ },
24
+ forceStream: true,
25
+ noAuth: true,
26
+ },
27
+ models: OPENCODE_FREE_MODELS,
28
+ modelsFetcher: { url: "https://opencode.ai/zen/v1/models", type: "opencode-free" },
29
+ passthroughModels: true,
30
+ };
@@ -14,6 +14,7 @@ import { resolveCodeBuddyModels } from "open-sse/services/codebuddyModels.js";
14
14
  import { getCapabilitiesForModel } from "open-sse/providers/capabilities.js";
15
15
  import { getThinkingLevels } from "open-sse/providers/thinkingLevels.js";
16
16
  import { AGENTROUTER_MODELS_URL, AGENTROUTER_OPENAI_HEADERS } from "open-sse/providers/shared.js";
17
+ import { OPENCODE_USER_AGENT } from "open-sse/utils/opencode.js";
17
18
  import { resolveConnectionProxyConfig } from "@/lib/network/connectionProxy.js";
18
19
  import { logRouteError } from "@/lib/errors/publicError";
19
20
 
@@ -244,6 +245,33 @@ const PROVIDER_MODELS_CONFIG = {
244
245
  "codebuddy-intl": {
245
246
  customResolver: async () => ({ models: getStaticProviderModels("codebuddy-intl") }),
246
247
  },
248
+ opencode: {
249
+ customResolver: async () => ({ models: getStaticProviderModels("opencode") }),
250
+ },
251
+ "opencode-zen": {
252
+ url: "https://opencode.ai/zen/v1/models",
253
+ method: "GET",
254
+ headers: {
255
+ "Content-Type": "application/json",
256
+ "User-Agent": OPENCODE_USER_AGENT,
257
+ "x-opencode-client": "desktop",
258
+ },
259
+ authHeader: "Authorization",
260
+ authPrefix: "Bearer ",
261
+ parseResponse: parseOpenAIStyleModels,
262
+ },
263
+ "opencode-go": {
264
+ url: "https://opencode.ai/zen/go/v1/models",
265
+ method: "GET",
266
+ headers: {
267
+ "Content-Type": "application/json",
268
+ "User-Agent": OPENCODE_USER_AGENT,
269
+ "x-opencode-client": "desktop",
270
+ },
271
+ authHeader: "Authorization",
272
+ authPrefix: "Bearer ",
273
+ parseResponse: parseOpenAIStyleModels,
274
+ },
247
275
  antigravity: {
248
276
  customResolver: buildOAuthResolver({
249
277
  refreshFn: (conn) => refreshGoogleToken(conn.refreshToken, GEMINI_CONFIG.clientId, GEMINI_CONFIG.clientSecret),
@@ -23,6 +23,7 @@ import {
23
23
  } from "@/lib/oauth/constants/oauth";
24
24
  import { buildClineHeaders } from "@/shared/utils/clineAuth";
25
25
  import { AGENTROUTER_MODELS_URL, AGENTROUTER_OPENAI_HEADERS } from "open-sse/providers/shared.js";
26
+ import { OPENCODE_USER_AGENT } from "open-sse/utils/opencode.js";
26
27
  import { buildCodeBuddyModelHeaders } from "@/services/codebuddyModels.js";
27
28
 
28
29
  const OAUTH_TEST_CONFIG = {
@@ -763,13 +764,21 @@ async function testApiKeyConnection(connection, effectiveProxy = null) {
763
764
  const valid = res.status !== 401 && res.status !== 403;
764
765
  return { valid, error: valid ? null : "Invalid SSO cookie" };
765
766
  }
767
+ case "opencode-zen":
766
768
  case "opencode-go": {
767
- const res = await fetchWithConnectionProxy("https://opencode.ai/zen/go/v1/chat/completions", {
768
- method: "POST",
769
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${connection.apiKey}` },
770
- body: JSON.stringify({ model: getDefaultModel("opencode-go"), messages: [{ role: "user", content: "ping" }], max_tokens: 1, stream: false }),
771
- }, effectiveProxy);
772
- const valid = res.status !== 401 && res.status !== 403;
769
+ const res = await fetchWithConnectionProxy(
770
+ connection.provider === "opencode-go" ? "https://opencode.ai/zen/go/v1/models" : "https://opencode.ai/zen/v1/models",
771
+ {
772
+ method: "GET",
773
+ headers: {
774
+ Authorization: `Bearer ${connection.apiKey}`,
775
+ "User-Agent": OPENCODE_USER_AGENT,
776
+ "x-opencode-client": "desktop",
777
+ },
778
+ },
779
+ effectiveProxy,
780
+ );
781
+ const valid = res.ok;
773
782
  return { valid, error: valid ? null : "Invalid API key" };
774
783
  }
775
784
  case "xiaomi-mimo":
@@ -8,11 +8,12 @@ export async function GET() {
8
8
  const providers = REGISTRY.filter((r) => !r.hidden)
9
9
  .map((r) => {
10
10
  const display = r.display || {};
11
- const staticModels = ["codebuddy-cn", "codebuddy-intl"].includes(r.id)
11
+ const staticModels = ["codebuddy-cn", "codebuddy-intl", "opencode", "opencode-go"].includes(r.id)
12
12
  && Array.isArray(r.models)
13
13
  ? r.models.map((model) => ({
14
14
  id: model.id,
15
15
  name: model.name || model.id,
16
+ ...(model.targetFormat ? { targetFormat: model.targetFormat } : {}),
16
17
  }))
17
18
  : [];
18
19
 
@@ -38,6 +39,7 @@ export async function GET() {
38
39
  ...(r.noAuth ? { noAuth: true } : {}),
39
40
  ...(r.hasOAuth ? { hasOAuth: true } : {}),
40
41
  ...(r.passthroughModels ? { passthroughModels: true } : {}),
42
+ ...(r.modelsFetcher?.url ? { modelsFetcherUrl: r.modelsFetcher.url } : {}),
41
43
  ...(r.thinkingConfig ? { thinkingConfig: r.thinkingConfig } : {}),
42
44
  ...(staticModels.length ? { models: staticModels } : {}),
43
45
  ...(display.color ? { color: display.color } : {}),
@@ -12,10 +12,15 @@ export const FILTERS = {
12
12
  .map((m) => ({ id: m.id, name: m.name, contextLength: m.context_length }))
13
13
  .sort((a, b) => b.contextLength - a.contextLength),
14
14
 
15
- "opencode-free": (models) =>
15
+ "opencode-free": (models) =>
16
16
  models
17
17
  .filter((m) => m.id?.endsWith("-free") || KNOWN_FREE_OPENCODE_MODELS.includes(m.id))
18
- .map((m) => ({ id: m.id, name: m.id })),
18
+ .map((m) => ({ id: m.id, name: m.id })),
19
+
20
+ "opencode-all": (models) =>
21
+ models
22
+ .filter((m) => typeof m?.id === "string")
23
+ .map((m) => ({ id: m.id, name: m.name || m.id })),
19
24
 
20
25
  "mimo-free": (models) =>
21
26
  (Array.isArray(models) ? models : [])
@@ -9,6 +9,7 @@ import { normalizeProviderId } from "@/lib/providerNormalization";
9
9
  import { logRouteError } from "@/lib/errors/publicError";
10
10
  import { KiroService } from "@/lib/oauth/services/kiro";
11
11
  import { AGENTROUTER_MODELS_URL, AGENTROUTER_OPENAI_HEADERS } from "open-sse/providers/shared.js";
12
+ import { OPENCODE_USER_AGENT } from "open-sse/utils/opencode.js";
12
13
  import { resolveCodeBuddyModels } from "@/services/codebuddyModels.js";
13
14
 
14
15
  const VALIDATION_TIMEOUT_MS = 15000;
@@ -393,18 +394,20 @@ export async function POST(request) {
393
394
  break;
394
395
  }
395
396
 
397
+ case "opencode-zen":
396
398
  case "opencode-go": {
397
- const res = await fetchWithTimeout("https://opencode.ai/zen/go/v1/chat/completions", {
398
- method: "POST",
399
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
400
- body: JSON.stringify({
401
- model: getDefaultModel("opencode-go"),
402
- messages: [{ role: "user", content: "ping" }],
403
- max_tokens: 1,
404
- stream: false,
405
- }),
406
- });
407
- isValid = res.status !== 401 && res.status !== 403;
399
+ const res = await fetchWithTimeout(
400
+ provider === "opencode-go" ? "https://opencode.ai/zen/go/v1/models" : "https://opencode.ai/zen/v1/models",
401
+ {
402
+ method: "GET",
403
+ headers: {
404
+ "Authorization": `Bearer ${apiKey}`,
405
+ "User-Agent": OPENCODE_USER_AGENT,
406
+ "x-opencode-client": "desktop",
407
+ },
408
+ },
409
+ );
410
+ isValid = res.ok;
408
411
  break;
409
412
  }
410
413
 
@@ -10,7 +10,7 @@ export type ProviderType =
10
10
  | "groq" | "kilo-gateway" | "kilocode" | "kimchi" | "kimi"
11
11
  | "kiro" | "minimax" | "minimax-cn" | "mistral"
12
12
  | "morph" | "nebius" | "nvidia" | "ollama" | "ollama-local"
13
- | "openai" | "opencode" | "opencode-go" | "openrouter" | "perplexity"
13
+ | "openai" | "opencode" | "opencode-zen" | "opencode-go" | "openrouter" | "perplexity"
14
14
  | "poolside" | "qoder" | "tencent"
15
15
  | "together" | "trae" | "venice" | "vercel-ai-gateway"
16
16
  | "sumopod" | "vertex" | "vertex-partner" | "windsurf" | "xai" | "xiaomi-mimo"