dsh-connect-agnes-token-plan 0.11.1 → 0.11.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/CHANGELOG.md +8 -0
- package/client.js +18 -4
- package/lib/agnescode-models.js +1 -1
- package/lib/index.js +133 -92
- package/lib/llm-models.js +40 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
## [Unreleased]
|
|
25
25
|
|
|
26
|
+
## [0.11.2] — 2026-10-17
|
|
27
|
+
|
|
28
|
+
### 修复:AgnesCode 写操作不再被在途的读盖回、胶囊在后台页面上不再轮询
|
|
29
|
+
|
|
30
|
+
- **修(AgnesCode 面板)**:切换「启用 AgnesCode 提供方」开关、点「检测本机登录态」、保存模型清单之后,你看到的一直是操作后的状态。此前若操作发出前还有一次例行刷新在途,它的结果(操作前的)会在最长一个 60 秒周期里把操作后的状态盖回去——面板上会闪回旧值。
|
|
31
|
+
- **修(用量胶囊)**:页面处于后台(不可见)时挂载的胶囊,不再在没人看的时候持续轮询后端——每次轮询都占着一条连接;页面回到前台的瞬间立即读一次并恢复节奏。
|
|
32
|
+
- 影响面:`src/client/{agnescode-tab,composer-usage-pill}.ts`、`client.js`、`test/dom-render.test.mjs`。
|
|
33
|
+
|
|
26
34
|
## [0.11.1] — 2026-10-16
|
|
27
35
|
|
|
28
36
|
### 修复:用量胶囊首读失败不再卡死到会话结束、保存不再被旧轮询盖回
|
package/client.js
CHANGED
|
@@ -3188,6 +3188,10 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3188
3188
|
setError("unable to reach the Host");
|
|
3189
3189
|
}
|
|
3190
3190
|
}, []);
|
|
3191
|
+
const retireInFlight = () => {
|
|
3192
|
+
generation.current += 1;
|
|
3193
|
+
inFlight.current?.abort?.();
|
|
3194
|
+
};
|
|
3191
3195
|
useEffect(() => {
|
|
3192
3196
|
alive.current = true;
|
|
3193
3197
|
let timer = null;
|
|
@@ -3239,7 +3243,10 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3239
3243
|
action: "switch",
|
|
3240
3244
|
enabled
|
|
3241
3245
|
});
|
|
3242
|
-
if (alive.current)
|
|
3246
|
+
if (alive.current) {
|
|
3247
|
+
retireInFlight();
|
|
3248
|
+
setState(body);
|
|
3249
|
+
}
|
|
3243
3250
|
} catch (why) {
|
|
3244
3251
|
if (alive.current) setNote(format(tt("agnescode.switchError"), { error: why instanceof Error ? why.message : String(why) }));
|
|
3245
3252
|
}
|
|
@@ -3250,6 +3257,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3250
3257
|
try {
|
|
3251
3258
|
const body = await postJson(AGNESCODE_PATH, { action: "harvest" });
|
|
3252
3259
|
if (alive.current) {
|
|
3260
|
+
retireInFlight();
|
|
3253
3261
|
if (body !== null) setState(body);
|
|
3254
3262
|
if (body?.ok === true) setNote(tt("agnescode.harvestOk"));
|
|
3255
3263
|
else setNote(tt("agnescode.harvestFail"));
|
|
@@ -3265,7 +3273,10 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3265
3273
|
action: "saveModels",
|
|
3266
3274
|
enabledModelIds: ids
|
|
3267
3275
|
});
|
|
3268
|
-
if (alive.current)
|
|
3276
|
+
if (alive.current) {
|
|
3277
|
+
retireInFlight();
|
|
3278
|
+
setState(body);
|
|
3279
|
+
}
|
|
3269
3280
|
}, []);
|
|
3270
3281
|
const enabled = state?.enabled === true;
|
|
3271
3282
|
const view = agnescodeView(state, error);
|
|
@@ -3703,7 +3714,9 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3703
3714
|
}
|
|
3704
3715
|
}, []);
|
|
3705
3716
|
const dataRef = useRef(null);
|
|
3706
|
-
|
|
3717
|
+
useEffect(() => {
|
|
3718
|
+
dataRef.current = data;
|
|
3719
|
+
}, [data]);
|
|
3707
3720
|
const echoLlm = useCallback((patch) => {
|
|
3708
3721
|
generation.current += 1;
|
|
3709
3722
|
inFlight.current?.abort?.();
|
|
@@ -4129,6 +4142,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
4129
4142
|
}
|
|
4130
4143
|
}, [route]);
|
|
4131
4144
|
useEffect(() => {
|
|
4145
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
4132
4146
|
load();
|
|
4133
4147
|
}, [load]);
|
|
4134
4148
|
useEffect(() => {
|
|
@@ -4146,7 +4160,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
4146
4160
|
timer = null;
|
|
4147
4161
|
}
|
|
4148
4162
|
};
|
|
4149
|
-
start();
|
|
4163
|
+
if (!(typeof document !== "undefined" && document.visibilityState === "hidden")) start();
|
|
4150
4164
|
const onVisibility = () => {
|
|
4151
4165
|
if (typeof document === "undefined") return;
|
|
4152
4166
|
if (document.visibilityState === "hidden") stop();
|
package/lib/agnescode-models.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as obj, R as str, T as name, i as PROBED_VISION, j as numZeroOk, k as num } from "./llm-models.js";
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as numOrNull, C as inject, D as resolveSettings, E as resolveAuthOverrides, F as redactSecrets, I as
|
|
1
|
+
import { A as numOrNull, C as inject, D as resolveSettings, E as resolveAuthOverrides, F as redactSecrets, I as resolveToolInputs, L as retryBounded, M as obj, N as pluginError, O as forget, P as redactError, R as str, S as hostName, T as name, _ as parseUsageOverview, b as readSubscriptionExpiry, c as summarizeCatalog, d as isVideoGenModel, f as checkShape, g as parseSubscriptionUsage, h as parsePlans, j as numZeroOk, k as num, l as visionOf, m as matchCurrentPlan, n as LLM_DISPLAY_NAME, o as filterByEnabled, p as countOf, r as LLM_PROVIDER_ID, s as rosterWithAvailability, t as DEFAULT_REASONING_EFFORT, u as isImageGenModel, v as parseUsageSeries, w as isAdmitted, x as CONFIG_DEFAULTS, y as quotaWindows, z as verbatim } from "./llm-models.js";
|
|
2
2
|
import { S as writeStateFile, _ as profileSegment, a as filterAgnescodeRows, b as stateDir, c as fetchAgnescodeBalance, d as trustAgnescodeBffBase, f as createFileCatalogStore, g as ensureStateDir, h as createStateReadCache, l as fetchAgnescodeCatalog, m as STATE_READ_TTL_MS, n as AGNESCODE_PROVIDER_ID, o as AGNESCODE_FALLBACK_MODELS, p as normalizeEnabledIds, s as decodeAgnescodeJwtExpMs, t as AGNESCODE_DISPLAY_NAME, u as harvestAgnescodeLocalSession, v as profileStateDir, x as temporaryOf, y as readStateJson } from "./agnescode-models.js";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -3595,6 +3595,58 @@ function sweepCache(cache) {
|
|
|
3595
3595
|
for (const [key, entry] of cache) if (nowMs - entry.at > MAX_CACHE_AGE_MS) cache.delete(key);
|
|
3596
3596
|
}
|
|
3597
3597
|
/**
|
|
3598
|
+
* The cache + single-flight protocol every console fetch in this module uses.
|
|
3599
|
+
*
|
|
3600
|
+
* All three callers — {@link fetchConsole}, {@link fetchPlans},
|
|
3601
|
+
* {@link fetchModelCatalog} — need the same four moves in the same order, and
|
|
3602
|
+
* each used to spell them out: a TTL read off `cache`, a shared in-flight
|
|
3603
|
+
* promise per URL, the write-back, and the bounded sweep. The difference
|
|
3604
|
+
* between them is only what a REQUEST does (`produce`), never how an answer is
|
|
3605
|
+
* remembered. Keeping the protocol here means a fourth endpoint cannot pick up
|
|
3606
|
+
* a subtly different variant of it (the same "hand-copied rule drifts" failure
|
|
3607
|
+
* `switch-precedence.ts` was extracted to end).
|
|
3608
|
+
*
|
|
3609
|
+
* Two properties are load-bearing and are why `produce` is a callback rather
|
|
3610
|
+
* than a pre-made promise:
|
|
3611
|
+
*
|
|
3612
|
+
* 1. **Nothing is cached until the answer has been judged.** `produce` throws
|
|
3613
|
+
* for a refusal instead of returning one, so a transient 401 never gets
|
|
3614
|
+
* written down and left to stick for the whole TTL — which would leave the
|
|
3615
|
+
* panel reading "not signed in" long after the store had healed.
|
|
3616
|
+
* 2. **A failed flight leaves no trace.** The `finally` drops the in-flight
|
|
3617
|
+
* entry on the error path too, so the next poll genuinely retries rather
|
|
3618
|
+
* than awaiting a rejected promise it never saw.
|
|
3619
|
+
*
|
|
3620
|
+
* @param {string} url - the fully built request URL; also the cache and
|
|
3621
|
+
* in-flight key, which is what makes the de-duplication per-endpoint.
|
|
3622
|
+
* @param {number} cacheMs - how long a successful answer stays fresh.
|
|
3623
|
+
* @param {CacheMap} cache - the shared response cache.
|
|
3624
|
+
* @param {InflightMap} inflight - the shared in-flight map.
|
|
3625
|
+
* @param {() => Promise<unknown>} produce - performs the request and returns
|
|
3626
|
+
* the value to cache; throws to refuse, and a throw caches nothing.
|
|
3627
|
+
* @returns {Promise<unknown>} the cached, shared, or freshly produced value.
|
|
3628
|
+
*/
|
|
3629
|
+
async function sharedFetch(url, cacheMs, cache, inflight, produce) {
|
|
3630
|
+
const cached = cache.get(url);
|
|
3631
|
+
if (cached !== void 0 && Date.now() - cached.at < cacheMs) return cached.body;
|
|
3632
|
+
const pending = inflight.get(url);
|
|
3633
|
+
if (pending !== void 0) return pending;
|
|
3634
|
+
const run = async () => {
|
|
3635
|
+
const body = await produce();
|
|
3636
|
+
cache.set(url, {
|
|
3637
|
+
body,
|
|
3638
|
+
at: Date.now()
|
|
3639
|
+
});
|
|
3640
|
+
sweepCache(cache);
|
|
3641
|
+
return body;
|
|
3642
|
+
};
|
|
3643
|
+
const flight = run().finally(() => {
|
|
3644
|
+
inflight.delete(url);
|
|
3645
|
+
});
|
|
3646
|
+
inflight.set(url, flight);
|
|
3647
|
+
return flight;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3598
3650
|
* Whether an envelope code means "the token is no longer good".
|
|
3599
3651
|
*
|
|
3600
3652
|
* Agnes reports an expired session as `code: 401` inside an HTTP 401, but the
|
|
@@ -3670,11 +3722,7 @@ function unwrapEnvelope(body, label) {
|
|
|
3670
3722
|
async function fetchConsole(settings, path, params, cacheMs, cache, inflight, tokenStore) {
|
|
3671
3723
|
const query = params && Object.keys(params).length > 0 ? `?${new URLSearchParams(params).toString()}` : "";
|
|
3672
3724
|
const url = `${settings.consoleBase}${path}${query}`;
|
|
3673
|
-
|
|
3674
|
-
if (cached !== void 0 && Date.now() - cached.at < cacheMs) return cached.body;
|
|
3675
|
-
const pending = inflight.get(url);
|
|
3676
|
-
if (pending !== void 0) return pending;
|
|
3677
|
-
const run = async () => {
|
|
3725
|
+
return sharedFetch(url, cacheMs, cache, inflight, async () => {
|
|
3678
3726
|
/** One attempt: `{data}` on success, `{authRefused: true}` when renewing helps. */
|
|
3679
3727
|
const attempt = async (token) => {
|
|
3680
3728
|
const response = await fetch(url, {
|
|
@@ -3706,18 +3754,8 @@ async function fetchConsole(settings, path, params, cacheMs, cache, inflight, to
|
|
|
3706
3754
|
error.code = CODE.JWT_EXPIRED;
|
|
3707
3755
|
throw error;
|
|
3708
3756
|
}
|
|
3709
|
-
cache.set(url, {
|
|
3710
|
-
body: result.data,
|
|
3711
|
-
at: Date.now()
|
|
3712
|
-
});
|
|
3713
|
-
sweepCache(cache);
|
|
3714
3757
|
return result.data;
|
|
3715
|
-
};
|
|
3716
|
-
const flight = run().finally(() => {
|
|
3717
|
-
inflight.delete(url);
|
|
3718
3758
|
});
|
|
3719
|
-
inflight.set(url, flight);
|
|
3720
|
-
return flight;
|
|
3721
3759
|
}
|
|
3722
3760
|
/**
|
|
3723
3761
|
* Fetch the public plan catalogue — the only quota source that needs no login.
|
|
@@ -3736,29 +3774,14 @@ async function fetchConsole(settings, path, params, cacheMs, cache, inflight, to
|
|
|
3736
3774
|
*/
|
|
3737
3775
|
async function fetchPlans(settings, cacheMs, cache, inflight) {
|
|
3738
3776
|
const url = `${settings.consoleBase}${PLANS_PATH}`;
|
|
3739
|
-
|
|
3740
|
-
if (cached !== void 0 && Date.now() - cached.at < cacheMs) return cached.body;
|
|
3741
|
-
const pending = inflight.get(url);
|
|
3742
|
-
if (pending !== void 0) return pending;
|
|
3743
|
-
const run = async () => {
|
|
3777
|
+
return sharedFetch(url, cacheMs, cache, inflight, async () => {
|
|
3744
3778
|
const response = await fetch(url, {
|
|
3745
3779
|
headers: { accept: "application/json" },
|
|
3746
3780
|
signal: AbortSignal.timeout(settings.consoleTimeoutMs)
|
|
3747
3781
|
});
|
|
3748
3782
|
if (!response.ok) throw new Error(`${PLANS_PATH} returned HTTP ${response.status}`);
|
|
3749
|
-
|
|
3750
|
-
cache.set(url, {
|
|
3751
|
-
body,
|
|
3752
|
-
at: Date.now()
|
|
3753
|
-
});
|
|
3754
|
-
sweepCache(cache);
|
|
3755
|
-
return body;
|
|
3756
|
-
};
|
|
3757
|
-
const flight = run().finally(() => {
|
|
3758
|
-
inflight.delete(url);
|
|
3783
|
+
return unwrapEnvelope(await response.json(), PLANS_PATH);
|
|
3759
3784
|
});
|
|
3760
|
-
inflight.set(url, flight);
|
|
3761
|
-
return flight;
|
|
3762
3785
|
}
|
|
3763
3786
|
/**
|
|
3764
3787
|
* Fetch the API-key model catalog: the models this key can actually call.
|
|
@@ -3777,11 +3800,7 @@ async function fetchPlans(settings, cacheMs, cache, inflight) {
|
|
|
3777
3800
|
*/
|
|
3778
3801
|
async function fetchModelCatalog(settings, cacheMs, cache, inflight, apiKey) {
|
|
3779
3802
|
const url = `${settings.apiBase}/models`;
|
|
3780
|
-
|
|
3781
|
-
if (cached !== void 0 && Date.now() - cached.at < cacheMs) return cached.body;
|
|
3782
|
-
const pending = inflight.get(url);
|
|
3783
|
-
if (pending !== void 0) return pending;
|
|
3784
|
-
const run = async () => {
|
|
3803
|
+
return sharedFetch(url, cacheMs, cache, inflight, async () => {
|
|
3785
3804
|
const response = await fetch(url, {
|
|
3786
3805
|
headers: {
|
|
3787
3806
|
authorization: `Bearer ${apiKey}`,
|
|
@@ -3791,25 +3810,14 @@ async function fetchModelCatalog(settings, cacheMs, cache, inflight, apiKey) {
|
|
|
3791
3810
|
});
|
|
3792
3811
|
if (!response.ok) throw new Error(`/v1/models returned HTTP ${response.status}`);
|
|
3793
3812
|
const body = await response.json();
|
|
3794
|
-
|
|
3813
|
+
return Array.isArray(body?.data) ? body.data.map((entry) => {
|
|
3795
3814
|
const source = obj(entry);
|
|
3796
3815
|
return {
|
|
3797
3816
|
id: str(source.id, ""),
|
|
3798
3817
|
...source
|
|
3799
3818
|
};
|
|
3800
3819
|
}).filter((entry) => entry.id !== "") : [];
|
|
3801
|
-
cache.set(url, {
|
|
3802
|
-
body: models,
|
|
3803
|
-
at: Date.now()
|
|
3804
|
-
});
|
|
3805
|
-
sweepCache(cache);
|
|
3806
|
-
return models;
|
|
3807
|
-
};
|
|
3808
|
-
const flight = run().finally(() => {
|
|
3809
|
-
inflight.delete(url);
|
|
3810
3820
|
});
|
|
3811
|
-
inflight.set(url, flight);
|
|
3812
|
-
return flight;
|
|
3813
3821
|
}
|
|
3814
3822
|
|
|
3815
3823
|
//#endregion
|
|
@@ -4135,15 +4143,10 @@ function defineDrawTool({ defineTool, resolveApiKey, getEntries, settings, fetch
|
|
|
4135
4143
|
const prompt = str(params?.prompt, "").trim();
|
|
4136
4144
|
if (prompt === "") throw new Error("prompt is required");
|
|
4137
4145
|
if (cooldown.blocked()) throw new Error(`draw cooldown: 上一次出图失败,${Math.ceil(cooldown.remainingMs() / 1e3)}s 后再试`);
|
|
4138
|
-
const apiKey = await
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
picked = await getEntries?.() ?? [];
|
|
4143
|
-
} catch {
|
|
4144
|
-
picked = [];
|
|
4145
|
-
}
|
|
4146
|
-
const entries = Array.isArray(picked) ? picked : [];
|
|
4146
|
+
const { apiKey, entries } = await resolveToolInputs({
|
|
4147
|
+
resolveApiKey,
|
|
4148
|
+
getEntries
|
|
4149
|
+
});
|
|
4147
4150
|
const model = pickDrawModel(entries, params?.model, settings?.drawModelId);
|
|
4148
4151
|
if (model === null || model === void 0) throw new Error("catalog 中没有出图模型(`output_modalities` 字段与 `agnes-image-*` 名称判定均为空):确认 Key 已配置、面板已至少轮询一次,且套餐含出图模型");
|
|
4149
4152
|
const body = buildDrawBody({
|
|
@@ -4603,15 +4606,66 @@ async function applyCatalogEffects({ catalogStore, publisher, providerState, cat
|
|
|
4603
4606
|
};
|
|
4604
4607
|
}
|
|
4605
4608
|
/**
|
|
4606
|
-
* Fetch the four console sources plus the model
|
|
4607
|
-
*
|
|
4609
|
+
* Fetch the four console sources plus the model catalogue — the NETWORK half of
|
|
4610
|
+
* the poll, kept apart from the projection that shapes the answer.
|
|
4611
|
+
*
|
|
4612
|
+
* All five go out through the shared `cache` + `inflight` maps (a single-flight
|
|
4613
|
+
* per URL so concurrent polls share one call) and the `tokenStore` (so a 401
|
|
4614
|
+
* triggers one renewal before the call). Only the catalogue is authenticated by
|
|
4615
|
+
* the API key instead; a missing key degrades the model lists, not the quota —
|
|
4616
|
+
* resolved per poll so a key that arrives after the plugin mounted still lights
|
|
4617
|
+
* the lists on the next poll.
|
|
4618
|
+
*
|
|
4619
|
+
* Extracted from `buildSnapshotBody`, which is now "fetch, then shape": this was
|
|
4620
|
+
* the only part of it that touches the network, and the reasoning about WHY the
|
|
4621
|
+
* five requests share one batch belongs next to the batch rather than in the
|
|
4622
|
+
* middle of the field assembly. The outcomes are passed straight through —
|
|
4623
|
+
* `soft` has already turned every failure into `{value: null, error}`.
|
|
4624
|
+
*
|
|
4625
|
+
* @param {object} context
|
|
4626
|
+
* @param {Settings} context.settings - the resolved settings row.
|
|
4627
|
+
* @param {CacheMap} context.cache - the console-response cache (shared across polls).
|
|
4628
|
+
* @param {InflightMap} context.inflight - the single-flight map (shared across polls).
|
|
4629
|
+
* @param {object} context.tokenStore - the `createTokenStore` instance.
|
|
4630
|
+
* @param {object} context.apiKeyStore - the `createApiKeyStore` instance.
|
|
4631
|
+
* @returns {Promise<{overview: object, series: object, subscription: object, plans: object, catalog: unknown}>}
|
|
4632
|
+
* one `{value, error}` outcome per console source, plus the raw catalogue.
|
|
4633
|
+
*/
|
|
4634
|
+
async function fetchQuotaSources({ settings, cache, inflight, tokenStore, apiKeyStore }) {
|
|
4635
|
+
const resolveApiKey = async () => (await apiKeyStore.resolve()).value;
|
|
4636
|
+
const { startDate, endDate } = usageWindow(settings.usageDays);
|
|
4637
|
+
const [overview, series, subscription, plans, catalog] = await Promise.all([
|
|
4638
|
+
soft(() => fetchConsole(settings, USAGE_OVERVIEW_PATH, void 0, settings.cacheSeconds * 1e3, cache, inflight, tokenStore)),
|
|
4639
|
+
soft(() => fetchConsole(settings, USAGE_SERIES_PATH, {
|
|
4640
|
+
range: "custom",
|
|
4641
|
+
start_date: startDate,
|
|
4642
|
+
end_date: endDate
|
|
4643
|
+
}, Math.max(settings.cacheSeconds, 300) * 1e3, cache, inflight, tokenStore)),
|
|
4644
|
+
soft(() => fetchConsole(settings, SUBSCRIPTION_PATH, void 0, settings.cacheSeconds * 1e3, cache, inflight, tokenStore)),
|
|
4645
|
+
soft(() => fetchPlans(settings, 36e5, cache, inflight)),
|
|
4646
|
+
(async () => {
|
|
4647
|
+
const apiKey = await resolveApiKey();
|
|
4648
|
+
return apiKey === "" ? null : fetchModelCatalog(settings, 36e5, cache, inflight, apiKey).catch(() => null);
|
|
4649
|
+
})()
|
|
4650
|
+
]);
|
|
4651
|
+
return {
|
|
4652
|
+
overview,
|
|
4653
|
+
series,
|
|
4654
|
+
subscription,
|
|
4655
|
+
plans,
|
|
4656
|
+
catalog
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
/**
|
|
4660
|
+
* Assemble the snapshot body the route writes, from the five sources the poll
|
|
4661
|
+
* fetched plus the stores' current state.
|
|
4608
4662
|
*
|
|
4609
|
-
*
|
|
4610
|
-
*
|
|
4611
|
-
*
|
|
4612
|
-
*
|
|
4613
|
-
*
|
|
4614
|
-
*
|
|
4663
|
+
* The NETWORK half is {@link fetchQuotaSources}: this function starts from its
|
|
4664
|
+
* `{value, error}` outcomes, so every field below is either a degraded source
|
|
4665
|
+
* or a value read off a store, and the only side effects it performs are the
|
|
4666
|
+
* catalogue persist + provider republish isolated in `applyCatalogEffects`.
|
|
4667
|
+
* The five requests' parallelism, caching and 401 handling are that function's
|
|
4668
|
+
* concern, not this one's.
|
|
4615
4669
|
*
|
|
4616
4670
|
* @param {object} context
|
|
4617
4671
|
* @param {object} context.settings - the resolved settings row.
|
|
@@ -4636,22 +4690,13 @@ async function applyCatalogEffects({ catalogStore, publisher, providerState, cat
|
|
|
4636
4690
|
*/
|
|
4637
4691
|
async function buildSnapshotBody({ settings, cache, inflight, tokenStore, apiKeyStore, publisher, catalogStore, panelSwitch, drawSwitch, drawModelId, videoSwitch, videoModelId }) {
|
|
4638
4692
|
const providerState = publisher.state;
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
end_date: endDate
|
|
4647
|
-
}, Math.max(settings.cacheSeconds, 300) * 1e3, cache, inflight, tokenStore)),
|
|
4648
|
-
soft(() => fetchConsole(settings, SUBSCRIPTION_PATH, void 0, settings.cacheSeconds * 1e3, cache, inflight, tokenStore)),
|
|
4649
|
-
soft(() => fetchPlans(settings, 36e5, cache, inflight)),
|
|
4650
|
-
(async () => {
|
|
4651
|
-
const apiKey = await resolveApiKey();
|
|
4652
|
-
return apiKey === "" ? null : fetchModelCatalog(settings, 36e5, cache, inflight, apiKey).catch(() => null);
|
|
4653
|
-
})()
|
|
4654
|
-
]);
|
|
4693
|
+
const { overview, series, subscription, plans, catalog } = await fetchQuotaSources({
|
|
4694
|
+
settings,
|
|
4695
|
+
cache,
|
|
4696
|
+
inflight,
|
|
4697
|
+
tokenStore,
|
|
4698
|
+
apiKeyStore
|
|
4699
|
+
});
|
|
4655
4700
|
const quota = buildQuotaBlock(overview, series, subscription, plans);
|
|
4656
4701
|
const usage = series.value === null ? null : (() => {
|
|
4657
4702
|
const parsed = parseUsageSeries(series.value, settings.usageDays);
|
|
@@ -6934,15 +6979,11 @@ function defineVideoTool({ defineTool, resolveApiKey, getEntries, settings, fetc
|
|
|
6934
6979
|
if (isDisposed()) throw new Error("Agnes video tool is no longer mounted");
|
|
6935
6980
|
const prompt = str(params?.prompt, "").trim();
|
|
6936
6981
|
if (prompt === "") throw new Error("prompt is required");
|
|
6937
|
-
const apiKey = await
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
} catch {
|
|
6943
|
-
picked = [];
|
|
6944
|
-
}
|
|
6945
|
-
const model = pickVideoModel(Array.isArray(picked) ? picked : [], params?.model, settings?.videoModelId);
|
|
6982
|
+
const { apiKey, entries } = await resolveToolInputs({
|
|
6983
|
+
resolveApiKey,
|
|
6984
|
+
getEntries
|
|
6985
|
+
});
|
|
6986
|
+
const model = pickVideoModel(entries, params?.model, settings?.videoModelId);
|
|
6946
6987
|
if (model === null) throw new Error("catalog 中没有视频模型(`output_modalities` 字段与 `agnes-video-*` 名称判定均为空):确认 Key 已配置、面板已至少轮询一次,且套餐含视频模型");
|
|
6947
6988
|
if (!isVideo25Family(model)) {
|
|
6948
6989
|
const twentyFiveOnly = VIDEO25_ONLY_FIELDS.filter((field) => params?.[field] !== void 0 && params?.[field] !== null);
|
package/lib/llm-models.js
CHANGED
|
@@ -130,6 +130,45 @@ async function retryBounded({ attempts, delayMs, run }) {
|
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
|
+
* The shared preamble every agent tool runs before it can build a request:
|
|
134
|
+
* resolve the live API key, refuse when there is none, then read the model
|
|
135
|
+
* catalogue defensively.
|
|
136
|
+
*
|
|
137
|
+
* `defineDrawTool` and `defineVideoTool` each spelled this out, which meant the
|
|
138
|
+
* key-missing message — the one line a user actually reads, naming the panel
|
|
139
|
+
* section and both key shapes — existed twice and could be fixed once. The
|
|
140
|
+
* ORDER is load-bearing and is why this is a function rather than two call
|
|
141
|
+
* sites: the key is resolved and validated BEFORE the catalogue is touched, so
|
|
142
|
+
* an unconfigured deployment costs no catalogue read, and a `getEntries` that
|
|
143
|
+
* throws degrades the picker to empty rather than failing the tool (the catalog
|
|
144
|
+
* is a cache-shaped input; a stale or missing one must not kill a call that the
|
|
145
|
+
* caller may still be able to make with an explicit model id).
|
|
146
|
+
*
|
|
147
|
+
* What it deliberately does NOT do is anything modality-specific: draw's
|
|
148
|
+
* cooldown gate and video's family-dispatch checks stay with their own tools
|
|
149
|
+
* because those are real domain differences, not ceremony.
|
|
150
|
+
*
|
|
151
|
+
* @param {{resolveApiKey: () => Promise<string|undefined>, getEntries?: () => Promise<unknown[]>}} wiring
|
|
152
|
+
* the two `ToolWiring` seams the tools are mounted with.
|
|
153
|
+
* @returns {Promise<{apiKey: string, entries: unknown[]}>} a non-empty key and
|
|
154
|
+
* the catalogue entries (empty array when the read is absent or throws).
|
|
155
|
+
* @throws {Error} when no key is configured — the message names the fix.
|
|
156
|
+
*/
|
|
157
|
+
async function resolveToolInputs(wiring) {
|
|
158
|
+
const apiKey = await wiring.resolveApiKey();
|
|
159
|
+
if (typeof apiKey !== "string" || apiKey.trim() === "") throw new Error("AGNES_TOKEN_PLAN_API_KEY 未配置:在面板「模型接入」粘贴 API Key(免费版 sk- 或 Token Plan cpk-),或设置该环境变量");
|
|
160
|
+
let picked;
|
|
161
|
+
try {
|
|
162
|
+
picked = await wiring.getEntries?.() ?? [];
|
|
163
|
+
} catch {
|
|
164
|
+
picked = [];
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
apiKey,
|
|
168
|
+
entries: Array.isArray(picked) ? picked : []
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
133
172
|
* Run a promise to completion without awaiting it — and guarantee it cannot
|
|
134
173
|
* become an unhandled rejection.
|
|
135
174
|
*
|
|
@@ -1873,4 +1912,4 @@ function summarizeCatalog(entries) {
|
|
|
1873
1912
|
}
|
|
1874
1913
|
|
|
1875
1914
|
//#endregion
|
|
1876
|
-
export { numOrNull as A, inject as C, resolveSettings as D, resolveAuthOverrides as E, redactSecrets as F,
|
|
1915
|
+
export { numOrNull as A, inject as C, resolveSettings as D, resolveAuthOverrides as E, redactSecrets as F, resolveToolInputs as I, retryBounded as L, obj as M, pluginError as N, forget as O, redactError as P, str as R, hostName as S, name as T, parseUsageOverview as _, buildDescriptors as a, readSubscriptionExpiry as b, summarizeCatalog as c, isVideoGenModel as d, checkShape as f, parseSubscriptionUsage as g, parsePlans as h, PROBED_VISION as i, numZeroOk as j, num as k, visionOf as l, matchCurrentPlan as m, LLM_DISPLAY_NAME as n, filterByEnabled as o, countOf as p, LLM_PROVIDER_ID as r, rosterWithAvailability as s, DEFAULT_REASONING_EFFORT as t, isImageGenModel as u, parseUsageSeries as v, isAdmitted as w, CONFIG_DEFAULTS as x, quotaWindows as y, verbatim as z };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-agnes-token-plan",
|
|
3
3
|
"displayName": "DSH Connect Agnes Token Plan",
|
|
4
4
|
"icon": "./icon.svg",
|
|
5
|
-
"version": "0.11.
|
|
5
|
+
"version": "0.11.2",
|
|
6
6
|
"description": "Agnes connect bundle for the Harness Web UI: Token Plan quota panel (account-wide quota windows, cumulative usage, console sign-in) plus optional provider registration that brings Agnes models into DSH conversation, with host-side rate-limit self-healing.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|