@oh-my-pi/pi-catalog 17.2.14 → 17.3.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/src/models.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { buildModel } from "./build";
2
2
  import MODELS from "./models.json" with { type: "json" };
3
- import type { Api, KnownProvider, Model, ModelSpec, Usage } from "./types";
3
+ import type { Api, KnownProvider, Model, ModelSpec, TokenCost, Usage } from "./types";
4
4
 
5
5
  /**
6
6
  * Static bundled model registry loaded from `models.json`.
@@ -42,13 +42,27 @@ export function getBundledModels(provider: GeneratedProvider): Model<Api>[] {
42
42
  const models = getProviderModels(provider);
43
43
  return models ? (Array.from(models.values()) as Model<Api>[]) : [];
44
44
  }
45
+ function resolveTokenCost(cost: Model["cost"], promptInputTokens: number): TokenCost {
46
+ const longContext = cost.longContext;
47
+ if (!longContext) return cost;
48
+ return promptInputTokens > longContext.inputThreshold ? longContext : cost;
49
+ }
50
+
51
+ /** Price a prompt as fully uncached input under its active context-length tier. */
52
+ export function calculateUncachedInputCost(cost: Model["cost"], promptInputTokens: number): number {
53
+ const rates = resolveTokenCost(cost, promptInputTokens);
54
+ return (rates.input / 1_000_000) * promptInputTokens;
55
+ }
45
56
 
46
57
  export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
47
58
  const orchestration = usage.orchestration;
48
- usage.cost.input = (model.cost.input / 1000000) * (usage.input + (orchestration?.input ?? 0));
49
- usage.cost.output = (model.cost.output / 1000000) * (usage.output + (orchestration?.output ?? 0));
50
- usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * (usage.cacheRead + (orchestration?.cacheRead ?? 0));
51
- usage.cost.cacheWrite = cacheWriteCost(model, usage);
59
+ const promptInputTokens =
60
+ usage.input + usage.cacheRead + usage.cacheWrite + (orchestration?.input ?? 0) + (orchestration?.cacheRead ?? 0);
61
+ const rates = resolveTokenCost(model.cost, promptInputTokens);
62
+ usage.cost.input = (rates.input / 1000000) * (usage.input + (orchestration?.input ?? 0));
63
+ usage.cost.output = (rates.output / 1000000) * (usage.output + (orchestration?.output ?? 0));
64
+ usage.cost.cacheRead = (rates.cacheRead / 1000000) * (usage.cacheRead + (orchestration?.cacheRead ?? 0));
65
+ usage.cost.cacheWrite = cacheWriteCost(rates, usage);
52
66
  usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
53
67
  return usage.cost;
54
68
  }
@@ -56,12 +70,12 @@ export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage
56
70
  /**
57
71
  * Price cache-write tokens, honoring the TTL breakdown when the provider reports one.
58
72
  *
59
- * `model.cost.cacheWrite` is the 5-minute write rate (Anthropic bills 5m writes at
60
- * 1.25x base input). When `usage.cttl` is present the write mixes 5m and 1h
61
- * breakpoints — omp defaults to 1h retention on first-party Anthropic, and 1h writes
62
- * bill at 2x base input — so each component is priced at its own rate instead of the
63
- * flat 5m rate. Deriving 1h from `input * 2` (Anthropic's published multiplier) is
64
- * model-independent and stays correct even for legacy entries whose stored
73
+ * `rates.cacheWrite` is the 5-minute write rate (Anthropic bills 5m writes at
74
+ * 1.25x base input). When `usage.cttl` is present the write can mix 5m and 1h
75
+ * breakpoints, and 1h writes bill at 2x base input, so each component is
76
+ * priced at its own rate instead of the flat 5m rate. Deriving 1h from
77
+ * `input * 2` (Anthropic's published multiplier) is model-independent and
78
+ * stays correct even for legacy entries whose stored
65
79
  * `cacheWrite` scalar drifts from 1.25x input. Providers that omit `cttl`
66
80
  * (everyone but Anthropic) keep the flat-rate calculation.
67
81
  *
@@ -70,14 +84,14 @@ export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage
70
84
  * so any unattributed remainder is priced at the flat rate instead of being dropped:
71
85
  * a partial or stale breakdown must never make write tokens free.
72
86
  */
73
- function cacheWriteCost<TApi extends Api>(model: Model<TApi>, usage: Usage): number {
74
- const rate5m = model.cost.cacheWrite / 1000000;
87
+ function cacheWriteCost(rates: TokenCost, usage: Usage): number {
88
+ const rate5m = rates.cacheWrite / 1000000;
75
89
  const cttl = usage.cttl;
76
90
  if (!cttl) return rate5m * usage.cacheWrite;
77
91
  const fiveMinute = cttl.ephemeral5m ?? 0;
78
92
  const oneHour = cttl.ephemeral1h ?? 0;
79
93
  const residual = Math.max(0, usage.cacheWrite - fiveMinute - oneHour);
80
- return rate5m * (fiveMinute + residual) + ((model.cost.input * 2) / 1000000) * oneHour;
94
+ return rate5m * (fiveMinute + residual) + ((rates.input * 2) / 1000000) * oneHour;
81
95
  }
82
96
 
83
97
  /**
@@ -67,9 +67,11 @@ export function createReferenceResolver<TApi extends Api>(
67
67
  ? () => (lazyProviderReferences ??= providerReferenceSource())
68
68
  : () => providerReferenceSource;
69
69
  return (modelId: string) => {
70
- const providerRef = getProviderReferences().get(modelId);
70
+ const providerRefs = getProviderReferences();
71
+ const globalRefs = getGlobalReferences();
72
+ const providerRef = providerRefs.get(modelId);
71
73
  if (providerRef) return providerRef;
72
- const globalRef = getGlobalReferences().get(modelId);
74
+ const globalRef = globalRefs.get(modelId);
73
75
  return globalRef ? toModelSpec(globalRef as Model<TApi>) : undefined;
74
76
  };
75
77
  }
@@ -866,6 +866,44 @@ export function umansModelManagerOptions(config?: UmansModelManagerConfig): Mode
866
866
  // 1. OpenAI
867
867
  // ---------------------------------------------------------------------------
868
868
 
869
+ const OPENAI_API_BASE_URL = "https://api.openai.com/v1";
870
+ export const OPENAI_GPT_56_LONG_CONTEXT_COSTS = {
871
+ luna: {
872
+ inputThreshold: 272_000,
873
+ input: 0.4,
874
+ output: 1.8,
875
+ cacheRead: 0.04,
876
+ cacheWrite: 0.5,
877
+ },
878
+ sol: {
879
+ inputThreshold: 272_000,
880
+ input: 10,
881
+ output: 45,
882
+ cacheRead: 1,
883
+ cacheWrite: 12.5,
884
+ },
885
+ terra: {
886
+ inputThreshold: 272_000,
887
+ input: 4,
888
+ output: 18,
889
+ cacheRead: 0.4,
890
+ cacheWrite: 5,
891
+ },
892
+ } as const;
893
+ const OPENAI_GPT_56_SOL_STANDARD_COST = {
894
+ input: 5,
895
+ output: 30,
896
+ cacheRead: 0.5,
897
+ cacheWrite: 6.25,
898
+ longContext: OPENAI_GPT_56_LONG_CONTEXT_COSTS.sol,
899
+ } as const;
900
+ const OPENAI_GPT_56_CYBER_STANDARD_COST = {
901
+ input: 12.5,
902
+ output: 75,
903
+ cacheRead: 1.25,
904
+ cacheWrite: 15.625,
905
+ } as const;
906
+
869
907
  export interface OpenAIModelManagerConfig {
870
908
  apiKey?: string;
871
909
  baseUrl?: string;
@@ -876,7 +914,7 @@ export function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): Mo
876
914
  return createOpenAICompatibleModelManagerOptions({
877
915
  api: "openai-responses",
878
916
  providerId: "openai",
879
- defaultBaseUrl: "https://api.openai.com/v1",
917
+ defaultBaseUrl: OPENAI_API_BASE_URL,
880
918
  config,
881
919
  requireApiKey: true,
882
920
  filterModel: (_entry, model, references) => isLikelyOpenAIResponsesModelId(model.id, references),
@@ -884,6 +922,50 @@ export function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): Mo
884
922
  });
885
923
  }
886
924
 
925
+ /**
926
+ * Daybreak models are approval-gated first-party Responses models that are not
927
+ * yet present in stencil.so. Seed the documented aliases and current Cyber
928
+ * snapshot so fresh installs expose them without credentialed discovery.
929
+ */
930
+ export const OPENAI_DAYBREAK_CURATED_FALLBACK_MODELS: readonly ModelSpec<"openai-responses">[] = [
931
+ {
932
+ id: "daybreak-blue-latest",
933
+ name: "Daybreak Blue",
934
+ api: "openai-responses",
935
+ provider: "openai",
936
+ baseUrl: OPENAI_API_BASE_URL,
937
+ reasoning: true,
938
+ input: ["text", "image"],
939
+ cost: OPENAI_GPT_56_SOL_STANDARD_COST,
940
+ contextWindow: 1_050_000,
941
+ maxTokens: 128_000,
942
+ },
943
+ {
944
+ id: "daybreak-red-latest",
945
+ name: "Daybreak Red",
946
+ api: "openai-responses",
947
+ provider: "openai",
948
+ baseUrl: OPENAI_API_BASE_URL,
949
+ reasoning: true,
950
+ input: ["text", "image"],
951
+ cost: OPENAI_GPT_56_CYBER_STANDARD_COST,
952
+ contextWindow: 400_000,
953
+ maxTokens: 128_000,
954
+ },
955
+ {
956
+ id: "gpt-5.6-cyber",
957
+ name: "GPT-5.6 Cyber",
958
+ api: "openai-responses",
959
+ provider: "openai",
960
+ baseUrl: OPENAI_API_BASE_URL,
961
+ reasoning: true,
962
+ input: ["text", "image"],
963
+ cost: OPENAI_GPT_56_CYBER_STANDARD_COST,
964
+ contextWindow: 400_000,
965
+ maxTokens: 128_000,
966
+ },
967
+ ];
968
+
887
969
  /** First-party gpt-5.6 SKUs that accept `reasoning: { mode: "pro" }` on the Responses APIs. */
888
970
  const OPENAI_PRO_REASONING_BASE_IDS: Record<string, true> = {
889
971
  "gpt-5.6-luna": true,
package/src/types.ts CHANGED
@@ -155,6 +155,7 @@ export type OpenAIReasoningFormat = "openai" | "openrouter" | "zai" | "kimi" | "
155
155
  export type OpenAIReasoningDisableMode =
156
156
  | "omit"
157
157
  | "lowest-effort"
158
+ | "none-effort"
158
159
  | "openrouter-enabled-false"
159
160
  | "zai-thinking-disabled"
160
161
  | "qwen-enable-thinking-false"
@@ -190,13 +191,6 @@ export interface OpenAICompat {
190
191
  reasoningEffortMap?: Partial<Record<Effort, string>>;
191
192
  /** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
192
193
  supportsUsageInStreaming?: boolean;
193
- /**
194
- * Enable the Gemini thinking-loop guard (pi-ai stream layer) for this model.
195
- * Defaults to true when the model id classifies as the gemini family. Set
196
- * explicitly to cover an opaque OpenAI-compat proxy alias (e.g. `my-model`)
197
- * that routes to Gemini, or to false to opt a gemini-family id out.
198
- */
199
- enableGeminiThinkingLoopGuard?: boolean;
200
194
  /** Which field to use for max tokens. Default: auto-detected from URL. */
201
195
  maxTokensField?: "max_completion_tokens" | "max_tokens";
202
196
  /** Whether tool results require the `name` field. Default: auto-detected from URL. */
@@ -627,8 +621,6 @@ export interface ResolvedOpenAISharedCompat {
627
621
  isOpenRouterHost: boolean;
628
622
  /** Whether this endpoint needs a max-token field even when caller did not set one. */
629
623
  alwaysSendMaxTokens: boolean;
630
- /** See {@link OpenAICompat.enableGeminiThinkingLoopGuard}. Set by the builder from the family classifier. */
631
- enableGeminiThinkingLoopGuard?: boolean;
632
624
  openRouterRouting?: OpenAICompat["openRouterRouting"];
633
625
  /** Provider-specific wire model-id transform applied to the base id. */
634
626
  wireModelIdMode: "raw" | "firepass" | "fireworks" | "openrouter";
@@ -697,7 +689,6 @@ export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat &
697
689
  | "thinkingKeep"
698
690
  | "strictResponsesPairing"
699
691
  | "supportsImageDetailOriginal"
700
- | "enableGeminiThinkingLoopGuard"
701
692
  | "whenThinking"
702
693
  >
703
694
  > & {
@@ -819,6 +810,28 @@ export interface RemoteCompactionConfig<TApi extends Api = Api> {
819
810
  model?: string;
820
811
  }
821
812
 
813
+ /** Per-million-token rates for one model pricing tier. */
814
+ export interface TokenCost {
815
+ input: number;
816
+ output: number;
817
+ cacheRead: number;
818
+ cacheWrite: number;
819
+ }
820
+
821
+ /**
822
+ * Rates applied to the full request when its prompt exceeds `inputThreshold`.
823
+ * Prompt input is the sum of uncached, cached-read, cache-write, and
824
+ * provider-orchestration input tokens.
825
+ */
826
+ export interface LongContextTokenCost extends TokenCost {
827
+ inputThreshold: number;
828
+ }
829
+
830
+ /** Base token rates plus an optional long-context tier. */
831
+ export interface ModelCost extends TokenCost {
832
+ longContext?: LongContextTokenCost;
833
+ }
834
+
822
835
  // Model interface for the unified model system
823
836
  export interface Model<TApi extends Api = Api> {
824
837
  id: string;
@@ -864,12 +877,7 @@ export interface Model<TApi extends Api = Api> {
864
877
  gitlabDuoWorkflowRootNamespaceId?: string;
865
878
  /** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
866
879
  cursorMaxMode?: boolean;
867
- cost: {
868
- input: number; // $/million tokens
869
- output: number; // $/million tokens
870
- cacheRead: number; // $/million tokens
871
- cacheWrite: number; // $/million tokens
872
- };
880
+ cost: ModelCost;
873
881
  /** Premium Copilot requests charged per user-initiated request (defaults to 1). */
874
882
  premiumMultiplier?: number;
875
883
  contextWindow: number | null;