pi-meta-oauth 0.7.3 → 0.7.5

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
@@ -48,7 +48,7 @@ Prefer a static key instead? Set `META_API_KEY` (or `MODEL_API_KEY`) and skip
48
48
 
49
49
  ## Models
50
50
 
51
- Fallback models use a 1,048,576-token context window, up to 256K output tokens, image input, and reasoning levels `minimal`, `low`, `medium`, `high`, and `xhigh` (`muse-spark-1.3` additionally supports `max`; `muse-spark-1.3-contributor` does too when [opted in](#contributor-max-opt-in)).
51
+ Bundled models use a 1,048,576-token context window, up to 256K output tokens, image input, and reasoning levels `minimal`, `low`, `medium`, `high`, and `xhigh`. `muse-spark-1.3`, `muse-wonder-1.0`, and `muse-wonder-full-1.0` additionally support `max`; their `-contributor` variants do too when [opted in](#contributor-max-opt-in). Meta's catalog endpoint has returned bare model IDs without names, prices, or modalities since at least 2026-10-23, so these bundled definitions supply that metadata for known IDs; an unknown ID still appears with default values.
52
52
 
53
53
  | id | pricing (input/output/cached) $/M |
54
54
  | --- | --- |
@@ -57,6 +57,12 @@ Fallback models use a 1,048,576-token context window, up to 256K output tokens,
57
57
  | `muse-spark-1.2` | 1.25 / 4.25 / 0.15 |
58
58
  | `muse-spark-1.2-contributor` | 0.10 / 0.20 / 0.002 |
59
59
  | `muse-spark-1.1` | 1.25 / 4.25 / 0.15 |
60
+ | `muse-wonder-1.0` | 4.00 / 20.00 / 0.20 |
61
+ | `muse-wonder-1.0-contributor` | no published per-token rate (Muse Code subscription); Pi shows $0 |
62
+ | `muse-wonder-full-1.0` | no published per-token rate (Muse Code subscription); Pi shows $0 |
63
+ | `muse-wonder-full-1.0-contributor` | no published per-token rate (Muse Code subscription); Pi shows $0 |
64
+
65
+ The catalog also lists Meta's non-chat models, which are billed per generated image, per hour of audio, or per video frame rather than per token: the `muse-image-*`, `muse-voice-*`, and `sam-*` families. The extension hides them from the chat model list, including entries an earlier release cached in `models-store.json`.
60
66
 
61
67
  > **Contributor-model privacy:** discounted contributor models allow Meta to use prompts and completions for product improvement, including training future Meta models. Use a standard model such as `muse-spark-1.3` if you do not want the contributor terms. See [Meta's model documentation](https://dev.meta.ai/docs/models).
62
68
 
@@ -75,7 +81,7 @@ Then open a **new** terminal and restart Pi. Shells that were already open don't
75
81
 
76
82
  The header is the same on every OS. The captured string names `linux-x86_64`, but the platform segment doesn't appear to be checked: it was accepted from Windows (2026-09-26), and oh-my-pi sends the same fixed string on every platform.
77
83
 
78
- With the flag set, the extension exposes `max` on `muse-spark-1.3-contributor` and sends the captured Muse `User-Agent` on that model's requests to `https://api.meta.ai/v1` only. Other models, proxies and custom `baseUrl`s, and any `User-Agent` you set yourself are left untouched. Without the flag, Contributor `max` is hidden and Pi's own `User-Agent` is sent.
84
+ With the flag set, the extension exposes `max` on the gated contributor models (`muse-spark-1.3-contributor`, `muse-wonder-1.0-contributor`, and `muse-wonder-full-1.0-contributor`) and sends the captured Muse `User-Agent` on those models' requests to `https://api.meta.ai/v1` only. Other models, proxies and custom `baseUrl`s, and any `User-Agent` you set yourself are left untouched. Without the flag, Contributor `max` is hidden and Pi's own `User-Agent` is sent.
79
85
 
80
86
  > **Warning:** this makes Pi identify as Meta's first-party Muse client. It relies on undocumented server behavior, is not supported by Meta, may stop working without notice, and may conflict with Meta's terms. Enable it only if you accept that risk for your account.
81
87
 
@@ -135,10 +141,11 @@ also bypasses isolation and can run these probes when credentials are available.
135
141
  The cache probe makes two billable Responses requests plus one retry on a cache
136
142
  miss. The fingerprint probe makes two tiny Contributor-`max` requests; it sends
137
143
  the header directly, independently of `META_MUSE_USER_AGENT`. The catalog probe
138
- runs when `PI_META_LIVE_API_KEY` is set. Cache and fingerprint probes resolve
139
- credentials from `PI_META_LIVE_API_KEY`, `META_API_KEY`, `MODEL_API_KEY`, or an
140
- unexpired minted key in `~/.pi/agent/auth.json`, in that order. Probes skip when
141
- no usable credential exists.
144
+ makes one catalog request and fails when a live model ID has neither a bundled
145
+ definition nor a non-chat prefix. All three probes resolve credentials from
146
+ `PI_META_LIVE_API_KEY`, `META_API_KEY`, `MODEL_API_KEY`, or an unexpired minted
147
+ key in `~/.pi/agent/auth.json`, in that order, and skip when no usable
148
+ credential exists.
142
149
 
143
150
  ```bash
144
151
  bun run test:live tests/meta-cache.test.ts
@@ -15,7 +15,11 @@ export {
15
15
  MUSE_USER_AGENT_ENV_VAR,
16
16
  } from "../src/meta/constants.ts";
17
17
  export { refreshMetaModels } from "../src/meta/model-store.ts";
18
- export { metaFallbackCost, toProviderModels } from "../src/meta/models.ts";
18
+ export {
19
+ isNonChatModelId,
20
+ metaFallbackCost,
21
+ toProviderModels,
22
+ } from "../src/meta/models.ts";
19
23
  export { isMuseUserAgentEnabled } from "../src/meta/muse-policy.ts";
20
24
  export {
21
25
  loginMeta,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-meta-oauth",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Meta Model API OAuth provider for pi",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -32,13 +32,13 @@
32
32
  "image": "https://raw.githubusercontent.com/BlockedPath/pi-meta-oauth/main/preview.png"
33
33
  },
34
34
  "peerDependencies": {
35
- "@earendil-works/pi-ai": ">=0.83.0 <0.88.0",
36
- "@earendil-works/pi-coding-agent": ">=0.83.0 <0.88.0"
35
+ "@earendil-works/pi-ai": ">=0.83.0 <2.0.0",
36
+ "@earendil-works/pi-coding-agent": ">=0.83.0 <2.0.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@biomejs/biome": "2.5.15",
40
- "@earendil-works/pi-ai": "0.87.1",
41
- "@earendil-works/pi-coding-agent": "0.87.1",
40
+ "@earendil-works/pi-ai": "1.4.0",
41
+ "@earendil-works/pi-coding-agent": "1.4.0",
42
42
  "bun-types": "latest",
43
43
  "typescript": "^5.9.0"
44
44
  },
@@ -21,6 +21,24 @@ const CONTRIBUTOR_COST = {
21
21
  cacheRead: 0.002,
22
22
  cacheWrite: 0,
23
23
  };
24
+ /** Meta Model API list price for muse-wonder-1.0 (dev.meta.ai pricing page, 2026-10-23). */
25
+ const WONDER_COST = {
26
+ input: 4,
27
+ output: 20,
28
+ cacheRead: 0.2,
29
+ cacheWrite: 0,
30
+ };
31
+ /**
32
+ * Muse Wonder Full and the Wonder Contributor variants are reachable only
33
+ * through a Muse Code subscription; Meta publishes no per-token rate for
34
+ * them, so Pi shows $0. This is a representation of "unpriced", not a fact.
35
+ */
36
+ const SUBSCRIPTION_ONLY_COST = {
37
+ input: 0,
38
+ output: 0,
39
+ cacheRead: 0,
40
+ cacheWrite: 0,
41
+ };
24
42
  const UNKNOWN_MODEL_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
25
43
  const THINKING_LEVELS = [
26
44
  "minimal",
@@ -32,7 +50,13 @@ const THINKING_LEVELS = [
32
50
  ] as const;
33
51
  type ThinkingLevelMap = NonNullable<MetaProviderModel["thinkingLevelMap"]>;
34
52
 
35
- /** Data-only definitions; callers always receive fresh model objects. */
53
+ /**
54
+ * Data-only definitions; callers always receive fresh model objects. The live
55
+ * catalog has carried no `metadata["muse-code"]` block since at least
56
+ * 2026-10-23, so names, prices, and image input for known IDs come from here.
57
+ * Spark first keeps the long-standing default model; after the first refresh
58
+ * the catalog's own order wins anyway.
59
+ */
36
60
  const FALLBACK_DEFINITIONS = [
37
61
  {
38
62
  id: "muse-spark-1.3",
@@ -64,8 +88,54 @@ const FALLBACK_DEFINITIONS = [
64
88
  cost: STANDARD_COST,
65
89
  max: false,
66
90
  },
91
+ {
92
+ id: "muse-wonder-1.0",
93
+ name: "Muse Wonder 1.0",
94
+ cost: WONDER_COST,
95
+ max: true,
96
+ },
97
+ {
98
+ id: "muse-wonder-1.0-contributor",
99
+ name: "Muse Wonder 1.0 Contributor",
100
+ cost: SUBSCRIPTION_ONLY_COST,
101
+ max: false,
102
+ },
103
+ {
104
+ id: "muse-wonder-full-1.0",
105
+ name: "Muse Wonder Full 1.0",
106
+ cost: SUBSCRIPTION_ONLY_COST,
107
+ max: true,
108
+ },
109
+ {
110
+ id: "muse-wonder-full-1.0-contributor",
111
+ name: "Muse Wonder Full 1.0 Contributor",
112
+ cost: SUBSCRIPTION_ONLY_COST,
113
+ max: false,
114
+ },
67
115
  ] as const;
68
116
 
117
+ /**
118
+ * Catalog families Meta bills per generated image, per hour of audio, or per
119
+ * video frame instead of per token (dev.meta.ai pricing page, 2026-10-23):
120
+ * Muse Image, Muse Voice Transcribe, and Segment Anything. They share the
121
+ * catalog with the chat models but are not chat models, so neither the live
122
+ * catalog nor a cached entry may list them as one. Prefixes, not exact IDs,
123
+ * so a new version of a family stays hidden without a code change.
124
+ */
125
+ export const NON_CHAT_MODEL_ID_PREFIXES = [
126
+ "muse-image-",
127
+ "muse-voice-",
128
+ "sam-",
129
+ ] as const;
130
+
131
+ export function isNonChatModelId(id: string): boolean {
132
+ return NON_CHAT_MODEL_ID_PREFIXES.some((prefix) => id.startsWith(prefix));
133
+ }
134
+
135
+ function isBundledModelId(id: string): boolean {
136
+ return FALLBACK_DEFINITIONS.some((model) => model.id === id);
137
+ }
138
+
69
139
  function nonemptyString(value: unknown): string | undefined {
70
140
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
71
141
  }
@@ -166,7 +236,7 @@ export function toProviderModels(catalog: unknown): MetaProviderModel[] {
166
236
  for (const value of data) {
167
237
  const entry = asRecord(value);
168
238
  const id = nonemptyString(entry?.id);
169
- if (!id || seen.has(id)) continue;
239
+ if (!id || seen.has(id) || isNonChatModelId(id)) continue;
170
240
  const metadata = asRecord(asRecord(entry?.metadata)?.["muse-code"]);
171
241
  if (metadata?.is_hidden === true) continue;
172
242
  const fallback = defaultModel(id);
@@ -308,7 +378,14 @@ function storedCost(
308
378
  return cost;
309
379
  }
310
380
 
311
- /** Cache contents cross the same untrusted-data boundary as the remote catalog. */
381
+ /**
382
+ * Cache contents cross the same untrusted-data boundary as the remote catalog.
383
+ * For bundled IDs the name, price, and input come from FALLBACK_DEFINITIONS:
384
+ * the bare catalog cannot have advertised them, so a cached copy is at best an
385
+ * older release's bundled value and at worst its unknown-model default (0.7.4
386
+ * persisted Wonder as a $0 text-only model). Cached limits, effort names,
387
+ * headers, and compat flags are kept; unknown IDs restore every cached field.
388
+ */
312
389
  export function restoreProviderModels(value: unknown): MetaProviderModel[] {
313
390
  if (!Array.isArray(value)) return [];
314
391
  const models: MetaProviderModel[] = [];
@@ -321,12 +398,16 @@ export function restoreProviderModels(value: unknown): MetaProviderModel[] {
321
398
  )
322
399
  continue;
323
400
  const id = nonemptyString(entry.id);
324
- if (!id || seen.has(id)) continue;
401
+ // A catalog persisted by an earlier release may hold non-chat IDs.
402
+ if (!id || seen.has(id) || isNonChatModelId(id)) continue;
325
403
  const fallback = defaultModel(id);
404
+ const bundled = isBundledModelId(id);
326
405
  models.push(
327
406
  gateMuseMaxEffort({
328
407
  ...fallback,
329
- name: nonemptyString(entry.name) ?? fallback.name,
408
+ name: bundled
409
+ ? fallback.name
410
+ : (nonemptyString(entry.name) ?? fallback.name),
330
411
  api: "openai-responses",
331
412
  // The extension only persists the direct endpoint; a cached URL must never redirect the key.
332
413
  baseUrl: META_API_BASE_URL,
@@ -338,8 +419,10 @@ export function restoreProviderModels(value: unknown): MetaProviderModel[] {
338
419
  entry.thinkingLevelMap,
339
420
  fallback.thinkingLevelMap ?? {},
340
421
  ),
341
- input: modelInput(entry.input, fallback.input),
342
- cost: storedCost(entry.cost, fallback.cost),
422
+ input: bundled
423
+ ? fallback.input
424
+ : modelInput(entry.input, fallback.input),
425
+ cost: bundled ? fallback.cost : storedCost(entry.cost, fallback.cost),
343
426
  contextWindow: positiveInteger(
344
427
  entry.contextWindow,
345
428
  fallback.contextWindow,
@@ -4,6 +4,20 @@ import type { MetaEnv, MetaProviderModel } from "./types.ts";
4
4
  /** Models whose max effort requires the captured Muse client fingerprint. */
5
5
  export const MUSE_USER_AGENT_MODEL_IDS: ReadonlySet<string> = new Set([
6
6
  "muse-spark-1.3-contributor",
7
+ "muse-wonder-1.0-contributor",
8
+ "muse-wonder-full-1.0-contributor",
9
+ ]);
10
+
11
+ /**
12
+ * Models known to accept literal max effort with any User-Agent. Each also
13
+ * has a bundled definition in models.ts that declares max, but they are
14
+ * listed here so the gate — which runs on every model list — is the single
15
+ * authority and a stale cached `max: null` cannot mask that support.
16
+ */
17
+ export const MAX_EFFORT_MODEL_IDS: ReadonlySet<string> = new Set([
18
+ "muse-spark-1.3",
19
+ "muse-wonder-1.0",
20
+ "muse-wonder-full-1.0",
7
21
  ]);
8
22
 
9
23
  /** Recognize the documented, explicit Muse fingerprint opt-in values. */
@@ -15,18 +29,26 @@ export function isMuseUserAgentEnabled(env: MetaEnv = process.env): boolean {
15
29
  }
16
30
 
17
31
  /**
18
- * Gate the literal max effort on known fingerprint-dependent models. A future
19
- * server-advertised mapping to a different effort remains authoritative.
32
+ * Apply known literal-max support: always on MAX_EFFORT_MODEL_IDS, and on
33
+ * fingerprint-dependent models only when opted in. A future server-advertised
34
+ * mapping to a different effort remains authoritative. Runs on every model
35
+ * list (fallbacks, catalog, cached restore) so a stale cached `max: null`
36
+ * cannot mask known support.
20
37
  */
21
38
  export function gateMuseMaxEffort(
22
39
  model: MetaProviderModel,
23
40
  env: MetaEnv = process.env,
24
41
  ): MetaProviderModel {
25
42
  const map = model.thinkingLevelMap;
26
- if (!map || !MUSE_USER_AGENT_MODEL_IDS.has(model.id)) return model;
43
+ if (!map) return model;
27
44
  if (map.max != null && map.max !== "max") return model;
28
45
 
29
- const max = isMuseUserAgentEnabled(env) ? "max" : null;
46
+ let max: string | null;
47
+ if (MAX_EFFORT_MODEL_IDS.has(model.id)) max = "max";
48
+ else if (MUSE_USER_AGENT_MODEL_IDS.has(model.id))
49
+ max = isMuseUserAgentEnabled(env) ? "max" : null;
50
+ else return model;
51
+
30
52
  if (map.max === max) return model;
31
53
  return { ...model, thinkingLevelMap: { ...map, max } };
32
54
  }
package/src/meta/types.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import type { ProviderConfig } from "@earendil-works/pi-coding-agent";
2
2
 
3
- type ProviderModel = NonNullable<ProviderConfig["models"]>[number];
3
+ /**
4
+ * Pi 0.99+ registers chat, image, and classifier model configs as a union; Omit
5
+ * over that union keeps only their shared keys. Narrow to the chat member, which
6
+ * is the whole element type on older Pi versions.
7
+ */
8
+ type ProviderModel = Extract<
9
+ NonNullable<ProviderConfig["models"]>[number],
10
+ { reasoning: boolean }
11
+ >;
4
12
 
5
13
  /** Responses hints spanning the supported Pi versions, including newer flags. */
6
14
  export interface MetaResponsesCompat {