dsh-qoder-plugin 0.6.0 → 0.6.4

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/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { B as QoderCredentialStore, D as qoderCatalogPath, E as QoderCatalogStore, H as qoderCredentialIdentity, O as FALLBACK_QODER_MODELS, 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 } from "./variants-D6eeKFyY.js";
2
+ import { B as QoderCredentialStore, D as qoderCatalogPath, E as QoderCatalogStore, H as qoderCredentialIdentity, O as FALLBACK_QODER_MODELS, 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 } from "./variants--jTtaG59.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
@@ -193,21 +193,40 @@ window.__ModuleLoader__.load({
193
193
  className: cx("qdp-iconButton", className)
194
194
  });
195
195
  }
196
+ /** Hairline marks under the rail — 33 marks, one every 3.125 % of the field. */
197
+ const SLIDER_RULER_MARKS = 33;
196
198
  /**
197
- * Context-window slider: a thick filled rail with a narrow vertical grab handle
198
- * at the fill's edge (the Qoder model-settings look). Pointer drags anywhere on
199
- * the surface jump and follow the pointer CONTINUOUSLY; arrows,
200
- * PageUp/PageDown and Home/End keep it keyboard operable. With `snapTo` the
201
- * value is snapped to the nearest offered window when the drag ENDS, so a
202
- * moving handle is a free position while a saved one is always an offer. The
203
- * component is fully controlled — it never writes anything by itself.
199
+ * Context-window slider, replicating the Qoder model-settings control.
200
+ *
201
+ * Geometry follows the upstream component: a 56 px field whose 18 px rail sits
202
+ * on the vertical centre and whose 12 px label row hangs 40 px below the field's
203
+ * top, 33 hairlines along the rail (every eighth one taller and darker) and a
204
+ * 2×12 px grab tick at the fill's edge. The scale starts at zero — a window's
205
+ * position is `value / max`, so 200K sits at 20 % of a 1M axis exactly as in the
206
+ * official table — and the rail itself stops at the widest offered window. Only
207
+ * the offered windows carry a label (9 px, sitting on the field colour so the
208
+ * hairlines pass behind); the one in force is the dark, medium-weight label.
209
+ *
210
+ * With `snapTo` a pointer drag moves the grab tick one tier at a time (it lands
211
+ * only on the offered windows); without offers it follows the pointer quantized
212
+ * to `step`. Arrows, PageUp/PageDown and Home/End keep it keyboard operable,
213
+ * with arrows walking the offers one by one. The component is fully controlled —
214
+ * it never writes anything by itself.
204
215
  */
205
216
  function QdpSlider({ label, value, min, max, step = 1e3, disabled, onChange, valueText, ticks, format, snapTo, testId }) {
206
217
  const formatValue = (raw) => format !== void 0 ? format(raw) : raw === value && valueText !== void 0 ? valueText : String(raw);
207
- const percentOf = (raw) => span > 0 ? Math.min(100, Math.max(0, (raw - min) / span * 100)) : 0;
218
+ /**
219
+ * Position of a window on the field, in percent.
220
+ *
221
+ * The official control's axis starts at zero (`value / maxValue`), not at the
222
+ * smallest window, so a 200K window covers the first fifth of a 1M axis on
223
+ * every row and equal windows line up across rows.
224
+ */
225
+ const percentOf = (raw) => max > 0 ? Math.min(100, Math.max(0, raw / max * 100)) : 0;
226
+ /** Label/tick transforms that keep the end marks inside the field. */
227
+ const edgeTransform = (percent) => percent >= 99.5 ? "translate(-100%, -50%)" : percent <= .5 ? "translate(0, -50%)" : "translate(-50%, -50%)";
208
228
  const trackRef = (0, react.useRef)(null);
209
229
  const span = max > min ? max - min : 0;
210
- const ratio = span > 0 ? Math.min(1, Math.max(0, (value - min) / span)) : 0;
211
230
  /** The offered values that actually fit the shared axis, ascending. */
212
231
  const anchors = (0, react.useMemo)(() => {
213
232
  if (snapTo === void 0 || snapTo.length === 0) return void 0;
@@ -218,6 +237,14 @@ window.__ModuleLoader__.load({
218
237
  min,
219
238
  snapTo
220
239
  ]);
240
+ /** Where the grab tick and the filled rail end. */
241
+ const fillPercent = percentOf(value);
242
+ /**
243
+ * The rail stops at the widest offered window, not at the axis end: a row that
244
+ * offers only 200K / 400K draws a shorter rail instead of empty track beyond
245
+ * its last tier (the official control behaves the same way).
246
+ */
247
+ const railPercent = percentOf(anchors === void 0 ? max : anchors[anchors.length - 1] ?? max);
221
248
  const snap = (0, react.useCallback)((raw) => {
222
249
  const clamped = Math.min(max, Math.max(min, raw));
223
250
  if (anchors !== void 0) {
@@ -235,24 +262,28 @@ window.__ModuleLoader__.load({
235
262
  step
236
263
  ]);
237
264
  /**
238
- * The value a live drag reports: clamped, then quantized to `step` only.
265
+ * The value a live drag reports.
239
266
  *
240
- * Deliberately NOT `snap`: dragging must not jump between the offered
241
- * windows, or the handle feels ratcheted. `step` (1 000 tokens by default) is
242
- * fine enough that the movement reads as continuous, while keeping the
243
- * displayed number free of round-off noise.
267
+ * With `snapTo` the drag advances tier by tier: every pointer move reports the
268
+ * nearest offered window, so the handle is never parked between two tiers
269
+ * (one slide, one context size) and the number under it is always a window the
270
+ * upstream declares. Without offers the drag is quantized to `step`, keeping
271
+ * the displayed number free of round-off noise.
244
272
  */
245
273
  const free = (0, react.useCallback)((raw) => {
274
+ if (anchors !== void 0) return snap(raw);
246
275
  const clamped = Math.min(max, Math.max(min, raw));
247
276
  if (step <= 0) return clamped;
248
277
  const stepped = min + Math.round((clamped - min) / step) * step;
249
278
  return Math.min(max, Math.max(min, stepped));
250
279
  }, [
280
+ anchors,
251
281
  max,
252
282
  min,
283
+ snap,
253
284
  step
254
285
  ]);
255
- /** Continuous position of the drag in flight; the release handler snaps it. */
286
+ /** Pointer position of the drag in flight, kept for the release settle. */
256
287
  const lastRaw = (0, react.useRef)(void 0);
257
288
  const fromClientX = (0, react.useCallback)((clientX) => {
258
289
  const element = trackRef.current;
@@ -279,12 +310,12 @@ window.__ModuleLoader__.load({
279
310
  fromClientX(event.clientX);
280
311
  };
281
312
  /**
282
- * End of drag: snap to the nearest offer.
313
+ * End of drag: settle on the offer nearest the pointer.
283
314
  *
284
- * `lastRaw` is the continuous position the drag ended at, not `value` — the
285
- * controlled parent may still be rendering the previous tick when the release
286
- * lands, so reading the raw pointer position is what makes the snap land
287
- * where the user let go.
315
+ * A drag that has offers already reports one on every move, so this only
316
+ * matters for the last finger position: `lastRaw` is the pointer position the
317
+ * drag ended at, not `value` — the controlled parent may still be rendering
318
+ * the previous tick when the release lands.
288
319
  */
289
320
  const release = (event) => {
290
321
  if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
@@ -316,10 +347,10 @@ window.__ModuleLoader__.load({
316
347
  event.preventDefault();
317
348
  if (next !== value) onChange(next);
318
349
  };
319
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
350
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
320
351
  className: "qdp-sliderField",
321
352
  "data-testid": testId,
322
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
353
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
323
354
  className: "qdp-slider",
324
355
  role: "slider",
325
356
  "aria-label": label,
@@ -334,25 +365,46 @@ window.__ModuleLoader__.load({
334
365
  onPointerUp: release,
335
366
  onPointerCancel: release,
336
367
  onKeyDown,
337
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
368
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
338
369
  className: "qdp-sliderTrack",
339
370
  ref: trackRef,
340
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
341
- className: "qdp-sliderFill",
342
- style: { width: `${(ratio * 100).toFixed(2)}%` }
343
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
344
- className: "qdp-sliderThumb",
345
- style: { left: `${(ratio * 100).toFixed(2)}%` }
371
+ children: [
372
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
373
+ className: "qdp-sliderRail",
374
+ style: { width: `${railPercent.toFixed(2)}%` }
375
+ }),
376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
377
+ className: "qdp-sliderFill",
378
+ style: { width: `${fillPercent.toFixed(2)}%` }
379
+ }),
380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
381
+ className: "qdp-sliderThumb",
382
+ style: { left: `calc(${fillPercent.toFixed(2)}% - 5px)` }
383
+ })
384
+ ]
385
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
386
+ className: "qdp-windowTicks",
387
+ children: [Array.from({ length: SLIDER_RULER_MARKS }, (_, index) => {
388
+ const percent = index / 32 * 100;
389
+ if (percent > railPercent + .01) return null;
390
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
391
+ "aria-hidden": "true",
392
+ className: cx("qdp-windowTickMark", index % 8 === 0 && "qdp-windowTickMarkMajor"),
393
+ style: { left: `${percent.toFixed(2)}%` }
394
+ }, `mark-${index}`);
395
+ }), (ticks ?? []).map((tick) => {
396
+ const percent = percentOf(tick);
397
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
398
+ className: cx("qdp-windowTick", tick === value && "qdp-windowTickActive"),
399
+ style: {
400
+ left: `${percent.toFixed(2)}%`,
401
+ transform: edgeTransform(percent)
402
+ },
403
+ children: formatValue(tick)
404
+ }, tick);
346
405
  })]
347
- })
348
- }), ticks !== void 0 && ticks.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
349
- className: "qdp-windowTicks",
350
- children: ticks.map((tick) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
351
- className: "qdp-windowTick",
352
- style: { left: `${percentOf(tick).toFixed(2)}%` },
353
- children: formatValue(tick)
354
- }, tick))
355
- })]
406
+ })]
407
+ })
356
408
  });
357
409
  }
358
410
  //#endregion
@@ -405,20 +457,19 @@ window.__ModuleLoader__.load({
405
457
  return `${factor.toFixed(1)}x`;
406
458
  }
407
459
  /**
408
- * The reasoning-effort tag's text, or `undefined` when the model advertises no
409
- * selectable efforts.
460
+ * The reasoning-effort tag's text: the levels the model can reason at, plus the
461
+ * upstream default.
410
462
  *
411
- * Wire ids are translated through the locale (unknown ids fall back to their
412
- * raw spelling), and the upstream's default effort is called out so the user
413
- * can see which level the model actually runs at until they change it.
463
+ * The tag speaks the upstream's English wire vocabulary (`low`, `high`,
464
+ * `xhigh`, `max`, …) in every UI language — the words are model parameters, and
465
+ * the official Qoder app lists them untranslated. A model that advertises no
466
+ * selectable effort says so instead of drawing an empty tag.
414
467
  */
415
468
  function effortsTagText(model, t) {
416
469
  const efforts = (model.reasoningEfforts ?? []).filter((id) => id !== "");
417
- if (efforts.length === 0) return void 0;
470
+ if (efforts.length === 0) return t("effortsUnsupported");
418
471
  const nameOf = (id) => id === "low" ? t("effortLow") : id === "medium" ? t("effortMedium") : id === "high" ? t("effortHigh") : id === "xhigh" ? t("effortXhigh") : id === "max" ? t("effortMax") : id === "minimal" ? t("effortMinimal") : id;
419
- const levels = efforts.map(nameOf).join(" · ");
420
- const fallback = model.defaultReasoningEffort !== void 0 && efforts.includes(model.defaultReasoningEffort) ? `(${t("modelEffortsDefault", { name: nameOf(model.defaultReasoningEffort) })})` : "";
421
- return t("modelEfforts", { levels }) + fallback;
472
+ return efforts.map(nameOf).join(" · ") + (model.defaultReasoningEffort !== void 0 && efforts.includes(model.defaultReasoningEffort) ? ` (${t("modelEffortsDefault", { name: nameOf(model.defaultReasoningEffort) })})` : "");
422
473
  }
423
474
  /** Round an axis end up to a clean K/M boundary so tick labels stay readable. */
424
475
  function roundUpAxis(value) {
@@ -446,19 +497,17 @@ window.__ModuleLoader__.load({
446
497
  const clampWindow = (value, min, max) => Math.min(max, Math.max(min, value));
447
498
  const uniqueSorted = (ids) => [...new Set(ids)].sort((a, b) => a.localeCompare(b));
448
499
  /**
449
- * Marks under a row's track: the model's declared windows plus both axis ends,
450
- * so the shared scale stays readable (contract: min on the left, axisMax on the
451
- * right). The endpoint labels are the same on every row, which is what lets a
452
- * 400K on one row line up with a 400K on the next.
500
+ * The windows this row offers as tiers, clipped to the shared axis.
501
+ *
502
+ * These are exactly the values the slider lands on and the marks it draws; the
503
+ * shared axis ENDS are deliberately not added. On a row whose narrowest offer
504
+ * sits close to the global minimum (the international roster's Performance
505
+ * offers 272K while the shared minimum is 200K) the extra minimum label
506
+ * overlapped the row's own first label. The scale is still shared, so a 400K on
507
+ * one row keeps lining up with a 400K on the next.
453
508
  */
454
- function rowTicks(model, min, max) {
455
- const declared = declaredWindows(model);
456
- const marks = declared.length > 0 ? [
457
- min,
458
- ...declared,
459
- max
460
- ] : [min, max];
461
- return [...new Set(marks)].filter((value) => value >= min && value <= max).sort((a, b) => a - b);
509
+ function rowTiers(model, min, max) {
510
+ return declaredWindows(model).filter((value) => value >= min && value <= max);
462
511
  }
463
512
  /**
464
513
  * Signature of everything the draft is based on. When it changes the draft is
@@ -557,6 +606,7 @@ window.__ModuleLoader__.load({
557
606
  children: [
558
607
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": "true" }),
559
608
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelNameColumn") }),
609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("effortsHeading") }),
560
610
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("contextHeading") })
561
611
  ]
562
612
  }), list.map((model) => {
@@ -566,6 +616,7 @@ window.__ModuleLoader__.load({
566
616
  const value = draft.windows[model.id] ?? baseline;
567
617
  const current = value === void 0 ? void 0 : clampWindow(value, minWindow, axisMax);
568
618
  const effortsText = effortsTagText(model, t);
619
+ const tiers = rowTiers(model, minWindow, axisMax);
569
620
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
570
621
  className: "qdp-modelRow",
571
622
  "data-model-id": model.id,
@@ -593,13 +644,16 @@ window.__ModuleLoader__.load({
593
644
  })
594
645
  }),
595
646
  model.priceFactor === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QdpTag, { children: formatRate(model.priceFactor) }),
596
- effortsText === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QdpTag, { children: effortsText }),
597
647
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
598
648
  className: "qdp-modelMeta",
599
649
  children: model.id
600
650
  })
601
651
  ]
602
652
  }),
653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
654
+ className: "qdp-modelEfforts",
655
+ children: effortsText
656
+ }),
603
657
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
604
658
  className: "qdp-modelAxis",
605
659
  children: current === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -614,8 +668,8 @@ window.__ModuleLoader__.load({
614
668
  disabled: controlsDisabled,
615
669
  onChange: (next) => changeWindow(model, next),
616
670
  valueText: formatTokens(current),
617
- ...model.supportedContextWindows === void 0 ? {} : { snapTo: model.supportedContextWindows },
618
- ticks: rowTicks(model, minWindow, axisMax),
671
+ snapTo: tiers,
672
+ ticks: tiers,
619
673
  format: formatTokens,
620
674
  testId: `qdp-window-${model.id}`
621
675
  })
@@ -2083,6 +2137,36 @@ window.__ModuleLoader__.load({
2083
2137
  })
2084
2138
  })]
2085
2139
  }),
2140
+ settings.status === "unavailable" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2141
+ className: "qdp-row",
2142
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2143
+ className: "qdp-rowText",
2144
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2145
+ className: "qdp-rowTitle",
2146
+ children: t("quotaPollLabel")
2147
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2148
+ className: "qdp-rowDesc",
2149
+ children: t("quotaPollHint")
2150
+ })]
2151
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2152
+ className: "qdp-rowControl",
2153
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QdpInput, {
2154
+ className: "qdp-inputShort",
2155
+ type: "number",
2156
+ min: 1,
2157
+ step: 1,
2158
+ value: pollMinutes,
2159
+ "aria-label": t("quotaPollLabel"),
2160
+ onChange: (event) => {
2161
+ const minutes = Number.parseInt(event.target.value, 10);
2162
+ if (Number.isFinite(minutes) && minutes >= 1) writePollMinutes(minutes);
2163
+ }
2164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2165
+ className: "qdp-caption",
2166
+ children: t("quotaPollUnit")
2167
+ })]
2168
+ })]
2169
+ }),
2086
2170
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2087
2171
  className: "qdp-row",
2088
2172
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2125,36 +2209,6 @@ window.__ModuleLoader__.load({
2125
2209
  })
2126
2210
  })]
2127
2211
  }),
2128
- settings.status === "unavailable" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2129
- className: "qdp-row",
2130
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2131
- className: "qdp-rowText",
2132
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2133
- className: "qdp-rowTitle",
2134
- children: t("quotaPollLabel")
2135
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2136
- className: "qdp-rowDesc",
2137
- children: t("quotaPollHint")
2138
- })]
2139
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2140
- className: "qdp-rowControl",
2141
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QdpInput, {
2142
- className: "qdp-inputShort",
2143
- type: "number",
2144
- min: 1,
2145
- step: 1,
2146
- value: pollMinutes,
2147
- "aria-label": t("quotaPollLabel"),
2148
- onChange: (event) => {
2149
- const minutes = Number.parseInt(event.target.value, 10);
2150
- if (Number.isFinite(minutes) && minutes >= 1) writePollMinutes(minutes);
2151
- }
2152
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2153
- className: "qdp-caption",
2154
- children: t("quotaPollUnit")
2155
- })]
2156
- })]
2157
- }),
2158
2212
  settings.status === "ready" && !settings.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2159
2213
  className: "qdp-error",
2160
2214
  children: t("quotaSettingsSaveFailed")
@@ -3592,56 +3646,66 @@ window.__ModuleLoader__.load({
3592
3646
 
3593
3647
  /* ---------------------------------------------------------------- sliders */
3594
3648
 
3649
+ /* The official Qoder model-settings slider: a 56 px field, an 18 px rail on the
3650
+ vertical centre, a 2 × 12 px grab tick at the fill's edge and a 12 px label
3651
+ row hanging 40 px below the field's top. The rail is a soft neutral and the
3652
+ fill a 42 % brand tint, so the grab tick reads as the only strong mark. */
3595
3653
  .qdp-slider {
3596
3654
  position: relative;
3597
- display: flex;
3598
- align-items: center;
3599
- height: 32px;
3600
- padding: 0 2px;
3655
+ width: 100%;
3656
+ max-width: 252px;
3657
+ height: 56px;
3601
3658
  border-radius: var(--dsw-radius-xs);
3602
3659
  cursor: pointer;
3603
3660
  touch-action: none;
3604
3661
  user-select: none;
3605
3662
  }
3606
3663
  .qdp-slider[aria-disabled='true'] {
3607
- opacity: 0.5;
3664
+ opacity: 0.55;
3608
3665
  cursor: default;
3609
3666
  }
3610
3667
  .qdp-sliderTrack {
3611
- position: relative;
3612
- width: 100%;
3613
- height: 12px;
3614
- border-radius: 999px;
3615
- corner-shape: round;
3616
- background: var(--dsw-alias-interactive-bg-hover);
3668
+ position: absolute;
3669
+ top: 50%;
3670
+ right: 0;
3671
+ left: 0;
3672
+ height: 18px;
3673
+ transform: translateY(-50%);
3617
3674
  }
3675
+ .qdp-sliderRail,
3618
3676
  .qdp-sliderFill {
3619
3677
  position: absolute;
3620
3678
  top: 0;
3621
3679
  left: 0;
3622
3680
  height: 100%;
3623
- border-radius: 999px;
3624
- corner-shape: round;
3625
- /* A solid, readable bar (the Qoder model-settings look): the fill is a
3626
- strong brand tint, the handle the same colour at full strength. */
3627
- background: var(--dsw-alias-state-business-primary);
3628
- opacity: 0.42;
3681
+ border-radius: var(--dsw-radius-xs);
3682
+ }
3683
+ .qdp-sliderRail {
3684
+ background: var(--dsw-alias-interactive-bg-hover);
3685
+ }
3686
+ /* The chosen stretch is a quiet neutral, exactly like the upstream rail: the
3687
+ strong mark on the control is the grab tick, not a saturated fill. */
3688
+ .qdp-sliderFill {
3689
+ background: var(--dsw-alias-border-l3);
3629
3690
  }
3630
3691
  .qdp-sliderThumb {
3631
3692
  position: absolute;
3632
- top: 50%;
3633
- width: 4px;
3634
- height: 20px;
3635
- border-radius: 2px;
3693
+ top: 3px;
3694
+ width: 2px;
3695
+ height: 12px;
3696
+ border-radius: 1px;
3636
3697
  background: var(--dsw-alias-state-business-primary);
3637
- transform: translate(-50%, -50%);
3638
3698
  }
3639
3699
 
3640
3700
  /* ----------------------------------------------------------------- models */
3641
3701
 
3642
3702
  .qdp-modelScroll {
3643
3703
  max-height: 420px;
3704
+ /* Vertical scrolling only: a horizontal bar under the roster was pure noise,
3705
+ and the extra right padding keeps the scrollbar off the context slider. */
3706
+ overflow-x: hidden;
3644
3707
  overflow-y: auto;
3708
+ padding-right: 12px;
3645
3709
  overscroll-behavior: contain;
3646
3710
  }
3647
3711
  .qdp-modelColumns {
@@ -3649,8 +3713,8 @@ window.__ModuleLoader__.load({
3649
3713
  top: 0;
3650
3714
  z-index: 1;
3651
3715
  display: grid;
3652
- grid-template-columns: auto minmax(0, 1fr) 232px;
3653
- gap: 10px;
3716
+ grid-template-columns: auto minmax(0, 1fr) minmax(0, 1.15fr) 232px;
3717
+ gap: 12px;
3654
3718
  align-items: center;
3655
3719
  padding: 4px 0 4px;
3656
3720
  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
@@ -3660,8 +3724,8 @@ window.__ModuleLoader__.load({
3660
3724
  }
3661
3725
  .qdp-modelRow {
3662
3726
  display: grid;
3663
- grid-template-columns: auto minmax(0, 1fr) 232px;
3664
- gap: 10px;
3727
+ grid-template-columns: auto minmax(0, 1fr) minmax(0, 1.15fr) 232px;
3728
+ gap: 12px;
3665
3729
  align-items: center;
3666
3730
  min-height: 44px;
3667
3731
  padding: 6px 0;
@@ -3708,34 +3772,67 @@ window.__ModuleLoader__.load({
3708
3772
  color: var(--dsw-alias-label-caption);
3709
3773
  font-variant-numeric: tabular-nums;
3710
3774
  }
3775
+ /* The effort column: the tiers themselves, no "Efforts:" prefix — the column
3776
+ header says what they are. */
3777
+ .qdp-modelEfforts {
3778
+ min-width: 0;
3779
+ font: var(--dsw-font-xxs-12);
3780
+ color: var(--dsw-alias-label-secondary);
3781
+ font-variant-numeric: tabular-nums;
3782
+ overflow-wrap: anywhere;
3783
+ }
3711
3784
  .qdp-modelAxis {
3712
3785
  min-width: 0;
3713
3786
  }
3714
3787
  .qdp-sliderField {
3715
3788
  position: relative;
3716
- display: flex;
3717
- flex-direction: column;
3718
- gap: 2px;
3719
3789
  min-width: 0;
3720
3790
  }
3721
3791
  .qdp-windowTicks {
3722
- position: relative;
3792
+ position: absolute;
3793
+ top: 40px;
3794
+ right: 0;
3795
+ left: 0;
3723
3796
  height: 12px;
3724
- margin: 0 2px;
3797
+ pointer-events: none;
3725
3798
  }
3799
+ /* Hairline ruler under the rail: 33 marks across the field, every eighth one
3800
+ taller and a step darker, matching the official scale. */
3801
+ .qdp-windowTickMark {
3802
+ position: absolute;
3803
+ top: 50%;
3804
+ width: 1px;
3805
+ height: 7px;
3806
+ border-radius: 999px;
3807
+ background: var(--dsw-alias-label-caption);
3808
+ transform: translate(-50%, -50%);
3809
+ }
3810
+ .qdp-windowTickMarkMajor {
3811
+ height: 10px;
3812
+ background: var(--dsw-alias-label-tertiary);
3813
+ }
3814
+ /* Tier labels sit on the field colour so the ruler passes behind them. */
3726
3815
  .qdp-windowTick {
3727
3816
  position: absolute;
3728
- transform: translateX(-50%);
3817
+ top: 50%;
3818
+ padding: 0 4px;
3819
+ background: var(--dsw-alias-settings-card-fill);
3729
3820
  font: var(--dsw-font-xxxs-11);
3821
+ font-size: 9px;
3822
+ line-height: 12px;
3730
3823
  color: var(--dsw-alias-label-tertiary);
3731
3824
  font-variant-numeric: tabular-nums;
3732
3825
  white-space: nowrap;
3733
3826
  }
3734
- .qdp-windowTick:first-child {
3735
- transform: none;
3827
+ /* The window in force is the only dark number: the other tiers are offers the
3828
+ user has not selected, so they keep the tertiary (grey) tone. */
3829
+ .qdp-windowTickActive {
3830
+ color: var(--dsw-alias-label-primary);
3831
+ font-weight: 500;
3736
3832
  }
3737
- .qdp-windowTick:last-child {
3738
- transform: translateX(-100%);
3833
+ /* A disabled slider greys every label, the active one included. */
3834
+ .qdp-slider[aria-disabled='true'] .qdp-windowTick {
3835
+ color: var(--dsw-alias-label-caption);
3739
3836
  }
3740
3837
 
3741
3838
  /* --------------------------------------------------------------- save bar */
@@ -4113,6 +4210,7 @@ window.__ModuleLoader__.load({
4113
4210
  modelsNoModels: "No models available.",
4114
4211
  modelsNoMatch: "No models matching \"{query}\".",
4115
4212
  modelNameColumn: "Model name",
4213
+ effortsHeading: "Reasoning effort",
4116
4214
  contextHeading: "Context window",
4117
4215
  contextUpTo: "up to {size}",
4118
4216
  contextDefault: "default {size}",
@@ -4157,17 +4255,17 @@ window.__ModuleLoader__.load({
4157
4255
  quotaToggleHint: "Show this account’s remaining credit in the sidebar footer.",
4158
4256
  autoCheckInCN: "Qoder (China) daily auto check-in (100 Credits)",
4159
4257
  autoCheckInGlobal: "Qoder Global (International) daily auto check-in",
4160
- autoCheckInHintCN: "Automatically check in daily to claim 100 Credits for the China account.",
4161
- autoCheckInHintGlobal: "Automatically check in daily to claim available perks for the Global account.",
4162
- checkInTimeHint: "The moment the automatic check-in runs each day, in UTC+8.",
4258
+ autoCheckInHintCN: "Check in automatically every day; during campaigns this earns 100 Credits of compute for the China and Global accounts.",
4259
+ autoCheckInHintGlobal: "Check in automatically every day; during campaigns this earns 100 Credits of compute for the China and Global accounts.",
4260
+ checkInTimeHint: "The time the automatic check-in runs each day, Beijing time (UTC+8), refreshing at 10:00 daily.",
4163
4261
  autoCheckInStatusClaimed: "Checked in today (+{amount} Credits)",
4164
4262
  autoCheckInStatusAlready: "Already checked in today",
4165
4263
  autoCheckInStatusNoCampaign: "No active benefit campaign today",
4166
4264
  checkInNeedsClient: "Qoder's daily campaign needs the Qoder desktop app on this machine: the international service only serves it to requests carrying a client-minted device identity. Install Qoder and try again.",
4167
4265
  autoCheckInStatusError: "Auto check-in error: {message}",
4168
4266
  quotaSignInRequired: "Save a PAT for this variant first to enable this card.",
4169
- quotaPollLabel: "Refresh interval",
4170
- quotaPollHint: "Applies to both quota cards. Longer is kinder to the billing endpoint.",
4267
+ quotaPollLabel: "Credit refresh interval",
4268
+ quotaPollHint: "How often the remaining-credit label refreshes; a longer interval is kinder to the billing endpoint.",
4171
4269
  quotaPollUnit: "min",
4172
4270
  quotaSettingsSave: "Save",
4173
4271
  quotaSettingsSaving: "Saving…",
@@ -4212,10 +4310,9 @@ window.__ModuleLoader__.load({
4212
4310
  modelsSaveFailed: "Save failed: {message}",
4213
4311
  contextWindowAria: "Context window size",
4214
4312
  variantSwitcherAria: "Qoder version",
4215
- quotaReadoutEnabledLabel: "Show credits in the composer",
4216
- quotaReadoutEnabledDesc: "A remaining-credit chip in the composer toolbar; click it to open the details panel.",
4313
+ quotaReadoutEnabledLabel: "Credits in the dialog",
4314
+ quotaReadoutEnabledDesc: "Show the remaining credits on the composer toolbar; click to expand the details panel.",
4217
4315
  checkInTime: "Check-in time",
4218
- modelEfforts: "Efforts: {levels}",
4219
4316
  modelEffortsDefault: "default {name}",
4220
4317
  effortMinimal: "minimal",
4221
4318
  effortLow: "low",
@@ -4223,6 +4320,7 @@ window.__ModuleLoader__.load({
4223
4320
  effortHigh: "high",
4224
4321
  effortXhigh: "xhigh",
4225
4322
  effortMax: "max",
4323
+ effortsUnsupported: "not supported",
4226
4324
  accountCurrent: "In use",
4227
4325
  creditsRemaining: "Remaining credit",
4228
4326
  checkInStatus: "Check-in",
@@ -4324,6 +4422,7 @@ window.__ModuleLoader__.load({
4324
4422
  modelsNoModels: "暂无可配置的模型。",
4325
4423
  modelsNoMatch: "未找到匹配“{query}”的模型。",
4326
4424
  modelNameColumn: "模型名称",
4425
+ effortsHeading: "思考强度",
4327
4426
  contextHeading: "上下文窗口",
4328
4427
  contextUpTo: "最高 {size}",
4329
4428
  contextDefault: "默认 {size}",
@@ -4368,17 +4467,17 @@ window.__ModuleLoader__.load({
4368
4467
  quotaToggleHint: "在侧栏底部展示该账号的剩余额度。",
4369
4468
  autoCheckInCN: "Qoder(国内版)每日自动签到",
4370
4469
  autoCheckInGlobal: "Qoder Global(国际版)每日自动签到",
4371
- autoCheckInHintCN: "每天自动签到,为国内版账号领取 100 Credits 算力额度。",
4372
- autoCheckInHintGlobal: "每天自动签到,为国际版账号领取可用福利额度。",
4373
- checkInTimeHint: "每天执行自动签到的时刻,按北京时间(UTC+8)。",
4470
+ autoCheckInHintCN: "每天自动签到,活动期间为国内版和国际版账号领取 100 Credits 算力额度。",
4471
+ autoCheckInHintGlobal: "每天自动签到,活动期间为国内版和国际版账号领取 100 Credits 算力额度。",
4472
+ checkInTimeHint: "每天执行自动签到的时刻,按北京时间(UTC+8)于每日 10:00 刷新。",
4374
4473
  autoCheckInStatusClaimed: "今日已自动签到(+{amount} Credits)",
4375
4474
  autoCheckInStatusAlready: "今日已完成签到",
4376
4475
  autoCheckInStatusNoCampaign: "今日无可用签到福利活动",
4377
4476
  checkInNeedsClient: "每日签到需要本机安装 Qoder 桌面客户端:国际版只对携带客户端设备身份的请求下发该活动。安装 Qoder 后重试即可。",
4378
4477
  autoCheckInStatusError: "自动签到出错:{message}",
4379
4478
  quotaSignInRequired: "请先为该变体保存 PAT,再开启此开关。",
4380
- quotaPollLabel: "刷新间隔",
4381
- quotaPollHint: "对两张额度卡片同时生效。间隔越长对计费接口越友好。",
4479
+ quotaPollLabel: "积分刷新间隔",
4480
+ quotaPollHint: "剩余积分标签的刷新频率,间隔越长对计费接口越友好。",
4382
4481
  quotaPollUnit: "分钟",
4383
4482
  quotaSettingsSave: "保存",
4384
4483
  quotaSettingsSaving: "保存中…",
@@ -4423,17 +4522,17 @@ window.__ModuleLoader__.load({
4423
4522
  modelsSaveFailed: "保存失败:{message}",
4424
4523
  contextWindowAria: "上下文窗口大小",
4425
4524
  variantSwitcherAria: "Qoder 版本",
4426
- quotaReadoutEnabledLabel: "在对话框显示积分",
4427
- quotaReadoutEnabledDesc: "在输入框工具条上显示剩余积分标签,点击可展开明细面板。",
4525
+ quotaReadoutEnabledLabel: "对话框显示积分",
4526
+ quotaReadoutEnabledDesc: "在输入框工具条上展示剩余积分,点击可展开明细面板。",
4428
4527
  checkInTime: "签到时间",
4429
- modelEfforts: "思考:{levels}",
4430
- modelEffortsDefault: "默认{name}",
4431
- effortMinimal: "最低",
4432
- effortLow: "低",
4433
- effortMedium: "中",
4434
- effortHigh: "高",
4435
- effortXhigh: "超高",
4436
- effortMax: "最大",
4528
+ modelEffortsDefault: "default {name}",
4529
+ effortMinimal: "minimal",
4530
+ effortLow: "low",
4531
+ effortMedium: "medium",
4532
+ effortHigh: "high",
4533
+ effortXhigh: "xhigh",
4534
+ effortMax: "max",
4535
+ effortsUnsupported: "not supported",
4437
4536
  accountCurrent: "当前使用",
4438
4537
  creditsRemaining: "剩余积分",
4439
4538
  checkInStatus: "签到状态",
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as QODER_AUTH_PATH, B as QoderCredentialStore, C as PROBE_EFFORT_CANDIDATES, D as qoderCatalogPath, E as QoderCatalogStore, F as QODER_SETTINGS_FACE_PATH, G as QODER_DATA_DIR_NAME, H as qoderCredentialIdentity, I as QODER_STATUS_PATH, J as qoderStateDir, K as qoderMachineIdPath, L as QODER_AUTH_FILENAME, M as QODER_GLOBAL_PROBE_PATH, N as QODER_GLOBAL_STATUS_PATH, O as FALLBACK_QODER_MODELS, P as QODER_PROBE_PATH, R as QODER_PAT_ENV_CN, S as getMachineId, T as randomSentinel, U as qoderOwnAuthPath, V as patTail, W as QODER_DATA_DIR_ENV, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_GLOBAL_AUTH_PATH, k as QoderCatalog, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderPluginDataDir, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as probeModel, x as getClaimWindowDateString, y as validateApiKey, z as QODER_PAT_ENV_GLOBAL } from "./variants-D6eeKFyY.js";
1
+ import { A as QODER_AUTH_PATH, B as QoderCredentialStore, C as PROBE_EFFORT_CANDIDATES, D as qoderCatalogPath, E as QoderCatalogStore, F as QODER_SETTINGS_FACE_PATH, G as QODER_DATA_DIR_NAME, H as qoderCredentialIdentity, I as QODER_STATUS_PATH, J as qoderStateDir, K as qoderMachineIdPath, L as QODER_AUTH_FILENAME, M as QODER_GLOBAL_PROBE_PATH, N as QODER_GLOBAL_STATUS_PATH, O as FALLBACK_QODER_MODELS, P as QODER_PROBE_PATH, R as QODER_PAT_ENV_CN, S as getMachineId, T as randomSentinel, U as qoderOwnAuthPath, V as patTail, W as QODER_DATA_DIR_ENV, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_GLOBAL_AUTH_PATH, k as QoderCatalog, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderPluginDataDir, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as probeModel, x as getClaimWindowDateString, y as validateApiKey, z as QODER_PAT_ENV_GLOBAL } from "./variants--jTtaG59.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -4767,7 +4767,7 @@ const DEFAULT_MAX_TOKENS = 32768;
4767
4767
  const DEFAULT_CONTEXT_WINDOW = 18e4;
4768
4768
  //#endregion
4769
4769
  //#region src/version.ts
4770
- const QODER_CONNECT_VERSION = "0.6.0";
4770
+ const QODER_CONNECT_VERSION = "0.6.4";
4771
4771
  //#endregion
4772
4772
  //#region src/host-heartbeat.ts
4773
4773
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-qoder-plugin",
3
3
  "displayName": "dsh-qoder-plugin",
4
4
  "description": "把 Qoder 模型接入 DeepSeek Harness:用 PAT 调用与 Qoder CLI 同源的模型服务,支持国内版 / 国际版双区域、模型开关、上下文窗口与每日签到,并提供只读的额度概览。",
5
- "version": "0.6.0",
5
+ "version": "0.6.4",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",