dsh-provider-qoder 0.4.1 → 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
@@ -69,6 +69,29 @@ window.__ModuleLoader__.load({
69
69
  tokenCount
70
70
  };
71
71
  }
72
+ /** Clone a model catalog entry, detaching any contextOptions object. */
73
+ function cloneCatalogModel(model) {
74
+ return {
75
+ ...model,
76
+ ...model.contextOptions !== void 0 ? { contextOptions: { ...model.contextOptions } } : {}
77
+ };
78
+ }
79
+ /** Carry subscriber context choices only while the refreshed catalog supports them. */
80
+ function mergeAdvertisedContext(remembered, advertised, updated) {
81
+ const merged = { ...updated };
82
+ delete merged.contextOptions;
83
+ delete merged.contextTier;
84
+ if (advertised.contextOptions !== void 0) merged.contextOptions = { ...advertised.contextOptions };
85
+ const tier = selectedContextTier({
86
+ contextTier: remembered.contextTier,
87
+ contextOptions: merged.contextOptions
88
+ });
89
+ if (tier !== void 0) {
90
+ merged.contextTier = tier.key;
91
+ merged.contextWindow = tier.tokenCount;
92
+ } else if (advertised.contextWindow !== void 0) merged.contextWindow = remembered.contextTier !== void 0 ? advertised.contextWindow : Math.min(remembered.contextWindow ?? advertised.contextWindow, advertised.contextWindow);
93
+ return merged;
94
+ }
72
95
  const defaultMaxTokens = 32768;
73
96
  const defaultModels = [
74
97
  {
@@ -120,6 +143,31 @@ window.__ModuleLoader__.load({
120
143
  }
121
144
  ];
122
145
  //#endregion
146
+ //#region src/client/credential-operations.ts
147
+ function regionOf(value) {
148
+ if (typeof value !== "object" || value === null) return "global";
149
+ return value.region === "china" ? "china" : "global";
150
+ }
151
+ function modelsOf(section, region) {
152
+ const scoped = section?.modelsByRegion?.[region];
153
+ return Array.isArray(scoped) ? scoped : defaultModels.map((model) => ({ ...model }));
154
+ }
155
+ function reconcileQoderModels(known, discovered) {
156
+ const availableIds = new Set(discovered.map((model) => model.id));
157
+ const unavailable = known.filter((model) => !availableIds.has(model.id)).map(cloneCatalogModel);
158
+ const previous = new Map(known.map((model) => [model.id, model]));
159
+ const reconciled = discovered.map((model) => {
160
+ const remembered = previous.get(model.id);
161
+ if (remembered === void 0) return cloneCatalogModel(model);
162
+ return mergeAdvertisedContext(remembered, model, model);
163
+ });
164
+ return {
165
+ catalog: [...reconciled, ...unavailable],
166
+ selected: reconciled,
167
+ unavailableIds: new Set(unavailable.map((model) => model.id))
168
+ };
169
+ }
170
+ //#endregion
123
171
  //#region src/client/locales.ts
124
172
  const zh = {
125
173
  nav: "Qoder",
@@ -165,7 +213,6 @@ window.__ModuleLoader__.load({
165
213
  contextTierDefaultSuffix: "(默认)",
166
214
  contextTierHint: "选择该模型使用的上下文档位;更大的档位会同时提升 DSH 的上下文预算并改变 Qoder 请求使用的档位。",
167
215
  contextSelectFailed: "上下文切换失败,请重试。",
168
- contextDefaultFailed: "当前会话已切换,但新会话默认值保存失败。",
169
216
  contextSelectTitle: "选择上下文大小",
170
217
  contextSelectMenuHeader: "上下文容量",
171
218
  accountTitle: "Qoder",
@@ -176,6 +223,7 @@ window.__ModuleLoader__.load({
176
223
  refreshing: "正在刷新…",
177
224
  userQuotaTitle: "个人额度",
178
225
  orgResourceTitle: "组织资源包",
226
+ addOnQuotaTitle: "增值额度",
179
227
  dedicatedResourceTitle: "专属资源包",
180
228
  dedicatedResourceExpiresAt: "有效期至:{value}",
181
229
  quotaRemaining: "剩余 {value}%",
@@ -237,7 +285,6 @@ window.__ModuleLoader__.load({
237
285
  contextTierDefaultSuffix: " (default)",
238
286
  contextTierHint: "Select the context tier for this model. A larger tier raises the DSH context budget and changes the tier the Qoder request uses.",
239
287
  contextSelectFailed: "Could not change the context window. Please retry.",
240
- contextDefaultFailed: "Session updated, but the default for new sessions could not be saved.",
241
288
  contextSelectTitle: "Select context window size",
242
289
  contextSelectMenuHeader: "Context Capacity",
243
290
  accountTitle: "Qoder",
@@ -248,6 +295,7 @@ window.__ModuleLoader__.load({
248
295
  refreshing: "Refreshing…",
249
296
  userQuotaTitle: "Personal Quota",
250
297
  orgResourceTitle: "Org Resource Package",
298
+ addOnQuotaTitle: "Add-on Credits",
251
299
  dedicatedResourceTitle: "Dedicated Resource Package",
252
300
  dedicatedResourceExpiresAt: "Expires at: {value}",
253
301
  quotaRemaining: "{value}% remaining",
@@ -316,48 +364,145 @@ window.__ModuleLoader__.load({
316
364
  }
317
365
  }
318
366
  //#endregion
319
- //#region src/client/credential-operations.ts
320
- function reconcileQoderModels(known, discovered) {
321
- const availableIds = new Set(discovered.map((model) => model.id));
322
- const unavailable = known.filter((model) => !availableIds.has(model.id)).map((model) => ({
323
- ...model,
324
- ...model.contextOptions !== void 0 ? { contextOptions: { ...model.contextOptions } } : {}
325
- }));
326
- const previous = new Map(known.map((model) => [model.id, model]));
327
- const reconciled = discovered.map((model) => {
328
- const remembered = previous.get(model.id);
329
- if (remembered === void 0) return {
330
- ...model,
331
- ...model.contextOptions !== void 0 ? { contextOptions: { ...model.contextOptions } } : {}
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()
332
384
  };
333
- const carried = selectedContextTier({
334
- contextTier: remembered.contextTier,
335
- contextOptions: model.contextOptions ?? remembered.contextOptions
336
- });
337
- const options = model.contextOptions ?? remembered.contextOptions;
338
- const clonedOptions = options !== void 0 ? { contextOptions: { ...options } } : {};
339
- if (carried !== void 0) return {
340
- ...model,
341
- ...clonedOptions,
342
- contextTier: carried.key,
343
- contextWindow: carried.tokenCount
344
- };
345
- const budget = remembered.contextWindow;
346
- return budget === void 0 || model.contextWindow === void 0 ? {
347
- ...model,
348
- ...clonedOptions
349
- } : {
350
- ...model,
351
- ...clonedOptions,
352
- contextWindow: Math.min(budget, model.contextWindow)
385
+ }
386
+ getSnapshot = () => this.snapshot;
387
+ subscribe = (listener) => {
388
+ this.listeners.add(listener);
389
+ return () => {
390
+ this.listeners.delete(listener);
353
391
  };
354
- });
355
- return {
356
- catalog: [...reconciled, ...unavailable],
357
- selected: reconciled,
358
- unavailableIds: new Set(unavailable.map((model) => model.id))
359
392
  };
360
- }
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
+ };
361
506
  //#endregion
362
507
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderCredentialCard.module.css.mjs
363
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}";
@@ -446,57 +591,26 @@ window.__ModuleLoader__.load({
446
591
  };
447
592
  //#endregion
448
593
  //#region src/client/QoderModelCatalog.tsx
449
- function validateModelCatalog(models) {
450
- return models.length === 0 ? "modelsRequired" : void 0;
451
- }
594
+ /** The key resets directory state whenever the active Qoder service region changes. */
452
595
  function QoderModelCatalog(props) {
453
- const { operations, models, disabled, fetchDisabled, hideTitle, onChange, t } = props;
454
- const [fetching, setFetching] = (0, react.useState)(false);
455
- const [failure, setFailure] = (0, react.useState)();
456
- const [catalog, setCatalog] = (0, react.useState)();
457
- const [unavailableIds, setUnavailableIds] = (0, react.useState)(/* @__PURE__ */ new Set());
458
- const displayedModels = catalog ?? models;
459
- const selectedIds = (0, react.useMemo)(() => new Set(models.map((model) => model.id)), [models]);
460
- const fetchModels = async () => {
461
- setFetching(true);
462
- setFailure(void 0);
463
- const result = await operations.discoverModels();
464
- setFetching(false);
465
- if (!result.ok) {
466
- setFailure(result.error.message || t("modelsFetchFailed"));
467
- return;
468
- }
469
- const reconciled = reconcileQoderModels(catalog ?? models, result.value);
470
- setCatalog(reconciled.catalog);
471
- setUnavailableIds(reconciled.unavailableIds);
472
- onChange(reconciled.selected);
473
- };
474
- const toggleModel = (id, enabled) => {
475
- if (unavailableIds.has(id)) return;
476
- const source = catalog ?? models;
477
- if (catalog === void 0) setCatalog(source);
478
- const nextSelected = new Set(selectedIds);
479
- if (enabled) nextSelected.add(id);
480
- else nextSelected.delete(id);
481
- onChange(source.filter((model) => nextSelected.has(model.id) && !unavailableIds.has(model.id)));
482
- };
483
- const changeContextTier = (id, tierKey) => {
484
- const source = catalog ?? models;
485
- if (catalog === void 0) setCatalog(source);
486
- const apply = (model) => {
487
- if (model.id !== id) return model;
488
- const tokenCount = model.contextOptions?.[tierKey]?.tokenCount;
489
- if (typeof tokenCount !== "number" || !Number.isFinite(tokenCount) || tokenCount <= 0) return model;
490
- return {
491
- ...model,
492
- contextTier: tierKey,
493
- contextWindow: tokenCount
494
- };
495
- };
496
- const nextCatalog = source.map(apply);
497
- setCatalog(nextCatalog);
498
- onChange(models.map(apply));
499
- };
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);
500
614
  const renderRate = (model) => {
501
615
  if (model.isFree || model.priceFactor === 0) return t("modelRateFree");
502
616
  if (model.priceFactor === void 0) return t("modelRateUnknown");
@@ -519,13 +633,13 @@ window.__ModuleLoader__.load({
519
633
  children: t("modelsTitle")
520
634
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
521
635
  className: QoderCredentialCard_module_css_default.modelCatalogMeta,
522
- children: t("modelsEnabled", { count: models.length })
636
+ children: t("modelsEnabled", { count: snapshot.models.length })
523
637
  })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
524
638
  type: "button",
525
639
  className: QoderCredentialCard_module_css_default.linkButton,
526
- disabled: disabled || fetching || Boolean(fetchDisabled),
640
+ disabled: disabled || busy || Boolean(fetchDisabled),
527
641
  onClick: () => {
528
- fetchModels();
642
+ if (!disabled && !fetchDisabled) editor.discover();
529
643
  },
530
644
  children: fetching ? t("modelsFetching") : t("modelsFetch")
531
645
  })]
@@ -550,9 +664,9 @@ window.__ModuleLoader__.load({
550
664
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
551
665
  type: "checkbox",
552
666
  checked: !unavailable && selectedIds.has(model.id),
553
- disabled: disabled || unavailable,
667
+ disabled: disabled || busy || unavailable,
554
668
  onChange: (event) => {
555
- toggleModel(model.id, event.currentTarget.checked);
669
+ if (!disabled) editor.toggle(model.id, event.currentTarget.checked);
556
670
  }
557
671
  }),
558
672
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -577,9 +691,9 @@ window.__ModuleLoader__.load({
577
691
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
578
692
  className: QoderCredentialCard_module_css_default.modelTierSelect,
579
693
  value: selectedTier ?? "",
580
- disabled: disabled || unavailable || !selectedIds.has(model.id),
694
+ disabled: disabled || busy || unavailable || !selectedIds.has(model.id),
581
695
  onChange: (event) => {
582
- changeContextTier(model.id, event.currentTarget.value);
696
+ if (!disabled) editor.selectTier(model.id, event.currentTarget.value);
583
697
  },
584
698
  children: tiers.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
585
699
  value: tier.key,
@@ -610,32 +724,33 @@ window.__ModuleLoader__.load({
610
724
  minute: "2-digit"
611
725
  });
612
726
  }
613
- function modelsOf$1(value, selectedRegion) {
614
- if (typeof value !== "object" || value === null) return defaultModels.map((model) => ({ ...model }));
615
- const section = value;
616
- const region = selectedRegion ?? (section.region === "china" ? "china" : "global");
617
- const scoped = section.modelsByRegion?.[region];
618
- if (Array.isArray(scoped)) return scoped;
619
- return defaultModels.map((model) => ({ ...model }));
620
- }
621
- function regionOf$1(value) {
622
- if (typeof value !== "object" || value === null) return "global";
623
- return value.region === "china" ? "china" : "global";
624
- }
625
727
  /**
626
728
  * Settings card for the Qoder subscription: credential state, subscriber
627
- * 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.
628
731
  */
629
- 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 }) {
630
740
  const [credentialState, setCredentialState] = (0, react.useState)({ status: "loading" });
631
741
  const [accountState, setAccountState] = (0, react.useState)({ status: "idle" });
632
742
  const [refreshing, setRefreshing] = (0, react.useState)(false);
633
743
  const [credentialRevision, setCredentialRevision] = (0, react.useState)(0);
634
- const [modelSaveError, setModelSaveError] = (0, react.useState)();
635
744
  const latestAccountRequest = (0, react.useRef)(0);
636
- 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
+ }, []);
637
750
  const loadCredential = (0, react.useCallback)(async () => {
751
+ const requestId = ++latestCredentialRequest.current;
638
752
  const info = await operations.describe();
753
+ if (requestId !== latestCredentialRequest.current) return;
639
754
  setCredentialState(info === void 0 ? { status: "failed" } : {
640
755
  status: "ready",
641
756
  info
@@ -790,13 +905,14 @@ window.__ModuleLoader__.load({
790
905
  const { profile, usage, plan } = accountState.account;
791
906
  const userQuota = usage?.userQuota;
792
907
  const orgPackage = usage?.orgResourcePackage;
908
+ const addOnQuota = usage?.addOnQuota;
793
909
  const dedicatedPackages = usage?.dedicatedResourcePackages ?? [];
794
910
  const locale = activeLocale();
795
911
  const avatarInitial = (profile.name || profile.email || "Q").charAt(0).toUpperCase();
796
912
  const resetDate = formatResetDate(usage?.expiresAt);
797
913
  const planExpiry = plan?.endDate ? formatResetDate(plan.endDate) : void 0;
798
914
  const isSuspended = plan?.organization?.isSuspended === true;
799
- 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);
800
916
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
801
917
  className: QoderCredentialCard_module_css_default.accountSection,
802
918
  children: [
@@ -853,6 +969,7 @@ window.__ModuleLoader__.load({
853
969
  }) : null,
854
970
  renderQuota("userQuota", t("userQuotaTitle"), userQuota, { resetDate }),
855
971
  renderQuota("orgResource", t("orgResourceTitle"), orgPackage, { resetDate }),
972
+ renderQuota("addOnQuota", t("addOnQuotaTitle"), addOnQuota),
856
973
  dedicatedPackages.map((pkg, index) => renderQuota(`dedicatedResource-${pkg.id ?? index}`, resolveLocalizedText(pkg.title, locale) ?? t("dedicatedResourceTitle"), pkg, {
857
974
  expiresAt: formatResetDate(pkg.expiresAt),
858
975
  description: resolveLocalizedText(pkg.description, locale)
@@ -928,19 +1045,9 @@ window.__ModuleLoader__.load({
928
1045
  ]
929
1046
  });
930
1047
  };
931
- const currentRegion = regionOf$1(modelSnapshot.value);
932
- const currentModels = modelsOf$1(modelSnapshot.value, currentRegion);
1048
+ const currentRegion = regionOf(modelSnapshot.value);
1049
+ const currentModels = modelsOf(modelSnapshot.value, currentRegion);
933
1050
  const canModifyModels = modelSnapshot.status === "ready" && modelSnapshot.writable;
934
- const handleModelsChange = async (models) => {
935
- if (!canModifyModels) return;
936
- setModelSaveError(void 0);
937
- const failure = validateModelCatalog(models);
938
- if (failure) {
939
- setModelSaveError(t(failure));
940
- return;
941
- }
942
- if (!await operations.storeModels(currentRegion, models)) setModelSaveError(t("saveFailed"));
943
- };
944
1051
  const renderModelCatalogSection = () => {
945
1052
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
946
1053
  className: QoderCredentialCard_module_css_default.customized,
@@ -950,29 +1057,23 @@ window.__ModuleLoader__.load({
950
1057
  className: QoderCredentialCard_module_css_default.modelSummaryBadge,
951
1058
  children: t("modelsEnabled", { count: currentModels.length })
952
1059
  })]
953
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1060
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
954
1061
  className: QoderCredentialCard_module_css_default.customizedBody,
955
- children: [modelSnapshot.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1062
+ children: modelSnapshot.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
956
1063
  className: QoderCredentialCard_module_css_default.hint,
957
1064
  children: t("modelsLoading")
958
1065
  }) : modelSnapshot.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
959
1066
  className: QoderCredentialCard_module_css_default.error,
960
1067
  children: t("modelsUnavailable")
961
1068
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QoderModelCatalog, {
1069
+ region: currentRegion,
962
1070
  operations,
963
1071
  models: currentModels,
964
1072
  disabled: !canModifyModels,
965
1073
  fetchDisabled: !configured,
966
1074
  hideTitle: true,
967
- t,
968
- onChange: (models) => {
969
- handleModelsChange(models);
970
- }
971
- }), modelSaveError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
972
- className: QoderCredentialCard_module_css_default.error,
973
- role: "alert",
974
- children: modelSaveError
975
- }) : null]
1075
+ t
1076
+ })
976
1077
  })]
977
1078
  });
978
1079
  };
@@ -998,10 +1099,6 @@ window.__ModuleLoader__.load({
998
1099
  }
999
1100
  //#endregion
1000
1101
  //#region src/client/QoderCredentialCard.tsx
1001
- function regionOf(value) {
1002
- if (typeof value !== "object" || value === null) return "global";
1003
- return value.region === "china" ? "china" : "global";
1004
- }
1005
1102
  function QoderCredentialCard({ operations, t }) {
1006
1103
  const [state, setState] = (0, react.useState)({ status: "loading" });
1007
1104
  const [editing, setEditing] = (0, react.useState)(false);
@@ -1240,39 +1337,105 @@ window.__ModuleLoader__.load({
1240
1337
  "qoder-subscription"
1241
1338
  ].includes(provider ?? "");
1242
1339
  }
1243
- function modelsOf(section, region, _legacyRegion) {
1244
- const scoped = section?.modelsByRegion?.[region];
1245
- if (scoped !== void 0) return scoped;
1246
- return defaultModels.map((model) => ({ ...model }));
1247
- }
1248
- function historicalContextWindow(current, lastUsed, contextWindow) {
1249
- return current.provider === lastUsed?.provider && current.model === lastUsed?.model ? contextWindow : void 0;
1250
- }
1251
- /** A failed session write must not change defaults; a failed default write is a partial success. */
1252
- async function saveContextSelection(operations, sessionId, region, modelId, tierKey, legacyRegion) {
1253
- try {
1254
- if (!await operations.setSessionTier?.(sessionId, modelId, tierKey, region)) return "session-failed";
1255
- } catch {
1256
- 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;
1257
1359
  }
1258
- try {
1259
- const models = modelsOf(operations.getModelSnapshot().value, region, legacyRegion);
1260
- const model = models.find((candidate) => candidate.id === modelId);
1261
- const tier = model && contextTiersOf(model).find((candidate) => candidate.key === tierKey);
1262
- if (!tier) return "default-failed";
1263
- const updated = models.map((candidate) => candidate.id === modelId ? {
1264
- ...candidate,
1265
- contextTier: tierKey,
1266
- contextWindow: tier.tokenCount
1267
- } : candidate);
1268
- return await operations.storeModels(region, updated) ? "saved" : "default-failed";
1269
- } catch {
1270
- return "default-failed";
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();
1271
1373
  }
1272
- }
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
+ };
1273
1436
  //#endregion
1274
1437
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderContextSelect.module.css.mjs
1275
- 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-specific-menu);border:1px solid var(--dsw-alias-border-l1);min-width:180px;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}";
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}";
1276
1439
  const tagId = "dsh-provider-qoder/QoderContextSelect.module.css";
1277
1440
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1278
1441
  const tag = document.createElement("style");
@@ -1302,31 +1465,9 @@ window.__ModuleLoader__.load({
1302
1465
  //#endregion
1303
1466
  //#region src/client/QoderContextSelect.tsx
1304
1467
  function QoderContextSelect(props) {
1305
- const { directory, operations, t, sessionId, useProjection } = props;
1306
- const [open, setOpen] = (0, react.useState)(false);
1307
- const [manualTiers, setManualTiers] = (0, react.useState)({});
1308
- const [pending, setPending] = (0, react.useState)(false);
1309
- const [error, setError] = (0, react.useState)(null);
1310
- const saving = (0, react.useRef)(false);
1311
- const legacyRegion = (0, react.useRef)(void 0);
1312
- const rootRef = (0, react.useRef)(null);
1468
+ const { directory, operations, sessionId, useProjection } = props;
1313
1469
  const directoryState = (0, react.useSyncExternalStore)((fn) => directory ? directory.subscribe(fn) : () => () => {}, () => directory ? directory.getSnapshot() : null);
1314
1470
  const modelSnapshot = (0, react.useSyncExternalStore)((fn) => operations ? operations.subscribeModels(fn) : () => () => {}, () => operations ? operations.getModelSnapshot() : null);
1315
- (0, react.useEffect)(() => {
1316
- if (!open) return;
1317
- const handleClickOutside = (e) => {
1318
- if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
1319
- };
1320
- const handleKeyDown = (e) => {
1321
- if (e.key === "Escape") setOpen(false);
1322
- };
1323
- document.addEventListener("mousedown", handleClickOutside);
1324
- document.addEventListener("keydown", handleKeyDown);
1325
- return () => {
1326
- document.removeEventListener("mousedown", handleClickOutside);
1327
- document.removeEventListener("keydown", handleKeyDown);
1328
- };
1329
- }, [open]);
1330
1471
  const modelProj = useProjection ? useProjection("modelSelection") : void 0;
1331
1472
  const pressure = useProjection ? useProjection("contextPressure") : void 0;
1332
1473
  if (!operations) return null;
@@ -1336,45 +1477,77 @@ window.__ModuleLoader__.load({
1336
1477
  model: projectedCurrent.model
1337
1478
  } : null);
1338
1479
  if (!current) return null;
1339
- const region = modelSnapshot?.value?.region === "china" ? "china" : "global";
1340
- if (legacyRegion.current === void 0 && modelSnapshot?.value) legacyRegion.current = region;
1341
- const models = modelsOf(modelSnapshot?.value, region, legacyRegion.current ?? region);
1480
+ const region = regionOf(modelSnapshot?.value);
1481
+ const models = modelsOf(modelSnapshot?.value, region);
1342
1482
  const currentModelId = current.model.includes("/") ? current.model.split("/")[1] : current.model;
1343
1483
  const currentProviderId = current.provider ?? (current.model.includes("/") ? current.model.split("/")[0] : void 0);
1344
1484
  const activeModel = models.find((m) => m.id === currentModelId || m.id === current.model);
1345
1485
  if (!isQoderProvider(currentProviderId) || !activeModel) return null;
1346
- const tiers = contextTiersOf(activeModel);
1347
- if (tiers.length <= 1) return null;
1348
- 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([
1349
1497
  region,
1350
1498
  sessionId,
1351
1499
  activeModel.id
1352
- ]) : void 0;
1353
- const historicalWindow = operations.canRestoreContextHistory?.() === false ? void 0 : historicalContextWindow(current, modelProj?.lastUsed, pressure?.contextWindow);
1354
- const currentTier = resolveContextTier(activeModel, sessionKey ? manualTiers[sessionKey] : void 0, historicalWindow);
1355
- if (!currentTier) return null;
1356
- const selectedTierKey = currentTier.key;
1357
- 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) => {
1358
1549
  setOpen(false);
1359
- if (!sessionId || !sessionKey || saving.current) return;
1360
- if (tierKey === selectedTierKey && error?.key !== sessionKey) return;
1361
- saving.current = true;
1362
- setPending(true);
1363
- setError(null);
1364
- try {
1365
- const result = await saveContextSelection(operations, sessionId, region, activeModel.id, tierKey, legacyRegion.current ?? region);
1366
- if (result !== "session-failed") setManualTiers((prev) => ({
1367
- ...prev,
1368
- [sessionKey]: tierKey
1369
- }));
1370
- if (result !== "saved") setError({
1371
- key: sessionKey,
1372
- message: result === "session-failed" ? "contextSelectFailed" : "contextDefaultFailed"
1373
- });
1374
- } finally {
1375
- saving.current = false;
1376
- setPending(false);
1377
- }
1550
+ state.select(tierKey);
1378
1551
  };
1379
1552
  const defaultSuffix = t ? t("contextTierDefaultSuffix").replace(/[()()]/g, "") : "默认";
1380
1553
  const selectTitle = t ? t("contextSelectTitle") : "选择上下文大小";
@@ -1386,10 +1559,10 @@ window.__ModuleLoader__.load({
1386
1559
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1387
1560
  type: "button",
1388
1561
  className: `${QoderContextSelect_module_css_default.trigger} ${open ? QoderContextSelect_module_css_default.triggerActive : ""}`,
1389
- disabled: pending || !sessionId,
1562
+ disabled: pending || !currentTier,
1390
1563
  "aria-expanded": open,
1391
1564
  "aria-label": selectTitle,
1392
- title: `${selectTitle}: ${formatContextTokens(currentTier.tokenCount)}`,
1565
+ title: `${selectTitle}: ${currentTier ? formatContextTokens(currentTier.tokenCount) : "…"}`,
1393
1566
  onClick: () => setOpen((prev) => !prev),
1394
1567
  children: [
1395
1568
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1404,7 +1577,7 @@ window.__ModuleLoader__.load({
1404
1577
  }),
1405
1578
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1406
1579
  className: QoderContextSelect_module_css_default.label,
1407
- children: formatContextTokens(currentTier.tokenCount)
1580
+ children: currentTier ? formatContextTokens(currentTier.tokenCount) : "…"
1408
1581
  }),
1409
1582
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1410
1583
  className: `${QoderContextSelect_module_css_default.chevron} ${open ? QoderContextSelect_module_css_default.chevronOpen : ""}`,
@@ -1422,10 +1595,21 @@ window.__ModuleLoader__.load({
1422
1595
  })
1423
1596
  ]
1424
1597
  }),
1425
- error && error.key === sessionKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1598
+ snapshot.error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1426
1599
  role: "alert",
1427
1600
  className: QoderContextSelect_module_css_default.error,
1428
- children: t ? t(error.message) : error.message
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
+ ]
1429
1613
  }),
1430
1614
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1431
1615
  className: QoderContextSelect_module_css_default.menu,
@@ -1439,6 +1623,7 @@ window.__ModuleLoader__.load({
1439
1623
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1440
1624
  type: "button",
1441
1625
  role: "menuitemradio",
1626
+ disabled: pending,
1442
1627
  "aria-checked": isSelected,
1443
1628
  className: `${QoderContextSelect_module_css_default.option} ${isSelected ? QoderContextSelect_module_css_default.optionSelected : ""}`,
1444
1629
  onClick: () => {
@@ -1494,6 +1679,97 @@ window.__ModuleLoader__.load({
1494
1679
  return typeof value === "string" && qoderRpcErrorCodes.includes(value);
1495
1680
  }
1496
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
1497
1773
  //#region src/client/rpc-client.ts
1498
1774
  /**
1499
1775
  * Browser caller for the Qoder settings RPC.
@@ -1589,21 +1865,9 @@ window.__ModuleLoader__.load({
1589
1865
  }), "provider-qoder: credential copy");
1590
1866
  const credentials = ctx.remote.credentials;
1591
1867
  const rpc = createQoderRpcCaller();
1592
- let historyRegion;
1593
- let historyRegionChanged = false;
1594
- const readModelSnapshot = () => {
1595
- const snapshot = modelScope.getSnapshot();
1596
- if (snapshot.value) {
1597
- const region = snapshot.value.region ?? "global";
1598
- if (historyRegion !== void 0 && historyRegion !== region) historyRegionChanged = true;
1599
- historyRegion = region;
1600
- }
1601
- return snapshot;
1602
- };
1603
- readModelSnapshot();
1604
- ctx.effect(() => modelScope.subscribe(() => {
1605
- readModelSnapshot();
1606
- }), "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();
1607
1871
  const operations = {
1608
1872
  describe: async () => {
1609
1873
  try {
@@ -1629,19 +1893,15 @@ window.__ModuleLoader__.load({
1629
1893
  },
1630
1894
  getAccount: async (force) => await rpc.call("account", { force }),
1631
1895
  getModelSnapshot: readModelSnapshot,
1632
- canRestoreContextHistory: () => !historyRegionChanged,
1633
1896
  subscribeModels: (listener) => modelScope.subscribe(listener),
1634
1897
  storeModels: async (region, models) => {
1635
1898
  try {
1636
- const current = modelScope.getSnapshot().value;
1637
- const clonedModels = models.map((m) => ({
1638
- ...m,
1639
- ...m.contextOptions !== void 0 ? { contextOptions: { ...m.contextOptions } } : {}
1640
- }));
1641
- return await modelScope.set("modelsByRegion", {
1642
- ...current?.modelsByRegion,
1643
- [region]: clonedModels
1644
- }) !== 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;
1645
1905
  } catch {
1646
1906
  return false;
1647
1907
  }
@@ -1663,16 +1923,22 @@ window.__ModuleLoader__.load({
1663
1923
  discoverModels: async () => await rpc.call("models", {}),
1664
1924
  setSessionTier: async (sessionId, modelId, tierKey, region) => {
1665
1925
  try {
1666
- return (await rpc.call("sessionTier", {
1926
+ const res = await rpc.call("sessionTier", {
1667
1927
  sessionId,
1668
1928
  modelId,
1669
1929
  tierKey,
1670
1930
  region
1671
- })).ok;
1931
+ });
1932
+ return res.ok ? res.value : void 0;
1672
1933
  } catch {
1673
- return false;
1934
+ return;
1674
1935
  }
1675
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),
1676
1942
  subscribe: (listener) => ctx.remote.$on("credentials/reference-updated", (ref) => {
1677
1943
  if (ref === "QODER_MANAGED_CREDENTIAL") listener();
1678
1944
  })
@@ -1709,25 +1975,31 @@ window.__ModuleLoader__.load({
1709
1975
  activeLocale
1710
1976
  })
1711
1977
  }, QoderCredentialCard));
1712
- ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
1713
- name: "conversation.input.right",
1714
- id: "qoder-context-select",
1715
- order: 10,
1716
- inject: (sessionId) => {
1717
- let directory = void 0;
1718
- try {
1719
- const modelDirectories = ctx.get("modelDirectories");
1720
- directory = sessionId && modelDirectories ? modelDirectories.directoryFor(sessionId)?.store : void 0;
1721
- } catch {}
1722
- return {
1723
- sessionId,
1724
- directory,
1725
- operations,
1726
- t,
1727
- activeLocale
1728
- };
1729
- }
1730
- }, QoderContextSelect));
1978
+ ctx.inject([
1979
+ "modelDirectories",
1980
+ "sessions",
1981
+ "remote.session"
1982
+ ], (scope) => {
1983
+ scope.slots.inject("conversation.input.right", () => scope.slots.register({
1984
+ name: "conversation.input.right",
1985
+ id: "qoder-context-select",
1986
+ order: 10,
1987
+ inject: (sessionId) => {
1988
+ let directory = void 0;
1989
+ try {
1990
+ const modelDirectories = scope.get("modelDirectories");
1991
+ directory = sessionId && modelDirectories ? modelDirectories.directoryFor(sessionId)?.store : void 0;
1992
+ } catch {}
1993
+ return {
1994
+ sessionId,
1995
+ directory,
1996
+ operations,
1997
+ t,
1998
+ activeLocale
1999
+ };
2000
+ }
2001
+ }, QoderContextSelect));
2002
+ });
1731
2003
  }
1732
2004
  //#endregion
1733
2005
  exports.apply = apply;