@spinabot/brigade 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +18 -4
  2. package/convex/schema.d.ts +6 -6
  3. package/convex/sessions.d.ts +4 -4
  4. package/convex/subagents.d.ts +4 -4
  5. package/dist/agents/agent-loop.d.ts.map +1 -1
  6. package/dist/agents/agent-loop.js +70 -10
  7. package/dist/agents/agent-loop.js.map +1 -1
  8. package/dist/agents/tools/manage-agent-tool.d.ts.map +1 -1
  9. package/dist/agents/tools/manage-agent-tool.js +2 -1
  10. package/dist/agents/tools/manage-agent-tool.js.map +1 -1
  11. package/dist/agents/tools/manage-provider-tool.d.ts.map +1 -1
  12. package/dist/agents/tools/manage-provider-tool.js +2 -1
  13. package/dist/agents/tools/manage-provider-tool.js.map +1 -1
  14. package/dist/agents/tools/org-tool.d.ts +15 -0
  15. package/dist/agents/tools/org-tool.d.ts.map +1 -1
  16. package/dist/agents/tools/org-tool.js +53 -6
  17. package/dist/agents/tools/org-tool.js.map +1 -1
  18. package/dist/agents/tools/registry.d.ts.map +1 -1
  19. package/dist/agents/tools/registry.js +3 -0
  20. package/dist/agents/tools/registry.js.map +1 -1
  21. package/dist/buildstamp.json +1 -1
  22. package/dist/cli/commands/doctor.d.ts.map +1 -1
  23. package/dist/cli/commands/doctor.js +25 -6
  24. package/dist/cli/commands/doctor.js.map +1 -1
  25. package/dist/cli/commands/login.d.ts +27 -0
  26. package/dist/cli/commands/login.d.ts.map +1 -0
  27. package/dist/cli/commands/login.js +142 -0
  28. package/dist/cli/commands/login.js.map +1 -0
  29. package/dist/cli/flows/web-setup.d.ts +2 -2
  30. package/dist/cli/flows/web-setup.js +2 -2
  31. package/dist/cli/program/build-program.d.ts.map +1 -1
  32. package/dist/cli/program/build-program.js +8 -0
  33. package/dist/cli/program/build-program.js.map +1 -1
  34. package/dist/core/auth-bridge.d.ts.map +1 -1
  35. package/dist/core/auth-bridge.js +75 -25
  36. package/dist/core/auth-bridge.js.map +1 -1
  37. package/dist/integrations/cli-login.d.ts +50 -0
  38. package/dist/integrations/cli-login.d.ts.map +1 -0
  39. package/dist/integrations/cli-login.js +114 -0
  40. package/dist/integrations/cli-login.js.map +1 -0
  41. package/dist/integrations/custom-provider.d.ts +21 -0
  42. package/dist/integrations/custom-provider.d.ts.map +1 -0
  43. package/dist/integrations/custom-provider.js +65 -0
  44. package/dist/integrations/custom-provider.js.map +1 -0
  45. package/dist/integrations/provider-discovery.d.ts +30 -0
  46. package/dist/integrations/provider-discovery.d.ts.map +1 -1
  47. package/dist/integrations/provider-discovery.js +155 -0
  48. package/dist/integrations/provider-discovery.js.map +1 -1
  49. package/dist/providers/catalog.d.ts +33 -0
  50. package/dist/providers/catalog.d.ts.map +1 -1
  51. package/dist/providers/catalog.js +91 -5
  52. package/dist/providers/catalog.js.map +1 -1
  53. package/dist/providers/validate-key.d.ts.map +1 -1
  54. package/dist/providers/validate-key.js +20 -6
  55. package/dist/providers/validate-key.js.map +1 -1
  56. package/dist/system-prompt/org/render-org-block.js +2 -2
  57. package/dist/system-prompt/org/render-org-block.js.map +1 -1
  58. package/dist/ui/onboard-storage-mode.js +24 -21
  59. package/dist/ui/onboard-storage-mode.js.map +1 -1
  60. package/dist/ui/onboarding.d.ts +18 -0
  61. package/dist/ui/onboarding.d.ts.map +1 -1
  62. package/dist/ui/onboarding.js +587 -71
  63. package/dist/ui/onboarding.js.map +1 -1
  64. package/package.json +6 -2
  65. package/scripts/assets/brigade-favicon.ico +0 -0
  66. package/scripts/assets/brigade-logo.webp +0 -0
  67. package/scripts/brand-oauth-page.mjs +92 -0
@@ -52,51 +52,101 @@ function readBrigadeCredentials(agentId) {
52
52
  // Fall through to env-backed bootstrap.
53
53
  }
54
54
  for (const profile of Object.values(parsed.profiles ?? {})) {
55
- if (!profile?.provider || profile.type !== "api_key")
55
+ if (!profile?.provider)
56
56
  continue;
57
- const resolvedKey = resolveProfileKey(profile);
58
- if (!resolvedKey)
57
+ if (out[profile.provider] !== undefined)
58
+ continue; // first-wins per provider
59
+ // Subscription credentials (OAuth login / setup-token) pass straight
60
+ // through — Pi's AuthStorage handles {type:"oauth"} (auto-refresh) and
61
+ // detects an `sk-ant-oat…` token to switch to Bearer auth.
62
+ if (profile.type === "oauth" || profile.type === "token") {
63
+ const cred = subscriptionProfileToCredential(profile);
64
+ if (cred)
65
+ out[profile.provider] = cred;
59
66
  continue;
60
- // First-wins per provider — matches Primitive #1's no-cooldown path.
61
- if (out[profile.provider] === undefined) {
62
- out[profile.provider] = { type: "api_key", key: resolvedKey };
63
67
  }
68
+ if (profile.type !== "api_key")
69
+ continue;
70
+ const resolvedKey = resolveProfileKey(profile);
71
+ if (resolvedKey)
72
+ out[profile.provider] = { type: "api_key", key: resolvedKey };
64
73
  }
65
- // Env-backed bootstrap. If a user has ANTHROPIC_API_KEY (etc) exported
66
- // in their shell but never ran `brigade onboard`, Pi's registry would
67
- // hide the provider unless we surface that key here. Profile-stored keys
68
- // take precedence — env is only consulted when a provider has no profile
69
- // entry.
74
+ // Env-backed bootstrap. If a user has ANTHROPIC_API_KEY (or the OAuth-token
75
+ // fallback ANTHROPIC_OAUTH_TOKEN) exported but never ran `brigade onboard`,
76
+ // surface it so Pi's registry exposes the provider. Profile-stored creds
77
+ // take precedence — env is only consulted when a provider has no profile.
70
78
  for (const provider of PROVIDERS) {
71
- if (!provider.envVar || provider.noAuth)
79
+ if (provider.noAuth)
72
80
  continue;
73
81
  if (out[provider.id] !== undefined)
74
82
  continue;
75
- const apiKey = process.env[provider.envVar];
76
- if (!apiKey)
77
- continue;
78
- out[provider.id] = { type: "api_key", key: apiKey };
83
+ const envNames = [provider.envVar, ...(provider.envVarFallbacks ?? [])];
84
+ for (const name of envNames) {
85
+ if (!name)
86
+ continue;
87
+ const value = process.env[name];
88
+ if (!value)
89
+ continue;
90
+ out[provider.id] = { type: "api_key", key: value };
91
+ break;
92
+ }
79
93
  }
80
94
  return out;
81
95
  }
82
- function resolveProfileKey(profile) {
83
- if (profile.key && profile.key.length > 0)
84
- return profile.key;
85
- const ref = profile.keyRef;
96
+ // Resolve a literal-or-ref secret value (key / access / refresh / token).
97
+ // String refs are the legacy `${ENV_VAR}` form; object refs (BrigadeSecretRef)
98
+ // resolve only env source synchronously — file/exec backends are out of scope
99
+ // for the bridge's sync build.
100
+ function resolveRefValue(value, ref) {
101
+ if (value && value.length > 0)
102
+ return value;
86
103
  if (!ref)
87
104
  return "";
88
- // String form (legacy): `${ENV_VAR}` literal. Matches the regex agent-loop
89
- // uses for the same shape.
90
105
  if (typeof ref === "string") {
91
106
  const m = /^\$\{([A-Z_][A-Z0-9_]*)\}$/.exec(ref);
92
107
  if (m && m[1])
93
108
  return process.env[m[1]] ?? "";
94
109
  return ref;
95
110
  }
96
- // BrigadeSecretRef object form: only env-source is resolvable synchronously.
97
- if (ref.source === "env" && ref.id) {
111
+ if (ref.source === "env" && ref.id)
98
112
  return process.env[ref.id] ?? "";
99
- }
100
113
  return "";
101
114
  }
115
+ function resolveProfileKey(profile) {
116
+ return resolveRefValue(profile.key, profile.keyRef);
117
+ }
118
+ // Map an OAuth-login / setup-token profile to a Pi credential. OAuth →
119
+ // {type:"oauth", access, refresh, expires} (Pi auto-refreshes); token →
120
+ // {type:"api_key", key} so Pi's value-based `sk-ant-oat` Bearer detection
121
+ // fires. Returns null when no secret resolves.
122
+ function subscriptionProfileToCredential(profile) {
123
+ if (profile.type === "oauth") {
124
+ const access = resolveRefValue(profile.access, profile.accessRef);
125
+ if (!access)
126
+ return null;
127
+ const refresh = resolveRefValue(profile.refresh, profile.refreshRef);
128
+ // A durable oauth profile CAN lack `expires` (the Claude/Codex CLI-login
129
+ // path). Coerce a missing/garbage value to 0 so Pi treats the access token
130
+ // as expired and refreshes via the refresh token immediately. Spread
131
+ // `metadata` FIRST so the known oauth fields always win — it carries the
132
+ // extras (Copilot enterprise refresh + availableModelIds) Pi needs.
133
+ const expires = typeof profile.expires === "number" && Number.isFinite(profile.expires)
134
+ ? profile.expires
135
+ : 0;
136
+ return {
137
+ ...(profile.metadata && typeof profile.metadata === "object" ? profile.metadata : {}),
138
+ type: "oauth",
139
+ access,
140
+ refresh: refresh || undefined,
141
+ expires,
142
+ };
143
+ }
144
+ if (profile.type === "token") {
145
+ const token = resolveRefValue(profile.token, profile.tokenRef);
146
+ if (!token)
147
+ return null;
148
+ return { type: "api_key", key: token };
149
+ }
150
+ return null;
151
+ }
102
152
  //# sourceMappingURL=auth-bridge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-bridge.js","sourceRoot":"","sources":["../../src/core/auth-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-bridge.js","sourceRoot":"","sources":["../../src/core/auth-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA8BnD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB,gBAAgB;IACvE,MAAM,WAAW,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,WAGf,CAAC;IACF,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC3C,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;QAC9C,OAAO,OAAO,CAAC,WAAW,CAAC;YACzB,QAAQ,CAAI,MAAyD;gBACnE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBAChE,OAAO,MAAM,CAAC;YAChB,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IACD,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,OAAe;IAC7C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,2EAA2E;IAC3E,0EAA0E;IAC1E,yEAAyE;IACzE,2CAA2C;IAC3C,IAAI,MAAM,GAAqB,EAAE,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,OAAO,CAAgC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,OAAO,EAAE,QAAQ;YAAE,SAAS;QACjC,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,SAAS;YAAE,SAAS,CAAC,0BAA0B;QAC7E,qEAAqE;QACrE,uEAAuE;QACvE,2DAA2D;QAC3D,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzD,MAAM,IAAI,GAAG,+BAA+B,CAAC,OAAO,CAAC,CAAC;YACtD,IAAI,IAAI;gBAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;YACvC,SAAS;QACX,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;YAAE,SAAS;QACzC,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,WAAW;YAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;IACjF,CAAC;IACD,4EAA4E;IAC5E,4EAA4E;IAC5E,yEAAyE;IACzE,0EAA0E;IAC1E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,QAAQ,CAAC,MAAM;YAAE,SAAS;QAC9B,IAAI,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,SAAS;YAAE,SAAS;QAC7C,MAAM,QAAQ,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC;QACxE,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACnD,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0EAA0E;AAC1E,+EAA+E;AAC/E,8EAA8E;AAC9E,+BAA+B;AAC/B,SAAS,eAAe,CACtB,KAAyB,EACzB,GAA6E;IAE7E,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IACrE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAoB;IAC7C,OAAO,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,uEAAuE;AACvE,wEAAwE;AACxE,0EAA0E;AAC1E,+CAA+C;AAC/C,SAAS,+BAA+B,CAAC,OAAoB;IAC3D,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QACrE,yEAAyE;QACzE,2EAA2E;QAC3E,qEAAqE;QACrE,yEAAyE;QACzE,oEAAoE;QACpE,MAAM,OAAO,GACX,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC;YACrE,CAAC,CAAC,OAAO,CAAC,OAAO;YACjB,CAAC,CAAC,CAAC,CAAC;QACR,OAAO;YACL,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACrF,IAAI,EAAE,OAAO;YACb,MAAM;YACN,OAAO,EAAE,OAAO,IAAI,SAAS;YAC7B,OAAO;SACR,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Reuse an already-logged-in CLI's stored credential.
3
+ *
4
+ * Brigade can adopt the token that a vendor's own CLI already minted on this
5
+ * machine — so an operator who has logged into Claude Code or Codex can connect
6
+ * Brigade with no browser flow and no API key. We read the CLI's on-disk
7
+ * credential file directly (file-based only for v1) and hand it back in a normalized
8
+ * shape the onboarding flow persists via the standard profile helpers.
9
+ *
10
+ * Everything here is DEFENSIVE: any missing file, parse failure, or malformed
11
+ * shape returns `null`. We never throw — a failed read just means "no CLI login
12
+ * present" and onboarding falls through to the key / fresh-login path.
13
+ *
14
+ * // TODO: macOS keychain (Claude Code-credentials / Codex Auth) — file-based only for v1.
15
+ */
16
+ /** Normalized credential returned by a CLI-login reader. */
17
+ export type CliLogin = {
18
+ provider: string;
19
+ type: "oauth";
20
+ access: string;
21
+ refresh: string;
22
+ expires?: number;
23
+ accountId?: string;
24
+ } | {
25
+ provider: string;
26
+ type: "token";
27
+ token: string;
28
+ expires?: number;
29
+ };
30
+ /**
31
+ * Read Claude Code's stored login from `~/.claude/.credentials.json`.
32
+ *
33
+ * Shape: `{ claudeAiOauth: { accessToken, refreshToken, expiresAt } }`.
34
+ * When a refresh token is present we return an `oauth` credential (Pi can
35
+ * refresh it); otherwise a `token` credential carrying just the access token.
36
+ * Returns `null` on any missing file / parse error / absent access token.
37
+ */
38
+ export declare function readClaudeCliLogin(): CliLogin | null;
39
+ /**
40
+ * Read Codex's stored login from `${CODEX_HOME || ~/.codex}/auth.json`.
41
+ *
42
+ * Shape: `{ tokens: { access_token, refresh_token, account_id } }`. Codex
43
+ * doesn't store an explicit expiry, so we decode the `exp` claim from the
44
+ * access token JWT (×1000 → epoch-ms). If decoding fails we fall back to the
45
+ * file's mtime + 1 hour — a conservative TTL that triggers a refresh sooner
46
+ * rather than later. Returns `null` on any missing file / parse error / absent
47
+ * access token.
48
+ */
49
+ export declare function readCodexCliLogin(): CliLogin | null;
50
+ //# sourceMappingURL=cli-login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-login.d.ts","sourceRoot":"","sources":["../../src/integrations/cli-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,4DAA4D;AAC5D,MAAM,MAAM,QAAQ,GACjB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1G;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,QAAQ,GAAG,IAAI,CAmBpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,QAAQ,GAAG,IAAI,CAkCnD"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Reuse an already-logged-in CLI's stored credential.
3
+ *
4
+ * Brigade can adopt the token that a vendor's own CLI already minted on this
5
+ * machine — so an operator who has logged into Claude Code or Codex can connect
6
+ * Brigade with no browser flow and no API key. We read the CLI's on-disk
7
+ * credential file directly (file-based only for v1) and hand it back in a normalized
8
+ * shape the onboarding flow persists via the standard profile helpers.
9
+ *
10
+ * Everything here is DEFENSIVE: any missing file, parse failure, or malformed
11
+ * shape returns `null`. We never throw — a failed read just means "no CLI login
12
+ * present" and onboarding falls through to the key / fresh-login path.
13
+ *
14
+ * // TODO: macOS keychain (Claude Code-credentials / Codex Auth) — file-based only for v1.
15
+ */
16
+ import * as fs from "node:fs";
17
+ import { homedir } from "node:os";
18
+ import * as path from "node:path";
19
+ /**
20
+ * Read Claude Code's stored login from `~/.claude/.credentials.json`.
21
+ *
22
+ * Shape: `{ claudeAiOauth: { accessToken, refreshToken, expiresAt } }`.
23
+ * When a refresh token is present we return an `oauth` credential (Pi can
24
+ * refresh it); otherwise a `token` credential carrying just the access token.
25
+ * Returns `null` on any missing file / parse error / absent access token.
26
+ */
27
+ export function readClaudeCliLogin() {
28
+ try {
29
+ const credPath = path.join(homedir(), ".claude", ".credentials.json");
30
+ if (!fs.existsSync(credPath))
31
+ return null;
32
+ const parsed = JSON.parse(fs.readFileSync(credPath, "utf8"));
33
+ const oauth = parsed.claudeAiOauth;
34
+ const access = oauth?.accessToken;
35
+ if (!access)
36
+ return null;
37
+ const refresh = oauth?.refreshToken;
38
+ const expires = oauth?.expiresAt;
39
+ if (refresh) {
40
+ return { provider: "anthropic", type: "oauth", access, refresh, expires };
41
+ }
42
+ return { provider: "anthropic", type: "token", token: access, expires };
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * Read Codex's stored login from `${CODEX_HOME || ~/.codex}/auth.json`.
50
+ *
51
+ * Shape: `{ tokens: { access_token, refresh_token, account_id } }`. Codex
52
+ * doesn't store an explicit expiry, so we decode the `exp` claim from the
53
+ * access token JWT (×1000 → epoch-ms). If decoding fails we fall back to the
54
+ * file's mtime + 1 hour — a conservative TTL that triggers a refresh sooner
55
+ * rather than later. Returns `null` on any missing file / parse error / absent
56
+ * access token.
57
+ */
58
+ export function readCodexCliLogin() {
59
+ try {
60
+ const codexHome = process.env.CODEX_HOME || path.join(homedir(), ".codex");
61
+ const authPath = path.join(codexHome, "auth.json");
62
+ if (!fs.existsSync(authPath))
63
+ return null;
64
+ const parsed = JSON.parse(fs.readFileSync(authPath, "utf8"));
65
+ const tokens = parsed.tokens;
66
+ const access = tokens?.access_token;
67
+ const refresh = tokens?.refresh_token;
68
+ if (!access || !refresh)
69
+ return null;
70
+ let expires = decodeJwtExp(access);
71
+ if (expires === undefined) {
72
+ try {
73
+ expires = fs.statSync(authPath).mtimeMs + 3600_000;
74
+ }
75
+ catch {
76
+ /* mtime unavailable — leave expires undefined */
77
+ }
78
+ }
79
+ const accountId = tokens?.account_id;
80
+ return {
81
+ provider: "openai-codex",
82
+ type: "oauth",
83
+ access,
84
+ refresh,
85
+ expires,
86
+ ...(accountId ? { accountId } : {}),
87
+ };
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Decode the `exp` claim (seconds) of a JWT and return it as epoch-ms, or
95
+ * `undefined` if the token isn't a parseable three-part JWT with a numeric
96
+ * `exp`. Base64url-decodes the middle (payload) segment.
97
+ */
98
+ function decodeJwtExp(jwt) {
99
+ try {
100
+ const parts = jwt.split(".");
101
+ if (parts.length !== 3)
102
+ return undefined;
103
+ const payloadJson = Buffer.from(parts[1], "base64url").toString("utf8");
104
+ const payload = JSON.parse(payloadJson);
105
+ if (typeof payload.exp === "number" && Number.isFinite(payload.exp)) {
106
+ return payload.exp * 1000;
107
+ }
108
+ return undefined;
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ }
114
+ //# sourceMappingURL=cli-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-login.js","sourceRoot":"","sources":["../../src/integrations/cli-login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Custom (catalog-defined) provider registration.
3
+ *
4
+ * Some providers ship a key + a known Anthropic-compatible (or OpenAI-
5
+ * compatible) endpoint we already know from the catalog — GLM, Kimi, Qwen,
6
+ * MiniMax, DeepSeek. Pi-AI doesn't bundle these as built-in providers, so we
7
+ * register them dynamically via the `~/.brigade/models.json` mechanism, the
8
+ * same way Ollama is registered. Each catalog model id becomes a Pi model
9
+ * routed through the provider's `baseUrl` + `api`.
10
+ *
11
+ * We MERGE rather than overwrite — the user (or other providers) may have
12
+ * existing entries in the file we shouldn't clobber.
13
+ */
14
+ export declare function writeCustomProviderToModelsJson(modelsJsonPath: string, p: {
15
+ id: string;
16
+ baseUrl: string;
17
+ api: "openai-completions" | "anthropic-messages";
18
+ apiKey: string;
19
+ models: string[];
20
+ }): Promise<void>;
21
+ //# sourceMappingURL=custom-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-provider.d.ts","sourceRoot":"","sources":["../../src/integrations/custom-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,wBAAsB,+BAA+B,CACpD,cAAc,EAAE,MAAM,EACtB,CAAC,EAAE;IACF,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB,GACC,OAAO,CAAC,IAAI,CAAC,CAgDf"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Custom (catalog-defined) provider registration.
3
+ *
4
+ * Some providers ship a key + a known Anthropic-compatible (or OpenAI-
5
+ * compatible) endpoint we already know from the catalog — GLM, Kimi, Qwen,
6
+ * MiniMax, DeepSeek. Pi-AI doesn't bundle these as built-in providers, so we
7
+ * register them dynamically via the `~/.brigade/models.json` mechanism, the
8
+ * same way Ollama is registered. Each catalog model id becomes a Pi model
9
+ * routed through the provider's `baseUrl` + `api`.
10
+ *
11
+ * We MERGE rather than overwrite — the user (or other providers) may have
12
+ * existing entries in the file we shouldn't clobber.
13
+ */
14
+ import * as fs from "node:fs/promises";
15
+ import path from "node:path";
16
+ import { tryGetRuntimeContext } from "../storage/runtime-context.js";
17
+ export async function writeCustomProviderToModelsJson(modelsJsonPath, p) {
18
+ let existing = { providers: {} };
19
+ try {
20
+ const raw = await fs.readFile(modelsJsonPath, "utf8");
21
+ existing = JSON.parse(raw);
22
+ if (!existing.providers)
23
+ existing.providers = {};
24
+ }
25
+ catch {
26
+ // File missing or unparseable — start fresh. Pi treats an absent file as no config.
27
+ }
28
+ existing.providers[p.id] = {
29
+ baseUrl: p.baseUrl,
30
+ api: p.api,
31
+ apiKey: p.apiKey,
32
+ models: p.models.map((id) => ({ id, name: id })),
33
+ };
34
+ // In convex mode resolveModelsPath routes to the OS cache dir, which may
35
+ // not exist yet on a fresh machine — a bare write would ENOENT. Filesystem
36
+ // mode: ~/.brigade always exists by this point, so the mkdir is a no-op.
37
+ await fs.mkdir(path.dirname(modelsJsonPath), { recursive: true });
38
+ await fs.writeFile(modelsJsonPath, JSON.stringify(existing, null, 2), "utf8");
39
+ // The coding-plan apiKey is written PLAINTEXT into models.json. Lock the
40
+ // file down to owner-only on POSIX so a shared-host neighbour can't read the
41
+ // key (mirrors the `chmodIfPosix` pattern in src/auth/profiles.ts). No-op on
42
+ // Windows (NTFS perms model differs) and best-effort on filesystems that
43
+ // don't support chmod (e.g. mounted FAT32).
44
+ if (process.platform !== "win32") {
45
+ try {
46
+ await fs.chmod(modelsJsonPath, 0o600);
47
+ }
48
+ catch {
49
+ // Filesystem may not support chmod — non-fatal.
50
+ }
51
+ }
52
+ // Convex mode — the file just written lives in the OS cache (resolveModelsPath
53
+ // routed it there) and is a regenerable mirror; the durable copy is the
54
+ // sealed "models" blob. Push it so a fresh machine re-materialises the
55
+ // catalog at boot.
56
+ const rctx = tryGetRuntimeContext();
57
+ if (rctx?.mode === "convex") {
58
+ await rctx.store.auth
59
+ .writeAuthFileBlob("main", "models", existing)
60
+ .catch((err) => {
61
+ console.error(`brigade: models catalog write to convex failed — ${err.message}`);
62
+ });
63
+ }
64
+ }
65
+ //# sourceMappingURL=custom-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-provider.js","sourceRoot":"","sources":["../../src/integrations/custom-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACpD,cAAsB,EACtB,CAMC;IAED,IAAI,QAAQ,GAAwC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACtE,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;QACtD,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,QAAQ,CAAC,SAAS,GAAG,EAAE,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,oFAAoF;IACrF,CAAC;IAED,QAAQ,CAAC,SAAU,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG;QAC3B,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;KAChD,CAAC;IAEF,yEAAyE;IACzE,2EAA2E;IAC3E,yEAAyE;IACzE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,MAAM,EAAE,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAE9E,yEAAyE;IACzE,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE,4CAA4C;IAC5C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,IAAI,CAAC;YACJ,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACR,gDAAgD;QACjD,CAAC;IACF,CAAC;IAED,+EAA+E;IAC/E,wEAAwE;IACxE,uEAAuE;IACvE,mBAAmB;IACnB,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,IAAI,IAAI,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI;aACnB,iBAAiB,CAAC,MAAM,EAAE,QAAiB,EAAE,QAAmC,CAAC;aACjF,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;YACrB,OAAO,CAAC,KAAK,CAAC,oDAAoD,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;AACF,CAAC"}
@@ -63,4 +63,34 @@ export interface LiveCloudModel {
63
63
  * never throws. Pricing → `cost.input` is per-Mtok (OpenRouter quotes per-token).
64
64
  */
65
65
  export declare function listOpenRouterModels(): Promise<LiveCloudModel[]>;
66
+ /** Last-fetched live models for a subscription provider, or `undefined` if never fetched. */
67
+ export declare function getCachedSubscriptionModels(providerId: string): LiveCloudModel[] | undefined;
68
+ /**
69
+ * GitHub Copilot's per-account model catalog. The token embeds the proxy host
70
+ * (`proxy-ep=…`) which `getGitHubCopilotBaseUrl` rewrites to the api host; we GET
71
+ * `${baseUrl}/models` with Copilot's required editor headers (a plain
72
+ * Authorization isn't enough — the endpoint 400s without the editor/integration
73
+ * headers). Keeps only models the account can actually pick (model_picker_enabled,
74
+ * policy not disabled, tool_calls not explicitly off). Never throws — returns the
75
+ * last good cache or `[]` so the caller falls back to Pi's bundled catalog.
76
+ */
77
+ export declare function fetchGitHubCopilotModels(copilotToken: string): Promise<LiveCloudModel[]>;
78
+ /**
79
+ * Models a Claude Pro/Max SUBSCRIPTION can use. There is NO live per-account
80
+ * models endpoint for a subscription OAuth token: Anthropic scopes that token to
81
+ * inference only, so `GET /v1/models` returns 401/403 (verified). Instead we
82
+ * surface Pi's bundled Anthropic catalog — it IS the current Claude model family
83
+ * and updates as Pi ships new models — so the picker is always populated and
84
+ * current with ZERO network round-trip (no guaranteed-fail request, no timeout)
85
+ * on every sign-in. (GitHub Copilot DOES expose a live per-account list, so that
86
+ * path stays a real fetch; Anthropic subscriptions simply don't have one.)
87
+ */
88
+ export declare function fetchAnthropicSubscriptionModels(): LiveCloudModel[];
89
+ /**
90
+ * Warm the live cache for a subscription provider right after OAuth login so the
91
+ * model picker has the account's CURRENT models ready. Best-effort: any failure is
92
+ * swallowed (login must never block on this). `codex` has no live list endpoint —
93
+ * it falls through to Pi's bundled catalog, so this is a no-op for it.
94
+ */
95
+ export declare function prefetchSubscriptionModels(providerId: string, oauthAccessToken: string): Promise<void>;
66
96
  //# sourceMappingURL=provider-discovery.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider-discovery.d.ts","sourceRoot":"","sources":["../../src/integrations/provider-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,WAAW,mBAAmB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,mBAAmB,CAAC;CAC1B;AAyED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAKD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAsDtE"}
1
+ {"version":3,"file":"provider-discovery.d.ts","sourceRoot":"","sources":["../../src/integrations/provider-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,mBAAmB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,mBAAmB,CAAC;CAC1B;AAyED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAKD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAsDtE;AAeD,6FAA6F;AAC7F,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,SAAS,CAG5F;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAiE9F;AAED;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,IAAI,cAAc,EAAE,CA4BnE;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe5G"}
@@ -14,6 +14,7 @@
14
14
  * `integrations/ollama.ts` (native /api/tags); this module is for cloud +
15
15
  * OpenAI-compatible HTTP endpoints.
16
16
  */
17
+ import { getModels } from "@earendil-works/pi-ai";
17
18
  const TIMEOUT_MS = 5000;
18
19
  const EMPTY = { exists: false, meta: {} };
19
20
  /** OpenRouter's public model list — no key needed, rich metadata. */
@@ -163,4 +164,158 @@ export async function listOpenRouterModels() {
163
164
  return openRouterListCache?.models ?? [];
164
165
  }
165
166
  }
167
+ /* ──────────────────────── subscription live catalogs ──────────────────────── */
168
+ /**
169
+ * Live model catalogs for OAuth subscription providers (GitHub Copilot,
170
+ * Anthropic Claude Pro/Max). Same shape + degrade-to-cache contract as
171
+ * `listOpenRouterModels`, but keyed by providerId because there are several
172
+ * subscription providers (OpenRouter is a singleton). The onboarding picker
173
+ * reads the cache after login via `getCachedSubscriptionModels` and joins the
174
+ * static Pi catalog by id for richer metadata.
175
+ */
176
+ const SUBSCRIPTION_MODELS_TTL_MS = 5 * 60_000;
177
+ const subscriptionModelsCache = new Map();
178
+ /** Last-fetched live models for a subscription provider, or `undefined` if never fetched. */
179
+ export function getCachedSubscriptionModels(providerId) {
180
+ const e = subscriptionModelsCache.get(providerId);
181
+ return e ? e.models : undefined;
182
+ }
183
+ /**
184
+ * GitHub Copilot's per-account model catalog. The token embeds the proxy host
185
+ * (`proxy-ep=…`) which `getGitHubCopilotBaseUrl` rewrites to the api host; we GET
186
+ * `${baseUrl}/models` with Copilot's required editor headers (a plain
187
+ * Authorization isn't enough — the endpoint 400s without the editor/integration
188
+ * headers). Keeps only models the account can actually pick (model_picker_enabled,
189
+ * policy not disabled, tool_calls not explicitly off). Never throws — returns the
190
+ * last good cache or `[]` so the caller falls back to Pi's bundled catalog.
191
+ */
192
+ export async function fetchGitHubCopilotModels(copilotToken) {
193
+ const now = Date.now();
194
+ const cached = subscriptionModelsCache.get("github-copilot");
195
+ if (cached && now - cached.at < SUBSCRIPTION_MODELS_TTL_MS) {
196
+ return cached.models;
197
+ }
198
+ const controller = new AbortController();
199
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
200
+ try {
201
+ const { getGitHubCopilotBaseUrl } = await import("@earendil-works/pi-ai/oauth");
202
+ const baseUrl = getGitHubCopilotBaseUrl(copilotToken);
203
+ const res = await fetch(`${baseUrl}/models`, {
204
+ signal: controller.signal,
205
+ headers: {
206
+ Accept: "application/json",
207
+ Authorization: `Bearer ${copilotToken}`,
208
+ "User-Agent": "GitHubCopilotChat/0.35.0",
209
+ "Editor-Version": "vscode/1.107.0",
210
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
211
+ "Copilot-Integration-Id": "vscode-chat",
212
+ "X-GitHub-Api-Version": "2026-06-01",
213
+ },
214
+ });
215
+ if (!res.ok)
216
+ return cached?.models ?? [];
217
+ const body = (await res.json());
218
+ const list = body?.data;
219
+ if (!Array.isArray(list))
220
+ return cached?.models ?? [];
221
+ const out = [];
222
+ for (const raw of list) {
223
+ const item = raw;
224
+ const id = typeof item.id === "string" ? item.id : undefined;
225
+ if (!id)
226
+ continue;
227
+ // Selectability gate (mirrors Pi's own `isSelectableCopilotModel`): only
228
+ // surface models the account is allowed to pick. Every access is guarded —
229
+ // the response is untyped.
230
+ const policy = item.policy;
231
+ const capabilities = item.capabilities;
232
+ const supports = capabilities?.supports;
233
+ if (item.model_picker_enabled !== true)
234
+ continue;
235
+ if (policy?.state === "disabled")
236
+ continue;
237
+ if (supports?.tool_calls === false)
238
+ continue;
239
+ const maxCtx = capabilities?.limits?.max_context_window_tokens;
240
+ const contextWindow = typeof maxCtx === "number" && maxCtx > 0 ? maxCtx : undefined;
241
+ const name = typeof item.name === "string" && item.name.length > 0 ? item.name : id;
242
+ const input = supports?.vision ? ["text", "image"] : ["text"];
243
+ out.push({
244
+ provider: "github-copilot",
245
+ id,
246
+ name,
247
+ ...(contextWindow !== undefined ? { contextWindow } : {}),
248
+ // Copilot's list doesn't flag reasoning here — leave false; the static
249
+ // catalog join in onboarding fills the richer fields when Pi knows it.
250
+ reasoning: false,
251
+ input,
252
+ });
253
+ }
254
+ subscriptionModelsCache.set("github-copilot", { at: now, models: out });
255
+ return out;
256
+ }
257
+ catch {
258
+ return cached?.models ?? [];
259
+ }
260
+ finally {
261
+ clearTimeout(timer);
262
+ }
263
+ }
264
+ /**
265
+ * Models a Claude Pro/Max SUBSCRIPTION can use. There is NO live per-account
266
+ * models endpoint for a subscription OAuth token: Anthropic scopes that token to
267
+ * inference only, so `GET /v1/models` returns 401/403 (verified). Instead we
268
+ * surface Pi's bundled Anthropic catalog — it IS the current Claude model family
269
+ * and updates as Pi ships new models — so the picker is always populated and
270
+ * current with ZERO network round-trip (no guaranteed-fail request, no timeout)
271
+ * on every sign-in. (GitHub Copilot DOES expose a live per-account list, so that
272
+ * path stays a real fetch; Anthropic subscriptions simply don't have one.)
273
+ */
274
+ export function fetchAnthropicSubscriptionModels() {
275
+ const now = Date.now();
276
+ const cached = subscriptionModelsCache.get("anthropic");
277
+ if (cached && now - cached.at < SUBSCRIPTION_MODELS_TTL_MS) {
278
+ return cached.models;
279
+ }
280
+ let out = [];
281
+ try {
282
+ const catalog = getModels("anthropic");
283
+ out = catalog.map((m) => ({
284
+ provider: "anthropic",
285
+ id: m.id,
286
+ name: m.name ?? m.id,
287
+ contextWindow: m.contextWindow,
288
+ reasoning: m.reasoning ?? false,
289
+ input: m.input ?? ["text"],
290
+ }));
291
+ }
292
+ catch {
293
+ out = [];
294
+ }
295
+ subscriptionModelsCache.set("anthropic", { at: now, models: out });
296
+ return out;
297
+ }
298
+ /**
299
+ * Warm the live cache for a subscription provider right after OAuth login so the
300
+ * model picker has the account's CURRENT models ready. Best-effort: any failure is
301
+ * swallowed (login must never block on this). `codex` has no live list endpoint —
302
+ * it falls through to Pi's bundled catalog, so this is a no-op for it.
303
+ */
304
+ export async function prefetchSubscriptionModels(providerId, oauthAccessToken) {
305
+ try {
306
+ if (providerId === "github-copilot") {
307
+ await fetchGitHubCopilotModels(oauthAccessToken);
308
+ return;
309
+ }
310
+ if (providerId === "anthropic") {
311
+ // No network — populates the cache from Pi's current Anthropic catalog.
312
+ fetchAnthropicSubscriptionModels();
313
+ return;
314
+ }
315
+ // Other subscription providers (codex) have no live endpoint — no-op.
316
+ }
317
+ catch {
318
+ // Best-effort warm-up — never blocks login; picker falls back to the catalog.
319
+ }
320
+ }
166
321
  //# sourceMappingURL=provider-discovery.js.map