dsh-provider-qoder 0.4.2 → 0.4.3

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/client.js CHANGED
@@ -223,6 +223,7 @@ window.__ModuleLoader__.load({
223
223
  refreshing: "正在刷新…",
224
224
  userQuotaTitle: "个人额度",
225
225
  orgResourceTitle: "组织资源包",
226
+ addOnQuotaTitle: "增值额度",
226
227
  dedicatedResourceTitle: "专属资源包",
227
228
  dedicatedResourceExpiresAt: "有效期至:{value}",
228
229
  quotaRemaining: "剩余 {value}%",
@@ -294,6 +295,7 @@ window.__ModuleLoader__.load({
294
295
  refreshing: "Refreshing…",
295
296
  userQuotaTitle: "Personal Quota",
296
297
  orgResourceTitle: "Org Resource Package",
298
+ addOnQuotaTitle: "Add-on Credits",
297
299
  dedicatedResourceTitle: "Dedicated Resource Package",
298
300
  dedicatedResourceExpiresAt: "Expires at: {value}",
299
301
  quotaRemaining: "{value}% remaining",
@@ -362,6 +364,146 @@ window.__ModuleLoader__.load({
362
364
  }
363
365
  }
364
366
  //#endregion
367
+ //#region src/client/model-catalog-editor.ts
368
+ /** One mounted region owns its directory and asynchronous edits. */
369
+ var QoderModelCatalogEditor = class {
370
+ region;
371
+ operations;
372
+ active = true;
373
+ confirmed;
374
+ snapshot;
375
+ listeners = /* @__PURE__ */ new Set();
376
+ constructor(region, models, operations) {
377
+ this.region = region;
378
+ this.operations = operations;
379
+ this.confirmed = models.map(cloneCatalogModel);
380
+ this.snapshot = {
381
+ models: this.confirmed,
382
+ catalog: this.confirmed,
383
+ unavailableIds: /* @__PURE__ */ new Set()
384
+ };
385
+ }
386
+ getSnapshot = () => this.snapshot;
387
+ subscribe = (listener) => {
388
+ this.listeners.add(listener);
389
+ return () => {
390
+ this.listeners.delete(listener);
391
+ };
392
+ };
393
+ /** ConfigForms supplies accepted values, including its recovery read after refusal. */
394
+ replaceModels(models) {
395
+ const followsConfig = this.snapshot.catalog === this.confirmed;
396
+ this.confirmed = models.map(cloneCatalogModel);
397
+ const selected = new Map(this.confirmed.map((model) => [model.id, model]));
398
+ this.publish({
399
+ ...this.snapshot,
400
+ models: this.confirmed,
401
+ catalog: followsConfig ? this.confirmed : this.snapshot.catalog.map((model) => selected.get(model.id) ?? model)
402
+ });
403
+ }
404
+ activate() {
405
+ this.active = true;
406
+ }
407
+ dispose() {
408
+ this.active = false;
409
+ this.listeners.clear();
410
+ }
411
+ publish(snapshot) {
412
+ if (!this.active) return;
413
+ this.snapshot = snapshot;
414
+ for (const listener of this.listeners) listener();
415
+ }
416
+ async discover() {
417
+ if (!this.active || this.snapshot.pending) return;
418
+ this.publish({
419
+ ...this.snapshot,
420
+ pending: "discovering",
421
+ failure: void 0,
422
+ failureMessage: void 0
423
+ });
424
+ try {
425
+ const result = await this.operations.discoverModels();
426
+ if (!this.active) return;
427
+ if (!result.ok) {
428
+ this.publish({
429
+ ...this.snapshot,
430
+ pending: void 0,
431
+ failure: "modelsFetchFailed",
432
+ failureMessage: result.error.message
433
+ });
434
+ return;
435
+ }
436
+ const reconciled = reconcileQoderModels(this.snapshot.catalog, result.value);
437
+ await this.save(reconciled.selected, reconciled.catalog, reconciled.unavailableIds);
438
+ } catch (error) {
439
+ this.publish({
440
+ ...this.snapshot,
441
+ pending: void 0,
442
+ failure: "modelsFetchFailed",
443
+ failureMessage: error instanceof Error ? error.message : void 0
444
+ });
445
+ }
446
+ }
447
+ async toggle(id, enabled) {
448
+ if (!this.active || this.snapshot.pending || this.snapshot.unavailableIds.has(id)) return;
449
+ const selected = new Set(this.snapshot.models.map((model) => model.id));
450
+ if (enabled) selected.add(id);
451
+ else selected.delete(id);
452
+ await this.save(this.snapshot.catalog.filter((model) => selected.has(model.id) && !this.snapshot.unavailableIds.has(model.id)));
453
+ }
454
+ async selectTier(id, tierKey) {
455
+ if (!this.active || this.snapshot.pending || !this.snapshot.models.some((model) => model.id === id)) return;
456
+ const tokenCount = this.snapshot.catalog.find((model) => model.id === id)?.contextOptions?.[tierKey]?.tokenCount;
457
+ if (typeof tokenCount !== "number" || !Number.isFinite(tokenCount) || tokenCount <= 0) return;
458
+ const apply = (candidate) => candidate.id === id ? {
459
+ ...candidate,
460
+ contextTier: tierKey,
461
+ contextWindow: tokenCount
462
+ } : candidate;
463
+ await this.save(this.snapshot.models.map(apply), this.snapshot.catalog.map(apply));
464
+ }
465
+ async save(models, catalog = this.snapshot.catalog, unavailableIds = this.snapshot.unavailableIds) {
466
+ if (models.length === 0) {
467
+ this.publish({
468
+ ...this.snapshot,
469
+ pending: void 0,
470
+ failure: "modelsRequired",
471
+ failureMessage: void 0
472
+ });
473
+ return;
474
+ }
475
+ const submitted = models.map(cloneCatalogModel);
476
+ const confirmedAtSubmission = this.confirmed;
477
+ this.publish({
478
+ ...this.snapshot,
479
+ pending: "saving",
480
+ failure: void 0,
481
+ failureMessage: void 0
482
+ });
483
+ let saved = false;
484
+ try {
485
+ saved = await this.operations.storeModels(this.region, submitted);
486
+ } catch {}
487
+ if (!this.active) return;
488
+ if (saved) {
489
+ if (this.confirmed === confirmedAtSubmission) this.confirmed = submitted;
490
+ const accepted = new Map(this.confirmed.map((model) => [model.id, model]));
491
+ const directory = new Map(catalog.map((model) => [model.id, model]));
492
+ for (const [id, model] of accepted) directory.set(id, model);
493
+ this.publish({
494
+ models: this.confirmed,
495
+ catalog: [...directory.values()],
496
+ unavailableIds: new Set([...unavailableIds].filter((id) => !accepted.has(id)))
497
+ });
498
+ } else this.publish({
499
+ models: this.confirmed,
500
+ catalog: this.confirmed,
501
+ unavailableIds: /* @__PURE__ */ new Set(),
502
+ failure: "saveFailed"
503
+ });
504
+ }
505
+ };
506
+ //#endregion
365
507
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderCredentialCard.module.css.mjs
366
508
  const css$1 = ".SRqrhW_credential{color:var(--dsw-alias-label-primary);border-radius:10px;margin-top:12px}.SRqrhW_credential p{margin:8px 0 0}.SRqrhW_credentialEditor{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);flex-direction:column;gap:12px;padding:12px 14px;display:flex}.SRqrhW_credentialRowHead{align-items:center;gap:10px;display:flex}.SRqrhW_credentialIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.SRqrhW_credentialName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.SRqrhW_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.SRqrhW_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.SRqrhW_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.SRqrhW_regionBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:500;line-height:16px;display:inline-flex}.SRqrhW_regionSelector{flex-direction:column;gap:8px;margin-bottom:14px;display:flex}.SRqrhW_regionGroup{gap:8px;display:flex}.SRqrhW_regionOption{border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);user-select:none;border-radius:8px;align-items:center;gap:6px;padding:6px 12px;font-size:12px;line-height:18px;transition:all .12s;display:inline-flex}.SRqrhW_regionOption:hover:not(.SRqrhW_regionOptionDisabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.SRqrhW_credential button.SRqrhW_regionOptionActive{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-selected);color:var(--dsw-alias-brand-primary);font-weight:500}.SRqrhW_regionOptionDisabled{cursor:not-allowed;opacity:.5}.SRqrhW_editButton{box-sizing:border-box;font:inherit;background:0 0;margin-left:auto;font-size:12px;line-height:18px;border:1px solid var(--dsw-alias-border-l2)!important;border-radius:14px!important;height:28px!important;padding:0 10px!important}.SRqrhW_editButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.SRqrhW_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;padding:14px 16px}.SRqrhW_customized{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);margin-top:12px;transition:all .12s;overflow:hidden}.SRqrhW_customizedSummary{box-sizing:border-box;border-radius:var(--dsw-radius-xl,12px);cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);user-select:none;align-items:center;gap:8px;margin:0;padding:12px 14px;font-size:13px;font-weight:600;line-height:18px;list-style:none;transition:background .12s;display:flex}.SRqrhW_customizedSummary:hover{background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelSummaryBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px;display:inline-flex}.SRqrhW_customizedSummary::-webkit-details-marker{display:none}.SRqrhW_customizedSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.SRqrhW_customized[open]>.SRqrhW_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.SRqrhW_customized[open]>.SRqrhW_customizedSummary{border-bottom:.5px solid var(--dsw-alias-border-l2);border-bottom-right-radius:0;border-bottom-left-radius:0}.SRqrhW_customizedBody{padding:12px 14px 14px}.SRqrhW_modelCatalog{flex-direction:column;gap:10px;display:flex}.SRqrhW_modelList{flex-direction:column;gap:10px;max-height:360px;padding-right:4px;display:flex;overflow-y:auto}.SRqrhW_modelList::-webkit-scrollbar{width:4px}.SRqrhW_modelList::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);border-radius:2px}.SRqrhW_modelCatalogHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.SRqrhW_modelCatalogHeadCompact{justify-content:flex-end;align-items:center;gap:12px;display:flex}.SRqrhW_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.SRqrhW_modelCatalogMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:2px 0 0!important}.SRqrhW_linkButton{box-sizing:border-box;font:inherit;background:0 0;font-size:12px;line-height:18px;border:none!important;border-radius:14px!important;height:28px!important;padding:0 10px!important}.SRqrhW_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelChoice{border-radius:6px;flex-direction:column;gap:4px;padding:6px 8px;font-size:13px;display:flex}.SRqrhW_modelChoice:hover{background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelChoiceHead{cursor:pointer;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;gap:8px;display:grid}.SRqrhW_modelTier{align-items:center;gap:8px;padding-left:24px;display:flex}.SRqrhW_modelTierLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.SRqrhW_modelTierSelect{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);height:24px;color:inherit;font:inherit;cursor:pointer;border:0;border-radius:6px;padding:0 6px;font-size:12px}.SRqrhW_modelTierHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;margin:0!important}.SRqrhW_modelChoiceName{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.SRqrhW_modelChoice code{color:var(--dsw-alias-label-tertiary);font-size:11px}.SRqrhW_modelRate{min-width:34px;color:var(--dsw-alias-brand-primary);text-align:right;font-size:12px;font-weight:600}.SRqrhW_modelUnavailable{cursor:not-allowed;opacity:.55}.SRqrhW_modelBadge{color:var(--dsw-alias-state-error-primary);font-size:11px}@media (prefers-reduced-motion:reduce){.SRqrhW_customizedSummary:before{transition:none}}.SRqrhW_head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:20px;display:flex}.SRqrhW_heading{margin:0;font-size:18px;font-weight:600}.SRqrhW_status{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_configured,.SRqrhW_success{color:var(--dsw-alias-state-success-primary)}.SRqrhW_label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;display:block}.SRqrhW_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;height:32px;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;margin-top:6px;padding:0 10px}.SRqrhW_input:focus{border-color:var(--dsw-alias-brand-primary)}.SRqrhW_input::placeholder{color:var(--dsw-alias-label-dimmed)}.SRqrhW_input:disabled{cursor:not-allowed;opacity:.4}.SRqrhW_hint{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_actions{justify-content:flex-end;gap:8px;margin-top:14px;display:flex}.SRqrhW_credential button{cursor:pointer;height:32px;color:var(--dsw-alias-label-primary);border:1px solid #0000;border-radius:16px;padding:0 12px}.SRqrhW_credential button:disabled{cursor:not-allowed;opacity:.4}.SRqrhW_primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)!important}.SRqrhW_primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.SRqrhW_secondary{background:0 0;border-color:var(--dsw-alias-border-l2)!important}.SRqrhW_secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_danger{background:0 0;border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.SRqrhW_danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.SRqrhW_error{color:var(--dsw-alias-state-error-primary);font-size:12px}.SRqrhW_success{font-size:12px}.SRqrhW_accountSection{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.SRqrhW_accountHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.SRqrhW_userInfo{align-items:center;gap:10px;min-width:0;display:flex}.SRqrhW_avatar{background:var(--dsw-alias-brand-primary);width:32px;height:32px;color:var(--dsw-alias-label-primary-foreground);text-transform:uppercase;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:600;display:flex}.SRqrhW_userDetails{flex-direction:column;gap:2px;min-width:0;display:flex}.SRqrhW_userName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.SRqrhW_userEmail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.SRqrhW_refreshBtn{border:1px solid var(--dsw-alias-border-l2)!important;height:26px!important;color:var(--dsw-alias-label-secondary)!important;background:0 0!important;border-radius:13px!important;padding:0 8px!important;font-size:12px!important}.SRqrhW_refreshBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)!important}.SRqrhW_quotaBlock{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding-top:8px;display:flex}.SRqrhW_quotaHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.SRqrhW_quotaLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.SRqrhW_quotaTop{justify-content:space-between;align-items:baseline;gap:8px;font-size:12px;display:flex}.SRqrhW_quotaTop strong{color:var(--dsw-alias-brand-primary);font-size:13px}.SRqrhW_quotaDescription{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0!important}.SRqrhW_quotaProgress{background:var(--dsw-alias-border-l3);width:100%;height:6px;accent-color:var(--dsw-alias-brand-primary);-webkit-appearance:none;appearance:none;border:0;border-radius:999px;overflow:hidden}.SRqrhW_quotaProgress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.SRqrhW_quotaProgress::-webkit-progress-value{background:var(--dsw-alias-brand-primary);border-radius:999px}.SRqrhW_quotaProgress::-moz-progress-bar{background:var(--dsw-alias-brand-primary);border-radius:999px}.SRqrhW_quotaMeta{color:var(--dsw-alias-label-tertiary);justify-content:space-between;align-items:center;gap:8px;font-size:11px;display:flex}.SRqrhW_accountLoadingText,.SRqrhW_accountEmptyText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}.SRqrhW_userNameRow{align-items:center;gap:8px;display:flex}.SRqrhW_planBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-brand-primary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:600;line-height:16px;display:inline-flex}.SRqrhW_orgTag{color:var(--dsw-alias-label-tertiary);font-size:11px}.SRqrhW_suspendedBanner{background:var(--dsw-alias-state-error-subtle,#ff4d4f1a);color:var(--dsw-alias-state-error-primary);border-radius:6px;align-items:center;gap:6px;margin-bottom:8px;padding:6px 10px;font-size:12px;display:flex}.SRqrhW_searchModeSection{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.SRqrhW_searchModeHintText{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:12px;line-height:18px}";
367
509
  const tagId$1 = "dsh-provider-qoder/QoderCredentialCard.module.css";
@@ -449,57 +591,26 @@ window.__ModuleLoader__.load({
449
591
  };
450
592
  //#endregion
451
593
  //#region src/client/QoderModelCatalog.tsx
452
- function validateModelCatalog(models) {
453
- return models.length === 0 ? "modelsRequired" : void 0;
454
- }
594
+ /** The key resets directory state whenever the active Qoder service region changes. */
455
595
  function QoderModelCatalog(props) {
456
- const { operations, models, disabled, fetchDisabled, hideTitle, onChange, t } = props;
457
- const [fetching, setFetching] = (0, react.useState)(false);
458
- const [failure, setFailure] = (0, react.useState)();
459
- const [catalog, setCatalog] = (0, react.useState)();
460
- const [unavailableIds, setUnavailableIds] = (0, react.useState)(/* @__PURE__ */ new Set());
461
- const displayedModels = catalog ?? models;
462
- const selectedIds = (0, react.useMemo)(() => new Set(models.map((model) => model.id)), [models]);
463
- const fetchModels = async () => {
464
- setFetching(true);
465
- setFailure(void 0);
466
- const result = await operations.discoverModels();
467
- setFetching(false);
468
- if (!result.ok) {
469
- setFailure(result.error.message || t("modelsFetchFailed"));
470
- return;
471
- }
472
- const reconciled = reconcileQoderModels(catalog ?? models, result.value);
473
- setCatalog(reconciled.catalog);
474
- setUnavailableIds(reconciled.unavailableIds);
475
- onChange(reconciled.selected);
476
- };
477
- const toggleModel = (id, enabled) => {
478
- if (unavailableIds.has(id)) return;
479
- const source = catalog ?? models;
480
- if (catalog === void 0) setCatalog(source);
481
- const nextSelected = new Set(selectedIds);
482
- if (enabled) nextSelected.add(id);
483
- else nextSelected.delete(id);
484
- onChange(source.filter((model) => nextSelected.has(model.id) && !unavailableIds.has(model.id)));
485
- };
486
- const changeContextTier = (id, tierKey) => {
487
- const source = catalog ?? models;
488
- if (catalog === void 0) setCatalog(source);
489
- const apply = (model) => {
490
- if (model.id !== id) return model;
491
- const tokenCount = model.contextOptions?.[tierKey]?.tokenCount;
492
- if (typeof tokenCount !== "number" || !Number.isFinite(tokenCount) || tokenCount <= 0) return model;
493
- return {
494
- ...model,
495
- contextTier: tierKey,
496
- contextWindow: tokenCount
497
- };
498
- };
499
- const nextCatalog = source.map(apply);
500
- setCatalog(nextCatalog);
501
- onChange(models.map(apply));
502
- };
596
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegionModelCatalog, { ...props }, props.region);
597
+ }
598
+ function RegionModelCatalog({ operations, models, region, disabled, fetchDisabled, hideTitle, t }) {
599
+ const [editor] = (0, react.useState)(() => new QoderModelCatalogEditor(region, models, operations));
600
+ const snapshot = (0, react.useSyncExternalStore)(editor.subscribe, editor.getSnapshot, editor.getSnapshot);
601
+ (0, react.useLayoutEffect)(() => {
602
+ editor.activate();
603
+ return () => editor.dispose();
604
+ }, [editor]);
605
+ (0, react.useLayoutEffect)(() => {
606
+ editor.replaceModels(models);
607
+ }, [editor, models]);
608
+ const displayedModels = snapshot.catalog;
609
+ const unavailableIds = snapshot.unavailableIds;
610
+ const selectedIds = new Set(snapshot.models.map((model) => model.id));
611
+ const busy = snapshot.pending !== void 0;
612
+ const fetching = snapshot.pending === "discovering";
613
+ const failure = snapshot.failureMessage || (snapshot.failure ? t(snapshot.failure) : void 0);
503
614
  const renderRate = (model) => {
504
615
  if (model.isFree || model.priceFactor === 0) return t("modelRateFree");
505
616
  if (model.priceFactor === void 0) return t("modelRateUnknown");
@@ -522,13 +633,13 @@ window.__ModuleLoader__.load({
522
633
  children: t("modelsTitle")
523
634
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
524
635
  className: QoderCredentialCard_module_css_default.modelCatalogMeta,
525
- children: t("modelsEnabled", { count: models.length })
636
+ children: t("modelsEnabled", { count: snapshot.models.length })
526
637
  })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
527
638
  type: "button",
528
639
  className: QoderCredentialCard_module_css_default.linkButton,
529
- disabled: disabled || fetching || Boolean(fetchDisabled),
640
+ disabled: disabled || busy || Boolean(fetchDisabled),
530
641
  onClick: () => {
531
- fetchModels();
642
+ if (!disabled && !fetchDisabled) editor.discover();
532
643
  },
533
644
  children: fetching ? t("modelsFetching") : t("modelsFetch")
534
645
  })]
@@ -553,9 +664,9 @@ window.__ModuleLoader__.load({
553
664
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
554
665
  type: "checkbox",
555
666
  checked: !unavailable && selectedIds.has(model.id),
556
- disabled: disabled || unavailable,
667
+ disabled: disabled || busy || unavailable,
557
668
  onChange: (event) => {
558
- toggleModel(model.id, event.currentTarget.checked);
669
+ if (!disabled) editor.toggle(model.id, event.currentTarget.checked);
559
670
  }
560
671
  }),
561
672
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -580,9 +691,9 @@ window.__ModuleLoader__.load({
580
691
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
581
692
  className: QoderCredentialCard_module_css_default.modelTierSelect,
582
693
  value: selectedTier ?? "",
583
- disabled: disabled || unavailable || !selectedIds.has(model.id),
694
+ disabled: disabled || busy || unavailable || !selectedIds.has(model.id),
584
695
  onChange: (event) => {
585
- changeContextTier(model.id, event.currentTarget.value);
696
+ if (!disabled) editor.selectTier(model.id, event.currentTarget.value);
586
697
  },
587
698
  children: tiers.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
588
699
  value: tier.key,
@@ -615,18 +726,31 @@ window.__ModuleLoader__.load({
615
726
  }
616
727
  /**
617
728
  * Settings card for the Qoder subscription: credential state, subscriber
618
- * profile, and every quota block the provider reports.
729
+ * profile, and every quota block the provider reports. Region changes remount
730
+ * the account state before any old profile can be displayed.
619
731
  */
620
- function QoderAccountCard({ operations, t, activeLocale }) {
732
+ function QoderAccountCard(props) {
733
+ const modelSnapshot = (0, react.useSyncExternalStore)(props.operations.subscribeModels, props.operations.getModelSnapshot, props.operations.getModelSnapshot);
734
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegionAccountCard, {
735
+ ...props,
736
+ modelSnapshot
737
+ }, regionOf(modelSnapshot.value));
738
+ }
739
+ function RegionAccountCard({ operations, t, activeLocale, modelSnapshot }) {
621
740
  const [credentialState, setCredentialState] = (0, react.useState)({ status: "loading" });
622
741
  const [accountState, setAccountState] = (0, react.useState)({ status: "idle" });
623
742
  const [refreshing, setRefreshing] = (0, react.useState)(false);
624
743
  const [credentialRevision, setCredentialRevision] = (0, react.useState)(0);
625
- const [modelSaveError, setModelSaveError] = (0, react.useState)();
626
744
  const latestAccountRequest = (0, react.useRef)(0);
627
- const modelSnapshot = (0, react.useSyncExternalStore)(operations.subscribeModels, operations.getModelSnapshot, operations.getModelSnapshot);
745
+ const latestCredentialRequest = (0, react.useRef)(0);
746
+ (0, react.useLayoutEffect)(() => () => {
747
+ latestAccountRequest.current++;
748
+ latestCredentialRequest.current++;
749
+ }, []);
628
750
  const loadCredential = (0, react.useCallback)(async () => {
751
+ const requestId = ++latestCredentialRequest.current;
629
752
  const info = await operations.describe();
753
+ if (requestId !== latestCredentialRequest.current) return;
630
754
  setCredentialState(info === void 0 ? { status: "failed" } : {
631
755
  status: "ready",
632
756
  info
@@ -781,13 +905,14 @@ window.__ModuleLoader__.load({
781
905
  const { profile, usage, plan } = accountState.account;
782
906
  const userQuota = usage?.userQuota;
783
907
  const orgPackage = usage?.orgResourcePackage;
908
+ const addOnQuota = usage?.addOnQuota;
784
909
  const dedicatedPackages = usage?.dedicatedResourcePackages ?? [];
785
910
  const locale = activeLocale();
786
911
  const avatarInitial = (profile.name || profile.email || "Q").charAt(0).toUpperCase();
787
912
  const resetDate = formatResetDate(usage?.expiresAt);
788
913
  const planExpiry = plan?.endDate ? formatResetDate(plan.endDate) : void 0;
789
914
  const isSuspended = plan?.organization?.isSuspended === true;
790
- const hasQuota = (userQuota?.total ?? 0) > 0 || (orgPackage?.total ?? 0) > 0 || dedicatedPackages.some((pkg) => pkg.total > 0);
915
+ const hasQuota = (userQuota?.total ?? 0) > 0 || (orgPackage?.total ?? 0) > 0 || (addOnQuota?.total ?? 0) > 0 || dedicatedPackages.some((pkg) => pkg.total > 0);
791
916
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
792
917
  className: QoderCredentialCard_module_css_default.accountSection,
793
918
  children: [
@@ -844,6 +969,7 @@ window.__ModuleLoader__.load({
844
969
  }) : null,
845
970
  renderQuota("userQuota", t("userQuotaTitle"), userQuota, { resetDate }),
846
971
  renderQuota("orgResource", t("orgResourceTitle"), orgPackage, { resetDate }),
972
+ renderQuota("addOnQuota", t("addOnQuotaTitle"), addOnQuota),
847
973
  dedicatedPackages.map((pkg, index) => renderQuota(`dedicatedResource-${pkg.id ?? index}`, resolveLocalizedText(pkg.title, locale) ?? t("dedicatedResourceTitle"), pkg, {
848
974
  expiresAt: formatResetDate(pkg.expiresAt),
849
975
  description: resolveLocalizedText(pkg.description, locale)
@@ -922,16 +1048,6 @@ window.__ModuleLoader__.load({
922
1048
  const currentRegion = regionOf(modelSnapshot.value);
923
1049
  const currentModels = modelsOf(modelSnapshot.value, currentRegion);
924
1050
  const canModifyModels = modelSnapshot.status === "ready" && modelSnapshot.writable;
925
- const handleModelsChange = async (models) => {
926
- if (!canModifyModels) return;
927
- setModelSaveError(void 0);
928
- const failure = validateModelCatalog(models);
929
- if (failure) {
930
- setModelSaveError(t(failure));
931
- return;
932
- }
933
- if (!await operations.storeModels(currentRegion, models)) setModelSaveError(t("saveFailed"));
934
- };
935
1051
  const renderModelCatalogSection = () => {
936
1052
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
937
1053
  className: QoderCredentialCard_module_css_default.customized,
@@ -941,29 +1057,23 @@ window.__ModuleLoader__.load({
941
1057
  className: QoderCredentialCard_module_css_default.modelSummaryBadge,
942
1058
  children: t("modelsEnabled", { count: currentModels.length })
943
1059
  })]
944
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1060
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
945
1061
  className: QoderCredentialCard_module_css_default.customizedBody,
946
- children: [modelSnapshot.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1062
+ children: modelSnapshot.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
947
1063
  className: QoderCredentialCard_module_css_default.hint,
948
1064
  children: t("modelsLoading")
949
1065
  }) : modelSnapshot.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
950
1066
  className: QoderCredentialCard_module_css_default.error,
951
1067
  children: t("modelsUnavailable")
952
1068
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QoderModelCatalog, {
1069
+ region: currentRegion,
953
1070
  operations,
954
1071
  models: currentModels,
955
1072
  disabled: !canModifyModels,
956
1073
  fetchDisabled: !configured,
957
1074
  hideTitle: true,
958
- t,
959
- onChange: (models) => {
960
- handleModelsChange(models);
961
- }
962
- }), modelSaveError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
963
- className: QoderCredentialCard_module_css_default.error,
964
- role: "alert",
965
- children: modelSaveError
966
- }) : null]
1075
+ t
1076
+ })
967
1077
  })]
968
1078
  });
969
1079
  };
@@ -1227,17 +1337,102 @@ window.__ModuleLoader__.load({
1227
1337
  "qoder-subscription"
1228
1338
  ].includes(provider ?? "");
1229
1339
  }
1230
- function historicalContextWindow(current, lastUsed, contextWindow) {
1231
- return current.provider === lastUsed?.provider && current.model === lastUsed?.model ? contextWindow : void 0;
1232
- }
1233
- /** Composer selections belong to the current session; model defaults live in settings. */
1234
- async function saveContextSelection(operations, sessionId, region, modelId, tierKey) {
1235
- try {
1236
- return await operations.setSessionTier?.(sessionId, modelId, tierKey, region) ? "saved" : "session-failed";
1237
- } catch {
1238
- return "session-failed";
1340
+ //#endregion
1341
+ //#region src/client/session-tier-state.ts
1342
+ /** A mounted view of the host's accepted selection; it owns no manual tier map. */
1343
+ var SessionTierState = class {
1344
+ snapshot = {
1345
+ loading: true,
1346
+ saving: false,
1347
+ error: false
1348
+ };
1349
+ listeners = /* @__PURE__ */ new Set();
1350
+ generation = 0;
1351
+ active = false;
1352
+ lifetime = 0;
1353
+ unsubscribe;
1354
+ scope;
1355
+ operations;
1356
+ constructor(scope, operations) {
1357
+ this.scope = scope;
1358
+ this.operations = operations;
1239
1359
  }
1240
- }
1360
+ getSnapshot = () => this.snapshot;
1361
+ subscribe = (listener) => {
1362
+ this.listeners.add(listener);
1363
+ return () => {
1364
+ this.listeners.delete(listener);
1365
+ };
1366
+ };
1367
+ publish(value) {
1368
+ this.snapshot = {
1369
+ ...this.snapshot,
1370
+ ...value
1371
+ };
1372
+ for (const listener of this.listeners) listener();
1373
+ }
1374
+ activate() {
1375
+ this.active = true;
1376
+ this.lifetime++;
1377
+ this.publish({ saving: false });
1378
+ this.unsubscribe = this.operations.subscribeSessionTiers?.(this.scope, () => {
1379
+ this.refresh();
1380
+ });
1381
+ this.refresh();
1382
+ }
1383
+ dispose() {
1384
+ this.active = false;
1385
+ this.generation++;
1386
+ this.unsubscribe?.();
1387
+ this.unsubscribe = void 0;
1388
+ }
1389
+ matches(value) {
1390
+ return !!value && value.region === this.scope.region && value.sessionId === this.scope.sessionId && value.modelId === this.scope.modelId;
1391
+ }
1392
+ async refresh() {
1393
+ if (!this.active) return;
1394
+ const generation = ++this.generation;
1395
+ this.publish({ loading: true });
1396
+ try {
1397
+ const selection = await this.operations.readSessionTier?.(this.scope);
1398
+ if (this.active && generation === this.generation) this.publish(this.matches(selection) ? {
1399
+ selection,
1400
+ loading: false,
1401
+ error: false
1402
+ } : {
1403
+ loading: false,
1404
+ error: true
1405
+ });
1406
+ } catch {
1407
+ if (this.active && generation === this.generation) this.publish({
1408
+ loading: false,
1409
+ error: true
1410
+ });
1411
+ }
1412
+ }
1413
+ async select(tierKey) {
1414
+ if (!this.active || this.snapshot.loading || this.snapshot.saving || !this.snapshot.selection) return;
1415
+ if (tierKey === this.snapshot.selection.tierKey && !this.snapshot.error) return;
1416
+ const generation = ++this.generation;
1417
+ const lifetime = this.lifetime;
1418
+ this.publish({
1419
+ saving: true,
1420
+ error: false
1421
+ });
1422
+ try {
1423
+ const { sessionId, modelId, region } = this.scope;
1424
+ const selection = await this.operations.setSessionTier?.(sessionId, modelId, tierKey, region);
1425
+ if (this.active && generation === this.generation) this.publish(this.matches(selection) ? {
1426
+ selection,
1427
+ error: false
1428
+ } : { error: true });
1429
+ } catch {
1430
+ if (this.active && generation === this.generation) this.publish({ error: true });
1431
+ } finally {
1432
+ if (this.active && lifetime === this.lifetime) this.publish({ saving: false });
1433
+ }
1434
+ }
1435
+ };
1241
1436
  //#endregion
1242
1437
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderContextSelect.module.css.mjs
1243
1438
  const css = ".hiDrJa_root{align-items:center;display:inline-flex;position:relative}.hiDrJa_trigger{height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;user-select:none;background:0 0;border:none;border-radius:24px;outline:none;align-items:center;gap:4px;padding:0 8px;font-size:13px;font-weight:500;line-height:20px;transition:background .12s,color .12s;display:flex}.hiDrJa_trigger:hover,.hiDrJa_triggerActive{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.hiDrJa_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.hiDrJa_icon{color:var(--dsw-alias-label-caption);justify-content:center;align-items:center;display:inline-flex}.hiDrJa_label{white-space:nowrap}.hiDrJa_chevron{color:var(--dsw-alias-label-caption);align-items:center;transition:transform .12s;display:inline-flex}.hiDrJa_chevronOpen{transform:rotate(180deg)}.hiDrJa_menu{z-index:1100;background:var(--dsw-menu-surface-fill);min-width:180px;backdrop-filter:var(--dsw-menu-backdrop-filter);border:1px solid var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;padding:4px;animation:.12s hiDrJa_fadeIn;display:flex;position:absolute;bottom:calc(100% + 8px);right:0}@keyframes hiDrJa_fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.hiDrJa_menuHeader{text-transform:uppercase;letter-spacing:.5px;color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l1);margin-bottom:4px;padding:6px 10px 4px;font-size:11px;font-weight:600}.hiDrJa_option{box-sizing:border-box;width:100%;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;border-radius:8px;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;font-size:13px;line-height:20px;transition:background .1s;display:flex}.hiDrJa_option:hover{background:var(--dsw-alias-interactive-bg-hover)}.hiDrJa_optionSelected{color:var(--dsw-alias-state-brand);font-weight:600}.hiDrJa_optionInfo{align-items:center;gap:6px;display:flex}.hiDrJa_tag{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-caption);border-radius:4px;padding:1px 5px;font-size:11px;font-weight:500}.hiDrJa_check{color:var(--dsw-alias-state-brand);align-items:center;font-size:12px;display:inline-flex}.hiDrJa_error{color:var(--dsw-alias-label-primary);max-width:240px;font-size:12px}";
@@ -1270,30 +1465,9 @@ window.__ModuleLoader__.load({
1270
1465
  //#endregion
1271
1466
  //#region src/client/QoderContextSelect.tsx
1272
1467
  function QoderContextSelect(props) {
1273
- const { directory, operations, t, sessionId, useProjection } = props;
1274
- const [open, setOpen] = (0, react.useState)(false);
1275
- const [manualTiers, setManualTiers] = (0, react.useState)({});
1276
- const [pending, setPending] = (0, react.useState)(false);
1277
- const [error, setError] = (0, react.useState)(null);
1278
- const saving = (0, react.useRef)(false);
1279
- const rootRef = (0, react.useRef)(null);
1468
+ const { directory, operations, sessionId, useProjection } = props;
1280
1469
  const directoryState = (0, react.useSyncExternalStore)((fn) => directory ? directory.subscribe(fn) : () => () => {}, () => directory ? directory.getSnapshot() : null);
1281
1470
  const modelSnapshot = (0, react.useSyncExternalStore)((fn) => operations ? operations.subscribeModels(fn) : () => () => {}, () => operations ? operations.getModelSnapshot() : null);
1282
- (0, react.useEffect)(() => {
1283
- if (!open) return;
1284
- const handleClickOutside = (e) => {
1285
- if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
1286
- };
1287
- const handleKeyDown = (e) => {
1288
- if (e.key === "Escape") setOpen(false);
1289
- };
1290
- document.addEventListener("mousedown", handleClickOutside);
1291
- document.addEventListener("keydown", handleKeyDown);
1292
- return () => {
1293
- document.removeEventListener("mousedown", handleClickOutside);
1294
- document.removeEventListener("keydown", handleKeyDown);
1295
- };
1296
- }, [open]);
1297
1471
  const modelProj = useProjection ? useProjection("modelSelection") : void 0;
1298
1472
  const pressure = useProjection ? useProjection("contextPressure") : void 0;
1299
1473
  if (!operations) return null;
@@ -1309,34 +1483,71 @@ window.__ModuleLoader__.load({
1309
1483
  const currentProviderId = current.provider ?? (current.model.includes("/") ? current.model.split("/")[0] : void 0);
1310
1484
  const activeModel = models.find((m) => m.id === currentModelId || m.id === current.model);
1311
1485
  if (!isQoderProvider(currentProviderId) || !activeModel) return null;
1312
- const tiers = contextTiersOf(activeModel);
1313
- if (tiers.length <= 1) return null;
1314
- const sessionKey = sessionId ? JSON.stringify([
1486
+ if (contextTiersOf(activeModel).length <= 1) return null;
1487
+ if (!sessionId) return null;
1488
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionContextSelect, {
1489
+ ...props,
1490
+ sessionId,
1491
+ operations,
1492
+ region,
1493
+ activeModel,
1494
+ historicalWindow: pressure?.contextWindow,
1495
+ lastUsed: modelProj?.lastUsed
1496
+ }, JSON.stringify([
1315
1497
  region,
1316
1498
  sessionId,
1317
1499
  activeModel.id
1318
- ]) : void 0;
1319
- const historicalWindow = operations.canRestoreContextHistory?.() === false ? void 0 : historicalContextWindow(current, modelProj?.lastUsed, pressure?.contextWindow);
1320
- const currentTier = resolveContextTier(activeModel, sessionKey ? manualTiers[sessionKey] : void 0, historicalWindow);
1321
- if (!currentTier) return null;
1322
- const selectedTierKey = currentTier.key;
1323
- const selectTier = async (tierKey) => {
1500
+ ]));
1501
+ }
1502
+ function SessionContextSelect({ operations, sessionId, region, activeModel, t, historicalWindow, lastUsed }) {
1503
+ const [open, setOpen] = (0, react.useState)(false);
1504
+ const rootRef = (0, react.useRef)(null);
1505
+ const state = (0, react.useMemo)(() => new SessionTierState({
1506
+ region,
1507
+ sessionId,
1508
+ modelId: activeModel.id
1509
+ }, operations), [
1510
+ region,
1511
+ sessionId,
1512
+ activeModel.id,
1513
+ operations
1514
+ ]);
1515
+ const snapshot = (0, react.useSyncExternalStore)(state.subscribe, state.getSnapshot);
1516
+ (0, react.useLayoutEffect)(() => {
1517
+ state.activate();
1518
+ return () => state.dispose();
1519
+ }, [state]);
1520
+ (0, react.useEffect)(() => {
1521
+ state.refresh();
1522
+ }, [
1523
+ state,
1524
+ JSON.stringify(activeModel),
1525
+ historicalWindow,
1526
+ lastUsed?.provider,
1527
+ lastUsed?.model
1528
+ ]);
1529
+ (0, react.useEffect)(() => {
1530
+ if (!open) return;
1531
+ const handleClickOutside = (e) => {
1532
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
1533
+ };
1534
+ const handleKeyDown = (e) => {
1535
+ if (e.key === "Escape") setOpen(false);
1536
+ };
1537
+ document.addEventListener("mousedown", handleClickOutside);
1538
+ document.addEventListener("keydown", handleKeyDown);
1539
+ return () => {
1540
+ document.removeEventListener("mousedown", handleClickOutside);
1541
+ document.removeEventListener("keydown", handleKeyDown);
1542
+ };
1543
+ }, [open]);
1544
+ const tiers = contextTiersOf(activeModel);
1545
+ const currentTier = snapshot.selection;
1546
+ const selectedTierKey = currentTier?.tierKey;
1547
+ const pending = snapshot.loading || snapshot.saving;
1548
+ const selectTier = (tierKey) => {
1324
1549
  setOpen(false);
1325
- if (!sessionId || !sessionKey || saving.current) return;
1326
- if (tierKey === selectedTierKey && error?.key !== sessionKey) return;
1327
- saving.current = true;
1328
- setPending(true);
1329
- setError(null);
1330
- try {
1331
- if (await saveContextSelection(operations, sessionId, region, activeModel.id, tierKey) === "saved") setManualTiers((prev) => ({
1332
- ...prev,
1333
- [sessionKey]: tierKey
1334
- }));
1335
- else setError({ key: sessionKey });
1336
- } finally {
1337
- saving.current = false;
1338
- setPending(false);
1339
- }
1550
+ state.select(tierKey);
1340
1551
  };
1341
1552
  const defaultSuffix = t ? t("contextTierDefaultSuffix").replace(/[()()]/g, "") : "默认";
1342
1553
  const selectTitle = t ? t("contextSelectTitle") : "选择上下文大小";
@@ -1348,10 +1559,10 @@ window.__ModuleLoader__.load({
1348
1559
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1349
1560
  type: "button",
1350
1561
  className: `${QoderContextSelect_module_css_default.trigger} ${open ? QoderContextSelect_module_css_default.triggerActive : ""}`,
1351
- disabled: pending || !sessionId,
1562
+ disabled: pending || !currentTier,
1352
1563
  "aria-expanded": open,
1353
1564
  "aria-label": selectTitle,
1354
- title: `${selectTitle}: ${formatContextTokens(currentTier.tokenCount)}`,
1565
+ title: `${selectTitle}: ${currentTier ? formatContextTokens(currentTier.tokenCount) : "…"}`,
1355
1566
  onClick: () => setOpen((prev) => !prev),
1356
1567
  children: [
1357
1568
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1366,7 +1577,7 @@ window.__ModuleLoader__.load({
1366
1577
  }),
1367
1578
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1368
1579
  className: QoderContextSelect_module_css_default.label,
1369
- children: formatContextTokens(currentTier.tokenCount)
1580
+ children: currentTier ? formatContextTokens(currentTier.tokenCount) : "…"
1370
1581
  }),
1371
1582
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1372
1583
  className: `${QoderContextSelect_module_css_default.chevron} ${open ? QoderContextSelect_module_css_default.chevronOpen : ""}`,
@@ -1384,10 +1595,21 @@ window.__ModuleLoader__.load({
1384
1595
  })
1385
1596
  ]
1386
1597
  }),
1387
- error && error.key === sessionKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1598
+ snapshot.error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1388
1599
  role: "alert",
1389
1600
  className: QoderContextSelect_module_css_default.error,
1390
- children: t ? t("contextSelectFailed") : "contextSelectFailed"
1601
+ children: [
1602
+ t ? t("contextSelectFailed") : "contextSelectFailed",
1603
+ " ",
1604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1605
+ type: "button",
1606
+ disabled: pending,
1607
+ onClick: () => {
1608
+ state.refresh();
1609
+ },
1610
+ children: t ? t("refresh") : "refresh"
1611
+ })
1612
+ ]
1391
1613
  }),
1392
1614
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1393
1615
  className: QoderContextSelect_module_css_default.menu,
@@ -1401,6 +1623,7 @@ window.__ModuleLoader__.load({
1401
1623
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1402
1624
  type: "button",
1403
1625
  role: "menuitemradio",
1626
+ disabled: pending,
1404
1627
  "aria-checked": isSelected,
1405
1628
  className: `${QoderContextSelect_module_css_default.option} ${isSelected ? QoderContextSelect_module_css_default.optionSelected : ""}`,
1406
1629
  onClick: () => {
@@ -1456,6 +1679,97 @@ window.__ModuleLoader__.load({
1456
1679
  return typeof value === "string" && qoderRpcErrorCodes.includes(value);
1457
1680
  }
1458
1681
  //#endregion
1682
+ //#region src/client/session-tier-events.ts
1683
+ /** One authenticated host change stream shared by the mounted composer controls. */
1684
+ function createSessionTierEvents(send = (...args) => globalThis.fetch(...args)) {
1685
+ const listeners = /* @__PURE__ */ new Set();
1686
+ let active;
1687
+ let disposed = false;
1688
+ const notify = (scope) => {
1689
+ for (const entry of listeners) if (!scope || entry.scope.region === scope.region && entry.scope.sessionId === scope.sessionId && entry.scope.modelId === scope.modelId) try {
1690
+ entry.listener();
1691
+ } catch {}
1692
+ };
1693
+ const start = () => {
1694
+ if (active || disposed || !listeners.size) return;
1695
+ const controller = new AbortController();
1696
+ active = controller;
1697
+ follow(controller).finally(() => {
1698
+ if (active === controller) active = void 0;
1699
+ });
1700
+ };
1701
+ async function follow(controller) {
1702
+ let delay = 1e3;
1703
+ while (!controller.signal.aborted) {
1704
+ try {
1705
+ const response = await send(qoderRpcPath("sessionTierEvents"), {
1706
+ method: "POST",
1707
+ credentials: "include",
1708
+ signal: controller.signal,
1709
+ headers: { "content-type": "application/json" },
1710
+ body: "{}"
1711
+ });
1712
+ if (!response.ok || !response.body) throw new Error("Context tier stream unavailable");
1713
+ const reader = response.body.getReader();
1714
+ const decoder = new TextDecoder();
1715
+ let buffered = "";
1716
+ try {
1717
+ while (!controller.signal.aborted) {
1718
+ const { value, done } = await reader.read();
1719
+ if (done) break;
1720
+ buffered += decoder.decode(value, { stream: true });
1721
+ let newline;
1722
+ while ((newline = buffered.indexOf("\n")) >= 0) {
1723
+ const item = JSON.parse(buffered.slice(0, newline));
1724
+ buffered = buffered.slice(newline + 1);
1725
+ delay = 1e3;
1726
+ if (item.ready === true) notify();
1727
+ else if ((item.region === "global" || item.region === "china") && typeof item.sessionId === "string" && typeof item.modelId === "string") notify(item);
1728
+ }
1729
+ }
1730
+ } finally {
1731
+ await reader.cancel().catch(() => {});
1732
+ reader.releaseLock();
1733
+ }
1734
+ } catch {}
1735
+ if (controller.signal.aborted) break;
1736
+ await new Promise((resolve) => {
1737
+ const finish = () => {
1738
+ clearTimeout(timer);
1739
+ controller.signal.removeEventListener("abort", finish);
1740
+ resolve();
1741
+ };
1742
+ const timer = setTimeout(finish, delay);
1743
+ controller.signal.addEventListener("abort", finish, { once: true });
1744
+ });
1745
+ delay = Math.min(delay * 2, 3e4);
1746
+ }
1747
+ }
1748
+ return {
1749
+ subscribe(scope, listener) {
1750
+ const entry = {
1751
+ scope,
1752
+ listener
1753
+ };
1754
+ listeners.add(entry);
1755
+ start();
1756
+ return () => {
1757
+ listeners.delete(entry);
1758
+ if (!listeners.size) {
1759
+ active?.abort();
1760
+ active = void 0;
1761
+ }
1762
+ };
1763
+ },
1764
+ dispose() {
1765
+ disposed = true;
1766
+ listeners.clear();
1767
+ active?.abort();
1768
+ active = void 0;
1769
+ }
1770
+ };
1771
+ }
1772
+ //#endregion
1459
1773
  //#region src/client/rpc-client.ts
1460
1774
  /**
1461
1775
  * Browser caller for the Qoder settings RPC.
@@ -1551,21 +1865,9 @@ window.__ModuleLoader__.load({
1551
1865
  }), "provider-qoder: credential copy");
1552
1866
  const credentials = ctx.remote.credentials;
1553
1867
  const rpc = createQoderRpcCaller();
1554
- let historyRegion;
1555
- let historyRegionChanged = false;
1556
- const readModelSnapshot = () => {
1557
- const snapshot = modelScope.getSnapshot();
1558
- if (snapshot.value) {
1559
- const region = snapshot.value.region ?? "global";
1560
- if (historyRegion !== void 0 && historyRegion !== region) historyRegionChanged = true;
1561
- historyRegion = region;
1562
- }
1563
- return snapshot;
1564
- };
1565
- readModelSnapshot();
1566
- ctx.effect(() => modelScope.subscribe(() => {
1567
- readModelSnapshot();
1568
- }), "provider-qoder: context history region");
1868
+ const tierEvents = createSessionTierEvents();
1869
+ ctx.effect(() => () => tierEvents.dispose(), "provider-qoder: session context tier events");
1870
+ const readModelSnapshot = () => modelScope.getSnapshot();
1569
1871
  const operations = {
1570
1872
  describe: async () => {
1571
1873
  try {
@@ -1591,16 +1893,15 @@ window.__ModuleLoader__.load({
1591
1893
  },
1592
1894
  getAccount: async (force) => await rpc.call("account", { force }),
1593
1895
  getModelSnapshot: readModelSnapshot,
1594
- canRestoreContextHistory: () => !historyRegionChanged,
1595
1896
  subscribeModels: (listener) => modelScope.subscribe(listener),
1596
1897
  storeModels: async (region, models) => {
1597
1898
  try {
1598
- const current = modelScope.getSnapshot().value;
1599
- const clonedModels = models.map(cloneCatalogModel);
1600
- return await modelScope.set("modelsByRegion", {
1601
- ...current?.modelsByRegion,
1602
- [region]: clonedModels
1603
- }) !== false;
1899
+ const clonedModels = JSON.parse(JSON.stringify(models));
1900
+ return await modelScope.mutate([{
1901
+ op: "set",
1902
+ path: ["modelsByRegion", region],
1903
+ value: clonedModels
1904
+ }]) !== false;
1604
1905
  } catch {
1605
1906
  return false;
1606
1907
  }
@@ -1622,16 +1923,22 @@ window.__ModuleLoader__.load({
1622
1923
  discoverModels: async () => await rpc.call("models", {}),
1623
1924
  setSessionTier: async (sessionId, modelId, tierKey, region) => {
1624
1925
  try {
1625
- return (await rpc.call("sessionTier", {
1926
+ const res = await rpc.call("sessionTier", {
1626
1927
  sessionId,
1627
1928
  modelId,
1628
1929
  tierKey,
1629
1930
  region
1630
- })).ok;
1931
+ });
1932
+ return res.ok ? res.value : void 0;
1631
1933
  } catch {
1632
- return false;
1934
+ return;
1633
1935
  }
1634
1936
  },
1937
+ readSessionTier: async (scope) => {
1938
+ const result = await rpc.call("readSessionTier", scope);
1939
+ return result.ok ? result.value : void 0;
1940
+ },
1941
+ subscribeSessionTiers: (scope, listener) => tierEvents.subscribe(scope, listener),
1635
1942
  subscribe: (listener) => ctx.remote.$on("credentials/reference-updated", (ref) => {
1636
1943
  if (ref === "QODER_MANAGED_CREDENTIAL") listener();
1637
1944
  })