@xopcai/xopc 0.0.102 → 0.0.103

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 (123) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/feishu/src/transport/webhook/monitor.js +2 -2
  5. package/dist/extensions/feishu/src/transport/webhook/monitor.js.map +1 -1
  6. package/dist/extensions/telegram/src/send-options.js +8 -5
  7. package/dist/extensions/telegram/src/send-options.js.map +1 -1
  8. package/dist/extensions/telegram/xopc.extension.json +1 -1
  9. package/dist/gateway/static/root/assets/{agents-DQ0mQxIA.js → agents-BwXkia_W.js} +1 -1
  10. package/dist/gateway/static/root/assets/{apps-page-Uv4kOmDE.js → apps-page-C5RmwmTT.js} +1 -1
  11. package/dist/gateway/static/root/assets/{channels-settings-BEtP53V6.js → channels-settings-tlOTdaXw.js} +1 -1
  12. package/dist/gateway/static/root/assets/{channels-status-swr-CxTtS1hI.js → channels-status-swr-D4XjqwqC.js} +1 -1
  13. package/dist/gateway/static/root/assets/{cron-api-CznlPIZT.js → cron-api-DcmfGuj0.js} +1 -1
  14. package/dist/gateway/static/root/assets/{cron-page-Dx6dE3eD.js → cron-page-CijJ4G6F.js} +1 -1
  15. package/dist/gateway/static/root/assets/{dist-DH4IuRV0.js → dist-CWm8hTXZ.js} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-debug-page-BNkhvRQW.js → extension-debug-page-BzYgTkS9.js} +1 -1
  17. package/dist/gateway/static/root/assets/{extension-page-CcIGQah2.js → extension-page-BmTZH6gL.js} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-settings-page-DuDCSb3J.js → extension-settings-page-CyJk_G64.js} +1 -1
  19. package/dist/gateway/static/root/assets/{fetch-drJUxxKP.js → fetch-CMoWY2Si.js} +1 -1
  20. package/dist/gateway/static/root/assets/{field-primitives-BUwUiDf5.js → field-primitives-C4GpxsDo.js} +1 -1
  21. package/dist/gateway/static/root/assets/{heartbeat-config-api-BjFW1cl-.js → heartbeat-config-api-CLD0Gpi3.js} +1 -1
  22. package/dist/gateway/static/root/assets/index-0HmunJW9.js +4845 -0
  23. package/dist/gateway/static/root/assets/index-Bk75Phrd.css +1 -0
  24. package/dist/gateway/static/root/assets/{logs-page-CiG0m81R.js → logs-page-BjdQjHwk.js} +1 -1
  25. package/dist/gateway/static/root/assets/{note-detail-page-DG3DVFTf.js → note-detail-page-8Er4197b.js} +1 -1
  26. package/dist/gateway/static/root/assets/{note-time-DjlllCF3.js → note-time-CF3vf1jm.js} +1 -1
  27. package/dist/gateway/static/root/assets/{notes-page-D40ZpS0Y.js → notes-page-BBnTh0uI.js} +1 -1
  28. package/dist/gateway/static/root/assets/{sessions-page-BvtdbmW8.js → sessions-page-B0riYK7j.js} +1 -1
  29. package/dist/gateway/static/root/assets/{settings-advanced-gate-BQamRjkV.js → settings-advanced-gate-DiP_WHu_.js} +1 -1
  30. package/dist/gateway/static/root/assets/{settings-form-section-C3_sEFfD.js → settings-form-section-C7qA7y0J.js} +1 -1
  31. package/dist/gateway/static/root/assets/{settings-page-D972_jaS.js → settings-page-1ZPhcCZ9.js} +2 -2
  32. package/dist/gateway/static/root/assets/share-preview-page-Tvr5Mtq5.js +2 -0
  33. package/dist/gateway/static/root/assets/{skills-page-QwFCB4yV.js → skills-page-BQ1hllKD.js} +1 -1
  34. package/dist/gateway/static/root/assets/{theme-store-B6KXPHgK.js → theme-store-HTvJlFLL.js} +1 -1
  35. package/dist/gateway/static/root/assets/url-F3_D_6zZ.js +3 -0
  36. package/dist/gateway/static/root/assets/{utils-DCm11_KW.js → utils-CPr3FBh2.js} +1 -1
  37. package/dist/gateway/static/root/assets/{voice-api-key-field-CywiB7Dw.js → voice-api-key-field-BkbDl6dH.js} +1 -1
  38. package/dist/gateway/static/root/assets/{workflow-page.utils-g57BRfEd.js → workflow-page.utils-CeBkAjP7.js} +1 -1
  39. package/dist/gateway/static/root/assets/{workflows-page-Be7l_SWw.js → workflows-page-D059ysoF.js} +1 -1
  40. package/dist/gateway/static/root/index.html +5 -5
  41. package/dist/package.js +1 -1
  42. package/dist/src/agent/inbound/attachment-pipeline.js +2 -2
  43. package/dist/src/agent/inbound/attachment-pipeline.js.map +1 -1
  44. package/dist/src/agent/tools/send-media.js +1 -2
  45. package/dist/src/agent/tools/send-media.js.map +1 -1
  46. package/dist/src/auth/credentials.d.ts +7 -0
  47. package/dist/src/auth/credentials.js +35 -2
  48. package/dist/src/auth/credentials.js.map +1 -1
  49. package/dist/src/auth/index.d.ts +1 -1
  50. package/dist/src/auth/index.js.map +1 -1
  51. package/dist/src/auth/oauth/github-copilot.d.ts +1 -1
  52. package/dist/src/auth/oauth/google-antigravity.d.ts +1 -3
  53. package/dist/src/auth/oauth/google-antigravity.js +1 -1
  54. package/dist/src/auth/oauth/google-gemini-cli.d.ts +1 -3
  55. package/dist/src/auth/oauth/google-gemini-cli.js +1 -1
  56. package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-antigravity.js +3 -5
  57. package/dist/src/auth/oauth/google-oauth/google-antigravity.js.map +1 -0
  58. package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-gemini-cli.js +4 -5
  59. package/dist/src/auth/oauth/google-oauth/google-gemini-cli.js.map +1 -0
  60. package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/oauth-page.js +12 -4
  61. package/dist/src/auth/oauth/google-oauth/oauth-page.js.map +1 -0
  62. package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/pkce.d.ts +1 -1
  63. package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/pkce.js +2 -2
  64. package/dist/src/auth/oauth/google-oauth/pkce.js.map +1 -0
  65. package/dist/src/auth/oauth/index.js +2 -2
  66. package/dist/src/auth/oauth/kimi-coding.d.ts +1 -1
  67. package/dist/src/auth/oauth/kimi-coding.js +1 -1
  68. package/dist/src/auth/oauth/kimi-coding.js.map +1 -1
  69. package/dist/src/auth/oauth/minimax-cn.js +1 -1
  70. package/dist/src/auth/oauth/minimax-cn.js.map +1 -1
  71. package/dist/src/auth/oauth/minimax.js +1 -1
  72. package/dist/src/auth/oauth/minimax.js.map +1 -1
  73. package/dist/src/auth/oauth/openai-codex.d.ts +7 -2
  74. package/dist/src/auth/oauth/openai-codex.js +318 -1
  75. package/dist/src/auth/oauth/openai-codex.js.map +1 -0
  76. package/dist/src/auth/profiles/store.js +3 -2
  77. package/dist/src/auth/profiles/store.js.map +1 -1
  78. package/dist/src/channels/attachments/attachment-classifiers.d.ts +1 -0
  79. package/dist/src/channels/attachments/attachment-classifiers.js +13 -2
  80. package/dist/src/channels/attachments/attachment-classifiers.js.map +1 -1
  81. package/dist/src/chat-commands/builtins/model.js +2 -2
  82. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  83. package/dist/src/cli/command-manifest.js +1 -1
  84. package/dist/src/cli/command-manifest.js.map +1 -1
  85. package/dist/src/cli/commands/auth.js +39 -51
  86. package/dist/src/cli/commands/auth.js.map +1 -1
  87. package/dist/src/cli/commands/models.js +189 -54
  88. package/dist/src/cli/commands/models.js.map +1 -1
  89. package/dist/src/cli/commands/onboard/model.d.ts +1 -4
  90. package/dist/src/cli/commands/onboard/model.js +253 -183
  91. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  92. package/dist/src/cli/utils/oauth-login.d.ts +17 -0
  93. package/dist/src/cli/utils/oauth-login.js +93 -0
  94. package/dist/src/cli/utils/oauth-login.js.map +1 -0
  95. package/dist/src/cli/utils/oauth-providers.js +2 -2
  96. package/dist/src/gateway/heartbeat/service.js +1 -1
  97. package/dist/src/gateway/hono/oauth-async.js +2 -2
  98. package/dist/src/gateway/hono/oauth.js +2 -2
  99. package/dist/src/gateway/hono/routes/models.js +25 -5
  100. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  101. package/dist/src/heartbeat/index.js +1 -1
  102. package/dist/src/media/store.js +3 -0
  103. package/dist/src/media/store.js.map +1 -1
  104. package/dist/src/notes/service.js +1 -1
  105. package/dist/src/notes/service.js.map +1 -1
  106. package/dist/src/providers/index.d.ts +3 -3
  107. package/dist/src/providers/index.js +23 -15
  108. package/dist/src/providers/index.js.map +1 -1
  109. package/dist/src/providers/presentation.d.ts +34 -0
  110. package/dist/src/providers/presentation.js +162 -0
  111. package/dist/src/providers/presentation.js.map +1 -0
  112. package/package.json +1 -1
  113. package/dist/gateway/static/root/assets/index-DKgHC1Ta.js +0 -4840
  114. package/dist/gateway/static/root/assets/index-DjNDHzwG.css +0 -1
  115. package/dist/gateway/static/root/assets/share-preview-page-CKCTMo0_.js +0 -2
  116. package/dist/gateway/static/root/assets/url-q28QhI7l.js +0 -3
  117. package/dist/src/auth/oauth/vendor-pi-google/google-antigravity.js.map +0 -1
  118. package/dist/src/auth/oauth/vendor-pi-google/google-gemini-cli.js.map +0 -1
  119. package/dist/src/auth/oauth/vendor-pi-google/oauth-page.js.map +0 -1
  120. package/dist/src/auth/oauth/vendor-pi-google/pkce.js.map +0 -1
  121. /package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-antigravity.d.ts +0 -0
  122. /package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-gemini-cli.d.ts +0 -0
  123. /package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/oauth-page.d.ts +0 -0
@@ -1,29 +1,73 @@
1
- import { getApiKeyFromEnv, init_env_keys } from "../../../providers/env-keys.js";
1
+ import { getApiKeyFromEnv } from "../../../providers/env-keys.js";
2
+ import { resolveModelsJsonPath } from "../../../config/paths.js";
2
3
  import { CredentialResolver, init_credentials } from "../../../auth/credentials.js";
3
- import { getModelsByProvider, getProviderDisplayName, getSortedProviders, init_providers, providerSupportsApiKey, providerSupportsOAuth } from "../../../providers/index.js";
4
- import { listProfilesForProvider, upsertAuthProfile } from "../../../auth/profiles/profiles.js";
4
+ import { init_models_json, loadModelsJson, saveModelsJson } from "../../../config/models-json.js";
5
+ import { getModelRegistry } from "../../../providers/model-registry.js";
6
+ import { getAllProviders, getModelsByProvider, getProviderDisplayName, init_providers, providerSupportsApiKey, providerSupportsOAuth } from "../../../providers/index.js";
7
+ import { getProviderHint, getRecommendedModelsForProvider, sortModelsForPicker, sortProvidersForPicker } from "../../../providers/presentation.js";
8
+ import { listProfilesForProvider } from "../../../auth/profiles/profiles.js";
5
9
  import { colors } from "../../utils/colors.js";
6
10
  import { getOAuthProvider } from "../../utils/oauth-providers.js";
11
+ import { runCliOAuthLogin } from "../../utils/oauth-login.js";
7
12
  import { confirm, input, select } from "@inquirer/prompts";
8
13
  //#region src/cli/commands/onboard/model.ts
9
14
  /**
10
- * Model Configuration for Onboarding
15
+ * Catalog-driven model configuration for onboarding.
11
16
  */
12
- init_providers();
13
17
  init_credentials();
14
- init_env_keys();
15
- /**
16
- * Get available models for a provider
17
- */
18
- async function getModelsForProvider(provider) {
19
- return getModelsByProvider(provider).map((m) => ({
20
- value: `${m.provider}/${m.id}`,
21
- name: m.name || m.id
18
+ init_models_json();
19
+ init_providers();
20
+ function ensureAgentDefaults(config, workspacePath) {
21
+ config.agents = config.agents || {};
22
+ config.agents.defaults = config.agents.defaults || {
23
+ workspace: workspacePath,
24
+ maxTokens: 8192,
25
+ temperature: .7,
26
+ maxToolIterations: 20,
27
+ maxRequestsPerTurn: 50,
28
+ maxToolFailuresPerTurn: 3
29
+ };
30
+ config.agents.defaults.workspace = workspacePath;
31
+ return config.agents.defaults;
32
+ }
33
+ function setPrimaryModel(config, workspacePath, modelRef) {
34
+ const defaults = ensureAgentDefaults(config, workspacePath);
35
+ defaults.model = {
36
+ primary: modelRef,
37
+ fallbacks: []
38
+ };
39
+ return config;
40
+ }
41
+ function formatRecommended(provider) {
42
+ const labels = getRecommendedModelsForProvider(provider, 3).map((m) => m.name || m.id);
43
+ return labels.length > 0 ? `Recommended: ${labels.join(", ")}` : getProviderHint(provider);
44
+ }
45
+ function providerChoices() {
46
+ const choices = sortProvidersForPicker(getAllProviders()).map((provider) => ({
47
+ value: provider,
48
+ name: getProviderDisplayName(provider),
49
+ description: formatRecommended(provider)
22
50
  }));
51
+ choices.push({
52
+ value: "custom-api",
53
+ name: "Custom API",
54
+ description: "OpenAI-compatible, OpenAI Responses, or Anthropic-compatible endpoint"
55
+ });
56
+ return choices;
57
+ }
58
+ function modelChoiceName(choice) {
59
+ return choice.description ? `${choice.name} ${colors.gray(choice.description)}` : choice.name;
60
+ }
61
+ function getModelsForProvider(provider) {
62
+ return sortModelsForPicker(getModelsByProvider(provider)).map((m) => {
63
+ const badges = [m.reasoning ? "reasoning" : "", m.input?.includes("image") ? "vision" : ""].filter(Boolean).join(", ");
64
+ return {
65
+ value: `${m.provider}/${m.id}`,
66
+ name: m.name || m.id,
67
+ description: badges || void 0
68
+ };
69
+ });
23
70
  }
24
- /**
25
- * Perform OAuth login for a provider
26
- */
27
71
  async function doOAuthLogin(provider) {
28
72
  const config = getOAuthProvider(provider);
29
73
  if (!config) {
@@ -31,50 +75,197 @@ async function doOAuthLogin(provider) {
31
75
  return false;
32
76
  }
33
77
  console.log(`\n🔐 Starting ${config.displayName} OAuth login...`);
34
- const callbacks = {
35
- onAuth: (info) => {
36
- console.log(`\n${config.urlPrompt}`);
37
- console.log(info.url);
38
- if (info.instructions) console.log("\n" + info.instructions);
39
- console.log("\n");
40
- },
41
- onDeviceCode: (info) => {
42
- console.log(`\nOpen ${info.verificationUri} and enter code ${info.userCode}\n`);
43
- },
44
- onPrompt: async (prompt) => {
45
- return input({ message: prompt.message });
46
- },
47
- onProgress: (message) => {
48
- console.log(" →", message);
78
+ try {
79
+ await runCliOAuthLogin({
80
+ provider,
81
+ onProgress: (message) => console.log(" →", message)
82
+ });
83
+ return true;
84
+ } catch (error) {
85
+ console.error("❌ OAuth login failed:", error);
86
+ return false;
87
+ }
88
+ }
89
+ function joinEndpoint(baseUrl, suffix) {
90
+ return `${baseUrl.replace(/\/+$/, "")}/${suffix.replace(/^\/+/, "")}`;
91
+ }
92
+ async function probeCustomProvider(params) {
93
+ const timeout = AbortSignal.timeout(3e4);
94
+ const commonHeaders = {
95
+ "content-type": "application/json",
96
+ authorization: `Bearer ${params.apiKey}`
97
+ };
98
+ const request = params.api === "anthropic-messages" ? {
99
+ url: joinEndpoint(params.baseUrl, "/messages"),
100
+ headers: {
101
+ ...commonHeaders,
102
+ "anthropic-version": "2023-06-01"
49
103
  },
50
- onSelect: async (prompt) => {
51
- return prompt.options.find((option) => option.id === "browser")?.id ?? prompt.options[0]?.id;
104
+ body: {
105
+ model: params.modelId,
106
+ max_tokens: 1,
107
+ messages: [{
108
+ role: "user",
109
+ content: "ping"
110
+ }]
111
+ }
112
+ } : params.api === "openai-responses" ? {
113
+ url: joinEndpoint(params.baseUrl, "/responses"),
114
+ headers: commonHeaders,
115
+ body: {
116
+ model: params.modelId,
117
+ input: "ping",
118
+ max_output_tokens: 1
119
+ }
120
+ } : {
121
+ url: joinEndpoint(params.baseUrl, "/chat/completions"),
122
+ headers: commonHeaders,
123
+ body: {
124
+ model: params.modelId,
125
+ messages: [{
126
+ role: "user",
127
+ content: "ping"
128
+ }],
129
+ max_tokens: 1
52
130
  }
53
131
  };
132
+ const res = await fetch(request.url, {
133
+ method: "POST",
134
+ headers: request.headers,
135
+ body: JSON.stringify(request.body),
136
+ signal: timeout
137
+ });
138
+ if (!res.ok) {
139
+ const text = await res.text().catch(() => "");
140
+ throw new Error(`Probe failed (${res.status}) at ${request.url}${text ? `: ${text.slice(0, 300)}` : ""}`);
141
+ }
142
+ }
143
+ function defaultProviderIdFromBaseUrl(baseUrl) {
54
144
  try {
55
- const creds = await config.provider.login(callbacks);
56
- upsertAuthProfile({
57
- profileId: config.profileId,
58
- credential: {
59
- type: "oauth",
60
- provider,
61
- ...creds
145
+ return new URL(baseUrl).hostname.replace(/^api\./, "").split(".").filter(Boolean).slice(0, 2).join("-").toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "") || "custom";
146
+ } catch {
147
+ return "custom";
148
+ }
149
+ }
150
+ function validateProviderId(value) {
151
+ return /^[a-z0-9]([a-z0-9-_]*[a-z0-9])?$/.test(value) ? true : "Use lowercase letters, numbers, hyphens, or underscores; start/end with a letter or number.";
152
+ }
153
+ async function setupCustomApi(config, ctx) {
154
+ console.log("\n🔌 Custom API provider\n");
155
+ const baseUrl = (await input({
156
+ message: "API base URL:",
157
+ default: "https://api.example.com/v1",
158
+ validate: (value) => URL.canParse(value) || "Enter a valid URL"
159
+ })).trim();
160
+ const apiKey = (await input({
161
+ message: "API key:",
162
+ validate: (value) => value.trim().length > 0 || "Required"
163
+ })).trim();
164
+ const api = await select({
165
+ message: "Compatibility:",
166
+ choices: [
167
+ {
168
+ value: "openai-completions",
169
+ name: "OpenAI-compatible Chat Completions"
170
+ },
171
+ {
172
+ value: "openai-responses",
173
+ name: "OpenAI Responses"
174
+ },
175
+ {
176
+ value: "anthropic-messages",
177
+ name: "Anthropic Messages"
62
178
  }
179
+ ]
180
+ });
181
+ const modelId = (await input({
182
+ message: "Model ID:",
183
+ validate: (value) => value.trim().length > 0 || "Required"
184
+ })).trim();
185
+ console.log("\n→ Probing endpoint...");
186
+ await probeCustomProvider({
187
+ baseUrl,
188
+ apiKey,
189
+ modelId,
190
+ api
191
+ });
192
+ console.log(colors.green("✓ Probe succeeded"));
193
+ const existingProviders = new Set(getAllProviders());
194
+ const providerId = (await input({
195
+ message: "Provider ID:",
196
+ default: defaultProviderIdFromBaseUrl(baseUrl),
197
+ validate: (value) => {
198
+ const valid = validateProviderId(value.trim());
199
+ if (valid !== true) return valid;
200
+ if (existingProviders.has(value.trim())) return "Provider ID already exists; choose a new ID.";
201
+ return true;
202
+ }
203
+ })).trim();
204
+ const path = resolveModelsJsonPath();
205
+ const { config: modelsConfig } = loadModelsJson(path);
206
+ const saved = saveModelsJson(path, { providers: {
207
+ ...modelsConfig.providers ?? {},
208
+ [providerId]: {
209
+ baseUrl,
210
+ apiKey,
211
+ api,
212
+ models: [{
213
+ id: modelId,
214
+ name: modelId,
215
+ api,
216
+ contextWindow: 128e3,
217
+ input: ["text"]
218
+ }]
219
+ }
220
+ } });
221
+ if (!saved.success) throw new Error(saved.error || "Failed to save models.json");
222
+ getModelRegistry().refresh();
223
+ const ref = `${providerId}/${modelId}`;
224
+ console.log("\n✅ Custom model configured:", ref);
225
+ return setPrimaryModel(config, ctx.workspacePath, ref);
226
+ }
227
+ async function ensureProviderCredential(provider, providerName) {
228
+ if (listProfilesForProvider(provider).length > 0) {
229
+ console.log(`\n${colors.green("✓")} Found existing credentials for ${providerName}`);
230
+ return await confirm({
231
+ message: "Use existing credentials?",
232
+ default: true
63
233
  });
234
+ }
235
+ if (getApiKeyFromEnv(provider)) {
236
+ console.log(`\n${colors.green("✓")} Found API key for ${providerName} in environment`);
64
237
  return true;
65
- } catch (error) {
66
- console.error("❌ OAuth login failed:", error);
67
- return false;
68
238
  }
239
+ const supportsOAuth = providerSupportsOAuth(provider);
240
+ const supportsApiKey = providerSupportsApiKey(provider);
241
+ if (supportsOAuth && !supportsApiKey) return await doOAuthLogin(provider);
242
+ let useOAuth = false;
243
+ if (supportsOAuth && supportsApiKey) useOAuth = await select({
244
+ message: `How would you like to authenticate with ${providerName}?`,
245
+ choices: [{
246
+ value: "api_key",
247
+ name: "API key"
248
+ }, {
249
+ value: "oauth",
250
+ name: "OAuth login"
251
+ }]
252
+ }) === "oauth";
253
+ if (useOAuth) {
254
+ if (await doOAuthLogin(provider)) return true;
255
+ console.log("\n⚠️ OAuth login failed. Enter an API key instead.");
256
+ }
257
+ if (!supportsApiKey) return false;
258
+ const apiKey = await input({
259
+ message: `API key for ${providerName}:`,
260
+ validate: (value) => value.trim().length > 0 || "Required"
261
+ });
262
+ await new CredentialResolver().saveApiKey(provider, apiKey.trim(), { profileName: "default" });
263
+ return true;
69
264
  }
70
- /**
71
- * Configure AI model provider and model
72
- */
73
265
  async function setupModel(existingConfig, ctx) {
74
266
  console.log("\n🤖 Step: AI Model\n");
75
267
  const config = existingConfig || {};
76
- const currentModelConfig = config?.agents?.defaults?.model;
77
- const currentModel = typeof currentModelConfig === "string" ? currentModelConfig : currentModelConfig?.primary;
268
+ const currentModel = config?.agents?.defaults?.model?.primary;
78
269
  if (currentModel) {
79
270
  console.log("Current model:", currentModel);
80
271
  if (await confirm({
@@ -86,148 +277,27 @@ async function setupModel(existingConfig, ctx) {
86
277
  }
87
278
  }
88
279
  const provider = await select({
89
- message: "Select provider:",
90
- choices: getSortedProviders().map((p) => ({
91
- value: p,
92
- name: getProviderDisplayName(p)
93
- }))
280
+ message: "Model/auth provider:",
281
+ choices: providerChoices()
94
282
  });
283
+ if (provider === "custom-api") return await setupCustomApi(config, ctx);
95
284
  const providerName = getProviderDisplayName(provider);
96
- if (listProfilesForProvider(provider).length > 0) {
97
- console.log(`\n${colors.green("✓")} Found existing credentials for ${providerName}`);
98
- if (await confirm({
99
- message: "Use existing credentials?",
100
- default: true
101
- })) {
102
- const modelChoices = await getModelsForProvider(provider);
103
- if (modelChoices.length === 0) console.log(`\n⚠️ No models found for ${providerName}. Please check your credentials.`);
104
- else {
105
- const model = await select({
106
- message: "Select model:",
107
- choices: modelChoices
108
- });
109
- config.agents = config.agents || {};
110
- config.agents.defaults = config.agents.defaults || {
111
- workspace: ctx.workspacePath,
112
- model: {
113
- primary: model,
114
- fallbacks: []
115
- },
116
- maxTokens: 8192,
117
- temperature: .7,
118
- maxToolIterations: 20,
119
- maxRequestsPerTurn: 50,
120
- maxToolFailuresPerTurn: 3
121
- };
122
- config.agents.defaults.model = {
123
- primary: model,
124
- fallbacks: []
125
- };
126
- config.agents.defaults.workspace = ctx.workspacePath;
127
- console.log("\n✅ Model configured:", model);
128
- return config;
129
- }
130
- }
131
- }
132
- let apiKey;
133
- let useOAuth = false;
134
- apiKey = getApiKeyFromEnv(provider);
135
- if (apiKey) console.log(`\n${colors.green("✓")} Found API key for ${providerName} in environment`);
136
- if (!apiKey) {
137
- const supportsOAuth = providerSupportsOAuth(provider);
138
- const supportsApiKey = providerSupportsApiKey(provider);
139
- if (supportsOAuth && !supportsApiKey) if (await doOAuthLogin(provider)) {
140
- useOAuth = true;
141
- console.log("\n✅ OAuth login successful!");
142
- } else {
143
- console.error("\n❌ OAuth login failed. This provider requires OAuth.");
144
- return config;
145
- }
146
- else if (supportsOAuth && supportsApiKey) if (await select({
147
- message: `How would you like to authenticate with ${providerName}?`,
148
- choices: [{
149
- value: "api_key",
150
- name: "API Key (enter manually)"
151
- }, {
152
- value: "oauth",
153
- name: "OAuth Login (browser-based)"
154
- }]
155
- }) === "oauth") if (await doOAuthLogin(provider)) {
156
- useOAuth = true;
157
- console.log("\n✅ OAuth login successful!");
158
- } else {
159
- console.log("\n⚠️ OAuth login failed. Please enter API key manually.");
160
- apiKey = await input({
161
- message: `API Key for ${providerName}:`,
162
- validate: (v) => v.length > 0 || "Required"
163
- });
164
- useOAuth = false;
165
- }
166
- else apiKey = await input({
167
- message: `API Key for ${providerName}:`,
168
- validate: (v) => v.length > 0 || "Required"
169
- });
170
- else apiKey = await input({
171
- message: `API Key for ${providerName}:`,
172
- validate: (v) => v.length > 0 || "Required"
173
- });
174
- }
175
- const modelChoices = await getModelsForProvider(provider);
176
- if (modelChoices.length === 0) {
177
- console.log(`\n⚠️ No built-in models found for ${providerName}.`);
178
- console.log(" You can still use custom model names.");
179
- const model = await input({
180
- message: "Model name:",
181
- validate: (v) => v.length > 0 || "Required"
182
- });
183
- if (apiKey) await new CredentialResolver().saveApiKey(provider, apiKey, { profileName: "default" });
184
- config.agents = config.agents || {};
185
- config.agents.defaults = config.agents.defaults || {
186
- workspace: ctx.workspacePath,
187
- model: {
188
- primary: `${provider}/${model}`,
189
- fallbacks: []
190
- },
191
- maxTokens: 8192,
192
- temperature: .7,
193
- maxToolIterations: 20,
194
- maxRequestsPerTurn: 50,
195
- maxToolFailuresPerTurn: 3
196
- };
197
- config.agents.defaults.model = {
198
- primary: `${provider}/${model}`,
199
- fallbacks: []
200
- };
201
- config.agents.defaults.workspace = ctx.workspacePath;
202
- console.log("\n✅ Model configured:", `${provider}/${model}`);
285
+ if (!await ensureProviderCredential(provider, providerName)) {
286
+ console.error(`\n Could not configure credentials for ${providerName}.`);
203
287
  return config;
204
288
  }
205
- console.log(`\n📋 Available models for ${providerName}:`);
289
+ const modelChoices = getModelsForProvider(provider);
290
+ if (modelChoices.length === 0) throw new Error(`No catalog models found for ${providerName}. Add a Custom API provider instead.`);
291
+ console.log(`\n📋 Models for ${providerName}:`);
206
292
  const model = await select({
207
- message: "Select model:",
208
- choices: modelChoices
293
+ message: "Model:",
294
+ choices: modelChoices.map((choice) => ({
295
+ ...choice,
296
+ name: modelChoiceName(choice)
297
+ }))
209
298
  });
210
- if (!useOAuth && apiKey) await new CredentialResolver().saveApiKey(provider, apiKey, { profileName: "default" });
211
- config.agents = config.agents || {};
212
- config.agents.defaults = config.agents.defaults || {
213
- workspace: ctx.workspacePath,
214
- model: {
215
- primary: model,
216
- fallbacks: []
217
- },
218
- maxTokens: 8192,
219
- temperature: .7,
220
- maxToolIterations: 20,
221
- maxRequestsPerTurn: 50,
222
- maxToolFailuresPerTurn: 3
223
- };
224
- config.agents.defaults.model = {
225
- primary: model,
226
- fallbacks: []
227
- };
228
- config.agents.defaults.workspace = ctx.workspacePath;
229
299
  console.log("\n✅ Model configured:", model);
230
- return config;
300
+ return setPrimaryModel(config, ctx.workspacePath, model);
231
301
  }
232
302
  //#endregion
233
303
  export { setupModel };
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","names":[],"sources":["../../../../../src/cli/commands/onboard/model.ts"],"sourcesContent":["/**\n * Model Configuration for Onboarding\n */\n\nimport { input, select, confirm } from '@inquirer/prompts';\nimport type { Config } from '../../../config/schema.js';\nimport type { CLIContext } from '../../registry.js';\nimport { colors } from '../../utils/colors.js';\nimport {\n getModelsByProvider,\n getSortedProviders,\n getProviderDisplayName,\n providerSupportsOAuth,\n providerSupportsApiKey,\n} from '../../../providers/index.js';\nimport { listProfilesForProvider } from '../../../auth/profiles/index.js';\nimport { upsertAuthProfile } from '../../../auth/profiles/index.js';\nimport { getOAuthProvider } from '../../utils/oauth-providers.js';\nimport type { OAuthLoginCallbacks } from '../../../auth/index.js';\nimport { CredentialResolver } from '../../../auth/credentials.js';\nimport { getApiKeyFromEnv } from '../../../providers/env-keys.js';\n\n/**\n * Get available models for a provider\n */\nasync function getModelsForProvider(provider: string): Promise<{ value: string; name: string }[]> {\n const models = getModelsByProvider(provider);\n return models.map((m) => ({\n value: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n }));\n}\n\n/**\n * Perform OAuth login for a provider\n */\nasync function doOAuthLogin(provider: string): Promise<boolean> {\n const config = getOAuthProvider(provider);\n if (!config) {\n console.error(`OAuth not supported for provider: ${provider}`);\n return false;\n }\n\n console.log(`\\n🔐 Starting ${config.displayName} OAuth login...`);\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (info) => {\n console.log(`\\n${config.urlPrompt}`);\n console.log(info.url);\n if (info.instructions) {\n console.log('\\n' + info.instructions);\n }\n console.log('\\n');\n },\n onDeviceCode: (info) => {\n console.log(`\\nOpen ${info.verificationUri} and enter code ${info.userCode}\\n`);\n },\n onPrompt: async (prompt) => {\n return input({ message: prompt.message });\n },\n onProgress: (message) => {\n console.log(' →', message);\n },\n onSelect: async (prompt) => {\n const browserOption = prompt.options.find((option) => option.id === 'browser');\n return browserOption?.id ?? prompt.options[0]?.id;\n },\n };\n\n try {\n const creds = await config.provider.login(callbacks);\n upsertAuthProfile({\n profileId: config.profileId,\n credential: {\n type: 'oauth',\n provider,\n ...creds,\n },\n });\n return true;\n } catch (error) {\n console.error('❌ OAuth login failed:', error);\n return false;\n }\n}\n\n/**\n * Configure AI model provider and model\n */\nexport async function setupModel(\n existingConfig: Config | null,\n ctx: CLIContext\n): Promise<Config> {\n console.log('\\n🤖 Step: AI Model\\n');\n\n const config = existingConfig || ({} as Config);\n const currentModelConfig = config?.agents?.defaults?.model;\n const currentModel =\n typeof currentModelConfig === 'string' ? currentModelConfig : currentModelConfig?.primary;\n\n if (currentModel) {\n console.log('Current model:', currentModel);\n const keepCurrent = await confirm({\n message: 'Keep using this model?',\n default: true,\n });\n if (keepCurrent) {\n console.log('✅ Keeping:', currentModel);\n return config;\n }\n }\n\n // Get sorted providers with metadata\n const sortedProviders = getSortedProviders();\n\n const choices = sortedProviders.map((p) => ({\n value: p,\n name: getProviderDisplayName(p),\n }));\n\n const provider = await select({\n message: 'Select provider:',\n choices,\n });\n\n const providerName = getProviderDisplayName(provider);\n\n // Check if provider has existing profiles\n const existingProfiles = listProfilesForProvider(provider);\n if (existingProfiles.length > 0) {\n console.log(`\\n${colors.green('✓')} Found existing credentials for ${providerName}`);\n const useExisting = await confirm({\n message: 'Use existing credentials?',\n default: true,\n });\n\n if (useExisting) {\n // Get available models\n const modelChoices = await getModelsForProvider(provider);\n if (modelChoices.length === 0) {\n console.log(`\\n⚠️ No models found for ${providerName}. Please check your credentials.`);\n } else {\n const model = await select({\n message: 'Select model:',\n choices: modelChoices,\n });\n\n config.agents = config.agents || {};\n config.agents.defaults = config.agents.defaults || {\n workspace: ctx.workspacePath,\n model: { primary: model, fallbacks: [] },\n maxTokens: 8192,\n temperature: 0.7,\n maxToolIterations: 20,\n maxRequestsPerTurn: 50,\n maxToolFailuresPerTurn: 3,\n };\n config.agents.defaults.model = { primary: model, fallbacks: [] };\n config.agents.defaults.workspace = ctx.workspacePath;\n\n console.log('\\n✅ Model configured:', model);\n return config;\n }\n }\n }\n\n let apiKey: string | undefined;\n let useOAuth = false;\n\n apiKey = getApiKeyFromEnv(provider);\n if (apiKey) {\n console.log(`\\n${colors.green('✓')} Found API key for ${providerName} in environment`);\n }\n\n if (!apiKey) {\n // Check auth support from metadata\n const supportsOAuth = providerSupportsOAuth(provider);\n const supportsApiKey = providerSupportsApiKey(provider);\n const isOAuthOnly = supportsOAuth && !supportsApiKey;\n\n if (isOAuthOnly) {\n // OAuth only - no choice\n const success = await doOAuthLogin(provider);\n if (success) {\n useOAuth = true;\n console.log('\\n✅ OAuth login successful!');\n } else {\n console.error('\\n❌ OAuth login failed. This provider requires OAuth.');\n return config;\n }\n } else if (supportsOAuth && supportsApiKey) {\n // Dual auth - let user choose\n const authMethod = await select({\n message: `How would you like to authenticate with ${providerName}?`,\n choices: [\n { value: 'api_key', name: 'API Key (enter manually)' },\n { value: 'oauth', name: 'OAuth Login (browser-based)' },\n ],\n });\n\n if (authMethod === 'oauth') {\n const success = await doOAuthLogin(provider);\n if (success) {\n useOAuth = true;\n console.log('\\n✅ OAuth login successful!');\n } else {\n console.log('\\n⚠️ OAuth login failed. Please enter API key manually.');\n apiKey = await input({\n message: `API Key for ${providerName}:`,\n validate: (v: string) => v.length > 0 || 'Required',\n });\n useOAuth = false;\n }\n } else {\n apiKey = await input({\n message: `API Key for ${providerName}:`,\n validate: (v: string) => v.length > 0 || 'Required',\n });\n }\n } else {\n // API key only\n apiKey = await input({\n message: `API Key for ${providerName}:`,\n validate: (v: string) => v.length > 0 || 'Required',\n });\n }\n }\n\n // Get available models\n const modelChoices = await getModelsForProvider(provider);\n if (modelChoices.length === 0) {\n console.log(`\\n⚠️ No built-in models found for ${providerName}.`);\n console.log(' You can still use custom model names.');\n const model = await input({\n message: 'Model name:',\n validate: (v: string) => v.length > 0 || 'Required',\n });\n\n // Store API key in new credential system\n if (apiKey) {\n const resolver = new CredentialResolver();\n await resolver.saveApiKey(provider, apiKey, { profileName: 'default' });\n }\n\n config.agents = config.agents || {};\n config.agents.defaults = config.agents.defaults || {\n workspace: ctx.workspacePath,\n model: { primary: `${provider}/${model}`, fallbacks: [] },\n maxTokens: 8192,\n temperature: 0.7,\n maxToolIterations: 20,\n maxRequestsPerTurn: 50,\n maxToolFailuresPerTurn: 3,\n };\n config.agents.defaults.model = { primary: `${provider}/${model}`, fallbacks: [] };\n config.agents.defaults.workspace = ctx.workspacePath;\n\n console.log('\\n✅ Model configured:', `${provider}/${model}`);\n return config;\n }\n\n console.log(`\\n📋 Available models for ${providerName}:`);\n const model = await select({\n message: 'Select model:',\n choices: modelChoices,\n });\n\n // Store in new credential system\n if (!useOAuth && apiKey) {\n const resolver = new CredentialResolver();\n await resolver.saveApiKey(provider, apiKey, { profileName: 'default' });\n }\n\n config.agents = config.agents || {};\n config.agents.defaults = config.agents.defaults || {\n workspace: ctx.workspacePath,\n model: { primary: model, fallbacks: [] },\n maxTokens: 8192,\n temperature: 0.7,\n maxToolIterations: 20,\n maxRequestsPerTurn: 50,\n maxToolFailuresPerTurn: 3,\n };\n config.agents.defaults.model = { primary: model, fallbacks: [] };\n config.agents.defaults.workspace = ctx.workspacePath;\n\n console.log('\\n✅ Model configured:', model);\n return config;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model.js","names":["getModelsJsonPath"],"sources":["../../../../../src/cli/commands/onboard/model.ts"],"sourcesContent":["/**\n * Catalog-driven model configuration for onboarding.\n */\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport { CredentialResolver } from '../../../auth/credentials.js';\nimport {\n getModelsJsonPath,\n loadModelsJson,\n saveModelsJson,\n type ModelsJsonConfig,\n} from '../../../config/models-json.js';\nimport type { Config } from '../../../config/schema.js';\nimport {\n getAllProviders,\n getApiKeyFromEnv,\n getModelRegistry,\n getModelsByProvider,\n getProviderDisplayName,\n providerSupportsApiKey,\n providerSupportsOAuth,\n} from '../../../providers/index.js';\nimport {\n getProviderHint,\n getRecommendedModelsForProvider,\n sortModelsForPicker,\n sortProvidersForPicker,\n} from '../../../providers/presentation.js';\nimport { listProfilesForProvider } from '../../../auth/profiles/index.js';\nimport { getOAuthProvider } from '../../utils/oauth-providers.js';\nimport { runCliOAuthLogin } from '../../utils/oauth-login.js';\nimport type { CLIContext } from '../../registry.js';\nimport { colors } from '../../utils/colors.js';\n\ntype ModelChoice = { value: string; name: string; description?: string };\ntype CustomApiKind = 'openai-completions' | 'openai-responses' | 'anthropic-messages';\n\nfunction ensureAgentDefaults(config: Config, workspacePath: string): NonNullable<Config['agents']>['defaults'] {\n config.agents = config.agents || {};\n config.agents.defaults = config.agents.defaults || {\n workspace: workspacePath,\n maxTokens: 8192,\n temperature: 0.7,\n maxToolIterations: 20,\n maxRequestsPerTurn: 50,\n maxToolFailuresPerTurn: 3,\n };\n config.agents.defaults.workspace = workspacePath;\n return config.agents.defaults;\n}\n\nfunction setPrimaryModel(config: Config, workspacePath: string, modelRef: string): Config {\n const defaults = ensureAgentDefaults(config, workspacePath);\n defaults.model = { primary: modelRef, fallbacks: [] };\n return config;\n}\n\nfunction formatRecommended(provider: string): string | undefined {\n const labels = getRecommendedModelsForProvider(provider, 3).map((m) => m.name || m.id);\n return labels.length > 0 ? `Recommended: ${labels.join(', ')}` : getProviderHint(provider);\n}\n\nfunction providerChoices(): Array<{ value: string; name: string; description?: string }> {\n const providers = sortProvidersForPicker(getAllProviders());\n const choices = providers.map((provider) => ({\n value: provider,\n name: getProviderDisplayName(provider),\n description: formatRecommended(provider),\n }));\n choices.push({\n value: 'custom-api',\n name: 'Custom API',\n description: 'OpenAI-compatible, OpenAI Responses, or Anthropic-compatible endpoint',\n });\n return choices;\n}\n\nfunction modelChoiceName(choice: ModelChoice): string {\n return choice.description ? `${choice.name} ${colors.gray(choice.description)}` : choice.name;\n}\n\nfunction getModelsForProvider(provider: string): ModelChoice[] {\n return sortModelsForPicker(getModelsByProvider(provider)).map((m) => {\n const badges = [m.reasoning ? 'reasoning' : '', m.input?.includes('image') ? 'vision' : '']\n .filter(Boolean)\n .join(', ');\n return {\n value: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n description: badges || undefined,\n };\n });\n}\n\nasync function doOAuthLogin(provider: string): Promise<boolean> {\n const config = getOAuthProvider(provider);\n if (!config) {\n console.error(`OAuth not supported for provider: ${provider}`);\n return false;\n }\n\n console.log(`\\n🔐 Starting ${config.displayName} OAuth login...`);\n try {\n await runCliOAuthLogin({ provider, onProgress: (message) => console.log(' →', message) });\n return true;\n } catch (error) {\n console.error('❌ OAuth login failed:', error);\n return false;\n }\n}\n\nfunction joinEndpoint(baseUrl: string, suffix: string): string {\n return `${baseUrl.replace(/\\/+$/, '')}/${suffix.replace(/^\\/+/, '')}`;\n}\n\nasync function probeCustomProvider(params: {\n baseUrl: string;\n apiKey: string;\n modelId: string;\n api: CustomApiKind;\n}): Promise<void> {\n const timeout = AbortSignal.timeout(30_000);\n const commonHeaders = {\n 'content-type': 'application/json',\n authorization: `Bearer ${params.apiKey}`,\n };\n\n const request =\n params.api === 'anthropic-messages'\n ? {\n url: joinEndpoint(params.baseUrl, '/messages'),\n headers: { ...commonHeaders, 'anthropic-version': '2023-06-01' },\n body: { model: params.modelId, max_tokens: 1, messages: [{ role: 'user', content: 'ping' }] },\n }\n : params.api === 'openai-responses'\n ? {\n url: joinEndpoint(params.baseUrl, '/responses'),\n headers: commonHeaders,\n body: { model: params.modelId, input: 'ping', max_output_tokens: 1 },\n }\n : {\n url: joinEndpoint(params.baseUrl, '/chat/completions'),\n headers: commonHeaders,\n body: { model: params.modelId, messages: [{ role: 'user', content: 'ping' }], max_tokens: 1 },\n };\n\n const res = await fetch(request.url, {\n method: 'POST',\n headers: request.headers,\n body: JSON.stringify(request.body),\n signal: timeout,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Probe failed (${res.status}) at ${request.url}${text ? `: ${text.slice(0, 300)}` : ''}`);\n }\n}\n\nfunction defaultProviderIdFromBaseUrl(baseUrl: string): string {\n try {\n const url = new URL(baseUrl);\n return url.hostname\n .replace(/^api\\./, '')\n .split('.')\n .filter(Boolean)\n .slice(0, 2)\n .join('-')\n .toLowerCase()\n .replace(/[^a-z0-9-_]+/g, '-')\n .replace(/^-+|-+$/g, '') || 'custom';\n } catch {\n return 'custom';\n }\n}\n\nfunction validateProviderId(value: string): true | string {\n return /^[a-z0-9]([a-z0-9-_]*[a-z0-9])?$/.test(value)\n ? true\n : 'Use lowercase letters, numbers, hyphens, or underscores; start/end with a letter or number.';\n}\n\nasync function setupCustomApi(config: Config, ctx: CLIContext): Promise<Config> {\n console.log('\\n🔌 Custom API provider\\n');\n\n const baseUrl = (await input({\n message: 'API base URL:',\n default: 'https://api.example.com/v1',\n validate: (value) => URL.canParse(value) || 'Enter a valid URL',\n })).trim();\n\n const apiKey = (await input({\n message: 'API key:',\n validate: (value) => value.trim().length > 0 || 'Required',\n })).trim();\n\n const api = await select<CustomApiKind>({\n message: 'Compatibility:',\n choices: [\n { value: 'openai-completions', name: 'OpenAI-compatible Chat Completions' },\n { value: 'openai-responses', name: 'OpenAI Responses' },\n { value: 'anthropic-messages', name: 'Anthropic Messages' },\n ],\n });\n\n const modelId = (await input({\n message: 'Model ID:',\n validate: (value) => value.trim().length > 0 || 'Required',\n })).trim();\n\n console.log('\\n→ Probing endpoint...');\n await probeCustomProvider({ baseUrl, apiKey, modelId, api });\n console.log(colors.green('✓ Probe succeeded'));\n\n const existingProviders = new Set(getAllProviders());\n const providerId = (await input({\n message: 'Provider ID:',\n default: defaultProviderIdFromBaseUrl(baseUrl),\n validate: (value) => {\n const valid = validateProviderId(value.trim());\n if (valid !== true) return valid;\n if (existingProviders.has(value.trim())) return 'Provider ID already exists; choose a new ID.';\n return true;\n },\n })).trim();\n\n const path = getModelsJsonPath();\n const { config: modelsConfig } = loadModelsJson(path);\n const nextModelsConfig: ModelsJsonConfig = {\n providers: {\n ...(modelsConfig.providers ?? {}),\n [providerId]: {\n baseUrl,\n apiKey,\n api,\n models: [{ id: modelId, name: modelId, api, contextWindow: 128000, input: ['text'] }],\n },\n },\n };\n const saved = saveModelsJson(path, nextModelsConfig);\n if (!saved.success) {\n throw new Error(saved.error || 'Failed to save models.json');\n }\n getModelRegistry().refresh();\n\n const ref = `${providerId}/${modelId}`;\n console.log('\\n✅ Custom model configured:', ref);\n return setPrimaryModel(config, ctx.workspacePath, ref);\n}\n\nasync function ensureProviderCredential(provider: string, providerName: string): Promise<boolean> {\n const existingProfiles = listProfilesForProvider(provider);\n if (existingProfiles.length > 0) {\n console.log(`\\n${colors.green('✓')} Found existing credentials for ${providerName}`);\n return await confirm({ message: 'Use existing credentials?', default: true });\n }\n\n const envKey = getApiKeyFromEnv(provider);\n if (envKey) {\n console.log(`\\n${colors.green('✓')} Found API key for ${providerName} in environment`);\n return true;\n }\n\n const supportsOAuth = providerSupportsOAuth(provider);\n const supportsApiKey = providerSupportsApiKey(provider);\n\n if (supportsOAuth && !supportsApiKey) {\n return await doOAuthLogin(provider);\n }\n\n let useOAuth = false;\n if (supportsOAuth && supportsApiKey) {\n const authMethod = await select<'api_key' | 'oauth'>({\n message: `How would you like to authenticate with ${providerName}?`,\n choices: [\n { value: 'api_key', name: 'API key' },\n { value: 'oauth', name: 'OAuth login' },\n ],\n });\n useOAuth = authMethod === 'oauth';\n }\n\n if (useOAuth) {\n const ok = await doOAuthLogin(provider);\n if (ok) return true;\n console.log('\\n⚠️ OAuth login failed. Enter an API key instead.');\n }\n\n if (!supportsApiKey) return false;\n const apiKey = await input({\n message: `API key for ${providerName}:`,\n validate: (value) => value.trim().length > 0 || 'Required',\n });\n const resolver = new CredentialResolver();\n await resolver.saveApiKey(provider, apiKey.trim(), { profileName: 'default' });\n return true;\n}\n\nexport async function setupModel(existingConfig: Config | null, ctx: CLIContext): Promise<Config> {\n console.log('\\n🤖 Step: AI Model\\n');\n\n const config = existingConfig || ({} as Config);\n const currentModel = config?.agents?.defaults?.model?.primary;\n\n if (currentModel) {\n console.log('Current model:', currentModel);\n const keepCurrent = await confirm({ message: 'Keep using this model?', default: true });\n if (keepCurrent) {\n console.log('✅ Keeping:', currentModel);\n return config;\n }\n }\n\n const provider = await select({\n message: 'Model/auth provider:',\n choices: providerChoices(),\n });\n\n if (provider === 'custom-api') {\n return await setupCustomApi(config, ctx);\n }\n\n const providerName = getProviderDisplayName(provider);\n const credentialOk = await ensureProviderCredential(provider, providerName);\n if (!credentialOk) {\n console.error(`\\n❌ Could not configure credentials for ${providerName}.`);\n return config;\n }\n\n const modelChoices = getModelsForProvider(provider);\n if (modelChoices.length === 0) {\n throw new Error(`No catalog models found for ${providerName}. Add a Custom API provider instead.`);\n }\n\n console.log(`\\n📋 Models for ${providerName}:`);\n const model = await select({\n message: 'Model:',\n choices: modelChoices.map((choice) => ({ ...choice, name: modelChoiceName(choice) })),\n });\n\n console.log('\\n✅ Model configured:', model);\n return setPrimaryModel(config, ctx.workspacePath, model);\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from '../../auth/index.js';
2
+ import { type OAuthProviderConfig } from './oauth-providers.js';
3
+ export interface RunCliOAuthLoginOptions {
4
+ provider: string;
5
+ onProgress?: (message: string) => void;
6
+ }
7
+ export interface RunCliOAuthLoginResult {
8
+ provider: string;
9
+ credentials: OAuthCredentials;
10
+ expires?: number;
11
+ }
12
+ export declare function buildCliOAuthCallbacks(params: {
13
+ config: OAuthProviderConfig;
14
+ provider: OAuthProviderInterface;
15
+ onProgress?: (message: string) => void;
16
+ }): OAuthLoginCallbacks;
17
+ export declare function runCliOAuthLogin(options: RunCliOAuthLoginOptions): Promise<RunCliOAuthLoginResult>;
@@ -0,0 +1,93 @@
1
+ import { createLogger } from "../../utils/logger/index.js";
2
+ import { init_logger } from "../../utils/logger.js";
3
+ import { CredentialResolver, init_credentials } from "../../auth/credentials.js";
4
+ import { getOAuthProvider } from "./oauth-providers.js";
5
+ import { input, select } from "@inquirer/prompts";
6
+ //#region src/cli/utils/oauth-login.ts
7
+ init_credentials();
8
+ init_logger();
9
+ const log = createLogger("OAuthLogin");
10
+ async function openBrowser(url) {
11
+ const { spawn } = await import("node:child_process");
12
+ const platform = process.platform;
13
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
14
+ const child = spawn(command, platform === "win32" ? [
15
+ "/c",
16
+ "start",
17
+ "",
18
+ url
19
+ ] : [url], {
20
+ stdio: "ignore",
21
+ detached: true
22
+ });
23
+ child.on("error", (err) => {
24
+ log.debug({
25
+ err,
26
+ command
27
+ }, "Browser open command failed");
28
+ });
29
+ child.unref();
30
+ }
31
+ function buildCliOAuthCallbacks(params) {
32
+ let manualPromptPromise;
33
+ const promptForManualCode = () => manualPromptPromise ??= input({
34
+ message: "Paste the authorization code (or full redirect URL):",
35
+ validate: (value) => value.trim().length > 0 || "Required"
36
+ });
37
+ return {
38
+ onAuth: (info) => {
39
+ console.log("\n" + params.config.urlPrompt);
40
+ console.log(info.url);
41
+ if (info.instructions) console.log("\n" + info.instructions);
42
+ if (params.provider.usesCallbackServer) console.log("\nWaiting for localhost callback. If it does not complete, paste the final redirect URL below.");
43
+ openBrowser(info.url);
44
+ console.log("");
45
+ },
46
+ onDeviceCode: (info) => {
47
+ console.log(`\nOpen ${info.verificationUri} and enter code ${info.userCode}\n`);
48
+ openBrowser(info.verificationUri);
49
+ },
50
+ onPrompt: async (prompt) => input({
51
+ message: prompt.message,
52
+ validate: prompt.allowEmpty ? void 0 : (value) => value.trim().length > 0 || "Required"
53
+ }),
54
+ onManualCodeInput: params.provider.usesCallbackServer && params.provider.id !== "openai-codex" ? promptForManualCode : void 0,
55
+ onProgress: (message) => {
56
+ params.onProgress?.(message);
57
+ },
58
+ onSelect: async (prompt) => {
59
+ return select({
60
+ message: prompt.message,
61
+ choices: prompt.options.map((option) => ({
62
+ value: option.id,
63
+ name: option.label
64
+ }))
65
+ });
66
+ }
67
+ };
68
+ }
69
+ async function runCliOAuthLogin(options) {
70
+ const config = getOAuthProvider(options.provider);
71
+ if (!config) throw new Error(`OAuth not supported for provider: ${options.provider}`);
72
+ const credentials = await config.provider.login(buildCliOAuthCallbacks({
73
+ config,
74
+ provider: config.provider,
75
+ onProgress: options.onProgress
76
+ }));
77
+ await new CredentialResolver().saveOAuthToken(options.provider, {
78
+ access: config.provider.getApiKey(credentials),
79
+ refresh: credentials.refresh,
80
+ expiresAt: credentials.expires,
81
+ scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value) => typeof value === "string") : void 0,
82
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
83
+ });
84
+ return {
85
+ provider: options.provider,
86
+ credentials,
87
+ expires: credentials.expires
88
+ };
89
+ }
90
+ //#endregion
91
+ export { buildCliOAuthCallbacks, runCliOAuthLogin };
92
+
93
+ //# sourceMappingURL=oauth-login.js.map