@herbertgao/pi-extensions 2026.9.10 → 2026.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -2
- package/node_modules/@herbertgao/resume-from/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
- package/node_modules/@herbertgao/sol-pi/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
- package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
- package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
- package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
- package/node_modules/pi-multi-account/LICENSE +21 -0
- package/node_modules/pi-multi-account/README.md +197 -0
- package/node_modules/pi-multi-account/SECURITY.md +27 -0
- package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
- package/node_modules/pi-multi-account/child-usability.ts +233 -0
- package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
- package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
- package/node_modules/pi-multi-account/context-guard.ts +420 -0
- package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
- package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
- package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
- package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
- package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
- package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
- package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
- package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
- package/node_modules/pi-multi-account/cursor/index.ts +572 -0
- package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
- package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
- package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
- package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
- package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
- package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
- package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
- package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
- package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
- package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
- package/node_modules/pi-multi-account/index.ts +11825 -0
- package/node_modules/pi-multi-account/model-catalog.ts +354 -0
- package/node_modules/pi-multi-account/package.json +101 -0
- package/node_modules/pi-multi-account/pi-contract.ts +281 -0
- package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
- package/node_modules/pi-multi-account/provider-priority.ts +189 -0
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
- package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
- package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
- package/node_modules/pi-multi-account/usage.ts +1099 -0
- package/node_modules/pi-typesafe/README.md +6 -2
- package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
- package/node_modules/pi-typesafe/dist/client.js +45 -10
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/package.json +2 -2
- package/node_modules/pi-web-access/CHANGELOG.md +36 -0
- package/node_modules/pi-web-access/README.md +75 -18
- package/node_modules/pi-web-access/anysearch.ts +4 -15
- package/node_modules/pi-web-access/bocha.ts +3 -22
- package/node_modules/pi-web-access/brave.ts +3 -21
- package/node_modules/pi-web-access/brightdata.ts +5 -32
- package/node_modules/pi-web-access/content-find.ts +168 -53
- package/node_modules/pi-web-access/curator-page.ts +4 -1
- package/node_modules/pi-web-access/curator-run.ts +2 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +24620 -0
- package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
- package/node_modules/pi-web-access/duckduckgo.ts +3 -21
- package/node_modules/pi-web-access/extract.ts +3 -1
- package/node_modules/pi-web-access/firecrawl.ts +5 -29
- package/node_modules/pi-web-access/gemini-search.ts +81 -32
- package/node_modules/pi-web-access/index.ts +149 -148
- package/node_modules/pi-web-access/jina-search.ts +4 -15
- package/node_modules/pi-web-access/kagi.ts +4 -13
- package/node_modules/pi-web-access/kimi-search.ts +5 -30
- package/node_modules/pi-web-access/mistral-search.ts +1 -15
- package/node_modules/pi-web-access/ollama.ts +2 -7
- package/node_modules/pi-web-access/openai-search.ts +174 -36
- package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
- package/node_modules/pi-web-access/package.json +10 -4
- package/node_modules/pi-web-access/page-query.ts +10 -2
- package/node_modules/pi-web-access/parallel.ts +1 -15
- package/node_modules/pi-web-access/pdf-extract.ts +3 -0
- package/node_modules/pi-web-access/querit.ts +5 -29
- package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
- package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
- package/node_modules/pi-web-access/search1api.ts +5 -29
- package/node_modules/pi-web-access/searchinfinity.ts +5 -29
- package/node_modules/pi-web-access/searxng.ts +3 -21
- package/node_modules/pi-web-access/serpapi.ts +5 -28
- package/node_modules/pi-web-access/serpbase.ts +3 -22
- package/node_modules/pi-web-access/serpdive.ts +3 -21
- package/node_modules/pi-web-access/serper.ts +5 -28
- package/node_modules/pi-web-access/serply.ts +197 -0
- package/node_modules/pi-web-access/source-check.ts +11 -47
- package/node_modules/pi-web-access/summary-review.ts +7 -3
- package/node_modules/pi-web-access/tavily.ts +3 -21
- package/node_modules/pi-web-access/tinyfish.ts +5 -29
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/node_modules/pi-web-access/valyu.ts +5 -28
- package/node_modules/pi-web-access/xai-search.ts +1 -15
- package/node_modules/pi-web-access/xcrawl.ts +5 -32
- package/package.json +17 -11
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
export type CodexCatalogCredential = {
|
|
2
|
+
type?: string;
|
|
3
|
+
access?: string;
|
|
4
|
+
accountId?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type CodexCatalogModel = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
reasoning: boolean;
|
|
11
|
+
thinkingLevelMap?: Record<string, string | null>;
|
|
12
|
+
input: ("text" | "image")[];
|
|
13
|
+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
14
|
+
contextWindow: number;
|
|
15
|
+
maxTokens: number;
|
|
16
|
+
/** OpenAI's lower-is-better catalog priority. Missing on models learned from Pi itself. */
|
|
17
|
+
priority?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type CodexCatalogSnapshot = {
|
|
21
|
+
fetchedAt: number;
|
|
22
|
+
models: CodexCatalogModel[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
26
|
+
const DEFAULT_CONTEXT_WINDOW = 272_000;
|
|
27
|
+
const DEFAULT_MAX_TOKENS = 128_000;
|
|
28
|
+
// Levels Pi itself understands, weakest → strongest. This is a FILTER, not a grant: a model's
|
|
29
|
+
// own advertised efforts are intersected with this list (see thinkingLevelMap), so a level named
|
|
30
|
+
// here only ever reaches a model that asked for it. Provider gradations differ wildly and do not
|
|
31
|
+
// nest — Claude Opus 4.6 advertises `max` alone, glm-5.2 has `max` but no `xhigh`, gpt-5.6 has
|
|
32
|
+
// `max`/`xhigh`/`minimal` but no `medium` — which is exactly why the per-model intersection, not
|
|
33
|
+
// this list, decides what a given model gets.
|
|
34
|
+
const KNOWN_PI_THINKING_LEVELS = [
|
|
35
|
+
"minimal",
|
|
36
|
+
"low",
|
|
37
|
+
"medium",
|
|
38
|
+
"high",
|
|
39
|
+
"xhigh",
|
|
40
|
+
"max",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function record(value: unknown): Record<string, any> {
|
|
44
|
+
return value && typeof value === "object" ? (value as Record<string, any>) : {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function positiveInteger(value: unknown, fallback: number): number {
|
|
48
|
+
const parsed = Number(value);
|
|
49
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function reasoningEfforts(source: Record<string, any>): string[] {
|
|
53
|
+
const raw = source.supported_reasoning_levels ?? source.supported_reasoning_efforts;
|
|
54
|
+
if (!Array.isArray(raw)) return [];
|
|
55
|
+
return raw
|
|
56
|
+
.map((item) => {
|
|
57
|
+
if (typeof item === "string") return item;
|
|
58
|
+
const value = record(item).effort ?? record(item).level;
|
|
59
|
+
return typeof value === "string" ? value : undefined;
|
|
60
|
+
})
|
|
61
|
+
.filter((value): value is string => !!value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function thinkingLevelMap(efforts: string[]): Record<string, string | null> | undefined {
|
|
65
|
+
if (efforts.length === 0) return undefined;
|
|
66
|
+
const supported = new Set(efforts);
|
|
67
|
+
const result: Record<string, string | null> = {};
|
|
68
|
+
for (const level of KNOWN_PI_THINKING_LEVELS) {
|
|
69
|
+
if (supported.has(level)) result[level] = level;
|
|
70
|
+
}
|
|
71
|
+
// Pi has a `minimal` setting while some Codex catalogs begin at `low`.
|
|
72
|
+
if (!("minimal" in result) && supported.has("low")) result.minimal = "low";
|
|
73
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function versionParts(id: string): number[] {
|
|
77
|
+
const match = id.match(/(?:^|[-_])(?:gpt[-_]?)?(\d+)(?:\.(\d+))?(?:\.(\d+))?/i);
|
|
78
|
+
return match ? [Number(match[1]), Number(match[2] ?? 0), Number(match[3] ?? 0)] : [0, 0, 0];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function variantPower(id: string): number {
|
|
82
|
+
const lower = id.toLowerCase();
|
|
83
|
+
if (/(?:^|[-_])(?:astra|sol)(?:$|[-_])/.test(lower)) return 100;
|
|
84
|
+
if (/(?:^|[-_])max(?:$|[-_])/.test(lower)) return 95;
|
|
85
|
+
if (/(?:^|[-_])terra(?:$|[-_])/.test(lower)) return 75;
|
|
86
|
+
if (/(?:^|[-_])luna(?:$|[-_])/.test(lower)) return 50;
|
|
87
|
+
if (/(?:^|[-_])mini(?:$|[-_])/.test(lower)) return 30;
|
|
88
|
+
if (/(?:^|[-_])spark(?:$|[-_])/.test(lower)) return 20;
|
|
89
|
+
if (/(?:^|[-_])nano(?:$|[-_])/.test(lower)) return 10;
|
|
90
|
+
return 85;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Strongest-first fallback ordering when a provider catalog does not carry priorities.
|
|
95
|
+
* Server priority always wins when both models came from the live OpenAI catalog.
|
|
96
|
+
*/
|
|
97
|
+
export function compareCodexModelStrength(
|
|
98
|
+
a: Pick<CodexCatalogModel, "id" | "priority">,
|
|
99
|
+
b: Pick<CodexCatalogModel, "id" | "priority">,
|
|
100
|
+
): number {
|
|
101
|
+
if (a.priority !== undefined && b.priority !== undefined && a.priority !== b.priority) {
|
|
102
|
+
return a.priority - b.priority;
|
|
103
|
+
}
|
|
104
|
+
if (a.priority !== undefined && b.priority === undefined) return -1;
|
|
105
|
+
if (a.priority === undefined && b.priority !== undefined) return 1;
|
|
106
|
+
const av = versionParts(a.id);
|
|
107
|
+
const bv = versionParts(b.id);
|
|
108
|
+
for (let i = 0; i < Math.max(av.length, bv.length); i++) {
|
|
109
|
+
if ((av[i] ?? 0) !== (bv[i] ?? 0)) return (bv[i] ?? 0) - (av[i] ?? 0);
|
|
110
|
+
}
|
|
111
|
+
const variant = variantPower(b.id) - variantPower(a.id);
|
|
112
|
+
return variant || a.id.localeCompare(b.id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Anthropic ships dated aliases (`claude-opus-4-5-20251101`) beside the rolling id. */
|
|
116
|
+
function isDatedAnthropicAlias(id: string): boolean {
|
|
117
|
+
return /-\d{8}$/.test(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function anthropicTierRank(id: string): number {
|
|
121
|
+
const lower = id.toLowerCase();
|
|
122
|
+
// Fable is Anthropic's exceptional/apex line. Keep this separate from generation parsing:
|
|
123
|
+
// claude-fable-5-1 must outrank every Opus 5 variant, not merely sort as another Claude 5.
|
|
124
|
+
if (lower.includes("fable")) return 4;
|
|
125
|
+
if (lower.includes("opus")) return 3;
|
|
126
|
+
if (lower.includes("sonnet")) return 2;
|
|
127
|
+
if (lower.includes("haiku")) return 1;
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Numeric generation parts after the tier: `claude-opus-4-8` -> [4,8], `claude-opus-5` -> [5]. */
|
|
132
|
+
function anthropicVersionParts(id: string): number[] {
|
|
133
|
+
const tail = id.toLowerCase().replace(/^.*?(opus|sonnet|haiku)/, "");
|
|
134
|
+
return (tail.match(/\d+/g) ?? [])
|
|
135
|
+
.map(Number)
|
|
136
|
+
.filter((n) => Number.isFinite(n) && n < 100_000);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Strongest-first ordering for Anthropic model ids, so a newly released flagship (e.g.
|
|
141
|
+
* `claude-opus-5` arriving in Pi's registry) outranks the newest id this extension happened to
|
|
142
|
+
* be released with — the same release-per-model treadmill that was removed for Codex.
|
|
143
|
+
*
|
|
144
|
+
* Tier dominates version: Anthropic's flagship line is Opus, and the project's hard rule is to
|
|
145
|
+
* stay on a provider's top model. Within a tier, the higher generation wins.
|
|
146
|
+
*/
|
|
147
|
+
export function compareAnthropicModelStrength(a: string, b: string): number {
|
|
148
|
+
const tier = anthropicTierRank(b) - anthropicTierRank(a);
|
|
149
|
+
if (tier !== 0) return tier;
|
|
150
|
+
const av = anthropicVersionParts(a);
|
|
151
|
+
const bv = anthropicVersionParts(b);
|
|
152
|
+
for (let i = 0; i < Math.max(av.length, bv.length); i++) {
|
|
153
|
+
if ((av[i] ?? 0) !== (bv[i] ?? 0)) return (bv[i] ?? 0) - (av[i] ?? 0);
|
|
154
|
+
}
|
|
155
|
+
// Prefer the rolling id over its dated alias so the list is not doubled up.
|
|
156
|
+
const dated = Number(isDatedAnthropicAlias(a)) - Number(isDatedAnthropicAlias(b));
|
|
157
|
+
return dated || a.localeCompare(b);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Known Anthropic chat model ids, strongest first, deduped and without dated aliases. */
|
|
161
|
+
export function rankAnthropicModelIds(ids: Iterable<string>): string[] {
|
|
162
|
+
const seen = new Set<string>();
|
|
163
|
+
for (const id of ids) {
|
|
164
|
+
if (typeof id !== "string" || !id.trim()) continue;
|
|
165
|
+
if (!/claude/i.test(id) || isDatedAnthropicAlias(id)) continue;
|
|
166
|
+
seen.add(id);
|
|
167
|
+
}
|
|
168
|
+
return [...seen].sort(compareAnthropicModelStrength);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function parseCodexModelCatalog(body: unknown): CodexCatalogModel[] {
|
|
172
|
+
const source = record(body);
|
|
173
|
+
const rawModels = Array.isArray(source.models)
|
|
174
|
+
? source.models
|
|
175
|
+
: Array.isArray(source.data)
|
|
176
|
+
? source.data
|
|
177
|
+
: [];
|
|
178
|
+
const byId = new Map<string, CodexCatalogModel>();
|
|
179
|
+
for (const raw of rawModels) {
|
|
180
|
+
const model = record(raw);
|
|
181
|
+
const id = model.slug ?? model.id ?? model.model;
|
|
182
|
+
if (typeof id !== "string" || !id.trim()) continue;
|
|
183
|
+
// The backend may retain migration-only entries but marks picker-ready models as `list`.
|
|
184
|
+
if (typeof model.visibility === "string" && model.visibility.toLowerCase() !== "list") continue;
|
|
185
|
+
const efforts = reasoningEfforts(model);
|
|
186
|
+
const modalities = Array.isArray(model.input_modalities)
|
|
187
|
+
? model.input_modalities.filter(
|
|
188
|
+
(value: unknown): value is "text" | "image" => value === "text" || value === "image",
|
|
189
|
+
)
|
|
190
|
+
: ["text", "image"] as ("text" | "image")[];
|
|
191
|
+
const priority = Number(model.priority);
|
|
192
|
+
byId.set(id, {
|
|
193
|
+
id,
|
|
194
|
+
name:
|
|
195
|
+
typeof model.display_name === "string"
|
|
196
|
+
? model.display_name
|
|
197
|
+
: typeof model.name === "string"
|
|
198
|
+
? model.name
|
|
199
|
+
: id,
|
|
200
|
+
reasoning: efforts.length > 0 || !!model.default_reasoning_level,
|
|
201
|
+
thinkingLevelMap: thinkingLevelMap(efforts),
|
|
202
|
+
input: modalities.length > 0 ? modalities : ["text"],
|
|
203
|
+
cost: ZERO_COST,
|
|
204
|
+
contextWindow: positiveInteger(
|
|
205
|
+
model.context_window ?? model.max_context_window,
|
|
206
|
+
DEFAULT_CONTEXT_WINDOW,
|
|
207
|
+
),
|
|
208
|
+
maxTokens: positiveInteger(
|
|
209
|
+
model.max_output_tokens ?? model.max_tokens,
|
|
210
|
+
DEFAULT_MAX_TOKENS,
|
|
211
|
+
),
|
|
212
|
+
...(Number.isFinite(priority) ? { priority } : {}),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return [...byId.values()].sort(compareCodexModelStrength);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function accountIdFromToken(token: string): string | undefined {
|
|
219
|
+
try {
|
|
220
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8"));
|
|
221
|
+
const id = payload?.["https://api.openai.com/auth"]?.chatgpt_account_id;
|
|
222
|
+
return typeof id === "string" ? id : undefined;
|
|
223
|
+
} catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export class CodexCatalogFetchError extends Error {
|
|
229
|
+
readonly status?: number;
|
|
230
|
+
|
|
231
|
+
constructor(message: string, status?: number) {
|
|
232
|
+
super(message);
|
|
233
|
+
this.name = "CodexCatalogFetchError";
|
|
234
|
+
this.status = status;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export class OllamaCatalogFetchError extends Error {
|
|
239
|
+
constructor(message: string) {
|
|
240
|
+
super(message);
|
|
241
|
+
this.name = "OllamaCatalogFetchError";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Pull the live Ollama Cloud model catalog (`GET https://ollama.com/v1/models`).
|
|
247
|
+
* Unlike Codex, Ollama Cloud publishes a single account-agnostic catalog keyed by an
|
|
248
|
+
* API key, so one fetch covers the base provider and every cloned `ollama-account-N` slot.
|
|
249
|
+
* Returns the canonical model ids Ollama Cloud expects in chat requests (e.g. `kimi-k3`,
|
|
250
|
+
* `glm-5.2`) — no `:cloud` suffix. The extension registers them verbatim; Ollama Cloud
|
|
251
|
+
* accepts both the bare and the `:cloud`-suffixed form, so legacy configured ids keep working.
|
|
252
|
+
*/
|
|
253
|
+
export async function fetchOllamaCloudCatalog(
|
|
254
|
+
apiKey: string,
|
|
255
|
+
options: { fetchImpl?: typeof fetch; timeoutMs?: number } = {},
|
|
256
|
+
): Promise<string[]> {
|
|
257
|
+
if (!apiKey) throw new OllamaCatalogFetchError("Ollama Cloud has no stored API key");
|
|
258
|
+
const controller = new AbortController();
|
|
259
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 8_000);
|
|
260
|
+
try {
|
|
261
|
+
const response = await (options.fetchImpl ?? fetch)(
|
|
262
|
+
"https://ollama.com/v1/models",
|
|
263
|
+
{
|
|
264
|
+
method: "GET",
|
|
265
|
+
headers: {
|
|
266
|
+
Authorization: `Bearer ${apiKey}`,
|
|
267
|
+
Accept: "application/json",
|
|
268
|
+
"User-Agent": "pi-multi-account",
|
|
269
|
+
},
|
|
270
|
+
signal: controller.signal,
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
throw new OllamaCatalogFetchError(
|
|
275
|
+
`Ollama Cloud catalog returned HTTP ${response.status}`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const body = record(await response.json());
|
|
279
|
+
const data = Array.isArray(body.data) ? body.data : [];
|
|
280
|
+
const ids: string[] = [];
|
|
281
|
+
for (const raw of data) {
|
|
282
|
+
const id = record(raw).id;
|
|
283
|
+
if (typeof id === "string" && id.trim()) ids.push(id.trim());
|
|
284
|
+
}
|
|
285
|
+
if (ids.length === 0) {
|
|
286
|
+
throw new OllamaCatalogFetchError("Ollama Cloud catalog returned no models");
|
|
287
|
+
}
|
|
288
|
+
return ids;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error instanceof OllamaCatalogFetchError) throw error;
|
|
291
|
+
if ((error as any)?.name === "AbortError") {
|
|
292
|
+
throw new OllamaCatalogFetchError("Ollama Cloud catalog request timed out");
|
|
293
|
+
}
|
|
294
|
+
throw new OllamaCatalogFetchError(
|
|
295
|
+
`Ollama Cloud catalog request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
296
|
+
);
|
|
297
|
+
} finally {
|
|
298
|
+
clearTimeout(timer);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function fetchCodexModelCatalog(
|
|
303
|
+
credential: CodexCatalogCredential,
|
|
304
|
+
options: {
|
|
305
|
+
fetchImpl?: typeof fetch;
|
|
306
|
+
timeoutMs?: number;
|
|
307
|
+
clientVersion?: string;
|
|
308
|
+
} = {},
|
|
309
|
+
): Promise<CodexCatalogModel[]> {
|
|
310
|
+
if (credential.type !== "oauth" || !credential.access) {
|
|
311
|
+
throw new CodexCatalogFetchError("Codex account has no OAuth access token");
|
|
312
|
+
}
|
|
313
|
+
const accountId = credential.accountId ?? accountIdFromToken(credential.access);
|
|
314
|
+
if (!accountId) throw new CodexCatalogFetchError("Codex access token has no account id");
|
|
315
|
+
const controller = new AbortController();
|
|
316
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 5_000);
|
|
317
|
+
try {
|
|
318
|
+
const response = await (options.fetchImpl ?? fetch)(
|
|
319
|
+
`https://chatgpt.com/backend-api/codex/models?client_version=${encodeURIComponent(options.clientVersion ?? "pi-multi-account")}`,
|
|
320
|
+
{
|
|
321
|
+
method: "GET",
|
|
322
|
+
headers: {
|
|
323
|
+
Authorization: `Bearer ${credential.access}`,
|
|
324
|
+
"ChatGPT-Account-Id": accountId,
|
|
325
|
+
Accept: "application/json",
|
|
326
|
+
originator: "pi",
|
|
327
|
+
"User-Agent": "pi-multi-account",
|
|
328
|
+
},
|
|
329
|
+
signal: controller.signal,
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
if (!response.ok) {
|
|
333
|
+
throw new CodexCatalogFetchError(
|
|
334
|
+
`Codex model catalog returned HTTP ${response.status}`,
|
|
335
|
+
response.status,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const models = parseCodexModelCatalog(await response.json());
|
|
339
|
+
if (models.length === 0) {
|
|
340
|
+
throw new CodexCatalogFetchError("Codex model catalog returned no selectable models");
|
|
341
|
+
}
|
|
342
|
+
return models;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error instanceof CodexCatalogFetchError) throw error;
|
|
345
|
+
if ((error as any)?.name === "AbortError") {
|
|
346
|
+
throw new CodexCatalogFetchError("Codex model catalog request timed out");
|
|
347
|
+
}
|
|
348
|
+
throw new CodexCatalogFetchError(
|
|
349
|
+
`Codex model catalog request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
350
|
+
);
|
|
351
|
+
} finally {
|
|
352
|
+
clearTimeout(timer);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-multi-account",
|
|
3
|
+
"version": "1.22.0",
|
|
4
|
+
"description": "Automatic multi-account failover & rotation for Pi Agent across Anthropic (Claude), OpenAI/ChatGPT Codex, Kimi For Coding, Cursor, Qwen/Alibaba, and Ollama. Auto-discovers authenticated accounts, grows the rotation on login, and drops accounts on logout, expiry, or quota/rate-limit errors",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "sars267",
|
|
8
|
+
"homepage": "https://github.com/Sarrius/pi-multi-account#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Sarrius/pi-multi-account.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Sarrius/pi-multi-account/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"pi-extension",
|
|
19
|
+
"pi-agent",
|
|
20
|
+
"pi-coding-agent",
|
|
21
|
+
"agent-pi",
|
|
22
|
+
"failover",
|
|
23
|
+
"fallback",
|
|
24
|
+
"rotation",
|
|
25
|
+
"multi-account",
|
|
26
|
+
"rate-limit",
|
|
27
|
+
"quota",
|
|
28
|
+
"anthropic",
|
|
29
|
+
"claude",
|
|
30
|
+
"openai",
|
|
31
|
+
"codex",
|
|
32
|
+
"chatgpt",
|
|
33
|
+
"qwen",
|
|
34
|
+
"alibaba",
|
|
35
|
+
"ollama",
|
|
36
|
+
"glm",
|
|
37
|
+
"glm-5.2",
|
|
38
|
+
"oauth",
|
|
39
|
+
"kimi",
|
|
40
|
+
"kimi-coding",
|
|
41
|
+
"cursor"
|
|
42
|
+
],
|
|
43
|
+
"files": [
|
|
44
|
+
"index.ts",
|
|
45
|
+
"provider-payload-stream.ts",
|
|
46
|
+
"auth-file-transaction.ts",
|
|
47
|
+
"state-file-transaction.ts",
|
|
48
|
+
"model-catalog.ts",
|
|
49
|
+
"usage.ts",
|
|
50
|
+
"cursor-bridge.ts",
|
|
51
|
+
"cursor-model-name.ts",
|
|
52
|
+
"compaction-summary.ts",
|
|
53
|
+
"context-guard.ts",
|
|
54
|
+
"completion-route-planner.ts",
|
|
55
|
+
"provider-priority.ts",
|
|
56
|
+
"child-usability.ts",
|
|
57
|
+
"pi-contract.ts",
|
|
58
|
+
"slot-proxy.ts",
|
|
59
|
+
"slot-proxy-auth.ts",
|
|
60
|
+
"cursor",
|
|
61
|
+
"README.md",
|
|
62
|
+
"CHANGELOG.md",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"SECURITY.md",
|
|
65
|
+
"CONTRIBUTING.md"
|
|
66
|
+
],
|
|
67
|
+
"scripts": {
|
|
68
|
+
"check": "tsc --noEmit",
|
|
69
|
+
"test": "node --test test/*.test.ts",
|
|
70
|
+
"pack:check": "node scripts/check-package.mjs && node scripts/check-tracked-privacy.mjs",
|
|
71
|
+
"release:check": "npm run check && npm test && npm run pack:check",
|
|
72
|
+
"prepublishOnly": "npm run release:check"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=22"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@earendil-works/pi-coding-agent": ">=0.85.1"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
85
|
+
"@types/node": "^22.20.1",
|
|
86
|
+
"typescript": "^6.0.3"
|
|
87
|
+
},
|
|
88
|
+
"pi": {
|
|
89
|
+
"extensions": [
|
|
90
|
+
"./index.ts"
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"dependencies": {
|
|
94
|
+
"@bufbuild/protobuf": "^2.14.0",
|
|
95
|
+
"@earendil-works/pi-ai": "^0.85.1",
|
|
96
|
+
"proper-lockfile": "4.1.2"
|
|
97
|
+
},
|
|
98
|
+
"overrides": {
|
|
99
|
+
"protobufjs": "7.6.5"
|
|
100
|
+
}
|
|
101
|
+
}
|