@xopcai/xopc 0.0.132 → 0.0.133

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 (106) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CnZIdUyv.js → agent-avatar-dicebear-Ab81VpCx.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agents-C-rdLOrb.js → agents-HCGFJG8I.js} +1 -1
  5. package/dist/gateway/static/root/assets/{app-management-settings-panel-BcvaXb7T.js → app-management-settings-panel-DMI_sbnc.js} +1 -1
  6. package/dist/gateway/static/root/assets/{appearance-settings-CuxzNz-I.js → appearance-settings-oQiCWQIQ.js} +1 -1
  7. package/dist/gateway/static/root/assets/{apps-page-_VJm0o44.js → apps-page-CRYuPIdb.js} +1 -1
  8. package/dist/gateway/static/root/assets/{archive-plugin-CcXXh0X9.js → archive-plugin-BaKuWJws.js} +1 -1
  9. package/dist/gateway/static/root/assets/{attachment-load-BYhYBw17.js → attachment-load-DYCFtbAA.js} +1 -1
  10. package/dist/gateway/static/root/assets/{automation-suggestion-card-ByjDtlOw.js → automation-suggestion-card-C6ZIz_B1.js} +1 -1
  11. package/dist/gateway/static/root/assets/{automations-page-DUFct5t3.js → automations-page-DjbtV2VP.js} +1 -1
  12. package/dist/gateway/static/root/assets/{binary-plugins-B1A5KhWX.js → binary-plugins-Bdg1hfhV.js} +1 -1
  13. package/dist/gateway/static/root/assets/{block-editor-DWpXiVTf.js → block-editor-CEgvbzpG.js} +1 -1
  14. package/dist/gateway/static/root/assets/{browser-settings-page-Cff75b5Z.js → browser-settings-page-KcwlS_iE.js} +1 -1
  15. package/dist/gateway/static/root/assets/{capability-presets-api-DFWL6PJ0.js → capability-presets-api-CC3vmrm5.js} +1 -1
  16. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-CBT89nI-.js → capability-presets-settings-panel-Di4uiKbv.js} +1 -1
  17. package/dist/gateway/static/root/assets/{channels-settings-DHBjZ2fa.js → channels-settings-DMa0BEWf.js} +1 -1
  18. package/dist/gateway/static/root/assets/{connectors-page-B4CGNErH.js → connectors-page-DIZKYaMj.js} +1 -1
  19. package/dist/gateway/static/root/assets/{desktop-pet-CDD29oYv.js → desktop-pet-CNnlqbkx.js} +1 -1
  20. package/dist/gateway/static/root/assets/{desktop-pet-settings-CEkfvSnb.js → desktop-pet-settings-ukJCc0LA.js} +1 -1
  21. package/dist/gateway/static/root/assets/{directory-picker-path-field-JbVQ2_md.js → directory-picker-path-field-8hoieEyt.js} +1 -1
  22. package/dist/gateway/static/root/assets/{dreaming-settings-C-fzh8SN.js → dreaming-settings-BOrBSNLL.js} +1 -1
  23. package/dist/gateway/static/root/assets/{extension-debug-page-B4jZ0Yim.js → extension-debug-page-D6Jc0hYZ.js} +1 -1
  24. package/dist/gateway/static/root/assets/{extension-page-Bi0GmOZ2.js → extension-page-CJMhBASX.js} +1 -1
  25. package/dist/gateway/static/root/assets/{extension-settings-page-BJpxz8Dq.js → extension-settings-page-Dt-FWyMN.js} +1 -1
  26. package/dist/gateway/static/root/assets/{gateway-config-api-Ci1tJtJ4.js → gateway-config-api-CyYi-a3k.js} +1 -1
  27. package/dist/gateway/static/root/assets/{gateway-settings-C-YBJHLX.js → gateway-settings-yJvCmOBC.js} +1 -1
  28. package/dist/gateway/static/root/assets/{goal-create-dialog-DhfNsglx.js → goal-create-dialog-B3PLFUAl.js} +1 -1
  29. package/dist/gateway/static/root/assets/{goal-detail-page-9WeYgD-u.js → goal-detail-page-BQXW29kk.js} +1 -1
  30. package/dist/gateway/static/root/assets/{goals-config-api-DRJGqa_W.js → goals-config-api-FU_bdYvR.js} +1 -1
  31. package/dist/gateway/static/root/assets/{goals-page-DXEQqzEX.js → goals-page-BmppU6mC.js} +1 -1
  32. package/dist/gateway/static/root/assets/{goals-settings-5pFGRkD6.js → goals-settings-BsXWV6CN.js} +1 -1
  33. package/dist/gateway/static/root/assets/{heartbeat-settings-CGX78-fM.js → heartbeat-settings-BqwSCZLD.js} +1 -1
  34. package/dist/gateway/static/root/assets/index-DXUkWmeK.css +1 -0
  35. package/dist/gateway/static/root/assets/{index-BjGnv8uJ.js → index-DqFBdoK-.js} +31 -31
  36. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-D23eHsli.js → keyboard-shortcuts-settings-D8J0hkTt.js} +1 -1
  37. package/dist/gateway/static/root/assets/{logs-page-jMLLOxWk.js → logs-page-Cr5ptDtc.js} +1 -1
  38. package/dist/gateway/static/root/assets/{markdown-split-Dn7rJV-v.js → markdown-split-w-hIAZ9S.js} +1 -1
  39. package/dist/gateway/static/root/assets/{media-plugins-DOnTRGyE.js → media-plugins-DjFnDy3R.js} +1 -1
  40. package/dist/gateway/static/root/assets/{models-hub-panel-Br6l-Di9.js → models-hub-panel-DEsMnITi.js} +1 -1
  41. package/dist/gateway/static/root/assets/{notes-page-BiXfX1he.js → notes-page-D7bHXaeR.js} +1 -1
  42. package/dist/gateway/static/root/assets/{notes-workbench-DjaTvXj3.js → notes-workbench-DiuG4Ov7.js} +2 -2
  43. package/dist/gateway/static/root/assets/{page-tabs-CYEVap7D.js → page-tabs-smjfSAwc.js} +1 -1
  44. package/dist/gateway/static/root/assets/{preview-open-alternatives-Dk7Cjd6E.js → preview-open-alternatives-v02THugE.js} +1 -1
  45. package/dist/gateway/static/root/assets/{project-detail-page-B6UccBNl.js → project-detail-page-Us3zDRE6.js} +1 -1
  46. package/dist/gateway/static/root/assets/{projects-page-B56s8p-Q.js → projects-page-BcvW8R0Z.js} +1 -1
  47. package/dist/gateway/static/root/assets/{remote-access-hub-D3ekwIaB.js → remote-access-hub-Bun2k0V-.js} +1 -1
  48. package/dist/gateway/static/root/assets/{save-bar-controls-D7FzAPOc.js → save-bar-controls-CD7LPbLU.js} +1 -1
  49. package/dist/gateway/static/root/assets/{schedule-field-BZP1-U9M.js → schedule-field-Cf3p_M0z.js} +1 -1
  50. package/dist/gateway/static/root/assets/{schema-form-1NNFTrvK.js → schema-form-D8XdvcNH.js} +1 -1
  51. package/dist/gateway/static/root/assets/{sessions-page-08vT4R9X.js → sessions-page-Bh44TVld.js} +1 -1
  52. package/dist/gateway/static/root/assets/settings-advanced-gate-BhyLZ0IW.js +1 -0
  53. package/dist/gateway/static/root/assets/{settings-collapsible-section-CX5IUGk1.js → settings-collapsible-section-BVpPSgMs.js} +1 -1
  54. package/dist/gateway/static/root/assets/{settings-form-section-a71ujXN4.js → settings-form-section-GAwBG9Vp.js} +1 -1
  55. package/dist/gateway/static/root/assets/{settings-loading-skeleton-D4mq628A.js → settings-loading-skeleton-CGxa3aCb.js} +1 -1
  56. package/dist/gateway/static/root/assets/{settings-page-DSQWZsZt.js → settings-page-bZKl6uRR.js} +1 -1
  57. package/dist/gateway/static/root/assets/{settings-page-layout-BG-k3_CF.js → settings-page-layout-DfbMLJgN.js} +1 -1
  58. package/dist/gateway/static/root/assets/{setup-status-panel-CFBgxLKm.js → setup-status-panel-DBIruh16.js} +1 -1
  59. package/dist/gateway/static/root/assets/{share-preview-page-Cz4X3Vcf.js → share-preview-page-BglBMvA0.js} +1 -1
  60. package/dist/gateway/static/root/assets/{shares-settings-BHjEkA9t.js → shares-settings-C0fR-Oto.js} +1 -1
  61. package/dist/gateway/static/root/assets/skill-reload-api-BG-FEo9B.js +1 -0
  62. package/dist/gateway/static/root/assets/{skills-page-C0KVNC0o.js → skills-page-CwwZOoCH.js} +1 -1
  63. package/dist/gateway/static/root/assets/{system-settings-panel-Bo9mLewC.js → system-settings-panel-CTT0BkbS.js} +1 -1
  64. package/dist/gateway/static/root/assets/{typed-models-lib-Bp6wXydR.js → typed-models-lib-Fa9PjI0-.js} +1 -1
  65. package/dist/gateway/static/root/assets/{user-profile-settings-panel-NcG1KFUf.js → user-profile-settings-panel-pCK-aMqu.js} +1 -1
  66. package/dist/gateway/static/root/assets/{utils-BNtcPshb.js → utils-aleluS3U.js} +1 -1
  67. package/dist/gateway/static/root/assets/{voice-api-key-field-ByppRAVD.js → voice-api-key-field-Fh4mqUIK.js} +1 -1
  68. package/dist/gateway/static/root/assets/{work-item-detail-page-5wdg0Xmy.js → work-item-detail-page-C0_3nId3.js} +1 -1
  69. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-eKBZIV-N.js → workflow-run-setup-panel-r969H1r2.js} +1 -1
  70. package/dist/gateway/static/root/assets/{workflow-start-dialog-B6qfU5GE.js → workflow-start-dialog-C1GbcWN0.js} +1 -1
  71. package/dist/gateway/static/root/assets/{workflows-page-BtLILZtw.js → workflows-page-DsPM3HEI.js} +1 -1
  72. package/dist/gateway/static/root/index.html +2 -2
  73. package/dist/package.js +1 -1
  74. package/dist/src/agent/image/tool-model-config.js +5 -5
  75. package/dist/src/agent/image/tool-model-config.js.map +1 -1
  76. package/dist/src/agent/transcript/thinking-types.js +2 -0
  77. package/dist/src/agent/transcript/thinking-types.js.map +1 -1
  78. package/dist/src/auth/oauth/google-oauth/google-gemini-cli.d.ts +1 -1
  79. package/dist/src/auth/oauth/google-oauth/google-gemini-cli.js.map +1 -1
  80. package/dist/src/chat-commands/builtins/model.js +3 -3
  81. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  82. package/dist/src/cli/commands/models.js +9 -0
  83. package/dist/src/cli/commands/models.js.map +1 -1
  84. package/dist/src/config/agent-profile.js +1 -1
  85. package/dist/src/gateway/agents-admin.js +1 -1
  86. package/dist/src/gateway/hono/routes/status.js +2 -2
  87. package/dist/src/gateway/hono/routes/status.js.map +1 -1
  88. package/dist/src/gateway/hono/routes/voice.js +1 -1
  89. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  90. package/dist/src/providers/domestic-presets.d.ts +5 -15
  91. package/dist/src/providers/domestic-presets.js +31 -49
  92. package/dist/src/providers/domestic-presets.js.map +1 -1
  93. package/dist/src/providers/index.js +19 -5
  94. package/dist/src/providers/index.js.map +1 -1
  95. package/dist/src/providers/model-registry.js.map +1 -1
  96. package/dist/src/providers/model-supplements.d.ts +6 -0
  97. package/dist/src/providers/model-supplements.js +22 -0
  98. package/dist/src/providers/model-supplements.js.map +1 -0
  99. package/dist/src/providers/presentation.js +25 -26
  100. package/dist/src/providers/presentation.js.map +1 -1
  101. package/dist/src/voice/tts/summarize.js +1 -1
  102. package/dist/src/voice/tts/summarize.js.map +1 -1
  103. package/package.json +5 -5
  104. package/dist/gateway/static/root/assets/index-W4ogAiHt.css +0 -1
  105. package/dist/gateway/static/root/assets/settings-advanced-gate-IsaFJ6ZC.js +0 -1
  106. package/dist/gateway/static/root/assets/skill-reload-api-CfHG_ekm.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"google-gemini-cli.js","names":[],"sources":["../../../../../src/auth/oauth/google-oauth/google-gemini-cli.ts"],"sourcesContent":["/**\n * Gemini CLI OAuth flow (Google Cloud Code Assist)\n * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport type { Server } from \"node:http\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.js\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"../types.js\";\n\ntype GeminiCredentials = OAuthCredentials & {\n\tprojectId: string;\n};\n\nlet _createServer: typeof import(\"node:http\").createServer | null = null;\nlet _httpImportPromise: Promise<void> | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\t_httpImportPromise = import(\"node:http\").then((m) => {\n\t\t_createServer = m.createServer;\n\t});\n}\n\nconst GEMINI_CLI_CLIENT_ID_ENV = 'XOPC_GOOGLE_GEMINI_CLI_OAUTH_CLIENT_ID';\nconst GEMINI_CLI_CLIENT_SECRET_ENV = 'XOPC_GOOGLE_GEMINI_CLI_OAUTH_CLIENT_SECRET';\n\nfunction getGeminiCliOAuthClient(): { clientId: string; clientSecret: string } {\n\tconst clientId = process.env[GEMINI_CLI_CLIENT_ID_ENV]?.trim();\n\tconst clientSecret = process.env[GEMINI_CLI_CLIENT_SECRET_ENV]?.trim();\n\tif (!clientId || !clientSecret) {\n\t\tthrow new Error(\n\t\t\t`Google Gemini CLI OAuth requires ${GEMINI_CLI_CLIENT_ID_ENV} and ${GEMINI_CLI_CLIENT_SECRET_ENV}.`,\n\t\t);\n\t}\n\treturn { clientId, clientSecret };\n}\n\nconst REDIRECT_URI = \"http://localhost:8085/oauth2callback\";\nconst SCOPES = [\n\t\"https://www.googleapis.com/auth/cloud-platform\",\n\t\"https://www.googleapis.com/auth/userinfo.email\",\n\t\"https://www.googleapis.com/auth/userinfo.profile\",\n];\nconst AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst CODE_ASSIST_ENDPOINT = \"https://cloudcode-pa.googleapis.com\";\n\ntype CallbackServerInfo = {\n\tserver: Server;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string; state: string } | null>;\n};\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nasync function getNodeCreateServer(): Promise<typeof import(\"node:http\").createServer> {\n\tif (_createServer) return _createServer;\n\tif (_httpImportPromise) {\n\t\tawait _httpImportPromise;\n\t}\n\tif (_createServer) return _createServer;\n\tthrow new Error(\"Gemini CLI OAuth is only available in Node.js environments\");\n}\n\nasync function startCallbackServer(): Promise<CallbackServerInfo> {\n\tconst createServer = await getNodeCreateServer();\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settleWait: ((value: { code: string; state: string } | null) => void) | undefined;\n\t\tconst waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {\n\t\t\tlet settled = false;\n\t\t\tsettleWait = (value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolveWait(value);\n\t\t\t};\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:8085`);\n\n\t\t\tif (url.pathname === \"/oauth2callback\") {\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Google authentication did not complete.\", `Error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code && state) {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthSuccessHtml(\"Google authentication completed. You can close this window.\"));\n\t\t\t\t\tsettleWait?.({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(8085, () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t},\n\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\n/**\n * Parse redirect URL to extract code and state\n */\nfunction parseRedirectUrl(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// Not a URL, return empty\n\t\treturn {};\n\t}\n}\n\ninterface LoadCodeAssistPayload {\n\tcloudaicompanionProject?: string;\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\n/**\n * Long-running operation response from onboardUser\n */\ninterface LongRunningOperationResponse {\n\tname?: string;\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n// Tier IDs as used by the Cloud Code API\nconst TIER_FREE = \"free-tier\";\nconst TIER_STANDARD = \"standard-tier\";\n\ninterface GoogleRpcErrorResponse {\n\terror?: {\n\t\tdetails?: Array<{ reason?: string }>;\n\t};\n}\n\n/**\n * Wait helper for onboarding retries\n */\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Get default tier from allowed tiers\n */\nfunction getDefaultTier(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): { id?: string } {\n\tif (!allowedTiers || allowedTiers.length === 0) return { id: TIER_FREE };\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier ?? allowedTiers[0] ?? { id: TIER_FREE };\n}\n\nfunction isVpcScAffectedUser(payload: unknown): boolean {\n\tif (!payload || typeof payload !== \"object\") return false;\n\tif (!(\"error\" in payload)) return false;\n\tconst error = (payload as GoogleRpcErrorResponse).error;\n\tif (!error?.details || !Array.isArray(error.details)) return false;\n\treturn error.details.some((detail) => detail.reason === \"SECURITY_POLICY_VIOLATED\");\n}\n\n/**\n * Poll a long-running operation until completion\n */\nasync function pollOperation(\n\toperationName: string,\n\theaders: Record<string, string>,\n\tonProgress?: (message: string) => void,\n): Promise<LongRunningOperationResponse> {\n\tlet attempt = 0;\n\twhile (true) {\n\t\tif (attempt > 0) {\n\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 1})...`);\n\t\t\tawait wait(5000);\n\t\t}\n\n\t\tconst response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to poll operation: ${response.status} ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as LongRunningOperationResponse;\n\t\tif (data.done) {\n\t\t\treturn data;\n\t\t}\n\n\t\tattempt += 1;\n\t}\n}\n\n/**\n * Discover or provision a Google Cloud project for the user\n */\nasync function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {\n\t// Check for user-provided project ID via environment variable\n\tconst envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;\n\n\tconst headers = {\n\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n\t\t\"X-Goog-Api-Client\": \"gl-node/22.17.0\",\n\t};\n\n\t// Try to load existing project via loadCodeAssist\n\tonProgress?.(\"Checking for existing Cloud Code Assist project...\");\n\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({\n\t\t\tcloudaicompanionProject: envProjectId,\n\t\t\tmetadata: {\n\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t\tduetProject: envProjectId,\n\t\t\t},\n\t\t}),\n\t});\n\n\tlet data: LoadCodeAssistPayload;\n\n\tif (!loadResponse.ok) {\n\t\tlet errorPayload: unknown;\n\t\ttry {\n\t\t\terrorPayload = await loadResponse.clone().json();\n\t\t} catch {\n\t\t\terrorPayload = undefined;\n\t\t}\n\n\t\tif (isVpcScAffectedUser(errorPayload)) {\n\t\t\tdata = { currentTier: { id: TIER_STANDARD } };\n\t\t} else {\n\t\t\tconst errorText = await loadResponse.text();\n\t\t\tthrow new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);\n\t\t}\n\t} else {\n\t\tdata = (await loadResponse.json()) as LoadCodeAssistPayload;\n\t}\n\n\t// If user already has a current tier and project, use it\n\tif (data.currentTier) {\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\t\t// User has a tier but no managed project - they need to provide one via env var\n\t\tif (envProjectId) {\n\t\t\treturn envProjectId;\n\t\t}\n\t\tthrow new Error(\n\t\t\t\"This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t\t);\n\t}\n\n\t// User needs to be onboarded - get the default tier\n\tconst tier = getDefaultTier(data.allowedTiers);\n\tconst tierId = tier?.id ?? TIER_FREE;\n\n\tif (tierId !== TIER_FREE && !envProjectId) {\n\t\tthrow new Error(\n\t\t\t\"This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t\t);\n\t}\n\n\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t// Build onboard request - for free tier, don't include project ID (Google provisions one)\n\t// For other tiers, include the user's project ID if available\n\tconst onboardBody: Record<string, unknown> = {\n\t\ttierId,\n\t\tmetadata: {\n\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\tpluginType: \"GEMINI\",\n\t\t},\n\t};\n\n\tif (tierId !== TIER_FREE && envProjectId) {\n\t\tonboardBody.cloudaicompanionProject = envProjectId;\n\t\t(onboardBody.metadata as Record<string, unknown>).duetProject = envProjectId;\n\t}\n\n\t// Start onboarding - this returns a long-running operation\n\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify(onboardBody),\n\t});\n\n\tif (!onboardResponse.ok) {\n\t\tconst errorText = await onboardResponse.text();\n\t\tthrow new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}: ${errorText}`);\n\t}\n\n\tlet lroData = (await onboardResponse.json()) as LongRunningOperationResponse;\n\n\t// If the operation isn't done yet, poll until completion\n\tif (!lroData.done && lroData.name) {\n\t\tlroData = await pollOperation(lroData.name, headers, onProgress);\n\t}\n\n\t// Try to get project ID from the response\n\tconst projectId = lroData.response?.cloudaicompanionProject?.id;\n\tif (projectId) {\n\t\treturn projectId;\n\t}\n\n\t// If no project ID from onboarding, fall back to env var\n\tif (envProjectId) {\n\t\treturn envProjectId;\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Try setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t);\n}\n\n/**\n * Get user email from the access token\n */\nasync function getUserEmail(accessToken: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetch(\"https://www.googleapis.com/oauth2/v1/userinfo?alt=json\", {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\tconst data = (await response.json()) as { email?: string };\n\t\t\treturn data.email;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, email is optional\n\t}\n\treturn undefined;\n}\n\n/**\n * Refresh Google Cloud Code Assist token\n */\nexport async function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst { clientId, clientSecret } = getGeminiCliOAuthClient();\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: clientId,\n\t\t\tclient_secret: clientSecret,\n\t\t\trefresh_token: refreshToken,\n\t\t\tgrant_type: \"refresh_token\",\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Google Cloud token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\texpires_in: number;\n\t\trefresh_token?: string;\n\t};\n\n\treturn {\n\t\trefresh: data.refresh_token || refreshToken,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t\tprojectId,\n\t};\n}\n\n/**\n * Login with Gemini CLI (Google Cloud Code Assist) OAuth\n *\n * @param onAuth - Callback with URL and optional instructions\n * @param onProgress - Optional progress callback\n * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.\n * Races with browser callback - whichever completes first wins.\n */\nexport async function loginGeminiCli(\n\tonAuth: (info: { url: string; instructions?: string }) => void,\n\tonProgress?: (message: string) => void,\n\tonManualCodeInput?: () => Promise<string>,\n): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst { clientId, clientSecret } = getGeminiCliOAuthClient();\n\n\t// Start local server for callback\n\tonProgress?.(\"Starting local server for OAuth callback...\");\n\tconst server = await startCallbackServer();\n\n\tlet code: string | undefined;\n\n\ttry {\n\t\t// Build authorization URL\n\t\tconst authParams = new URLSearchParams({\n\t\t\tclient_id: clientId,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES.join(\" \"),\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t\taccess_type: \"offline\",\n\t\t\tprompt: \"consent\",\n\t\t});\n\n\t\tconst authUrl = `${AUTH_URL}?${authParams.toString()}`;\n\n\t\t// Notify caller with URL to open\n\t\tonAuth({\n\t\t\turl: authUrl,\n\t\t\tinstructions: \"Complete the sign-in in your browser.\",\n\t\t});\n\n\t\t// Wait for the callback, racing with manual input if provided\n\t\tonProgress?.(\"Waiting for OAuth callback...\");\n\n\t\tif (onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualInput: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualInput = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won - verify state\n\t\t\t\tif (result.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualInput) {\n\t\t\t\t// Manual input won\n\t\t\t\tconst parsed = parseRedirectUrl(manualInput);\n\t\t\t\tif (parsed.state && parsed.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualInput) {\n\t\t\t\t\tconst parsed = parseRedirectUrl(manualInput);\n\t\t\t\t\tif (parsed.state && parsed.state !== verifier) {\n\t\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: just wait for callback\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tif (result.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"No authorization code received\");\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tonProgress?.(\"Exchanging authorization code for tokens...\");\n\t\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: clientId,\n\t\t\t\tclient_secret: clientSecret,\n\t\t\t\tcode,\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t});\n\n\t\tif (!tokenResponse.ok) {\n\t\t\tconst error = await tokenResponse.text();\n\t\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t\t}\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t};\n\n\t\tif (!tokenData.refresh_token) {\n\t\t\tthrow new Error(\"No refresh token received. Please try again.\");\n\t\t}\n\n\t\t// Get user email\n\t\tonProgress?.(\"Getting user info...\");\n\t\tconst email = await getUserEmail(tokenData.access_token);\n\n\t\t// Discover project\n\t\tconst projectId = await discoverProject(tokenData.access_token, onProgress);\n\n\t\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\t\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t\tconst credentials: OAuthCredentials = {\n\t\t\trefresh: tokenData.refresh_token,\n\t\t\taccess: tokenData.access_token,\n\t\t\texpires: expiresAt,\n\t\t\tprojectId,\n\t\t\temail,\n\t\t};\n\n\t\treturn credentials;\n\t} finally {\n\t\tserver.server.close();\n\t}\n}\n\nexport const geminiCliOAuthProvider: OAuthProviderInterface = {\n\tid: \"google-gemini-cli\",\n\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGeminiCli(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as GeminiCredentials;\n\t\tif (!creds.projectId) {\n\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t}\n\t\treturn refreshGoogleCloudToken(creds.refresh, creds.projectId);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\tconst creds = credentials as GeminiCredentials;\n\t\treturn JSON.stringify({ token: creds.access, projectId: creds.projectId });\n\t},\n};\n"],"mappings":";;;AAiBA,IAAI,gBAAgE;AACpE,IAAI,qBAA2C;AAC/C,IAAI,OAAO,YAAY,gBAAgB,QAAQ,UAAU,QAAQ,QAAQ,UAAU,KAClF,sBAAqB,OAAO,aAAa,MAAM,MAAM;AACpD,iBAAgB,EAAE;EACjB;AAGH,MAAM,2BAA2B;AACjC,MAAM,+BAA+B;AAErC,SAAS,0BAAsE;CAC9E,MAAM,WAAW,QAAQ,IAAI,2BAA2B,MAAM;CAC9D,MAAM,eAAe,QAAQ,IAAI,+BAA+B,MAAM;AACtE,KAAI,CAAC,YAAY,CAAC,aACjB,OAAM,IAAI,MACT,oCAAoC,yBAAyB,OAAO,6BAA6B,GACjG;AAEF,QAAO;EAAE;EAAU;EAAc;;AAGlC,MAAM,eAAe;AACrB,MAAM,SAAS;CACd;CACA;CACA;CACA;AACD,MAAM,WAAW;AACjB,MAAM,YAAY;AAClB,MAAM,uBAAuB;;;;AAW7B,eAAe,sBAAwE;AACtF,KAAI,cAAe,QAAO;AAC1B,KAAI,mBACH,OAAM;AAEP,KAAI,cAAe,QAAO;AAC1B,OAAM,IAAI,MAAM,6DAA6D;;AAG9E,eAAe,sBAAmD;CACjE,MAAM,eAAe,MAAM,qBAAqB;AAEhD,QAAO,IAAI,SAAS,SAAS,WAAW;EACvC,IAAI;EACJ,MAAM,qBAAqB,IAAI,SAAiD,gBAAgB;GAC/F,IAAI,UAAU;AACd,iBAAc,UAAU;AACvB,QAAI,QAAS;AACb,cAAU;AACV,gBAAY,MAAM;;IAElB;EAEF,MAAM,SAAS,cAAc,KAAK,QAAQ;GACzC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,wBAAwB;AAE3D,OAAI,IAAI,aAAa,mBAAmB;IACvC,MAAM,OAAO,IAAI,aAAa,IAAI,OAAO;IACzC,MAAM,QAAQ,IAAI,aAAa,IAAI,QAAQ;IAC3C,MAAM,QAAQ,IAAI,aAAa,IAAI,QAAQ;AAE3C,QAAI,OAAO;AACV,SAAI,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AAClE,SAAI,IAAI,eAAe,2CAA2C,UAAU,QAAQ,CAAC;AACrF;;AAGD,QAAI,QAAQ,OAAO;AAClB,SAAI,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AAClE,SAAI,IAAI,iBAAiB,8DAA8D,CAAC;AACxF,kBAAa;MAAE;MAAM;MAAO,CAAC;WACvB;AACN,SAAI,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AAClE,SAAI,IAAI,eAAe,mCAAmC,CAAC;;UAEtD;AACN,QAAI,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AAClE,QAAI,IAAI,eAAe,4BAA4B,CAAC;;IAEpD;AAEF,SAAO,GAAG,UAAU,QAAQ;AAC3B,UAAO,IAAI;IACV;AAEF,SAAO,OAAO,YAAY;AACzB,WAAQ;IACP;IACA,kBAAkB;AACjB,kBAAa,KAAK;;IAEnB,mBAAmB;IACnB,CAAC;IACD;GACD;;;;;AAMH,SAAS,iBAAiB,OAAkD;CAC3E,MAAM,QAAQ,MAAM,MAAM;AAC1B,KAAI,CAAC,MAAO,QAAO,EAAE;AAErB,KAAI;EACH,MAAM,MAAM,IAAI,IAAI,MAAM;AAC1B,SAAO;GACN,MAAM,IAAI,aAAa,IAAI,OAAO,IAAI,KAAA;GACtC,OAAO,IAAI,aAAa,IAAI,QAAQ,IAAI,KAAA;GACxC;SACM;AAEP,SAAO,EAAE;;;AAsBX,MAAM,YAAY;AAClB,MAAM,gBAAgB;;;;AAWtB,SAAS,KAAK,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;AAMzD,SAAS,eAAe,cAA6E;AACpG,KAAI,CAAC,gBAAgB,aAAa,WAAW,EAAG,QAAO,EAAE,IAAI,WAAW;AAExE,QADoB,aAAa,MAAM,MAAM,EAAE,UAC7B,IAAI,aAAa,MAAM,EAAE,IAAI,WAAW;;AAG3D,SAAS,oBAAoB,SAA2B;AACvD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,KAAI,EAAE,WAAW,SAAU,QAAO;CAClC,MAAM,QAAS,QAAmC;AAClD,KAAI,CAAC,OAAO,WAAW,CAAC,MAAM,QAAQ,MAAM,QAAQ,CAAE,QAAO;AAC7D,QAAO,MAAM,QAAQ,MAAM,WAAW,OAAO,WAAW,2BAA2B;;;;;AAMpF,eAAe,cACd,eACA,SACA,YACwC;CACxC,IAAI,UAAU;AACd,QAAO,MAAM;AACZ,MAAI,UAAU,GAAG;AAChB,gBAAa,6CAA6C,UAAU,EAAE,MAAM;AAC5E,SAAM,KAAK,IAAK;;EAGjB,MAAM,WAAW,MAAM,MAAM,GAAG,qBAAqB,cAAc,iBAAiB;GACnF,QAAQ;GACR;GACA,CAAC;AAEF,MAAI,CAAC,SAAS,GACb,OAAM,IAAI,MAAM,6BAA6B,SAAS,OAAO,GAAG,SAAS,aAAa;EAGvF,MAAM,OAAQ,MAAM,SAAS,MAAM;AACnC,MAAI,KAAK,KACR,QAAO;AAGR,aAAW;;;;;;AAOb,eAAe,gBAAgB,aAAqB,YAAyD;CAE5G,MAAM,eAAe,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;CAErE,MAAM,UAAU;EACf,eAAe,UAAU;EACzB,gBAAgB;EAChB,cAAc;EACd,qBAAqB;EACrB;AAGD,cAAa,qDAAqD;CAClE,MAAM,eAAe,MAAM,MAAM,GAAG,qBAAqB,6BAA6B;EACrF,QAAQ;EACR;EACA,MAAM,KAAK,UAAU;GACpB,yBAAyB;GACzB,UAAU;IACT,SAAS;IACT,UAAU;IACV,YAAY;IACZ,aAAa;IACb;GACD,CAAC;EACF,CAAC;CAEF,IAAI;AAEJ,KAAI,CAAC,aAAa,IAAI;EACrB,IAAI;AACJ,MAAI;AACH,kBAAe,MAAM,aAAa,OAAO,CAAC,MAAM;UACzC;AACP,kBAAe,KAAA;;AAGhB,MAAI,oBAAoB,aAAa,CACpC,QAAO,EAAE,aAAa,EAAE,IAAI,eAAe,EAAE;OACvC;GACN,MAAM,YAAY,MAAM,aAAa,MAAM;AAC3C,SAAM,IAAI,MAAM,0BAA0B,aAAa,OAAO,GAAG,aAAa,WAAW,IAAI,YAAY;;OAG1G,QAAQ,MAAM,aAAa,MAAM;AAIlC,KAAI,KAAK,aAAa;AACrB,MAAI,KAAK,wBACR,QAAO,KAAK;AAGb,MAAI,aACH,QAAO;AAER,QAAM,IAAI,MACT,iKAEA;;CAKF,MAAM,SADO,eAAe,KAAK,aACd,EAAE,MAAM;AAE3B,KAAI,WAAW,aAAa,CAAC,aAC5B,OAAM,IAAI,MACT,iKAEA;AAGF,cAAa,qEAAqE;CAIlF,MAAM,cAAuC;EAC5C;EACA,UAAU;GACT,SAAS;GACT,UAAU;GACV,YAAY;GACZ;EACD;AAED,KAAI,WAAW,aAAa,cAAc;AACzC,cAAY,0BAA0B;AACrC,cAAY,SAAqC,cAAc;;CAIjE,MAAM,kBAAkB,MAAM,MAAM,GAAG,qBAAqB,0BAA0B;EACrF,QAAQ;EACR;EACA,MAAM,KAAK,UAAU,YAAY;EACjC,CAAC;AAEF,KAAI,CAAC,gBAAgB,IAAI;EACxB,MAAM,YAAY,MAAM,gBAAgB,MAAM;AAC9C,QAAM,IAAI,MAAM,uBAAuB,gBAAgB,OAAO,GAAG,gBAAgB,WAAW,IAAI,YAAY;;CAG7G,IAAI,UAAW,MAAM,gBAAgB,MAAM;AAG3C,KAAI,CAAC,QAAQ,QAAQ,QAAQ,KAC5B,WAAU,MAAM,cAAc,QAAQ,MAAM,SAAS,WAAW;CAIjE,MAAM,YAAY,QAAQ,UAAU,yBAAyB;AAC7D,KAAI,UACH,QAAO;AAIR,KAAI,aACH,QAAO;AAGR,OAAM,IAAI,MACT,uMAGA;;;;;AAMF,eAAe,aAAa,aAAkD;AAC7E,KAAI;EACH,MAAM,WAAW,MAAM,MAAM,0DAA0D,EACtF,SAAS,EACR,eAAe,UAAU,eACzB,EACD,CAAC;AAEF,MAAI,SAAS,GAEZ,SAAO,MADa,SAAS,MAAM,EACvB;SAEN;;;;;AAST,eAAsB,wBAAwB,cAAsB,WAA8C;CACjH,MAAM,EAAE,UAAU,iBAAiB,yBAAyB;CAC5D,MAAM,WAAW,MAAM,MAAM,WAAW;EACvC,QAAQ;EACR,SAAS,EAAE,gBAAgB,qCAAqC;EAChE,MAAM,IAAI,gBAAgB;GACzB,WAAW;GACX,eAAe;GACf,eAAe;GACf,YAAY;GACZ,CAAC;EACF,CAAC;AAEF,KAAI,CAAC,SAAS,IAAI;EACjB,MAAM,QAAQ,MAAM,SAAS,MAAM;AACnC,QAAM,IAAI,MAAM,sCAAsC,QAAQ;;CAG/D,MAAM,OAAQ,MAAM,SAAS,MAAM;AAMnC,QAAO;EACN,SAAS,KAAK,iBAAiB;EAC/B,QAAQ,KAAK;EACb,SAAS,KAAK,KAAK,GAAG,KAAK,aAAa,MAAO,MAAS;EACxD;EACA;;;;;;;;;;AAWF,eAAsB,eACrB,QACA,YACA,mBAC4B;CAC5B,MAAM,EAAE,UAAU,cAAc,MAAM,cAAc;CACpD,MAAM,EAAE,UAAU,iBAAiB,yBAAyB;AAG5D,cAAa,8CAA8C;CAC3D,MAAM,SAAS,MAAM,qBAAqB;CAE1C,IAAI;AAEJ,KAAI;AAiBH,SAAO;GACN,KAAK,GAJa,SAAS,GAAG,IAZR,gBAAgB;IACtC,WAAW;IACX,eAAe;IACf,cAAc;IACd,OAAO,OAAO,KAAK,IAAI;IACvB,gBAAgB;IAChB,uBAAuB;IACvB,OAAO;IACP,aAAa;IACb,QAAQ;IACR,CAEwC,CAAC,UAAU;GAKnD,cAAc;GACd,CAAC;AAGF,eAAa,gCAAgC;AAE7C,MAAI,mBAAmB;GAEtB,IAAI;GACJ,IAAI;GACJ,MAAM,gBAAgB,mBAAmB,CACvC,MAAM,UAAU;AAChB,kBAAc;AACd,WAAO,YAAY;KAClB,CACD,OAAO,QAAQ;AACf,kBAAc,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,WAAO,YAAY;KAClB;GAEH,MAAM,SAAS,MAAM,OAAO,aAAa;AAGzC,OAAI,YACH,OAAM;AAGP,OAAI,QAAQ,MAAM;AAEjB,QAAI,OAAO,UAAU,SACpB,OAAM,IAAI,MAAM,8CAA8C;AAE/D,WAAO,OAAO;cACJ,aAAa;IAEvB,MAAM,SAAS,iBAAiB,YAAY;AAC5C,QAAI,OAAO,SAAS,OAAO,UAAU,SACpC,OAAM,IAAI,MAAM,8CAA8C;AAE/D,WAAO,OAAO;;AAIf,OAAI,CAAC,MAAM;AACV,UAAM;AACN,QAAI,YACH,OAAM;AAEP,QAAI,aAAa;KAChB,MAAM,SAAS,iBAAiB,YAAY;AAC5C,SAAI,OAAO,SAAS,OAAO,UAAU,SACpC,OAAM,IAAI,MAAM,8CAA8C;AAE/D,YAAO,OAAO;;;SAGV;GAEN,MAAM,SAAS,MAAM,OAAO,aAAa;AACzC,OAAI,QAAQ,MAAM;AACjB,QAAI,OAAO,UAAU,SACpB,OAAM,IAAI,MAAM,8CAA8C;AAE/D,WAAO,OAAO;;;AAIhB,MAAI,CAAC,KACJ,OAAM,IAAI,MAAM,iCAAiC;AAIlD,eAAa,8CAA8C;EAC3D,MAAM,gBAAgB,MAAM,MAAM,WAAW;GAC5C,QAAQ;GACR,SAAS,EACR,gBAAgB,qCAChB;GACD,MAAM,IAAI,gBAAgB;IACzB,WAAW;IACX,eAAe;IACf;IACA,YAAY;IACZ,cAAc;IACd,eAAe;IACf,CAAC;GACF,CAAC;AAEF,MAAI,CAAC,cAAc,IAAI;GACtB,MAAM,QAAQ,MAAM,cAAc,MAAM;AACxC,SAAM,IAAI,MAAM,0BAA0B,QAAQ;;EAGnD,MAAM,YAAa,MAAM,cAAc,MAAM;AAM7C,MAAI,CAAC,UAAU,cACd,OAAM,IAAI,MAAM,+CAA+C;AAIhE,eAAa,uBAAuB;EACpC,MAAM,QAAQ,MAAM,aAAa,UAAU,aAAa;EAGxD,MAAM,YAAY,MAAM,gBAAgB,UAAU,cAAc,WAAW;EAG3E,MAAM,YAAY,KAAK,KAAK,GAAG,UAAU,aAAa,MAAO,MAAS;AAUtE,SAAO;GAPN,SAAS,UAAU;GACnB,QAAQ,UAAU;GAClB,SAAS;GACT;GACA;GAGiB;WACT;AACT,SAAO,OAAO,OAAO;;;AAIvB,MAAa,yBAAiD;CAC7D,IAAI;CACJ,MAAM;CACN,oBAAoB;CAEpB,MAAM,MAAM,WAA2D;AACtE,SAAO,eAAe,UAAU,QAAQ,UAAU,YAAY,UAAU,kBAAkB;;CAG3F,MAAM,aAAa,aAA0D;EAC5E,MAAM,QAAQ;AACd,MAAI,CAAC,MAAM,UACV,OAAM,IAAI,MAAM,6CAA6C;AAE9D,SAAO,wBAAwB,MAAM,SAAS,MAAM,UAAU;;CAG/D,UAAU,aAAuC;EAChD,MAAM,QAAQ;AACd,SAAO,KAAK,UAAU;GAAE,OAAO,MAAM;GAAQ,WAAW,MAAM;GAAW,CAAC;;CAE3E"}
1
+ {"version":3,"file":"google-gemini-cli.js","names":[],"sources":["../../../../../src/auth/oauth/google-oauth/google-gemini-cli.ts"],"sourcesContent":["/**\n * Gemini CLI OAuth flow (Google Cloud Code Assist)\n * Standard Gemini models through Google Cloud Code Assist.\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport type { Server } from \"node:http\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.js\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"../types.js\";\n\ntype GeminiCredentials = OAuthCredentials & {\n\tprojectId: string;\n};\n\nlet _createServer: typeof import(\"node:http\").createServer | null = null;\nlet _httpImportPromise: Promise<void> | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\t_httpImportPromise = import(\"node:http\").then((m) => {\n\t\t_createServer = m.createServer;\n\t});\n}\n\nconst GEMINI_CLI_CLIENT_ID_ENV = 'XOPC_GOOGLE_GEMINI_CLI_OAUTH_CLIENT_ID';\nconst GEMINI_CLI_CLIENT_SECRET_ENV = 'XOPC_GOOGLE_GEMINI_CLI_OAUTH_CLIENT_SECRET';\n\nfunction getGeminiCliOAuthClient(): { clientId: string; clientSecret: string } {\n\tconst clientId = process.env[GEMINI_CLI_CLIENT_ID_ENV]?.trim();\n\tconst clientSecret = process.env[GEMINI_CLI_CLIENT_SECRET_ENV]?.trim();\n\tif (!clientId || !clientSecret) {\n\t\tthrow new Error(\n\t\t\t`Google Gemini CLI OAuth requires ${GEMINI_CLI_CLIENT_ID_ENV} and ${GEMINI_CLI_CLIENT_SECRET_ENV}.`,\n\t\t);\n\t}\n\treturn { clientId, clientSecret };\n}\n\nconst REDIRECT_URI = \"http://localhost:8085/oauth2callback\";\nconst SCOPES = [\n\t\"https://www.googleapis.com/auth/cloud-platform\",\n\t\"https://www.googleapis.com/auth/userinfo.email\",\n\t\"https://www.googleapis.com/auth/userinfo.profile\",\n];\nconst AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst CODE_ASSIST_ENDPOINT = \"https://cloudcode-pa.googleapis.com\";\n\ntype CallbackServerInfo = {\n\tserver: Server;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string; state: string } | null>;\n};\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nasync function getNodeCreateServer(): Promise<typeof import(\"node:http\").createServer> {\n\tif (_createServer) return _createServer;\n\tif (_httpImportPromise) {\n\t\tawait _httpImportPromise;\n\t}\n\tif (_createServer) return _createServer;\n\tthrow new Error(\"Gemini CLI OAuth is only available in Node.js environments\");\n}\n\nasync function startCallbackServer(): Promise<CallbackServerInfo> {\n\tconst createServer = await getNodeCreateServer();\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settleWait: ((value: { code: string; state: string } | null) => void) | undefined;\n\t\tconst waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {\n\t\t\tlet settled = false;\n\t\t\tsettleWait = (value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolveWait(value);\n\t\t\t};\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:8085`);\n\n\t\t\tif (url.pathname === \"/oauth2callback\") {\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Google authentication did not complete.\", `Error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code && state) {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthSuccessHtml(\"Google authentication completed. You can close this window.\"));\n\t\t\t\t\tsettleWait?.({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(8085, () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t},\n\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\n/**\n * Parse redirect URL to extract code and state\n */\nfunction parseRedirectUrl(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// Not a URL, return empty\n\t\treturn {};\n\t}\n}\n\ninterface LoadCodeAssistPayload {\n\tcloudaicompanionProject?: string;\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\n/**\n * Long-running operation response from onboardUser\n */\ninterface LongRunningOperationResponse {\n\tname?: string;\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n// Tier IDs as used by the Cloud Code API\nconst TIER_FREE = \"free-tier\";\nconst TIER_STANDARD = \"standard-tier\";\n\ninterface GoogleRpcErrorResponse {\n\terror?: {\n\t\tdetails?: Array<{ reason?: string }>;\n\t};\n}\n\n/**\n * Wait helper for onboarding retries\n */\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Get default tier from allowed tiers\n */\nfunction getDefaultTier(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): { id?: string } {\n\tif (!allowedTiers || allowedTiers.length === 0) return { id: TIER_FREE };\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier ?? allowedTiers[0] ?? { id: TIER_FREE };\n}\n\nfunction isVpcScAffectedUser(payload: unknown): boolean {\n\tif (!payload || typeof payload !== \"object\") return false;\n\tif (!(\"error\" in payload)) return false;\n\tconst error = (payload as GoogleRpcErrorResponse).error;\n\tif (!error?.details || !Array.isArray(error.details)) return false;\n\treturn error.details.some((detail) => detail.reason === \"SECURITY_POLICY_VIOLATED\");\n}\n\n/**\n * Poll a long-running operation until completion\n */\nasync function pollOperation(\n\toperationName: string,\n\theaders: Record<string, string>,\n\tonProgress?: (message: string) => void,\n): Promise<LongRunningOperationResponse> {\n\tlet attempt = 0;\n\twhile (true) {\n\t\tif (attempt > 0) {\n\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 1})...`);\n\t\t\tawait wait(5000);\n\t\t}\n\n\t\tconst response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to poll operation: ${response.status} ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as LongRunningOperationResponse;\n\t\tif (data.done) {\n\t\t\treturn data;\n\t\t}\n\n\t\tattempt += 1;\n\t}\n}\n\n/**\n * Discover or provision a Google Cloud project for the user\n */\nasync function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {\n\t// Check for user-provided project ID via environment variable\n\tconst envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;\n\n\tconst headers = {\n\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n\t\t\"X-Goog-Api-Client\": \"gl-node/22.17.0\",\n\t};\n\n\t// Try to load existing project via loadCodeAssist\n\tonProgress?.(\"Checking for existing Cloud Code Assist project...\");\n\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({\n\t\t\tcloudaicompanionProject: envProjectId,\n\t\t\tmetadata: {\n\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t\tduetProject: envProjectId,\n\t\t\t},\n\t\t}),\n\t});\n\n\tlet data: LoadCodeAssistPayload;\n\n\tif (!loadResponse.ok) {\n\t\tlet errorPayload: unknown;\n\t\ttry {\n\t\t\terrorPayload = await loadResponse.clone().json();\n\t\t} catch {\n\t\t\terrorPayload = undefined;\n\t\t}\n\n\t\tif (isVpcScAffectedUser(errorPayload)) {\n\t\t\tdata = { currentTier: { id: TIER_STANDARD } };\n\t\t} else {\n\t\t\tconst errorText = await loadResponse.text();\n\t\t\tthrow new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);\n\t\t}\n\t} else {\n\t\tdata = (await loadResponse.json()) as LoadCodeAssistPayload;\n\t}\n\n\t// If user already has a current tier and project, use it\n\tif (data.currentTier) {\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\t\t// User has a tier but no managed project - they need to provide one via env var\n\t\tif (envProjectId) {\n\t\t\treturn envProjectId;\n\t\t}\n\t\tthrow new Error(\n\t\t\t\"This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t\t);\n\t}\n\n\t// User needs to be onboarded - get the default tier\n\tconst tier = getDefaultTier(data.allowedTiers);\n\tconst tierId = tier?.id ?? TIER_FREE;\n\n\tif (tierId !== TIER_FREE && !envProjectId) {\n\t\tthrow new Error(\n\t\t\t\"This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t\t);\n\t}\n\n\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t// Build onboard request - for free tier, don't include project ID (Google provisions one)\n\t// For other tiers, include the user's project ID if available\n\tconst onboardBody: Record<string, unknown> = {\n\t\ttierId,\n\t\tmetadata: {\n\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\tpluginType: \"GEMINI\",\n\t\t},\n\t};\n\n\tif (tierId !== TIER_FREE && envProjectId) {\n\t\tonboardBody.cloudaicompanionProject = envProjectId;\n\t\t(onboardBody.metadata as Record<string, unknown>).duetProject = envProjectId;\n\t}\n\n\t// Start onboarding - this returns a long-running operation\n\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify(onboardBody),\n\t});\n\n\tif (!onboardResponse.ok) {\n\t\tconst errorText = await onboardResponse.text();\n\t\tthrow new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}: ${errorText}`);\n\t}\n\n\tlet lroData = (await onboardResponse.json()) as LongRunningOperationResponse;\n\n\t// If the operation isn't done yet, poll until completion\n\tif (!lroData.done && lroData.name) {\n\t\tlroData = await pollOperation(lroData.name, headers, onProgress);\n\t}\n\n\t// Try to get project ID from the response\n\tconst projectId = lroData.response?.cloudaicompanionProject?.id;\n\tif (projectId) {\n\t\treturn projectId;\n\t}\n\n\t// If no project ID from onboarding, fall back to env var\n\tif (envProjectId) {\n\t\treturn envProjectId;\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Try setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t);\n}\n\n/**\n * Get user email from the access token\n */\nasync function getUserEmail(accessToken: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetch(\"https://www.googleapis.com/oauth2/v1/userinfo?alt=json\", {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\tconst data = (await response.json()) as { email?: string };\n\t\t\treturn data.email;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, email is optional\n\t}\n\treturn undefined;\n}\n\n/**\n * Refresh Google Cloud Code Assist token\n */\nexport async function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst { clientId, clientSecret } = getGeminiCliOAuthClient();\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: clientId,\n\t\t\tclient_secret: clientSecret,\n\t\t\trefresh_token: refreshToken,\n\t\t\tgrant_type: \"refresh_token\",\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Google Cloud token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\texpires_in: number;\n\t\trefresh_token?: string;\n\t};\n\n\treturn {\n\t\trefresh: data.refresh_token || refreshToken,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t\tprojectId,\n\t};\n}\n\n/**\n * Login with Gemini CLI (Google Cloud Code Assist) OAuth\n *\n * @param onAuth - Callback with URL and optional instructions\n * @param onProgress - Optional progress callback\n * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.\n * Races with browser callback - whichever completes first wins.\n */\nexport async function loginGeminiCli(\n\tonAuth: (info: { url: string; instructions?: string }) => void,\n\tonProgress?: (message: string) => void,\n\tonManualCodeInput?: () => Promise<string>,\n): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst { clientId, clientSecret } = getGeminiCliOAuthClient();\n\n\t// Start local server for callback\n\tonProgress?.(\"Starting local server for OAuth callback...\");\n\tconst server = await startCallbackServer();\n\n\tlet code: string | undefined;\n\n\ttry {\n\t\t// Build authorization URL\n\t\tconst authParams = new URLSearchParams({\n\t\t\tclient_id: clientId,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES.join(\" \"),\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t\taccess_type: \"offline\",\n\t\t\tprompt: \"consent\",\n\t\t});\n\n\t\tconst authUrl = `${AUTH_URL}?${authParams.toString()}`;\n\n\t\t// Notify caller with URL to open\n\t\tonAuth({\n\t\t\turl: authUrl,\n\t\t\tinstructions: \"Complete the sign-in in your browser.\",\n\t\t});\n\n\t\t// Wait for the callback, racing with manual input if provided\n\t\tonProgress?.(\"Waiting for OAuth callback...\");\n\n\t\tif (onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualInput: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualInput = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won - verify state\n\t\t\t\tif (result.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualInput) {\n\t\t\t\t// Manual input won\n\t\t\t\tconst parsed = parseRedirectUrl(manualInput);\n\t\t\t\tif (parsed.state && parsed.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualInput) {\n\t\t\t\t\tconst parsed = parseRedirectUrl(manualInput);\n\t\t\t\t\tif (parsed.state && parsed.state !== verifier) {\n\t\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: just wait for callback\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tif (result.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"No authorization code received\");\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tonProgress?.(\"Exchanging authorization code for tokens...\");\n\t\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: clientId,\n\t\t\t\tclient_secret: clientSecret,\n\t\t\t\tcode,\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t});\n\n\t\tif (!tokenResponse.ok) {\n\t\t\tconst error = await tokenResponse.text();\n\t\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t\t}\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t};\n\n\t\tif (!tokenData.refresh_token) {\n\t\t\tthrow new Error(\"No refresh token received. Please try again.\");\n\t\t}\n\n\t\t// Get user email\n\t\tonProgress?.(\"Getting user info...\");\n\t\tconst email = await getUserEmail(tokenData.access_token);\n\n\t\t// Discover project\n\t\tconst projectId = await discoverProject(tokenData.access_token, onProgress);\n\n\t\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\t\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t\tconst credentials: OAuthCredentials = {\n\t\t\trefresh: tokenData.refresh_token,\n\t\t\taccess: tokenData.access_token,\n\t\t\texpires: expiresAt,\n\t\t\tprojectId,\n\t\t\temail,\n\t\t};\n\n\t\treturn credentials;\n\t} finally {\n\t\tserver.server.close();\n\t}\n}\n\nexport const geminiCliOAuthProvider: OAuthProviderInterface = {\n\tid: \"google-gemini-cli\",\n\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGeminiCli(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as GeminiCredentials;\n\t\tif (!creds.projectId) {\n\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t}\n\t\treturn refreshGoogleCloudToken(creds.refresh, creds.projectId);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\tconst creds = credentials as GeminiCredentials;\n\t\treturn JSON.stringify({ token: creds.access, projectId: creds.projectId });\n\t},\n};\n"],"mappings":"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"}
@@ -66,10 +66,10 @@ const switchCommand = {
66
66
  "group"
67
67
  ],
68
68
  acceptsArgs: true,
69
- examples: ["/switch deepseek/deepseek-chat", "/switch minimax/minimax-m2.7"],
69
+ examples: ["/switch openai/gpt-5.6", "/switch anthropic/claude-sonnet-5"],
70
70
  handler: async (ctx, args) => {
71
71
  if (!args.trim()) return {
72
- content: joinBlocks("❌ Missing model ref.", "**Usage:** `/switch provider/model-id`", "Run `/models` — each line shows a display name and a `provider/model` ref in backticks. Copy that ref.", "**Example:** `/switch deepseek/deepseek-chat`"),
72
+ content: joinBlocks("❌ Missing model ref.", "**Usage:** `/switch provider/model-id`", "Run `/models` — each line shows a display name and a `provider/model` ref in backticks. Copy that ref.", "**Example:** `/switch openai/gpt-5.6`"),
73
73
  success: false
74
74
  };
75
75
  await ctx.setTyping(true);
@@ -82,7 +82,7 @@ const switchCommand = {
82
82
  };
83
83
  }
84
84
  return {
85
- content: joinBlocks(`❌ Could not switch to ${code(modelId)}.`, "Use the exact `provider/model` ref from `/models` (not only the display name).", "**Example:** `/switch anthropic/claude-sonnet-4-20250514`"),
85
+ content: joinBlocks(`❌ Could not switch to ${code(modelId)}.`, "Use the exact `provider/model` ref from `/models` (not only the display name).", "**Example:** `/switch anthropic/claude-sonnet-5`"),
86
86
  success: false
87
87
  };
88
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","names":[],"sources":["../../../../src/chat-commands/builtins/model.ts"],"sourcesContent":["/**\n * Model Commands\n *\n * Built-in commands for model management:\n * - /models - List models with display names and `provider/model` refs for /switch\n * - /switch - Switch model using the ref from /models\n * - /usage - Show token usage statistics\n */\n\nimport type { CommandDefinition, CommandContext, UIComponent } from '../types.js';\nimport { commandRegistry } from '../registry.js';\nimport { bulletList, code, hint, joinBlocks, kvList, section } from '../format-output.js';\n\nconst modelsCommand: CommandDefinition = {\n id: 'model.list',\n name: 'models',\n aliases: ['model'],\n description: 'List models with display names and `provider/model` refs for /switch',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n\n const models = await ctx.listModels();\n const currentModel = ctx.getCurrentModel();\n\n if (models.length === 0) {\n return {\n content: '🤖 No models available. Please check your configuration.',\n success: true,\n };\n }\n\n // Group by provider\n const byProvider = new Map<string, typeof models>();\n for (const m of models) {\n if (!byProvider.has(m.provider)) {\n byProvider.set(m.provider, []);\n }\n byProvider.get(m.provider)!.push(m);\n }\n\n /** `m.id` from listModels is always `serviceId/modelId` (canonical `/switch` ref). */\n const providerBlocks = Array.from(byProvider.entries()).map(([provider, providerModels]) => {\n const items = providerModels.slice(0, 5).map((m) => {\n const marker = m.id === currentModel ? '▶️ ' : '';\n return `${marker}**${m.name}** — ${code(m.id)}`;\n });\n if (providerModels.length > 5) {\n items.push(hint(`… and ${providerModels.length - 5} more in this provider`));\n }\n return joinBlocks(section(provider), bulletList(items));\n });\n\n const content = joinBlocks(\n '🤖 Available models (use the `provider/model` ref with `/switch`):',\n ...providerBlocks,\n hint('Copy the ref in backticks after each name, then: `/switch provider/model-id`.'),\n );\n\n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'model-picker',\n providers: Array.from(byProvider.entries()).map(([id, models]) => ({\n id,\n name: id,\n models: models.map((m) => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n })),\n })),\n currentModel,\n };\n\n return {\n content,\n success: true,\n components: [component],\n };\n }\n\n return {\n content,\n success: true,\n };\n },\n};\n\nconst switchCommand: CommandDefinition = {\n id: 'model.switch',\n name: 'switch',\n description: 'Switch model — pass the `provider/model` ref shown by /models',\n category: 'model',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/switch deepseek/deepseek-chat', '/switch minimax/minimax-m2.7'],\n handler: async (ctx: CommandContext, args: string) => {\n if (!args.trim()) {\n return {\n content: joinBlocks(\n '❌ Missing model ref.',\n '**Usage:** `/switch provider/model-id`',\n 'Run `/models` — each line shows a display name and a `provider/model` ref in backticks. Copy that ref.',\n '**Example:** `/switch deepseek/deepseek-chat`',\n ),\n success: false,\n };\n }\n\n await ctx.setTyping(true);\n\n const modelId = args.trim();\n const success = await ctx.switchModel(modelId);\n\n if (success) {\n const modelName = modelId.split('/').pop() || modelId;\n return {\n content: joinBlocks(\n `✅ Switched to ${code(modelId)} (${modelName}).`,\n 'This model will be used for your next message.',\n ),\n success: true,\n };\n }\n\n return {\n content: joinBlocks(\n `❌ Could not switch to ${code(modelId)}.`,\n 'Use the exact `provider/model` ref from `/models` (not only the display name).',\n '**Example:** `/switch anthropic/claude-sonnet-4-20250514`',\n ),\n success: false,\n };\n },\n};\n\nconst usageCommand: CommandDefinition = {\n id: 'model.usage',\n name: 'usage',\n description: 'Show token usage statistics for current session',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n\n const stats = await ctx.getUsage();\n const modelName = ctx.getCurrentModel().split('/').pop() || 'Unknown';\n\n const content = joinBlocks(\n section('📊 Session Token Usage'),\n kvList([\n { key: 'Model', value: modelName },\n { key: 'Prompt', value: `${stats.promptTokens.toLocaleString()} tokens` },\n { key: 'Completion', value: `${stats.completionTokens.toLocaleString()} tokens` },\n { key: 'Total', value: `${stats.totalTokens.toLocaleString()} tokens` },\n { key: 'Messages', value: String(stats.messageCount) },\n ]),\n );\n\n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'usage-display',\n stats,\n modelName,\n };\n\n return {\n content,\n success: true,\n components: [component],\n };\n }\n\n return {\n content,\n success: true,\n };\n },\n};\n\n// Register all model commands\nexport function registerModelCommands(): void {\n commandRegistry.register(modelsCommand);\n commandRegistry.register(switchCommand);\n commandRegistry.register(usageCommand);\n}\n"],"mappings":";;;AAaA,MAAM,gBAAmC;CACvC,IAAI;CACJ,MAAM;CACN,SAAS,CAAC,QAAQ;CAClB,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,QAAwB;AACtC,QAAM,IAAI,UAAU,KAAK;EAEzB,MAAM,SAAS,MAAM,IAAI,YAAY;EACrC,MAAM,eAAe,IAAI,iBAAiB;AAE1C,MAAI,OAAO,WAAW,EACpB,QAAO;GACL,SAAS;GACT,SAAS;GACV;EAIH,MAAM,6BAAa,IAAI,KAA4B;AACnD,OAAK,MAAM,KAAK,QAAQ;AACtB,OAAI,CAAC,WAAW,IAAI,EAAE,SAAS,CAC7B,YAAW,IAAI,EAAE,UAAU,EAAE,CAAC;AAEhC,cAAW,IAAI,EAAE,SAAS,CAAE,KAAK,EAAE;;EAerC,MAAM,UAAU,WACd,sEACA,GAbqB,MAAM,KAAK,WAAW,SAAS,CAAC,CAAC,KAAK,CAAC,UAAU,oBAAoB;GAC1F,MAAM,QAAQ,eAAe,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM;AAElD,WAAO,GADQ,EAAE,OAAO,eAAe,QAAQ,GAC9B,IAAI,EAAE,KAAK,OAAO,KAAK,EAAE,GAAG;KAC7C;AACF,OAAI,eAAe,SAAS,EAC1B,OAAM,KAAK,KAAK,SAAS,eAAe,SAAS,EAAE,wBAAwB,CAAC;AAE9E,UAAO,WAAW,QAAQ,SAAS,EAAE,WAAW,MAAM,CAAC;IAKtC,EACjB,KAAK,gFAAgF,CACtF;AAGD,MAAI,IAAI,SAAS,UAAU,CAezB,QAAO;GACL;GACA,SAAS;GACT,YAAY,CAAC;IAhBb,MAAM;IACN,WAAW,MAAM,KAAK,WAAW,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,aAAa;KACjE;KACA,MAAM;KACN,QAAQ,OAAO,KAAK,OAAO;MACzB,IAAI,EAAE;MACN,MAAM,EAAE;MACR,UAAU,EAAE;MACb,EAAE;KACJ,EAAE;IACH;IAMsB,CAAC;GACxB;AAGH,SAAO;GACL;GACA,SAAS;GACV;;CAEJ;AAED,MAAM,gBAAmC;CACvC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,aAAa;CACb,UAAU,CAAC,kCAAkC,+BAA+B;CAC5E,SAAS,OAAO,KAAqB,SAAiB;AACpD,MAAI,CAAC,KAAK,MAAM,CACd,QAAO;GACL,SAAS,WACP,wBACA,0CACA,0GACA,gDACD;GACD,SAAS;GACV;AAGH,QAAM,IAAI,UAAU,KAAK;EAEzB,MAAM,UAAU,KAAK,MAAM;AAG3B,MAAI,MAFkB,IAAI,YAAY,QAAQ,EAEjC;GACX,MAAM,YAAY,QAAQ,MAAM,IAAI,CAAC,KAAK,IAAI;AAC9C,UAAO;IACL,SAAS,WACP,iBAAiB,KAAK,QAAQ,CAAC,IAAI,UAAU,KAC7C,iDACD;IACD,SAAS;IACV;;AAGH,SAAO;GACL,SAAS,WACP,yBAAyB,KAAK,QAAQ,CAAC,IACvC,kFACA,4DACD;GACD,SAAS;GACV;;CAEJ;AAED,MAAM,eAAkC;CACtC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,QAAwB;AACtC,QAAM,IAAI,UAAU,KAAK;EAEzB,MAAM,QAAQ,MAAM,IAAI,UAAU;EAClC,MAAM,YAAY,IAAI,iBAAiB,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI;EAE5D,MAAM,UAAU,WACd,QAAQ,yBAAyB,EACjC,OAAO;GACL;IAAE,KAAK;IAAS,OAAO;IAAW;GAClC;IAAE,KAAK;IAAU,OAAO,GAAG,MAAM,aAAa,gBAAgB,CAAC;IAAU;GACzE;IAAE,KAAK;IAAc,OAAO,GAAG,MAAM,iBAAiB,gBAAgB,CAAC;IAAU;GACjF;IAAE,KAAK;IAAS,OAAO,GAAG,MAAM,YAAY,gBAAgB,CAAC;IAAU;GACvE;IAAE,KAAK;IAAY,OAAO,OAAO,MAAM,aAAa;IAAE;GACvD,CAAC,CACH;AAGD,MAAI,IAAI,SAAS,UAAU,CAOzB,QAAO;GACL;GACA,SAAS;GACT,YAAY,CAAC;IARb,MAAM;IACN;IACA;IAMsB,CAAC;GACxB;AAGH,SAAO;GACL;GACA,SAAS;GACV;;CAEJ;AAGD,SAAgB,wBAA8B;AAC5C,iBAAgB,SAAS,cAAc;AACvC,iBAAgB,SAAS,cAAc;AACvC,iBAAgB,SAAS,aAAa"}
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../../../src/chat-commands/builtins/model.ts"],"sourcesContent":["/**\n * Model Commands\n *\n * Built-in commands for model management:\n * - /models - List models with display names and `provider/model` refs for /switch\n * - /switch - Switch model using the ref from /models\n * - /usage - Show token usage statistics\n */\n\nimport type { CommandDefinition, CommandContext, UIComponent } from '../types.js';\nimport { commandRegistry } from '../registry.js';\nimport { bulletList, code, hint, joinBlocks, kvList, section } from '../format-output.js';\n\nconst modelsCommand: CommandDefinition = {\n id: 'model.list',\n name: 'models',\n aliases: ['model'],\n description: 'List models with display names and `provider/model` refs for /switch',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n\n const models = await ctx.listModels();\n const currentModel = ctx.getCurrentModel();\n\n if (models.length === 0) {\n return {\n content: '🤖 No models available. Please check your configuration.',\n success: true,\n };\n }\n\n // Group by provider\n const byProvider = new Map<string, typeof models>();\n for (const m of models) {\n if (!byProvider.has(m.provider)) {\n byProvider.set(m.provider, []);\n }\n byProvider.get(m.provider)!.push(m);\n }\n\n /** `m.id` from listModels is always `serviceId/modelId` (canonical `/switch` ref). */\n const providerBlocks = Array.from(byProvider.entries()).map(([provider, providerModels]) => {\n const items = providerModels.slice(0, 5).map((m) => {\n const marker = m.id === currentModel ? '▶️ ' : '';\n return `${marker}**${m.name}** — ${code(m.id)}`;\n });\n if (providerModels.length > 5) {\n items.push(hint(`… and ${providerModels.length - 5} more in this provider`));\n }\n return joinBlocks(section(provider), bulletList(items));\n });\n\n const content = joinBlocks(\n '🤖 Available models (use the `provider/model` ref with `/switch`):',\n ...providerBlocks,\n hint('Copy the ref in backticks after each name, then: `/switch provider/model-id`.'),\n );\n\n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'model-picker',\n providers: Array.from(byProvider.entries()).map(([id, models]) => ({\n id,\n name: id,\n models: models.map((m) => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n })),\n })),\n currentModel,\n };\n\n return {\n content,\n success: true,\n components: [component],\n };\n }\n\n return {\n content,\n success: true,\n };\n },\n};\n\nconst switchCommand: CommandDefinition = {\n id: 'model.switch',\n name: 'switch',\n description: 'Switch model — pass the `provider/model` ref shown by /models',\n category: 'model',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/switch openai/gpt-5.6', '/switch anthropic/claude-sonnet-5'],\n handler: async (ctx: CommandContext, args: string) => {\n if (!args.trim()) {\n return {\n content: joinBlocks(\n '❌ Missing model ref.',\n '**Usage:** `/switch provider/model-id`',\n 'Run `/models` — each line shows a display name and a `provider/model` ref in backticks. Copy that ref.',\n '**Example:** `/switch openai/gpt-5.6`',\n ),\n success: false,\n };\n }\n\n await ctx.setTyping(true);\n\n const modelId = args.trim();\n const success = await ctx.switchModel(modelId);\n\n if (success) {\n const modelName = modelId.split('/').pop() || modelId;\n return {\n content: joinBlocks(\n `✅ Switched to ${code(modelId)} (${modelName}).`,\n 'This model will be used for your next message.',\n ),\n success: true,\n };\n }\n\n return {\n content: joinBlocks(\n `❌ Could not switch to ${code(modelId)}.`,\n 'Use the exact `provider/model` ref from `/models` (not only the display name).',\n '**Example:** `/switch anthropic/claude-sonnet-5`',\n ),\n success: false,\n };\n },\n};\n\nconst usageCommand: CommandDefinition = {\n id: 'model.usage',\n name: 'usage',\n description: 'Show token usage statistics for current session',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n\n const stats = await ctx.getUsage();\n const modelName = ctx.getCurrentModel().split('/').pop() || 'Unknown';\n\n const content = joinBlocks(\n section('📊 Session Token Usage'),\n kvList([\n { key: 'Model', value: modelName },\n { key: 'Prompt', value: `${stats.promptTokens.toLocaleString()} tokens` },\n { key: 'Completion', value: `${stats.completionTokens.toLocaleString()} tokens` },\n { key: 'Total', value: `${stats.totalTokens.toLocaleString()} tokens` },\n { key: 'Messages', value: String(stats.messageCount) },\n ]),\n );\n\n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'usage-display',\n stats,\n modelName,\n };\n\n return {\n content,\n success: true,\n components: [component],\n };\n }\n\n return {\n content,\n success: true,\n };\n },\n};\n\n// Register all model commands\nexport function registerModelCommands(): void {\n commandRegistry.register(modelsCommand);\n commandRegistry.register(switchCommand);\n commandRegistry.register(usageCommand);\n}\n"],"mappings":"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"}
@@ -27,6 +27,15 @@ const IMAGE_GENERATION_IDS = new Set([
27
27
  "image-01-live"
28
28
  ]);
29
29
  const VISION_IDS = new Set([
30
+ "gpt-5.6",
31
+ "gpt-5.6-luna",
32
+ "gpt-5.6-sol",
33
+ "gpt-5.6-terra",
34
+ "claude-sonnet-5",
35
+ "gemini-3.5-flash",
36
+ "gemini-3.1-pro-preview",
37
+ "gemini-3-flash-preview",
38
+ "gemini-3-pro-preview",
30
39
  "gpt-4o",
31
40
  "gpt-4o-mini",
32
41
  "gpt-4-turbo",
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","names":[],"sources":["../../../../src/cli/commands/models.ts"],"sourcesContent":["import { input } from '@inquirer/prompts';\nimport { Command } from 'commander';\n\nimport { CredentialResolver } from '../../auth/credentials.js';\nimport { loadConfig, saveConfig } from '../../config/index.js';\nimport { getAgentDefaultModelRef, type Config } from '../../config/schema.js';\nimport { prepareUpdateGlobalDefaults } from '../../gateway/global-defaults-admin.js';\nimport {\n getAllModels,\n getAvailableModels,\n getConfiguredProviders,\n getProviderAuthState,\n getProviderDisplayName,\n isProviderConfigured,\n providerSupportsApiKey,\n providerSupportsOAuth,\n resolveModel,\n} from '../../providers/index.js';\n\nimport { getContextWithOpts } from '../context.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { colors } from '../utils/colors.js';\nimport { runCliOAuthLogin } from '../utils/oauth-login.js';\n\nconst IMAGE_GENERATION_IDS = new Set([\n 'gpt-image-1',\n 'dall-e-3',\n 'dall-e-2',\n 'wan2.6-t2i',\n 'wan2.7-image-pro',\n 'wan2.7-image',\n 'wan2.1-t2i-turbo',\n 'wan2.1-t2i-plus',\n 'image-01',\n 'image-01-live',\n]);\n\nconst VISION_IDS = new Set([\n 'gpt-4o',\n 'gpt-4o-mini',\n 'gpt-4-turbo',\n 'claude-sonnet-4-5',\n 'claude-haiku-3-5',\n 'gemini-2.0-flash',\n 'gemini-1.5-pro',\n 'qwen-vl-max',\n 'qwen2.5-vl-72b-instruct',\n]);\n\ntype ModelsListOptions = {\n json?: boolean;\n all?: boolean;\n provider?: string;\n};\n\ntype ModelsAuthListOptions = {\n provider?: string;\n json?: boolean;\n};\n\ntype ModelsAuthLoginOptions = {\n method?: 'oauth' | 'api-key';\n setDefault?: boolean;\n};\n\nfunction currentModelRef(config: Config): string | undefined {\n return getAgentDefaultModelRef(config);\n}\n\nfunction setDefaultModel(config: Config, modelRef: string): Config {\n const prep = prepareUpdateGlobalDefaults(config, {\n models: {\n defaultRole: 'deep',\n roles: {\n deep: { model: modelRef },\n },\n },\n });\n if (prep.ok === false) {\n throw new Error(prep.error);\n }\n return prep.data.nextConfig;\n}\n\nfunction modelRef(provider: string, id: string): string {\n return `${provider}/${id}`;\n}\n\nasync function runModelsList(options: ModelsListOptions): Promise<void> {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const configuredProviders = await getConfiguredProviders();\n const providerFilter = options.provider?.trim();\n const models = (options.all ? getAllModels() : await getAvailableModels()).filter((m) =>\n providerFilter ? m.provider === providerFilter : true,\n );\n\n if (options.json) {\n console.log(\n JSON.stringify(\n {\n defaultModel: currentModelRef(config) ?? null,\n providers: configuredProviders,\n models: models.map((m) => ({\n id: modelRef(m.provider, m.id),\n name: m.name,\n provider: m.provider,\n authConfigured: configuredProviders.includes(m.provider),\n })),\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n console.log('\\n🤖 Models\\n');\n console.log('═'.repeat(60));\n\n console.log(`\\n📌 Current default model: ${currentModelRef(config) ?? 'Not set'}\\n`);\n\n const byProvider = new Map<string, typeof models>();\n for (const model of models) {\n const list = byProvider.get(model.provider) ?? [];\n list.push(model);\n byProvider.set(model.provider, list);\n }\n\n for (const [provider, providerModels] of byProvider) {\n const available = await isProviderConfigured(provider);\n console.log(` [${provider}] ${available ? colors.green('configured') : colors.gray('not configured')}`);\n for (const model of providerModels) {\n const badges: string[] = [];\n if (IMAGE_GENERATION_IDS.has(model.id)) badges.push('gen');\n if (model.input?.includes('image') || VISION_IDS.has(model.id)) badges.push('vision');\n const badgeStr = badges.length > 0 ? ` [${badges.join(', ')}]` : '';\n console.log(` ${available ? '✓' : '○'} ${modelRef(model.provider, model.id)}${badgeStr}`);\n }\n }\n\n console.log('\\nUsage:');\n console.log(' xopc models set deepseek/deepseek-v4-flash');\n console.log(' xopc models auth login --provider anthropic');\n console.log(' xopc models auth paste-api-key --provider deepseek');\n}\n\nasync function runModelsStatus(options: { json?: boolean }): Promise<void> {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const providers = Array.from(new Set(getAllModels().map((m) => m.provider))).sort();\n const auth = [];\n for (const provider of providers) {\n const state = await getProviderAuthState(provider);\n if (state.authMode !== 'none') {\n auth.push({ provider, ...state });\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify({ defaultModel: currentModelRef(config) ?? null, auth }, null, 2));\n return;\n }\n\n console.log('\\n🤖 Model status\\n');\n console.log(`Default: ${currentModelRef(config) ?? 'Not set'}`);\n console.log('\\nAuth:');\n if (auth.length === 0) {\n console.log(' No provider credentials configured.');\n return;\n }\n for (const entry of auth) {\n const status = entry.authStatus === 'connected' ? colors.green(entry.authStatus) : colors.yellow(entry.authStatus);\n console.log(` ${entry.provider}: ${status} (${entry.authMode})`);\n }\n}\n\nasync function runModelsSet(model: string): Promise<void> {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const resolved = resolveModel(model);\n const ref = modelRef(resolved.provider, resolved.id);\n await saveConfig(setDefaultModel(config, ref), ctx.configPath);\n console.log(`✅ Default model set: ${ref}`);\n}\n\nasync function runAuthList(options: ModelsAuthListOptions): Promise<void> {\n const resolver = new CredentialResolver();\n const profiles = (await resolver.listProfiles()).filter((profile) =>\n options.provider ? profile.provider === options.provider : true,\n );\n const oauthTokens = (await resolver.listOAuthTokens()).filter((token) =>\n options.provider ? token.provider === options.provider : true,\n );\n\n if (options.json) {\n console.log(JSON.stringify({ profiles, oauth: oauthTokens }, null, 2));\n return;\n }\n\n console.log('\\n🔐 Model auth\\n');\n if (profiles.length === 0 && oauthTokens.length === 0) {\n console.log(' No model credentials configured.');\n return;\n }\n for (const profile of profiles) {\n console.log(` ${profile.id} ${colors.blue('api-key')} ${profile.provider} ${profile.source}`);\n }\n for (const token of oauthTokens) {\n const expires = token.expiresAt ? ` expires ${new Date(token.expiresAt).toLocaleString()}` : '';\n console.log(` ${token.provider}:oauth ${colors.yellow('oauth')} ${token.hasAccess ? 'configured' : 'missing'}${expires}`);\n }\n}\n\nasync function runAuthPasteApiKey(provider: string): Promise<void> {\n if (!providerSupportsApiKey(provider)) {\n throw new Error(`Provider ${provider} does not support API key auth`);\n }\n const key = await input({\n message: `API key for ${provider}:`,\n validate: (value: string) => value.trim().length > 0 || 'Required',\n });\n await new CredentialResolver().saveApiKey(provider, key.trim(), { profileName: 'default' });\n console.log(`✅ API key saved for ${provider}`);\n}\n\nfunction resolveAuthMethod(provider: string, rawMethod?: string): 'oauth' | 'api-key' {\n if (rawMethod && rawMethod !== 'oauth' && rawMethod !== 'api-key') {\n throw new Error(`Unsupported auth method: ${rawMethod}`);\n }\n if (rawMethod === 'oauth' || rawMethod === 'api-key') return rawMethod;\n return providerSupportsOAuth(provider) ? 'oauth' : 'api-key';\n}\n\nasync function runAuthLogin(provider: string, options: ModelsAuthLoginOptions): Promise<void> {\n const method = resolveAuthMethod(provider, options.method);\n if (method === 'api-key') {\n await runAuthPasteApiKey(provider);\n } else {\n if (!providerSupportsOAuth(provider)) {\n throw new Error(`Provider ${provider} does not support OAuth`);\n }\n console.log(`\\n🔐 Starting ${getProviderDisplayName(provider)} OAuth login...`);\n await runCliOAuthLogin({ provider, onProgress: (message) => console.log(' →', message) });\n console.log(`✅ OAuth login successful for ${provider}`);\n }\n\n if (options.setDefault) {\n const first = getAllModels().find((model) => model.provider === provider);\n if (!first) throw new Error(`No models found for provider ${provider}`);\n await runModelsSet(modelRef(first.provider, first.id));\n }\n}\n\nasync function runAuthLogout(provider: string): Promise<void> {\n await new CredentialResolver().deleteProviderCredential(provider);\n console.log(`✅ Credentials removed for ${provider}`);\n}\n\nfunction attachModelsListOptions(command: Command): Command {\n return command\n .option('--json', 'Output as JSON', false)\n .option('--all, -a', 'Show all built-in models', false)\n .option('--provider <id>', 'Filter by provider id');\n}\n\nfunction createModelsCommand(_ctx: CLIContext): Command {\n const cmd = new Command('models')\n .description('List and manage models and model auth')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc models list',\n 'xopc models status',\n 'xopc models set deepseek/deepseek-v4-flash',\n 'xopc models auth list',\n 'xopc models auth login --provider anthropic',\n 'xopc models auth paste-api-key --provider deepseek',\n ]),\n );\n\n attachModelsListOptions(cmd).action(async (options) => {\n await runModelsList(options);\n });\n\n attachModelsListOptions(cmd.command('list').description('List available models')).action(async (options) => {\n await runModelsList(options);\n });\n\n cmd\n .command('status')\n .description('Show default model and provider auth status')\n .option('--json', 'Output as JSON', false)\n .action(async (options: { json?: boolean }) => {\n await runModelsStatus(options);\n });\n\n cmd\n .command('set <model>')\n .description('Set the default model')\n .action(async (model: string) => {\n await runModelsSet(model);\n });\n\n const auth = cmd.command('auth').description('Manage model provider auth');\n\n auth\n .command('list')\n .description('List model auth credentials')\n .option('--provider <id>', 'Filter by provider id')\n .option('--json', 'Output as JSON', false)\n .action(async (options: ModelsAuthListOptions) => {\n await runAuthList(options);\n });\n\n auth\n .command('login')\n .description('Run provider auth flow')\n .requiredOption('--provider <id>', 'Provider id')\n .option('--method <method>', 'Auth method: oauth or api-key')\n .option('--set-default', 'Set the provider first model as default after login', false)\n .action(async (options: ModelsAuthLoginOptions & { provider: string }) => {\n await runAuthLogin(options.provider, options);\n });\n\n auth\n .command('paste-api-key')\n .description('Paste and save an API key')\n .requiredOption('--provider <id>', 'Provider id')\n .action(async (options: { provider: string }) => {\n await runAuthPasteApiKey(options.provider);\n });\n\n auth\n .command('logout')\n .description('Remove provider credentials')\n .requiredOption('--provider <id>', 'Provider id')\n .action(async (options: { provider: string }) => {\n await runAuthLogout(options.provider);\n });\n\n return cmd;\n}\n\nregister({\n id: 'models',\n name: 'models',\n description: 'List and manage models and model auth',\n factory: createModelsCommand,\n metadata: {\n category: 'utility',\n examples: ['xopc models list', 'xopc models status', 'xopc models auth list'],\n },\n});\n\nexport { createModelsCommand };\n"],"mappings":"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"}
1
+ {"version":3,"file":"models.js","names":[],"sources":["../../../../src/cli/commands/models.ts"],"sourcesContent":["import { input } from '@inquirer/prompts';\nimport { Command } from 'commander';\n\nimport { CredentialResolver } from '../../auth/credentials.js';\nimport { loadConfig, saveConfig } from '../../config/index.js';\nimport { getAgentDefaultModelRef, type Config } from '../../config/schema.js';\nimport { prepareUpdateGlobalDefaults } from '../../gateway/global-defaults-admin.js';\nimport {\n getAllModels,\n getAvailableModels,\n getConfiguredProviders,\n getProviderAuthState,\n getProviderDisplayName,\n isProviderConfigured,\n providerSupportsApiKey,\n providerSupportsOAuth,\n resolveModel,\n} from '../../providers/index.js';\n\nimport { getContextWithOpts } from '../context.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { colors } from '../utils/colors.js';\nimport { runCliOAuthLogin } from '../utils/oauth-login.js';\n\nconst IMAGE_GENERATION_IDS = new Set([\n 'gpt-image-1',\n 'dall-e-3',\n 'dall-e-2',\n 'wan2.6-t2i',\n 'wan2.7-image-pro',\n 'wan2.7-image',\n 'wan2.1-t2i-turbo',\n 'wan2.1-t2i-plus',\n 'image-01',\n 'image-01-live',\n]);\n\nconst VISION_IDS = new Set([\n 'gpt-5.6',\n 'gpt-5.6-luna',\n 'gpt-5.6-sol',\n 'gpt-5.6-terra',\n 'claude-sonnet-5',\n 'gemini-3.5-flash',\n 'gemini-3.1-pro-preview',\n 'gemini-3-flash-preview',\n 'gemini-3-pro-preview',\n 'gpt-4o',\n 'gpt-4o-mini',\n 'gpt-4-turbo',\n 'claude-sonnet-4-5',\n 'claude-haiku-3-5',\n 'gemini-2.0-flash',\n 'gemini-1.5-pro',\n 'qwen-vl-max',\n 'qwen2.5-vl-72b-instruct',\n]);\n\ntype ModelsListOptions = {\n json?: boolean;\n all?: boolean;\n provider?: string;\n};\n\ntype ModelsAuthListOptions = {\n provider?: string;\n json?: boolean;\n};\n\ntype ModelsAuthLoginOptions = {\n method?: 'oauth' | 'api-key';\n setDefault?: boolean;\n};\n\nfunction currentModelRef(config: Config): string | undefined {\n return getAgentDefaultModelRef(config);\n}\n\nfunction setDefaultModel(config: Config, modelRef: string): Config {\n const prep = prepareUpdateGlobalDefaults(config, {\n models: {\n defaultRole: 'deep',\n roles: {\n deep: { model: modelRef },\n },\n },\n });\n if (prep.ok === false) {\n throw new Error(prep.error);\n }\n return prep.data.nextConfig;\n}\n\nfunction modelRef(provider: string, id: string): string {\n return `${provider}/${id}`;\n}\n\nasync function runModelsList(options: ModelsListOptions): Promise<void> {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const configuredProviders = await getConfiguredProviders();\n const providerFilter = options.provider?.trim();\n const models = (options.all ? getAllModels() : await getAvailableModels()).filter((m) =>\n providerFilter ? m.provider === providerFilter : true,\n );\n\n if (options.json) {\n console.log(\n JSON.stringify(\n {\n defaultModel: currentModelRef(config) ?? null,\n providers: configuredProviders,\n models: models.map((m) => ({\n id: modelRef(m.provider, m.id),\n name: m.name,\n provider: m.provider,\n authConfigured: configuredProviders.includes(m.provider),\n })),\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n console.log('\\n🤖 Models\\n');\n console.log('═'.repeat(60));\n\n console.log(`\\n📌 Current default model: ${currentModelRef(config) ?? 'Not set'}\\n`);\n\n const byProvider = new Map<string, typeof models>();\n for (const model of models) {\n const list = byProvider.get(model.provider) ?? [];\n list.push(model);\n byProvider.set(model.provider, list);\n }\n\n for (const [provider, providerModels] of byProvider) {\n const available = await isProviderConfigured(provider);\n console.log(` [${provider}] ${available ? colors.green('configured') : colors.gray('not configured')}`);\n for (const model of providerModels) {\n const badges: string[] = [];\n if (IMAGE_GENERATION_IDS.has(model.id)) badges.push('gen');\n if (model.input?.includes('image') || VISION_IDS.has(model.id)) badges.push('vision');\n const badgeStr = badges.length > 0 ? ` [${badges.join(', ')}]` : '';\n console.log(` ${available ? '✓' : '○'} ${modelRef(model.provider, model.id)}${badgeStr}`);\n }\n }\n\n console.log('\\nUsage:');\n console.log(' xopc models set deepseek/deepseek-v4-flash');\n console.log(' xopc models auth login --provider anthropic');\n console.log(' xopc models auth paste-api-key --provider deepseek');\n}\n\nasync function runModelsStatus(options: { json?: boolean }): Promise<void> {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const providers = Array.from(new Set(getAllModels().map((m) => m.provider))).sort();\n const auth = [];\n for (const provider of providers) {\n const state = await getProviderAuthState(provider);\n if (state.authMode !== 'none') {\n auth.push({ provider, ...state });\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify({ defaultModel: currentModelRef(config) ?? null, auth }, null, 2));\n return;\n }\n\n console.log('\\n🤖 Model status\\n');\n console.log(`Default: ${currentModelRef(config) ?? 'Not set'}`);\n console.log('\\nAuth:');\n if (auth.length === 0) {\n console.log(' No provider credentials configured.');\n return;\n }\n for (const entry of auth) {\n const status = entry.authStatus === 'connected' ? colors.green(entry.authStatus) : colors.yellow(entry.authStatus);\n console.log(` ${entry.provider}: ${status} (${entry.authMode})`);\n }\n}\n\nasync function runModelsSet(model: string): Promise<void> {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const resolved = resolveModel(model);\n const ref = modelRef(resolved.provider, resolved.id);\n await saveConfig(setDefaultModel(config, ref), ctx.configPath);\n console.log(`✅ Default model set: ${ref}`);\n}\n\nasync function runAuthList(options: ModelsAuthListOptions): Promise<void> {\n const resolver = new CredentialResolver();\n const profiles = (await resolver.listProfiles()).filter((profile) =>\n options.provider ? profile.provider === options.provider : true,\n );\n const oauthTokens = (await resolver.listOAuthTokens()).filter((token) =>\n options.provider ? token.provider === options.provider : true,\n );\n\n if (options.json) {\n console.log(JSON.stringify({ profiles, oauth: oauthTokens }, null, 2));\n return;\n }\n\n console.log('\\n🔐 Model auth\\n');\n if (profiles.length === 0 && oauthTokens.length === 0) {\n console.log(' No model credentials configured.');\n return;\n }\n for (const profile of profiles) {\n console.log(` ${profile.id} ${colors.blue('api-key')} ${profile.provider} ${profile.source}`);\n }\n for (const token of oauthTokens) {\n const expires = token.expiresAt ? ` expires ${new Date(token.expiresAt).toLocaleString()}` : '';\n console.log(` ${token.provider}:oauth ${colors.yellow('oauth')} ${token.hasAccess ? 'configured' : 'missing'}${expires}`);\n }\n}\n\nasync function runAuthPasteApiKey(provider: string): Promise<void> {\n if (!providerSupportsApiKey(provider)) {\n throw new Error(`Provider ${provider} does not support API key auth`);\n }\n const key = await input({\n message: `API key for ${provider}:`,\n validate: (value: string) => value.trim().length > 0 || 'Required',\n });\n await new CredentialResolver().saveApiKey(provider, key.trim(), { profileName: 'default' });\n console.log(`✅ API key saved for ${provider}`);\n}\n\nfunction resolveAuthMethod(provider: string, rawMethod?: string): 'oauth' | 'api-key' {\n if (rawMethod && rawMethod !== 'oauth' && rawMethod !== 'api-key') {\n throw new Error(`Unsupported auth method: ${rawMethod}`);\n }\n if (rawMethod === 'oauth' || rawMethod === 'api-key') return rawMethod;\n return providerSupportsOAuth(provider) ? 'oauth' : 'api-key';\n}\n\nasync function runAuthLogin(provider: string, options: ModelsAuthLoginOptions): Promise<void> {\n const method = resolveAuthMethod(provider, options.method);\n if (method === 'api-key') {\n await runAuthPasteApiKey(provider);\n } else {\n if (!providerSupportsOAuth(provider)) {\n throw new Error(`Provider ${provider} does not support OAuth`);\n }\n console.log(`\\n🔐 Starting ${getProviderDisplayName(provider)} OAuth login...`);\n await runCliOAuthLogin({ provider, onProgress: (message) => console.log(' →', message) });\n console.log(`✅ OAuth login successful for ${provider}`);\n }\n\n if (options.setDefault) {\n const first = getAllModels().find((model) => model.provider === provider);\n if (!first) throw new Error(`No models found for provider ${provider}`);\n await runModelsSet(modelRef(first.provider, first.id));\n }\n}\n\nasync function runAuthLogout(provider: string): Promise<void> {\n await new CredentialResolver().deleteProviderCredential(provider);\n console.log(`✅ Credentials removed for ${provider}`);\n}\n\nfunction attachModelsListOptions(command: Command): Command {\n return command\n .option('--json', 'Output as JSON', false)\n .option('--all, -a', 'Show all built-in models', false)\n .option('--provider <id>', 'Filter by provider id');\n}\n\nfunction createModelsCommand(_ctx: CLIContext): Command {\n const cmd = new Command('models')\n .description('List and manage models and model auth')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc models list',\n 'xopc models status',\n 'xopc models set deepseek/deepseek-v4-flash',\n 'xopc models auth list',\n 'xopc models auth login --provider anthropic',\n 'xopc models auth paste-api-key --provider deepseek',\n ]),\n );\n\n attachModelsListOptions(cmd).action(async (options) => {\n await runModelsList(options);\n });\n\n attachModelsListOptions(cmd.command('list').description('List available models')).action(async (options) => {\n await runModelsList(options);\n });\n\n cmd\n .command('status')\n .description('Show default model and provider auth status')\n .option('--json', 'Output as JSON', false)\n .action(async (options: { json?: boolean }) => {\n await runModelsStatus(options);\n });\n\n cmd\n .command('set <model>')\n .description('Set the default model')\n .action(async (model: string) => {\n await runModelsSet(model);\n });\n\n const auth = cmd.command('auth').description('Manage model provider auth');\n\n auth\n .command('list')\n .description('List model auth credentials')\n .option('--provider <id>', 'Filter by provider id')\n .option('--json', 'Output as JSON', false)\n .action(async (options: ModelsAuthListOptions) => {\n await runAuthList(options);\n });\n\n auth\n .command('login')\n .description('Run provider auth flow')\n .requiredOption('--provider <id>', 'Provider id')\n .option('--method <method>', 'Auth method: oauth or api-key')\n .option('--set-default', 'Set the provider first model as default after login', false)\n .action(async (options: ModelsAuthLoginOptions & { provider: string }) => {\n await runAuthLogin(options.provider, options);\n });\n\n auth\n .command('paste-api-key')\n .description('Paste and save an API key')\n .requiredOption('--provider <id>', 'Provider id')\n .action(async (options: { provider: string }) => {\n await runAuthPasteApiKey(options.provider);\n });\n\n auth\n .command('logout')\n .description('Remove provider credentials')\n .requiredOption('--provider <id>', 'Provider id')\n .action(async (options: { provider: string }) => {\n await runAuthLogout(options.provider);\n });\n\n return cmd;\n}\n\nregister({\n id: 'models',\n name: 'models',\n description: 'List and manage models and model auth',\n factory: createModelsCommand,\n metadata: {\n category: 'utility',\n examples: ['xopc models list', 'xopc models status', 'xopc models auth list'],\n },\n});\n\nexport { createModelsCommand };\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
+ import { init_agent_scope, normalizeAgentId, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
1
2
  import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
2
3
  import "../agent-manifest/index.js";
3
- import { init_agent_scope, normalizeAgentId, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
4
4
  import { init_session_key, parseSessionKey } from "../routing/session-key.js";
5
5
  import { agentExists, getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
6
6
  //#region src/config/agent-profile.ts
@@ -1,5 +1,5 @@
1
- import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
2
1
  import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentDir, resolveAgentProfileDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveUserPath, validateAgentIdForNewAgent } from "../agent/agent-scope.js";
2
+ import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
3
3
  import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
4
4
  import { WORKSPACE_FILES, init_paths, resolveUserProfilePath } from "../config/paths.js";
5
5
  import { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, REQUIRED_AGENT_PROFILE_MARKDOWN_FILE_SET } from "../agent/context/workspace.js";
@@ -8,8 +8,8 @@ function isRefineAvailable(config) {
8
8
  if (!config) return false;
9
9
  for (const candidate of [
10
10
  process.env.XOPC_VOICE_REFINE_MODEL?.trim(),
11
- "openai/gpt-4o-mini",
12
- "google/gemini-2.0-flash"
11
+ "openai/gpt-5.6-luna",
12
+ "google/gemini-3.5-flash"
13
13
  ]) {
14
14
  if (!candidate) continue;
15
15
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","names":[],"sources":["../../../../../src/gateway/hono/routes/status.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport type { Hono } from 'hono';\n\nimport type { Config } from '../../../config/schema.js';\nimport { isSTTAvailable } from '../../../voice/stt/index.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nfunction isRefineAvailable(config: Config | undefined): boolean {\n if (!config) return false;\n for (const candidate of [\n process.env.XOPC_VOICE_REFINE_MODEL?.trim(),\n 'openai/gpt-4o-mini',\n 'google/gemini-2.0-flash',\n ]) {\n if (!candidate) continue;\n try {\n resolveModel(candidate);\n return true;\n } catch { /* next */ }\n }\n try {\n resolveModel(getDefaultModelSync(config));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction buildStatusPayload(service: AuthenticatedRouteDeps['service']) {\n const health = service.getHealth();\n const rows = service.getChannelsStatus();\n const channels: Record<string, { status: string }> = {};\n for (const row of rows) {\n const status = !row.enabled ? 'disabled' : row.connected ? 'connected' : 'disconnected';\n channels[row.name] = { status };\n }\n\n const config = service.currentConfig as Config | undefined;\n const sttConfig = config\n ? mergeSttConfigFromAppConfig(config.tools?.media?.audio, config.tools?.media)\n : undefined;\n\n return {\n status: health.status,\n version: health.version,\n channels,\n uptime: health.uptime,\n voice: {\n sttAvailable: sttConfig ? isSTTAvailable(sttConfig) : false,\n refineAvailable: isRefineAvailable(config),\n },\n };\n}\n\nexport function registerStatusRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n const handler = (c: Context) => c.json(buildStatusPayload(service));\n\n authenticated.get('/status', handler);\n authenticated.get('/api/status', handler);\n\n /**\n * POST /api/gateway/restart — respawn gateway process when supported (foreground `xopc gateway`).\n */\n authenticated.post('/api/gateway/restart', strictRateLimitMiddleware, (c) => {\n const result = service.triggerGatewayProcessRestart();\n if (!result.ok) {\n return c.json({ ok: false, error: result.mode, message: result.message ?? 'Restart failed' }, 400);\n }\n return c.json({ ok: true, payload: { mode: result.mode } });\n });\n}\n"],"mappings":";;;;;gBAMgF;AAGhF,SAAS,kBAAkB,QAAqC;AAC9D,KAAI,CAAC,OAAQ,QAAO;AACpB,MAAK,MAAM,aAAa;EACtB,QAAQ,IAAI,yBAAyB,MAAM;EAC3C;EACA;EACD,EAAE;AACD,MAAI,CAAC,UAAW;AAChB,MAAI;AACF,gBAAa,UAAU;AACvB,UAAO;UACD;;AAEV,KAAI;AACF,eAAa,oBAAoB,OAAO,CAAC;AACzC,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,mBAAmB,SAA4C;CACtE,MAAM,SAAS,QAAQ,WAAW;CAClC,MAAM,OAAO,QAAQ,mBAAmB;CACxC,MAAM,WAA+C,EAAE;AACvD,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,SAAS,CAAC,IAAI,UAAU,aAAa,IAAI,YAAY,cAAc;AACzE,WAAS,IAAI,QAAQ,EAAE,QAAQ;;CAGjC,MAAM,SAAS,QAAQ;CACvB,MAAM,YAAY,SACd,4BAA4B,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM,GAC5E,KAAA;AAEJ,QAAO;EACL,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB;EACA,QAAQ,OAAO;EACf,OAAO;GACL,cAAc,YAAY,eAAe,UAAU,GAAG;GACtD,iBAAiB,kBAAkB,OAAO;GAC3C;EACF;;AAGH,SAAgB,qBAAqB,eAAqB,MAAoC;CAC5F,MAAM,EAAE,SAAS,8BAA8B;CAE/C,MAAM,WAAW,MAAe,EAAE,KAAK,mBAAmB,QAAQ,CAAC;AAEnE,eAAc,IAAI,WAAW,QAAQ;AACrC,eAAc,IAAI,eAAe,QAAQ;;;;AAKzC,eAAc,KAAK,wBAAwB,4BAA4B,MAAM;EAC3E,MAAM,SAAS,QAAQ,8BAA8B;AACrD,MAAI,CAAC,OAAO,GACV,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,OAAO;GAAM,SAAS,OAAO,WAAW;GAAkB,EAAE,IAAI;AAEpG,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,MAAM,OAAO,MAAM;GAAE,CAAC;GAC3D"}
1
+ {"version":3,"file":"status.js","names":[],"sources":["../../../../../src/gateway/hono/routes/status.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport type { Hono } from 'hono';\n\nimport type { Config } from '../../../config/schema.js';\nimport { isSTTAvailable } from '../../../voice/stt/index.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nfunction isRefineAvailable(config: Config | undefined): boolean {\n if (!config) return false;\n for (const candidate of [\n process.env.XOPC_VOICE_REFINE_MODEL?.trim(),\n 'openai/gpt-5.6-luna',\n 'google/gemini-3.5-flash',\n ]) {\n if (!candidate) continue;\n try {\n resolveModel(candidate);\n return true;\n } catch { /* next */ }\n }\n try {\n resolveModel(getDefaultModelSync(config));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction buildStatusPayload(service: AuthenticatedRouteDeps['service']) {\n const health = service.getHealth();\n const rows = service.getChannelsStatus();\n const channels: Record<string, { status: string }> = {};\n for (const row of rows) {\n const status = !row.enabled ? 'disabled' : row.connected ? 'connected' : 'disconnected';\n channels[row.name] = { status };\n }\n\n const config = service.currentConfig as Config | undefined;\n const sttConfig = config\n ? mergeSttConfigFromAppConfig(config.tools?.media?.audio, config.tools?.media)\n : undefined;\n\n return {\n status: health.status,\n version: health.version,\n channels,\n uptime: health.uptime,\n voice: {\n sttAvailable: sttConfig ? isSTTAvailable(sttConfig) : false,\n refineAvailable: isRefineAvailable(config),\n },\n };\n}\n\nexport function registerStatusRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n const handler = (c: Context) => c.json(buildStatusPayload(service));\n\n authenticated.get('/status', handler);\n authenticated.get('/api/status', handler);\n\n /**\n * POST /api/gateway/restart — respawn gateway process when supported (foreground `xopc gateway`).\n */\n authenticated.post('/api/gateway/restart', strictRateLimitMiddleware, (c) => {\n const result = service.triggerGatewayProcessRestart();\n if (!result.ok) {\n return c.json({ ok: false, error: result.mode, message: result.message ?? 'Restart failed' }, 400);\n }\n return c.json({ ok: true, payload: { mode: result.mode } });\n });\n}\n"],"mappings":";;;;;gBAMgF;AAGhF,SAAS,kBAAkB,QAAqC;AAC9D,KAAI,CAAC,OAAQ,QAAO;AACpB,MAAK,MAAM,aAAa;EACtB,QAAQ,IAAI,yBAAyB,MAAM;EAC3C;EACA;EACD,EAAE;AACD,MAAI,CAAC,UAAW;AAChB,MAAI;AACF,gBAAa,UAAU;AACvB,UAAO;UACD;;AAEV,KAAI;AACF,eAAa,oBAAoB,OAAO,CAAC;AACzC,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,mBAAmB,SAA4C;CACtE,MAAM,SAAS,QAAQ,WAAW;CAClC,MAAM,OAAO,QAAQ,mBAAmB;CACxC,MAAM,WAA+C,EAAE;AACvD,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,SAAS,CAAC,IAAI,UAAU,aAAa,IAAI,YAAY,cAAc;AACzE,WAAS,IAAI,QAAQ,EAAE,QAAQ;;CAGjC,MAAM,SAAS,QAAQ;CACvB,MAAM,YAAY,SACd,4BAA4B,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM,GAC5E,KAAA;AAEJ,QAAO;EACL,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB;EACA,QAAQ,OAAO;EACf,OAAO;GACL,cAAc,YAAY,eAAe,UAAU,GAAG;GACtD,iBAAiB,kBAAkB,OAAO;GAC3C;EACF;;AAGH,SAAgB,qBAAqB,eAAqB,MAAoC;CAC5F,MAAM,EAAE,SAAS,8BAA8B;CAE/C,MAAM,WAAW,MAAe,EAAE,KAAK,mBAAmB,QAAQ,CAAC;AAEnE,eAAc,IAAI,WAAW,QAAQ;AACrC,eAAc,IAAI,eAAe,QAAQ;;;;AAKzC,eAAc,KAAK,wBAAwB,4BAA4B,MAAM;EAC3E,MAAM,SAAS,QAAQ,8BAA8B;AACrD,MAAI,CAAC,OAAO,GACV,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,OAAO;GAAM,SAAS,OAAO,WAAW;GAAkB,EAAE,IAAI;AAEpG,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,MAAM,OAAO,MAAM;GAAE,CAAC;GAC3D"}
@@ -38,7 +38,7 @@ function resolveRefineModel(config) {
38
38
  if (envRef) try {
39
39
  return resolveModel(envRef);
40
40
  } catch {}
41
- for (const candidate of ["openai/gpt-4o-mini", "google/gemini-2.0-flash"]) try {
41
+ for (const candidate of ["openai/gpt-5.6-luna", "google/gemini-3.5-flash"]) try {
42
42
  return resolveModel(candidate);
43
43
  } catch {}
44
44
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"voice.js","names":[],"sources":["../../../../../src/gateway/hono/routes/voice.ts"],"sourcesContent":["/**\n * Voice routes — POST /api/voice/transcribe\n *\n * Single endpoint that:\n * 1. Runs STT (Whisper preferred for low latency, Alibaba fallback)\n * 2. Optionally runs LLM refine on the raw transcript\n * 3. Returns { raw, refined?, language }\n *\n * LLM refine is auto-applied when a model is resolvable; gracefully degrades\n * to raw-only when no LLM is configured.\n */\n\nimport type { Hono } from 'hono';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport { isSTTAvailable, transcribe } from '../../../voice/stt/index.js';\nimport { isTTSAvailable, mergeTtsConfigFromAppConfig, speak } from '../../../voice/tts/index.js';\nimport { listTtsProvidersForApi } from '../../../voice/tts/list-providers.js';\nimport { listSttProvidersForApi } from '../../../voice/stt/list-providers.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { resolveSttProviderConfigSlice } from '../../../voice/stt/config-slice.js';\nimport { resolveTtsProviderConfigSlice } from '../../../voice/tts/config-slice.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createGatewayRouteLogger('Voice');\n\nfunction readVoiceApiKeyFromConfigFileOnly(\n cfg: Config,\n kind: 'stt' | 'tts',\n providerId: string,\n): string | undefined {\n const id = providerId.trim();\n if (!id) return undefined;\n const slice =\n kind === 'stt'\n ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio)\n : resolveTtsProviderConfigSlice(id, cfg.messages?.tts);\n const key = slice.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\nconst REFINE_TIMEOUT_MS = 15_000;\nconst MAX_AUDIO_BYTES = 25 * 1024 * 1024; // 25 MB\n\nconst REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。\n\n规则:\n1. 修正明显的语音识别错误\n2. 添加正确的标点符号\n3. 去除口语赘词(嗯、啊、那个、就是说、然后就是)\n4. 保持原意不变,不要扩写或改变语义\n5. 如果原文已经很好,原样输出\n6. 只输出整理后的文字,不要解释`;\n\nfunction resolveRefineModel(config: Config | undefined): ReturnType<typeof resolveModel> | null {\n const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();\n if (envRef) {\n try {\n return resolveModel(envRef);\n } catch { /* fall through */ }\n }\n for (const candidate of ['openai/gpt-4o-mini', 'google/gemini-2.0-flash']) {\n try {\n return resolveModel(candidate);\n } catch { /* next */ }\n }\n try {\n return resolveModel(getDefaultModelSync(config));\n } catch {\n return null;\n }\n}\n\nasync function refineTranscript(\n raw: string,\n config: Config | undefined,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n if (!raw.trim()) return undefined;\n\n const model = resolveRefineModel(config);\n if (!model) {\n log.debug('No LLM model available for voice refine; returning raw only');\n return undefined;\n }\n\n try {\n const userMsg: UserMessage = {\n role: 'user',\n content: `${REFINE_SYSTEM_PROMPT}\\n\\n原文:${raw}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(REFINE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(raw.length * 3, 4096),\n temperature: 0.2,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const refined = out.trim();\n if (!refined || refined === raw.trim()) return undefined;\n return refined;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: msg }, 'Voice refine failed; returning raw only');\n return undefined;\n }\n}\n\nexport function registerVoiceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n /**\n * GET /api/voice/providers\n *\n * Lists registered SpeechProviderPlugin ids with configured state for the\n * current gateway config (OpenClaw `tts.providers` equivalent).\n */\n authenticated.get('/api/voice/providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listTtsProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/stt-providers\n *\n * Lists registered MediaUnderstandingProvider ids with configured state for\n * the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).\n */\n authenticated.get('/api/voice/stt-providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listSttProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.\n * Body: `{ kind: 'stt' | 'tts', provider: string }`\n */\n authenticated.post('/api/voice/reveal-api-key', strictRateLimitMiddleware, async (c) => {\n let body: { kind?: unknown; provider?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n const kind = body.kind === 'stt' || body.kind === 'tts' ? body.kind : null;\n const provider = typeof body.provider === 'string' ? body.provider.trim() : '';\n if (!kind) {\n return c.json({ ok: false, error: { message: 'kind must be \"stt\" or \"tts\"' } }, 400);\n }\n if (!provider) {\n return c.json({ ok: false, error: { message: 'provider is required' } }, 400);\n }\n\n const cfg = service.currentConfig as Config;\n const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);\n return c.json({\n ok: true,\n payload: {\n kind,\n provider,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n });\n\n /**\n * POST /api/voice/tts-test\n *\n * Body: { text: string, provider?: string, model?: string, voice?: string }\n * Response: { ok: true, payload: { audio: string, format: string, provider: string } }\n */\n authenticated.post('/api/voice/tts-test', strictRateLimitMiddleware, async (c) => {\n let body: { text?: unknown; provider?: unknown; model?: unknown; voice?: unknown; providerConfig?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ ok: false, error: { message: 'text is required' } }, 400);\n }\n if (text.length > 1000) {\n return c.json({ ok: false, error: { message: 'text exceeds 1000 characters' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);\n const provider = typeof body.provider === 'string' && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;\n const providerConfig =\n body.providerConfig && typeof body.providerConfig === 'object' && !Array.isArray(body.providerConfig)\n ? (body.providerConfig as Record<string, unknown>)\n : undefined;\n const ttsConfig = {\n ...baseTtsConfig,\n enabled: true,\n provider,\n providers: {\n ...(baseTtsConfig.providers ?? {}),\n ...(providerConfig ? { [provider]: { ...(baseTtsConfig.providers?.[provider] ?? {}), ...providerConfig } } : {}),\n },\n fallback: { enabled: false, order: [provider] },\n };\n\n if (!isTTSAvailable(ttsConfig)) {\n return c.json({\n ok: false,\n error: { message: `TTS provider \"${provider}\" is not configured.` },\n }, 503);\n }\n\n try {\n const startedAt = Date.now();\n const result = await speak(text, ttsConfig, {\n appConfig: config,\n parseDirectives: false,\n tts: {\n ...(typeof body.model === 'string' && body.model.trim() ? { model: body.model.trim() } : {}),\n ...(typeof body.voice === 'string' && body.voice.trim() ? { voice: body.voice.trim() } : {}),\n },\n });\n if (!result.audio.length) {\n throw new Error(`TTS provider \"${result.provider}\" returned empty audio`);\n }\n const mimeType =\n result.format === 'opus' || result.format === 'ogg'\n ? 'audio/ogg'\n : result.format === 'mp3' || result.format === 'mpeg'\n ? 'audio/mpeg'\n : result.format === 'wav'\n ? 'audio/wav'\n : `audio/${result.format}`;\n return c.json({\n ok: true,\n payload: {\n audio: result.audio.toString('base64'),\n mimeType,\n format: result.format,\n provider: result.provider,\n latencyMs: Date.now() - startedAt,\n audioSize: result.audio.length,\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg, provider }, 'Voice TTS test failed');\n return c.json({ ok: false, error: { message: `TTS test failed: ${msg}` } }, 502);\n }\n });\n\n /**\n * POST /api/voice/transcribe\n *\n * Body: { audio: string (base64), mimeType: string, language?: string }\n * Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }\n */\n authenticated.post('/api/voice/transcribe', async (c) => {\n let body: { audio?: string; mimeType?: string; language?: string } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const { audio, mimeType, language } = body;\n if (!audio || typeof audio !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: audio (base64)' } }, 400);\n }\n if (!mimeType || typeof mimeType !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: mimeType' } }, 400);\n }\n\n // Decode base64 audio\n let audioBuffer: Buffer;\n try {\n audioBuffer = Buffer.from(audio, 'base64');\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid base64 audio data' } }, 400);\n }\n\n if (audioBuffer.length === 0) {\n return c.json({ ok: false, error: { message: 'Empty audio data' } }, 400);\n }\n if (audioBuffer.length > MAX_AUDIO_BYTES) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 400);\n }\n\n // Resolve STT config from app config\n const config = service.currentConfig as Config;\n const sttConfigRaw = config.tools?.media?.audio;\n const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);\n\n if (!isSTTAvailable(sttConfig)) {\n return c.json({\n ok: false,\n error: { message: 'STT is not configured. Enable STT in gateway config (tools.media.audio).' },\n }, 503);\n }\n\n // Run STT\n let raw: string;\n let detectedLanguage: string | undefined;\n try {\n const result = await transcribe(audioBuffer, sttConfig, {\n language: language || (sttConfig.provider === 'alibaba' ? 'zh' : undefined),\n });\n raw = result.text;\n detectedLanguage = result.language ?? language;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg }, 'Voice transcription failed');\n return c.json({ ok: false, error: { message: `Transcription failed: ${msg}` } }, 502);\n }\n\n if (!raw.trim()) {\n return c.json({\n ok: true,\n payload: { raw: '', language: detectedLanguage },\n });\n }\n\n // Run LLM refine (auto, best-effort)\n const refined = await refineTranscript(raw, config);\n\n return c.json({\n ok: true,\n payload: {\n raw,\n ...(refined ? { refined } : {}),\n ...(detectedLanguage ? { language: detectedLanguage } : {}),\n },\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"voice.js","names":[],"sources":["../../../../../src/gateway/hono/routes/voice.ts"],"sourcesContent":["/**\n * Voice routes — POST /api/voice/transcribe\n *\n * Single endpoint that:\n * 1. Runs STT (Whisper preferred for low latency, Alibaba fallback)\n * 2. Optionally runs LLM refine on the raw transcript\n * 3. Returns { raw, refined?, language }\n *\n * LLM refine is auto-applied when a model is resolvable; gracefully degrades\n * to raw-only when no LLM is configured.\n */\n\nimport type { Hono } from 'hono';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport { isSTTAvailable, transcribe } from '../../../voice/stt/index.js';\nimport { isTTSAvailable, mergeTtsConfigFromAppConfig, speak } from '../../../voice/tts/index.js';\nimport { listTtsProvidersForApi } from '../../../voice/tts/list-providers.js';\nimport { listSttProvidersForApi } from '../../../voice/stt/list-providers.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { resolveSttProviderConfigSlice } from '../../../voice/stt/config-slice.js';\nimport { resolveTtsProviderConfigSlice } from '../../../voice/tts/config-slice.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createGatewayRouteLogger('Voice');\n\nfunction readVoiceApiKeyFromConfigFileOnly(\n cfg: Config,\n kind: 'stt' | 'tts',\n providerId: string,\n): string | undefined {\n const id = providerId.trim();\n if (!id) return undefined;\n const slice =\n kind === 'stt'\n ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio)\n : resolveTtsProviderConfigSlice(id, cfg.messages?.tts);\n const key = slice.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\nconst REFINE_TIMEOUT_MS = 15_000;\nconst MAX_AUDIO_BYTES = 25 * 1024 * 1024; // 25 MB\n\nconst REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。\n\n规则:\n1. 修正明显的语音识别错误\n2. 添加正确的标点符号\n3. 去除口语赘词(嗯、啊、那个、就是说、然后就是)\n4. 保持原意不变,不要扩写或改变语义\n5. 如果原文已经很好,原样输出\n6. 只输出整理后的文字,不要解释`;\n\nfunction resolveRefineModel(config: Config | undefined): ReturnType<typeof resolveModel> | null {\n const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();\n if (envRef) {\n try {\n return resolveModel(envRef);\n } catch { /* fall through */ }\n }\n for (const candidate of ['openai/gpt-5.6-luna', 'google/gemini-3.5-flash']) {\n try {\n return resolveModel(candidate);\n } catch { /* next */ }\n }\n try {\n return resolveModel(getDefaultModelSync(config));\n } catch {\n return null;\n }\n}\n\nasync function refineTranscript(\n raw: string,\n config: Config | undefined,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n if (!raw.trim()) return undefined;\n\n const model = resolveRefineModel(config);\n if (!model) {\n log.debug('No LLM model available for voice refine; returning raw only');\n return undefined;\n }\n\n try {\n const userMsg: UserMessage = {\n role: 'user',\n content: `${REFINE_SYSTEM_PROMPT}\\n\\n原文:${raw}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(REFINE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(raw.length * 3, 4096),\n temperature: 0.2,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const refined = out.trim();\n if (!refined || refined === raw.trim()) return undefined;\n return refined;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: msg }, 'Voice refine failed; returning raw only');\n return undefined;\n }\n}\n\nexport function registerVoiceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n /**\n * GET /api/voice/providers\n *\n * Lists registered SpeechProviderPlugin ids with configured state for the\n * current gateway config (OpenClaw `tts.providers` equivalent).\n */\n authenticated.get('/api/voice/providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listTtsProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/stt-providers\n *\n * Lists registered MediaUnderstandingProvider ids with configured state for\n * the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).\n */\n authenticated.get('/api/voice/stt-providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listSttProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.\n * Body: `{ kind: 'stt' | 'tts', provider: string }`\n */\n authenticated.post('/api/voice/reveal-api-key', strictRateLimitMiddleware, async (c) => {\n let body: { kind?: unknown; provider?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n const kind = body.kind === 'stt' || body.kind === 'tts' ? body.kind : null;\n const provider = typeof body.provider === 'string' ? body.provider.trim() : '';\n if (!kind) {\n return c.json({ ok: false, error: { message: 'kind must be \"stt\" or \"tts\"' } }, 400);\n }\n if (!provider) {\n return c.json({ ok: false, error: { message: 'provider is required' } }, 400);\n }\n\n const cfg = service.currentConfig as Config;\n const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);\n return c.json({\n ok: true,\n payload: {\n kind,\n provider,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n });\n\n /**\n * POST /api/voice/tts-test\n *\n * Body: { text: string, provider?: string, model?: string, voice?: string }\n * Response: { ok: true, payload: { audio: string, format: string, provider: string } }\n */\n authenticated.post('/api/voice/tts-test', strictRateLimitMiddleware, async (c) => {\n let body: { text?: unknown; provider?: unknown; model?: unknown; voice?: unknown; providerConfig?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ ok: false, error: { message: 'text is required' } }, 400);\n }\n if (text.length > 1000) {\n return c.json({ ok: false, error: { message: 'text exceeds 1000 characters' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);\n const provider = typeof body.provider === 'string' && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;\n const providerConfig =\n body.providerConfig && typeof body.providerConfig === 'object' && !Array.isArray(body.providerConfig)\n ? (body.providerConfig as Record<string, unknown>)\n : undefined;\n const ttsConfig = {\n ...baseTtsConfig,\n enabled: true,\n provider,\n providers: {\n ...(baseTtsConfig.providers ?? {}),\n ...(providerConfig ? { [provider]: { ...(baseTtsConfig.providers?.[provider] ?? {}), ...providerConfig } } : {}),\n },\n fallback: { enabled: false, order: [provider] },\n };\n\n if (!isTTSAvailable(ttsConfig)) {\n return c.json({\n ok: false,\n error: { message: `TTS provider \"${provider}\" is not configured.` },\n }, 503);\n }\n\n try {\n const startedAt = Date.now();\n const result = await speak(text, ttsConfig, {\n appConfig: config,\n parseDirectives: false,\n tts: {\n ...(typeof body.model === 'string' && body.model.trim() ? { model: body.model.trim() } : {}),\n ...(typeof body.voice === 'string' && body.voice.trim() ? { voice: body.voice.trim() } : {}),\n },\n });\n if (!result.audio.length) {\n throw new Error(`TTS provider \"${result.provider}\" returned empty audio`);\n }\n const mimeType =\n result.format === 'opus' || result.format === 'ogg'\n ? 'audio/ogg'\n : result.format === 'mp3' || result.format === 'mpeg'\n ? 'audio/mpeg'\n : result.format === 'wav'\n ? 'audio/wav'\n : `audio/${result.format}`;\n return c.json({\n ok: true,\n payload: {\n audio: result.audio.toString('base64'),\n mimeType,\n format: result.format,\n provider: result.provider,\n latencyMs: Date.now() - startedAt,\n audioSize: result.audio.length,\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg, provider }, 'Voice TTS test failed');\n return c.json({ ok: false, error: { message: `TTS test failed: ${msg}` } }, 502);\n }\n });\n\n /**\n * POST /api/voice/transcribe\n *\n * Body: { audio: string (base64), mimeType: string, language?: string }\n * Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }\n */\n authenticated.post('/api/voice/transcribe', async (c) => {\n let body: { audio?: string; mimeType?: string; language?: string } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const { audio, mimeType, language } = body;\n if (!audio || typeof audio !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: audio (base64)' } }, 400);\n }\n if (!mimeType || typeof mimeType !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: mimeType' } }, 400);\n }\n\n // Decode base64 audio\n let audioBuffer: Buffer;\n try {\n audioBuffer = Buffer.from(audio, 'base64');\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid base64 audio data' } }, 400);\n }\n\n if (audioBuffer.length === 0) {\n return c.json({ ok: false, error: { message: 'Empty audio data' } }, 400);\n }\n if (audioBuffer.length > MAX_AUDIO_BYTES) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 400);\n }\n\n // Resolve STT config from app config\n const config = service.currentConfig as Config;\n const sttConfigRaw = config.tools?.media?.audio;\n const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);\n\n if (!isSTTAvailable(sttConfig)) {\n return c.json({\n ok: false,\n error: { message: 'STT is not configured. Enable STT in gateway config (tools.media.audio).' },\n }, 503);\n }\n\n // Run STT\n let raw: string;\n let detectedLanguage: string | undefined;\n try {\n const result = await transcribe(audioBuffer, sttConfig, {\n language: language || (sttConfig.provider === 'alibaba' ? 'zh' : undefined),\n });\n raw = result.text;\n detectedLanguage = result.language ?? language;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg }, 'Voice transcription failed');\n return c.json({ ok: false, error: { message: `Transcription failed: ${msg}` } }, 502);\n }\n\n if (!raw.trim()) {\n return c.json({\n ok: true,\n payload: { raw: '', language: detectedLanguage },\n });\n }\n\n // Run LLM refine (auto, best-effort)\n const refined = await refineTranscript(raw, config);\n\n return c.json({\n ok: true,\n payload: {\n raw,\n ...(refined ? { refined } : {}),\n ...(detectedLanguage ? { language: detectedLanguage } : {}),\n },\n });\n });\n}\n"],"mappings":"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"}
@@ -236,11 +236,6 @@ export declare const DOMESTIC_PROVIDER_PRESETS: readonly [{
236
236
  readonly name: "Kimi K2.6";
237
237
  readonly contextWindow: 128000;
238
238
  readonly input: ["text"];
239
- }, {
240
- readonly id: "kimi-k2.5";
241
- readonly name: "Kimi K2.5";
242
- readonly contextWindow: 128000;
243
- readonly input: ["text"];
244
239
  }];
245
240
  readonly aliases: ["kimi", "moonshot", "moonshot cn"];
246
241
  readonly quirks: ["Provider-specific thinking and tool-call stream fields may need compatibility handling."];
@@ -272,11 +267,6 @@ export declare const DOMESTIC_PROVIDER_PRESETS: readonly [{
272
267
  readonly name: "Kimi K2.6";
273
268
  readonly contextWindow: 128000;
274
269
  readonly input: ["text"];
275
- }, {
276
- readonly id: "kimi-k2.5";
277
- readonly name: "Kimi K2.5";
278
- readonly contextWindow: 128000;
279
- readonly input: ["text"];
280
270
  }];
281
271
  readonly aliases: ["kimi intl", "moonshot intl", "moonshot ai"];
282
272
  readonly quirks: ["Provider-specific thinking and tool-call stream fields may need compatibility handling."];
@@ -290,17 +280,17 @@ export declare const DOMESTIC_PROVIDER_PRESETS: readonly [{
290
280
  readonly envVars: ["KIMI_API_KEY", "MOONSHOT_API_KEY"];
291
281
  readonly api: "anthropic-messages";
292
282
  readonly headers: {
293
- readonly 'User-Agent': "claude-code/0.1.0";
283
+ readonly 'User-Agent': "KimiCLI/1.5";
294
284
  };
295
285
  readonly baseUrlPresets: [{
296
286
  readonly id: "coding";
297
287
  readonly label: "Coding endpoint";
298
- readonly baseUrl: "https://api.kimi.com/coding/";
288
+ readonly baseUrl: "https://api.kimi.com/coding";
299
289
  }];
300
290
  readonly defaultBaseUrlPreset: "coding";
301
- readonly defaultModel: "kimi-k2.7-code";
291
+ readonly defaultModel: "k2p7";
302
292
  readonly models: [{
303
- readonly id: "kimi-k2.7-code";
293
+ readonly id: "k2p7";
304
294
  readonly name: "Kimi K2.7 Code";
305
295
  readonly contextWindow: 262144;
306
296
  readonly maxTokens: 32768;
@@ -308,7 +298,7 @@ export declare const DOMESTIC_PROVIDER_PRESETS: readonly [{
308
298
  readonly input: ["text", "image"];
309
299
  }, {
310
300
  readonly id: "kimi-for-coding";
311
- readonly name: "Kimi Code";
301
+ readonly name: "Kimi For Coding";
312
302
  readonly contextWindow: 262144;
313
303
  readonly maxTokens: 32768;
314
304
  readonly reasoning: true;