@desplega.ai/agent-swarm 1.152.2 → 1.154.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/README.md +2 -2
- package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-eb2aj8xy.js} +4 -4
- package/dist/{actions-0sttc7f5.js → actions-kpnwp1kz.js} +31 -27
- package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
- package/dist/{app-p8teva95.js → app-zk1apkf6.js} +10 -7
- package/dist/{assistant-r45jnsrt.js → assistant-kwq4y50q.js} +32 -28
- package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
- package/dist/{boot-reembed-je26h5y7.js → boot-reembed-22hd3jjn.js} +9 -7
- package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-qe168424.js} +10 -8
- package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-pkdpsrjd.js} +7 -5
- package/dist/{claude-adapter-csc1mzka.js → claude-adapter-3fx90nww.js} +18 -16
- package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
- package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-z7fahf1k.js} +18 -16
- package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
- package/dist/{cli-wbyk465z.js → cli-09y3tjxz.js} +1 -1
- package/dist/{cli-ht5rs63n.js → cli-0e31nm4n.js} +16 -7
- package/dist/cli-0x5qzvkg.js +4279 -0
- package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
- package/dist/{cli-4ahpr4sw.js → cli-1jpjafw4.js} +5 -5
- package/dist/{cli-jbmc0r7r.js → cli-243tzws8.js} +1 -1
- package/dist/{cli-ch38q5z4.js → cli-24k806sc.js} +6789 -2542
- package/dist/cli-3bkz2p7j.js +2828 -0
- package/dist/cli-3er6gcsn.js +1849 -0
- package/dist/cli-4k7jazkq.js +3339 -0
- package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
- package/dist/{cli-qd65dvr6.js → cli-5vs0nh43.js} +65 -44
- package/dist/{cli-z3wkng35.js → cli-6r3cgpk2.js} +224 -156
- package/dist/{cli-pgj4a5fb.js → cli-71cj99hx.js} +4 -3
- package/dist/cli-8sea27q4.js +14075 -0
- package/dist/{cli-jvy9qgjs.js → cli-913yakhv.js} +22 -7
- package/dist/{cli-pf9q3696.js → cli-927erqqt.js} +5 -2
- package/dist/cli-95jnpnqg.js +230 -0
- package/dist/{cli-wpv5rgg2.js → cli-abp3g4ax.js} +15 -2
- package/dist/{cli-ckz5t2hb.js → cli-acy6bfhr.js} +10 -7
- package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
- package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
- package/dist/{cli-qn042eh7.js → cli-c1v10xb2.js} +194 -1913
- package/dist/cli-cgj4hc12.js +18902 -0
- package/dist/{cli-2nnd56zt.js → cli-e0pg4zeh.js} +3 -2
- package/dist/{cli-r84ctdxn.js → cli-e31ehqwy.js} +2 -2
- package/dist/{cli-gphgnvpf.js → cli-e3229na9.js} +3 -3
- package/dist/{cli-gpqcs935.js → cli-e3cyf59d.js} +2 -2
- package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
- package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
- package/dist/{cli-j4nv2wqy.js → cli-fdzg6jhf.js} +10 -4
- package/dist/{cli-fm4ytsdk.js → cli-g2h29qar.js} +2 -2
- package/dist/{cli-zvgj5zg8.js → cli-ge67p080.js} +302 -3497
- package/dist/cli-h38x0qhg.js +6912 -0
- package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
- package/dist/{cli-jew3jda5.js → cli-hws0vn86.js} +2 -2
- package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
- package/dist/{cli-xw7vak96.js → cli-kp3qs53m.js} +5 -5
- package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
- package/dist/{cli-zvkq5csn.js → cli-myaxtgh8.js} +7 -6
- package/dist/{cli-56h18ng2.js → cli-n62atwh9.js} +1 -1
- package/dist/cli-n7h1kqpf.js +29 -0
- package/dist/{cli-fmy65jdn.js → cli-pf1v8xhb.js} +7 -5
- package/dist/{cli-xy1cchaf.js → cli-q85qhckh.js} +238 -11
- package/dist/{cli-3bpzj8me.js → cli-r1d3dzt6.js} +2 -2
- package/dist/{pi-mono-adapter-jcqt71cg.js → cli-rxkqvngf.js} +67343 -101083
- package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
- package/dist/{cli-b9dd0h3k.js → cli-stwsmd6k.js} +3 -2
- package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
- package/dist/{cli-z61ykvam.js → cli-wg04s9kr.js} +4 -3
- package/dist/cli-yn0a417a.js +4069 -0
- package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
- package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
- package/dist/cli.js +28 -35
- package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-ttdm995v.js} +12 -11
- package/dist/{codex-hook-8jexc34x.js → codex-hook-8gx7bcz0.js} +3 -3
- package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-3v8nh4c8.js} +12 -11
- package/dist/{commands-0f5rxz51.js → commands-1cxwbhhq.js} +7 -5
- package/dist/{db-r3jxf3wy.js → db-y9zwsr79.js} +25 -5
- package/dist/{dispatcher-06vph6q4.js → dispatcher-9m9zss36.js} +30 -26
- package/dist/dsh-adapter-gsb2qqf9.js +19 -0
- package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
- package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-gwwrnzqn.js} +33 -73
- package/dist/{google-vertex-fr665tqs.js → google-vertex-5wfawpwz.js} +38 -68
- package/dist/{handlers-0hgtc910.js → handlers-36fm1bwn.js} +33 -29
- package/dist/{hook-9kvrv1er.js → hook-pntw4r8h.js} +12 -10
- package/dist/{hook-fbfhkat3.js → hook-vwq8y26p.js} +13 -11
- package/dist/{http-xbyx8k5j.js → http-20jesakk.js} +357 -261
- package/dist/{index-jsw3aynb.js → index-6artrehg.js} +30 -26
- package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
- package/dist/{index-rnyahfn5.js → index-9n5z2492.js} +30 -26
- package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
- package/dist/{index-tfsdxyv6.js → index-j5wrf1yw.js} +40 -25
- package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
- package/dist/{index-sgk50mbg.js → index-m2xqpz5r.js} +31 -27
- package/dist/{index-gpfn1741.js → index-m9ks8a7t.js} +9 -7
- package/dist/index-w097eg0x.js +5 -0
- package/dist/jiti-loader-b4837mtm.js +34 -0
- package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
- package/dist/{keepalive-s3xpr7sw.js → keepalive-pwxvxd2c.js} +9 -7
- package/dist/{lead-rq4n5r16.js → lead-m789wqa3.js} +46 -41
- package/dist/{maintenance-f0atj1ab.js → maintenance-bvg6w7e2.js} +10 -8
- package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
- package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
- package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-6v4bd737.js} +8 -6
- package/dist/{onboard-br5j1kch.js → onboard-snckpcj1.js} +115 -35
- package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
- package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
- package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
- package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
- package/dist/{otel-impl-vdqge2nt.js → otel-impl-e0ytens5.js} +4 -4
- package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
- package/dist/pi-mono-adapter-c4ggemhz.js +1316 -0
- package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-52g2ywrw.js} +9 -7
- package/dist/{rbac-roles-t78xt39b.js → rbac-roles-5455kfdn.js} +8 -6
- package/dist/{rbac-roles-jh280k9g.js → rbac-roles-hbf0xa52.js} +9 -7
- package/dist/{render-v2-7p8r2ymg.js → render-v2-npm8xdzh.js} +16 -9
- package/dist/{seed-pricing-hf889enh.js → seed-pricing-650wbr1n.js} +8 -6
- package/dist/{setup-w6mb03jv.js → setup-k1mew5rf.js} +4 -5
- package/dist/virtual-modules-2q5b2pk4.js +54 -0
- package/dist/{work-objects-nt8gy1kj.js → work-objects-2ea3kvwg.js} +33 -29
- package/dist/{worker-dwy617t0.js → worker-ec6hvc4m.js} +46 -41
- package/openapi.json +109 -16
- package/package.json +7 -6
- package/plugin/opencode-plugins/lib/memory-raters.ts +7 -0
- package/src/be/automation-preflight.ts +6 -4
- package/src/be/db/agents.ts +75 -2
- package/src/be/db/tasks/read.ts +2 -0
- package/src/be/db/tasks/write.ts +7 -1
- package/src/be/db.ts +140 -18
- package/src/be/extensions/db.ts +35 -4
- package/src/be/memory/raters/llm.ts +2 -6
- package/src/be/memory/raters/noop.ts +2 -3
- package/src/be/memory/raters/registry.ts +4 -10
- package/src/be/migrations/157_defer_provenance_and_marker.sql +27 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +213 -0
- package/src/be/rbac-audit.ts +2 -0
- package/src/be/seed/agent-fs-provision.ts +23 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/onboard/compose-generator.ts +16 -1
- package/src/commands/onboard/env-generator.ts +14 -1
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/integration-slack.tsx +56 -16
- package/src/commands/onboard/steps/review.tsx +5 -0
- package/src/commands/onboard/types.ts +6 -0
- package/src/commands/onboard.tsx +5 -1
- package/src/commands/provider-credentials.ts +14 -2
- package/src/commands/runner.ts +18 -2
- package/src/extensions/identity.ts +15 -7
- package/src/extensions/lifecycle.ts +15 -21
- package/src/hooks/codex-hook.ts +7 -1
- package/src/http/agents.ts +12 -4
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +5 -3
- package/src/http/extensions.ts +108 -34
- package/src/http/poll.ts +7 -0
- package/src/http/schedules.ts +10 -0
- package/src/http/script-runs.ts +35 -26
- package/src/http/status.ts +24 -11
- package/src/http/tasks.ts +6 -1
- package/src/prompts/base-prompt.ts +15 -7
- package/src/prompts/memories.ts +5 -7
- package/src/prompts/session-templates.ts +3 -2
- package/src/prompts/steering-delivery.ts +2 -0
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/codex-adapter.ts +3 -6
- package/src/providers/codex-models.ts +15 -1
- package/src/providers/dsh-adapter.ts +248 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/modelsdev-reasoning.json +50 -0
- package/src/providers/pi-mono-extension.ts +2 -5
- package/src/rbac/legacy-policy.ts +15 -1
- package/src/rbac/types.ts +2 -0
- package/src/server.ts +7 -1
- package/src/slack/ack.ts +43 -8
- package/src/slack/app.ts +32 -10
- package/src/slack/config.ts +29 -0
- package/src/slack/handlers.ts +7 -1
- package/src/slack/render-v2.ts +186 -17
- package/src/slack/responses.ts +1 -3
- package/src/slack/task-output.ts +66 -8
- package/src/slack/thread-buffer.ts +27 -3
- package/src/telemetry.ts +2 -3
- package/src/tests/agent-fs-provision-seeder.test.ts +56 -2
- package/src/tests/agents-harness-provider.test.ts +19 -0
- package/src/tests/agents-list-model-display.test.ts +60 -1
- package/src/tests/base-prompt.test.ts +25 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/codex-gpt6-release.test.ts +97 -0
- package/src/tests/codex-steering-hook.test.ts +2 -2
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +9 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/defer-task.test.ts +115 -16
- package/src/tests/dsh-adapter.test.ts +276 -0
- package/src/tests/e2e-nightly-report.test.ts +3 -3
- package/src/tests/e2e-redact.test.ts +2 -0
- package/src/tests/env-flag.test.ts +6 -0
- package/src/tests/extension-agent-visibility.test.ts +474 -0
- package/src/tests/extensions-db.test.ts +41 -0
- package/src/tests/extensions-http.test.ts +148 -4
- package/src/tests/extensions-lifecycle.test.ts +21 -1
- package/src/tests/extensions-mcp-tools.test.ts +9 -5
- package/src/tests/fetch-resolved-env.test.ts +43 -0
- package/src/tests/internal-ai/complete-structured.test.ts +32 -9
- package/src/tests/internal-ai/credentials.test.ts +4 -4
- package/src/tests/internal-ai/models.test.ts +20 -0
- package/src/tests/memory-rate-tool.test.ts +7 -10
- package/src/tests/memory-rater-defaults.test.ts +23 -0
- package/src/tests/memory-rater-e2e.test.ts +2 -3
- package/src/tests/migration-046-budgets.test.ts +2 -0
- package/src/tests/model-control.test.ts +2 -2
- package/src/tests/onboard-compose.test.ts +14 -0
- package/src/tests/onboard-env.test.ts +17 -0
- package/src/tests/onboard-manifest.test.ts +12 -0
- package/src/tests/opus-5-5-registry.test.ts +48 -0
- package/src/tests/pricing-refresh.test.ts +1 -1
- package/src/tests/pricing-routes.test.ts +15 -3
- package/src/tests/provider-registration.test.ts +5 -2
- package/src/tests/rbac-admission-e2e.test.ts +54 -0
- package/src/tests/rbac-e2e-helpers.ts +2 -0
- package/src/tests/rbac-engine.test.ts +32 -0
- package/src/tests/rbac-wire-e2e.test.ts +33 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/reload-config.test.ts +33 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/slack-config.test.ts +136 -0
- package/src/tests/slack-mode-lifecycle.test.ts +69 -0
- package/src/tests/slack-render-v2.test.ts +446 -16
- package/src/tests/slack-steering.test.ts +69 -1
- package/src/tests/slack-task-output.test.ts +33 -12
- package/src/tests/status.test.ts +22 -2
- package/src/tests/steering-core.test.ts +35 -0
- package/src/tests/steering-delivery.test.ts +39 -0
- package/src/tests/steering-transport.test.ts +2 -2
- package/src/tests/task-tool-preload-mcp.test.ts +3 -1
- package/src/tests/telemetry-init.test.ts +18 -0
- package/src/tests/workflow-live-recovery.test.ts +126 -0
- package/src/tests/workflow-llm.test.ts +3 -3
- package/src/tools/defer-task.ts +64 -26
- package/src/tools/extension-common.ts +4 -0
- package/src/tools/extension-install.ts +1 -1
- package/src/tools/extension-list.ts +4 -3
- package/src/tools/join-swarm.ts +1 -1
- package/src/tools/schedules/create-schedule.ts +11 -1
- package/src/tools/schedules/patch-schedule.ts +5 -0
- package/src/tools/schedules/update-schedule.ts +5 -0
- package/src/tools/send-task.ts +6 -0
- package/src/tools/utils.ts +1 -1
- package/src/types.ts +30 -8
- package/src/utils/context-window.ts +1 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/internal-ai/complete-structured.ts +8 -0
- package/src/utils/internal-ai/models.ts +5 -5
- package/src/utils/memory-raters.ts +3 -0
- package/src/workflows/engine.ts +28 -0
- package/src/workflows/recovery.ts +7 -4
- package/templates/schedules/daily-status-report/content.md +25 -12
- package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
- package/templates/skills/swarm-extensions/content.md +9 -5
- package/dist/cli-4wppfkcm.js +0 -16384
- package/dist/cli-55qrx3q8.js +0 -6041
- package/dist/cli-cg4vnyh4.js +0 -305
- package/dist/cli-jyetr64j.js +0 -52
- package/dist/cli-pp9ax9pa.js +0 -520
- package/dist/cli-qa66qaf0.js +0 -6076
- package/dist/cli-qghpvca2.js +0 -590
- package/dist/cli-rembwgt8.js +0 -861
- package/dist/cli-tcvj7j2r.js +0 -1971
- package/dist/cli-vfcth11k.js +0 -36
- package/dist/cli-w1d1bgkk.js +0 -1422
- package/dist/cli-y6x0gqtg.js +0 -5977
- package/dist/es5-tj3tn4jj.js +0 -844
- package/dist/index-1kq5hw2p.js +0 -467
- package/dist/index-1p8wdstt.js +0 -7
- package/dist/index-8fdjzb2f.js +0 -100
- package/dist/index-bxmwzbgw.js +0 -365
- package/dist/index-nzerxd5z.js +0 -7
- package/dist/index-pyymnzat.js +0 -1168
- package/dist/index-qv6atbab.js +0 -619
- package/dist/index-rja32qa7.js +0 -801
- package/dist/index-tzg5srfp.js +0 -211
- package/dist/index-vhncw983.js +0 -90
- package/dist/index-xk036zsk.js +0 -859
- package/dist/index-zyazbzcb.js +0 -6
- package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
|
@@ -0,0 +1,2828 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getValidCodexOAuth,
|
|
3
|
+
persistCodexOAuth
|
|
4
|
+
} from "./cli-e7d20cty.js";
|
|
5
|
+
import {
|
|
6
|
+
GITHUB_COPILOT_MODELS,
|
|
7
|
+
builtinModels,
|
|
8
|
+
getEnvApiKey,
|
|
9
|
+
normalizeRadiusGatewayUrl,
|
|
10
|
+
registerBundledOAuthFlowLoaders
|
|
11
|
+
} from "./cli-8sea27q4.js";
|
|
12
|
+
import {
|
|
13
|
+
init_zod
|
|
14
|
+
} from "./cli-sd0mxa3c.js";
|
|
15
|
+
import {
|
|
16
|
+
exports_external
|
|
17
|
+
} from "./cli-w5e1d1vv.js";
|
|
18
|
+
import {
|
|
19
|
+
getProviderEnvValue
|
|
20
|
+
} from "./cli-btbzvq95.js";
|
|
21
|
+
import {
|
|
22
|
+
InMemoryCredentialStore
|
|
23
|
+
} from "./cli-mcnhgnn1.js";
|
|
24
|
+
import {
|
|
25
|
+
__require
|
|
26
|
+
} from "./cli-bc05qbss.js";
|
|
27
|
+
|
|
28
|
+
// plugin/opencode-plugins/lib/memory-raters.ts
|
|
29
|
+
function getMemoryRaterNames() {
|
|
30
|
+
return (process.env.MEMORY_RATERS ?? "implicit-citation,explicit-self").split(",").map((name) => name.trim()).filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
// src/be/memory/raters/llm.ts
|
|
33
|
+
init_zod();
|
|
34
|
+
|
|
35
|
+
// src/be/memory/raters/llm-client.ts
|
|
36
|
+
var DEFAULT_TIMEOUT_MS = 30000;
|
|
37
|
+
var PROMPT_TEMPLATE = `You are scoring the usefulness of one retrieved memory.
|
|
38
|
+
|
|
39
|
+
Return ONLY a JSON object with these fields (no prose, no markdown):
|
|
40
|
+
{
|
|
41
|
+
"score": number, // 0 = misleading/unhelpful, 1 = highly useful
|
|
42
|
+
"reasoning": string // 1..500 chars, why
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
QUERY:
|
|
46
|
+
\${query}
|
|
47
|
+
|
|
48
|
+
MEMORY:
|
|
49
|
+
id: \${memoryId}
|
|
50
|
+
name: \${memoryName}
|
|
51
|
+
content: \${memoryContent}
|
|
52
|
+
|
|
53
|
+
AGENT RESPONSE / SUMMARY:
|
|
54
|
+
\${response}
|
|
55
|
+
|
|
56
|
+
Score 0..1.`;
|
|
57
|
+
var PLACEHOLDER_PREFIX = "$";
|
|
58
|
+
var QUERY_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{query}`;
|
|
59
|
+
var MEMORY_ID_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryId}`;
|
|
60
|
+
var MEMORY_NAME_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryName}`;
|
|
61
|
+
var MEMORY_CONTENT_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryContent}`;
|
|
62
|
+
var RESPONSE_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{response}`;
|
|
63
|
+
function buildPrompt(input) {
|
|
64
|
+
return PROMPT_TEMPLATE.replace(QUERY_PLACEHOLDER, input.query).replace(MEMORY_ID_PLACEHOLDER, input.memory.id).replace(MEMORY_NAME_PLACEHOLDER, input.memory.name).replace(MEMORY_CONTENT_PLACEHOLDER, input.memory.content).replace(RESPONSE_PLACEHOLDER, input.response);
|
|
65
|
+
}
|
|
66
|
+
function parseScoreAndReasoning(raw) {
|
|
67
|
+
if (typeof raw !== "string")
|
|
68
|
+
return null;
|
|
69
|
+
let parsed;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(raw.trim());
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (!parsed || typeof parsed !== "object")
|
|
76
|
+
return null;
|
|
77
|
+
const obj = parsed;
|
|
78
|
+
const score = typeof obj.score === "number" ? obj.score : null;
|
|
79
|
+
const reasoning = typeof obj.reasoning === "string" ? obj.reasoning : null;
|
|
80
|
+
if (score == null || reasoning == null)
|
|
81
|
+
return null;
|
|
82
|
+
if (!Number.isFinite(score) || score < 0 || score > 1)
|
|
83
|
+
return null;
|
|
84
|
+
if (reasoning.length === 0 || reasoning.length > 500)
|
|
85
|
+
return null;
|
|
86
|
+
return { score, reasoning };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class ClaudeCliLlmRaterClient {
|
|
90
|
+
model;
|
|
91
|
+
timeoutMs;
|
|
92
|
+
constructor(opts = {}) {
|
|
93
|
+
this.model = opts.model ?? process.env.MEMORY_LLM_RATER_MODEL ?? "haiku";
|
|
94
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
95
|
+
}
|
|
96
|
+
async rate(input) {
|
|
97
|
+
const prompt = buildPrompt(input);
|
|
98
|
+
const tmpFile = `/tmp/llm-rater-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`;
|
|
99
|
+
let stdout = "";
|
|
100
|
+
try {
|
|
101
|
+
await Bun.write(tmpFile, prompt);
|
|
102
|
+
const proc = Bun.spawn(["bash", "-c", `cat "${tmpFile}" | claude -p --model ${this.model} --output-format json`], {
|
|
103
|
+
stdout: "pipe",
|
|
104
|
+
stderr: "pipe",
|
|
105
|
+
env: { ...process.env, SKIP_SESSION_SUMMARY: "1" }
|
|
106
|
+
});
|
|
107
|
+
const timeoutId = setTimeout(() => proc.kill(), this.timeoutMs);
|
|
108
|
+
stdout = await new Response(proc.stdout).text();
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error("[memory-rater:llm] claude -p shell-out failed:", err.message);
|
|
112
|
+
return null;
|
|
113
|
+
} finally {
|
|
114
|
+
try {
|
|
115
|
+
await Bun.$`rm -f ${tmpFile}`.quiet();
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
let envelope;
|
|
119
|
+
try {
|
|
120
|
+
envelope = JSON.parse(stdout);
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return parseScoreAndReasoning(envelope.result);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/be/memory/raters/types.ts
|
|
129
|
+
var REFERENCES_SOURCE_MAX_LENGTH = 512;
|
|
130
|
+
var NUL_CHAR_CODE = 0;
|
|
131
|
+
var DEL_CHAR_CODE = 127;
|
|
132
|
+
var FIRST_PRINTABLE_ASCII = 32;
|
|
133
|
+
function sanitizeReferencesSource(input) {
|
|
134
|
+
let stripped = "";
|
|
135
|
+
for (let i = 0;i < input.length; i++) {
|
|
136
|
+
const code = input.charCodeAt(i);
|
|
137
|
+
if (code === NUL_CHAR_CODE)
|
|
138
|
+
return null;
|
|
139
|
+
if (code < FIRST_PRINTABLE_ASCII || code === DEL_CHAR_CODE) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
stripped += input[i];
|
|
143
|
+
}
|
|
144
|
+
if (stripped.length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
return stripped;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/be/memory/raters/llm.ts
|
|
150
|
+
var LLM_RATER_WEIGHT = 0.8;
|
|
151
|
+
var RatingSchema = exports_external.object({
|
|
152
|
+
id: exports_external.string().min(1),
|
|
153
|
+
score: exports_external.number().min(0).max(1),
|
|
154
|
+
reasoning: exports_external.string().min(1).max(500),
|
|
155
|
+
referencesSource: exports_external.string().min(1).max(REFERENCES_SOURCE_MAX_LENGTH).optional()
|
|
156
|
+
});
|
|
157
|
+
var SummaryWithRatingsSchema = exports_external.object({
|
|
158
|
+
summary: exports_external.string(),
|
|
159
|
+
ratings: exports_external.array(RatingSchema).default([])
|
|
160
|
+
});
|
|
161
|
+
var BASE_SUMMARIZE_PROMPT = `You are summarizing an AI agent's work session. Extract ONLY high-value learnings.
|
|
162
|
+
|
|
163
|
+
DO NOT include:
|
|
164
|
+
- Generic descriptions of what was done ("worked on task X")
|
|
165
|
+
- Tool calls or file reads
|
|
166
|
+
- Routine progress updates
|
|
167
|
+
|
|
168
|
+
DO include (if present):
|
|
169
|
+
- **Mistakes made and corrections** — what went wrong and what fixed it
|
|
170
|
+
- **Discovered patterns** — reusable approaches, APIs, or codebase conventions
|
|
171
|
+
- **Codebase knowledge** — important file paths, architecture decisions, gotchas
|
|
172
|
+
- **Environment knowledge** — service URLs, config details, tool quirks
|
|
173
|
+
- **Failed approaches** — what was tried and didn't work (and why)
|
|
174
|
+
|
|
175
|
+
Format as a bulleted list of concrete, reusable facts. If the session was routine with no significant learnings, respond with exactly: "No significant learnings."`;
|
|
176
|
+
|
|
177
|
+
class LlmRater {
|
|
178
|
+
client;
|
|
179
|
+
name = "llm";
|
|
180
|
+
constructor(client = new ClaudeCliLlmRaterClient) {
|
|
181
|
+
this.client = client;
|
|
182
|
+
}
|
|
183
|
+
async rate(ctx) {
|
|
184
|
+
const enriched = ctx;
|
|
185
|
+
if (enriched.retrievedMemoryIds.length === 0)
|
|
186
|
+
return [];
|
|
187
|
+
const memories = enriched.retrievedMemories;
|
|
188
|
+
if (!memories || memories.length === 0)
|
|
189
|
+
return [];
|
|
190
|
+
const events = [];
|
|
191
|
+
for (const memoryId of enriched.retrievedMemoryIds) {
|
|
192
|
+
const memory = memories.find((m) => m.id === memoryId);
|
|
193
|
+
if (!memory)
|
|
194
|
+
continue;
|
|
195
|
+
let result;
|
|
196
|
+
try {
|
|
197
|
+
result = await this.client.rate({
|
|
198
|
+
query: enriched.query ?? "",
|
|
199
|
+
memory,
|
|
200
|
+
response: enriched.response ?? enriched.evidence ?? ""
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.error(`[memory-rater:llm] client.rate threw for memoryId=${memoryId}:`, err.message);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (!result)
|
|
207
|
+
continue;
|
|
208
|
+
events.push({
|
|
209
|
+
memoryId,
|
|
210
|
+
signal: 2 * result.score - 1,
|
|
211
|
+
weight: LLM_RATER_WEIGHT,
|
|
212
|
+
source: "",
|
|
213
|
+
reasoning: result.reasoning
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return events;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function buildRatingsFromLlm(ratings, retrievals) {
|
|
220
|
+
const allowed = new Set(retrievals.map((r) => r.id));
|
|
221
|
+
const events = [];
|
|
222
|
+
for (const r of ratings) {
|
|
223
|
+
if (!allowed.has(r.id))
|
|
224
|
+
continue;
|
|
225
|
+
let cleanedReferencesSource;
|
|
226
|
+
if (r.referencesSource !== undefined) {
|
|
227
|
+
const cleaned = sanitizeReferencesSource(r.referencesSource);
|
|
228
|
+
if (cleaned !== null) {
|
|
229
|
+
cleanedReferencesSource = cleaned;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
events.push({
|
|
233
|
+
memoryId: r.id,
|
|
234
|
+
signal: 2 * r.score - 1,
|
|
235
|
+
weight: LLM_RATER_WEIGHT,
|
|
236
|
+
source: "llm",
|
|
237
|
+
reasoning: r.reasoning,
|
|
238
|
+
...cleanedReferencesSource !== undefined ? { referencesSource: cleanedReferencesSource } : {}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return events;
|
|
242
|
+
}
|
|
243
|
+
var RETRIEVAL_PROMPT_CONTENT_CAP = 600;
|
|
244
|
+
function buildSummaryWithRatingsPrompt(basePrompt, retrievals) {
|
|
245
|
+
if (retrievals.length === 0)
|
|
246
|
+
return basePrompt;
|
|
247
|
+
const memoryBlock = retrievals.map((m, i) => {
|
|
248
|
+
const content = m.content.length > RETRIEVAL_PROMPT_CONTENT_CAP ? `${m.content.slice(0, RETRIEVAL_PROMPT_CONTENT_CAP)}…` : m.content;
|
|
249
|
+
return `Memory #${i + 1}
|
|
250
|
+
id: ${m.id}
|
|
251
|
+
name: ${m.name}
|
|
252
|
+
content: ${content}`;
|
|
253
|
+
}).join(`
|
|
254
|
+
|
|
255
|
+
`);
|
|
256
|
+
return `${basePrompt}
|
|
257
|
+
|
|
258
|
+
CRITICAL: Your entire response MUST be a single JSON object that conforms to the schema below. Do NOT wrap it in triple-backtick fences (no \`\`\`json or \`\`\`), do NOT add a prose preamble, do NOT add trailing commentary. Just the JSON object, nothing else.
|
|
259
|
+
|
|
260
|
+
Schema:
|
|
261
|
+
{
|
|
262
|
+
"summary": string, // your existing summary text
|
|
263
|
+
"ratings": [ // one entry per memory you can score
|
|
264
|
+
{
|
|
265
|
+
"id": string, // memory id, copied from the list below
|
|
266
|
+
"score": number, // 0 = misleading/unhelpful, 1 = highly useful
|
|
267
|
+
"reasoning": string, // 1..500 chars, why
|
|
268
|
+
"referencesSource": string // OPTIONAL — see note below
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
Score ONLY memories present in the list below. Use the exact ids. Omit any you cannot evaluate.
|
|
274
|
+
|
|
275
|
+
Optionally for each rating, if the memory clearly references a specific external source (a GitHub PR/issue, a Linear issue, a customer, a Slack thread, an AgentMail thread, etc.), include a \`referencesSource\` string using the convention "<source>:<identifier>" (e.g. "github:owner/repo#N", "linear:KEY-N", "customer:<slug>"). Any prefix is fine — pick what matches the source. Omit the field if no clear external source.
|
|
276
|
+
|
|
277
|
+
Memories retrieved during this session:
|
|
278
|
+
|
|
279
|
+
${memoryBlock}`;
|
|
280
|
+
}
|
|
281
|
+
function isLlmRaterEnabled() {
|
|
282
|
+
return getMemoryRaterNames().includes("llm");
|
|
283
|
+
}
|
|
284
|
+
function dedupeRetrievalsForRater(rows) {
|
|
285
|
+
const seenSchedules = new Set;
|
|
286
|
+
const out = [];
|
|
287
|
+
for (const row of rows) {
|
|
288
|
+
const scheduleId = row.scheduleId;
|
|
289
|
+
if (typeof scheduleId === "string" && scheduleId.length > 0) {
|
|
290
|
+
if (seenSchedules.has(scheduleId))
|
|
291
|
+
continue;
|
|
292
|
+
seenSchedules.add(scheduleId);
|
|
293
|
+
}
|
|
294
|
+
out.push(row);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
async function fetchRetrievalsForTask(opts) {
|
|
299
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
300
|
+
try {
|
|
301
|
+
const url = `${opts.apiUrl}/api/memory/retrievals?taskId=${encodeURIComponent(opts.taskId)}`;
|
|
302
|
+
const res = await fetchFn(url, {
|
|
303
|
+
headers: {
|
|
304
|
+
"X-Agent-ID": opts.agentId,
|
|
305
|
+
...opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
if (!res.ok) {
|
|
309
|
+
console.error(`[memory-rater:llm] GET /api/memory/retrievals failed: ${res.status} ${res.statusText}`);
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
const body = await res.json();
|
|
313
|
+
return body.results ?? [];
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.error("[memory-rater:llm] fetchRetrievalsForTask threw:", err.message);
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function postRatings(opts) {
|
|
320
|
+
if (opts.events.length === 0)
|
|
321
|
+
return { ok: true, status: 0 };
|
|
322
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
323
|
+
const events = opts.events.map((e) => ({
|
|
324
|
+
memoryId: e.memoryId,
|
|
325
|
+
signal: e.signal,
|
|
326
|
+
weight: e.weight,
|
|
327
|
+
source: e.source,
|
|
328
|
+
...e.reasoning !== undefined ? { reasoning: e.reasoning } : {},
|
|
329
|
+
...e.referencesSource !== undefined ? { referencesSource: e.referencesSource } : {},
|
|
330
|
+
...opts.taskId ? { taskId: opts.taskId } : {}
|
|
331
|
+
}));
|
|
332
|
+
try {
|
|
333
|
+
const res = await fetchFn(`${opts.apiUrl}/api/memory/rate`, {
|
|
334
|
+
method: "POST",
|
|
335
|
+
headers: {
|
|
336
|
+
"Content-Type": "application/json",
|
|
337
|
+
"X-Agent-ID": opts.agentId,
|
|
338
|
+
...opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {}
|
|
339
|
+
},
|
|
340
|
+
body: JSON.stringify({ events })
|
|
341
|
+
});
|
|
342
|
+
if (!res.ok) {
|
|
343
|
+
const text = await res.text().catch(() => "");
|
|
344
|
+
console.error(`[memory-rater:llm] POST /api/memory/rate failed: ${res.status} ${res.statusText} ${text.slice(0, 200)}`);
|
|
345
|
+
}
|
|
346
|
+
return { ok: res.ok, status: res.status };
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error("[memory-rater:llm] postRatings threw:", err.message);
|
|
349
|
+
return { ok: false, status: 0 };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/utils/internal-ai/models.ts
|
|
354
|
+
var DEFAULT_MODEL = {
|
|
355
|
+
openrouter: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
356
|
+
anthropic: "anthropic/claude-haiku-4-5",
|
|
357
|
+
openai: "openai/gpt-6-luna",
|
|
358
|
+
"openai-codex": "openai-codex/gpt-6-luna",
|
|
359
|
+
"claude-cli": "haiku"
|
|
360
|
+
};
|
|
361
|
+
function resolveModelString(kind) {
|
|
362
|
+
return process.env.MEMORY_RATER_MODEL ?? DEFAULT_MODEL[kind];
|
|
363
|
+
}
|
|
364
|
+
function parseModelStr(modelStr) {
|
|
365
|
+
const idx = modelStr.indexOf("/");
|
|
366
|
+
if (idx < 0)
|
|
367
|
+
throw new Error(`invalid model string (no '/'): ${modelStr}`);
|
|
368
|
+
return [modelStr.slice(0, idx), modelStr.slice(idx + 1)];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/oauth-page.js
|
|
372
|
+
var LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;
|
|
373
|
+
function escapeHtml(value) {
|
|
374
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
375
|
+
}
|
|
376
|
+
function renderPage(options) {
|
|
377
|
+
const title = escapeHtml(options.title);
|
|
378
|
+
const heading = escapeHtml(options.heading);
|
|
379
|
+
const message = escapeHtml(options.message);
|
|
380
|
+
const details = options.details ? escapeHtml(options.details) : undefined;
|
|
381
|
+
return `<!doctype html>
|
|
382
|
+
<html lang="en">
|
|
383
|
+
<head>
|
|
384
|
+
<meta charset="utf-8" />
|
|
385
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
386
|
+
<title>${title}</title>
|
|
387
|
+
<style>
|
|
388
|
+
:root {
|
|
389
|
+
--text: #fafafa;
|
|
390
|
+
--text-dim: #a1a1aa;
|
|
391
|
+
--page-bg: #09090b;
|
|
392
|
+
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
393
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
394
|
+
}
|
|
395
|
+
* { box-sizing: border-box; }
|
|
396
|
+
html { color-scheme: dark; }
|
|
397
|
+
body {
|
|
398
|
+
margin: 0;
|
|
399
|
+
min-height: 100vh;
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
justify-content: center;
|
|
403
|
+
padding: 24px;
|
|
404
|
+
background: var(--page-bg);
|
|
405
|
+
color: var(--text);
|
|
406
|
+
font-family: var(--font-sans);
|
|
407
|
+
text-align: center;
|
|
408
|
+
}
|
|
409
|
+
main {
|
|
410
|
+
width: 100%;
|
|
411
|
+
max-width: 560px;
|
|
412
|
+
display: flex;
|
|
413
|
+
flex-direction: column;
|
|
414
|
+
align-items: center;
|
|
415
|
+
justify-content: center;
|
|
416
|
+
}
|
|
417
|
+
.logo {
|
|
418
|
+
width: 72px;
|
|
419
|
+
height: 72px;
|
|
420
|
+
display: block;
|
|
421
|
+
margin-bottom: 24px;
|
|
422
|
+
}
|
|
423
|
+
h1 {
|
|
424
|
+
margin: 0 0 10px;
|
|
425
|
+
font-size: 28px;
|
|
426
|
+
line-height: 1.15;
|
|
427
|
+
font-weight: 650;
|
|
428
|
+
color: var(--text);
|
|
429
|
+
}
|
|
430
|
+
p {
|
|
431
|
+
margin: 0;
|
|
432
|
+
line-height: 1.7;
|
|
433
|
+
color: var(--text-dim);
|
|
434
|
+
font-size: 15px;
|
|
435
|
+
}
|
|
436
|
+
.details {
|
|
437
|
+
margin-top: 16px;
|
|
438
|
+
font-family: var(--font-mono);
|
|
439
|
+
font-size: 13px;
|
|
440
|
+
color: var(--text-dim);
|
|
441
|
+
white-space: pre-wrap;
|
|
442
|
+
word-break: break-word;
|
|
443
|
+
}
|
|
444
|
+
</style>
|
|
445
|
+
</head>
|
|
446
|
+
<body>
|
|
447
|
+
<main>
|
|
448
|
+
<div class="logo">${LOGO_SVG}</div>
|
|
449
|
+
<h1>${heading}</h1>
|
|
450
|
+
<p>${message}</p>
|
|
451
|
+
${details ? `<div class="details">${details}</div>` : ""}
|
|
452
|
+
</main>
|
|
453
|
+
</body>
|
|
454
|
+
</html>`;
|
|
455
|
+
}
|
|
456
|
+
function oauthSuccessHtml(message) {
|
|
457
|
+
return renderPage({
|
|
458
|
+
title: "Authentication successful",
|
|
459
|
+
heading: "Authentication successful",
|
|
460
|
+
message
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function oauthErrorHtml(message, details) {
|
|
464
|
+
return renderPage({
|
|
465
|
+
title: "Authentication failed",
|
|
466
|
+
heading: "Authentication failed",
|
|
467
|
+
message,
|
|
468
|
+
details
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/pkce.js
|
|
473
|
+
function base64urlEncode(bytes) {
|
|
474
|
+
let binary = "";
|
|
475
|
+
for (const byte of bytes) {
|
|
476
|
+
binary += String.fromCharCode(byte);
|
|
477
|
+
}
|
|
478
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
479
|
+
}
|
|
480
|
+
async function generatePKCE() {
|
|
481
|
+
const verifierBytes = new Uint8Array(32);
|
|
482
|
+
crypto.getRandomValues(verifierBytes);
|
|
483
|
+
const verifier = base64urlEncode(verifierBytes);
|
|
484
|
+
const encoder = new TextEncoder;
|
|
485
|
+
const data = encoder.encode(verifier);
|
|
486
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
487
|
+
const challenge = base64urlEncode(new Uint8Array(hashBuffer));
|
|
488
|
+
return { verifier, challenge };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js
|
|
492
|
+
var nodeApis = null;
|
|
493
|
+
var nodeApisPromise = null;
|
|
494
|
+
var decode = (s) => atob(s);
|
|
495
|
+
var CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
496
|
+
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
497
|
+
var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
498
|
+
var CALLBACK_HOST = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
499
|
+
var CALLBACK_PORT = 53692;
|
|
500
|
+
var CALLBACK_PATH = "/callback";
|
|
501
|
+
var REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
502
|
+
var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
503
|
+
async function getNodeApis() {
|
|
504
|
+
if (nodeApis)
|
|
505
|
+
return nodeApis;
|
|
506
|
+
if (!nodeApisPromise) {
|
|
507
|
+
if (typeof process === "undefined" || !process.versions?.node && !process.versions?.bun) {
|
|
508
|
+
throw new Error("Anthropic OAuth is only available in Node.js environments");
|
|
509
|
+
}
|
|
510
|
+
nodeApisPromise = import("node:http").then((httpModule) => ({
|
|
511
|
+
createServer: httpModule.createServer
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
nodeApis = await nodeApisPromise;
|
|
515
|
+
return nodeApis;
|
|
516
|
+
}
|
|
517
|
+
function parseAuthorizationInput(input) {
|
|
518
|
+
const value = input.trim();
|
|
519
|
+
if (!value)
|
|
520
|
+
return {};
|
|
521
|
+
try {
|
|
522
|
+
const url = new URL(value);
|
|
523
|
+
return {
|
|
524
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
525
|
+
state: url.searchParams.get("state") ?? undefined
|
|
526
|
+
};
|
|
527
|
+
} catch {}
|
|
528
|
+
if (value.includes("#")) {
|
|
529
|
+
const [code, state] = value.split("#", 2);
|
|
530
|
+
return { code, state };
|
|
531
|
+
}
|
|
532
|
+
if (value.includes("code=")) {
|
|
533
|
+
const params = new URLSearchParams(value);
|
|
534
|
+
return {
|
|
535
|
+
code: params.get("code") ?? undefined,
|
|
536
|
+
state: params.get("state") ?? undefined
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
return { code: value };
|
|
540
|
+
}
|
|
541
|
+
function formatErrorDetails(error) {
|
|
542
|
+
if (error instanceof Error) {
|
|
543
|
+
const details = [`${error.name}: ${error.message}`];
|
|
544
|
+
const errorWithCode = error;
|
|
545
|
+
if (errorWithCode.code)
|
|
546
|
+
details.push(`code=${errorWithCode.code}`);
|
|
547
|
+
if (typeof errorWithCode.errno !== "undefined")
|
|
548
|
+
details.push(`errno=${String(errorWithCode.errno)}`);
|
|
549
|
+
if (typeof error.cause !== "undefined") {
|
|
550
|
+
details.push(`cause=${formatErrorDetails(error.cause)}`);
|
|
551
|
+
}
|
|
552
|
+
if (error.stack) {
|
|
553
|
+
details.push(`stack=${error.stack}`);
|
|
554
|
+
}
|
|
555
|
+
return details.join("; ");
|
|
556
|
+
}
|
|
557
|
+
return String(error);
|
|
558
|
+
}
|
|
559
|
+
async function startCallbackServer(expectedState) {
|
|
560
|
+
const { createServer } = await getNodeApis();
|
|
561
|
+
return new Promise((resolve, reject) => {
|
|
562
|
+
let settleWait;
|
|
563
|
+
const waitForCodePromise = new Promise((resolveWait) => {
|
|
564
|
+
let settled = false;
|
|
565
|
+
settleWait = (value) => {
|
|
566
|
+
if (settled)
|
|
567
|
+
return;
|
|
568
|
+
settled = true;
|
|
569
|
+
resolveWait(value);
|
|
570
|
+
};
|
|
571
|
+
});
|
|
572
|
+
const server = createServer((req, res) => {
|
|
573
|
+
try {
|
|
574
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
575
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
576
|
+
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
577
|
+
res.end(oauthErrorHtml("Callback route not found."));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const code = url.searchParams.get("code");
|
|
581
|
+
const state = url.searchParams.get("state");
|
|
582
|
+
const error = url.searchParams.get("error");
|
|
583
|
+
if (error) {
|
|
584
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
585
|
+
res.end(oauthErrorHtml("Anthropic authentication did not complete.", `Error: ${error}`));
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (!code || !state) {
|
|
589
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
590
|
+
res.end(oauthErrorHtml("Missing code or state parameter."));
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (state !== expectedState) {
|
|
594
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
595
|
+
res.end(oauthErrorHtml("State mismatch."));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
599
|
+
res.end(oauthSuccessHtml("Anthropic authentication completed. You can close this window."));
|
|
600
|
+
settleWait?.({ code, state });
|
|
601
|
+
} catch {
|
|
602
|
+
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
603
|
+
res.end("Internal error");
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
server.on("error", (err) => {
|
|
607
|
+
reject(err);
|
|
608
|
+
});
|
|
609
|
+
server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
|
|
610
|
+
resolve({
|
|
611
|
+
server,
|
|
612
|
+
redirectUri: REDIRECT_URI,
|
|
613
|
+
cancelWait: () => {
|
|
614
|
+
settleWait?.(null);
|
|
615
|
+
},
|
|
616
|
+
waitForCode: () => waitForCodePromise
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
async function postJson(url, body, signal) {
|
|
622
|
+
const response = await fetch(url, {
|
|
623
|
+
method: "POST",
|
|
624
|
+
headers: {
|
|
625
|
+
"Content-Type": "application/json",
|
|
626
|
+
Accept: "application/json"
|
|
627
|
+
},
|
|
628
|
+
body: JSON.stringify(body),
|
|
629
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(30000)])
|
|
630
|
+
});
|
|
631
|
+
const responseBody = await response.text();
|
|
632
|
+
if (!response.ok) {
|
|
633
|
+
throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);
|
|
634
|
+
}
|
|
635
|
+
return responseBody;
|
|
636
|
+
}
|
|
637
|
+
async function exchangeAuthorizationCode(code, state, verifier, redirectUri, signal) {
|
|
638
|
+
let responseBody;
|
|
639
|
+
try {
|
|
640
|
+
responseBody = await postJson(TOKEN_URL, {
|
|
641
|
+
grant_type: "authorization_code",
|
|
642
|
+
client_id: CLIENT_ID,
|
|
643
|
+
code,
|
|
644
|
+
state,
|
|
645
|
+
redirect_uri: redirectUri,
|
|
646
|
+
code_verifier: verifier
|
|
647
|
+
}, signal);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
throw new Error(`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`);
|
|
650
|
+
}
|
|
651
|
+
let tokenData;
|
|
652
|
+
try {
|
|
653
|
+
tokenData = JSON.parse(responseBody);
|
|
654
|
+
} catch (error) {
|
|
655
|
+
throw new Error(`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`);
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
type: "oauth",
|
|
659
|
+
refresh: tokenData.refresh_token,
|
|
660
|
+
access: tokenData.access_token,
|
|
661
|
+
expires: Date.now() + tokenData.expires_in * 1000 - 300000
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
async function loginAnthropic(interaction) {
|
|
665
|
+
const { verifier, challenge } = await generatePKCE();
|
|
666
|
+
const server = await startCallbackServer(verifier);
|
|
667
|
+
const manualAbort = new AbortController;
|
|
668
|
+
const onAbort = () => server.cancelWait();
|
|
669
|
+
interaction.signal.addEventListener("abort", onAbort, { once: true });
|
|
670
|
+
if (interaction.signal.aborted)
|
|
671
|
+
onAbort();
|
|
672
|
+
let code;
|
|
673
|
+
let state;
|
|
674
|
+
let manualInput;
|
|
675
|
+
let manualError;
|
|
676
|
+
try {
|
|
677
|
+
const authParams = new URLSearchParams({
|
|
678
|
+
code: "true",
|
|
679
|
+
client_id: CLIENT_ID,
|
|
680
|
+
response_type: "code",
|
|
681
|
+
redirect_uri: REDIRECT_URI,
|
|
682
|
+
scope: SCOPES,
|
|
683
|
+
code_challenge: challenge,
|
|
684
|
+
code_challenge_method: "S256",
|
|
685
|
+
state: verifier
|
|
686
|
+
});
|
|
687
|
+
interaction.notify({
|
|
688
|
+
type: "auth_url",
|
|
689
|
+
url: `${AUTHORIZE_URL}?${authParams.toString()}`,
|
|
690
|
+
instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here."
|
|
691
|
+
});
|
|
692
|
+
const manualPromise = interaction.prompt({
|
|
693
|
+
type: "manual_code",
|
|
694
|
+
message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
|
|
695
|
+
placeholder: REDIRECT_URI,
|
|
696
|
+
signal: manualAbort.signal
|
|
697
|
+
}).then((input) => {
|
|
698
|
+
manualInput = input;
|
|
699
|
+
server.cancelWait();
|
|
700
|
+
}).catch((error) => {
|
|
701
|
+
manualError = error instanceof Error ? error : new Error(String(error));
|
|
702
|
+
server.cancelWait();
|
|
703
|
+
});
|
|
704
|
+
const result = await server.waitForCode();
|
|
705
|
+
if (manualError)
|
|
706
|
+
throw manualError;
|
|
707
|
+
if (result?.code) {
|
|
708
|
+
code = result.code;
|
|
709
|
+
state = result.state;
|
|
710
|
+
} else if (manualInput) {
|
|
711
|
+
const parsed = parseAuthorizationInput(manualInput);
|
|
712
|
+
if (parsed.state && parsed.state !== verifier)
|
|
713
|
+
throw new Error("OAuth state mismatch");
|
|
714
|
+
code = parsed.code;
|
|
715
|
+
state = parsed.state ?? verifier;
|
|
716
|
+
}
|
|
717
|
+
if (!code) {
|
|
718
|
+
await manualPromise;
|
|
719
|
+
if (manualError)
|
|
720
|
+
throw manualError;
|
|
721
|
+
if (manualInput) {
|
|
722
|
+
const parsed = parseAuthorizationInput(manualInput);
|
|
723
|
+
if (parsed.state && parsed.state !== verifier)
|
|
724
|
+
throw new Error("OAuth state mismatch");
|
|
725
|
+
code = parsed.code;
|
|
726
|
+
state = parsed.state ?? verifier;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (!code)
|
|
730
|
+
throw new Error("Missing authorization code");
|
|
731
|
+
if (!state)
|
|
732
|
+
throw new Error("Missing OAuth state");
|
|
733
|
+
interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
|
|
734
|
+
return exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI, interaction.signal);
|
|
735
|
+
} finally {
|
|
736
|
+
interaction.signal.removeEventListener("abort", onAbort);
|
|
737
|
+
manualAbort.abort();
|
|
738
|
+
server.server.close();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async function refreshAnthropicToken(refreshToken, signal) {
|
|
742
|
+
let responseBody;
|
|
743
|
+
try {
|
|
744
|
+
responseBody = await postJson(TOKEN_URL, {
|
|
745
|
+
grant_type: "refresh_token",
|
|
746
|
+
client_id: CLIENT_ID,
|
|
747
|
+
refresh_token: refreshToken
|
|
748
|
+
}, signal);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);
|
|
751
|
+
}
|
|
752
|
+
let data;
|
|
753
|
+
try {
|
|
754
|
+
data = JSON.parse(responseBody);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
throw new Error(`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`);
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
type: "oauth",
|
|
760
|
+
refresh: data.refresh_token,
|
|
761
|
+
access: data.access_token,
|
|
762
|
+
expires: Date.now() + data.expires_in * 1000 - 300000
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
var anthropicOAuth = {
|
|
766
|
+
name: "Anthropic (Claude Pro/Max)",
|
|
767
|
+
isSubscription: true,
|
|
768
|
+
login: loginAnthropic,
|
|
769
|
+
refresh: (credential, signal) => refreshAnthropicToken(credential.refresh, signal),
|
|
770
|
+
async toAuth(credential) {
|
|
771
|
+
return { apiKey: credential.access };
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// node_modules/@earendil-works/pi-ai/dist/utils/sleep.js
|
|
776
|
+
function sleep(ms, signal) {
|
|
777
|
+
return new Promise((resolve, reject) => {
|
|
778
|
+
signal.throwIfAborted();
|
|
779
|
+
const onAbort = () => {
|
|
780
|
+
clearTimeout(timeout);
|
|
781
|
+
reject(signal.reason);
|
|
782
|
+
};
|
|
783
|
+
const timeout = setTimeout(() => {
|
|
784
|
+
signal.removeEventListener("abort", onAbort);
|
|
785
|
+
resolve();
|
|
786
|
+
}, ms);
|
|
787
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js
|
|
792
|
+
var CANCEL_MESSAGE = "Login cancelled";
|
|
793
|
+
var TIMEOUT_MESSAGE = "Device flow timed out";
|
|
794
|
+
var SLOW_DOWN_TIMEOUT_MESSAGE = "Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.";
|
|
795
|
+
var MINIMUM_INTERVAL_MS = 1000;
|
|
796
|
+
var DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
797
|
+
var SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000;
|
|
798
|
+
function abortableSleep(ms, signal, cancelMessage) {
|
|
799
|
+
return new Promise((resolve, reject) => {
|
|
800
|
+
if (signal.aborted) {
|
|
801
|
+
reject(new Error(cancelMessage));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const onAbort = () => {
|
|
805
|
+
clearTimeout(timeout);
|
|
806
|
+
reject(new Error(cancelMessage));
|
|
807
|
+
};
|
|
808
|
+
const timeout = setTimeout(() => {
|
|
809
|
+
signal.removeEventListener("abort", onAbort);
|
|
810
|
+
resolve();
|
|
811
|
+
}, ms);
|
|
812
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
async function pollOAuthDeviceCodeFlow(options) {
|
|
816
|
+
const deadline = typeof options.expiresInSeconds === "number" ? Date.now() + options.expiresInSeconds * 1000 : Number.POSITIVE_INFINITY;
|
|
817
|
+
let intervalMs = Math.max(MINIMUM_INTERVAL_MS, Math.floor((options.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000));
|
|
818
|
+
let slowDownResponses = 0;
|
|
819
|
+
if (options.waitBeforeFirstPoll) {
|
|
820
|
+
const remainingMs = deadline - Date.now();
|
|
821
|
+
if (remainingMs > 0) {
|
|
822
|
+
await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
while (Date.now() < deadline) {
|
|
826
|
+
if (options.signal.aborted) {
|
|
827
|
+
throw new Error(CANCEL_MESSAGE);
|
|
828
|
+
}
|
|
829
|
+
const result = await options.poll();
|
|
830
|
+
if (result.status === "complete") {
|
|
831
|
+
return result.value;
|
|
832
|
+
}
|
|
833
|
+
if (result.status === "failed") {
|
|
834
|
+
throw new Error(result.message);
|
|
835
|
+
}
|
|
836
|
+
if (result.status === "slow_down") {
|
|
837
|
+
slowDownResponses += 1;
|
|
838
|
+
intervalMs = typeof result.intervalSeconds === "number" && Number.isFinite(result.intervalSeconds) && result.intervalSeconds > 0 ? Math.max(MINIMUM_INTERVAL_MS, Math.floor(result.intervalSeconds * 1000)) : Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);
|
|
839
|
+
}
|
|
840
|
+
const remainingMs = deadline - Date.now();
|
|
841
|
+
if (remainingMs <= 0) {
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);
|
|
845
|
+
}
|
|
846
|
+
throw new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js
|
|
850
|
+
var decode2 = (s) => atob(s);
|
|
851
|
+
var CLIENT_ID2 = decode2("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
|
|
852
|
+
var COPILOT_HEADERS = {
|
|
853
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
854
|
+
"Editor-Version": "vscode/1.107.0",
|
|
855
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
856
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
857
|
+
};
|
|
858
|
+
var COPILOT_API_VERSION = "2026-06-01";
|
|
859
|
+
function normalizeDomain(input) {
|
|
860
|
+
const trimmed = input.trim();
|
|
861
|
+
if (!trimmed)
|
|
862
|
+
return null;
|
|
863
|
+
try {
|
|
864
|
+
const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
|
|
865
|
+
return url.hostname;
|
|
866
|
+
} catch {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
function getUrls(domain) {
|
|
871
|
+
return {
|
|
872
|
+
deviceCodeUrl: `https://${domain}/login/device/code`,
|
|
873
|
+
accessTokenUrl: `https://${domain}/login/oauth/access_token`,
|
|
874
|
+
copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function getBaseUrlFromToken(token) {
|
|
878
|
+
const match = token.match(/proxy-ep=([^;]+)/);
|
|
879
|
+
if (!match)
|
|
880
|
+
return null;
|
|
881
|
+
const proxyHost = match[1];
|
|
882
|
+
const apiHost = proxyHost.replace(/^proxy\./, "api.");
|
|
883
|
+
return `https://${apiHost}`;
|
|
884
|
+
}
|
|
885
|
+
function getGitHubCopilotBaseUrl(token, enterpriseDomain) {
|
|
886
|
+
if (token) {
|
|
887
|
+
const urlFromToken = getBaseUrlFromToken(token);
|
|
888
|
+
if (urlFromToken)
|
|
889
|
+
return urlFromToken;
|
|
890
|
+
}
|
|
891
|
+
if (enterpriseDomain)
|
|
892
|
+
return `https://copilot-api.${enterpriseDomain}`;
|
|
893
|
+
return "https://api.individual.githubcopilot.com";
|
|
894
|
+
}
|
|
895
|
+
function asRecord(value) {
|
|
896
|
+
return value && typeof value === "object" ? value : undefined;
|
|
897
|
+
}
|
|
898
|
+
function parseGitHubCopilotModelCatalog(raw, allowPolicyFallback) {
|
|
899
|
+
const data = asRecord(raw)?.data;
|
|
900
|
+
if (!Array.isArray(data)) {
|
|
901
|
+
throw new Error("Invalid Copilot models response");
|
|
902
|
+
}
|
|
903
|
+
const accountModels = data.flatMap((rawItem) => {
|
|
904
|
+
const item = asRecord(rawItem);
|
|
905
|
+
const id = item?.id;
|
|
906
|
+
if (!item || typeof id !== "string")
|
|
907
|
+
return [];
|
|
908
|
+
const capabilities = asRecord(item.capabilities);
|
|
909
|
+
const supports = asRecord(capabilities?.supports);
|
|
910
|
+
if (supports?.tool_calls === false)
|
|
911
|
+
return [];
|
|
912
|
+
return [
|
|
913
|
+
{
|
|
914
|
+
id,
|
|
915
|
+
pickerEnabled: item.model_picker_enabled === true,
|
|
916
|
+
policyState: asRecord(item.policy)?.state
|
|
917
|
+
}
|
|
918
|
+
];
|
|
919
|
+
});
|
|
920
|
+
const pickerModelIds = accountModels.filter((model) => model.pickerEnabled && model.policyState !== "disabled").map((model) => model.id);
|
|
921
|
+
const usePolicyFallback = allowPolicyFallback && pickerModelIds.length === 0;
|
|
922
|
+
const availableModelIds = pickerModelIds.length > 0 || !allowPolicyFallback ? pickerModelIds : accountModels.filter((model) => model.policyState === "enabled").map((model) => model.id);
|
|
923
|
+
const policyModelIds = accountModels.filter((model) => model.policyState === "unconfigured" && Object.hasOwn(GITHUB_COPILOT_MODELS, model.id) && (model.pickerEnabled || usePolicyFallback)).map((model) => model.id);
|
|
924
|
+
return { availableModelIds, policyModelIds };
|
|
925
|
+
}
|
|
926
|
+
async function fetchWithRateLimitRetry(url, init, signal, retryPolicy) {
|
|
927
|
+
const retryBudgetSignal = retryPolicy.maxRetries > 0 && retryPolicy.maxElapsedMs > 0 ? AbortSignal.timeout(retryPolicy.maxElapsedMs) : undefined;
|
|
928
|
+
const requestSignal = retryBudgetSignal ? AbortSignal.any([signal, retryBudgetSignal]) : signal;
|
|
929
|
+
const retryDeadline = retryBudgetSignal ? Date.now() + retryPolicy.maxElapsedMs : undefined;
|
|
930
|
+
for (let retry = 0;; retry++) {
|
|
931
|
+
const response = await fetch(url, {
|
|
932
|
+
...init,
|
|
933
|
+
signal: AbortSignal.any([requestSignal, AbortSignal.timeout(5000)])
|
|
934
|
+
});
|
|
935
|
+
if (response.status !== 429 || retry === retryPolicy.maxRetries)
|
|
936
|
+
return response;
|
|
937
|
+
const retryAfter = response.headers.get("retry-after");
|
|
938
|
+
let delayMs = 500 * 2 ** retry;
|
|
939
|
+
if (retryAfter) {
|
|
940
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
941
|
+
delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;
|
|
942
|
+
if (!Number.isFinite(delayMs))
|
|
943
|
+
return response;
|
|
944
|
+
}
|
|
945
|
+
delayMs = Math.max(0, delayMs);
|
|
946
|
+
if (retryDeadline !== undefined && delayMs >= retryDeadline - Date.now())
|
|
947
|
+
return response;
|
|
948
|
+
await response.body?.cancel();
|
|
949
|
+
await sleep(delayMs, requestSignal);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
async function fetchGitHubCopilotModels(copilotToken, enterpriseDomain, signal, retryPolicy) {
|
|
953
|
+
const baseUrl = getGitHubCopilotBaseUrl(copilotToken, enterpriseDomain);
|
|
954
|
+
const allowPolicyFallback = baseUrl === "https://api.individual.githubcopilot.com";
|
|
955
|
+
const response = await fetchWithRateLimitRetry(`${baseUrl}/models`, {
|
|
956
|
+
headers: {
|
|
957
|
+
Accept: "application/json",
|
|
958
|
+
Authorization: `Bearer ${copilotToken}`,
|
|
959
|
+
...COPILOT_HEADERS,
|
|
960
|
+
"X-GitHub-Api-Version": COPILOT_API_VERSION
|
|
961
|
+
}
|
|
962
|
+
}, signal, retryPolicy);
|
|
963
|
+
if (!response.ok) {
|
|
964
|
+
throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`);
|
|
965
|
+
}
|
|
966
|
+
return parseGitHubCopilotModelCatalog(await response.json(), allowPolicyFallback);
|
|
967
|
+
}
|
|
968
|
+
async function fetchJson(url, init) {
|
|
969
|
+
const response = await fetch(url, init);
|
|
970
|
+
if (!response.ok) {
|
|
971
|
+
const text = await response.text();
|
|
972
|
+
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
973
|
+
}
|
|
974
|
+
return response.json();
|
|
975
|
+
}
|
|
976
|
+
async function startDeviceFlow(domain, signal) {
|
|
977
|
+
const urls = getUrls(domain);
|
|
978
|
+
const data = await fetchJson(urls.deviceCodeUrl, {
|
|
979
|
+
method: "POST",
|
|
980
|
+
headers: {
|
|
981
|
+
Accept: "application/json",
|
|
982
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
983
|
+
"User-Agent": "GitHubCopilotChat/0.35.0"
|
|
984
|
+
},
|
|
985
|
+
body: new URLSearchParams({
|
|
986
|
+
client_id: CLIENT_ID2,
|
|
987
|
+
scope: "read:user"
|
|
988
|
+
}),
|
|
989
|
+
signal
|
|
990
|
+
});
|
|
991
|
+
if (!data || typeof data !== "object") {
|
|
992
|
+
throw new Error("Invalid device code response");
|
|
993
|
+
}
|
|
994
|
+
const deviceCode = data.device_code;
|
|
995
|
+
const userCode = data.user_code;
|
|
996
|
+
const verificationUri = data.verification_uri;
|
|
997
|
+
const interval = data.interval;
|
|
998
|
+
const expiresIn = data.expires_in;
|
|
999
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string" || interval !== undefined && typeof interval !== "number" || typeof expiresIn !== "number") {
|
|
1000
|
+
throw new Error("Invalid device code response fields");
|
|
1001
|
+
}
|
|
1002
|
+
let parsedUri;
|
|
1003
|
+
try {
|
|
1004
|
+
parsedUri = new URL(verificationUri);
|
|
1005
|
+
} catch {
|
|
1006
|
+
throw new Error("Untrusted verification_uri in device code response");
|
|
1007
|
+
}
|
|
1008
|
+
if (parsedUri.protocol !== "https:" && parsedUri.protocol !== "http:") {
|
|
1009
|
+
throw new Error("Untrusted verification_uri in device code response");
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
device_code: deviceCode,
|
|
1013
|
+
user_code: userCode,
|
|
1014
|
+
verification_uri: parsedUri.href,
|
|
1015
|
+
interval,
|
|
1016
|
+
expires_in: expiresIn
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
async function pollForGitHubAccessToken(domain, device, signal) {
|
|
1020
|
+
const urls = getUrls(domain);
|
|
1021
|
+
return pollOAuthDeviceCodeFlow({
|
|
1022
|
+
intervalSeconds: device.interval,
|
|
1023
|
+
expiresInSeconds: device.expires_in,
|
|
1024
|
+
waitBeforeFirstPoll: true,
|
|
1025
|
+
signal,
|
|
1026
|
+
poll: async () => {
|
|
1027
|
+
const raw = await fetchJson(urls.accessTokenUrl, {
|
|
1028
|
+
method: "POST",
|
|
1029
|
+
headers: {
|
|
1030
|
+
Accept: "application/json",
|
|
1031
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1032
|
+
"User-Agent": "GitHubCopilotChat/0.35.0"
|
|
1033
|
+
},
|
|
1034
|
+
body: new URLSearchParams({
|
|
1035
|
+
client_id: CLIENT_ID2,
|
|
1036
|
+
device_code: device.device_code,
|
|
1037
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1038
|
+
}),
|
|
1039
|
+
signal
|
|
1040
|
+
});
|
|
1041
|
+
if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
|
|
1042
|
+
return { status: "complete", value: raw.access_token };
|
|
1043
|
+
}
|
|
1044
|
+
if (raw && typeof raw === "object" && typeof raw.error === "string") {
|
|
1045
|
+
const { error, error_description: description, interval } = raw;
|
|
1046
|
+
if (error === "authorization_pending") {
|
|
1047
|
+
return { status: "pending" };
|
|
1048
|
+
}
|
|
1049
|
+
if (error === "slow_down") {
|
|
1050
|
+
return { status: "slow_down", intervalSeconds: typeof interval === "number" ? interval : undefined };
|
|
1051
|
+
}
|
|
1052
|
+
const descriptionSuffix = description ? `: ${description}` : "";
|
|
1053
|
+
return { status: "failed", message: `Device flow failed: ${error}${descriptionSuffix}` };
|
|
1054
|
+
}
|
|
1055
|
+
return { status: "failed", message: "Invalid device token response" };
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
async function refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain, signal) {
|
|
1060
|
+
const domain = enterpriseDomain || "github.com";
|
|
1061
|
+
const urls = getUrls(domain);
|
|
1062
|
+
const raw = await fetchJson(urls.copilotTokenUrl, {
|
|
1063
|
+
headers: {
|
|
1064
|
+
Accept: "application/json",
|
|
1065
|
+
Authorization: `Bearer ${refreshToken}`,
|
|
1066
|
+
...COPILOT_HEADERS
|
|
1067
|
+
},
|
|
1068
|
+
signal
|
|
1069
|
+
});
|
|
1070
|
+
if (!raw || typeof raw !== "object") {
|
|
1071
|
+
throw new Error("Invalid Copilot token response");
|
|
1072
|
+
}
|
|
1073
|
+
const token = raw.token;
|
|
1074
|
+
const expiresAt = raw.expires_at;
|
|
1075
|
+
if (typeof token !== "string" || typeof expiresAt !== "number") {
|
|
1076
|
+
throw new Error("Invalid Copilot token response fields");
|
|
1077
|
+
}
|
|
1078
|
+
return {
|
|
1079
|
+
type: "oauth",
|
|
1080
|
+
refresh: refreshToken,
|
|
1081
|
+
access: token,
|
|
1082
|
+
expires: expiresAt * 1000 - 5 * 60 * 1000,
|
|
1083
|
+
enterpriseUrl: enterpriseDomain
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain, signal) {
|
|
1087
|
+
const credentials = await refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain, signal);
|
|
1088
|
+
const { availableModelIds } = await fetchGitHubCopilotModels(credentials.access, enterpriseDomain, signal, {
|
|
1089
|
+
maxRetries: 0,
|
|
1090
|
+
maxElapsedMs: 0
|
|
1091
|
+
});
|
|
1092
|
+
return {
|
|
1093
|
+
...credentials,
|
|
1094
|
+
availableModelIds
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
async function enableGitHubCopilotModel(token, modelId, enterpriseDomain, signal) {
|
|
1098
|
+
const baseUrl = getGitHubCopilotBaseUrl(token, enterpriseDomain);
|
|
1099
|
+
const url = `${baseUrl}/models/${modelId}/policy`;
|
|
1100
|
+
let response;
|
|
1101
|
+
try {
|
|
1102
|
+
response = await fetchWithRateLimitRetry(url, {
|
|
1103
|
+
method: "POST",
|
|
1104
|
+
headers: {
|
|
1105
|
+
"Content-Type": "application/json",
|
|
1106
|
+
Authorization: `Bearer ${token}`,
|
|
1107
|
+
...COPILOT_HEADERS,
|
|
1108
|
+
"openai-intent": "chat-policy",
|
|
1109
|
+
"x-interaction-type": "chat-policy"
|
|
1110
|
+
},
|
|
1111
|
+
body: JSON.stringify({ state: "enabled" })
|
|
1112
|
+
}, signal, { maxRetries: 2, maxElapsedMs: 5000 });
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
if (signal.aborted)
|
|
1115
|
+
throw error;
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
if (response.status === 429) {
|
|
1119
|
+
throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`);
|
|
1120
|
+
}
|
|
1121
|
+
return response.ok;
|
|
1122
|
+
}
|
|
1123
|
+
async function enableGitHubCopilotModels(token, modelIds, enterpriseDomain, signal) {
|
|
1124
|
+
const enabledModelIds = [];
|
|
1125
|
+
for (const modelId of modelIds) {
|
|
1126
|
+
try {
|
|
1127
|
+
if (await enableGitHubCopilotModel(token, modelId, enterpriseDomain, signal)) {
|
|
1128
|
+
enabledModelIds.push(modelId);
|
|
1129
|
+
}
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
if (signal.aborted)
|
|
1132
|
+
throw error;
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return enabledModelIds;
|
|
1137
|
+
}
|
|
1138
|
+
async function loginGitHubCopilot(interaction) {
|
|
1139
|
+
const input = await interaction.prompt({
|
|
1140
|
+
type: "text",
|
|
1141
|
+
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
1142
|
+
placeholder: "company.ghe.com"
|
|
1143
|
+
});
|
|
1144
|
+
if (interaction.signal.aborted)
|
|
1145
|
+
throw new Error("Login cancelled");
|
|
1146
|
+
const trimmed = input.trim();
|
|
1147
|
+
const enterpriseDomain = normalizeDomain(input);
|
|
1148
|
+
if (trimmed && !enterpriseDomain)
|
|
1149
|
+
throw new Error("Invalid GitHub Enterprise URL/domain");
|
|
1150
|
+
const domain = enterpriseDomain || "github.com";
|
|
1151
|
+
const device = await startDeviceFlow(domain, interaction.signal);
|
|
1152
|
+
interaction.notify({
|
|
1153
|
+
type: "device_code",
|
|
1154
|
+
userCode: device.user_code,
|
|
1155
|
+
verificationUri: device.verification_uri,
|
|
1156
|
+
intervalSeconds: device.interval,
|
|
1157
|
+
expiresInSeconds: device.expires_in
|
|
1158
|
+
});
|
|
1159
|
+
const githubAccessToken = await pollForGitHubAccessToken(domain, device, interaction.signal);
|
|
1160
|
+
const credentials = await refreshGitHubCopilotAccessToken(githubAccessToken, enterpriseDomain ?? undefined, interaction.signal);
|
|
1161
|
+
const models = await fetchGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined, interaction.signal, {
|
|
1162
|
+
maxRetries: 2,
|
|
1163
|
+
maxElapsedMs: 5000
|
|
1164
|
+
});
|
|
1165
|
+
let enabledModelIds = [];
|
|
1166
|
+
if (models.policyModelIds.length > 0) {
|
|
1167
|
+
interaction.notify({ type: "progress", message: "Enabling models..." });
|
|
1168
|
+
enabledModelIds = await enableGitHubCopilotModels(credentials.access, models.policyModelIds, enterpriseDomain ?? undefined, interaction.signal);
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
...credentials,
|
|
1172
|
+
availableModelIds: [...new Set([...models.availableModelIds, ...enabledModelIds])]
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function copilotEnterpriseDomain(credential) {
|
|
1176
|
+
const enterpriseUrl = credential.enterpriseUrl;
|
|
1177
|
+
if (typeof enterpriseUrl !== "string" || !enterpriseUrl)
|
|
1178
|
+
return;
|
|
1179
|
+
return normalizeDomain(enterpriseUrl) ?? undefined;
|
|
1180
|
+
}
|
|
1181
|
+
var githubCopilotOAuth = {
|
|
1182
|
+
name: "GitHub Copilot",
|
|
1183
|
+
isSubscription: true,
|
|
1184
|
+
login: loginGitHubCopilot,
|
|
1185
|
+
refresh: (credential, signal) => refreshGitHubCopilotToken(credential.refresh, copilotEnterpriseDomain(credential), signal),
|
|
1186
|
+
async toAuth(credential) {
|
|
1187
|
+
return {
|
|
1188
|
+
apiKey: credential.access,
|
|
1189
|
+
baseUrl: getGitHubCopilotBaseUrl(credential.access, copilotEnterpriseDomain(credential))
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js
|
|
1195
|
+
var CLIENT_ID3 = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
1196
|
+
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
1197
|
+
var DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;
|
|
1198
|
+
var DEFAULT_POLL_INTERVAL_SECONDS2 = 5;
|
|
1199
|
+
var REQUEST_TIMEOUT_MS = 30 * 1000;
|
|
1200
|
+
var REFRESH_MAX_RETRIES = 3;
|
|
1201
|
+
function getOauthHost() {
|
|
1202
|
+
const override = getProviderEnvValue("KIMI_CODE_OAUTH_HOST") || getProviderEnvValue("KIMI_OAUTH_HOST");
|
|
1203
|
+
return (override || DEFAULT_OAUTH_HOST).replace(/\/+$/, "");
|
|
1204
|
+
}
|
|
1205
|
+
function requestSignal(signal) {
|
|
1206
|
+
return AbortSignal.any([AbortSignal.timeout(REQUEST_TIMEOUT_MS), signal]);
|
|
1207
|
+
}
|
|
1208
|
+
function formUrlEncode(fields) {
|
|
1209
|
+
return new URLSearchParams(fields).toString();
|
|
1210
|
+
}
|
|
1211
|
+
async function readJson(response) {
|
|
1212
|
+
try {
|
|
1213
|
+
const json = await response.json();
|
|
1214
|
+
return json && typeof json === "object" ? json : null;
|
|
1215
|
+
} catch {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function trustedHttpUrl(value) {
|
|
1220
|
+
if (typeof value !== "string" || !value)
|
|
1221
|
+
return null;
|
|
1222
|
+
try {
|
|
1223
|
+
const url = new URL(value);
|
|
1224
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1225
|
+
return null;
|
|
1226
|
+
return url.href;
|
|
1227
|
+
} catch {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async function startDeviceAuthorization(oauthHost, signal) {
|
|
1232
|
+
const response = await fetch(`${oauthHost}/api/oauth/device_authorization`, {
|
|
1233
|
+
method: "POST",
|
|
1234
|
+
headers: {
|
|
1235
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1236
|
+
Accept: "application/json"
|
|
1237
|
+
},
|
|
1238
|
+
body: formUrlEncode({ client_id: CLIENT_ID3 }),
|
|
1239
|
+
signal: requestSignal(signal)
|
|
1240
|
+
});
|
|
1241
|
+
if (!response.ok) {
|
|
1242
|
+
const text = await response.text().catch(() => "");
|
|
1243
|
+
throw new Error(`Kimi Code device authorization failed with status ${response.status}${text ? `: ${text}` : ""}`);
|
|
1244
|
+
}
|
|
1245
|
+
const json = await readJson(response);
|
|
1246
|
+
const deviceCode = json?.device_code;
|
|
1247
|
+
const userCode = json?.user_code;
|
|
1248
|
+
const verificationUri = json?.verification_uri;
|
|
1249
|
+
const verificationUriComplete = json?.verification_uri_complete;
|
|
1250
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string" || typeof verificationUriComplete !== "string" || !trustedHttpUrl(verificationUriComplete) || !trustedHttpUrl(verificationUri)) {
|
|
1251
|
+
throw new Error(`Invalid Kimi Code device authorization response: ${JSON.stringify(json)}`);
|
|
1252
|
+
}
|
|
1253
|
+
const interval = json?.interval;
|
|
1254
|
+
const expiresIn = json?.expires_in;
|
|
1255
|
+
return {
|
|
1256
|
+
deviceCode,
|
|
1257
|
+
userCode,
|
|
1258
|
+
verificationUri,
|
|
1259
|
+
verificationUriComplete,
|
|
1260
|
+
intervalSeconds: typeof interval === "number" && Number.isFinite(interval) && interval > 0 ? interval : DEFAULT_POLL_INTERVAL_SECONDS2,
|
|
1261
|
+
expiresInSeconds: typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : DEVICE_CODE_TIMEOUT_SECONDS
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function parseTokenResponse(json, operation) {
|
|
1265
|
+
const accessToken = json?.access_token;
|
|
1266
|
+
const refreshToken = json?.refresh_token;
|
|
1267
|
+
const expiresIn = json?.expires_in;
|
|
1268
|
+
if (typeof accessToken !== "string" || !accessToken || typeof refreshToken !== "string" || !refreshToken || typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
1269
|
+
throw new Error(`Kimi Code token ${operation} response missing fields: ${JSON.stringify(json)}`);
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
access: accessToken,
|
|
1273
|
+
refresh: refreshToken,
|
|
1274
|
+
expires: Date.now() + expiresIn * 1000
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
async function pollForToken(oauthHost, device, signal) {
|
|
1278
|
+
return pollOAuthDeviceCodeFlow({
|
|
1279
|
+
intervalSeconds: device.intervalSeconds,
|
|
1280
|
+
expiresInSeconds: device.expiresInSeconds,
|
|
1281
|
+
waitBeforeFirstPoll: true,
|
|
1282
|
+
signal,
|
|
1283
|
+
poll: async () => {
|
|
1284
|
+
const response = await fetch(`${oauthHost}/api/oauth/token`, {
|
|
1285
|
+
method: "POST",
|
|
1286
|
+
headers: {
|
|
1287
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1288
|
+
Accept: "application/json"
|
|
1289
|
+
},
|
|
1290
|
+
body: formUrlEncode({
|
|
1291
|
+
client_id: CLIENT_ID3,
|
|
1292
|
+
device_code: device.deviceCode,
|
|
1293
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1294
|
+
}),
|
|
1295
|
+
signal: requestSignal(signal)
|
|
1296
|
+
});
|
|
1297
|
+
if (response.status >= 500) {
|
|
1298
|
+
const text = await response.text().catch(() => "");
|
|
1299
|
+
return {
|
|
1300
|
+
status: "failed",
|
|
1301
|
+
message: `Kimi Code device token request failed with status ${response.status}${text ? `: ${text}` : ""}`
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
const json = await readJson(response);
|
|
1305
|
+
if (response.ok && typeof json?.access_token === "string") {
|
|
1306
|
+
try {
|
|
1307
|
+
return { status: "complete", value: parseTokenResponse(json, "poll") };
|
|
1308
|
+
} catch (error2) {
|
|
1309
|
+
return { status: "failed", message: error2 instanceof Error ? error2.message : String(error2) };
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const error = json?.error;
|
|
1313
|
+
const description = typeof json?.error_description === "string" ? `: ${json.error_description}` : "";
|
|
1314
|
+
if (error === "authorization_pending") {
|
|
1315
|
+
return { status: "pending" };
|
|
1316
|
+
}
|
|
1317
|
+
if (error === "slow_down") {
|
|
1318
|
+
const interval = json?.interval;
|
|
1319
|
+
return {
|
|
1320
|
+
status: "slow_down",
|
|
1321
|
+
intervalSeconds: typeof interval === "number" && interval > 0 ? interval : undefined
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
if (error === "expired_token") {
|
|
1325
|
+
return { status: "failed", message: "Kimi Code device authorization expired. Please restart login." };
|
|
1326
|
+
}
|
|
1327
|
+
if (error === "access_denied") {
|
|
1328
|
+
return { status: "failed", message: "Kimi Code login was denied." };
|
|
1329
|
+
}
|
|
1330
|
+
return {
|
|
1331
|
+
status: "failed",
|
|
1332
|
+
message: `Kimi Code device token request failed (status ${response.status})${typeof error === "string" ? `: ${error}${description}` : ""}`
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
function isRetryableRefreshFailure(response) {
|
|
1338
|
+
return response.status === 429 || response.status >= 500;
|
|
1339
|
+
}
|
|
1340
|
+
async function refreshToken(oauthHost, refreshTokenValue, signal) {
|
|
1341
|
+
let lastError;
|
|
1342
|
+
for (let attempt = 0;attempt <= REFRESH_MAX_RETRIES; attempt++) {
|
|
1343
|
+
if (attempt > 0) {
|
|
1344
|
+
await sleep(1000 * 2 ** (attempt - 1), signal);
|
|
1345
|
+
}
|
|
1346
|
+
if (signal.aborted) {
|
|
1347
|
+
throw new Error("Kimi Code token refresh aborted");
|
|
1348
|
+
}
|
|
1349
|
+
let response;
|
|
1350
|
+
try {
|
|
1351
|
+
response = await fetch(`${oauthHost}/api/oauth/token`, {
|
|
1352
|
+
method: "POST",
|
|
1353
|
+
headers: {
|
|
1354
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1355
|
+
Accept: "application/json"
|
|
1356
|
+
},
|
|
1357
|
+
body: formUrlEncode({
|
|
1358
|
+
client_id: CLIENT_ID3,
|
|
1359
|
+
grant_type: "refresh_token",
|
|
1360
|
+
refresh_token: refreshTokenValue
|
|
1361
|
+
}),
|
|
1362
|
+
signal: requestSignal(signal)
|
|
1363
|
+
});
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
const json = await readJson(response);
|
|
1369
|
+
if (response.ok) {
|
|
1370
|
+
return parseTokenResponse(json, "refresh");
|
|
1371
|
+
}
|
|
1372
|
+
if (response.status === 401 || response.status === 403 || json?.error === "invalid_grant") {
|
|
1373
|
+
const description = typeof json?.error_description === "string" ? `: ${json.error_description}` : "";
|
|
1374
|
+
throw new Error(`Kimi Code token refresh unauthorized (status ${response.status})${description}`);
|
|
1375
|
+
}
|
|
1376
|
+
if (isRetryableRefreshFailure(response) && attempt < REFRESH_MAX_RETRIES) {
|
|
1377
|
+
lastError = new Error(`Kimi Code token refresh failed with status ${response.status}`);
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
const text = JSON.stringify(json);
|
|
1381
|
+
throw new Error(`Kimi Code token refresh failed with status ${response.status}${text ? `: ${text}` : ""}`);
|
|
1382
|
+
}
|
|
1383
|
+
throw lastError ?? new Error("Kimi Code token refresh failed");
|
|
1384
|
+
}
|
|
1385
|
+
async function loginKimiCoding(interaction) {
|
|
1386
|
+
const oauthHost = getOauthHost();
|
|
1387
|
+
const device = await startDeviceAuthorization(oauthHost, interaction.signal);
|
|
1388
|
+
interaction.notify({
|
|
1389
|
+
type: "device_code",
|
|
1390
|
+
userCode: device.userCode,
|
|
1391
|
+
verificationUri: device.verificationUriComplete,
|
|
1392
|
+
intervalSeconds: device.intervalSeconds,
|
|
1393
|
+
expiresInSeconds: device.expiresInSeconds
|
|
1394
|
+
});
|
|
1395
|
+
const token = await pollForToken(oauthHost, device, interaction.signal);
|
|
1396
|
+
return { type: "oauth", access: token.access, refresh: token.refresh, expires: token.expires };
|
|
1397
|
+
}
|
|
1398
|
+
var kimiCodingOAuth = {
|
|
1399
|
+
name: "Kimi Code (subscription)",
|
|
1400
|
+
isSubscription: true,
|
|
1401
|
+
loginLabel: "Sign in with Kimi Code",
|
|
1402
|
+
login: loginKimiCoding,
|
|
1403
|
+
refresh: async (credential, signal) => {
|
|
1404
|
+
const token = await refreshToken(getOauthHost(), credential.refresh, signal);
|
|
1405
|
+
return { type: "oauth", access: token.access, refresh: token.refresh, expires: token.expires };
|
|
1406
|
+
},
|
|
1407
|
+
async toAuth(credential) {
|
|
1408
|
+
return { headers: { Authorization: `Bearer ${credential.access}` } };
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/meta.js
|
|
1413
|
+
var CLIENT_ID4 = "1031625952748946";
|
|
1414
|
+
var AUTH_HOST = "https://auth.meta.com";
|
|
1415
|
+
var DEVICE_AUTHORIZATION_URL = `${AUTH_HOST}/oidc/device/authorization/`;
|
|
1416
|
+
var DEVICE_TOKEN_URL = `${AUTH_HOST}/oidc/device/token/`;
|
|
1417
|
+
var API_KEY_MINT_URL = "https://api.meta.ai/muse-code/key";
|
|
1418
|
+
var API_KEY_LIFETIME_MS = 24 * 60 * 60 * 1000;
|
|
1419
|
+
var REQUEST_TIMEOUT_MS2 = 30 * 1000;
|
|
1420
|
+
function requestSignal2(signal) {
|
|
1421
|
+
return AbortSignal.any([AbortSignal.timeout(REQUEST_TIMEOUT_MS2), signal]);
|
|
1422
|
+
}
|
|
1423
|
+
async function readJson2(response) {
|
|
1424
|
+
try {
|
|
1425
|
+
const json = await response.json();
|
|
1426
|
+
return json && typeof json === "object" ? json : null;
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function errorDetail(json) {
|
|
1432
|
+
for (const key of ["error_description", "detail", "message", "error"]) {
|
|
1433
|
+
const value = json?.[key];
|
|
1434
|
+
if (typeof value === "string" && value.trim())
|
|
1435
|
+
return `: ${value.trim()}`;
|
|
1436
|
+
}
|
|
1437
|
+
return "";
|
|
1438
|
+
}
|
|
1439
|
+
function trustedHttpUrl2(value) {
|
|
1440
|
+
if (typeof value !== "string" || !value)
|
|
1441
|
+
return null;
|
|
1442
|
+
try {
|
|
1443
|
+
const url = new URL(value);
|
|
1444
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1445
|
+
return null;
|
|
1446
|
+
return url.href;
|
|
1447
|
+
} catch {
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
function positiveNumber(value) {
|
|
1452
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
1453
|
+
}
|
|
1454
|
+
async function startDeviceAuthorization2(signal) {
|
|
1455
|
+
const response = await fetch(DEVICE_AUTHORIZATION_URL, {
|
|
1456
|
+
method: "POST",
|
|
1457
|
+
headers: {
|
|
1458
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1459
|
+
Accept: "application/json"
|
|
1460
|
+
},
|
|
1461
|
+
body: new URLSearchParams({ client_id: CLIENT_ID4 }).toString(),
|
|
1462
|
+
signal: requestSignal2(signal)
|
|
1463
|
+
});
|
|
1464
|
+
const json = await readJson2(response);
|
|
1465
|
+
if (!response.ok) {
|
|
1466
|
+
throw new Error(`Meta device authorization failed with status ${response.status}${errorDetail(json)}`);
|
|
1467
|
+
}
|
|
1468
|
+
const deviceCode = json?.device_code;
|
|
1469
|
+
const userCode = json?.user_code;
|
|
1470
|
+
const verificationUri = trustedHttpUrl2(json?.verification_uri_complete) ?? trustedHttpUrl2(json?.verification_uri);
|
|
1471
|
+
if (typeof deviceCode !== "string" || !deviceCode || typeof userCode !== "string" || !userCode || !verificationUri) {
|
|
1472
|
+
throw new Error(`Invalid Meta device authorization response: ${JSON.stringify(json)}`);
|
|
1473
|
+
}
|
|
1474
|
+
return {
|
|
1475
|
+
deviceCode,
|
|
1476
|
+
userCode,
|
|
1477
|
+
verificationUri,
|
|
1478
|
+
intervalSeconds: positiveNumber(json?.interval),
|
|
1479
|
+
expiresInSeconds: positiveNumber(json?.expires_in)
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
async function pollForIdentityToken(device, signal) {
|
|
1483
|
+
return pollOAuthDeviceCodeFlow({
|
|
1484
|
+
intervalSeconds: device.intervalSeconds,
|
|
1485
|
+
expiresInSeconds: device.expiresInSeconds,
|
|
1486
|
+
waitBeforeFirstPoll: true,
|
|
1487
|
+
signal,
|
|
1488
|
+
poll: async () => {
|
|
1489
|
+
const response = await fetch(DEVICE_TOKEN_URL, {
|
|
1490
|
+
method: "POST",
|
|
1491
|
+
headers: {
|
|
1492
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1493
|
+
Accept: "application/json"
|
|
1494
|
+
},
|
|
1495
|
+
body: new URLSearchParams({
|
|
1496
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1497
|
+
device_code: device.deviceCode,
|
|
1498
|
+
client_id: CLIENT_ID4
|
|
1499
|
+
}).toString(),
|
|
1500
|
+
signal: requestSignal2(signal)
|
|
1501
|
+
});
|
|
1502
|
+
const json = await readJson2(response);
|
|
1503
|
+
if (response.ok && typeof json?.access_token === "string" && json.access_token) {
|
|
1504
|
+
return { status: "complete", value: json.access_token };
|
|
1505
|
+
}
|
|
1506
|
+
switch (json?.error) {
|
|
1507
|
+
case "authorization_pending":
|
|
1508
|
+
return { status: "pending" };
|
|
1509
|
+
case "slow_down":
|
|
1510
|
+
return { status: "slow_down", intervalSeconds: positiveNumber(json?.interval) };
|
|
1511
|
+
case "access_denied":
|
|
1512
|
+
return { status: "failed", message: "Meta login was denied." };
|
|
1513
|
+
case "expired_token":
|
|
1514
|
+
return { status: "failed", message: "Meta device authorization expired. Please restart login." };
|
|
1515
|
+
default:
|
|
1516
|
+
return {
|
|
1517
|
+
status: "failed",
|
|
1518
|
+
message: `Meta device token request failed with status ${response.status}${errorDetail(json)}`
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
async function mintApiKey(identityToken, signal) {
|
|
1525
|
+
const response = await fetch(API_KEY_MINT_URL, {
|
|
1526
|
+
method: "POST",
|
|
1527
|
+
headers: {
|
|
1528
|
+
Accept: "application/json",
|
|
1529
|
+
Authorization: `Bearer ${identityToken}`,
|
|
1530
|
+
"Content-Type": "application/json",
|
|
1531
|
+
"x-api-version": "1.0.0"
|
|
1532
|
+
},
|
|
1533
|
+
body: "{}",
|
|
1534
|
+
signal: requestSignal2(signal)
|
|
1535
|
+
});
|
|
1536
|
+
const json = await readJson2(response);
|
|
1537
|
+
if (response.status === 401 || response.status === 403) {
|
|
1538
|
+
throw new Error(`Meta session expired (status ${response.status}). Run \`/login meta\` to sign in again.${errorDetail(json)}`);
|
|
1539
|
+
}
|
|
1540
|
+
if (!response.ok) {
|
|
1541
|
+
throw new Error(`Meta API key mint failed with status ${response.status}${errorDetail(json)}`);
|
|
1542
|
+
}
|
|
1543
|
+
const apiKey = json?.api_key;
|
|
1544
|
+
if (typeof apiKey !== "string" || !apiKey) {
|
|
1545
|
+
const actionUrl = trustedHttpUrl2(json?.action_url);
|
|
1546
|
+
throw new Error(`Meta did not issue an API key.${actionUrl ? ` Complete setup at ${actionUrl}` : ""}`);
|
|
1547
|
+
}
|
|
1548
|
+
return { type: "oauth", refresh: identityToken, access: apiKey, expires: Date.now() + API_KEY_LIFETIME_MS };
|
|
1549
|
+
}
|
|
1550
|
+
async function loginMeta(interaction) {
|
|
1551
|
+
try {
|
|
1552
|
+
const device = await startDeviceAuthorization2(interaction.signal);
|
|
1553
|
+
interaction.notify({
|
|
1554
|
+
type: "device_code",
|
|
1555
|
+
userCode: device.userCode,
|
|
1556
|
+
verificationUri: device.verificationUri,
|
|
1557
|
+
intervalSeconds: device.intervalSeconds,
|
|
1558
|
+
expiresInSeconds: device.expiresInSeconds
|
|
1559
|
+
});
|
|
1560
|
+
const identityToken = await pollForIdentityToken(device, interaction.signal);
|
|
1561
|
+
interaction.notify({ type: "progress", message: "Enabling Meta Model API access..." });
|
|
1562
|
+
return await mintApiKey(identityToken, interaction.signal);
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
if (interaction.signal.aborted)
|
|
1565
|
+
throw new Error("Login cancelled");
|
|
1566
|
+
throw e;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
var metaOAuth = {
|
|
1570
|
+
name: "Meta (Muse subscription)",
|
|
1571
|
+
isSubscription: true,
|
|
1572
|
+
loginLabel: "Sign in with Meta",
|
|
1573
|
+
login: loginMeta,
|
|
1574
|
+
refresh: (credential, signal) => mintApiKey(credential.refresh, signal),
|
|
1575
|
+
async toAuth(credential) {
|
|
1576
|
+
return { apiKey: credential.access };
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js
|
|
1581
|
+
var _randomBytes = null;
|
|
1582
|
+
var _http = null;
|
|
1583
|
+
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
1584
|
+
import("node:crypto").then((m) => {
|
|
1585
|
+
_randomBytes = m.randomBytes;
|
|
1586
|
+
});
|
|
1587
|
+
import("node:http").then((m) => {
|
|
1588
|
+
_http = m;
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
var CLIENT_ID5 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1592
|
+
var AUTH_BASE_URL = "https://auth.openai.com";
|
|
1593
|
+
var AUTHORIZE_URL2 = `${AUTH_BASE_URL}/oauth/authorize`;
|
|
1594
|
+
var TOKEN_URL2 = `${AUTH_BASE_URL}/oauth/token`;
|
|
1595
|
+
var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
|
|
1596
|
+
var DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;
|
|
1597
|
+
var DEVICE_TOKEN_URL2 = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;
|
|
1598
|
+
var DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;
|
|
1599
|
+
var DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;
|
|
1600
|
+
var DEVICE_CODE_TIMEOUT_SECONDS2 = 900;
|
|
1601
|
+
var OPENAI_CODEX_BROWSER_LOGIN_METHOD = "browser";
|
|
1602
|
+
var OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = "device_code";
|
|
1603
|
+
var SCOPE = "openid profile email offline_access";
|
|
1604
|
+
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
1605
|
+
function getCallbackHost() {
|
|
1606
|
+
return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
1607
|
+
}
|
|
1608
|
+
function createState() {
|
|
1609
|
+
if (!_randomBytes) {
|
|
1610
|
+
throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
|
|
1611
|
+
}
|
|
1612
|
+
return _randomBytes(16).toString("hex");
|
|
1613
|
+
}
|
|
1614
|
+
function parseAuthorizationInput2(input) {
|
|
1615
|
+
const value = input.trim();
|
|
1616
|
+
if (!value)
|
|
1617
|
+
return {};
|
|
1618
|
+
try {
|
|
1619
|
+
const url = new URL(value);
|
|
1620
|
+
return {
|
|
1621
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
1622
|
+
state: url.searchParams.get("state") ?? undefined
|
|
1623
|
+
};
|
|
1624
|
+
} catch {}
|
|
1625
|
+
if (value.includes("#")) {
|
|
1626
|
+
const [code, state] = value.split("#", 2);
|
|
1627
|
+
return { code, state };
|
|
1628
|
+
}
|
|
1629
|
+
if (value.includes("code=")) {
|
|
1630
|
+
const params = new URLSearchParams(value);
|
|
1631
|
+
return {
|
|
1632
|
+
code: params.get("code") ?? undefined,
|
|
1633
|
+
state: params.get("state") ?? undefined
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
return { code: value };
|
|
1637
|
+
}
|
|
1638
|
+
function decodeJwt(token) {
|
|
1639
|
+
try {
|
|
1640
|
+
const parts = token.split(".");
|
|
1641
|
+
if (parts.length !== 3)
|
|
1642
|
+
return null;
|
|
1643
|
+
const payload = parts[1] ?? "";
|
|
1644
|
+
const decoded = atob(payload);
|
|
1645
|
+
return JSON.parse(decoded);
|
|
1646
|
+
} catch {
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
async function fetchWithLoginCancellation(input, init) {
|
|
1651
|
+
try {
|
|
1652
|
+
return await fetch(input, init);
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
if (init.signal?.aborted) {
|
|
1655
|
+
throw new Error("Login cancelled");
|
|
1656
|
+
}
|
|
1657
|
+
throw error;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
async function readTokenResponse(response, operation) {
|
|
1661
|
+
if (!response.ok) {
|
|
1662
|
+
const text = await response.text().catch(() => "");
|
|
1663
|
+
throw new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text || response.statusText}`);
|
|
1664
|
+
}
|
|
1665
|
+
const rawJson = await response.json();
|
|
1666
|
+
const json = rawJson;
|
|
1667
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
1668
|
+
throw new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
access: json.access_token,
|
|
1672
|
+
refresh: json.refresh_token,
|
|
1673
|
+
expires: Date.now() + json.expires_in * 1000
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
async function exchangeAuthorizationCode2(code, verifier, redirectUri, signal) {
|
|
1677
|
+
const response = await fetchWithLoginCancellation(TOKEN_URL2, {
|
|
1678
|
+
method: "POST",
|
|
1679
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1680
|
+
body: new URLSearchParams({
|
|
1681
|
+
grant_type: "authorization_code",
|
|
1682
|
+
client_id: CLIENT_ID5,
|
|
1683
|
+
code,
|
|
1684
|
+
code_verifier: verifier,
|
|
1685
|
+
redirect_uri: redirectUri
|
|
1686
|
+
}),
|
|
1687
|
+
signal
|
|
1688
|
+
});
|
|
1689
|
+
return readTokenResponse(response, "exchange");
|
|
1690
|
+
}
|
|
1691
|
+
async function refreshAccessToken(refreshToken2, signal) {
|
|
1692
|
+
let response;
|
|
1693
|
+
try {
|
|
1694
|
+
response = await fetch(TOKEN_URL2, {
|
|
1695
|
+
method: "POST",
|
|
1696
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1697
|
+
body: new URLSearchParams({
|
|
1698
|
+
grant_type: "refresh_token",
|
|
1699
|
+
refresh_token: refreshToken2,
|
|
1700
|
+
client_id: CLIENT_ID5
|
|
1701
|
+
}),
|
|
1702
|
+
signal
|
|
1703
|
+
});
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
throw new Error(`OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1706
|
+
}
|
|
1707
|
+
return readTokenResponse(response, "refresh");
|
|
1708
|
+
}
|
|
1709
|
+
async function startOpenAICodexDeviceAuth(signal) {
|
|
1710
|
+
const response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, {
|
|
1711
|
+
method: "POST",
|
|
1712
|
+
headers: { "Content-Type": "application/json" },
|
|
1713
|
+
body: JSON.stringify({ client_id: CLIENT_ID5 }),
|
|
1714
|
+
signal
|
|
1715
|
+
});
|
|
1716
|
+
if (!response.ok) {
|
|
1717
|
+
if (response.status === 404) {
|
|
1718
|
+
throw new Error("OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.");
|
|
1719
|
+
}
|
|
1720
|
+
const responseBody = await response.text().catch(() => "");
|
|
1721
|
+
throw new Error(`OpenAI Codex device code request failed with status ${response.status}${responseBody ? `: ${responseBody}` : ""}`);
|
|
1722
|
+
}
|
|
1723
|
+
const rawJson = await response.json();
|
|
1724
|
+
const json = rawJson;
|
|
1725
|
+
const intervalSeconds = typeof json?.interval === "string" ? Number(json.interval.trim()) : json?.interval;
|
|
1726
|
+
if (!json?.device_auth_id || !json.user_code || typeof intervalSeconds !== "number" || !Number.isFinite(intervalSeconds) || intervalSeconds < 0) {
|
|
1727
|
+
throw new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);
|
|
1728
|
+
}
|
|
1729
|
+
return {
|
|
1730
|
+
deviceAuthId: json.device_auth_id,
|
|
1731
|
+
userCode: json.user_code,
|
|
1732
|
+
intervalSeconds
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
async function pollOpenAICodexDeviceAuth(device, signal) {
|
|
1736
|
+
return pollOAuthDeviceCodeFlow({
|
|
1737
|
+
intervalSeconds: device.intervalSeconds,
|
|
1738
|
+
expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS2,
|
|
1739
|
+
signal,
|
|
1740
|
+
poll: async () => {
|
|
1741
|
+
const response = await fetchWithLoginCancellation(DEVICE_TOKEN_URL2, {
|
|
1742
|
+
method: "POST",
|
|
1743
|
+
headers: { "Content-Type": "application/json" },
|
|
1744
|
+
body: JSON.stringify({
|
|
1745
|
+
device_auth_id: device.deviceAuthId,
|
|
1746
|
+
user_code: device.userCode
|
|
1747
|
+
}),
|
|
1748
|
+
signal
|
|
1749
|
+
});
|
|
1750
|
+
if (response.ok) {
|
|
1751
|
+
const rawJson = await response.json();
|
|
1752
|
+
const json = rawJson;
|
|
1753
|
+
if (!json?.authorization_code || !json.code_verifier) {
|
|
1754
|
+
return {
|
|
1755
|
+
status: "failed",
|
|
1756
|
+
message: `Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
return {
|
|
1760
|
+
status: "complete",
|
|
1761
|
+
value: { authorizationCode: json.authorization_code, codeVerifier: json.code_verifier }
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
if (response.status === 403 || response.status === 404) {
|
|
1765
|
+
return { status: "pending" };
|
|
1766
|
+
}
|
|
1767
|
+
const responseBody = await response.text().catch(() => "");
|
|
1768
|
+
let errorCode;
|
|
1769
|
+
try {
|
|
1770
|
+
const json = JSON.parse(responseBody);
|
|
1771
|
+
const error = json?.error;
|
|
1772
|
+
errorCode = typeof error === "object" ? error?.code : error;
|
|
1773
|
+
} catch {}
|
|
1774
|
+
if (errorCode === "deviceauth_authorization_pending") {
|
|
1775
|
+
return { status: "pending" };
|
|
1776
|
+
}
|
|
1777
|
+
if (errorCode === "slow_down") {
|
|
1778
|
+
return { status: "slow_down" };
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
status: "failed",
|
|
1782
|
+
message: `OpenAI Codex device auth failed with status ${response.status}${responseBody ? `: ${responseBody}` : ""}`
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
async function createAuthorizationFlow(originator = "pi") {
|
|
1788
|
+
const { verifier, challenge } = await generatePKCE();
|
|
1789
|
+
const state = createState();
|
|
1790
|
+
const url = new URL(AUTHORIZE_URL2);
|
|
1791
|
+
url.searchParams.set("response_type", "code");
|
|
1792
|
+
url.searchParams.set("client_id", CLIENT_ID5);
|
|
1793
|
+
url.searchParams.set("redirect_uri", REDIRECT_URI2);
|
|
1794
|
+
url.searchParams.set("scope", SCOPE);
|
|
1795
|
+
url.searchParams.set("code_challenge", challenge);
|
|
1796
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
1797
|
+
url.searchParams.set("state", state);
|
|
1798
|
+
url.searchParams.set("id_token_add_organizations", "true");
|
|
1799
|
+
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
1800
|
+
url.searchParams.set("originator", originator);
|
|
1801
|
+
return { verifier, state, url: url.toString() };
|
|
1802
|
+
}
|
|
1803
|
+
function startLocalOAuthServer(state) {
|
|
1804
|
+
if (!_http) {
|
|
1805
|
+
throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
|
|
1806
|
+
}
|
|
1807
|
+
let settleWait;
|
|
1808
|
+
const waitForCodePromise = new Promise((resolve) => {
|
|
1809
|
+
let settled = false;
|
|
1810
|
+
settleWait = (value) => {
|
|
1811
|
+
if (settled)
|
|
1812
|
+
return;
|
|
1813
|
+
settled = true;
|
|
1814
|
+
resolve(value);
|
|
1815
|
+
};
|
|
1816
|
+
});
|
|
1817
|
+
const server = _http.createServer((req, res) => {
|
|
1818
|
+
try {
|
|
1819
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
1820
|
+
if (url.pathname !== "/auth/callback") {
|
|
1821
|
+
res.statusCode = 404;
|
|
1822
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1823
|
+
res.end(oauthErrorHtml("Callback route not found."));
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (url.searchParams.get("state") !== state) {
|
|
1827
|
+
res.statusCode = 400;
|
|
1828
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1829
|
+
res.end(oauthErrorHtml("State mismatch."));
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
const code = url.searchParams.get("code");
|
|
1833
|
+
if (!code) {
|
|
1834
|
+
res.statusCode = 400;
|
|
1835
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1836
|
+
res.end(oauthErrorHtml("Missing authorization code."));
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
res.statusCode = 200;
|
|
1840
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1841
|
+
res.end(oauthSuccessHtml("OpenAI authentication completed. You can close this window."));
|
|
1842
|
+
settleWait?.({ code });
|
|
1843
|
+
} catch {
|
|
1844
|
+
res.statusCode = 500;
|
|
1845
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1846
|
+
res.end(oauthErrorHtml("Internal error while processing OAuth callback."));
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
return new Promise((resolve) => {
|
|
1850
|
+
server.listen(1455, getCallbackHost(), () => {
|
|
1851
|
+
resolve({
|
|
1852
|
+
close: () => server.close(),
|
|
1853
|
+
cancelWait: () => {
|
|
1854
|
+
settleWait?.(null);
|
|
1855
|
+
},
|
|
1856
|
+
waitForCode: () => waitForCodePromise
|
|
1857
|
+
});
|
|
1858
|
+
}).on("error", (_err) => {
|
|
1859
|
+
settleWait?.(null);
|
|
1860
|
+
resolve({
|
|
1861
|
+
close: () => {
|
|
1862
|
+
try {
|
|
1863
|
+
server.close();
|
|
1864
|
+
} catch {}
|
|
1865
|
+
},
|
|
1866
|
+
cancelWait: () => {},
|
|
1867
|
+
waitForCode: async () => null
|
|
1868
|
+
});
|
|
1869
|
+
});
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
function getAccountId(accessToken) {
|
|
1873
|
+
const payload = decodeJwt(accessToken);
|
|
1874
|
+
const auth = payload?.[JWT_CLAIM_PATH];
|
|
1875
|
+
const accountId = auth?.chatgpt_account_id;
|
|
1876
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
1877
|
+
}
|
|
1878
|
+
function credentialsFromToken(token) {
|
|
1879
|
+
const accountId = getAccountId(token.access);
|
|
1880
|
+
if (!accountId) {
|
|
1881
|
+
throw new Error("Failed to extract accountId from token");
|
|
1882
|
+
}
|
|
1883
|
+
return {
|
|
1884
|
+
type: "oauth",
|
|
1885
|
+
access: token.access,
|
|
1886
|
+
refresh: token.refresh,
|
|
1887
|
+
expires: token.expires,
|
|
1888
|
+
accountId
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
async function exchangeAuthorizationCodeForCredentials(code, verifier, redirectUri, signal) {
|
|
1892
|
+
return credentialsFromToken(await exchangeAuthorizationCode2(code, verifier, redirectUri, signal));
|
|
1893
|
+
}
|
|
1894
|
+
async function loginOpenAICodexDeviceCode(interaction) {
|
|
1895
|
+
const device = await startOpenAICodexDeviceAuth(interaction.signal);
|
|
1896
|
+
interaction.notify({
|
|
1897
|
+
type: "device_code",
|
|
1898
|
+
userCode: device.userCode,
|
|
1899
|
+
verificationUri: DEVICE_VERIFICATION_URI,
|
|
1900
|
+
intervalSeconds: device.intervalSeconds,
|
|
1901
|
+
expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS2
|
|
1902
|
+
});
|
|
1903
|
+
const code = await pollOpenAICodexDeviceAuth(device, interaction.signal);
|
|
1904
|
+
return exchangeAuthorizationCodeForCredentials(code.authorizationCode, code.codeVerifier, DEVICE_REDIRECT_URI, interaction.signal);
|
|
1905
|
+
}
|
|
1906
|
+
async function loginOpenAICodex(interaction) {
|
|
1907
|
+
const { verifier, state, url } = await createAuthorizationFlow();
|
|
1908
|
+
const server = await startLocalOAuthServer(state);
|
|
1909
|
+
const manualAbort = new AbortController;
|
|
1910
|
+
const onAbort = () => server.cancelWait();
|
|
1911
|
+
interaction.signal.addEventListener("abort", onAbort, { once: true });
|
|
1912
|
+
if (interaction.signal.aborted)
|
|
1913
|
+
onAbort();
|
|
1914
|
+
let code;
|
|
1915
|
+
let manualCode;
|
|
1916
|
+
let manualError;
|
|
1917
|
+
interaction.notify({
|
|
1918
|
+
type: "auth_url",
|
|
1919
|
+
url,
|
|
1920
|
+
instructions: "A browser window should open. Complete login to finish."
|
|
1921
|
+
});
|
|
1922
|
+
try {
|
|
1923
|
+
const manualPromise = interaction.prompt({
|
|
1924
|
+
type: "manual_code",
|
|
1925
|
+
message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
|
|
1926
|
+
placeholder: REDIRECT_URI2,
|
|
1927
|
+
signal: manualAbort.signal
|
|
1928
|
+
}).then((input) => {
|
|
1929
|
+
manualCode = input;
|
|
1930
|
+
server.cancelWait();
|
|
1931
|
+
}).catch((error) => {
|
|
1932
|
+
manualError = error instanceof Error ? error : new Error(String(error));
|
|
1933
|
+
server.cancelWait();
|
|
1934
|
+
});
|
|
1935
|
+
const result = await server.waitForCode();
|
|
1936
|
+
if (manualError)
|
|
1937
|
+
throw manualError;
|
|
1938
|
+
if (result?.code) {
|
|
1939
|
+
code = result.code;
|
|
1940
|
+
} else if (manualCode) {
|
|
1941
|
+
const parsed = parseAuthorizationInput2(manualCode);
|
|
1942
|
+
if (parsed.state && parsed.state !== state)
|
|
1943
|
+
throw new Error("State mismatch");
|
|
1944
|
+
code = parsed.code;
|
|
1945
|
+
}
|
|
1946
|
+
if (!code) {
|
|
1947
|
+
await manualPromise;
|
|
1948
|
+
if (manualError)
|
|
1949
|
+
throw manualError;
|
|
1950
|
+
if (manualCode) {
|
|
1951
|
+
const parsed = parseAuthorizationInput2(manualCode);
|
|
1952
|
+
if (parsed.state && parsed.state !== state)
|
|
1953
|
+
throw new Error("State mismatch");
|
|
1954
|
+
code = parsed.code;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
if (!code)
|
|
1958
|
+
throw new Error("Missing authorization code");
|
|
1959
|
+
return exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI2, interaction.signal);
|
|
1960
|
+
} finally {
|
|
1961
|
+
interaction.signal.removeEventListener("abort", onAbort);
|
|
1962
|
+
manualAbort.abort();
|
|
1963
|
+
server.close();
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
async function refreshOpenAICodexToken(refreshToken2, signal) {
|
|
1967
|
+
return credentialsFromToken(await refreshAccessToken(refreshToken2, signal));
|
|
1968
|
+
}
|
|
1969
|
+
var openaiCodexOAuth = {
|
|
1970
|
+
name: "OpenAI (ChatGPT Plus/Pro)",
|
|
1971
|
+
isSubscription: true,
|
|
1972
|
+
async login(interaction) {
|
|
1973
|
+
const method = await interaction.prompt({
|
|
1974
|
+
type: "select",
|
|
1975
|
+
message: "Select OpenAI Codex login method:",
|
|
1976
|
+
options: [
|
|
1977
|
+
{ id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: "Browser login (default)" },
|
|
1978
|
+
{ id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: "Device code login (headless)" }
|
|
1979
|
+
]
|
|
1980
|
+
});
|
|
1981
|
+
if (method === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) {
|
|
1982
|
+
return loginOpenAICodexDeviceCode(interaction);
|
|
1983
|
+
}
|
|
1984
|
+
if (method !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) {
|
|
1985
|
+
throw new Error(`Unknown OpenAI Codex login method: ${method}`);
|
|
1986
|
+
}
|
|
1987
|
+
return loginOpenAICodex(interaction);
|
|
1988
|
+
},
|
|
1989
|
+
refresh: (credential, signal) => refreshOpenAICodexToken(credential.refresh, signal),
|
|
1990
|
+
async toAuth(credential) {
|
|
1991
|
+
return { apiKey: credential.access };
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
|
|
1995
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.js
|
|
1996
|
+
import { createServer } from "node:http";
|
|
1997
|
+
var AUTHORIZE_URL3 = "https://openrouter.ai/auth";
|
|
1998
|
+
var TOKEN_URL3 = "https://openrouter.ai/api/v1/auth/keys";
|
|
1999
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
2000
|
+
var TOKEN_EXCHANGE_TIMEOUT_MS = 30000;
|
|
2001
|
+
function getCallbackHost2() {
|
|
2002
|
+
return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
2003
|
+
}
|
|
2004
|
+
function sendHtml(response, status, html) {
|
|
2005
|
+
response.statusCode = status;
|
|
2006
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
2007
|
+
response.setHeader("cache-control", "no-store");
|
|
2008
|
+
response.end(html);
|
|
2009
|
+
}
|
|
2010
|
+
function parseAuthorizationInput3(input) {
|
|
2011
|
+
const value = input.trim();
|
|
2012
|
+
if (!value)
|
|
2013
|
+
return;
|
|
2014
|
+
try {
|
|
2015
|
+
return new URL(value).searchParams.get("code") ?? undefined;
|
|
2016
|
+
} catch {}
|
|
2017
|
+
if (value.includes("code=")) {
|
|
2018
|
+
return new URLSearchParams(value).get("code") ?? undefined;
|
|
2019
|
+
}
|
|
2020
|
+
return value;
|
|
2021
|
+
}
|
|
2022
|
+
function errorDetail2(body) {
|
|
2023
|
+
if (typeof body.error_description === "string")
|
|
2024
|
+
return body.error_description;
|
|
2025
|
+
if (typeof body.message === "string")
|
|
2026
|
+
return body.message;
|
|
2027
|
+
if (typeof body.error === "string")
|
|
2028
|
+
return body.error;
|
|
2029
|
+
if (body.error && typeof body.error === "object" && !Array.isArray(body.error)) {
|
|
2030
|
+
const message = body.error.message;
|
|
2031
|
+
if (typeof message === "string")
|
|
2032
|
+
return message;
|
|
2033
|
+
}
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
async function exchangeAuthorizationCode3(code, verifier, signal) {
|
|
2037
|
+
if (signal.aborted)
|
|
2038
|
+
throw new Error("Login cancelled");
|
|
2039
|
+
const controller = new AbortController;
|
|
2040
|
+
const onAbort = () => controller.abort(signal.reason);
|
|
2041
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2042
|
+
const timeout = setTimeout(() => controller.abort(new Error("OpenRouter OAuth token exchange timed out")), TOKEN_EXCHANGE_TIMEOUT_MS);
|
|
2043
|
+
let response;
|
|
2044
|
+
let body = {};
|
|
2045
|
+
try {
|
|
2046
|
+
response = await fetch(TOKEN_URL3, {
|
|
2047
|
+
method: "POST",
|
|
2048
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
2049
|
+
body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: "S256" }),
|
|
2050
|
+
signal: controller.signal
|
|
2051
|
+
});
|
|
2052
|
+
try {
|
|
2053
|
+
const parsed = await response.json();
|
|
2054
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
2055
|
+
body = parsed;
|
|
2056
|
+
} catch {
|
|
2057
|
+
if (response.ok)
|
|
2058
|
+
throw new Error("OpenRouter OAuth returned invalid JSON");
|
|
2059
|
+
}
|
|
2060
|
+
} catch (error) {
|
|
2061
|
+
if (signal.aborted)
|
|
2062
|
+
throw new Error("Login cancelled");
|
|
2063
|
+
if (controller.signal.aborted)
|
|
2064
|
+
throw new Error("OpenRouter OAuth token exchange timed out");
|
|
2065
|
+
throw error;
|
|
2066
|
+
} finally {
|
|
2067
|
+
clearTimeout(timeout);
|
|
2068
|
+
signal.removeEventListener("abort", onAbort);
|
|
2069
|
+
}
|
|
2070
|
+
if (!response.ok) {
|
|
2071
|
+
const detail = errorDetail2(body);
|
|
2072
|
+
throw new Error(`OpenRouter OAuth key exchange failed (HTTP ${response.status})${detail ? `: ${detail}` : ""}`);
|
|
2073
|
+
}
|
|
2074
|
+
if (typeof body.key !== "string" || body.key.length === 0) {
|
|
2075
|
+
throw new Error('OpenRouter OAuth response carries no "key"');
|
|
2076
|
+
}
|
|
2077
|
+
return {
|
|
2078
|
+
type: "oauth",
|
|
2079
|
+
access: body.key,
|
|
2080
|
+
refresh: "",
|
|
2081
|
+
expires: Number.MAX_SAFE_INTEGER
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
async function startCallbackServer2(callbackPath, verifier, signal) {
|
|
2085
|
+
if (signal.aborted)
|
|
2086
|
+
throw new Error("Login cancelled");
|
|
2087
|
+
const callbackHost = getCallbackHost2();
|
|
2088
|
+
let resolveCredential = () => {};
|
|
2089
|
+
let rejectCredential = () => {};
|
|
2090
|
+
const credential = new Promise((resolve, reject) => {
|
|
2091
|
+
resolveCredential = resolve;
|
|
2092
|
+
rejectCredential = reject;
|
|
2093
|
+
});
|
|
2094
|
+
let server;
|
|
2095
|
+
let claimed = false;
|
|
2096
|
+
let settled = false;
|
|
2097
|
+
let timeout;
|
|
2098
|
+
let onAbort;
|
|
2099
|
+
const close = () => {
|
|
2100
|
+
if (timeout)
|
|
2101
|
+
clearTimeout(timeout);
|
|
2102
|
+
if (onAbort)
|
|
2103
|
+
signal.removeEventListener("abort", onAbort);
|
|
2104
|
+
server.close();
|
|
2105
|
+
};
|
|
2106
|
+
const finish = (result) => {
|
|
2107
|
+
if (settled)
|
|
2108
|
+
return;
|
|
2109
|
+
settled = true;
|
|
2110
|
+
close();
|
|
2111
|
+
if ("credential" in result)
|
|
2112
|
+
resolveCredential(result.credential);
|
|
2113
|
+
else
|
|
2114
|
+
rejectCredential(result.error);
|
|
2115
|
+
};
|
|
2116
|
+
server = createServer((request, response) => {
|
|
2117
|
+
(async () => {
|
|
2118
|
+
const requestUrl = new URL(request.url ?? "/", `http://${callbackHost}`);
|
|
2119
|
+
if (request.method !== "GET" || requestUrl.pathname !== callbackPath) {
|
|
2120
|
+
sendHtml(response, 404, oauthErrorHtml("OAuth callback route not found."));
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (claimed || settled) {
|
|
2124
|
+
sendHtml(response, 409, oauthErrorHtml("This OAuth callback has already been used."));
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
const oauthError = requestUrl.searchParams.get("error");
|
|
2128
|
+
if (oauthError) {
|
|
2129
|
+
const description = requestUrl.searchParams.get("error_description") ?? oauthError;
|
|
2130
|
+
sendHtml(response, 400, oauthErrorHtml("OpenRouter authorization was denied.", description));
|
|
2131
|
+
finish({ error: new Error(`OpenRouter authorization failed: ${description}`) });
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
const code = requestUrl.searchParams.get("code");
|
|
2135
|
+
if (!code) {
|
|
2136
|
+
sendHtml(response, 400, oauthErrorHtml("OpenRouter returned no authorization code."));
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
claimed = true;
|
|
2140
|
+
try {
|
|
2141
|
+
const result = await exchangeAuthorizationCode3(code, verifier, signal);
|
|
2142
|
+
sendHtml(response, 200, oauthSuccessHtml("Signed in to OpenRouter. You may now close this page."));
|
|
2143
|
+
finish({ credential: result });
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
const message = error instanceof Error ? error.message : "Unknown token exchange error";
|
|
2146
|
+
sendHtml(response, 502, oauthErrorHtml("OpenRouter key exchange failed.", message));
|
|
2147
|
+
finish({ error: error instanceof Error ? error : new Error(message) });
|
|
2148
|
+
}
|
|
2149
|
+
})();
|
|
2150
|
+
});
|
|
2151
|
+
await new Promise((resolve, reject) => {
|
|
2152
|
+
server.once("error", reject);
|
|
2153
|
+
server.listen(0, callbackHost, () => {
|
|
2154
|
+
server.removeListener("error", reject);
|
|
2155
|
+
resolve();
|
|
2156
|
+
});
|
|
2157
|
+
});
|
|
2158
|
+
server.on("error", (error) => finish({ error }));
|
|
2159
|
+
onAbort = () => finish({ error: new Error("Login cancelled") });
|
|
2160
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2161
|
+
if (signal.aborted) {
|
|
2162
|
+
close();
|
|
2163
|
+
throw new Error("Login cancelled");
|
|
2164
|
+
}
|
|
2165
|
+
timeout = setTimeout(() => finish({ error: new Error("OpenRouter OAuth login timed out") }), LOGIN_TIMEOUT_MS);
|
|
2166
|
+
const address = server.address();
|
|
2167
|
+
if (!address || typeof address === "string") {
|
|
2168
|
+
close();
|
|
2169
|
+
throw new Error("Could not determine the OpenRouter OAuth callback port");
|
|
2170
|
+
}
|
|
2171
|
+
return {
|
|
2172
|
+
callbackUrl: `http://${callbackHost}:${address.port}${callbackPath}`,
|
|
2173
|
+
close,
|
|
2174
|
+
cancelWait: () => {
|
|
2175
|
+
if (!claimed)
|
|
2176
|
+
finish({ credential: null });
|
|
2177
|
+
},
|
|
2178
|
+
waitForCredential: () => credential
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
async function loginOpenRouter(interaction) {
|
|
2182
|
+
const { verifier, challenge } = await generatePKCE();
|
|
2183
|
+
const callbackPath = `/oauth/callback/${crypto.randomUUID()}`;
|
|
2184
|
+
const callback = await startCallbackServer2(callbackPath, verifier, interaction.signal);
|
|
2185
|
+
const manualAbort = new AbortController;
|
|
2186
|
+
let manualInput;
|
|
2187
|
+
let manualError;
|
|
2188
|
+
try {
|
|
2189
|
+
const authorizeUrl = new URL(AUTHORIZE_URL3);
|
|
2190
|
+
authorizeUrl.search = new URLSearchParams({
|
|
2191
|
+
callback_url: callback.callbackUrl,
|
|
2192
|
+
code_challenge: challenge,
|
|
2193
|
+
code_challenge_method: "S256"
|
|
2194
|
+
}).toString();
|
|
2195
|
+
interaction.notify({
|
|
2196
|
+
type: "progress",
|
|
2197
|
+
message: `Listening for OpenRouter OAuth callback on ${callback.callbackUrl}`
|
|
2198
|
+
});
|
|
2199
|
+
interaction.notify({
|
|
2200
|
+
type: "auth_url",
|
|
2201
|
+
url: authorizeUrl.toString(),
|
|
2202
|
+
instructions: "Complete sign-in in your browser. If the browser is on another machine, paste the final redirect URL here."
|
|
2203
|
+
});
|
|
2204
|
+
const manualPromise = interaction.prompt({
|
|
2205
|
+
type: "manual_code",
|
|
2206
|
+
message: "Complete sign-in in your browser, or paste the authorization code / redirect URL here:",
|
|
2207
|
+
placeholder: callback.callbackUrl,
|
|
2208
|
+
signal: manualAbort.signal
|
|
2209
|
+
}).then((input) => {
|
|
2210
|
+
manualInput = input;
|
|
2211
|
+
callback.cancelWait();
|
|
2212
|
+
}).catch((error) => {
|
|
2213
|
+
manualError = error instanceof Error ? error : new Error(String(error));
|
|
2214
|
+
callback.cancelWait();
|
|
2215
|
+
});
|
|
2216
|
+
const credential = await callback.waitForCredential();
|
|
2217
|
+
if (manualError)
|
|
2218
|
+
throw manualError;
|
|
2219
|
+
if (credential)
|
|
2220
|
+
return credential;
|
|
2221
|
+
await manualPromise;
|
|
2222
|
+
if (manualError)
|
|
2223
|
+
throw manualError;
|
|
2224
|
+
const code = manualInput ? parseAuthorizationInput3(manualInput) : undefined;
|
|
2225
|
+
if (!code)
|
|
2226
|
+
throw new Error("Missing authorization code");
|
|
2227
|
+
interaction.notify({ type: "progress", message: "Exchanging authorization code for an API key..." });
|
|
2228
|
+
return await exchangeAuthorizationCode3(code, verifier, interaction.signal);
|
|
2229
|
+
} finally {
|
|
2230
|
+
manualAbort.abort();
|
|
2231
|
+
callback.close();
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
var openRouterOAuth = {
|
|
2235
|
+
name: "OpenRouter OAuth",
|
|
2236
|
+
loginLabel: "Sign in with OpenRouter",
|
|
2237
|
+
login: loginOpenRouter,
|
|
2238
|
+
async refresh(credential, _signal) {
|
|
2239
|
+
return credential;
|
|
2240
|
+
},
|
|
2241
|
+
async toAuth(credential) {
|
|
2242
|
+
return { apiKey: credential.access };
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
|
|
2246
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.js
|
|
2247
|
+
var _http2 = null;
|
|
2248
|
+
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
2249
|
+
import("node:http").then((m) => {
|
|
2250
|
+
_http2 = m;
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
var CALLBACK_HOST2 = "127.0.0.1";
|
|
2254
|
+
var CALLBACK_PORT2 = 1456;
|
|
2255
|
+
var CALLBACK_PATH2 = "/oauth/callback";
|
|
2256
|
+
var REDIRECT_URI3 = `http://${CALLBACK_HOST2}:${CALLBACK_PORT2}${CALLBACK_PATH2}`;
|
|
2257
|
+
var TOKEN_EXPIRY_SKEW_MS = 60000;
|
|
2258
|
+
var LOGIN_METHOD_BROWSER = "browser";
|
|
2259
|
+
var LOGIN_METHOD_DEVICE_CODE = "device-code";
|
|
2260
|
+
var OAUTH_CLIENT_ID = "pi-gateway";
|
|
2261
|
+
var OAUTH_SCOPE = "gateway offline_access";
|
|
2262
|
+
var OAUTH_DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
2263
|
+
async function loadRadiusOAuthDiscovery(gateway, signal) {
|
|
2264
|
+
const response = await fetch(new URL("/v1/oauth", gateway), {
|
|
2265
|
+
headers: { accept: "application/json" },
|
|
2266
|
+
signal
|
|
2267
|
+
});
|
|
2268
|
+
if (!response.ok) {
|
|
2269
|
+
throw new Error(`Could not load Radius OAuth config from ${gateway}: ${response.status} ${await response.text()}`);
|
|
2270
|
+
}
|
|
2271
|
+
const discovery = await response.json();
|
|
2272
|
+
if (typeof discovery.authorizationEndpoint !== "string") {
|
|
2273
|
+
throw new Error(`Invalid Radius OAuth config from ${gateway}`);
|
|
2274
|
+
}
|
|
2275
|
+
return { authorizationEndpoint: discovery.authorizationEndpoint };
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
class OAuthResponseError extends Error {
|
|
2279
|
+
status;
|
|
2280
|
+
oauthError;
|
|
2281
|
+
constructor(status, oauthError, description, message) {
|
|
2282
|
+
const detail = oauthError ? description ? `${oauthError}: ${description}` : oauthError : description || String(status);
|
|
2283
|
+
super(`${message}: ${detail}`);
|
|
2284
|
+
this.status = status;
|
|
2285
|
+
this.oauthError = oauthError;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
async function readOAuthResponseError(response, message) {
|
|
2289
|
+
const text = await response.text().catch(() => "");
|
|
2290
|
+
let oauthError;
|
|
2291
|
+
let description;
|
|
2292
|
+
if (text) {
|
|
2293
|
+
try {
|
|
2294
|
+
const data = JSON.parse(text);
|
|
2295
|
+
oauthError = typeof data.error === "string" ? data.error : undefined;
|
|
2296
|
+
description = typeof data.error_description === "string" ? data.error_description : undefined;
|
|
2297
|
+
} catch {
|
|
2298
|
+
description = text;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
return new OAuthResponseError(response.status, oauthError, description, message);
|
|
2302
|
+
}
|
|
2303
|
+
async function requestOAuthToken(gateway, body, signal) {
|
|
2304
|
+
let response;
|
|
2305
|
+
try {
|
|
2306
|
+
response = await fetch(new URL("/v1/oauth/token", gateway), {
|
|
2307
|
+
method: "POST",
|
|
2308
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
2309
|
+
body,
|
|
2310
|
+
signal
|
|
2311
|
+
});
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
if (signal.aborted) {
|
|
2314
|
+
throw new Error("Login cancelled");
|
|
2315
|
+
}
|
|
2316
|
+
throw error;
|
|
2317
|
+
}
|
|
2318
|
+
if (!response.ok) {
|
|
2319
|
+
throw await readOAuthResponseError(response, "Radius OAuth token request failed");
|
|
2320
|
+
}
|
|
2321
|
+
const data = await response.json();
|
|
2322
|
+
return {
|
|
2323
|
+
type: "oauth",
|
|
2324
|
+
access: data.access_token,
|
|
2325
|
+
refresh: data.refresh_token,
|
|
2326
|
+
expires: Date.now() + data.expires_in * 1000 - TOKEN_EXPIRY_SKEW_MS,
|
|
2327
|
+
scope: data.scope
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
function startOAuthCallbackServer(expectedState, signal) {
|
|
2331
|
+
if (!_http2) {
|
|
2332
|
+
throw new Error("Radius OAuth is only available in Node.js environments");
|
|
2333
|
+
}
|
|
2334
|
+
let settle = () => {};
|
|
2335
|
+
let settled = false;
|
|
2336
|
+
const wait = new Promise((resolve) => {
|
|
2337
|
+
settle = resolve;
|
|
2338
|
+
});
|
|
2339
|
+
const finish = (code) => {
|
|
2340
|
+
if (settled) {
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
settled = true;
|
|
2344
|
+
signal.removeEventListener("abort", onAbort);
|
|
2345
|
+
settle(code);
|
|
2346
|
+
};
|
|
2347
|
+
const onAbort = () => finish(null);
|
|
2348
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2349
|
+
const sendPage = (response, status, html) => {
|
|
2350
|
+
response.statusCode = status;
|
|
2351
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
2352
|
+
response.end(html);
|
|
2353
|
+
};
|
|
2354
|
+
const server = _http2.createServer((request, response) => {
|
|
2355
|
+
const url = new URL(request.url ?? "/", REDIRECT_URI3);
|
|
2356
|
+
if (url.pathname !== CALLBACK_PATH2) {
|
|
2357
|
+
sendPage(response, 404, oauthErrorHtml("Callback route not found."));
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
if (url.searchParams.get("state") !== expectedState) {
|
|
2361
|
+
sendPage(response, 400, oauthErrorHtml("OAuth state mismatch."));
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
const error = url.searchParams.get("error");
|
|
2365
|
+
if (error) {
|
|
2366
|
+
sendPage(response, 400, oauthErrorHtml(url.searchParams.get("error_description") ?? error));
|
|
2367
|
+
finish(null);
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
const code = url.searchParams.get("code");
|
|
2371
|
+
if (!code) {
|
|
2372
|
+
sendPage(response, 400, oauthErrorHtml("Missing authorization code."));
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
sendPage(response, 200, oauthSuccessHtml("Signed in to Radius. You may now close this page."));
|
|
2376
|
+
finish(code);
|
|
2377
|
+
});
|
|
2378
|
+
return new Promise((resolve) => {
|
|
2379
|
+
server.listen(CALLBACK_PORT2, CALLBACK_HOST2, () => {
|
|
2380
|
+
resolve({
|
|
2381
|
+
waitForCode: () => wait,
|
|
2382
|
+
close: () => {
|
|
2383
|
+
finish(null);
|
|
2384
|
+
server.close();
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
}).once("error", () => {
|
|
2388
|
+
finish(null);
|
|
2389
|
+
resolve({ waitForCode: async () => null, close: () => {} });
|
|
2390
|
+
});
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
async function loginWithBrowser(gateway, authorizationEndpoint, interaction) {
|
|
2394
|
+
const { verifier, challenge } = await generatePKCE();
|
|
2395
|
+
const state = crypto.randomUUID();
|
|
2396
|
+
const authorizeUrl = new URL(authorizationEndpoint);
|
|
2397
|
+
authorizeUrl.search = new URLSearchParams({
|
|
2398
|
+
response_type: "code",
|
|
2399
|
+
client_id: OAUTH_CLIENT_ID,
|
|
2400
|
+
redirect_uri: REDIRECT_URI3,
|
|
2401
|
+
scope: OAUTH_SCOPE,
|
|
2402
|
+
code_challenge: challenge,
|
|
2403
|
+
code_challenge_method: "S256",
|
|
2404
|
+
handoff: "url",
|
|
2405
|
+
state
|
|
2406
|
+
}).toString();
|
|
2407
|
+
const callbackServer = await startOAuthCallbackServer(state, interaction.signal);
|
|
2408
|
+
interaction.notify({ type: "progress", message: `Listening for OAuth callback on ${REDIRECT_URI3}` });
|
|
2409
|
+
interaction.notify({
|
|
2410
|
+
type: "auth_url",
|
|
2411
|
+
url: authorizeUrl.toString(),
|
|
2412
|
+
instructions: "Continue in your browser."
|
|
2413
|
+
});
|
|
2414
|
+
try {
|
|
2415
|
+
const code = await callbackServer.waitForCode();
|
|
2416
|
+
if (!code) {
|
|
2417
|
+
if (interaction.signal.aborted) {
|
|
2418
|
+
throw new Error("Login cancelled");
|
|
2419
|
+
}
|
|
2420
|
+
throw new Error("OAuth callback did not complete.");
|
|
2421
|
+
}
|
|
2422
|
+
return await requestOAuthToken(gateway, new URLSearchParams({
|
|
2423
|
+
grant_type: "authorization_code",
|
|
2424
|
+
client_id: OAUTH_CLIENT_ID,
|
|
2425
|
+
redirect_uri: REDIRECT_URI3,
|
|
2426
|
+
code,
|
|
2427
|
+
code_verifier: verifier
|
|
2428
|
+
}), interaction.signal);
|
|
2429
|
+
} finally {
|
|
2430
|
+
callbackServer.close();
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
async function requestDeviceAuthorization(gateway, signal) {
|
|
2434
|
+
let response;
|
|
2435
|
+
try {
|
|
2436
|
+
response = await fetch(new URL("/v1/oauth/device", gateway), {
|
|
2437
|
+
method: "POST",
|
|
2438
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
2439
|
+
body: new URLSearchParams({ client_id: OAUTH_CLIENT_ID, scope: OAUTH_SCOPE }),
|
|
2440
|
+
signal
|
|
2441
|
+
});
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
if (signal.aborted) {
|
|
2444
|
+
throw new Error("Login cancelled");
|
|
2445
|
+
}
|
|
2446
|
+
throw error;
|
|
2447
|
+
}
|
|
2448
|
+
if (!response.ok) {
|
|
2449
|
+
throw await readOAuthResponseError(response, "Radius OAuth device authorization failed");
|
|
2450
|
+
}
|
|
2451
|
+
const data = await response.json();
|
|
2452
|
+
if (!data.device_code || !data.user_code || !data.verification_uri || !data.expires_in) {
|
|
2453
|
+
throw new Error("Radius OAuth device authorization response is missing required fields");
|
|
2454
|
+
}
|
|
2455
|
+
return {
|
|
2456
|
+
device_code: data.device_code,
|
|
2457
|
+
user_code: data.user_code,
|
|
2458
|
+
verification_uri: data.verification_uri,
|
|
2459
|
+
expires_in: data.expires_in,
|
|
2460
|
+
interval: data.interval
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
async function loginWithDeviceCode(gateway, interaction) {
|
|
2464
|
+
const device = await requestDeviceAuthorization(gateway, interaction.signal);
|
|
2465
|
+
interaction.notify({
|
|
2466
|
+
type: "device_code",
|
|
2467
|
+
userCode: device.user_code,
|
|
2468
|
+
verificationUri: device.verification_uri,
|
|
2469
|
+
intervalSeconds: device.interval,
|
|
2470
|
+
expiresInSeconds: device.expires_in
|
|
2471
|
+
});
|
|
2472
|
+
return pollOAuthDeviceCodeFlow({
|
|
2473
|
+
intervalSeconds: device.interval,
|
|
2474
|
+
expiresInSeconds: device.expires_in,
|
|
2475
|
+
signal: interaction.signal,
|
|
2476
|
+
poll: async () => {
|
|
2477
|
+
try {
|
|
2478
|
+
const credentials = await requestOAuthToken(gateway, new URLSearchParams({
|
|
2479
|
+
grant_type: OAUTH_DEVICE_CODE_GRANT_TYPE,
|
|
2480
|
+
client_id: OAUTH_CLIENT_ID,
|
|
2481
|
+
device_code: device.device_code
|
|
2482
|
+
}), interaction.signal);
|
|
2483
|
+
return { status: "complete", value: credentials };
|
|
2484
|
+
} catch (error) {
|
|
2485
|
+
if (!(error instanceof OAuthResponseError)) {
|
|
2486
|
+
throw error;
|
|
2487
|
+
}
|
|
2488
|
+
switch (error.oauthError) {
|
|
2489
|
+
case "authorization_pending":
|
|
2490
|
+
return { status: "pending" };
|
|
2491
|
+
case "slow_down":
|
|
2492
|
+
return { status: "slow_down" };
|
|
2493
|
+
case "expired_token":
|
|
2494
|
+
return { status: "failed", message: "Device authorization expired." };
|
|
2495
|
+
case "access_denied":
|
|
2496
|
+
return { status: "failed", message: "Device authorization was denied." };
|
|
2497
|
+
default:
|
|
2498
|
+
throw error;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
function createRadiusOAuth(options) {
|
|
2505
|
+
const gateway = normalizeRadiusGatewayUrl(options.gateway);
|
|
2506
|
+
return {
|
|
2507
|
+
name: options.name,
|
|
2508
|
+
async login(interaction) {
|
|
2509
|
+
const loginMethod = await interaction.prompt({
|
|
2510
|
+
type: "select",
|
|
2511
|
+
message: `Sign in to ${options.name}:`,
|
|
2512
|
+
options: [
|
|
2513
|
+
{ id: LOGIN_METHOD_BROWSER, label: "Sign in with browser (recommended)" },
|
|
2514
|
+
{
|
|
2515
|
+
id: LOGIN_METHOD_DEVICE_CODE,
|
|
2516
|
+
label: "Sign in with device code (when signing in from another device)"
|
|
2517
|
+
}
|
|
2518
|
+
]
|
|
2519
|
+
});
|
|
2520
|
+
if (loginMethod === LOGIN_METHOD_DEVICE_CODE) {
|
|
2521
|
+
return loginWithDeviceCode(gateway, interaction);
|
|
2522
|
+
}
|
|
2523
|
+
if (loginMethod === LOGIN_METHOD_BROWSER) {
|
|
2524
|
+
const discovery = await loadRadiusOAuthDiscovery(gateway, interaction.signal);
|
|
2525
|
+
return loginWithBrowser(gateway, discovery.authorizationEndpoint, interaction);
|
|
2526
|
+
}
|
|
2527
|
+
throw new Error(`Unknown ${options.name} sign-in method: ${loginMethod}`);
|
|
2528
|
+
},
|
|
2529
|
+
async refresh(credential, signal) {
|
|
2530
|
+
const refreshed = await requestOAuthToken(gateway, new URLSearchParams({
|
|
2531
|
+
grant_type: "refresh_token",
|
|
2532
|
+
client_id: OAUTH_CLIENT_ID,
|
|
2533
|
+
refresh_token: credential.refresh
|
|
2534
|
+
}), signal);
|
|
2535
|
+
return refreshed;
|
|
2536
|
+
},
|
|
2537
|
+
async toAuth(credential) {
|
|
2538
|
+
return { apiKey: credential.access };
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/xai.js
|
|
2544
|
+
var XAI_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
2545
|
+
var XAI_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
2546
|
+
var XAI_DEVICE_CODE_URL = "https://auth.x.ai/oauth2/device/code";
|
|
2547
|
+
var XAI_TOKEN_URL = "https://auth.x.ai/oauth2/token";
|
|
2548
|
+
var REFRESH_SKEW_MS = 5 * 60 * 1000;
|
|
2549
|
+
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
2550
|
+
function requiredString(body, field) {
|
|
2551
|
+
const value = body[field];
|
|
2552
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2553
|
+
throw new Error(`Invalid xAI OAuth response field: ${field}`);
|
|
2554
|
+
}
|
|
2555
|
+
return value;
|
|
2556
|
+
}
|
|
2557
|
+
function positiveNumber2(body, field) {
|
|
2558
|
+
const value = body[field];
|
|
2559
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
2560
|
+
throw new Error(`Invalid xAI OAuth response field: ${field}`);
|
|
2561
|
+
}
|
|
2562
|
+
return value;
|
|
2563
|
+
}
|
|
2564
|
+
function validateVerificationUri(raw) {
|
|
2565
|
+
let url;
|
|
2566
|
+
try {
|
|
2567
|
+
url = new URL(raw);
|
|
2568
|
+
} catch {
|
|
2569
|
+
throw new Error("Untrusted verification URI in xAI OAuth response");
|
|
2570
|
+
}
|
|
2571
|
+
if (url.protocol !== "https:") {
|
|
2572
|
+
throw new Error("Untrusted verification URI in xAI OAuth response");
|
|
2573
|
+
}
|
|
2574
|
+
return url.href;
|
|
2575
|
+
}
|
|
2576
|
+
async function postForm(url, fields, signal) {
|
|
2577
|
+
let response;
|
|
2578
|
+
try {
|
|
2579
|
+
response = await fetch(url, {
|
|
2580
|
+
method: "POST",
|
|
2581
|
+
headers: {
|
|
2582
|
+
Accept: "application/json",
|
|
2583
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2584
|
+
},
|
|
2585
|
+
body: new URLSearchParams(fields),
|
|
2586
|
+
signal
|
|
2587
|
+
});
|
|
2588
|
+
} catch (error) {
|
|
2589
|
+
if (signal.aborted) {
|
|
2590
|
+
throw new Error("Login cancelled");
|
|
2591
|
+
}
|
|
2592
|
+
throw error;
|
|
2593
|
+
}
|
|
2594
|
+
let body;
|
|
2595
|
+
try {
|
|
2596
|
+
const parsed = await response.json();
|
|
2597
|
+
body = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
2598
|
+
} catch {
|
|
2599
|
+
if (signal.aborted) {
|
|
2600
|
+
throw new Error("Login cancelled");
|
|
2601
|
+
}
|
|
2602
|
+
throw new Error(`xAI OAuth returned invalid JSON (HTTP ${response.status})`);
|
|
2603
|
+
}
|
|
2604
|
+
return {
|
|
2605
|
+
ok: response.ok,
|
|
2606
|
+
status: response.status,
|
|
2607
|
+
body
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
function requestFailure(action, response) {
|
|
2611
|
+
const error = typeof response.body.error === "string" ? response.body.error : undefined;
|
|
2612
|
+
const description = typeof response.body.error_description === "string" ? response.body.error_description : undefined;
|
|
2613
|
+
const detail = [error, description].filter(Boolean).join(": ");
|
|
2614
|
+
return new Error(`xAI OAuth ${action} failed (HTTP ${response.status})${detail ? `: ${detail}` : ""}`);
|
|
2615
|
+
}
|
|
2616
|
+
function parseDeviceCode(body) {
|
|
2617
|
+
const interval = body.interval;
|
|
2618
|
+
const intervalSeconds = typeof interval === "number" && Number.isFinite(interval) && interval > 0 ? interval : undefined;
|
|
2619
|
+
const verificationUriComplete = typeof body.verification_uri_complete === "string" && body.verification_uri_complete.length > 0 ? validateVerificationUri(body.verification_uri_complete) : undefined;
|
|
2620
|
+
return {
|
|
2621
|
+
deviceCode: requiredString(body, "device_code"),
|
|
2622
|
+
userCode: requiredString(body, "user_code"),
|
|
2623
|
+
verificationUri: validateVerificationUri(requiredString(body, "verification_uri")),
|
|
2624
|
+
verificationUriComplete,
|
|
2625
|
+
intervalSeconds,
|
|
2626
|
+
expiresInSeconds: positiveNumber2(body, "expires_in")
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function credentialsFromTokenResponse(body, previousRefreshToken) {
|
|
2630
|
+
const access = requiredString(body, "access_token");
|
|
2631
|
+
const refresh = body.refresh_token === undefined && previousRefreshToken ? previousRefreshToken : requiredString(body, "refresh_token");
|
|
2632
|
+
const expiresInSeconds = body.expires_in === undefined ? DEFAULT_TOKEN_LIFETIME_SECONDS : positiveNumber2(body, "expires_in");
|
|
2633
|
+
return {
|
|
2634
|
+
type: "oauth",
|
|
2635
|
+
access,
|
|
2636
|
+
refresh,
|
|
2637
|
+
expires: Date.now() + expiresInSeconds * 1000 - REFRESH_SKEW_MS
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
async function requestDeviceCode(signal) {
|
|
2641
|
+
const response = await postForm(XAI_DEVICE_CODE_URL, {
|
|
2642
|
+
client_id: XAI_CLIENT_ID,
|
|
2643
|
+
scope: XAI_SCOPE,
|
|
2644
|
+
referrer: "pi"
|
|
2645
|
+
}, signal);
|
|
2646
|
+
if (!response.ok) {
|
|
2647
|
+
throw requestFailure("device authorization", response);
|
|
2648
|
+
}
|
|
2649
|
+
return parseDeviceCode(response.body);
|
|
2650
|
+
}
|
|
2651
|
+
async function pollForTokens(device, signal) {
|
|
2652
|
+
return pollOAuthDeviceCodeFlow({
|
|
2653
|
+
intervalSeconds: device.intervalSeconds,
|
|
2654
|
+
expiresInSeconds: device.expiresInSeconds,
|
|
2655
|
+
waitBeforeFirstPoll: true,
|
|
2656
|
+
signal,
|
|
2657
|
+
poll: async () => {
|
|
2658
|
+
const response = await postForm(XAI_TOKEN_URL, {
|
|
2659
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
2660
|
+
client_id: XAI_CLIENT_ID,
|
|
2661
|
+
device_code: device.deviceCode
|
|
2662
|
+
}, signal);
|
|
2663
|
+
if (response.ok) {
|
|
2664
|
+
return { status: "complete", value: credentialsFromTokenResponse(response.body) };
|
|
2665
|
+
}
|
|
2666
|
+
const error = response.body.error;
|
|
2667
|
+
if (error === "authorization_pending") {
|
|
2668
|
+
return { status: "pending" };
|
|
2669
|
+
}
|
|
2670
|
+
if (error === "slow_down") {
|
|
2671
|
+
const interval = response.body.interval;
|
|
2672
|
+
return { status: "slow_down", intervalSeconds: typeof interval === "number" ? interval : undefined };
|
|
2673
|
+
}
|
|
2674
|
+
if (error === "access_denied" || error === "authorization_denied") {
|
|
2675
|
+
return { status: "failed", message: "xAI device authorization was denied" };
|
|
2676
|
+
}
|
|
2677
|
+
if (error === "expired_token") {
|
|
2678
|
+
return { status: "failed", message: "xAI device code expired" };
|
|
2679
|
+
}
|
|
2680
|
+
return { status: "failed", message: requestFailure("device token polling", response).message };
|
|
2681
|
+
}
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
async function loginXai(interaction) {
|
|
2685
|
+
const device = await requestDeviceCode(interaction.signal);
|
|
2686
|
+
interaction.notify({
|
|
2687
|
+
type: "device_code",
|
|
2688
|
+
userCode: device.userCode,
|
|
2689
|
+
verificationUri: device.verificationUriComplete ?? device.verificationUri,
|
|
2690
|
+
intervalSeconds: device.intervalSeconds,
|
|
2691
|
+
expiresInSeconds: device.expiresInSeconds
|
|
2692
|
+
});
|
|
2693
|
+
return pollForTokens(device, interaction.signal);
|
|
2694
|
+
}
|
|
2695
|
+
async function refreshXaiToken(refreshToken2, signal) {
|
|
2696
|
+
const response = await postForm(XAI_TOKEN_URL, {
|
|
2697
|
+
grant_type: "refresh_token",
|
|
2698
|
+
client_id: XAI_CLIENT_ID,
|
|
2699
|
+
refresh_token: refreshToken2
|
|
2700
|
+
}, signal);
|
|
2701
|
+
if (!response.ok) {
|
|
2702
|
+
throw requestFailure("token refresh", response);
|
|
2703
|
+
}
|
|
2704
|
+
return credentialsFromTokenResponse(response.body, refreshToken2);
|
|
2705
|
+
}
|
|
2706
|
+
var xaiOAuth = {
|
|
2707
|
+
name: "xAI (Grok/X subscription)",
|
|
2708
|
+
isSubscription: true,
|
|
2709
|
+
loginLabel: "Sign in with SuperGrok or X Premium",
|
|
2710
|
+
login: loginXai,
|
|
2711
|
+
refresh: (credential, signal) => refreshXaiToken(credential.refresh, signal),
|
|
2712
|
+
async toAuth(credential) {
|
|
2713
|
+
return { apiKey: credential.access };
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
|
|
2717
|
+
// node_modules/@earendil-works/pi-ai/dist/bun-oauth.js
|
|
2718
|
+
function registerBunOAuthFlows() {
|
|
2719
|
+
registerBundledOAuthFlowLoaders({
|
|
2720
|
+
anthropic: () => anthropicOAuth,
|
|
2721
|
+
openaiCodex: () => openaiCodexOAuth,
|
|
2722
|
+
githubCopilot: () => githubCopilotOAuth,
|
|
2723
|
+
openrouter: () => openRouterOAuth,
|
|
2724
|
+
kimiCoding: () => kimiCodingOAuth,
|
|
2725
|
+
meta: () => metaOAuth,
|
|
2726
|
+
xai: () => xaiOAuth,
|
|
2727
|
+
radius: createRadiusOAuth
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// src/utils/internal-ai/credentials.ts
|
|
2732
|
+
registerBunOAuthFlows();
|
|
2733
|
+
var resolveOAuthApiKey = async (providerId, credentials) => {
|
|
2734
|
+
const input = credentials[providerId];
|
|
2735
|
+
if (!input)
|
|
2736
|
+
return null;
|
|
2737
|
+
const store = new InMemoryCredentialStore;
|
|
2738
|
+
const initial = { ...input, type: "oauth" };
|
|
2739
|
+
await store.modify(providerId, async () => initial);
|
|
2740
|
+
const models = builtinModels({ credentials: store });
|
|
2741
|
+
const resolved = await models.getAuth(providerId);
|
|
2742
|
+
const apiKey = resolved?.auth.apiKey;
|
|
2743
|
+
if (!apiKey)
|
|
2744
|
+
return null;
|
|
2745
|
+
const current = await store.read(providerId);
|
|
2746
|
+
const changed = current?.type === "oauth" && (current.access !== initial.access || current.refresh !== initial.refresh || current.expires !== initial.expires);
|
|
2747
|
+
return {
|
|
2748
|
+
apiKey,
|
|
2749
|
+
newCredentials: changed && current?.type === "oauth" ? current : undefined
|
|
2750
|
+
};
|
|
2751
|
+
};
|
|
2752
|
+
async function resolveCredential(opts = {}) {
|
|
2753
|
+
const env = opts.env ?? process.env;
|
|
2754
|
+
const getEnvKey = opts._getEnvApiKey ?? getEnvApiKey;
|
|
2755
|
+
const getCodex = opts._getValidCodexOAuth ?? getValidCodexOAuth;
|
|
2756
|
+
const getOAuth = opts._getOAuthApiKey ?? resolveOAuthApiKey;
|
|
2757
|
+
const persistCodex = opts._persistCodexOAuth ?? persistCodexOAuth;
|
|
2758
|
+
const openrouterKey = env.OPENROUTER_API_KEY ?? getEnvKey("openrouter");
|
|
2759
|
+
if (openrouterKey) {
|
|
2760
|
+
return {
|
|
2761
|
+
kind: "openrouter",
|
|
2762
|
+
apiKey: openrouterKey,
|
|
2763
|
+
modelDefault: resolveModelString("openrouter")
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
const anthropicKey = env.ANTHROPIC_API_KEY ?? getEnvKey("anthropic");
|
|
2767
|
+
if (anthropicKey) {
|
|
2768
|
+
return {
|
|
2769
|
+
kind: "anthropic",
|
|
2770
|
+
apiKey: anthropicKey,
|
|
2771
|
+
modelDefault: resolveModelString("anthropic")
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
const openaiKey = env.OPENAI_API_KEY ?? getEnvKey("openai");
|
|
2775
|
+
if (openaiKey) {
|
|
2776
|
+
return {
|
|
2777
|
+
kind: "openai",
|
|
2778
|
+
apiKey: openaiKey,
|
|
2779
|
+
modelDefault: resolveModelString("openai")
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
if (opts.apiUrl && opts.apiKey) {
|
|
2783
|
+
try {
|
|
2784
|
+
const codexCreds = await getCodex(opts.apiUrl, opts.apiKey);
|
|
2785
|
+
if (codexCreds) {
|
|
2786
|
+
const credMap = {
|
|
2787
|
+
"openai-codex": {
|
|
2788
|
+
access: codexCreds.access,
|
|
2789
|
+
refresh: codexCreds.refresh,
|
|
2790
|
+
expires: codexCreds.expires
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2793
|
+
const oauthResult = await getOAuth("openai-codex", credMap);
|
|
2794
|
+
if (oauthResult) {
|
|
2795
|
+
if (oauthResult.newCredentials) {
|
|
2796
|
+
const updated = oauthResult.newCredentials;
|
|
2797
|
+
try {
|
|
2798
|
+
await persistCodex(opts.apiUrl, opts.apiKey, {
|
|
2799
|
+
access: String(updated.access),
|
|
2800
|
+
refresh: String(updated.refresh),
|
|
2801
|
+
expires: Number(updated.expires),
|
|
2802
|
+
accountId: codexCreds.accountId
|
|
2803
|
+
});
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
console.error(`internal-ai: persistCodexOAuth failed (callerTag=${opts.callerTag ?? "<unset>"}):`, err);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
return {
|
|
2809
|
+
kind: "openai-codex",
|
|
2810
|
+
apiKey: oauthResult.apiKey,
|
|
2811
|
+
modelDefault: resolveModelString("openai-codex")
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
} catch (err) {
|
|
2816
|
+
console.error(`internal-ai: codex OAuth probe failed (callerTag=${opts.callerTag ?? "<unset>"}):`, err);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
if (env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN || env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
2820
|
+
return {
|
|
2821
|
+
kind: "claude-cli",
|
|
2822
|
+
modelDefault: resolveModelString("claude-cli")
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
export { getMemoryRaterNames, REFERENCES_SOURCE_MAX_LENGTH, sanitizeReferencesSource, SummaryWithRatingsSchema, BASE_SUMMARIZE_PROMPT, LlmRater, buildRatingsFromLlm, buildSummaryWithRatingsPrompt, isLlmRaterEnabled, dedupeRetrievalsForRater, fetchRetrievalsForTask, postRatings, DEFAULT_MODEL, parseModelStr, resolveCredential };
|