@oh-my-pi/pi-catalog 18.2.11 → 18.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 (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/THIRD-PARTY-NOTICES.txt +3 -3
  4. package/dist/types/compat/auth-ids.d.ts +1 -1
  5. package/dist/types/compat/fallback-credit.d.ts +7 -0
  6. package/dist/types/compat/output-limits.d.ts +5 -0
  7. package/dist/types/compat/provider-ids.d.ts +1 -1
  8. package/dist/types/compat/server-side-fallback.d.ts +7 -0
  9. package/dist/types/compat/types.d.ts +4 -0
  10. package/dist/types/discovery/cursor-proto.d.ts +1 -1
  11. package/dist/types/discovery/devin-proto.d.ts +1 -1
  12. package/dist/types/types.d.ts +37 -7
  13. package/package.json +8 -5
  14. package/src/compat/auth-ids.ts +1 -0
  15. package/src/compat/axes.ts +25 -0
  16. package/src/compat/fallback-credit.ts +13 -0
  17. package/src/compat/output-limits.ts +8 -0
  18. package/src/compat/provider-ids.ts +1 -0
  19. package/src/compat/resolve.ts +2 -0
  20. package/src/compat/rules/README.md +4 -0
  21. package/src/compat/rules/auth/anthropic.kdl +1 -0
  22. package/src/compat/rules/auth/apple.kdl +4 -0
  23. package/src/compat/rules/auth/openai-codex.kdl +1 -0
  24. package/src/compat/rules/auth/xai-oauth.kdl +1 -0
  25. package/src/compat/rules/classes/anthropic.kdl +46 -16
  26. package/src/compat/rules/classes/openai.kdl +3 -0
  27. package/src/compat/rules/providers/anthropic.kdl +15 -0
  28. package/src/compat/rules/providers/apple.kdl +8 -0
  29. package/src/compat/rules/providers/yolo-auto.kdl +8 -2
  30. package/src/compat/rules.json +1 -1
  31. package/src/compat/server-side-fallback.ts +13 -0
  32. package/src/compat/types.ts +4 -0
  33. package/src/discovery/codex.ts +29 -4
  34. package/src/discovery/cursor-proto.ts +1 -1
  35. package/src/discovery/devin-proto.ts +1 -1
  36. package/src/discovery/devin.ts +60 -1
  37. package/src/models.json +1 -1
  38. package/src/provider-models/cache-provider-id.ts +5 -0
  39. package/src/provider-models/openai-compat.ts +30 -7
  40. package/src/provider-models/special.ts +12 -2
  41. package/src/types.ts +39 -6
@@ -0,0 +1,13 @@
1
+ import type { Model } from "../types";
2
+ import { resolveCatalogPolicy } from "./catalog-policy";
3
+
4
+ /**
5
+ * Ordered model ids for Anthropic's server-side refusal `fallbacks` chain,
6
+ * authored on the `server-side-fallback-models` catalog axis. Empty when no
7
+ * rule assigns a chain (the model or host does not support the feature).
8
+ */
9
+ export function serverSideFallbackModels(model: Model): readonly string[] {
10
+ const value = resolveCatalogPolicy(model).serverSideFallbackModels;
11
+ if (!Array.isArray(value)) return [];
12
+ return value.filter((entry): entry is string => typeof entry === "string");
13
+ }
@@ -552,6 +552,10 @@ export interface CompiledAuthProvider {
552
552
  name: string;
553
553
  env?: { vars: string[] } | { hook: string };
554
554
  allowsMissingApiKey?: boolean;
555
+ /** Qualify credential and usage-report identity by org when an email may have multiple subscriptions. */
556
+ orgScopedIdentity?: boolean;
557
+ /** Environment variables carrying this provider's own OAuth bearer, excluding borrowed API-key aliases. */
558
+ oauthTokenEnv?: string[];
555
559
  /** APIs whose provider transport resolves credentials without a stored account. */
556
560
  nativeAuthApis?: string[];
557
561
  available?: boolean;
@@ -70,6 +70,7 @@ const codexModelEntrySchema = type({
70
70
  "prefer_websockets?": "unknown",
71
71
  "use_responses_lite?": "unknown",
72
72
  "tool_mode?": "unknown",
73
+ "available_access_programs?": "unknown",
73
74
  });
74
75
 
75
76
  const codexModelsResponseSchema = type({
@@ -161,7 +162,7 @@ export async function fetchCodexModels(options: CodexModelDiscoveryOptions): Pro
161
162
  continue;
162
163
  }
163
164
 
164
- const models = normalizeCodexModels(payload, baseUrl);
165
+ const models = normalizeCodexModels(payload, baseUrl, options.accountId);
165
166
  if (models === null) {
166
167
  continue;
167
168
  }
@@ -223,7 +224,11 @@ function normalizeClientVersion(value: unknown): string | undefined {
223
224
  return trimmed;
224
225
  }
225
226
 
226
- function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"openai-codex-responses">[] | null {
227
+ function normalizeCodexModels(
228
+ payload: unknown,
229
+ baseUrl: string,
230
+ accountId: string | undefined,
231
+ ): ModelSpec<"openai-codex-responses">[] | null {
227
232
  const parsedResponse = codexModelsResponseSchema(payload);
228
233
  if (parsedResponse instanceof type.errors) {
229
234
  return null;
@@ -250,10 +255,10 @@ function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"ope
250
255
  const normalized: NormalizedCodexModel[] = [];
251
256
  for (const parsed of parsedEntries) {
252
257
  const canonicalSlug = plainCounterpartForWorkerSlug(parsed.slug, bundledCodexModelIds) ?? parsed.slug;
253
- normalized.push(buildNormalizedCodexModel(parsed, parsed.slug, canonicalSlug, baseUrl));
258
+ normalized.push(buildNormalizedCodexModel(parsed, parsed.slug, canonicalSlug, baseUrl, accountId));
254
259
  const plainSlug = canonicalSlug !== parsed.slug ? canonicalSlug : null;
255
260
  if (plainSlug && !advertisedSlugs.has(plainSlug)) {
256
- normalized.push(buildNormalizedCodexModel(parsed, plainSlug, canonicalSlug, baseUrl));
261
+ normalized.push(buildNormalizedCodexModel(parsed, plainSlug, canonicalSlug, baseUrl, accountId));
257
262
  }
258
263
  }
259
264
 
@@ -288,6 +293,7 @@ function plainCounterpartForWorkerSlug(slug: string, bundledCodexModelIds: Reado
288
293
 
289
294
  interface ParsedCodexModelEntry {
290
295
  slug: string;
296
+ cyberPrograms: string[] | undefined;
291
297
  name: string;
292
298
  contextWindow: number | null;
293
299
  maxContextWindow: number | null;
@@ -316,8 +322,19 @@ function parseCodexModelEntry(entry: unknown): ParsedCodexModelEntry | null {
316
322
  return null;
317
323
  }
318
324
 
325
+ const programs = payload.available_access_programs;
326
+ let cyberPrograms: string[] | undefined;
327
+ if (programs !== null && typeof programs === "object" && "cyber" in programs && Array.isArray(programs.cyber)) {
328
+ cyberPrograms = [];
329
+ for (const program of programs.cyber) {
330
+ const name = toNonEmptyString(program);
331
+ if (name) cyberPrograms.push(name);
332
+ }
333
+ }
334
+
319
335
  return {
320
336
  slug,
337
+ cyberPrograms,
321
338
  name: toNonEmptyString(payload.display_name) ?? slug,
322
339
  contextWindow: toPositiveInt(payload.context_window),
323
340
  maxContextWindow: toPositiveInt(payload.max_context_window),
@@ -342,6 +359,7 @@ function buildNormalizedCodexModel(
342
359
  slug: string,
343
360
  canonicalSlug: string,
344
361
  baseUrl: string,
362
+ accountId: string | undefined,
345
363
  ): NormalizedCodexModel {
346
364
  // Codex discovery historically omitted `context_window` for GPT-5.6-family
347
365
  // SKUs (#5705); luna/sol/terra additionally floor the reported value because
@@ -371,6 +389,13 @@ function buildNormalizedCodexModel(
371
389
  api: "openai-codex-responses",
372
390
  provider: "openai-codex",
373
391
  baseUrl,
392
+ ...(accountId && accountId.trim().length > 0
393
+ ? {
394
+ accountAccess: {
395
+ [accountId]: parsed.cyberPrograms === undefined ? {} : { cyberPrograms: parsed.cyberPrograms },
396
+ },
397
+ }
398
+ : {}),
374
399
  reasoning: parsed.reasoning,
375
400
  input: parsed.input,
376
401
  // Codex discovery omits pricing; documented subscription credit-equivalent
@@ -1,7 +1,7 @@
1
1
  // @generated by packages/catalog/scripts/proto-parser.ts - DO NOT EDIT
2
2
 
3
3
  /**
4
- * Cursor agent protocol declarations used by Oh My Pi.
4
+ * Cursor agent protocol declarations used by omp.
5
5
  *
6
6
  * Each declaration retains only fields consumed by the client or its protocol tests.
7
7
  */
@@ -1,7 +1,7 @@
1
1
  // @generated by packages/catalog/scripts/proto-parser.ts - DO NOT EDIT
2
2
 
3
3
  /**
4
- * Devin protocol declarations used by Oh My Pi.
4
+ * Devin protocol declarations used by omp.
5
5
  *
6
6
  * Each declaration retains only fields consumed by the client or its protocol tests.
7
7
  */
@@ -453,6 +453,51 @@ function devinModelSpec(
453
453
  return spec;
454
454
  }
455
455
 
456
+ /**
457
+ * Lead chat uid of a Fusion pairing `fusion-<lead>[-fast]-sidekick-<sidekick>`.
458
+ * An exact live lead uid wins, so leads whose own uid ends in `-fast`
459
+ * (`swe-1-6-fast`) route as written. Otherwise `-fast` selects the lead's
460
+ * priority lane when the server lists one, then the standard lane.
461
+ *
462
+ * Returns `undefined` for uids that are not pairings and `null` for pairings
463
+ * whose lead is not live: the composite uid itself is never servable.
464
+ */
465
+ function devinFusionLeadUid(uid: string, liveUids: ReadonlyMap<string, unknown>): string | null | undefined {
466
+ if (!uid.startsWith("fusion-")) return undefined;
467
+ const cut = uid.indexOf("-sidekick-");
468
+ if (cut <= "fusion-".length) return undefined;
469
+ const lead = uid.slice("fusion-".length, cut);
470
+ if (liveUids.has(lead)) return lead;
471
+ if (lead.endsWith("-fast")) {
472
+ const base = lead.slice(0, -"-fast".length);
473
+ if (liveUids.has(`${base}-priority`)) return `${base}-priority`;
474
+ if (liveUids.has(base)) return base;
475
+ }
476
+ return null;
477
+ }
478
+
479
+ /**
480
+ * Point a Fusion pairing at its lead. omp runs only the lead (the sidekick is
481
+ * paired by the native client), so the limits and pricing a caller budgets
482
+ * against are the lead's, not the composite card's.
483
+ */
484
+ function routeDevinFusionLead(spec: ModelSpec<"devin-agent">, lead: ModelSpec<"devin-agent">): void {
485
+ spec.requestModelId = lead.id;
486
+ spec.reasoning = lead.reasoning;
487
+ spec.input = lead.input;
488
+ spec.supportsTools = lead.supportsTools;
489
+ spec.cost = lead.cost;
490
+ spec.contextWindow = lead.contextWindow;
491
+ spec.maxTokens = lead.maxTokens;
492
+ if (lead.compat?.supportsParallelToolCalls) {
493
+ spec.compat = { ...spec.compat, supportsParallelToolCalls: true };
494
+ } else if (spec.compat?.supportsParallelToolCalls) {
495
+ const { supportsParallelToolCalls: _, ...rest } = spec.compat;
496
+ if (Object.keys(rest).length > 0) spec.compat = rest;
497
+ else delete spec.compat;
498
+ }
499
+ }
500
+
456
501
  function normalizeDevinModels(
457
502
  configs: readonly ClientModelConfig[],
458
503
  baseUrlOverride: string | undefined,
@@ -461,6 +506,11 @@ function normalizeDevinModels(
461
506
  const specs: ModelSpec<"devin-agent">[] = [];
462
507
  const seen = new Set<string>();
463
508
  const lanes = new Map<string, DevinFamilyLane>();
509
+ const liveConfigs = new Map<string, ClientModelConfig>();
510
+ for (const config of configs) {
511
+ const uid = config.modelUid.trim();
512
+ if (!config.disabled && uid && !liveConfigs.has(uid)) liveConfigs.set(uid, config);
513
+ }
464
514
 
465
515
  for (const config of configs) {
466
516
  if (config.disabled) {
@@ -482,7 +532,16 @@ function normalizeDevinModels(
482
532
  // `fusion-sidekick-*`) that are themselves valid chat uids. Only the
483
533
  // former take the `AssignModel` path — sending a composite uid there 404s.
484
534
  const isAssignModelRouter = isRouter && (config.modelInfo?.harnessUids.length ?? 0) === 0;
485
- specs.push(devinModelSpec(config, uid, baseUrl, isAssignModelRouter));
535
+ // Fusion pairings are orchestrated by the native client: it runs the lead
536
+ // model as an ordinary chat uid and pairs a sidekick locally. The server
537
+ // has no provider for the composite uid itself (`permission_denied: no API
538
+ // providers are available`), so the chat request carries the lead uid and
539
+ // a pairing without a live lead is not listed.
540
+ const lead = devinFusionLeadUid(uid, liveConfigs);
541
+ if (lead === null) continue;
542
+ const spec = devinModelSpec(config, uid, baseUrl, isAssignModelRouter);
543
+ if (lead !== undefined) routeDevinFusionLead(spec, devinModelSpec(liveConfigs.get(lead)!, lead, baseUrl, false));
544
+ specs.push(spec);
486
545
  // A router is a server-side dispatcher, not an effort tier: it stays a
487
546
  // standalone model even when upstream files it under a family.
488
547
  if (!isRouter) {