pi-meta-oauth 0.2.1 → 0.2.2
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 +14 -5
- package/extensions/meta.ts +130 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# pi-meta-oauth
|
|
4
4
|
|
|
5
|
+
<!-- markdownlint-disable-next-line MD013 -->
|
|
6
|
+
[](https://www.npmjs.com/package/pi-meta-oauth) [](https://opensource.org/licenses/MIT) [](https://github.com/BlockedPath/pi-meta-oauth/stargazers) [](https://github.com/BlockedPath/pi-meta-oauth/commits/main) [](https://github.com/BlockedPath/pi-meta-oauth/issues) [](https://github.com/BlockedPath/pi-meta-oauth/pulls) [](https://www.conventionalcommits.org/en/v1.0.0/) [](https://www.typescriptlang.org/) [](https://github.com/BlockedPath/pi-meta-oauth/actions/workflows/ci.yml) [](https://pi.dev)
|
|
7
|
+
|
|
5
8
|
Meta Model API OAuth and Muse-style voice input for [pi](https://pi.dev).
|
|
6
9
|
|
|
7
10
|
- Use Muse Spark models through Pi's `openai-responses` provider
|
|
@@ -83,13 +86,19 @@ bun run typecheck
|
|
|
83
86
|
bun test
|
|
84
87
|
```
|
|
85
88
|
|
|
86
|
-
## Publish to pi.dev
|
|
89
|
+
## Publish to npm and pi.dev
|
|
87
90
|
|
|
88
|
-
The `pi-package` keyword makes the package discoverable at <https://pi.dev/packages>.
|
|
91
|
+
The `pi-package` keyword makes the package discoverable at <https://pi.dev/packages>. Publishing is handled by `.github/workflows/publish.yml` when a `v*` tag is pushed.
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
Before the first automated release, configure npm trusted publishing for `pi-meta-oauth` with:
|
|
94
|
+
|
|
95
|
+
- GitHub owner: `BlockedPath`
|
|
96
|
+
- Repository: `pi-meta-oauth`
|
|
97
|
+
- Workflow filename: `publish.yml`
|
|
98
|
+
- Environment: leave blank
|
|
99
|
+
- Allowed action: `npm publish`
|
|
100
|
+
|
|
101
|
+
Bump `package.json`, commit the change, create a `v<version>` tag, and push the commit and tag. The workflow verifies that the tag matches the package version, runs typechecking and tests, publishes to npm with provenance, and creates the GitHub Release. Re-running a partially completed workflow is safe when npm reports that the same commit was already published.
|
|
93
102
|
|
|
94
103
|
Users can then update with:
|
|
95
104
|
|
package/extensions/meta.ts
CHANGED
|
@@ -3,7 +3,10 @@ import type {
|
|
|
3
3
|
OAuthLoginCallbacks,
|
|
4
4
|
RefreshModelsContext,
|
|
5
5
|
} from "@earendil-works/pi-ai";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ExtensionAPI,
|
|
8
|
+
ProviderConfig,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
10
|
|
|
8
11
|
export const META_PROVIDER_ID = "meta";
|
|
9
12
|
export const META_API_BASE_URL = "https://api.meta.ai/v1";
|
|
@@ -60,7 +63,11 @@ interface MetaCatalogModel {
|
|
|
60
63
|
modalities?: { input?: string[] };
|
|
61
64
|
limit?: { context?: number; output?: number };
|
|
62
65
|
variants?: Record<string, { reasoningEffort?: string }>;
|
|
63
|
-
cost?: {
|
|
66
|
+
cost?: {
|
|
67
|
+
input?: string | number;
|
|
68
|
+
output?: string | number;
|
|
69
|
+
cached?: string | number;
|
|
70
|
+
};
|
|
64
71
|
};
|
|
65
72
|
};
|
|
66
73
|
}
|
|
@@ -83,7 +90,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
|
|
|
83
90
|
cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
|
84
91
|
contextWindow: 1_048_576,
|
|
85
92
|
maxTokens: 256_000,
|
|
86
|
-
compat: { supportsReasoningEffort: true },
|
|
93
|
+
compat: { supportsReasoningEffort: true, supportsToolSearch: true },
|
|
87
94
|
},
|
|
88
95
|
{
|
|
89
96
|
id: "muse-spark-1.2-contributor",
|
|
@@ -102,7 +109,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
|
|
|
102
109
|
cost: { input: 0.1, output: 0.2, cacheRead: 0.002, cacheWrite: 0 },
|
|
103
110
|
contextWindow: 1_048_576,
|
|
104
111
|
maxTokens: 256_000,
|
|
105
|
-
compat: { supportsReasoningEffort: true },
|
|
112
|
+
compat: { supportsReasoningEffort: true, supportsToolSearch: true },
|
|
106
113
|
},
|
|
107
114
|
{
|
|
108
115
|
id: "muse-spark-1.1",
|
|
@@ -121,7 +128,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
|
|
|
121
128
|
cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
|
122
129
|
contextWindow: 1_048_576,
|
|
123
130
|
maxTokens: 256_000,
|
|
124
|
-
compat: { supportsReasoningEffort: true },
|
|
131
|
+
compat: { supportsReasoningEffort: true, supportsToolSearch: true },
|
|
125
132
|
},
|
|
126
133
|
];
|
|
127
134
|
|
|
@@ -129,7 +136,9 @@ function delay(milliseconds: number): Promise<void> {
|
|
|
129
136
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
130
137
|
}
|
|
131
138
|
|
|
132
|
-
async function responseBody(
|
|
139
|
+
async function responseBody(
|
|
140
|
+
response: Response,
|
|
141
|
+
): Promise<Record<string, unknown>> {
|
|
133
142
|
const text = await response.text();
|
|
134
143
|
if (!text) return {};
|
|
135
144
|
try {
|
|
@@ -157,14 +166,23 @@ async function postForm<T>(
|
|
|
157
166
|
): Promise<{ response: Response; body: T & Record<string, unknown> }> {
|
|
158
167
|
const response = await fetchImpl(url, {
|
|
159
168
|
method: "POST",
|
|
160
|
-
headers: {
|
|
169
|
+
headers: {
|
|
170
|
+
Accept: "application/json",
|
|
171
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
172
|
+
},
|
|
161
173
|
body: new URLSearchParams(fields),
|
|
162
174
|
redirect: "manual",
|
|
163
175
|
});
|
|
164
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
response,
|
|
178
|
+
body: (await responseBody(response)) as T & Record<string, unknown>,
|
|
179
|
+
};
|
|
165
180
|
}
|
|
166
181
|
|
|
167
|
-
export async function mintMetaApiKey(
|
|
182
|
+
export async function mintMetaApiKey(
|
|
183
|
+
identityToken: string,
|
|
184
|
+
fetchImpl: Fetch = fetch,
|
|
185
|
+
): Promise<string> {
|
|
168
186
|
const response = await fetchImpl(API_KEY_MINT_URL, {
|
|
169
187
|
method: "POST",
|
|
170
188
|
headers: {
|
|
@@ -175,12 +193,18 @@ export async function mintMetaApiKey(identityToken: string, fetchImpl: Fetch = f
|
|
|
175
193
|
},
|
|
176
194
|
body: "{}",
|
|
177
195
|
});
|
|
178
|
-
const body = (await responseBody(response)) as MintResponse &
|
|
196
|
+
const body = (await responseBody(response)) as MintResponse &
|
|
197
|
+
Record<string, unknown>;
|
|
179
198
|
if (!response.ok) {
|
|
180
|
-
throw new Error(
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Meta API-key mint failed (HTTP ${response.status})${errorDetail(body) ? `: ${errorDetail(body)}` : ""}`,
|
|
201
|
+
);
|
|
181
202
|
}
|
|
182
203
|
if (typeof body.api_key !== "string" || !body.api_key) {
|
|
183
|
-
const setup =
|
|
204
|
+
const setup =
|
|
205
|
+
typeof body.action_url === "string" && body.action_url
|
|
206
|
+
? ` Complete setup at ${body.action_url}.`
|
|
207
|
+
: "";
|
|
184
208
|
throw new Error(`Meta did not issue an API key.${setup}`);
|
|
185
209
|
}
|
|
186
210
|
return body.api_key;
|
|
@@ -204,17 +228,24 @@ export async function loginMeta(
|
|
|
204
228
|
}
|
|
205
229
|
const device = authorization.body;
|
|
206
230
|
if (!device.device_code || !device.user_code || !device.verification_uri) {
|
|
207
|
-
throw new Error(
|
|
231
|
+
throw new Error(
|
|
232
|
+
"Meta device authorization returned an incomplete response",
|
|
233
|
+
);
|
|
208
234
|
}
|
|
209
235
|
|
|
210
|
-
let intervalSeconds =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
236
|
+
let intervalSeconds =
|
|
237
|
+
Number.isFinite(device.interval) && Number(device.interval) > 0
|
|
238
|
+
? Number(device.interval)
|
|
239
|
+
: 5;
|
|
240
|
+
const expiresInSeconds =
|
|
241
|
+
Number.isFinite(device.expires_in) && Number(device.expires_in) > 0
|
|
242
|
+
? Number(device.expires_in)
|
|
243
|
+
: 900;
|
|
214
244
|
const deadline = Date.now() + expiresInSeconds * 1000;
|
|
215
245
|
callbacks.onDeviceCode({
|
|
216
246
|
userCode: device.user_code,
|
|
217
|
-
verificationUri:
|
|
247
|
+
verificationUri:
|
|
248
|
+
device.verification_uri_complete || device.verification_uri,
|
|
218
249
|
intervalSeconds,
|
|
219
250
|
expiresInSeconds,
|
|
220
251
|
});
|
|
@@ -267,7 +298,10 @@ export async function refreshMetaToken(
|
|
|
267
298
|
credentials: OAuthCredentials,
|
|
268
299
|
fetchImpl: Fetch = fetch,
|
|
269
300
|
): Promise<OAuthCredentials> {
|
|
270
|
-
if (!credentials.refresh)
|
|
301
|
+
if (!credentials.refresh)
|
|
302
|
+
throw new Error(
|
|
303
|
+
"Meta login is missing its identity token; run /login meta again",
|
|
304
|
+
);
|
|
271
305
|
return {
|
|
272
306
|
...credentials,
|
|
273
307
|
access: await mintMetaApiKey(credentials.refresh, fetchImpl),
|
|
@@ -276,56 +310,83 @@ export async function refreshMetaToken(
|
|
|
276
310
|
}
|
|
277
311
|
|
|
278
312
|
function finitePositive(value: unknown, fallback: number): number {
|
|
279
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
313
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
314
|
+
? value
|
|
315
|
+
: fallback;
|
|
280
316
|
}
|
|
281
317
|
|
|
282
318
|
function numericCost(value: unknown, fallback: number): number {
|
|
283
|
-
const number =
|
|
319
|
+
const number =
|
|
320
|
+
typeof value === "number"
|
|
321
|
+
? value
|
|
322
|
+
: typeof value === "string"
|
|
323
|
+
? Number(value)
|
|
324
|
+
: Number.NaN;
|
|
284
325
|
return Number.isFinite(number) && number >= 0 ? number : fallback;
|
|
285
326
|
}
|
|
286
327
|
|
|
287
328
|
function displayName(id: string): string {
|
|
288
329
|
return id
|
|
289
330
|
.split("-")
|
|
290
|
-
.map((part) => part ? part[0].toUpperCase() + part.slice(1) : part)
|
|
331
|
+
.map((part) => (part ? part[0].toUpperCase() + part.slice(1) : part))
|
|
291
332
|
.join(" ");
|
|
292
333
|
}
|
|
293
334
|
|
|
294
|
-
export function toProviderModels(
|
|
335
|
+
export function toProviderModels(
|
|
336
|
+
catalog: CatalogResponse,
|
|
337
|
+
): MetaProviderModel[] {
|
|
295
338
|
return (catalog.data ?? []).flatMap((entry) => {
|
|
296
339
|
if (typeof entry.id !== "string" || !entry.id) return [];
|
|
297
340
|
const metadata = entry.metadata?.["muse-code"];
|
|
298
341
|
if (metadata?.is_hidden) return [];
|
|
299
342
|
const fallback = FALLBACK_MODELS.find((model) => model.id === entry.id);
|
|
300
|
-
const catalogName =
|
|
343
|
+
const catalogName =
|
|
344
|
+
metadata?.name === entry.id ? undefined : metadata?.name;
|
|
301
345
|
const variants = metadata?.variants ?? {};
|
|
302
|
-
const thinkingLevelMap: NonNullable<MetaProviderModel["thinkingLevelMap"]> =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
346
|
+
const thinkingLevelMap: NonNullable<MetaProviderModel["thinkingLevelMap"]> =
|
|
347
|
+
{
|
|
348
|
+
off: null,
|
|
349
|
+
minimal: variants.minimal?.reasoningEffort ?? "minimal",
|
|
350
|
+
low: variants.low?.reasoningEffort ?? "low",
|
|
351
|
+
medium: variants.medium?.reasoningEffort ?? "medium",
|
|
352
|
+
high: variants.high?.reasoningEffort ?? "high",
|
|
353
|
+
xhigh: variants.xhigh?.reasoningEffort ?? "xhigh",
|
|
354
|
+
max: null,
|
|
355
|
+
};
|
|
356
|
+
return [
|
|
357
|
+
{
|
|
358
|
+
id: entry.id,
|
|
359
|
+
name: catalogName || fallback?.name || displayName(entry.id),
|
|
360
|
+
reasoning: metadata?.reasoning ?? fallback?.reasoning ?? true,
|
|
361
|
+
thinkingLevelMap,
|
|
362
|
+
input: metadata
|
|
363
|
+
? metadata.modalities?.input?.includes("image")
|
|
364
|
+
? ["text", "image"]
|
|
365
|
+
: ["text"]
|
|
366
|
+
: (fallback?.input ?? ["text"]),
|
|
367
|
+
cost: {
|
|
368
|
+
input: numericCost(metadata?.cost?.input, fallback?.cost.input ?? 0),
|
|
369
|
+
output: numericCost(
|
|
370
|
+
metadata?.cost?.output,
|
|
371
|
+
fallback?.cost.output ?? 0,
|
|
372
|
+
),
|
|
373
|
+
cacheRead: numericCost(
|
|
374
|
+
metadata?.cost?.cached,
|
|
375
|
+
fallback?.cost.cacheRead ?? 0,
|
|
376
|
+
),
|
|
377
|
+
cacheWrite: 0,
|
|
378
|
+
},
|
|
379
|
+
contextWindow: finitePositive(
|
|
380
|
+
metadata?.limit?.context,
|
|
381
|
+
fallback?.contextWindow ?? 1_048_576,
|
|
382
|
+
),
|
|
383
|
+
maxTokens: finitePositive(
|
|
384
|
+
metadata?.limit?.output,
|
|
385
|
+
fallback?.maxTokens ?? 256_000,
|
|
386
|
+
),
|
|
387
|
+
compat: { supportsReasoningEffort: true, supportsToolSearch: true },
|
|
388
|
+
} satisfies MetaProviderModel,
|
|
389
|
+
];
|
|
329
390
|
});
|
|
330
391
|
}
|
|
331
392
|
|
|
@@ -333,21 +394,30 @@ export async function refreshMetaModels(
|
|
|
333
394
|
context: RefreshModelsContext,
|
|
334
395
|
fetchImpl: Fetch = fetch,
|
|
335
396
|
): Promise<MetaProviderModel[]> {
|
|
336
|
-
if (!context.allowNetwork || context.signal?.aborted)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
? context.credential.
|
|
341
|
-
:
|
|
397
|
+
if (!context.allowNetwork || context.signal?.aborted)
|
|
398
|
+
return [...FALLBACK_MODELS];
|
|
399
|
+
const apiKey =
|
|
400
|
+
context.credential?.type === "oauth"
|
|
401
|
+
? context.credential.access
|
|
402
|
+
: context.credential?.type === "api_key"
|
|
403
|
+
? context.credential.key
|
|
404
|
+
: undefined;
|
|
342
405
|
if (!apiKey) return [...FALLBACK_MODELS];
|
|
343
406
|
|
|
344
407
|
const response = await fetchImpl(META_MODEL_CATALOG_URL, {
|
|
345
|
-
headers: {
|
|
408
|
+
headers: {
|
|
409
|
+
Accept: "application/json",
|
|
410
|
+
Authorization: `Bearer ${apiKey}`,
|
|
411
|
+
"x-api-version": "1.0.0",
|
|
412
|
+
},
|
|
346
413
|
signal: context.signal,
|
|
347
414
|
});
|
|
348
|
-
const body = (await responseBody(response)) as CatalogResponse &
|
|
415
|
+
const body = (await responseBody(response)) as CatalogResponse &
|
|
416
|
+
Record<string, unknown>;
|
|
349
417
|
if (!response.ok) {
|
|
350
|
-
throw new Error(
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Meta model catalog failed (HTTP ${response.status})${errorDetail(body) ? `: ${errorDetail(body)}` : ""}`,
|
|
420
|
+
);
|
|
351
421
|
}
|
|
352
422
|
return toProviderModels(body);
|
|
353
423
|
}
|