portable-agent-layer 0.109.0 → 0.110.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/README.md CHANGED
@@ -90,7 +90,7 @@ pal cli status # check your setup
90
90
  | `pal cli patterns` | How you work across projects, read from the event log |
91
91
  | `pal cli content preserve` | Copy every Claude Code transcript, gzipped, into `memory/content/` before Claude Code deletes it (30 days by default). Runs on its own whenever a session closes; the content store never leaves the machine, so `export` skips it |
92
92
  | `pal cli events discord-nag <sent\|answered>` | For a bot outside PAL: record a nag it sent (`--kind <kind>`), or the answer to one and how long it took (`--after-sec <n>`), in the local event log |
93
- | `pal cli infer` | One model call for scripts and bots, on the active agent's route: prompt on stdin, reply on stdout. `--tier small\|medium`, `--system <file>`, `--schema <file>` for a JSON reply checked against it, `--timeout <seconds>`. The call loads no PAL hooks or context; exits 1 with the reason when no route answers |
93
+ | `pal cli infer` | One model call for scripts and bots, on the active agent's route: prompt on stdin, reply on stdout. `--tier small\|medium\|large\|flagship` (on Claude: Haiku, Sonnet, Opus, Fable; on Codex: Luna, Sol, Sol at high effort, Astra; on Antigravity: Flash at low, medium and high effort, and no flagship; an agent without the tier asked refuses it), `--system <file>`, `--schema <file>` for a JSON reply checked against it, `--timeout <seconds>`. The call loads no PAL hooks or context; exits 1 with the reason when no route answers |
94
94
  | `pal cli actor [label <name>]` | Show or rename the actor — who caused a record. Travels with an export, so a shared memory can tell two people apart |
95
95
  | `pal cli machine [label <name>]` | Show or rename this install — where a record was written. Never leaves the machine |
96
96
  | `pal cli telos` | Which TELOS topics are answered, in interview order, and which one comes next |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: scientific-research
3
3
  license: MIT
4
- description: "Answers scientific questions from trustworthy primary sources only (PubMed-indexed peer-reviewed literature, clinical guidelines from ERC, ESC, AHA, CDC and specialty societies, NEJM, The Lancet, JAMA, BMJ, UpToDate when accessible), with every claim cited to a source that was actually read, graded by evidence level and year, and cross-checked by a verifier agent. Use for EVERY health, medical, nutrition, drug, supplement, symptom, disease, treatment, diagnosis, exercise-physiology or other empirical scientific question, including 'is it safe to', 'does X work', 'what does the evidence say', 'side effects of', 'how much should I take' and 'what do the guidelines recommend'. Never answer such questions from memory; route them here."
4
+ description: "Answers scientific questions from trustworthy primary sources only (PubMed-indexed peer-reviewed literature, clinical guidelines from ERC, ESC, AHA, CDC and specialty societies, NEJM, The Lancet, JAMA, BMJ, UpToDate when accessible), with every claim cited to a source that was actually read, graded by evidence level and year, its funding and any vendor ties named, and cross-checked by a verifier agent. Excludes preprints and MDPI journals and prefers SJR Q1 journals. Use for EVERY health, medical, nutrition, drug, supplement, symptom, disease, treatment, diagnosis, exercise-physiology or other empirical scientific question, including 'is it safe to', 'does X work', 'what does the evidence say', 'side effects of', 'how much should I take' and 'what do the guidelines recommend'. Never answer such questions from memory; route them here."
5
5
  argument-hint: <scientific or health question>
6
6
  metadata:
7
7
  source: portable-agent-layer
@@ -57,9 +57,26 @@ specialty society (WHO, NICE, EMA, FDA and their peers). Label each such
57
57
  source in the answer as "outside the core list".
58
58
 
59
59
  Not sources, under any circumstances: blogs, news sites, forums, supplement or
60
- vendor sites, encyclopedias, press releases, lecture slides, model memory. Preprints
61
- may be mentioned only when the answer labels them "preprint, not peer-reviewed" and
62
- no peer-reviewed source covers the claim.
60
+ vendor sites, encyclopedias, press releases, lecture slides, model memory.
61
+
62
+ Excluded even when they look peer-reviewed:
63
+
64
+ - **Preprints and repositories**: bioRxiv, medRxiv, PsyArXiv, arXiv, SSRN, Research
65
+ Square, OSF Preprints, Preprints.org and any other server of unreviewed
66
+ manuscripts. Do not search them. PubMed and Europe PMC also index preprints, so
67
+ add `NOT preprint[pt]` to PubMed queries and drop any record whose publication type
68
+ is Preprint.
69
+ - **MDPI journals** (Nutrients, Medicina, IJMS, Healthcare and the rest), identified
70
+ by publisher, not by title: a DOI starting with `10.3390` or a page on `mdpi.com`.
71
+ Add `-site:mdpi.com` to web searches.
72
+
73
+ ### Journal rank
74
+
75
+ Prefer journals ranked Q1 by SCImago (SJR, `scimagojr.com`) in the subject category
76
+ that fits the question. Rank breaks ties within one evidence level and never
77
+ outranks study design: a Q2 RCT beats a Q1 observational study. Cite a journal below
78
+ Q1 only when no Q1 source covers the claim, and label it with its quartile, for
79
+ example "SJR Q2". Guidelines and official bodies are not ranked.
63
80
 
64
81
  ## Citation and evidence rules
65
82
 
@@ -74,12 +91,23 @@ For every claim record and later print:
74
91
  review.
75
92
  - **Year** of publication or of the guideline edition, so stale evidence is visible.
76
93
  When a newer edition of a guideline exists, cite the newer one.
94
+ - **Journal quartile**: the SJR quartile of the journal, for every journal article.
95
+ - **Funding and conflicts of interest**, read from the source's Funding, Acknowledgements
96
+ and Competing Interests sections, never inferred. Name the funders, then add one label:
97
+ - **vendor-funded**: a funder sells the product or intervention studied, or is a
98
+ trade group of its sellers (manufacturer, supplement brand, food or drug industry
99
+ association).
100
+ - **vendor-linked authors**: the funding is independent, but an author is employed,
101
+ paid or holds shares in such a company.
102
+ - **funding not stated**: the opened source has no funding statement.
103
+ - **funding not accessible**: the statement sits behind a paywall that did not open.
77
104
 
78
105
  Apply these tests before keeping a claim:
79
106
 
80
107
  1. Does the opened source state this claim, in this population, at this dose or in
81
108
  this setting? Paraphrase the source, do not extrapolate from it.
82
- 2. Does the source belong to the allowed set? If not, discard the claim.
109
+ 2. Does the source belong to the allowed set, and is it neither a preprint nor an MDPI
110
+ journal? If not, discard the claim.
83
111
  3. Is the number or direction of effect quoted exactly as the source gives it
84
112
  (effect size, confidence interval, absolute versus relative risk)?
85
113
 
@@ -144,12 +172,20 @@ Rules:
144
172
  - Search only your lane's domains (use site: restricted queries). Open each
145
173
  candidate and read the relevant section before using it.
146
174
  - Prefer the newest guideline edition, then systematic reviews and meta-analyses,
147
- then RCTs, then observational work.
175
+ then RCTs, then observational work. Within one level, prefer journals ranked SJR
176
+ Q1 in the question's subject category.
177
+ - Never search or cite preprint servers (bioRxiv, medRxiv, PsyArXiv, arXiv, SSRN,
178
+ Research Square, OSF Preprints, Preprints.org). Add NOT preprint[pt] to PubMed
179
+ queries and drop any record typed Preprint.
180
+ - Never cite an MDPI journal: a DOI starting with 10.3390 or a page on mdpi.com. Add
181
+ -site:mdpi.com to web searches.
148
182
  - For each claim return: the claim in one sentence, quoted as the source states it;
149
183
  population and setting; effect size with confidence interval when given; evidence
150
184
  level (guideline recommendation with class/level or GRADE, meta-analysis, RCT,
151
185
  observational, expert opinion); year; full citation (authors or body, title,
152
- journal or body, year, PMID or DOI or URL).
186
+ journal or body, year, PMID or DOI or URL); the journal's SJR quartile; funding:
187
+ the funders named in the source and one label, vendor-funded, vendor-linked
188
+ authors, funding not stated or funding not accessible.
153
189
  - Lane D only: if UpToDate does not open or shows a paywall, return the single line
154
190
  "UpToDate inaccessible" and nothing else.
155
191
  - Do not fill gaps from memory. If your lane has nothing on a sub-question, say
@@ -180,8 +216,15 @@ For every claim:
180
216
  numbers. Mark "confirmed" or "could not verify: <what differs>".
181
217
  3. Confirm the source is in the allowed set: PubMed-indexed peer-reviewed journal,
182
218
  ERC/ESC/AHA/CDC or another official health body or specialty society, NEJM, The
183
- Lancet, JAMA, BMJ, or UpToDate. Otherwise mark "could not verify: source not
184
- allowed".
219
+ Lancet, JAMA, BMJ, or UpToDate. A preprint, a record typed Preprint in PubMed, or
220
+ an MDPI journal (DOI starting with 10.3390) is never allowed. Otherwise mark
221
+ "could not verify: source not allowed".
222
+ 4. Check the journal's SJR quartile on scimagojr.com and correct it when the lane
223
+ got it wrong.
224
+ 5. Open the source's Funding and Competing Interests sections and confirm the
225
+ funders and the label (vendor-funded, vendor-linked authors, funding not stated,
226
+ funding not accessible). Correct the label when it differs, and flag every
227
+ vendor-funded or vendor-linked study.
185
228
 
186
229
  Then return three sections:
187
230
  - Confirmed matches: claims supported by two or more independent sources, with
@@ -203,8 +246,10 @@ Then:
203
246
  1. **Bottom line** — two to four sentences answering the question directly, with
204
247
  citation numbers.
205
248
  2. **Key findings** — one bullet per claim: the claim, inline citation number(s)
206
- `[n]`, evidence level, year. Group by sub-question when there are several. Mark
207
- any source outside the core list.
249
+ `[n]`, evidence level, year, funding label. Write **vendor-funded** or
250
+ **vendor-linked authors** in bold and name the company. Group by sub-question
251
+ when there are several. Mark any source outside the core list and any journal
252
+ below SJR Q1 with its quartile.
208
253
  3. **Agreement and contradictions** — the verifier's confirmed matches and
209
254
  contradictions, each contradiction with who says what and the likely reason.
210
255
  4. **What is uncertain / not found** — sub-questions with no trustworthy evidence
@@ -214,7 +259,8 @@ Then:
214
259
  5. **Method** — one line: parallel subagents or sequential lanes; UpToDate read or
215
260
  inaccessible; number of sources opened.
216
261
  6. **References** — numbered list matching the inline numbers: authors or body,
217
- title, journal or body, year, PMID or DOI or URL.
262
+ title, journal or body, year, PMID or DOI or URL, SJR quartile, and funding (the
263
+ funders and the label).
218
264
 
219
265
  For a question about the user's own health, add exactly one plain sentence after the
220
266
  Bottom line: this is a summary of published evidence, not individual medical advice,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.109.0",
3
+ "version": "0.110.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/infer.ts CHANGED
@@ -136,7 +136,7 @@ export const inferCommand = leaf({
136
136
  tier: {
137
137
  type: "string",
138
138
  value: "<tier>",
139
- description: `Model size: ${INFERENCE_TIERS.join(" or ")} (default small)`,
139
+ description: `Model size: ${INFERENCE_TIERS.join(", ")} (default small)`,
140
140
  },
141
141
  system: { type: "string", value: "<file>", description: "System prompt file" },
142
142
  schema: {
@@ -62,6 +62,7 @@ import { logDebug, logError } from "./log";
62
62
  import {
63
63
  type FixedModelRoute,
64
64
  type InferenceTier,
65
+ inferenceEffort,
65
66
  inferenceModel,
66
67
  isFixedModelRoute,
67
68
  } from "./models";
@@ -224,10 +225,21 @@ export function plannedInferenceRoute(
224
225
  return { agent: previewRoute().agent, ...plannedRoute({ user: "", tier, caller }) };
225
226
  }
226
227
 
228
+ function routeWithModel(
229
+ opts: InferenceOptions
230
+ ): ReturnType<typeof plannedRoute> | { error: string } {
231
+ try {
232
+ return plannedRoute(opts);
233
+ } catch (err) {
234
+ return { error: err instanceof Error ? err.message : String(err) };
235
+ }
236
+ }
237
+
227
238
  export async function inference(opts: InferenceOptions): Promise<InferenceResult> {
239
+ const planned = routeWithModel(opts);
240
+ if ("error" in planned) return { success: false, error: planned.error };
228
241
  const started = performance.now();
229
242
  const result = await checkedInference(opts);
230
- const planned = plannedRoute(opts);
231
243
  logInferenceCall(opts, planned, result, performance.now() - started);
232
244
  return { ...result, model: planned.model ?? undefined };
233
245
  }
@@ -524,9 +536,15 @@ export function buildCodexArgs(opts: InferenceOptions): string[] {
524
536
  "--ephemeral",
525
537
  "--json",
526
538
  ...(opts.web ? ["-c", "web_search=live"] : []),
539
+ ...codexEffortArgs(opts),
527
540
  ];
528
541
  }
529
542
 
543
+ function codexEffortArgs(opts: InferenceOptions): string[] {
544
+ const effort = inferenceEffort("codex-spawn", opts.tier);
545
+ return effort ? ["-c", `model_reasoning_effort="${effort}"`] : [];
546
+ }
547
+
530
548
  interface CodexItem {
531
549
  type?: string;
532
550
  text?: string;
@@ -1124,31 +1142,38 @@ async function inferenceViaApi(opts: InferenceOptions): Promise<InferenceResult>
1124
1142
  // structured-output schema for JSON-mode callers.
1125
1143
  // ─────────────────────────────────────────────────────────────────────────────
1126
1144
 
1145
+ export function buildOpenAiBody(opts: InferenceOptions): Record<string, unknown> {
1146
+ const { system, user, maxTokens = 500, jsonSchema } = opts;
1147
+ const messages: Array<{ role: string; content: string }> = [];
1148
+ if (system) messages.push({ role: "system", content: system });
1149
+ messages.push({ role: "user", content: user });
1150
+
1151
+ const body: Record<string, unknown> = {
1152
+ model: modelFor("openai-api", opts),
1153
+ max_tokens: maxTokens,
1154
+ messages,
1155
+ };
1156
+ const effort = inferenceEffort("openai-api", opts.tier);
1157
+ if (effort) body.reasoning_effort = effort;
1158
+ if (jsonSchema) {
1159
+ body.response_format = {
1160
+ type: "json_schema",
1161
+ json_schema: { name: "structured_response", strict: true, schema: jsonSchema },
1162
+ };
1163
+ }
1164
+ return body;
1165
+ }
1166
+
1127
1167
  async function inferenceViaOpenAiApi(opts: InferenceOptions): Promise<InferenceResult> {
1128
1168
  const apiKey = withPalEnv().PAL_OPENAI_API_KEY;
1129
1169
  if (!apiKey) return { success: false };
1130
1170
 
1131
- const { system, user, maxTokens = 500, timeout = 15000, jsonSchema } = opts;
1171
+ const { timeout = 15000 } = opts;
1132
1172
 
1133
1173
  try {
1134
1174
  const controller = new AbortController();
1135
1175
  const timer = setTimeout(() => controller.abort(), timeout);
1136
-
1137
- const messages: Array<{ role: string; content: string }> = [];
1138
- if (system) messages.push({ role: "system", content: system });
1139
- messages.push({ role: "user", content: user });
1140
-
1141
- const body: Record<string, unknown> = {
1142
- model: modelFor("openai-api", opts),
1143
- max_tokens: maxTokens,
1144
- messages,
1145
- };
1146
- if (jsonSchema) {
1147
- body.response_format = {
1148
- type: "json_schema",
1149
- json_schema: { name: "structured_response", strict: true, schema: jsonSchema },
1150
- };
1151
- }
1176
+ const body = buildOpenAiBody(opts);
1152
1177
 
1153
1178
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
1154
1179
  method: "POST",
@@ -6,11 +6,13 @@ import type { AgentType } from "./agent";
6
6
 
7
7
  export const HAIKU_MODEL = "claude-haiku-4-5-20251001";
8
8
  export const HAIKU_5_5_MODEL = "claude-haiku-5-5";
9
- export const SONNET_MODEL = "claude-sonnet-5";
10
- export const FABLE_MODEL = "claude-fable-5";
9
+ export const SONNET_MODEL = "claude-sonnet-5-5";
10
+ export const OPUS_MODEL = "claude-opus-5-5";
11
+ export const FABLE_MODEL = "claude-fable-5-1";
11
12
 
12
- export const INFERENCE_TIERS = ["small", "medium"] as const;
13
+ export const INFERENCE_TIERS = ["small", "medium", "large", "flagship"] as const;
13
14
  export type InferenceTier = (typeof INFERENCE_TIERS)[number];
15
+ type ReasoningEffort = "low" | "medium" | "high";
14
16
 
15
17
  export type FixedModelRoute =
16
18
  | "claude-spawn"
@@ -19,24 +21,37 @@ export type FixedModelRoute =
19
21
  | "openai-api"
20
22
  | "antigravity-spawn";
21
23
 
22
- /** `large` is the flagship that authors skills and subagents; without one, the agent authors inline. */
23
- type RouteModels = Record<InferenceTier, string> & { large?: string };
24
+ type RouteModels = Record<"small" | "medium" | "large", string> & { flagship?: string };
24
25
 
25
26
  const ANTHROPIC_MODELS: RouteModels = {
26
27
  small: HAIKU_MODEL,
27
28
  medium: SONNET_MODEL,
28
- large: FABLE_MODEL,
29
+ large: OPUS_MODEL,
30
+ flagship: FABLE_MODEL,
29
31
  };
30
32
  const OPENAI_MODELS: RouteModels = {
31
33
  small: "gpt-6-luna",
32
34
  medium: "gpt-6-sol",
33
- large: "gpt-6-astra",
35
+ large: "gpt-6-sol",
36
+ flagship: "gpt-6-astra",
34
37
  };
35
- /** An Antigravity subagent's `model:` takes only a tier name, so `large` is `pro`. */
36
38
  const GEMINI_MODELS: RouteModels = {
37
39
  small: "gemini-3.8-flash-low",
38
- medium: "gemini-3.8-flash-high",
39
- large: "pro",
40
+ medium: "gemini-3.8-flash-medium",
41
+ large: "gemini-3.8-flash-high",
42
+ };
43
+
44
+ const OPENAI_EFFORTS: Partial<Record<InferenceTier, ReasoningEffort>> = { large: "high" };
45
+ const INFERENCE_EFFORTS: Partial<
46
+ Record<FixedModelRoute, Partial<Record<InferenceTier, ReasoningEffort>>>
47
+ > = {
48
+ "codex-spawn": OPENAI_EFFORTS,
49
+ "openai-api": OPENAI_EFFORTS,
50
+ };
51
+
52
+ /** An Antigravity subagent's `model:` takes only a tier name, never a model id. */
53
+ const SUBAGENT_FLAGSHIP: Partial<Record<FixedModelRoute, string>> = {
54
+ "antigravity-spawn": "pro",
40
55
  };
41
56
 
42
57
  /**
@@ -63,9 +78,13 @@ export function isFixedModelRoute(route: string): route is FixedModelRoute {
63
78
  */
64
79
  const ANTHROPIC_ON_HAIKU_5_5 = new Set(["rule-drafter", "correction-kind"]);
65
80
 
66
- function evaluatedModel(models: RouteModels, caller?: string): string | undefined {
67
- const onAnthropic = models === ANTHROPIC_MODELS;
68
- return onAnthropic && caller && ANTHROPIC_ON_HAIKU_5_5.has(caller)
81
+ function evaluatedModel(
82
+ models: RouteModels,
83
+ tier: InferenceTier,
84
+ caller?: string
85
+ ): string | undefined {
86
+ const onAnthropicSmall = models === ANTHROPIC_MODELS && tier === "small";
87
+ return onAnthropicSmall && caller && ANTHROPIC_ON_HAIKU_5_5.has(caller)
69
88
  ? HAIKU_5_5_MODEL
70
89
  : undefined;
71
90
  }
@@ -76,7 +95,17 @@ export function inferenceModel(
76
95
  caller?: string
77
96
  ): string {
78
97
  const models = INFERENCE_MODELS[route];
79
- return evaluatedModel(models, caller) ?? models[tier];
98
+ const model = evaluatedModel(models, tier, caller) ?? models[tier];
99
+ if (!model) throw new Error(`${route} has no ${tier} model`);
100
+ return model;
101
+ }
102
+
103
+ /** The reasoning effort a tier asks of a model it shares with another tier; undefined keeps the model's default. */
104
+ export function inferenceEffort(
105
+ route: FixedModelRoute,
106
+ tier: InferenceTier = "small"
107
+ ): ReasoningEffort | undefined {
108
+ return INFERENCE_EFFORTS[route]?.[tier];
80
109
  }
81
110
 
82
111
  const AGENT_SPAWN_ROUTE: Partial<Record<AgentType, FixedModelRoute>> = {
@@ -88,11 +117,13 @@ const AGENT_SPAWN_ROUTE: Partial<Record<AgentType, FixedModelRoute>> = {
88
117
  /**
89
118
  * The model an agent's `skill-author` / `subagent-author` runs on, or undefined
90
119
  * when `create-skill` and `create-subagent` should author inline. A new provider
91
- * needs a `large` model on its route and a platform block in assets/agents/*-author.md.
120
+ * needs a flagship model on its route and a platform block in assets/agents/*-author.md.
92
121
  */
93
122
  export function flagshipAuthorModel(agent: AgentType): string | undefined {
94
123
  const route = AGENT_SPAWN_ROUTE[agent];
95
- return route ? INFERENCE_MODELS[route].large : undefined;
124
+ return route
125
+ ? (SUBAGENT_FLAGSHIP[route] ?? INFERENCE_MODELS[route].flagship)
126
+ : undefined;
96
127
  }
97
128
 
98
129
  export interface ModelPricing {
@@ -131,18 +162,31 @@ const withoutWritePremium = (
131
162
  cacheRead,
132
163
  });
133
164
 
165
+ const withWritePremium = (
166
+ input: number,
167
+ cacheRead: number,
168
+ output: number
169
+ ): ModelPricing => ({
170
+ input,
171
+ output,
172
+ cacheWrite5m: input * 1.25,
173
+ cacheWrite1h: input * 1.25,
174
+ cacheRead,
175
+ });
176
+
134
177
  /**
135
178
  * Standard-tier rates from https://developers.openai.com/api/docs/pricing; prompts over
136
- * 272K tokens cost more, and PAL's never get there. OpenAI bills no cache-write premium.
179
+ * 272K tokens cost more, and PAL's never get there. GPT-6 and GPT-5.6 bill cache writes
180
+ * at 1.25x input; older models publish no cache-write price, so writes bill as input.
137
181
  */
138
182
  const OPENAI_PRICING: Record<string, ModelPricing> = {
139
- "gpt-6-astra": withoutWritePremium(10, 1, 50),
140
- "gpt-6.1-sol": withoutWritePremium(2, 0.1, 10),
141
- "gpt-6-sol": withoutWritePremium(2, 0.2, 10),
142
- "gpt-6-luna": withoutWritePremium(0.1, 0.01, 0.5),
143
- "gpt-5.6-sol": withoutWritePremium(4, 0.4, 20),
144
- "gpt-5.6-terra": withoutWritePremium(2, 0.2, 12),
145
- "gpt-5.6-luna": withoutWritePremium(0.2, 0.02, 1.2),
183
+ "gpt-6-astra": withWritePremium(10, 1, 50),
184
+ "gpt-6.1-sol": withWritePremium(2, 0.1, 10),
185
+ "gpt-6-sol": withWritePremium(2, 0.2, 10),
186
+ "gpt-6-luna": withWritePremium(0.1, 0.01, 0.5),
187
+ "gpt-5.6-sol": withWritePremium(4, 0.4, 20),
188
+ "gpt-5.6-terra": withWritePremium(2, 0.2, 12),
189
+ "gpt-5.6-luna": withWritePremium(0.2, 0.02, 1.2),
146
190
  "gpt-5.5": withoutWritePremium(5, 0.5, 30),
147
191
  "gpt-5.5-pro": withoutWritePremium(30, 30, 180),
148
192
  "gpt-5.4": withoutWritePremium(2.5, 0.25, 15),
@@ -176,14 +220,14 @@ export const MODEL_PRICING: Record<string, ModelPricing> = {
176
220
  cacheWrite1h: 0.2,
177
221
  cacheRead: 0.01,
178
222
  },
179
- "claude-fable-5-1": {
223
+ [FABLE_MODEL]: {
180
224
  input: 10,
181
225
  output: 50,
182
226
  cacheWrite5m: 12.5,
183
227
  cacheWrite1h: 20,
184
228
  cacheRead: 0.25,
185
229
  },
186
- [FABLE_MODEL]: {
230
+ "claude-fable-5": {
187
231
  input: 10,
188
232
  output: 50,
189
233
  cacheWrite5m: 12.5,