pi-umans-provider 1.0.33 → 1.0.35
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/AGENTS.md +1 -0
- package/CHANGELOG.md +8 -0
- package/README.md +20 -1
- package/deprecated-models.json +20 -1
- package/index.ts +159 -31
- package/models.json +0 -17
- package/package.json +5 -3
- package/sync-policy.ts +309 -0
- package/tests/quiet-startup-sync.test.ts +357 -0
- package/tests/sync-policy.test.ts +259 -0
- package/vitest.config.ts +10 -0
package/AGENTS.md
CHANGED
|
@@ -19,6 +19,7 @@ When a model needs overrides, new properties, or corrections, edit the appropria
|
|
|
19
19
|
| `patch.json` | Per-model overrides keyed by model ID. Add reasoning flags, compat settings, pricing corrections, thinking level maps, etc. Applied on top of `models.json` at runtime and for README generation. |
|
|
20
20
|
| `custom-models.json` | Models that don't exist in the provider API (hidden models, router endpoints, cross-provider aliases). Merged after patch. Format: array of full model objects (same schema as `models.json` entries). |
|
|
21
21
|
| `index.ts` | Provider extension code. |
|
|
22
|
+
| `sync-policy.ts` | Quiet startup sync policy (TTL, key fingerprint, cross-process lock). Provider-agnostic and shared verbatim across pi-*-provider repos — keep it free of provider specifics. |
|
|
22
23
|
| `scripts/update-models.js` | The sync script itself (edit only if changing how models are fetched/transformed). |
|
|
23
24
|
|
|
24
25
|
## Data Flow
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.35
|
|
4
|
+
|
|
5
|
+
- Quiet startup sync: background `/v1/models/info` revalidation runs only with an API key, only when the disk cache is older than 1 hour (`PI_PROVIDER_SYNC_TTL_MS`, `0` = always), missing, or fetched with a different key, and only in one pi process at a time (lock file next to the cache). Failed refreshes back off for 5 minutes; sessions that skip adopt a cache refreshed by another process. `/login` forces a revalidation with the new key.
|
|
6
|
+
- Never store the API key: key changes are detected via a salted sha256 fingerprint in `cache/umans-models.sync.json`. The cache file format is unchanged.
|
|
7
|
+
- Usage footer: `/v1/usage` is no longer called without a UI (print mode, headless child agents). The session-start usage fetch is shared across pi processes for 60 seconds via `cache/umans-usage.json`; turn-end and idle polling are unchanged.
|
|
8
|
+
- Write the model cache atomically and swallow API key resolution failures in `session_start`.
|
|
9
|
+
- Add vitest coverage for the sync policy and the session_start integration.
|
|
10
|
+
|
|
3
11
|
## 1.0.32
|
|
4
12
|
|
|
5
13
|
- Validate provider registration and session lifecycle against Pi 1.0.0; update the pinned development SDK.
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ _A [pi](https://github.com/earendil-works/pi-coding-agent) provider extension wi
|
|
|
22
22
|
- **Streaming** - Real-time token streaming
|
|
23
23
|
- **Subscription-based** - All models included in your plan, no per-token cost
|
|
24
24
|
- **Usage status bar** — Displays your plan, concurrent sessions, and remaining requests in the pi footer
|
|
25
|
+
- **Quiet live model sync** — Models refresh from the UMANS API in the background, at most hourly and by one pi process at a time ([details](#model-sync))
|
|
25
26
|
|
|
26
27
|
## Available Models
|
|
27
28
|
|
|
@@ -35,7 +36,7 @@ _A [pi](https://github.com/earendil-works/pi-coding-agent) provider extension wi
|
|
|
35
36
|
| GLM 5.3 | GLM-5.3 | 1.0M | ❌ | ✅ | 131K |
|
|
36
37
|
| GLM 5.3 Flash | GLM-5.3-Flash | 1.0M | ✅ | ✅ | 131K |
|
|
37
38
|
| Kimi K3 | Kimi K3 | 1.0M | ✅ | ✅ | 131K |
|
|
38
|
-
| MiMo V2.6 Pro (lab) |
|
|
39
|
+
| MiMo V2.6 Pro (lab) | — | 1.0M | ✅ | ✅ | 131K |
|
|
39
40
|
| Qwen3.6 35B A3B | Qwen3.6-35B-A3B | 262K | ✅ | ✅ | 33K |
|
|
40
41
|
|
|
41
42
|
> **Note:** `umans-flash-beta` is deprecated (sunset 2026-06-07). Use `umans-flash` instead.
|
|
@@ -105,6 +106,7 @@ Get your API key from [code.umans.ai](https://code.umans.ai).
|
|
|
105
106
|
| Variable | Required | Description |
|
|
106
107
|
|----------|----------|-------------|
|
|
107
108
|
| `UMANS_API_KEY` | No | Your UMANS API key (fallback if not in auth.json) |
|
|
109
|
+
| `PI_PROVIDER_SYNC_TTL_MS` | No | Model sync freshness window in ms (default `3600000`; `0` = revalidate every session start) |
|
|
108
110
|
|
|
109
111
|
## Configuration
|
|
110
112
|
|
|
@@ -158,6 +160,23 @@ Code Max (Founding Seat) | ⟠ 1/4
|
|
|
158
160
|
|
|
159
161
|
The concurrent-session count is tracked locally: it's optimistically incremented the moment an agent run starts (no API call needed — the server has a brief registration lag anyway), reconciled with UMANS's `/v1/usage` endpoint after each agent run ends, and lightly polled while idle so the baseline stays fresh. The plan name and request budget are fetched on session start and model selection.
|
|
160
162
|
|
|
163
|
+
The footer (and every `/v1/usage` call) is interactive-only: print mode (`pi -p`) and headless child agents never poll. On session start, one pi process per 60 seconds calls `/v1/usage` and writes the result to `~/.pi/agent/cache/umans-usage.json` (with the same lock and key fingerprint as the model sync); other sessions started within that window render it from disk.
|
|
164
|
+
|
|
165
|
+
## Model sync
|
|
166
|
+
|
|
167
|
+
Startup never waits on the network. pi starts with the cached catalog (`~/.pi/agent/cache/umans-models.json`,
|
|
168
|
+
falling back to the bundled `models.json`) and revalidates against `/v1/models/info` in the background — only when:
|
|
169
|
+
|
|
170
|
+
- a UMANS API key is configured (no key, no request),
|
|
171
|
+
- the cache is older than 1 hour, missing, or was fetched with a different API key, and
|
|
172
|
+
- no other pi process is already refreshing it (an `umans-models.lock` file next to the cache).
|
|
173
|
+
|
|
174
|
+
So `/new`, `/reload`, `pi -p` and fan-out child agents share one cache instead of each calling the API; a
|
|
175
|
+
session that skips the fetch still picks up a cache another process refreshed. Logging in with `/login` revalidates
|
|
176
|
+
right away. A failed refresh is retried after 5 minutes at the earliest. `PI_PROVIDER_SYNC_TTL_MS` changes the
|
|
177
|
+
window (`0` revalidates on every session start). Only a hash fingerprint of the key is stored
|
|
178
|
+
(`umans-models.sync.json`), never the key itself.
|
|
179
|
+
|
|
161
180
|
## API Compatibility
|
|
162
181
|
|
|
163
182
|
The UMANS API follows OpenAI conventions with these differences (handled via `patch.json` and `before_provider_request` hook):
|
package/deprecated-models.json
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"umans-mimo-v2.6-pro-lab": {
|
|
3
|
+
"id": "umans-mimo-v2.6-pro-lab",
|
|
4
|
+
"name": "Umans MiMo V2.6 Pro (lab)",
|
|
5
|
+
"reasoning": true,
|
|
6
|
+
"input": [
|
|
7
|
+
"text",
|
|
8
|
+
"image"
|
|
9
|
+
],
|
|
10
|
+
"cost": {
|
|
11
|
+
"input": 0,
|
|
12
|
+
"output": 0,
|
|
13
|
+
"cacheRead": 0,
|
|
14
|
+
"cacheWrite": 0
|
|
15
|
+
},
|
|
16
|
+
"contextWindow": 1048576,
|
|
17
|
+
"maxTokens": 131071,
|
|
18
|
+
"deprecatedAt": "2026-10-09T00:00:27.031Z"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/index.ts
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* Model resolution strategy: Stale-While-Revalidate
|
|
20
20
|
* 1. Serve stale immediately: disk cache → embedded models.json (zero-latency)
|
|
21
21
|
* 2. Revalidate in background: live API /v1/models/info → merge with embedded → cache → hot-swap
|
|
22
|
+
* Quiet startup sync (sync-policy.ts): only with an API key, only when the
|
|
23
|
+
* disk cache is older than the TTL (1h, PI_PROVIDER_SYNC_TTL_MS) or the key
|
|
24
|
+
* changed, and only one pi process at a time (lock file next to the cache).
|
|
25
|
+
* /login forces a revalidation with the new key.
|
|
22
26
|
* 3. patch.json + custom-models.json applied on top of whichever source won
|
|
23
27
|
*
|
|
24
28
|
* Merge order: [live|cache|embedded] → apply patch.json → merge custom-models.json
|
|
@@ -45,6 +49,14 @@ import patchData from "./patch.json" with { type: "json" };
|
|
|
45
49
|
import deprecatedData from "./deprecated-models.json" with { type: "json" };
|
|
46
50
|
import fs from "fs";
|
|
47
51
|
import path from "path";
|
|
52
|
+
import {
|
|
53
|
+
acquireModelSync,
|
|
54
|
+
cacheChangedSinceLoad,
|
|
55
|
+
keyFingerprint,
|
|
56
|
+
markCacheLoaded,
|
|
57
|
+
syncMetaPath,
|
|
58
|
+
writeFileAtomic,
|
|
59
|
+
} from "./sync-policy";
|
|
48
60
|
|
|
49
61
|
// ─── Usage/Plan Types ────────────────────────────────────────────────────────
|
|
50
62
|
|
|
@@ -53,6 +65,15 @@ const USAGE_FETCH_TIMEOUT_MS = 5000;
|
|
|
53
65
|
const USAGE_THROTTLE_MS = 30_000;
|
|
54
66
|
const END_SETTLE_MS = 2000;
|
|
55
67
|
const IDLE_POLL_MS = 45_000;
|
|
68
|
+
/**
|
|
69
|
+
* Startup usage dedupe window. Every interactive session_start used to call
|
|
70
|
+
* /v1/usage; now one process per window fetches and writes the result to
|
|
71
|
+
* `cache/umans-usage.json`, and sessions started inside the window (/new,
|
|
72
|
+
* /reload, a second terminal) render it from disk. 60s: the plan name and
|
|
73
|
+
* request budget barely move, and the concurrent-session baseline is refreshed
|
|
74
|
+
* by the idle poll (IDLE_POLL_MS) and after every agent run anyway.
|
|
75
|
+
*/
|
|
76
|
+
const USAGE_SYNC_TTL_MS = 60_000;
|
|
56
77
|
|
|
57
78
|
let sessionPlan: string | null = null;
|
|
58
79
|
let sessionConcurrency: number | null = null;
|
|
@@ -287,7 +308,8 @@ function loadCachedModels(): JsonModel[] | null {
|
|
|
287
308
|
function cacheModels(models: JsonModel[]): void {
|
|
288
309
|
try {
|
|
289
310
|
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
290
|
-
|
|
311
|
+
// Atomic (tmp + rename): other pi processes may be reading the cache right now.
|
|
312
|
+
writeFileAtomic(CACHE_PATH, JSON.stringify(models, null, 2) + "\n");
|
|
291
313
|
} catch {
|
|
292
314
|
// Cache write failure is non-fatal
|
|
293
315
|
}
|
|
@@ -359,6 +381,7 @@ function withDeprecated(models: JsonModel[]): JsonModel[] {
|
|
|
359
381
|
}
|
|
360
382
|
|
|
361
383
|
function loadStaleModels(embeddedModels: JsonModel[]): JsonModel[] {
|
|
384
|
+
markCacheLoaded(CACHE_PATH); // stat before read: lets session_start spot newer caches
|
|
362
385
|
const cached = loadCachedModels();
|
|
363
386
|
if (!cached || cached.length === 0) return embeddedModels;
|
|
364
387
|
|
|
@@ -371,17 +394,34 @@ function loadStaleModels(embeddedModels: JsonModel[]): JsonModel[] {
|
|
|
371
394
|
return cached;
|
|
372
395
|
}
|
|
373
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Background revalidation, gated by the quiet startup sync policy
|
|
399
|
+
* (sync-policy.ts): no key → no fetch; fresh cache (TTL) → no fetch; another
|
|
400
|
+
* process already fetching → no fetch. When skipped, a cache that another
|
|
401
|
+
* process refreshed since we loaded it is adopted from disk instead.
|
|
402
|
+
* `force` bypasses the TTL (e.g. after /login); it is still single-flight.
|
|
403
|
+
*
|
|
404
|
+
* The Umans "OAuth" credential is the API key itself (access === refresh, never
|
|
405
|
+
* rotated), so it doubles as the stable identity for the key fingerprint.
|
|
406
|
+
*/
|
|
374
407
|
async function revalidateModels(
|
|
375
408
|
apiKey: string | undefined,
|
|
376
409
|
embeddedModels: JsonModel[],
|
|
377
410
|
signal?: AbortSignal,
|
|
411
|
+
force = false,
|
|
378
412
|
): Promise<JsonModel[] | null> {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
413
|
+
const lease = acquireModelSync({ cachePath: CACHE_PATH, apiKey, force });
|
|
414
|
+
if (!lease) return cacheChangedSinceLoad(CACHE_PATH) ? loadStaleModels(embeddedModels) : null;
|
|
415
|
+
let merged: JsonModel[] | null = null;
|
|
416
|
+
try {
|
|
417
|
+
const liveModels = apiKey ? await fetchLiveModels(apiKey, signal) : null;
|
|
418
|
+
if (!liveModels || liveModels.length === 0) return null;
|
|
419
|
+
merged = mergeWithEmbedded(liveModels, embeddedModels);
|
|
420
|
+
cacheModels(merged);
|
|
421
|
+
return merged;
|
|
422
|
+
} finally {
|
|
423
|
+
lease.release(merged ? "fetched" : signal?.aborted ? "aborted" : "failed");
|
|
424
|
+
}
|
|
385
425
|
}
|
|
386
426
|
|
|
387
427
|
// ─── API Key Resolution ────────────────────────────────────────────────────────
|
|
@@ -445,6 +485,59 @@ async function fetchUsage(
|
|
|
445
485
|
}
|
|
446
486
|
}
|
|
447
487
|
|
|
488
|
+
// ─── Shared usage snapshot (cross-process startup dedupe) ─────────────────────
|
|
489
|
+
|
|
490
|
+
const USAGE_CACHE_PATH = path.join(CACHE_DIR, `${PROVIDER_ID}-usage.json`);
|
|
491
|
+
|
|
492
|
+
/** Read the usage snapshot another process wrote — only if it was fetched with this key. */
|
|
493
|
+
function readUsageCache(apiKey: string): UmansUsage | null {
|
|
494
|
+
try {
|
|
495
|
+
const meta = JSON.parse(fs.readFileSync(syncMetaPath(USAGE_CACHE_PATH), "utf8"));
|
|
496
|
+
if (meta?.keyFingerprint !== keyFingerprint(apiKey)) return null;
|
|
497
|
+
const data = JSON.parse(fs.readFileSync(USAGE_CACHE_PATH, "utf8")) as UmansUsage;
|
|
498
|
+
return data?.plan?.slug ? data : null;
|
|
499
|
+
} catch {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function writeUsageCache(usage: UmansUsage): void {
|
|
505
|
+
try {
|
|
506
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
507
|
+
writeFileAtomic(USAGE_CACHE_PATH, JSON.stringify(usage) + "\n");
|
|
508
|
+
} catch {
|
|
509
|
+
// Non-fatal: other processes just fetch for themselves next time.
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Usage for session_start: one pi process per USAGE_SYNC_TTL_MS window calls
|
|
515
|
+
* /v1/usage (same lease/lock/backoff policy as the model sync, own cache
|
|
516
|
+
* file); the others render the snapshot it wrote. Never fetches without a key.
|
|
517
|
+
* `fetched` is false when this process skipped the network.
|
|
518
|
+
*/
|
|
519
|
+
async function startupUsage(
|
|
520
|
+
apiKey: string | undefined,
|
|
521
|
+
signal: AbortSignal,
|
|
522
|
+
): Promise<{ usage: UmansUsage | null; fetched: boolean }> {
|
|
523
|
+
if (!apiKey) return { usage: null, fetched: false };
|
|
524
|
+
const lease = acquireModelSync({
|
|
525
|
+
cachePath: USAGE_CACHE_PATH,
|
|
526
|
+
apiKey,
|
|
527
|
+
ttlMs: USAGE_SYNC_TTL_MS,
|
|
528
|
+
isValidCache: (data) => data != null,
|
|
529
|
+
});
|
|
530
|
+
if (!lease) return { usage: readUsageCache(apiKey), fetched: false };
|
|
531
|
+
let usage: UmansUsage | null = null;
|
|
532
|
+
try {
|
|
533
|
+
usage = await throttledFetchUsage(apiKey, { force: true, signal });
|
|
534
|
+
if (usage) writeUsageCache(usage);
|
|
535
|
+
return { usage, fetched: true };
|
|
536
|
+
} finally {
|
|
537
|
+
lease.release(usage ? "fetched" : signal.aborted ? "aborted" : "failed");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
448
541
|
function applyUsage(usage: UmansUsage, ctx: any): void {
|
|
449
542
|
sessionPlan = usage.plan.display_name;
|
|
450
543
|
sessionConcurrency = usage.limits?.concurrency?.limit ?? null;
|
|
@@ -573,6 +666,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
573
666
|
const staleBase = loadStaleModels(embeddedModels);
|
|
574
667
|
const staleModels = buildModels(staleBase, customModels, patches);
|
|
575
668
|
|
|
669
|
+
function registerFresh(freshBase: JsonModel[]): void {
|
|
670
|
+
pi.registerProvider("umans", {
|
|
671
|
+
baseUrl: BASE_URL,
|
|
672
|
+
apiKey: "$UMANS_API_KEY",
|
|
673
|
+
api: "openai-completions",
|
|
674
|
+
models: buildModels(freshBase, customModels, patches),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// After /login: revalidate with the new key right away (forced past the TTL,
|
|
679
|
+
// still single-flight). Deferred so pi stores the credential first; never
|
|
680
|
+
// awaited, never throws into the login flow.
|
|
681
|
+
async function loginAndRevalidate(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
682
|
+
const credentials = await loginUmans(callbacks);
|
|
683
|
+
setTimeout(() => {
|
|
684
|
+
const key = credentials.access;
|
|
685
|
+
cachedApiKey = key;
|
|
686
|
+
if (!revalidateAbort || revalidateAbort.signal.aborted) revalidateAbort = new AbortController();
|
|
687
|
+
const signal = revalidateAbort.signal;
|
|
688
|
+
void revalidateModels(key, embeddedModels, signal, true)
|
|
689
|
+
.then((freshBase) => {
|
|
690
|
+
if (freshBase && !signal.aborted) registerFresh(freshBase);
|
|
691
|
+
})
|
|
692
|
+
.catch(() => { /* keep serving the current catalog */ });
|
|
693
|
+
}, 0);
|
|
694
|
+
return credentials;
|
|
695
|
+
}
|
|
696
|
+
|
|
576
697
|
pi.registerProvider("umans", {
|
|
577
698
|
baseUrl: BASE_URL,
|
|
578
699
|
apiKey: "$UMANS_API_KEY",
|
|
@@ -580,7 +701,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
580
701
|
models: staleModels,
|
|
581
702
|
oauth: {
|
|
582
703
|
name: "Umans AI (API Key)",
|
|
583
|
-
login:
|
|
704
|
+
login: loginAndRevalidate,
|
|
584
705
|
refreshToken: refreshUmansToken,
|
|
585
706
|
getApiKey: getApiKey,
|
|
586
707
|
},
|
|
@@ -644,6 +765,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
644
765
|
return p;
|
|
645
766
|
});
|
|
646
767
|
|
|
768
|
+
// Fire-and-forget: never await network here — startup must not block on the
|
|
769
|
+
// provider API. Nothing is awaited and every rejection is swallowed.
|
|
647
770
|
pi.on("session_start", async (_event, ctx) => {
|
|
648
771
|
revalidateAbort?.abort();
|
|
649
772
|
revalidateAbort = new AbortController();
|
|
@@ -652,32 +775,37 @@ export default function (pi: ExtensionAPI) {
|
|
|
652
775
|
usageAbort?.abort();
|
|
653
776
|
usageAbort = new AbortController();
|
|
654
777
|
const signal = revalidateAbort.signal;
|
|
655
|
-
resolveApiKey(ctx.modelRegistry)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
778
|
+
const keyResolved = resolveApiKey(ctx.modelRegistry);
|
|
779
|
+
|
|
780
|
+
void keyResolved
|
|
781
|
+
.then(() => revalidateModels(cachedApiKey, embeddedModels, signal))
|
|
782
|
+
.then((freshBase) => {
|
|
783
|
+
if (freshBase && !signal.aborted) registerFresh(freshBase);
|
|
784
|
+
})
|
|
785
|
+
.catch(() => { /* keep serving the stale catalog */ });
|
|
786
|
+
|
|
787
|
+
// Usage footer: interactive sessions only (no footer in print mode or
|
|
788
|
+
// headless child agents, so no /v1/usage call there either).
|
|
789
|
+
void keyResolved
|
|
790
|
+
.then(async () => {
|
|
791
|
+
if (!isUmansModel(ctx)) {
|
|
792
|
+
clearUsageStatus(ctx);
|
|
793
|
+
return;
|
|
664
794
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
applyUsage(usage, ctx);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
795
|
+
if (!ctx.hasUI) return;
|
|
796
|
+
const { usage, fetched } = await startupUsage(cachedApiKey, signal);
|
|
797
|
+
if (signal.aborted) return;
|
|
798
|
+
if (usage) applyUsage(usage, ctx);
|
|
799
|
+
// Skipped the network and nothing usable on disk yet (another process
|
|
800
|
+
// is fetching right now): let the idle poll fill the footer in.
|
|
801
|
+
else if (!fetched && cachedApiKey) resetIdleTimer(ctx);
|
|
802
|
+
})
|
|
803
|
+
.catch(() => { /* the footer stays empty */ });
|
|
677
804
|
});
|
|
678
805
|
|
|
679
806
|
pi.on("model_select", (event, ctx) => {
|
|
680
807
|
if (event.model?.provider === "umans") {
|
|
808
|
+
if (!ctx.hasUI) return; // no footer to render without a UI
|
|
681
809
|
throttledFetchUsage(cachedApiKey, { force: true }).then((usage) => {
|
|
682
810
|
if (usage) {
|
|
683
811
|
applyUsage(usage, ctx);
|
|
@@ -695,7 +823,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
695
823
|
// registration lag, so fetching now would undercount us anyway. One span
|
|
696
824
|
// per prompt (agent_start → agent_settled), so no flicker between tool turns.
|
|
697
825
|
pi.on("agent_start", async (_event, ctx) => {
|
|
698
|
-
if (!isUmansModel(ctx)) return;
|
|
826
|
+
if (!isUmansModel(ctx) || !ctx.hasUI) return;
|
|
699
827
|
if (idleTimer) {
|
|
700
828
|
clearTimeout(idleTimer);
|
|
701
829
|
idleTimer = null;
|
|
@@ -707,7 +835,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
707
835
|
// Turn ended: drop our optimistic +1 and reconcile with the server after a
|
|
708
836
|
// short settle so the server has dropped our session from its count.
|
|
709
837
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
710
|
-
if (!isUmansModel(ctx)) return;
|
|
838
|
+
if (!isUmansModel(ctx) || !ctx.hasUI) return;
|
|
711
839
|
activeStreams = Math.max(0, activeStreams - 1);
|
|
712
840
|
updateUsageStatus(ctx);
|
|
713
841
|
scheduleEndFetch(ctx);
|
package/models.json
CHANGED
|
@@ -133,23 +133,6 @@
|
|
|
133
133
|
"contextWindow": 1048576,
|
|
134
134
|
"maxTokens": 131071
|
|
135
135
|
},
|
|
136
|
-
{
|
|
137
|
-
"id": "umans-mimo-v2.6-pro-lab",
|
|
138
|
-
"name": "Umans MiMo V2.6 Pro (lab)",
|
|
139
|
-
"reasoning": true,
|
|
140
|
-
"input": [
|
|
141
|
-
"text",
|
|
142
|
-
"image"
|
|
143
|
-
],
|
|
144
|
-
"cost": {
|
|
145
|
-
"input": 0,
|
|
146
|
-
"output": 0,
|
|
147
|
-
"cacheRead": 0,
|
|
148
|
-
"cacheWrite": 0
|
|
149
|
-
},
|
|
150
|
-
"contextWindow": 1048576,
|
|
151
|
-
"maxTokens": 131071
|
|
152
|
-
},
|
|
153
136
|
{
|
|
154
137
|
"id": "umans-qwen3.6-35b-a3b",
|
|
155
138
|
"name": "Umans Qwen3.6 35B A3B",
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umans-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
4
4
|
"description": "UMANS provider extension for pi - Access Kimi K2.6, GLM 5.1, Qwen 3.6, and coding-optimized models through the UMANS API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"clean": "echo 'nothing to clean'",
|
|
9
9
|
"build": "echo 'nothing to build'",
|
|
10
|
-
"check": "tsc --noEmit && node scripts/test-pi-compat.mjs",
|
|
10
|
+
"check": "tsc --noEmit && vitest run && node scripts/test-pi-compat.mjs",
|
|
11
|
+
"test": "vitest run",
|
|
11
12
|
"update-models": "node scripts/update-models.js",
|
|
12
13
|
"test:pi": "node scripts/test-pi-compat.mjs",
|
|
13
14
|
"typecheck": "tsc --noEmit"
|
|
@@ -39,7 +40,8 @@
|
|
|
39
40
|
"@earendil-works/pi-ai": "1.1.0",
|
|
40
41
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
41
42
|
"@types/node": "^25.9.2",
|
|
42
|
-
"typescript": "^6.0.3"
|
|
43
|
+
"typescript": "^6.0.3",
|
|
44
|
+
"vitest": "4.1.7"
|
|
43
45
|
},
|
|
44
46
|
"peerDependencies": {
|
|
45
47
|
"@earendil-works/pi-ai": "*",
|