@desplega.ai/agent-swarm 1.158.0 → 1.159.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/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
- package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
- package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
- package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
- package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
- package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
- package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
- package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
- package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
- package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
- package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
- package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
- package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
- package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
- package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
- package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
- package/dist/cli-0xqde53b.js +176 -0
- package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
- package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
- package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
- package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
- package/dist/cli-3gvrr8vj.js +1422 -0
- package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
- package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
- package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
- package/dist/cli-503ya656.js +455 -0
- package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
- package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
- package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
- package/dist/cli-7cxw8b60.js +14910 -0
- package/dist/cli-7kyk9jr3.js +22 -0
- package/dist/cli-7zttzetj.js +324 -0
- package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
- package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
- package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
- package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
- package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
- package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
- package/dist/cli-ca2w7zv7.js +115 -0
- package/dist/cli-cpvsaqsw.js +333 -0
- package/dist/cli-cygyqj3b.js +754 -0
- package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
- package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
- package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
- package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
- package/dist/cli-ejpygnqz.js +3815 -0
- package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
- package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
- package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
- package/dist/cli-gzyh0j0g.js +8455 -0
- package/dist/cli-hgqfttfc.js +183 -0
- package/dist/cli-hj65wsts.js +17755 -0
- package/dist/cli-j9z8mfx3.js +476 -0
- package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
- package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
- package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
- package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
- package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
- package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
- package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
- package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
- package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
- package/dist/cli-qvnnx4ct.js +1051 -0
- package/dist/cli-rb9m6rpe.js +4980 -0
- package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
- package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
- package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
- package/dist/cli-rykfmgwg.js +102 -0
- package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
- package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
- package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
- package/dist/cli-wd9hz40d.js +69 -0
- package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
- package/dist/cli-xgpf00sk.js +2149 -0
- package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
- package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
- package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
- package/dist/cli.js +38 -29
- package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
- package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
- package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
- package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
- package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
- package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
- package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
- package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
- package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
- package/dist/execute-71ysgv35.js +965 -0
- package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
- package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
- package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
- package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
- package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
- package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
- package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
- package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
- package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
- package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
- package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
- package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
- package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
- package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
- package/dist/llama-cpp-classify-t2tccams.js +335 -0
- package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
- package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
- package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
- package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
- package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
- package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
- package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
- package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
- package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
- package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
- package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
- package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
- package/dist/pricing-refresh-0v77385v.js +39 -0
- package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
- package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
- package/dist/runtime-ktjysacx.js +24 -0
- package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
- package/dist/typesafe-system-one-96960jxp.js +27 -0
- package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
- package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
- package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
- package/openapi.json +863 -14
- package/package.json +9 -8
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/tasks/human-free.ts +442 -0
- package/src/be/db/tasks/read.ts +56 -1
- package/src/be/db/tasks/write.ts +107 -11
- package/src/be/db.ts +89 -165
- package/src/be/harness-model-support.ts +182 -0
- package/src/be/human-free-drain.ts +67 -0
- package/src/be/migrations/182_task_human_free_flag.sql +63 -0
- package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
- package/src/be/migrations/184_model_catalog.sql +120 -0
- package/src/be/model-catalog-store.ts +644 -0
- package/src/be/model-tier-keys.ts +31 -0
- package/src/be/model-tier-resolution.ts +489 -0
- package/src/be/model-validation.ts +112 -0
- package/src/be/models-catalog.ts +61 -7
- package/src/be/modelsdev-cache.json +82 -0
- package/src/be/modelsdev-cache.ts +2 -0
- package/src/be/pricing-refresh.ts +154 -12
- package/src/be/scripts/typecheck.ts +15 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
- package/src/be/swarm-config-guard.ts +6 -0
- package/src/cli.tsx +10 -0
- package/src/commands/claude-managed-setup.ts +3 -1
- package/src/commands/runner.ts +76 -2
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +64 -15
- package/src/heartbeat/templates.ts +2 -2
- package/src/http/agents.ts +49 -1
- package/src/http/index.ts +8 -0
- package/src/http/models-catalog.ts +357 -8
- package/src/http/poll.ts +45 -2
- package/src/http/schedules.ts +36 -6
- package/src/http/session-data.ts +2 -5
- package/src/http/stats.ts +6 -8
- package/src/http/tasks.ts +26 -1
- package/src/http/usage-cache.ts +56 -15
- package/src/http/workflows.ts +35 -8
- package/src/prompts/session-templates.ts +1 -0
- package/src/providers/claude-managed-models.ts +47 -161
- package/src/providers/codex-adapter.ts +4 -4
- package/src/providers/codex-models.ts +65 -198
- package/src/providers/reasoning-effort.ts +44 -140
- package/src/rbac/legacy-policy.ts +25 -0
- package/src/rbac/permissions.ts +10 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/types/stdlib.d.ts +15 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
- package/src/server.ts +6 -0
- package/src/slack/message-text.ts +56 -2
- package/src/slack/thread-buffer.ts +10 -2
- package/src/tests/agents-list-model-display.test.ts +4 -6
- package/src/tests/base-prompt.test.ts +3 -0
- package/src/tests/claude-adapter.test.ts +10 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -18
- package/src/tests/codex-adapter.test.ts +2 -1
- package/src/tests/codex-gpt6-release.test.ts +66 -5
- package/src/tests/context-window.test.ts +7 -5
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +5 -1
- package/src/tests/db-task-write-contract.test.ts +4 -1
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/explicit-model-validation.test.ts +332 -0
- package/src/tests/harness-model-support.test.ts +374 -0
- package/src/tests/heartbeat-checklist-config.test.ts +71 -0
- package/src/tests/heartbeat-checklist.test.ts +4 -2
- package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
- package/src/tests/human-free-flag.test.ts +279 -0
- package/src/tests/human-free-oracle.ts +57 -0
- package/src/tests/human-free-reclassify.test.ts +669 -0
- package/src/tests/migration-046-budgets.test.ts +3 -3
- package/src/tests/model-catalog-migration.test.ts +134 -0
- package/src/tests/model-catalog-table.test.ts +249 -0
- package/src/tests/model-catalog-write-access.test.ts +387 -0
- package/src/tests/model-tier-resolution.test.ts +519 -0
- package/src/tests/models-catalog.test.ts +1 -1
- package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
- package/src/tests/package-publish.test.ts +137 -36
- package/src/tests/pricing-routes.test.ts +2 -2
- package/src/tests/rbac-engine.test.ts +42 -2
- package/src/tests/reasoning-effort.test.ts +109 -0
- package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
- package/src/tests/runtime-model-catalog.test.ts +86 -0
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/seed-scripts.test.ts +89 -0
- package/src/tests/session-costs.test.ts +14 -11
- package/src/tests/slack-message-text.test.ts +101 -0
- package/src/tests/slack-thread-buffer-context.test.ts +93 -0
- package/src/tests/task-cascade-fail.test.ts +148 -2
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/usage-cache.test.ts +166 -0
- package/src/tests/workflow-executors.test.ts +5 -2
- package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
- package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
- package/src/tests/workflow-system-one-decision.test.ts +2063 -0
- package/src/tools/model-catalog/index.ts +137 -0
- package/src/tools/schedules/create-schedule.ts +16 -1
- package/src/tools/schedules/patch-schedule.ts +17 -0
- package/src/tools/schedules/update-schedule.ts +17 -0
- package/src/tools/send-task.ts +14 -0
- package/src/tools/task-action.ts +12 -0
- package/src/tools/tool-config.ts +3 -1
- package/src/tools/workflows/create-workflow.ts +9 -2
- package/src/tools/workflows/patch-workflow-node.ts +17 -7
- package/src/tools/workflows/patch-workflow.ts +9 -2
- package/src/tools/workflows/update-workflow.ts +9 -2
- package/src/types.ts +30 -1
- package/src/utils/context-window.ts +47 -53
- package/src/utils/harness-cli-version.ts +115 -0
- package/src/utils/harness-model-error.ts +33 -0
- package/src/utils/runtime-model-catalog.ts +137 -0
- package/src/workflows/approval-resolution.ts +39 -0
- package/src/workflows/definition.ts +7 -0
- package/src/workflows/engine.ts +69 -1
- package/src/workflows/executors/agent-task.ts +9 -0
- package/src/workflows/executors/base.ts +53 -0
- package/src/workflows/executors/human-in-the-loop.ts +8 -8
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +1232 -0
- package/src/workflows/executors/system-one-providers.ts +301 -0
- package/src/workflows/executors/workflow-llm.ts +13 -1
- package/src/workflows/readiness.ts +86 -0
- package/src/workflows/recovery.ts +12 -12
- package/src/workflows/resume.ts +23 -9
- package/templates/schedules/daily-blocker-digest/content.md +4 -5
- package/templates/skills/agent-browser/SKILL.md +6 -5
- package/templates/skills/agent-browser/content.md +6 -5
- package/templates/skills/heartbeat-runbook/config.json +1 -1
- package/templates/skills/heartbeat-runbook/content.md +2 -2
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
- package/templates/skills/swarm-scripts/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +81 -1
- package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
- package/templates/workflows/pr-review-status-sweep/content.md +2 -0
- package/dist/claude-sdk-session-t442qz6f.js +0 -39108
- package/dist/cli-0xjkj1j7.js +0 -61
- package/dist/cli-65hjmev5.js +0 -1141
- package/dist/cli-aw1jwp1r.js +0 -14075
- package/dist/cli-h967nb6r.js +0 -6603
- package/dist/cli-vapyk8sw.js +0 -20
- package/dist/cli-ws6rqycm.js +0 -247
- package/dist/pricing-refresh-v1cbr9te.js +0 -27
- package/src/providers/modelsdev-reasoning.json +0 -6453
- package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
- package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
|
@@ -8,348 +8,21 @@ import {
|
|
|
8
8
|
getEnvApiKey,
|
|
9
9
|
normalizeRadiusGatewayUrl,
|
|
10
10
|
registerBundledOAuthFlowLoaders
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-gzyh0j0g.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
exports_external
|
|
17
|
-
} from "./cli-w5e1d1vv.js";
|
|
13
|
+
oauthErrorHtml,
|
|
14
|
+
oauthSuccessHtml
|
|
15
|
+
} from "./cli-rykfmgwg.js";
|
|
18
16
|
import {
|
|
19
17
|
getProviderEnvValue
|
|
20
18
|
} from "./cli-btbzvq95.js";
|
|
21
19
|
import {
|
|
22
20
|
InMemoryCredentialStore
|
|
23
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-ykk5jaax.js";
|
|
24
22
|
import {
|
|
25
23
|
__require
|
|
26
24
|
} from "./cli-bc05qbss.js";
|
|
27
25
|
|
|
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
26
|
// src/utils/internal-ai/models.ts
|
|
354
27
|
var DEFAULT_MODEL = {
|
|
355
28
|
openrouter: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
@@ -368,105 +41,135 @@ function parseModelStr(modelStr) {
|
|
|
368
41
|
return [modelStr.slice(0, idx), modelStr.slice(idx + 1)];
|
|
369
42
|
}
|
|
370
43
|
|
|
371
|
-
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/
|
|
372
|
-
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
44
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/callback-server.js
|
|
45
|
+
import { createServer } from "node:http";
|
|
46
|
+
function sendPage(response, status, html) {
|
|
47
|
+
response.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
48
|
+
response.end(html);
|
|
49
|
+
}
|
|
50
|
+
async function startOAuthCallbackServer(options) {
|
|
51
|
+
const { providerName, signal } = options;
|
|
52
|
+
if (signal?.aborted)
|
|
53
|
+
throw new Error("Login cancelled");
|
|
54
|
+
let resolveWait = () => {};
|
|
55
|
+
let rejectWait = () => {};
|
|
56
|
+
const waitPromise = new Promise((resolve, reject) => {
|
|
57
|
+
resolveWait = resolve;
|
|
58
|
+
rejectWait = reject;
|
|
59
|
+
});
|
|
60
|
+
waitPromise.catch(() => {
|
|
61
|
+
return;
|
|
461
62
|
});
|
|
63
|
+
let claimed = false;
|
|
64
|
+
let settled = false;
|
|
65
|
+
let timer;
|
|
66
|
+
const onAbort = () => finish({ error: new Error("Login cancelled") });
|
|
67
|
+
const finish = (result) => {
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
if (timer)
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
signal?.removeEventListener("abort", onAbort);
|
|
74
|
+
if ("error" in result)
|
|
75
|
+
rejectWait(result.error);
|
|
76
|
+
else
|
|
77
|
+
resolveWait(result.value);
|
|
78
|
+
};
|
|
79
|
+
const server = createServer((request, response) => {
|
|
80
|
+
(async () => {
|
|
81
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
82
|
+
if (request.method !== "GET" || url.pathname !== options.path) {
|
|
83
|
+
sendPage(response, 404, oauthErrorHtml("Callback route not found."));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (options.state !== undefined && url.searchParams.get("state") !== options.state) {
|
|
87
|
+
sendPage(response, 400, oauthErrorHtml("State mismatch."));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (claimed || settled) {
|
|
91
|
+
sendPage(response, 409, oauthErrorHtml("This sign-in has already been handled."));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const error = url.searchParams.get("error");
|
|
95
|
+
if (error) {
|
|
96
|
+
const description = url.searchParams.get("error_description") ?? error;
|
|
97
|
+
sendPage(response, 400, oauthErrorHtml(`${providerName} authorization failed.`, description));
|
|
98
|
+
finish({ error: new Error(`${providerName} authorization failed: ${description}`) });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const code = url.searchParams.get("code");
|
|
102
|
+
if (!code) {
|
|
103
|
+
sendPage(response, 400, oauthErrorHtml("Missing authorization code."));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
claimed = true;
|
|
107
|
+
try {
|
|
108
|
+
const value = await options.complete(code);
|
|
109
|
+
sendPage(response, 200, oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`));
|
|
110
|
+
finish({ value });
|
|
111
|
+
} catch (error2) {
|
|
112
|
+
const failure = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
113
|
+
sendPage(response, 502, oauthErrorHtml(`${providerName} sign-in failed.`, failure.message));
|
|
114
|
+
finish({ error: failure });
|
|
115
|
+
}
|
|
116
|
+
})();
|
|
117
|
+
});
|
|
118
|
+
await new Promise((resolve, reject) => {
|
|
119
|
+
server.once("error", reject);
|
|
120
|
+
server.listen(options.port, options.host, () => {
|
|
121
|
+
server.off("error", reject);
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
const address = server.address();
|
|
126
|
+
if (!address || typeof address === "string") {
|
|
127
|
+
server.close();
|
|
128
|
+
throw new Error("OAuth callback server did not bind to TCP");
|
|
129
|
+
}
|
|
130
|
+
server.on("error", (error) => finish({ error }));
|
|
131
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
132
|
+
if (options.timeoutMs !== undefined) {
|
|
133
|
+
timer = setTimeout(() => finish({ error: new Error(`${providerName} sign-in timed out`) }), options.timeoutMs);
|
|
134
|
+
}
|
|
135
|
+
const redirectHost = options.redirectHost ?? options.host;
|
|
136
|
+
return {
|
|
137
|
+
redirectUri: `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${options.path}`,
|
|
138
|
+
wait: () => waitPromise,
|
|
139
|
+
cancel: () => {
|
|
140
|
+
if (!claimed)
|
|
141
|
+
finish({ value: undefined });
|
|
142
|
+
},
|
|
143
|
+
close: () => {
|
|
144
|
+
finish({ error: new Error("OAuth callback server closed") });
|
|
145
|
+
server.close();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
462
148
|
}
|
|
463
|
-
function
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
149
|
+
async function waitForCallbackOrManualInput(interaction, callback, prompt) {
|
|
150
|
+
const manualAbort = new AbortController;
|
|
151
|
+
let manualError;
|
|
152
|
+
const manual = interaction.prompt({ type: "manual_code", ...prompt, signal: manualAbort.signal }).then((input) => {
|
|
153
|
+
callback?.cancel();
|
|
154
|
+
return input;
|
|
155
|
+
}).catch((error) => {
|
|
156
|
+
manualError = error instanceof Error ? error : new Error(String(error));
|
|
157
|
+
callback?.cancel();
|
|
158
|
+
return;
|
|
469
159
|
});
|
|
160
|
+
try {
|
|
161
|
+
const value = await callback?.wait();
|
|
162
|
+
if (manualError)
|
|
163
|
+
throw manualError;
|
|
164
|
+
if (value !== undefined)
|
|
165
|
+
return { type: "callback", value };
|
|
166
|
+
const input = await manual;
|
|
167
|
+
if (manualError)
|
|
168
|
+
throw manualError;
|
|
169
|
+
return { type: "manual", input: input ?? "" };
|
|
170
|
+
} finally {
|
|
171
|
+
manualAbort.abort();
|
|
172
|
+
}
|
|
470
173
|
}
|
|
471
174
|
|
|
472
175
|
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/pkce.js
|
|
@@ -489,8 +192,6 @@ async function generatePKCE() {
|
|
|
489
192
|
}
|
|
490
193
|
|
|
491
194
|
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js
|
|
492
|
-
var nodeApis = null;
|
|
493
|
-
var nodeApisPromise = null;
|
|
494
195
|
var decode = (s) => atob(s);
|
|
495
196
|
var CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
496
197
|
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
@@ -500,20 +201,6 @@ var CALLBACK_PORT = 53692;
|
|
|
500
201
|
var CALLBACK_PATH = "/callback";
|
|
501
202
|
var REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
502
203
|
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
204
|
function parseAuthorizationInput(input) {
|
|
518
205
|
const value = input.trim();
|
|
519
206
|
if (!value)
|
|
@@ -556,68 +243,6 @@ function formatErrorDetails(error) {
|
|
|
556
243
|
}
|
|
557
244
|
return String(error);
|
|
558
245
|
}
|
|
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
246
|
async function postJson(url, body, signal) {
|
|
622
247
|
const response = await fetch(url, {
|
|
623
248
|
method: "POST",
|
|
@@ -658,21 +283,22 @@ async function exchangeAuthorizationCode(code, state, verifier, redirectUri, sig
|
|
|
658
283
|
type: "oauth",
|
|
659
284
|
refresh: tokenData.refresh_token,
|
|
660
285
|
access: tokenData.access_token,
|
|
661
|
-
expires: Date.now() + tokenData.expires_in * 1000 -
|
|
286
|
+
expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000
|
|
662
287
|
};
|
|
663
288
|
}
|
|
664
289
|
async function loginAnthropic(interaction) {
|
|
665
290
|
const { verifier, challenge } = await generatePKCE();
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
291
|
+
const callback = await startOAuthCallbackServer({
|
|
292
|
+
providerName: "Anthropic",
|
|
293
|
+
host: CALLBACK_HOST,
|
|
294
|
+
port: CALLBACK_PORT,
|
|
295
|
+
path: CALLBACK_PATH,
|
|
296
|
+
state: verifier,
|
|
297
|
+
complete: async (code) => code,
|
|
298
|
+
signal: interaction.signal
|
|
299
|
+
}).catch(() => {
|
|
300
|
+
return;
|
|
301
|
+
});
|
|
676
302
|
try {
|
|
677
303
|
const authParams = new URLSearchParams({
|
|
678
304
|
code: "true",
|
|
@@ -689,53 +315,27 @@ async function loginAnthropic(interaction) {
|
|
|
689
315
|
url: `${AUTHORIZE_URL}?${authParams.toString()}`,
|
|
690
316
|
instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here."
|
|
691
317
|
});
|
|
692
|
-
const
|
|
693
|
-
type: "manual_code",
|
|
318
|
+
const result = await waitForCallbackOrManualInput(interaction, callback, {
|
|
694
319
|
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();
|
|
320
|
+
placeholder: REDIRECT_URI
|
|
703
321
|
});
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
} else if (manualInput) {
|
|
711
|
-
const parsed = parseAuthorizationInput(manualInput);
|
|
322
|
+
let code;
|
|
323
|
+
let state = verifier;
|
|
324
|
+
if (result.type === "callback") {
|
|
325
|
+
code = result.value;
|
|
326
|
+
} else {
|
|
327
|
+
const parsed = parseAuthorizationInput(result.input);
|
|
712
328
|
if (parsed.state && parsed.state !== verifier)
|
|
713
329
|
throw new Error("OAuth state mismatch");
|
|
714
330
|
code = parsed.code;
|
|
715
331
|
state = parsed.state ?? verifier;
|
|
716
332
|
}
|
|
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
333
|
if (!code)
|
|
730
334
|
throw new Error("Missing authorization code");
|
|
731
|
-
if (!state)
|
|
732
|
-
throw new Error("Missing OAuth state");
|
|
733
335
|
interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
|
|
734
|
-
return exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI, interaction.signal);
|
|
336
|
+
return await exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI, interaction.signal);
|
|
735
337
|
} finally {
|
|
736
|
-
|
|
737
|
-
manualAbort.abort();
|
|
738
|
-
server.server.close();
|
|
338
|
+
callback?.close();
|
|
739
339
|
}
|
|
740
340
|
}
|
|
741
341
|
async function refreshAnthropicToken(refreshToken, signal) {
|
|
@@ -759,7 +359,7 @@ async function refreshAnthropicToken(refreshToken, signal) {
|
|
|
759
359
|
type: "oauth",
|
|
760
360
|
refresh: data.refresh_token,
|
|
761
361
|
access: data.access_token,
|
|
762
|
-
expires: Date.now() + data.expires_in * 1000 -
|
|
362
|
+
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000
|
|
763
363
|
};
|
|
764
364
|
}
|
|
765
365
|
var anthropicOAuth = {
|
|
@@ -1577,22 +1177,258 @@ var metaOAuth = {
|
|
|
1577
1177
|
}
|
|
1578
1178
|
};
|
|
1579
1179
|
|
|
1180
|
+
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-chatgpt.js
|
|
1181
|
+
import { randomBytes } from "node:crypto";
|
|
1182
|
+
import { createServer as createServer2 } from "node:http";
|
|
1183
|
+
var DYNAMIC_CLIENT_ID = "dynamic_agent_client";
|
|
1184
|
+
var AGENT_NAME_HINT = "Pi";
|
|
1185
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1186
|
+
var AUTHORIZE_URL2 = "https://auth.openai.com/api/accounts/authorize";
|
|
1187
|
+
var TOKEN_URL2 = "https://auth.openai.com/api/accounts/oauth/token";
|
|
1188
|
+
var RESOURCE = "https://api.openai.com/v1";
|
|
1189
|
+
var CALLBACK_HOST2 = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
1190
|
+
var CALLBACK_PORT2 = 1455;
|
|
1191
|
+
var CALLBACK_PATH2 = "/auth/callback";
|
|
1192
|
+
var REDIRECT_URI2 = `http://127.0.0.1:${CALLBACK_PORT2}${CALLBACK_PATH2}`;
|
|
1193
|
+
var DIRECT_TOKEN_SCOPE = "chatgpt.tokens.use.direct";
|
|
1194
|
+
var SCOPE = `openid profile email offline_access resource.invoke ${DIRECT_TOKEN_SCOPE}`;
|
|
1195
|
+
var EXPIRY_MARGIN_MS = 3 * 60 * 1000;
|
|
1196
|
+
function randomValue() {
|
|
1197
|
+
return randomBytes(32).toString("base64url");
|
|
1198
|
+
}
|
|
1199
|
+
function authorizationResultFromCallback(url, expectedState) {
|
|
1200
|
+
const code = url.searchParams.get("code");
|
|
1201
|
+
if (!code)
|
|
1202
|
+
throw new Error("Missing authorization code");
|
|
1203
|
+
const state = url.searchParams.get("state");
|
|
1204
|
+
if (!state)
|
|
1205
|
+
throw new Error("Missing OAuth state");
|
|
1206
|
+
if (state !== expectedState)
|
|
1207
|
+
throw new Error("OAuth state mismatch");
|
|
1208
|
+
const clientId = url.searchParams.get("client_id")?.trim();
|
|
1209
|
+
if (!clientId)
|
|
1210
|
+
throw new Error("OpenAI OAuth registration callback did not contain an issued client ID");
|
|
1211
|
+
return { code, clientId };
|
|
1212
|
+
}
|
|
1213
|
+
function authorizationResultFromManualInput(input, expectedState) {
|
|
1214
|
+
let url;
|
|
1215
|
+
try {
|
|
1216
|
+
url = new URL(input.trim());
|
|
1217
|
+
} catch {
|
|
1218
|
+
throw new Error("Paste the full callback URL from the browser");
|
|
1219
|
+
}
|
|
1220
|
+
const expected = new URL(REDIRECT_URI2);
|
|
1221
|
+
if (url.origin !== expected.origin || url.pathname !== expected.pathname) {
|
|
1222
|
+
throw new Error(`The pasted callback URL must start with ${REDIRECT_URI2}`);
|
|
1223
|
+
}
|
|
1224
|
+
const error = url.searchParams.get("error");
|
|
1225
|
+
if (error)
|
|
1226
|
+
throw new Error(`ChatGPT authorization failed: ${error}`);
|
|
1227
|
+
return authorizationResultFromCallback(url, expectedState);
|
|
1228
|
+
}
|
|
1229
|
+
function sendHtml(response, status, body) {
|
|
1230
|
+
response.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
1231
|
+
response.end(body);
|
|
1232
|
+
}
|
|
1233
|
+
function startCallbackServer(expectedState) {
|
|
1234
|
+
return new Promise((resolve, reject) => {
|
|
1235
|
+
let resolveResult;
|
|
1236
|
+
let rejectResult;
|
|
1237
|
+
const result = new Promise((resolveAuthorization, rejectAuthorization) => {
|
|
1238
|
+
resolveResult = resolveAuthorization;
|
|
1239
|
+
rejectResult = rejectAuthorization;
|
|
1240
|
+
});
|
|
1241
|
+
const server = createServer2((request, response) => {
|
|
1242
|
+
try {
|
|
1243
|
+
const url = new URL(request.url || "", REDIRECT_URI2);
|
|
1244
|
+
if (url.pathname !== CALLBACK_PATH2) {
|
|
1245
|
+
sendHtml(response, 404, oauthErrorHtml("Callback route not found."));
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const error = url.searchParams.get("error");
|
|
1249
|
+
if (error) {
|
|
1250
|
+
sendHtml(response, 400, oauthErrorHtml("ChatGPT was not connected.", `Error: ${error}`));
|
|
1251
|
+
rejectResult(new Error(`ChatGPT authorization failed: ${error}`));
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
let authorizationResult;
|
|
1255
|
+
try {
|
|
1256
|
+
authorizationResult = authorizationResultFromCallback(url, expectedState);
|
|
1257
|
+
} catch (error2) {
|
|
1258
|
+
const message = error2 instanceof Error ? error2.message : "Invalid callback";
|
|
1259
|
+
sendHtml(response, 400, oauthErrorHtml(message));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
sendHtml(response, 200, oauthSuccessHtml("ChatGPT authentication completed. You can close this window."));
|
|
1263
|
+
resolveResult(authorizationResult);
|
|
1264
|
+
} catch {
|
|
1265
|
+
sendHtml(response, 500, oauthErrorHtml("Internal error while processing the callback."));
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
server.once("error", reject);
|
|
1269
|
+
server.listen(CALLBACK_PORT2, CALLBACK_HOST2, () => {
|
|
1270
|
+
server.removeListener("error", reject);
|
|
1271
|
+
server.on("error", rejectResult);
|
|
1272
|
+
resolve({ server, result });
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
async function requestToken(body, signal) {
|
|
1277
|
+
const response = await fetch(TOKEN_URL2, {
|
|
1278
|
+
method: "POST",
|
|
1279
|
+
headers: {
|
|
1280
|
+
accept: "application/json",
|
|
1281
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
1282
|
+
},
|
|
1283
|
+
body,
|
|
1284
|
+
signal
|
|
1285
|
+
});
|
|
1286
|
+
if (!response.ok) {
|
|
1287
|
+
const responseBody = await response.text().catch(() => "");
|
|
1288
|
+
throw new Error(`OpenAI OAuth token request failed (${response.status}): ${responseBody || response.statusText}`);
|
|
1289
|
+
}
|
|
1290
|
+
const data = await response.json();
|
|
1291
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
1292
|
+
throw new Error("OpenAI OAuth token response must be an object");
|
|
1293
|
+
}
|
|
1294
|
+
return data;
|
|
1295
|
+
}
|
|
1296
|
+
function requireTokenString(value, field) {
|
|
1297
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1298
|
+
throw new Error(`OpenAI OAuth token response has invalid ${field}`);
|
|
1299
|
+
}
|
|
1300
|
+
return value;
|
|
1301
|
+
}
|
|
1302
|
+
function credentialFromTokenResponse(token, clientId) {
|
|
1303
|
+
const access = requireTokenString(token.access_token, "access_token");
|
|
1304
|
+
const refresh = requireTokenString(token.refresh_token, "refresh_token");
|
|
1305
|
+
const scope = requireTokenString(token.scope, "scope");
|
|
1306
|
+
if (typeof token.expires_in !== "number" || !Number.isFinite(token.expires_in) || token.expires_in <= 0) {
|
|
1307
|
+
throw new Error("OpenAI OAuth token response has invalid expires_in");
|
|
1308
|
+
}
|
|
1309
|
+
const scopes = scope.trim().split(/\s+/).filter(Boolean);
|
|
1310
|
+
if (!scopes.includes(DIRECT_TOKEN_SCOPE)) {
|
|
1311
|
+
throw new Error(`OpenAI OAuth grant did not include ${DIRECT_TOKEN_SCOPE}`);
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
type: "oauth",
|
|
1315
|
+
access,
|
|
1316
|
+
refresh,
|
|
1317
|
+
expires: Date.now() + token.expires_in * 1000 - EXPIRY_MARGIN_MS,
|
|
1318
|
+
clientId,
|
|
1319
|
+
scopes
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
async function exchangeAuthorizationCode2(code, verifier, clientId, signal) {
|
|
1323
|
+
const token = await requestToken(new URLSearchParams({
|
|
1324
|
+
grant_type: "authorization_code",
|
|
1325
|
+
client_id: clientId,
|
|
1326
|
+
code,
|
|
1327
|
+
code_verifier: verifier,
|
|
1328
|
+
redirect_uri: REDIRECT_URI2,
|
|
1329
|
+
resource: RESOURCE
|
|
1330
|
+
}), signal);
|
|
1331
|
+
if (typeof token.id_token !== "string" || token.id_token.trim().length === 0) {
|
|
1332
|
+
throw new Error("OpenAI OAuth token response did not contain an ID token");
|
|
1333
|
+
}
|
|
1334
|
+
return credentialFromTokenResponse(token, clientId);
|
|
1335
|
+
}
|
|
1336
|
+
async function refreshAccessToken(credential, signal) {
|
|
1337
|
+
const clientId = credential.clientId;
|
|
1338
|
+
if (typeof clientId !== "string" || clientId.trim().length === 0) {
|
|
1339
|
+
throw new Error("Stored OpenAI OAuth credential does not contain an issued client ID; reconnect ChatGPT");
|
|
1340
|
+
}
|
|
1341
|
+
const token = await requestToken(new URLSearchParams({
|
|
1342
|
+
grant_type: "refresh_token",
|
|
1343
|
+
client_id: clientId,
|
|
1344
|
+
refresh_token: credential.refresh,
|
|
1345
|
+
resource: RESOURCE
|
|
1346
|
+
}), signal);
|
|
1347
|
+
return credentialFromTokenResponse(token, clientId);
|
|
1348
|
+
}
|
|
1349
|
+
function agentHostId(deviceId) {
|
|
1350
|
+
if (!deviceId || !UUID_PATTERN.test(deviceId)) {
|
|
1351
|
+
throw new Error("Sign in with ChatGPT requires a device ID (UUID) for this installation");
|
|
1352
|
+
}
|
|
1353
|
+
return `urn:uuid:${deviceId.toLowerCase()}`;
|
|
1354
|
+
}
|
|
1355
|
+
async function loginOpenAIChatGPT(interaction, options) {
|
|
1356
|
+
const hostId = agentHostId(options?.getDeviceId?.());
|
|
1357
|
+
const { verifier, challenge } = await generatePKCE();
|
|
1358
|
+
const state = randomValue();
|
|
1359
|
+
const nonce = randomValue();
|
|
1360
|
+
let callback;
|
|
1361
|
+
try {
|
|
1362
|
+
callback = await startCallbackServer(state);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
interaction.notify({
|
|
1365
|
+
type: "info",
|
|
1366
|
+
message: `Could not listen on ${REDIRECT_URI2}; paste the final redirect URL to continue. ${error instanceof Error ? error.message : String(error)}`
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
const authorizationUrl = new URL(AUTHORIZE_URL2);
|
|
1370
|
+
authorizationUrl.search = new URLSearchParams({
|
|
1371
|
+
client_id: DYNAMIC_CLIENT_ID,
|
|
1372
|
+
agent_name_hint: AGENT_NAME_HINT,
|
|
1373
|
+
ext_agent_host_id: hostId,
|
|
1374
|
+
response_type: "code",
|
|
1375
|
+
redirect_uri: REDIRECT_URI2,
|
|
1376
|
+
resource: RESOURCE,
|
|
1377
|
+
scope: SCOPE,
|
|
1378
|
+
state,
|
|
1379
|
+
code_challenge: challenge,
|
|
1380
|
+
code_challenge_method: "S256",
|
|
1381
|
+
nonce
|
|
1382
|
+
}).toString();
|
|
1383
|
+
interaction.notify({
|
|
1384
|
+
type: "auth_url",
|
|
1385
|
+
url: authorizationUrl.toString(),
|
|
1386
|
+
instructions: "Complete sign-in in your browser. If the callback does not complete, paste the final redirect URL here."
|
|
1387
|
+
});
|
|
1388
|
+
const manualAbort = new AbortController;
|
|
1389
|
+
const manualCode = interaction.prompt({
|
|
1390
|
+
type: "manual_code",
|
|
1391
|
+
message: "Complete login in your browser, or paste the final redirect URL here:",
|
|
1392
|
+
placeholder: REDIRECT_URI2,
|
|
1393
|
+
signal: AbortSignal.any([manualAbort.signal, interaction.signal])
|
|
1394
|
+
}).then((input) => authorizationResultFromManualInput(input, state));
|
|
1395
|
+
try {
|
|
1396
|
+
const result = await (callback ? Promise.race([callback.result, manualCode]) : manualCode);
|
|
1397
|
+
interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
|
|
1398
|
+
return await exchangeAuthorizationCode2(result.code, verifier, result.clientId, interaction.signal);
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
if (interaction.signal.aborted)
|
|
1401
|
+
throw new Error("Login cancelled");
|
|
1402
|
+
throw error;
|
|
1403
|
+
} finally {
|
|
1404
|
+
manualAbort.abort();
|
|
1405
|
+
callback?.server.close();
|
|
1406
|
+
callback?.server.closeAllConnections();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
var openaiChatGPTOAuth = {
|
|
1410
|
+
name: "OpenAI (ChatGPT subscription)",
|
|
1411
|
+
isSubscription: true,
|
|
1412
|
+
loginLabel: "Sign in with ChatGPT",
|
|
1413
|
+
login: loginOpenAIChatGPT,
|
|
1414
|
+
refresh: refreshAccessToken,
|
|
1415
|
+
async toAuth(credential) {
|
|
1416
|
+
return { apiKey: credential.access };
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1580
1420
|
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js
|
|
1581
1421
|
var _randomBytes = null;
|
|
1582
|
-
var _http = null;
|
|
1583
1422
|
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
1584
1423
|
import("node:crypto").then((m) => {
|
|
1585
1424
|
_randomBytes = m.randomBytes;
|
|
1586
1425
|
});
|
|
1587
|
-
import("node:http").then((m) => {
|
|
1588
|
-
_http = m;
|
|
1589
|
-
});
|
|
1590
1426
|
}
|
|
1591
1427
|
var CLIENT_ID5 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1592
1428
|
var AUTH_BASE_URL = "https://auth.openai.com";
|
|
1593
|
-
var
|
|
1594
|
-
var
|
|
1595
|
-
var
|
|
1429
|
+
var AUTHORIZE_URL3 = `${AUTH_BASE_URL}/oauth/authorize`;
|
|
1430
|
+
var TOKEN_URL3 = `${AUTH_BASE_URL}/oauth/token`;
|
|
1431
|
+
var REDIRECT_URI3 = "http://localhost:1455/auth/callback";
|
|
1596
1432
|
var DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;
|
|
1597
1433
|
var DEVICE_TOKEN_URL2 = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;
|
|
1598
1434
|
var DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;
|
|
@@ -1600,7 +1436,7 @@ var DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;
|
|
|
1600
1436
|
var DEVICE_CODE_TIMEOUT_SECONDS2 = 900;
|
|
1601
1437
|
var OPENAI_CODEX_BROWSER_LOGIN_METHOD = "browser";
|
|
1602
1438
|
var OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = "device_code";
|
|
1603
|
-
var
|
|
1439
|
+
var SCOPE2 = "openid profile email offline_access";
|
|
1604
1440
|
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
1605
1441
|
function getCallbackHost() {
|
|
1606
1442
|
return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
@@ -1673,8 +1509,8 @@ async function readTokenResponse(response, operation) {
|
|
|
1673
1509
|
expires: Date.now() + json.expires_in * 1000
|
|
1674
1510
|
};
|
|
1675
1511
|
}
|
|
1676
|
-
async function
|
|
1677
|
-
const response = await fetchWithLoginCancellation(
|
|
1512
|
+
async function exchangeAuthorizationCode3(code, verifier, redirectUri, signal) {
|
|
1513
|
+
const response = await fetchWithLoginCancellation(TOKEN_URL3, {
|
|
1678
1514
|
method: "POST",
|
|
1679
1515
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1680
1516
|
body: new URLSearchParams({
|
|
@@ -1688,10 +1524,10 @@ async function exchangeAuthorizationCode2(code, verifier, redirectUri, signal) {
|
|
|
1688
1524
|
});
|
|
1689
1525
|
return readTokenResponse(response, "exchange");
|
|
1690
1526
|
}
|
|
1691
|
-
async function
|
|
1527
|
+
async function refreshAccessToken2(refreshToken2, signal) {
|
|
1692
1528
|
let response;
|
|
1693
1529
|
try {
|
|
1694
|
-
response = await fetch(
|
|
1530
|
+
response = await fetch(TOKEN_URL3, {
|
|
1695
1531
|
method: "POST",
|
|
1696
1532
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1697
1533
|
body: new URLSearchParams({
|
|
@@ -1787,11 +1623,11 @@ async function pollOpenAICodexDeviceAuth(device, signal) {
|
|
|
1787
1623
|
async function createAuthorizationFlow(originator = "pi") {
|
|
1788
1624
|
const { verifier, challenge } = await generatePKCE();
|
|
1789
1625
|
const state = createState();
|
|
1790
|
-
const url = new URL(
|
|
1626
|
+
const url = new URL(AUTHORIZE_URL3);
|
|
1791
1627
|
url.searchParams.set("response_type", "code");
|
|
1792
1628
|
url.searchParams.set("client_id", CLIENT_ID5);
|
|
1793
|
-
url.searchParams.set("redirect_uri",
|
|
1794
|
-
url.searchParams.set("scope",
|
|
1629
|
+
url.searchParams.set("redirect_uri", REDIRECT_URI3);
|
|
1630
|
+
url.searchParams.set("scope", SCOPE2);
|
|
1795
1631
|
url.searchParams.set("code_challenge", challenge);
|
|
1796
1632
|
url.searchParams.set("code_challenge_method", "S256");
|
|
1797
1633
|
url.searchParams.set("state", state);
|
|
@@ -1800,75 +1636,6 @@ async function createAuthorizationFlow(originator = "pi") {
|
|
|
1800
1636
|
url.searchParams.set("originator", originator);
|
|
1801
1637
|
return { verifier, state, url: url.toString() };
|
|
1802
1638
|
}
|
|
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
1639
|
function getAccountId(accessToken) {
|
|
1873
1640
|
const payload = decodeJwt(accessToken);
|
|
1874
1641
|
const auth = payload?.[JWT_CLAIM_PATH];
|
|
@@ -1889,7 +1656,7 @@ function credentialsFromToken(token) {
|
|
|
1889
1656
|
};
|
|
1890
1657
|
}
|
|
1891
1658
|
async function exchangeAuthorizationCodeForCredentials(code, verifier, redirectUri, signal) {
|
|
1892
|
-
return credentialsFromToken(await
|
|
1659
|
+
return credentialsFromToken(await exchangeAuthorizationCode3(code, verifier, redirectUri, signal));
|
|
1893
1660
|
}
|
|
1894
1661
|
async function loginOpenAICodexDeviceCode(interaction) {
|
|
1895
1662
|
const device = await startOpenAICodexDeviceAuth(interaction.signal);
|
|
@@ -1905,66 +1672,45 @@ async function loginOpenAICodexDeviceCode(interaction) {
|
|
|
1905
1672
|
}
|
|
1906
1673
|
async function loginOpenAICodex(interaction) {
|
|
1907
1674
|
const { verifier, state, url } = await createAuthorizationFlow();
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1675
|
+
const callback = await startOAuthCallbackServer({
|
|
1676
|
+
providerName: "OpenAI",
|
|
1677
|
+
host: getCallbackHost(),
|
|
1678
|
+
port: 1455,
|
|
1679
|
+
path: "/auth/callback",
|
|
1680
|
+
state,
|
|
1681
|
+
complete: async (code) => code,
|
|
1682
|
+
signal: interaction.signal
|
|
1683
|
+
}).catch(() => {
|
|
1684
|
+
return;
|
|
1685
|
+
});
|
|
1917
1686
|
interaction.notify({
|
|
1918
1687
|
type: "auth_url",
|
|
1919
1688
|
url,
|
|
1920
1689
|
instructions: "A browser window should open. Complete login to finish."
|
|
1921
1690
|
});
|
|
1922
1691
|
try {
|
|
1923
|
-
const
|
|
1924
|
-
type: "manual_code",
|
|
1692
|
+
const result = await waitForCallbackOrManualInput(interaction, callback, {
|
|
1925
1693
|
message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
|
|
1926
|
-
placeholder:
|
|
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();
|
|
1694
|
+
placeholder: REDIRECT_URI3
|
|
1934
1695
|
});
|
|
1935
|
-
|
|
1936
|
-
if (
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
} else if (manualCode) {
|
|
1941
|
-
const parsed = parseAuthorizationInput2(manualCode);
|
|
1696
|
+
let code;
|
|
1697
|
+
if (result.type === "callback") {
|
|
1698
|
+
code = result.value;
|
|
1699
|
+
} else {
|
|
1700
|
+
const parsed = parseAuthorizationInput2(result.input);
|
|
1942
1701
|
if (parsed.state && parsed.state !== state)
|
|
1943
1702
|
throw new Error("State mismatch");
|
|
1944
1703
|
code = parsed.code;
|
|
1945
1704
|
}
|
|
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
1705
|
if (!code)
|
|
1958
1706
|
throw new Error("Missing authorization code");
|
|
1959
|
-
return exchangeAuthorizationCodeForCredentials(code, verifier,
|
|
1707
|
+
return await exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI3, interaction.signal);
|
|
1960
1708
|
} finally {
|
|
1961
|
-
|
|
1962
|
-
manualAbort.abort();
|
|
1963
|
-
server.close();
|
|
1709
|
+
callback?.close();
|
|
1964
1710
|
}
|
|
1965
1711
|
}
|
|
1966
1712
|
async function refreshOpenAICodexToken(refreshToken2, signal) {
|
|
1967
|
-
return credentialsFromToken(await
|
|
1713
|
+
return credentialsFromToken(await refreshAccessToken2(refreshToken2, signal));
|
|
1968
1714
|
}
|
|
1969
1715
|
var openaiCodexOAuth = {
|
|
1970
1716
|
name: "OpenAI (ChatGPT Plus/Pro)",
|
|
@@ -1993,20 +1739,13 @@ var openaiCodexOAuth = {
|
|
|
1993
1739
|
};
|
|
1994
1740
|
|
|
1995
1741
|
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.js
|
|
1996
|
-
|
|
1997
|
-
var
|
|
1998
|
-
var TOKEN_URL3 = "https://openrouter.ai/api/v1/auth/keys";
|
|
1742
|
+
var AUTHORIZE_URL4 = "https://openrouter.ai/auth";
|
|
1743
|
+
var TOKEN_URL4 = "https://openrouter.ai/api/v1/auth/keys";
|
|
1999
1744
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
2000
1745
|
var TOKEN_EXCHANGE_TIMEOUT_MS = 30000;
|
|
2001
1746
|
function getCallbackHost2() {
|
|
2002
1747
|
return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
2003
1748
|
}
|
|
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
1749
|
function parseAuthorizationInput3(input) {
|
|
2011
1750
|
const value = input.trim();
|
|
2012
1751
|
if (!value)
|
|
@@ -2033,7 +1772,7 @@ function errorDetail2(body) {
|
|
|
2033
1772
|
}
|
|
2034
1773
|
return;
|
|
2035
1774
|
}
|
|
2036
|
-
async function
|
|
1775
|
+
async function exchangeAuthorizationCode4(code, verifier, signal) {
|
|
2037
1776
|
if (signal.aborted)
|
|
2038
1777
|
throw new Error("Login cancelled");
|
|
2039
1778
|
const controller = new AbortController;
|
|
@@ -2043,7 +1782,7 @@ async function exchangeAuthorizationCode3(code, verifier, signal) {
|
|
|
2043
1782
|
let response;
|
|
2044
1783
|
let body = {};
|
|
2045
1784
|
try {
|
|
2046
|
-
response = await fetch(
|
|
1785
|
+
response = await fetch(TOKEN_URL4, {
|
|
2047
1786
|
method: "POST",
|
|
2048
1787
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
2049
1788
|
body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: "S256" }),
|
|
@@ -2081,153 +1820,45 @@ async function exchangeAuthorizationCode3(code, verifier, signal) {
|
|
|
2081
1820
|
expires: Number.MAX_SAFE_INTEGER
|
|
2082
1821
|
};
|
|
2083
1822
|
}
|
|
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
1823
|
async function loginOpenRouter(interaction) {
|
|
2182
1824
|
const { verifier, challenge } = await generatePKCE();
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
1825
|
+
const callback = await startOAuthCallbackServer({
|
|
1826
|
+
providerName: "OpenRouter",
|
|
1827
|
+
host: getCallbackHost2(),
|
|
1828
|
+
port: 0,
|
|
1829
|
+
path: `/oauth/callback/${crypto.randomUUID()}`,
|
|
1830
|
+
complete: (code) => exchangeAuthorizationCode4(code, verifier, interaction.signal),
|
|
1831
|
+
signal: interaction.signal,
|
|
1832
|
+
timeoutMs: LOGIN_TIMEOUT_MS
|
|
1833
|
+
});
|
|
2188
1834
|
try {
|
|
2189
|
-
const authorizeUrl = new URL(
|
|
1835
|
+
const authorizeUrl = new URL(AUTHORIZE_URL4);
|
|
2190
1836
|
authorizeUrl.search = new URLSearchParams({
|
|
2191
|
-
callback_url: callback.
|
|
1837
|
+
callback_url: callback.redirectUri,
|
|
2192
1838
|
code_challenge: challenge,
|
|
2193
1839
|
code_challenge_method: "S256"
|
|
2194
1840
|
}).toString();
|
|
2195
1841
|
interaction.notify({
|
|
2196
1842
|
type: "progress",
|
|
2197
|
-
message: `Listening for OpenRouter OAuth callback on ${callback.
|
|
1843
|
+
message: `Listening for OpenRouter OAuth callback on ${callback.redirectUri}`
|
|
2198
1844
|
});
|
|
2199
1845
|
interaction.notify({
|
|
2200
1846
|
type: "auth_url",
|
|
2201
1847
|
url: authorizeUrl.toString(),
|
|
2202
1848
|
instructions: "Complete sign-in in your browser. If the browser is on another machine, paste the final redirect URL here."
|
|
2203
1849
|
});
|
|
2204
|
-
const
|
|
2205
|
-
type: "manual_code",
|
|
1850
|
+
const result = await waitForCallbackOrManualInput(interaction, callback, {
|
|
2206
1851
|
message: "Complete sign-in in your browser, or paste the authorization code / redirect URL here:",
|
|
2207
|
-
placeholder: callback.
|
|
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();
|
|
1852
|
+
placeholder: callback.redirectUri
|
|
2215
1853
|
});
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
if (credential)
|
|
2220
|
-
return credential;
|
|
2221
|
-
await manualPromise;
|
|
2222
|
-
if (manualError)
|
|
2223
|
-
throw manualError;
|
|
2224
|
-
const code = manualInput ? parseAuthorizationInput3(manualInput) : undefined;
|
|
1854
|
+
if (result.type === "callback")
|
|
1855
|
+
return result.value;
|
|
1856
|
+
const code = parseAuthorizationInput3(result.input);
|
|
2225
1857
|
if (!code)
|
|
2226
1858
|
throw new Error("Missing authorization code");
|
|
2227
1859
|
interaction.notify({ type: "progress", message: "Exchanging authorization code for an API key..." });
|
|
2228
|
-
return await
|
|
1860
|
+
return await exchangeAuthorizationCode4(code, verifier, interaction.signal);
|
|
2229
1861
|
} finally {
|
|
2230
|
-
manualAbort.abort();
|
|
2231
1862
|
callback.close();
|
|
2232
1863
|
}
|
|
2233
1864
|
}
|
|
@@ -2244,16 +1875,10 @@ var openRouterOAuth = {
|
|
|
2244
1875
|
};
|
|
2245
1876
|
|
|
2246
1877
|
// node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.js
|
|
2247
|
-
var
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
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}`;
|
|
1878
|
+
var CALLBACK_HOST3 = "127.0.0.1";
|
|
1879
|
+
var CALLBACK_PORT3 = 1456;
|
|
1880
|
+
var CALLBACK_PATH3 = "/oauth/callback";
|
|
1881
|
+
var REDIRECT_URI4 = `http://${CALLBACK_HOST3}:${CALLBACK_PORT3}${CALLBACK_PATH3}`;
|
|
2257
1882
|
var TOKEN_EXPIRY_SKEW_MS = 60000;
|
|
2258
1883
|
var LOGIN_METHOD_BROWSER = "browser";
|
|
2259
1884
|
var LOGIN_METHOD_DEVICE_CODE = "device-code";
|
|
@@ -2327,69 +1952,6 @@ async function requestOAuthToken(gateway, body, signal) {
|
|
|
2327
1952
|
scope: data.scope
|
|
2328
1953
|
};
|
|
2329
1954
|
}
|
|
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
1955
|
async function loginWithBrowser(gateway, authorizationEndpoint, interaction) {
|
|
2394
1956
|
const { verifier, challenge } = await generatePKCE();
|
|
2395
1957
|
const state = crypto.randomUUID();
|
|
@@ -2397,37 +1959,41 @@ async function loginWithBrowser(gateway, authorizationEndpoint, interaction) {
|
|
|
2397
1959
|
authorizeUrl.search = new URLSearchParams({
|
|
2398
1960
|
response_type: "code",
|
|
2399
1961
|
client_id: OAUTH_CLIENT_ID,
|
|
2400
|
-
redirect_uri:
|
|
1962
|
+
redirect_uri: REDIRECT_URI4,
|
|
2401
1963
|
scope: OAUTH_SCOPE,
|
|
2402
1964
|
code_challenge: challenge,
|
|
2403
1965
|
code_challenge_method: "S256",
|
|
2404
1966
|
handoff: "url",
|
|
2405
1967
|
state
|
|
2406
1968
|
}).toString();
|
|
2407
|
-
const
|
|
2408
|
-
|
|
1969
|
+
const callback = await startOAuthCallbackServer({
|
|
1970
|
+
providerName: "Radius",
|
|
1971
|
+
host: CALLBACK_HOST3,
|
|
1972
|
+
port: CALLBACK_PORT3,
|
|
1973
|
+
path: CALLBACK_PATH3,
|
|
1974
|
+
state,
|
|
1975
|
+
complete: (code) => requestOAuthToken(gateway, new URLSearchParams({
|
|
1976
|
+
grant_type: "authorization_code",
|
|
1977
|
+
client_id: OAUTH_CLIENT_ID,
|
|
1978
|
+
redirect_uri: REDIRECT_URI4,
|
|
1979
|
+
code,
|
|
1980
|
+
code_verifier: verifier
|
|
1981
|
+
}), interaction.signal),
|
|
1982
|
+
signal: interaction.signal
|
|
1983
|
+
});
|
|
1984
|
+
interaction.notify({ type: "progress", message: `Listening for OAuth callback on ${REDIRECT_URI4}` });
|
|
2409
1985
|
interaction.notify({
|
|
2410
1986
|
type: "auth_url",
|
|
2411
1987
|
url: authorizeUrl.toString(),
|
|
2412
1988
|
instructions: "Continue in your browser."
|
|
2413
1989
|
});
|
|
2414
1990
|
try {
|
|
2415
|
-
const
|
|
2416
|
-
if (!
|
|
2417
|
-
if (interaction.signal.aborted) {
|
|
2418
|
-
throw new Error("Login cancelled");
|
|
2419
|
-
}
|
|
1991
|
+
const credential = await callback.wait();
|
|
1992
|
+
if (!credential)
|
|
2420
1993
|
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);
|
|
1994
|
+
return credential;
|
|
2429
1995
|
} finally {
|
|
2430
|
-
|
|
1996
|
+
callback.close();
|
|
2431
1997
|
}
|
|
2432
1998
|
}
|
|
2433
1999
|
async function requestDeviceAuthorization(gateway, signal) {
|
|
@@ -2719,6 +2285,7 @@ function registerBunOAuthFlows() {
|
|
|
2719
2285
|
registerBundledOAuthFlowLoaders({
|
|
2720
2286
|
anthropic: () => anthropicOAuth,
|
|
2721
2287
|
openaiCodex: () => openaiCodexOAuth,
|
|
2288
|
+
openaiChatGPT: () => openaiChatGPTOAuth,
|
|
2722
2289
|
githubCopilot: () => githubCopilotOAuth,
|
|
2723
2290
|
openrouter: () => openRouterOAuth,
|
|
2724
2291
|
kimiCoding: () => kimiCodingOAuth,
|
|
@@ -2825,4 +2392,4 @@ async function resolveCredential(opts = {}) {
|
|
|
2825
2392
|
return null;
|
|
2826
2393
|
}
|
|
2827
2394
|
|
|
2828
|
-
export { DEFAULT_MODEL, parseModelStr, resolveCredential
|
|
2395
|
+
export { DEFAULT_MODEL, parseModelStr, resolveCredential };
|