dsh-qoder-connect 0.1.8 → 0.2.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/assets/icon.svg +6 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +247 -22
- package/lib/index.d.ts +6 -0
- package/lib/index.js +759 -119
- package/lib/{variants-DE76gF0Y.js → variants-DuEgaUad.js} +183 -61
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +11 -4
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect x="1" y="1" width="34" height="34" rx="8" fill="url(#g)" />
|
|
3
|
+
<path d="M19.6 7.5 11 20h5.6l-1.4 8.5L24.5 16h-5.6l.7-8.5Z" fill="#FFFFFF"/>
|
|
4
|
+
<circle cx="25.5" cy="25.5" r="3.2" fill="#45D9E7"/>
|
|
5
|
+
<defs><linearGradient id="g" x1="18" y1="1" x2="18" y2="35" gradientUnits="userSpaceOnUse"><stop stop-color="#7CB7FF"/><stop offset="1" stop-color="#145AF3"/></linearGradient></defs>
|
|
6
|
+
</svg>
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, T as QoderCatalogStore, V as qoderCredentialIdentity, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey, z as QoderCredentialStore } from "./variants-DuEgaUad.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { realpathSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
package/lib/client.js
CHANGED
|
@@ -213,9 +213,9 @@ window.__ModuleLoader__.load({
|
|
|
213
213
|
* The shared quota-settings card: one card above the two variant cards that
|
|
214
214
|
* configures both sidebar quota widgets.
|
|
215
215
|
*
|
|
216
|
-
* Like the built-in plugin cards, it registers into
|
|
217
|
-
*
|
|
218
|
-
*
|
|
216
|
+
* Like the built-in plugin cards, it registers into the Plugins tab's card
|
|
217
|
+
* list — `settings.plugin.item` on DSH 0.1.5, the shared 《插件设置》 block on
|
|
218
|
+
* 0.1.7, where that list is gone — and writes through the settings face's
|
|
219
219
|
* revision-fenced `set` — the same durable-write path every preference row
|
|
220
220
|
* uses. A toggle commits on click: each click is one explicit user choice,
|
|
221
221
|
* and the scope's ordering makes the last one win, so no staged-draft form is
|
|
@@ -2793,6 +2793,115 @@ window.__ModuleLoader__.load({
|
|
|
2793
2793
|
return t("probeNoteUnknown");
|
|
2794
2794
|
}
|
|
2795
2795
|
//#endregion
|
|
2796
|
+
//#region src/client/http-settings-scope.ts
|
|
2797
|
+
/** Base path of the host half's settings face. */
|
|
2798
|
+
const ROUTE_BASE = "/plugins/dsh-qoder-connect";
|
|
2799
|
+
/**
|
|
2800
|
+
* Fetch one settings face document.
|
|
2801
|
+
* @param init - the request init (method, headers, body).
|
|
2802
|
+
* @returns the parsed document.
|
|
2803
|
+
* @throws when the transport or the host refuses the request.
|
|
2804
|
+
*/
|
|
2805
|
+
async function request(init) {
|
|
2806
|
+
const response = await fetch(`${ROUTE_BASE}/settings`, {
|
|
2807
|
+
credentials: "same-origin",
|
|
2808
|
+
...init
|
|
2809
|
+
});
|
|
2810
|
+
const value = await response.json().catch(() => void 0);
|
|
2811
|
+
if (!response.ok) {
|
|
2812
|
+
const error = typeof value === "object" && value !== null && "error" in value ? String(value["error"]) : `HTTP ${response.status}`;
|
|
2813
|
+
throw new Error(error);
|
|
2814
|
+
}
|
|
2815
|
+
if (typeof value !== "object" || value === null || !("value" in value)) throw new Error("qoder: settings face answered an invalid document");
|
|
2816
|
+
return value;
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* The scope the card was written against, backed by this plugin's own file.
|
|
2820
|
+
*
|
|
2821
|
+
* A write optimistically adopts the value it just sent, then confirms with the
|
|
2822
|
+
* host's answer (which re-reads the file it wrote), so the card never renders a
|
|
2823
|
+
* value the host does not hold.
|
|
2824
|
+
*/
|
|
2825
|
+
var OwnQuotaSettingsScope = class {
|
|
2826
|
+
document;
|
|
2827
|
+
listeners = /* @__PURE__ */ new Set();
|
|
2828
|
+
snapshot = {
|
|
2829
|
+
status: "loading",
|
|
2830
|
+
value: void 0,
|
|
2831
|
+
writable: true
|
|
2832
|
+
};
|
|
2833
|
+
/**
|
|
2834
|
+
* Load the current document. Call once before the card binds; repeated calls
|
|
2835
|
+
* are harmless and re-read the host.
|
|
2836
|
+
* @returns the effective values.
|
|
2837
|
+
*/
|
|
2838
|
+
async load() {
|
|
2839
|
+
try {
|
|
2840
|
+
this.document = await request({ headers: { accept: "application/json" } });
|
|
2841
|
+
} catch {
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
this.publish();
|
|
2845
|
+
return this.document.value;
|
|
2846
|
+
}
|
|
2847
|
+
/** @returns the stable snapshot the card reads (ready once loaded). */
|
|
2848
|
+
getSnapshot() {
|
|
2849
|
+
return this.snapshot;
|
|
2850
|
+
}
|
|
2851
|
+
/** @param listener - invoked after every snapshot change. @returns the disposer. */
|
|
2852
|
+
subscribe(listener) {
|
|
2853
|
+
this.listeners.add(listener);
|
|
2854
|
+
return () => {
|
|
2855
|
+
this.listeners.delete(listener);
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Write one field.
|
|
2860
|
+
* @param field - field name inside the settings document.
|
|
2861
|
+
* @param value - the JSON-shaped value to store.
|
|
2862
|
+
* @returns whether the host accepted the write.
|
|
2863
|
+
*/
|
|
2864
|
+
async set(field, value) {
|
|
2865
|
+
return this.patch({ [field]: value });
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Clear one field: the value falls back to the schema default.
|
|
2869
|
+
* @param field - field name inside the settings document.
|
|
2870
|
+
* @returns whether the host accepted the clear.
|
|
2871
|
+
*/
|
|
2872
|
+
async unset(field) {
|
|
2873
|
+
return this.patch({ [field]: null });
|
|
2874
|
+
}
|
|
2875
|
+
/** Send one patch and adopt the host's answer. */
|
|
2876
|
+
async patch(patch) {
|
|
2877
|
+
const key = this.document?.key;
|
|
2878
|
+
if (key === void 0) return false;
|
|
2879
|
+
try {
|
|
2880
|
+
this.document = await request({
|
|
2881
|
+
method: "POST",
|
|
2882
|
+
headers: {
|
|
2883
|
+
"Content-Type": "application/json",
|
|
2884
|
+
"X-Qoder-Settings-Key": key
|
|
2885
|
+
},
|
|
2886
|
+
body: JSON.stringify(patch)
|
|
2887
|
+
});
|
|
2888
|
+
this.publish();
|
|
2889
|
+
return true;
|
|
2890
|
+
} catch {
|
|
2891
|
+
return false;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
/** Rebuild the cached snapshot and notify the subscribers. */
|
|
2895
|
+
publish() {
|
|
2896
|
+
this.snapshot = {
|
|
2897
|
+
status: this.document === void 0 ? "loading" : "ready",
|
|
2898
|
+
value: this.document?.value,
|
|
2899
|
+
writable: true
|
|
2900
|
+
};
|
|
2901
|
+
for (const listener of this.listeners) listener();
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
//#endregion
|
|
2796
2905
|
//#region src/client/quota-merge.ts
|
|
2797
2906
|
/**
|
|
2798
2907
|
* Group credit accounts by (packageName, packageEndTime) and sum each group.
|
|
@@ -3928,23 +4037,29 @@ window.__ModuleLoader__.load({
|
|
|
3928
4037
|
/** Stable browser-plugin name. */
|
|
3929
4038
|
const name = "dsh-qoder-connect-client";
|
|
3930
4039
|
/**
|
|
3931
|
-
* Client services
|
|
4040
|
+
* Client services this bundle requires BEFORE it activates.
|
|
3932
4041
|
*
|
|
3933
4042
|
* DSH 0.1.2 removed `@deepseek-ai/dsh-client-runtime` (the package that used to
|
|
3934
4043
|
* hold the browser `ClientContext` alias and the `slots` service). The services
|
|
3935
4044
|
* this card relies on now come from narrower packages: the `slots` registry
|
|
3936
4045
|
* moved to `@deepseek-ai/dsh-client-ui-renderer`, `locale` stayed in
|
|
3937
|
-
* `@deepseek-ai/dsh-client-locale`, and the
|
|
3938
|
-
*
|
|
3939
|
-
*
|
|
3940
|
-
*
|
|
4046
|
+
* `@deepseek-ai/dsh-client-locale`, and the Plugins tab's card list
|
|
4047
|
+
* (`settings.plugin.item`) is declared by
|
|
4048
|
+
* `@deepseek-ai/dsh-client-ui-settings-plugins`. Those packages are named in
|
|
4049
|
+
* the package's `dsh.client.inject` list.
|
|
4050
|
+
*
|
|
4051
|
+
* NOT declared here: the settings services. `settingsScope` (0.1.5) does not
|
|
4052
|
+
* exist at all on 0.1.7 — a static service dependency on it is what left this
|
|
4053
|
+
* plugin's client activation pending forever — and `configForms` (0.1.7) does
|
|
4054
|
+
* not exist on 0.1.5. Both are waited for with `ctx.inject([...], callback)`
|
|
4055
|
+
* below, where a missing service simply never calls back instead of holding
|
|
4056
|
+
* activation.
|
|
3941
4057
|
*/
|
|
3942
4058
|
const inject = [
|
|
3943
4059
|
"slots",
|
|
3944
4060
|
"locale",
|
|
3945
4061
|
"remote",
|
|
3946
|
-
"remote.session"
|
|
3947
|
-
"settingsScope"
|
|
4062
|
+
"remote.session"
|
|
3948
4063
|
];
|
|
3949
4064
|
/** The status routes per variant id (mirrors the host's locked route table). */
|
|
3950
4065
|
const VARIANT_STATUS = {
|
|
@@ -3952,6 +4067,47 @@ window.__ModuleLoader__.load({
|
|
|
3952
4067
|
"qoder-global": QODER_GLOBAL_STATUS_PATH
|
|
3953
4068
|
};
|
|
3954
4069
|
/**
|
|
4070
|
+
* The shared 《插件设置》 block's slot and entry ids.
|
|
4071
|
+
*
|
|
4072
|
+
* The three connect plugins must agree on these exactly: `settings.section` is
|
|
4073
|
+
* a `list` slot, so many blocks may coexist, but a child slot name may be
|
|
4074
|
+
* declared once and a list entry id may be registered once. The first plugin to
|
|
4075
|
+
* arrive registers the container (declaring the child slot); the others detect
|
|
4076
|
+
* it and attach their own entry, which is why the ids are fixed constants
|
|
4077
|
+
* rather than per-plugin names.
|
|
4078
|
+
*/
|
|
4079
|
+
const SHARED_SECTION_SLOT = "settings.section";
|
|
4080
|
+
const SHARED_SECTION_ID = "plugin-settings";
|
|
4081
|
+
const SHARED_SECTION_ORDER = 900;
|
|
4082
|
+
const SHARED_ITEM_SLOT = "plugin-settings.item";
|
|
4083
|
+
/** This plugin's entry id inside the shared block: its package name (unique). */
|
|
4084
|
+
const SHARED_ITEM_ID = "dsh-qoder-connect";
|
|
4085
|
+
/** The shared block's title, fixed by the contract so all three plugins agree. */
|
|
4086
|
+
const SHARED_SECTION_LABEL = "插件设置";
|
|
4087
|
+
/**
|
|
4088
|
+
* The shared 《插件设置》 section the 0.1.7 card list lives in.
|
|
4089
|
+
*
|
|
4090
|
+
* It renders nothing but its child slot: every connect plugin contributes its
|
|
4091
|
+
* own card as one `plugin-settings.item` entry. The list wrapper mirrors the
|
|
4092
|
+
* list the removed Plugins tab used for the same cards (`gap: 10`, no markers),
|
|
4093
|
+
* so the cards keep the spacing they had on 0.1.5.
|
|
4094
|
+
*
|
|
4095
|
+
* @param props - the section's slot props (owner share plus `renderSlot`).
|
|
4096
|
+
* @returns the ordered card list.
|
|
4097
|
+
*/
|
|
4098
|
+
function PluginSettingsSection(props) {
|
|
4099
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4100
|
+
style: {
|
|
4101
|
+
display: "flex",
|
|
4102
|
+
flexDirection: "column",
|
|
4103
|
+
gap: 6,
|
|
4104
|
+
padding: 0,
|
|
4105
|
+
listStyle: "none"
|
|
4106
|
+
},
|
|
4107
|
+
children: props.renderSlot(SHARED_ITEM_SLOT, {})
|
|
4108
|
+
});
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
3955
4111
|
* Register card copy, the shared quota-settings card, the two variant cards,
|
|
3956
4112
|
* and the sidebar quota cards.
|
|
3957
4113
|
*
|
|
@@ -3983,9 +4139,22 @@ window.__ModuleLoader__.load({
|
|
|
3983
4139
|
en
|
|
3984
4140
|
}), "dsh-qoder-connect: settings copy");
|
|
3985
4141
|
const t = ctx.locale.bind(namespace);
|
|
4142
|
+
/**
|
|
4143
|
+
* The bound quota settings face, filled in as soon as a host configuration
|
|
4144
|
+
* service becomes available (see the two `ctx.inject` callbacks below).
|
|
4145
|
+
*
|
|
4146
|
+
* It is bound as a SERVICE ARRIVES rather than when the settings card's
|
|
4147
|
+
* inject factory first runs: the factory only executes while the settings
|
|
4148
|
+
* page renders, so a fresh page load read no toggles and rendered no sidebar
|
|
4149
|
+
* card until the user opened settings — the exact regression the
|
|
4150
|
+
* commandcode card avoids by reading its STORED fact independently of the
|
|
4151
|
+
* settings page. The face's subscription mirrors every accepted snapshot
|
|
4152
|
+
* (toggles + interval) into the shared store the sidebar cards and the
|
|
4153
|
+
* dashboard read; a deployment with no configuration service never binds,
|
|
4154
|
+
* and the sidebar cards stay hidden.
|
|
4155
|
+
*/
|
|
3986
4156
|
let quotaScope;
|
|
3987
|
-
|
|
3988
|
-
const scope = ctx.settingsScope.bind({ namespace: "qoder-quota" });
|
|
4157
|
+
const adoptQuotaScope = (scope) => {
|
|
3989
4158
|
quotaScope = scope;
|
|
3990
4159
|
const applySnapshot = () => {
|
|
3991
4160
|
const value = scope.getSnapshot().value;
|
|
@@ -3994,20 +4163,76 @@ window.__ModuleLoader__.load({
|
|
|
3994
4163
|
};
|
|
3995
4164
|
applySnapshot();
|
|
3996
4165
|
scope.subscribe(applySnapshot);
|
|
3997
|
-
}
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
4001
|
-
name: "settings.plugin.item",
|
|
4002
|
-
key: "qoder",
|
|
4003
|
-
priority: 50,
|
|
4004
|
-
inject: () => ({
|
|
4166
|
+
};
|
|
4167
|
+
const registerUnifiedCard = (slot) => {
|
|
4168
|
+
const inject = () => ({
|
|
4005
4169
|
t,
|
|
4006
4170
|
scope: quotaScope,
|
|
4007
4171
|
signedIn: () => quotaSignInState(),
|
|
4008
4172
|
unified: true
|
|
4009
|
-
})
|
|
4010
|
-
|
|
4173
|
+
});
|
|
4174
|
+
if (slot === "settings.plugin.item") return ctx.slots.inject(slot, () => ctx.slots.register({
|
|
4175
|
+
name: slot,
|
|
4176
|
+
key: "qoder",
|
|
4177
|
+
priority: 50,
|
|
4178
|
+
inject
|
|
4179
|
+
}, QoderPluginCard));
|
|
4180
|
+
return ctx.slots.inject(slot, () => ctx.slots.register({
|
|
4181
|
+
name: slot,
|
|
4182
|
+
id: SHARED_ITEM_ID,
|
|
4183
|
+
order: 30,
|
|
4184
|
+
inject
|
|
4185
|
+
}, QoderPluginCard));
|
|
4186
|
+
};
|
|
4187
|
+
/**
|
|
4188
|
+
* Attach this plugin's card to the shared 《插件设置》 block, building the
|
|
4189
|
+
* block first if nobody has.
|
|
4190
|
+
*
|
|
4191
|
+
* Backoff protocol (identical in all three connect plugins, on purpose: a
|
|
4192
|
+
* `list` slot takes many entries but only ONE registration per id, and a
|
|
4193
|
+
* child slot name may be declared once, so the container is a rendezvous
|
|
4194
|
+
* rather than a race to be won):
|
|
4195
|
+
* 1. wait for the host to declare `settings.section`;
|
|
4196
|
+
* 2. if an entry with the shared id is already there, only attach;
|
|
4197
|
+
* 3. otherwise register the container — and if a sibling won the race and
|
|
4198
|
+
* the registration throws, fall back to attaching to theirs.
|
|
4199
|
+
*/
|
|
4200
|
+
const joinSharedSettingsBlock = () => {
|
|
4201
|
+
ctx.slots.inject(SHARED_SECTION_SLOT, () => {
|
|
4202
|
+
let disposeContainer;
|
|
4203
|
+
if (!ctx.slots.entries(SHARED_SECTION_SLOT).some((entry) => entry.options?.id === SHARED_SECTION_ID)) try {
|
|
4204
|
+
disposeContainer = ctx.slots.register({
|
|
4205
|
+
name: SHARED_SECTION_SLOT,
|
|
4206
|
+
id: SHARED_SECTION_ID,
|
|
4207
|
+
order: SHARED_SECTION_ORDER,
|
|
4208
|
+
label: () => SHARED_SECTION_LABEL,
|
|
4209
|
+
children: { [SHARED_ITEM_SLOT]: {
|
|
4210
|
+
kind: "list",
|
|
4211
|
+
scope: "root"
|
|
4212
|
+
} }
|
|
4213
|
+
}, PluginSettingsSection);
|
|
4214
|
+
} catch (error) {
|
|
4215
|
+
console.error("[dsh-qoder-connect] shared plugin-settings block lost the race; attaching to the winner:", error);
|
|
4216
|
+
}
|
|
4217
|
+
const disposeItem = registerUnifiedCard(SHARED_ITEM_SLOT);
|
|
4218
|
+
return () => {
|
|
4219
|
+
disposeItem();
|
|
4220
|
+
disposeContainer?.();
|
|
4221
|
+
};
|
|
4222
|
+
});
|
|
4223
|
+
};
|
|
4224
|
+
/**
|
|
4225
|
+
* 插件自有配置(`<profile>/.dsh-qoder-connect/settings.json`):两条宿主线的
|
|
4226
|
+
* 读写都走宿主半的 settings face,不再经过 settingsScope / configForms。
|
|
4227
|
+
*
|
|
4228
|
+
* 0.1.7 的 configForms 写入会整树 reconcile + fiber 热重载(每次约
|
|
4229
|
+
* 1~1.5 秒,且每次保存都刷新所有客户端镜像);自有文件写入是本地毫秒级
|
|
4230
|
+
* 原子写。卡片注册无条件进行:scope 在启动时载入,迟到也不会漏掉 UI。
|
|
4231
|
+
*/
|
|
4232
|
+
const ownQuotaScope = new OwnQuotaSettingsScope();
|
|
4233
|
+
ownQuotaScope.load();
|
|
4234
|
+
adoptQuotaScope(ownQuotaScope);
|
|
4235
|
+
joinSharedSettingsBlock();
|
|
4011
4236
|
const QUOTA_PANEL_ID = "qoder-quota-panel";
|
|
4012
4237
|
const CONVERSATION_PANEL_ID = "conversation";
|
|
4013
4238
|
const dashboardDocuments = {
|
package/lib/index.d.ts
CHANGED
|
@@ -834,6 +834,12 @@ declare function classifyUpstreamError(status: number, body: string): UpstreamEr
|
|
|
834
834
|
* The transport's taxonomy (`src/qoder/errors.ts`) codes an HTTP 402 as
|
|
835
835
|
* `INVALID_REQUEST`, so the status gets the last say for the quota and auth
|
|
836
836
|
* families before the code-based defaults apply.
|
|
837
|
+
*
|
|
838
|
+
* `RATE_LIMIT` is tested before the auth family because the upstream's queue
|
|
839
|
+
* answer keeps its 401/403 status: a saturated qfmodel queue reads
|
|
840
|
+
* `code: RATE_LIMIT, status: 403` (body carries 10605/isQueued/
|
|
841
|
+
* retryAfterSeconds), and letting the status arm claim it reported a dead
|
|
842
|
+
* credential — which the host never retries — instead of a throttle it would.
|
|
837
843
|
*/
|
|
838
844
|
declare function kindFromQoderFailure(failure: {
|
|
839
845
|
code: string;
|