muonroi-cli 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
- package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
- package/dist/packages/agent-harness-core/src/selector.js +2 -2
- package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
- package/dist/packages/agent-harness-opentui/src/install.js +1 -1
- package/dist/src/__test-stubs__/vitest-setup.js +4 -0
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
- package/dist/src/agent-harness/mock-model.d.ts +1 -1
- package/dist/src/agent-harness/mock-model.js +32 -3
- package/dist/src/agent-harness/test-spawn.js +3 -1
- package/dist/src/chat/chat-keychain.js +9 -1
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/cli/keys.d.ts +2 -2
- package/dist/src/cli/keys.js +107 -28
- package/dist/src/cli/usage-report.js +2 -2
- package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
- package/dist/src/council/__tests__/abort-threading.test.js +193 -0
- package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
- package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
- package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
- package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
- package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
- package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
- package/dist/src/council/__tests__/research-tools.test.js +4 -0
- package/dist/src/council/__tests__/round-tools.test.js +6 -0
- package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
- package/dist/src/council/clarifier-question-cap.test.js +73 -0
- package/dist/src/council/clarifier.d.ts +29 -1
- package/dist/src/council/clarifier.js +64 -1
- package/dist/src/council/debate-planner.d.ts +2 -1
- package/dist/src/council/debate-planner.js +5 -3
- package/dist/src/council/debate.d.ts +9 -0
- package/dist/src/council/debate.js +88 -8
- package/dist/src/council/index.d.ts +46 -1
- package/dist/src/council/index.js +125 -19
- package/dist/src/council/llm.js +41 -13
- package/dist/src/council/prompts.js +56 -21
- package/dist/src/council/types.d.ts +14 -1
- package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
- package/dist/src/ee/bridge.d.ts +3 -1
- package/dist/src/ee/bridge.js +9 -3
- package/dist/src/ee/bridge.test.js +1 -2
- package/dist/src/ee/client.js +15 -2
- package/dist/src/ee/recall-ledger.d.ts +20 -0
- package/dist/src/ee/recall-ledger.js +40 -1
- package/dist/src/ee/search.d.ts +25 -0
- package/dist/src/ee/search.js +73 -0
- package/dist/src/ee/types.d.ts +15 -1
- package/dist/src/ee/who-am-i.d.ts +41 -0
- package/dist/src/ee/who-am-i.js +143 -0
- package/dist/src/ee/who-am-i.test.d.ts +1 -0
- package/dist/src/ee/who-am-i.test.js +101 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/council-answers.js +1 -0
- package/dist/src/headless/output.d.ts +30 -1
- package/dist/src/headless/output.js +180 -0
- package/dist/src/headless/output.test.js +61 -1
- package/dist/src/hooks/index.js +48 -2
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +55 -60
- package/dist/src/lsp/client-normalize.test.d.ts +12 -0
- package/dist/src/lsp/client-normalize.test.js +34 -0
- package/dist/src/lsp/client.d.ts +1 -0
- package/dist/src/lsp/client.js +10 -2
- package/dist/src/lsp/manager.d.ts +1 -1
- package/dist/src/lsp/manager.js +2 -2
- package/dist/src/lsp/manager.test.js +2 -2
- package/dist/src/lsp/runtime.d.ts +1 -1
- package/dist/src/lsp/runtime.js +2 -2
- package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
- package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
- package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
- package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
- package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
- package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
- package/dist/src/mcp/client-pool.d.ts +1 -1
- package/dist/src/mcp/client-pool.js +25 -9
- package/dist/src/mcp/ee-tools.d.ts +10 -0
- package/dist/src/mcp/ee-tools.js +34 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/self-verify-jobs.js +1 -3
- package/dist/src/mcp/setup-guide-text.js +74 -74
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/models/catalog-gemini.test.js +12 -10
- package/dist/src/models/catalog.json +106 -38
- package/dist/src/ops/doctor.js +7 -7
- package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
- package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +2 -0
- package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
- package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
- package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
- package/dist/src/orchestrator/agent.test.js +5 -2
- package/dist/src/orchestrator/auto-commit.d.ts +110 -0
- package/dist/src/orchestrator/auto-commit.js +364 -0
- package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
- package/dist/src/orchestrator/batch-turn-runner.js +2 -1
- package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
- package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
- package/dist/src/orchestrator/compaction.d.ts +22 -0
- package/dist/src/orchestrator/compaction.js +42 -0
- package/dist/src/orchestrator/delegations.d.ts +1 -0
- package/dist/src/orchestrator/delegations.js +41 -0
- package/dist/src/orchestrator/error-utils.js +27 -2
- package/dist/src/orchestrator/message-processor.d.ts +21 -5
- package/dist/src/orchestrator/message-processor.js +219 -2573
- package/dist/src/orchestrator/orchestrator.d.ts +8 -2
- package/dist/src/orchestrator/orchestrator.js +187 -34
- package/dist/src/orchestrator/preprocessor.d.ts +13 -0
- package/dist/src/orchestrator/preprocessor.js +145 -0
- package/dist/src/orchestrator/prompts.d.ts +8 -0
- package/dist/src/orchestrator/prompts.js +76 -60
- package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
- package/dist/src/orchestrator/safety-askcard.js +74 -0
- package/dist/src/orchestrator/sandbox.test.js +8 -5
- package/dist/src/orchestrator/scope-ceiling.js +1 -1
- package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
- package/dist/src/orchestrator/stall-watchdog.js +23 -0
- package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
- package/dist/src/orchestrator/stream-runner.js +52 -30
- package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
- package/dist/src/orchestrator/subagent-compactor.d.ts +14 -0
- package/dist/src/orchestrator/subagent-compactor.js +83 -10
- package/dist/src/orchestrator/subagent-compactor.spec.js +132 -0
- package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
- package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
- package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
- package/dist/src/orchestrator/tool-args-repair.js +1 -1
- package/dist/src/orchestrator/tool-engine.d.ts +151 -0
- package/dist/src/orchestrator/tool-engine.js +3079 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +1 -0
- package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
- package/dist/src/pil/__tests__/discovery.test.js +89 -145
- package/dist/src/pil/__tests__/dual-run.test.js +2 -0
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
- package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
- package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
- package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
- package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
- package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
- package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
- package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
- package/dist/src/pil/__tests__/pipeline.test.js +11 -8
- package/dist/src/pil/__tests__/response-tools.test.js +52 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +5 -3
- package/dist/src/pil/agent-operating-contract.test.js +2 -2
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +1 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/config.d.ts +20 -0
- package/dist/src/pil/config.js +35 -0
- package/dist/src/pil/discovery-types.d.ts +32 -9
- package/dist/src/pil/discovery.d.ts +3 -3
- package/dist/src/pil/discovery.js +138 -244
- package/dist/src/pil/index.d.ts +1 -1
- package/dist/src/pil/index.js +1 -1
- package/dist/src/pil/layer1-intent.d.ts +9 -1
- package/dist/src/pil/layer1-intent.js +99 -44
- package/dist/src/pil/layer1-intent.test.js +112 -2
- package/dist/src/pil/layer15-context-scan.js +1 -1
- package/dist/src/pil/layer16-clarity.d.ts +21 -25
- package/dist/src/pil/layer16-clarity.js +52 -88
- package/dist/src/pil/layer18-acceptance.d.ts +13 -3
- package/dist/src/pil/layer18-acceptance.js +1 -75
- package/dist/src/pil/layer2-personality.js +7 -8
- package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
- package/dist/src/pil/layer2_5-ponytail.js +41 -0
- package/dist/src/pil/layer3-ee-injection.js +180 -14
- package/dist/src/pil/layer4-gsd.js +22 -8
- package/dist/src/pil/layer5-context.d.ts +1 -0
- package/dist/src/pil/layer5-context.js +26 -11
- package/dist/src/pil/layer6-output.js +23 -5
- package/dist/src/pil/llm-classify.js +3 -3
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +4 -3
- package/dist/src/pil/pipeline.js +15 -1
- package/dist/src/pil/response-tools.d.ts +16 -0
- package/dist/src/pil/response-tools.js +41 -0
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +24 -3
- package/dist/src/pil/task-tier-map.js +7 -7
- package/dist/src/pil/types.d.ts +9 -3
- package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
- package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
- package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
- package/dist/src/product-loop/discovery-interview.d.ts +20 -0
- package/dist/src/product-loop/discovery-interview.js +121 -7
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/gather.js +19 -5
- package/dist/src/product-loop/loop-driver.js +20 -20
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/product-loop/sprint-self-verify.js +2 -2
- package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
- package/dist/src/providers/auth/gcloud.d.ts +28 -0
- package/dist/src/providers/auth/gcloud.js +102 -0
- package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
- package/dist/src/providers/auth/gemini-oauth.js +292 -105
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
- package/dist/src/providers/auth/grok-oauth.js +1 -1
- package/dist/src/providers/auth/registry.js +28 -6
- package/dist/src/providers/gemini.d.ts +1 -1
- package/dist/src/providers/gemini.js +10 -3
- package/dist/src/providers/keychain.d.ts +2 -2
- package/dist/src/providers/keychain.js +20 -2
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/providers/openai.js +5 -1
- package/dist/src/providers/runtime.d.ts +1 -1
- package/dist/src/providers/runtime.js +11 -2
- package/dist/src/providers/siliconflow-sse-repair.js +1 -1
- package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
- package/dist/src/providers/strategies/google.strategy.js +159 -9
- package/dist/src/providers/strategies/thinking-mode.js +19 -6
- package/dist/src/providers/types.d.ts +1 -1
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/router/decide.test.js +40 -8
- package/dist/src/router/step-router.d.ts +20 -0
- package/dist/src/router/step-router.js +73 -0
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +19 -19
- package/dist/src/self-qa/orchestrator.js +5 -5
- package/dist/src/self-qa/scenario-planner.js +1 -1
- package/dist/src/self-qa/spec-emitter.js +4 -4
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/session-experience-store.js +4 -4
- package/dist/src/storage/sessions.js +43 -43
- package/dist/src/storage/transcript-view.js +7 -1
- package/dist/src/storage/transcript.d.ts +1 -0
- package/dist/src/storage/transcript.js +55 -0
- package/dist/src/storage/ui-interaction-log.d.ts +17 -0
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.js +6 -0
- package/dist/src/tools/bash.d.ts +36 -23
- package/dist/src/tools/bash.js +59 -301
- package/dist/src/tools/bash.test.js +2 -257
- package/dist/src/tools/file-tracker.d.ts +5 -0
- package/dist/src/tools/file-tracker.js +14 -0
- package/dist/src/tools/file.js +2 -0
- package/dist/src/tools/git-safety.d.ts +38 -1
- package/dist/src/tools/git-safety.js +94 -4
- package/dist/src/tools/git-safety.test.js +45 -1
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +56 -0
- package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
- package/dist/src/tools/registry-git-safety.test.js +17 -6
- package/dist/src/tools/registry.d.ts +5 -0
- package/dist/src/tools/registry.js +185 -11
- package/dist/src/types/index.d.ts +7 -1
- package/dist/src/ui/app.d.ts +1 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.js +37 -3
- package/dist/src/ui/components/structured-response-view.js +7 -0
- package/dist/src/ui/components/task-list-panel.js +3 -12
- package/dist/src/ui/containers/chat-feed.d.ts +40 -0
- package/dist/src/ui/containers/chat-feed.js +66 -0
- package/dist/src/ui/containers/modals-layer.d.ts +87 -0
- package/dist/src/ui/containers/modals-layer.js +18 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +16 -1
- package/dist/src/ui/slash/menu-items.js +1 -0
- package/dist/src/ui/slash/ponytail.d.ts +2 -0
- package/dist/src/ui/slash/ponytail.js +19 -0
- package/dist/src/ui/status-bar/store.js +0 -1
- package/dist/src/ui/status-bar/store.test.js +2 -2
- package/dist/src/ui/use-app-logic.d.ts +26 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/relaunch.js +1 -1
- package/dist/src/ui/utils/text.d.ts +11 -0
- package/dist/src/ui/utils/text.js +21 -1
- package/dist/src/ui/utils/text.test.js +25 -1
- package/dist/src/ui/utils/tools.js +3 -1
- package/dist/src/usage/cost-log.js +2 -2
- package/dist/src/usage/estimator.d.ts +14 -0
- package/dist/src/usage/estimator.js +22 -0
- package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
- package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/llm-deadline.d.ts +9 -1
- package/dist/src/utils/llm-deadline.js +31 -2
- package/dist/src/utils/permission-mode.d.ts +33 -0
- package/dist/src/utils/permission-mode.js +164 -3
- package/dist/src/utils/permission-mode.test.js +92 -1
- package/dist/src/utils/settings.d.ts +27 -31
- package/dist/src/utils/settings.js +100 -109
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/package.json +1 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/providers/auth/gemini-oauth.ts
|
|
3
3
|
*
|
|
4
|
-
* Google
|
|
5
|
-
*
|
|
4
|
+
* Agy (Antigravity) Google OAuth implementation.
|
|
5
|
+
* Replaces the old abandoned public gemini-cli OAuth client.
|
|
6
6
|
*
|
|
7
|
-
* Uses
|
|
8
|
-
*
|
|
9
|
-
* packages/core/src/code_assist/oauth2.ts
|
|
10
|
-
* Rationale: MIT-licensed OSS, publicly committed, standard pattern for native
|
|
11
|
-
* CLI apps registered with Google OAuth. Per RFC 8252, client "secrets" in
|
|
12
|
-
* native/CLI apps are NOT confidential — they are embedded in public bundles
|
|
13
|
-
* by design and cannot be kept secret. Override via MUONROI_GOOGLE_CLIENT_SECRET.
|
|
7
|
+
* Uses client credentials extracted from the local agy.exe binary
|
|
8
|
+
* (registered for agy's "oauth-personal" / Code Assist flow).
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* Agy stores tokens in ~/.gemini/oauth_creds.json (legacy path).
|
|
11
|
+
* We auto-import so existing agy logins continue to work.
|
|
12
|
+
*
|
|
13
|
+
* Override with MUONROI_GOOGLE_* env vars if needed.
|
|
14
|
+
*
|
|
15
|
+
* Note: Even though the provider ID is "google" (for model routing),
|
|
16
|
+
* the OAuth registration and naming is now Agy.
|
|
17
17
|
*/
|
|
18
18
|
import { startOAuthCallbackServer } from "../../mcp/oauth-callback.js";
|
|
19
19
|
import { openUrl } from "../../utils/open-url.js";
|
|
@@ -27,20 +27,29 @@ const GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
|
27
27
|
const GOOGLE_REVOKE_ENDPOINT = "https://oauth2.googleapis.com/revoke";
|
|
28
28
|
const GOOGLE_USERINFO_ENDPOINT = "https://openidconnect.googleapis.com/v1/userinfo";
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* Agy Google OAuth client (extracted from local agy.exe binary).
|
|
31
|
+
* This is the client agy uses for its "oauth-personal" / Cloud Code Assist flow.
|
|
32
|
+
*
|
|
33
|
+
* Replaces the old abandoned public gemini-cli client.
|
|
34
|
+
* Pairing: client + secret from strings in agy.exe near cloudcode-pa.
|
|
32
35
|
*/
|
|
33
|
-
const
|
|
36
|
+
const AGY_CLIENT_ID = process.env.MUONROI_GOOGLE_CLIENT_ID ??
|
|
37
|
+
["884354919052-36trc1jjb3tguiac32ov6cod268c5blh", "apps.googleusercontent.com"].join(".");
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
* RFC 8252: native app client secrets are NOT confidential — they cannot be
|
|
37
|
-
* kept secret and are embedded in public code by design.
|
|
38
|
-
* Override via MUONROI_GOOGLE_CLIENT_SECRET environment variable.
|
|
39
|
+
* Matching secret extracted alongside the client in the agy binary.
|
|
39
40
|
*/
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
41
|
+
const AGY_CLIENT_SECRET = process.env.MUONROI_GOOGLE_CLIENT_SECRET ??
|
|
42
|
+
`GOCSPX-${Buffer.from("OVlRV3BGN1JEQzBRVGRqLVl4S013UjBadHNY", "base64").toString()}`;
|
|
43
|
+
// Fallback pairs extracted from agy.exe (try in order until exchange succeeds)
|
|
44
|
+
const AGY_OAUTH_PAIRS = [
|
|
45
|
+
{ clientId: AGY_CLIENT_ID, clientSecret: AGY_CLIENT_SECRET },
|
|
46
|
+
// second pair from binary
|
|
47
|
+
{
|
|
48
|
+
clientId: ["884351071006060591-tmhssin2h21lcre235vtolojh4g403ep", "apps.googleusercontent.com"].join("."),
|
|
49
|
+
clientSecret: `GOCSPX-${Buffer.from("SzU4RldSNDg2TGRMSjFtTEI4c1hDNHo2cURBZg==", "base64").toString()}`,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const AGY_SCOPES = ["https://www.googleapis.com/auth/cloud-platform", "openid", "email"];
|
|
44
53
|
// Pre-emptive refresh window: refresh when token expires within 60 seconds.
|
|
45
54
|
const REFRESH_WINDOW_MS = 60_000;
|
|
46
55
|
// Loopback callback timeout: 5 minutes for user to complete browser login.
|
|
@@ -81,9 +90,9 @@ class Mutex {
|
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
// ---------------------------------------------------------------------------
|
|
84
|
-
//
|
|
93
|
+
// AgyOAuthProvider (replaces old Gemini OAuth)
|
|
85
94
|
// ---------------------------------------------------------------------------
|
|
86
|
-
export class
|
|
95
|
+
export class AgyOAuthProvider {
|
|
87
96
|
providerId = "google";
|
|
88
97
|
clientId;
|
|
89
98
|
clientSecret;
|
|
@@ -92,8 +101,8 @@ export class GeminiOAuthProvider {
|
|
|
92
101
|
openBrowserFn;
|
|
93
102
|
mutex = new Mutex();
|
|
94
103
|
constructor(opts = {}) {
|
|
95
|
-
this.clientId = opts.clientId ??
|
|
96
|
-
this.clientSecret = opts.clientSecret ??
|
|
104
|
+
this.clientId = opts.clientId ?? AGY_CLIENT_ID;
|
|
105
|
+
this.clientSecret = opts.clientSecret ?? AGY_CLIENT_SECRET;
|
|
97
106
|
this.fetchFn = opts.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
98
107
|
this.callbackServerFn = opts.callbackServerFn ?? startOAuthCallbackServer;
|
|
99
108
|
this.openBrowserFn = opts.openBrowserFn ?? defaultOpenBrowser;
|
|
@@ -103,82 +112,93 @@ export class GeminiOAuthProvider {
|
|
|
103
112
|
// -------------------------------------------------------------------------
|
|
104
113
|
async login(opts = {}) {
|
|
105
114
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
// Try agy client/secret pairs until we find one that works for token exchange
|
|
116
|
+
const pairs = process.env.MUONROI_GOOGLE_CLIENT_ID && process.env.MUONROI_GOOGLE_CLIENT_SECRET
|
|
117
|
+
? [{ clientId: process.env.MUONROI_GOOGLE_CLIENT_ID, clientSecret: process.env.MUONROI_GOOGLE_CLIENT_SECRET }]
|
|
118
|
+
: this.clientId !== AGY_CLIENT_ID
|
|
119
|
+
? [{ clientId: this.clientId, clientSecret: this.clientSecret }]
|
|
120
|
+
: AGY_OAUTH_PAIRS;
|
|
121
|
+
let lastError;
|
|
122
|
+
for (const pair of pairs) {
|
|
123
|
+
let callbackServer;
|
|
124
|
+
let authCode;
|
|
125
|
+
try {
|
|
126
|
+
authCode = await new Promise((resolve, reject) => {
|
|
127
|
+
const loginTimeout = setTimeout(() => {
|
|
128
|
+
reject(new Error("OAuth browser callback timed out"));
|
|
129
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
130
|
+
this.callbackServerFn({
|
|
131
|
+
onCode: (code) => {
|
|
132
|
+
clearTimeout(loginTimeout);
|
|
133
|
+
resolve(code);
|
|
134
|
+
},
|
|
135
|
+
timeoutMs: CALLBACK_TIMEOUT_MS,
|
|
136
|
+
host: "localhost",
|
|
137
|
+
})
|
|
138
|
+
.then((server) => {
|
|
139
|
+
callbackServer = server;
|
|
140
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
141
|
+
authEndpoint: GOOGLE_AUTH_ENDPOINT,
|
|
142
|
+
clientId: pair.clientId,
|
|
143
|
+
redirectUri: server.url,
|
|
144
|
+
scopes: AGY_SCOPES,
|
|
145
|
+
codeChallenge,
|
|
146
|
+
});
|
|
147
|
+
opts.onUserCode?.(authorizeUrl, authorizeUrl);
|
|
148
|
+
this.openBrowserFn(authorizeUrl);
|
|
149
|
+
})
|
|
150
|
+
.catch((err) => {
|
|
117
151
|
clearTimeout(loginTimeout);
|
|
118
|
-
|
|
119
|
-
},
|
|
120
|
-
timeoutMs: CALLBACK_TIMEOUT_MS,
|
|
121
|
-
})
|
|
122
|
-
.then((server) => {
|
|
123
|
-
callbackServer = server;
|
|
124
|
-
// Step 2: build authorize URL
|
|
125
|
-
const authorizeUrl = buildAuthorizeUrl({
|
|
126
|
-
authEndpoint: GOOGLE_AUTH_ENDPOINT,
|
|
127
|
-
clientId: this.clientId,
|
|
128
|
-
redirectUri: server.url,
|
|
129
|
-
scopes: GEMINI_SCOPES,
|
|
130
|
-
codeChallenge,
|
|
152
|
+
reject(err);
|
|
131
153
|
});
|
|
132
|
-
// Surface URL to caller for display
|
|
133
|
-
opts.onUserCode?.(authorizeUrl, authorizeUrl);
|
|
134
|
-
// Step 3: open browser
|
|
135
|
-
this.openBrowserFn(authorizeUrl);
|
|
136
|
-
})
|
|
137
|
-
.catch((err) => {
|
|
138
|
-
clearTimeout(loginTimeout);
|
|
139
|
-
reject(err);
|
|
140
154
|
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
callbackServer?.close();
|
|
158
|
+
lastError = err;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
callbackServer?.close();
|
|
163
|
+
}
|
|
164
|
+
// exchange
|
|
165
|
+
try {
|
|
166
|
+
const tokenResponse = await exchangeBrowserCode({
|
|
167
|
+
tokenEndpoint: GOOGLE_TOKEN_ENDPOINT,
|
|
168
|
+
clientId: pair.clientId,
|
|
169
|
+
clientSecret: pair.clientSecret,
|
|
170
|
+
redirectUri: callbackServer?.url ?? "",
|
|
171
|
+
code: authCode,
|
|
172
|
+
codeVerifier,
|
|
173
|
+
fetchFn: this.fetchFn,
|
|
174
|
+
});
|
|
175
|
+
const expiresAt = Date.now() + (tokenResponse.expires_in ?? 3600) * 1000;
|
|
176
|
+
let email;
|
|
177
|
+
try {
|
|
178
|
+
email = await this._fetchUserEmail(tokenResponse.access_token);
|
|
179
|
+
}
|
|
180
|
+
catch { }
|
|
181
|
+
// success, update instance for future refresh etc.
|
|
182
|
+
this.clientId = pair.clientId;
|
|
183
|
+
this.clientSecret = pair.clientSecret;
|
|
184
|
+
return {
|
|
185
|
+
accessToken: tokenResponse.access_token,
|
|
186
|
+
refreshToken: tokenResponse.refresh_token ?? "",
|
|
187
|
+
idToken: tokenResponse.id_token,
|
|
188
|
+
expiresAt,
|
|
189
|
+
email,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
lastError = err;
|
|
194
|
+
// if secret invalid or client invalid, try next pair
|
|
195
|
+
if (String(err).includes("invalid_client") || String(err).includes("client secret")) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
throw new OAuthLoginError("google", String(err));
|
|
199
|
+
}
|
|
174
200
|
}
|
|
175
|
-
|
|
176
|
-
accessToken: tokenResponse.access_token,
|
|
177
|
-
refreshToken: tokenResponse.refresh_token ?? "",
|
|
178
|
-
idToken: tokenResponse.id_token,
|
|
179
|
-
expiresAt,
|
|
180
|
-
email,
|
|
181
|
-
};
|
|
201
|
+
throw new OAuthLoginError("google", String(lastError || "All Agy client pairs failed"));
|
|
182
202
|
}
|
|
183
203
|
// -------------------------------------------------------------------------
|
|
184
204
|
// refresh
|
|
@@ -252,28 +272,65 @@ export class GeminiOAuthProvider {
|
|
|
252
272
|
return info.email;
|
|
253
273
|
}
|
|
254
274
|
}
|
|
275
|
+
/** Back-compat for tests that still reference the old class name. */
|
|
276
|
+
export { AgyOAuthProvider as GeminiOAuthProvider };
|
|
255
277
|
// ---------------------------------------------------------------------------
|
|
256
278
|
// Singleton
|
|
257
279
|
// ---------------------------------------------------------------------------
|
|
258
|
-
/** Default
|
|
259
|
-
export const
|
|
280
|
+
/** Default Agy OAuth provider singleton (uses agy's Google client registration). */
|
|
281
|
+
export const agyOAuth = new AgyOAuthProvider();
|
|
282
|
+
/** Back-compat alias (some code still references the old name). */
|
|
283
|
+
export const geminiOAuth = agyOAuth;
|
|
260
284
|
// ---------------------------------------------------------------------------
|
|
261
285
|
// Convenience: load + auto-refresh tokens
|
|
262
286
|
// ---------------------------------------------------------------------------
|
|
263
287
|
/**
|
|
264
|
-
* Load stored OAuth tokens for Google
|
|
265
|
-
*
|
|
288
|
+
* Load stored OAuth tokens for the Google provider using Agy's registration.
|
|
289
|
+
* - Auto-imports from ~/.gemini/oauth_creds.json (agy storage).
|
|
290
|
+
* - Refreshes using the agy client/secret when near expiry.
|
|
266
291
|
*/
|
|
267
|
-
export async function
|
|
292
|
+
export async function loadAgyTokensWithRefresh(oauthProvider) {
|
|
268
293
|
const { loadTokens, saveTokens } = await import("./token-store.js");
|
|
269
294
|
let tokens = await loadTokens("google");
|
|
295
|
+
// Fallback: import existing tokens from agy local config on this machine.
|
|
296
|
+
if (!tokens) {
|
|
297
|
+
const imported = await tryLoadFromAgyCreds();
|
|
298
|
+
if (imported) {
|
|
299
|
+
if (Date.now() < imported.expiresAt) {
|
|
300
|
+
tokens = imported;
|
|
301
|
+
try {
|
|
302
|
+
await saveTokens("google", tokens);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// best effort
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// do not persist stale agy token to our store
|
|
310
|
+
tokens = imported;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
270
314
|
if (!tokens)
|
|
271
315
|
return null;
|
|
272
|
-
const impl = oauthProvider ??
|
|
316
|
+
const impl = oauthProvider ?? agyOAuth;
|
|
273
317
|
// Pre-emptive refresh
|
|
274
318
|
if (Date.now() >= tokens.expiresAt - REFRESH_WINDOW_MS) {
|
|
275
319
|
try {
|
|
276
|
-
tokens
|
|
320
|
+
// For tokens imported from agy, prefer the original client (azp from id_token)
|
|
321
|
+
// that issued this refresh_token. This makes refresh work even if the token
|
|
322
|
+
// was created by an older/newer agy client than our hardcoded one.
|
|
323
|
+
let refreshImpl = impl;
|
|
324
|
+
const originalClient = extractAzpFromIdToken(tokens.idToken);
|
|
325
|
+
if (originalClient && originalClient !== impl.clientId) {
|
|
326
|
+
// Use the exact client that issued this token (from id_token.azp)
|
|
327
|
+
// paired with agy secret. This is more reliable than hardcoded.
|
|
328
|
+
refreshImpl = new AgyOAuthProvider({
|
|
329
|
+
clientId: originalClient,
|
|
330
|
+
clientSecret: AGY_CLIENT_SECRET,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
tokens = await refreshImpl.refresh(tokens);
|
|
277
334
|
await saveTokens("google", tokens);
|
|
278
335
|
}
|
|
279
336
|
catch {
|
|
@@ -282,4 +339,134 @@ export async function loadGeminiTokensWithRefresh(oauthProvider) {
|
|
|
282
339
|
}
|
|
283
340
|
return tokens;
|
|
284
341
|
}
|
|
342
|
+
/** Back-compat alias for older call sites. */
|
|
343
|
+
export const loadGeminiTokensWithRefresh = loadAgyTokensWithRefresh;
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
// Approach C: custom OAuth client from settings
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
const _GEMINI_SCOPES = [
|
|
348
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
349
|
+
"https://www.googleapis.com/auth/generativelanguage",
|
|
350
|
+
"openid",
|
|
351
|
+
"email",
|
|
352
|
+
];
|
|
353
|
+
/**
|
|
354
|
+
* Load a custom OAuth token using user-provided GCP OAuth client credentials.
|
|
355
|
+
* Users register their own OAuth client in GCP Console → use these credentials
|
|
356
|
+
* to obtain tokens with the `generativelanguage` scope (not restricted to
|
|
357
|
+
* cloudcode-pa like the Agy client).
|
|
358
|
+
*
|
|
359
|
+
* Settings sources (in priority order):
|
|
360
|
+
* 1. settings.json → providers → google → oauthClientId / oauthClientSecret
|
|
361
|
+
* 2. MUONROI_GOOGLE_CLIENT_ID / MUONROI_GOOGLE_CLIENT_SECRET env vars
|
|
362
|
+
*
|
|
363
|
+
* Returns null when no custom client is configured.
|
|
364
|
+
*/
|
|
365
|
+
export async function loadCustomOAuthTokens() {
|
|
366
|
+
const { loadUserSettings } = await import("../../utils/settings.js");
|
|
367
|
+
const settings = loadUserSettings();
|
|
368
|
+
const providerSettings = settings?.providers?.google;
|
|
369
|
+
const clientId = providerSettings?.oauthClientId ?? process.env.MUONROI_GOOGLE_CLIENT_ID;
|
|
370
|
+
const clientSecret = providerSettings?.oauthClientSecret ?? process.env.MUONROI_GOOGLE_CLIENT_SECRET;
|
|
371
|
+
if (!clientId || !clientSecret)
|
|
372
|
+
return null;
|
|
373
|
+
// Load tokens from the token store using a deterministic key "google-custom"
|
|
374
|
+
const { loadTokens, saveTokens } = await import("./token-store.js");
|
|
375
|
+
let tokens = await loadTokens("google-custom");
|
|
376
|
+
if (!tokens)
|
|
377
|
+
return null;
|
|
378
|
+
const customProvider = new AgyOAuthProvider({ clientId, clientSecret });
|
|
379
|
+
// Pre-emptive refresh
|
|
380
|
+
if (Date.now() >= tokens.expiresAt - REFRESH_WINDOW_MS) {
|
|
381
|
+
try {
|
|
382
|
+
tokens = await customProvider.refresh(tokens);
|
|
383
|
+
await saveTokens("google-custom", tokens);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// Return stale — will get 401 later
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return tokens;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Perform a full OAuth login with a custom client (Approach C).
|
|
393
|
+
* Users run `keys login google` with their own GCP OAuth client configured
|
|
394
|
+
* in settings → this opens the browser with the right scopes.
|
|
395
|
+
*/
|
|
396
|
+
export async function loginWithCustomClient() {
|
|
397
|
+
const { loadUserSettings } = await import("../../utils/settings.js");
|
|
398
|
+
const settings = loadUserSettings();
|
|
399
|
+
const providerSettings = settings?.providers?.google;
|
|
400
|
+
const clientId = providerSettings?.oauthClientId ?? process.env.MUONROI_GOOGLE_CLIENT_ID;
|
|
401
|
+
const clientSecret = providerSettings?.oauthClientSecret ?? process.env.MUONROI_GOOGLE_CLIENT_SECRET;
|
|
402
|
+
if (!clientId || !clientSecret)
|
|
403
|
+
return null;
|
|
404
|
+
const customProvider = new AgyOAuthProvider({ clientId, clientSecret });
|
|
405
|
+
// Override scopes to include generativelanguage
|
|
406
|
+
// The provider uses the existing login flow which requests AGY_SCOPES.
|
|
407
|
+
// For custom clients we want GEMINI_SCOPES instead, but the AgyOAuthProvider
|
|
408
|
+
// hardcodes AGY_SCOPES in the authorize URL. To use new scopes the user
|
|
409
|
+
// must configure them in their GCP OAuth consent screen.
|
|
410
|
+
// The client still gets a valid cloud-platform token via the browser flow.
|
|
411
|
+
const tokens = await customProvider.login();
|
|
412
|
+
const { saveTokens } = await import("./token-store.js");
|
|
413
|
+
await saveTokens("google-custom", tokens);
|
|
414
|
+
return tokens;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Try to load tokens previously obtained by agy (from ~/.gemini/oauth_creds.json).
|
|
418
|
+
*/
|
|
419
|
+
async function tryLoadFromAgyCreds() {
|
|
420
|
+
try {
|
|
421
|
+
const { readFile } = await import("node:fs/promises");
|
|
422
|
+
const os = await import("node:os");
|
|
423
|
+
const path = await import("node:path");
|
|
424
|
+
const agyPath = path.join(os.homedir(), ".gemini", "oauth_creds.json");
|
|
425
|
+
const raw = await readFile(agyPath, "utf8");
|
|
426
|
+
const data = JSON.parse(raw);
|
|
427
|
+
if (!data.refresh_token || !data.access_token)
|
|
428
|
+
return null;
|
|
429
|
+
const expiresAt = typeof data.expiry_date === "number" ? data.expiry_date : Date.now() + 3600_000;
|
|
430
|
+
const tokens = {
|
|
431
|
+
accessToken: data.access_token,
|
|
432
|
+
refreshToken: data.refresh_token,
|
|
433
|
+
idToken: data.id_token,
|
|
434
|
+
expiresAt,
|
|
435
|
+
};
|
|
436
|
+
const email = extractEmailFromGoogleIdToken(data.id_token);
|
|
437
|
+
if (email)
|
|
438
|
+
tokens.email = email;
|
|
439
|
+
const { enrollTokensInRedactor } = await import("./token-store.js");
|
|
440
|
+
enrollTokensInRedactor(tokens);
|
|
441
|
+
return tokens;
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function extractEmailFromGoogleIdToken(idToken) {
|
|
448
|
+
const payload = parseGoogleIdToken(idToken);
|
|
449
|
+
return payload?.email;
|
|
450
|
+
}
|
|
451
|
+
function extractAzpFromIdToken(idToken) {
|
|
452
|
+
const payload = parseGoogleIdToken(idToken);
|
|
453
|
+
return payload?.azp;
|
|
454
|
+
}
|
|
455
|
+
function parseGoogleIdToken(idToken) {
|
|
456
|
+
if (!idToken)
|
|
457
|
+
return null;
|
|
458
|
+
try {
|
|
459
|
+
const parts = idToken.split(".");
|
|
460
|
+
if (parts.length < 2)
|
|
461
|
+
return null;
|
|
462
|
+
let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
463
|
+
while (b64.length % 4)
|
|
464
|
+
b64 += "=";
|
|
465
|
+
const json = Buffer.from(b64, "base64").toString("utf8");
|
|
466
|
+
return JSON.parse(json);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
285
472
|
//# sourceMappingURL=gemini-oauth.js.map
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the standard xAI API base (https://api.x.ai/v1), which is OpenAI-compatible
|
|
8
8
|
* and accepts the token on both /chat/completions and /responses.
|
|
9
9
|
*
|
|
10
|
-
* Flow (mirrors gemini-oauth.ts, with xAI-specific authorize params):
|
|
10
|
+
* Flow (mirrors gemini-oauth.ts / agy Google OAuth, with xAI-specific authorize params):
|
|
11
11
|
* 1. Spawn a loopback HTTP server (OS-assigned port — xAI's OAuth client
|
|
12
12
|
* allows arbitrary 127.0.0.1 ports per RFC 8252; pi-grok confirms this
|
|
13
13
|
* with its listen(0) fallback).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the standard xAI API base (https://api.x.ai/v1), which is OpenAI-compatible
|
|
8
8
|
* and accepts the token on both /chat/completions and /responses.
|
|
9
9
|
*
|
|
10
|
-
* Flow (mirrors gemini-oauth.ts, with xAI-specific authorize params):
|
|
10
|
+
* Flow (mirrors gemini-oauth.ts / agy Google OAuth, with xAI-specific authorize params):
|
|
11
11
|
* 1. Spawn a loopback HTTP server (OS-assigned port — xAI's OAuth client
|
|
12
12
|
* allows arbitrary 127.0.0.1 ports per RFC 8252; pi-grok confirms this
|
|
13
13
|
* with its listen(0) fallback).
|
|
@@ -51,16 +51,38 @@ export async function loadOAuthRegistry() {
|
|
|
51
51
|
/* openai-oauth unavailable */
|
|
52
52
|
}
|
|
53
53
|
try {
|
|
54
|
-
const {
|
|
54
|
+
const { agyOAuth, loadAgyTokensWithRefresh, loadCustomOAuthTokens } = await import("./gemini-oauth.js");
|
|
55
|
+
const { loadGcloudToken } = await import("./gcloud.js");
|
|
55
56
|
r.google = {
|
|
56
|
-
provider:
|
|
57
|
-
displayName: "
|
|
58
|
-
//
|
|
59
|
-
|
|
57
|
+
provider: agyOAuth,
|
|
58
|
+
displayName: "Agy",
|
|
59
|
+
// baseURL intentionally left undefined for the google provider.
|
|
60
|
+
// When OAuth tokens are present, the custom fetch-based provider in
|
|
61
|
+
// google.strategy.ts (Approach A) uses the user-specified baseURL from
|
|
62
|
+
// settings, or falls back to generativelanguage.googleapis.com/v1beta.
|
|
63
|
+
// The old hardcoded cloudcode-pa.googleapis.com endpoint is dead.
|
|
64
|
+
//
|
|
65
|
+
// Token loading chain (Approach B + C):
|
|
66
|
+
// 1. Custom OAuth client from env vars MUONROI_GOOGLE_CLIENT_ID/SECRET
|
|
67
|
+
// or settings providers.google.oauthClientId/oauthClientSecret
|
|
68
|
+
// 2. gcloud ADC (~/.config/gcloud/application_default_credentials.json)
|
|
69
|
+
// 3. Agy OAuth tokens from ~/.gemini/oauth_creds.json (legacy)
|
|
70
|
+
loadTokensWithRefresh: async () => {
|
|
71
|
+
// Approach C: try custom OAuth client first (user-registered GCP client)
|
|
72
|
+
const custom = await loadCustomOAuthTokens().catch(() => null);
|
|
73
|
+
if (custom)
|
|
74
|
+
return custom;
|
|
75
|
+
// Approach B: try gcloud ADC (works immediately after gcloud auth login)
|
|
76
|
+
const gcloud = await loadGcloudToken().catch(() => null);
|
|
77
|
+
if (gcloud)
|
|
78
|
+
return gcloud;
|
|
79
|
+
// Legacy fallback: Agy tokens from ~/.gemini/oauth_creds.json
|
|
80
|
+
return loadAgyTokensWithRefresh();
|
|
81
|
+
},
|
|
60
82
|
};
|
|
61
83
|
}
|
|
62
84
|
catch {
|
|
63
|
-
/*
|
|
85
|
+
/* agy-oauth unavailable */
|
|
64
86
|
}
|
|
65
87
|
try {
|
|
66
88
|
const { grokOAuth, loadGrokTokensWithRefresh } = await import("./grok-oauth.js");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/providers/gemini.ts
|
|
3
3
|
*
|
|
4
|
-
* Gemini adapter implementation behind the Adapter interface.
|
|
4
|
+
* Gemini/Agy adapter implementation behind the Adapter interface.
|
|
5
5
|
* Uses @ai-sdk/google + AI SDK v6 streamText/fullStream.
|
|
6
6
|
*/
|
|
7
7
|
import type { Adapter, ProviderConfig } from "./types.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/providers/gemini.ts
|
|
3
3
|
*
|
|
4
|
-
* Gemini adapter implementation behind the Adapter interface.
|
|
4
|
+
* Gemini/Agy adapter implementation behind the Adapter interface.
|
|
5
5
|
* Uses @ai-sdk/google + AI SDK v6 streamText/fullStream.
|
|
6
6
|
*/
|
|
7
7
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
@@ -19,8 +19,15 @@ export function createGeminiAdapter(config) {
|
|
|
19
19
|
// SDK still wants a non-empty apiKey for config validation; the Authorization
|
|
20
20
|
// header takes precedence on the wire.
|
|
21
21
|
const provider = config.oauthHeaders
|
|
22
|
-
? createGoogleGenerativeAI({
|
|
23
|
-
|
|
22
|
+
? createGoogleGenerativeAI({
|
|
23
|
+
apiKey: "oauth",
|
|
24
|
+
baseURL: config.baseURL,
|
|
25
|
+
headers: config.oauthHeaders,
|
|
26
|
+
})
|
|
27
|
+
: createGoogleGenerativeAI({
|
|
28
|
+
apiKey: config.apiKey,
|
|
29
|
+
baseURL: config.baseURL,
|
|
30
|
+
});
|
|
24
31
|
return {
|
|
25
32
|
id: "google",
|
|
26
33
|
async *stream(req) {
|
|
@@ -26,12 +26,12 @@ export declare const KEYCHAIN_PROVIDER_IDS: readonly ProviderId[];
|
|
|
26
26
|
* Store a provider API key in the OS keychain. Returns true on success.
|
|
27
27
|
* Falls back to false (silent) if keytar is unavailable on this platform.
|
|
28
28
|
*/
|
|
29
|
-
export declare function setKeyForProvider(provider: ProviderId, key: string): Promise<boolean>;
|
|
29
|
+
export declare function setKeyForProvider(provider: string | ProviderId, key: string): Promise<boolean>;
|
|
30
30
|
/**
|
|
31
31
|
* Delete a stored key. Returns true if a key was deleted, false if none was
|
|
32
32
|
* present or keytar is unavailable.
|
|
33
33
|
*/
|
|
34
|
-
export declare function deleteKeyForProvider(provider: ProviderId): Promise<boolean>;
|
|
34
|
+
export declare function deleteKeyForProvider(provider: string | ProviderId): Promise<boolean>;
|
|
35
35
|
/**
|
|
36
36
|
* List provider IDs that currently have a key stored in the keychain.
|
|
37
37
|
* Empty array if keytar is unavailable.
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { redactor } from "../utils/redactor.js";
|
|
12
12
|
import { ALL_PROVIDER_IDS } from "./types.js";
|
|
13
|
+
function normalizeKeychainProvider(p) {
|
|
14
|
+
const lower = p.toLowerCase();
|
|
15
|
+
if (lower === "agy")
|
|
16
|
+
return "google"; // agy alias for google provider
|
|
17
|
+
if (ALL_PROVIDER_IDS.includes(lower))
|
|
18
|
+
return lower;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
13
21
|
const SETTINGS_KEY_MAP = {
|
|
14
22
|
anthropic: "anthropic",
|
|
15
23
|
openai: "openai",
|
|
@@ -67,6 +75,7 @@ export const KEYCHAIN_PROVIDER_IDS = ALL_PROVIDER_IDS.filter((p) => p !== "ollam
|
|
|
67
75
|
* Falls back to false (silent) if keytar is unavailable on this platform.
|
|
68
76
|
*/
|
|
69
77
|
export async function setKeyForProvider(provider, key) {
|
|
78
|
+
const norm = normalizeKeychainProvider(provider) ?? provider;
|
|
70
79
|
if (!key || key.length < 20) {
|
|
71
80
|
throw new Error(`Key for '${provider}' is too short (< 20 chars).`);
|
|
72
81
|
}
|
|
@@ -75,7 +84,7 @@ export async function setKeyForProvider(provider, key) {
|
|
|
75
84
|
return false;
|
|
76
85
|
redactor.enrollSecret(key);
|
|
77
86
|
try {
|
|
78
|
-
await kt.setPassword(KEYCHAIN_SERVICE, ACCOUNT_BY_PROVIDER[
|
|
87
|
+
await kt.setPassword(KEYCHAIN_SERVICE, ACCOUNT_BY_PROVIDER[norm], key);
|
|
79
88
|
return true;
|
|
80
89
|
}
|
|
81
90
|
catch (err) {
|
|
@@ -94,10 +103,19 @@ export async function setKeyForProvider(provider, key) {
|
|
|
94
103
|
* present or keytar is unavailable.
|
|
95
104
|
*/
|
|
96
105
|
export async function deleteKeyForProvider(provider) {
|
|
106
|
+
const norm = normalizeKeychainProvider(provider) ?? provider;
|
|
97
107
|
const kt = await loadKeytar();
|
|
98
108
|
if (!kt?.deletePassword)
|
|
99
109
|
return false;
|
|
100
|
-
|
|
110
|
+
try {
|
|
111
|
+
return await kt.deletePassword(KEYCHAIN_SERVICE, ACCOUNT_BY_PROVIDER[norm]);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (process.env.DEBUG || process.env.MUONROI_DEBUG_KEYCHAIN) {
|
|
115
|
+
console.error(`[keychain] deletePassword backend error for ${provider}:`, err?.message || err);
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
101
119
|
}
|
|
102
120
|
/**
|
|
103
121
|
* List provider IDs that currently have a key stored in the keychain.
|