dsh-plugin-subscription-oauth 0.8.13 → 0.8.15

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
@@ -623,6 +623,67 @@ const usageChipCss = `
623
623
  background: var(--dsw-alias-state-success-primary, #067647);
624
624
  }
625
625
  `;
626
+ /**
627
+ * The composer's quick-mode button stylesheet.
628
+ *
629
+ * It renders in the composer's own control row — the same row as the model selector —
630
+ * so it borrows that row's geometry: a compact, borderless control that only colours on
631
+ * hover and when the mode is on. Tokens carry fallbacks for hosts that define none.
632
+ */
633
+ const fastModeCss = `
634
+ .dsh-subscription-fast-mode {
635
+ display: inline-flex;
636
+ align-items: center;
637
+ gap: 4px;
638
+ margin: 0;
639
+ padding: 3px;
640
+ border: 0;
641
+ border-radius: var(--dsw-radius-sm, 6px);
642
+ background: transparent;
643
+ color: var(--dsw-alias-label-secondary, #8a8f98);
644
+ font: inherit;
645
+ font-size: 12px;
646
+ line-height: 1;
647
+ cursor: pointer;
648
+ }
649
+ .dsh-subscription-fast-mode:hover {
650
+ background: var(--dsw-alias-interactive-bg-hover, #ffffff14);
651
+ }
652
+ /* A click has to look like one, even before the selection lands. */
653
+ .dsh-subscription-fast-mode:active {
654
+ background: var(--dsw-alias-interactive-bg-pressed, #ffffff2e);
655
+ }
656
+ .dsh-subscription-fast-mode[data-pending="true"] {
657
+ opacity: 0.7;
658
+ cursor: progress;
659
+ }
660
+ /* A switch the host refused is marked rather than left looking merely off, so a failure
661
+ is visible where it happened; the reason rides the title and the console. */
662
+ .dsh-subscription-fast-mode[data-failed="true"] {
663
+ color: var(--dsw-alias-state-error-primary, #b42318);
664
+ }
665
+ .dsh-subscription-fast-mode:focus-visible {
666
+ outline: 2px solid var(--dsw-alias-border-focus, #4d6bfe);
667
+ outline-offset: 1px;
668
+ }
669
+ /* Off: the bolt is dim, so an available mode reads as an invitation, not a state. */
670
+ .dsh-subscription-fast-mode[data-active="false"] .dsh-subscription-fast-mode-bolt {
671
+ opacity: 0.55;
672
+ }
673
+ /* On: the bolt takes the accent and glows, which is the only thing distinguishing the
674
+ fast variant from its base — both answer with the same model name. */
675
+ .dsh-subscription-fast-mode[data-active="true"] .dsh-subscription-fast-mode-bolt {
676
+ color: var(--dsw-alias-state-warning-primary, #f5a524);
677
+ filter: drop-shadow(0 0 6px var(--dsw-alias-state-warning-primary, #f5a52455));
678
+ }
679
+ .dsh-subscription-fast-mode-reason {
680
+ max-width: 14ch;
681
+ overflow: hidden;
682
+ text-overflow: ellipsis;
683
+ white-space: nowrap;
684
+ font-size: 11px;
685
+ }
686
+ `;
626
687
 
627
688
  //#endregion
628
689
  //#region src/protocol.ts
@@ -2457,7 +2518,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2457
2518
  const [now, setNow] = (0, react.useState)(() => Date.now());
2458
2519
  const [open, setOpen] = (0, react.useState)(false);
2459
2520
  const lastOwn = (0, react.useRef)(void 0);
2460
- const selected = useProjection("modelSelection", (value) => providerOf(value));
2521
+ const selected = providerOf(useProjection("modelSelection", (value) => value));
2461
2522
  const isDeepSeek = selected === "deepseek-account";
2462
2523
  const isBalanceProvider = selected !== void 0 && !isOwnProvider(selected) && !isDeepSeek;
2463
2524
  (0, react.useEffect)(() => {
@@ -2709,6 +2770,112 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2709
2770
  });
2710
2771
  }
2711
2772
 
2773
+ //#endregion
2774
+ //#region src/client/FastModeButton.tsx
2775
+ /**
2776
+ * The quick-mode control, for the composer's row of standard controls.
2777
+ *
2778
+ * @param props - injected RPC caller, directory, and the session standard kit.
2779
+ * @returns the button, or null when this model has no fast variant.
2780
+ */
2781
+ function FastModeButton({ call, isOwnProvider, modelDirectory, useProjection }) {
2782
+ const [pair, setPair] = (0, react.useState)(void 0);
2783
+ const [failure, setFailure] = (0, react.useState)(void 0);
2784
+ const [pending, setPending] = (0, react.useState)(false);
2785
+ const selection = useProjection("modelSelection", (value) => value);
2786
+ const provider = providerOf(selection);
2787
+ const model = selection?.next?.model ?? selection?.lastUsed?.model;
2788
+ (0, react.useEffect)(() => {
2789
+ if (provider === void 0 || model === void 0 || !isOwnProvider(provider)) {
2790
+ setPair(void 0);
2791
+ return;
2792
+ }
2793
+ let live = true;
2794
+ call("model-variants", {
2795
+ provider,
2796
+ id: model
2797
+ }).then((result) => {
2798
+ if (!live) return;
2799
+ setPair(result?.fast === void 0 ? void 0 : {
2800
+ wireId: result.fast,
2801
+ ...result.fastBase === void 0 ? {} : { baseId: result.fastBase }
2802
+ });
2803
+ }).catch(() => {
2804
+ if (live) setPair(void 0);
2805
+ });
2806
+ return () => {
2807
+ live = false;
2808
+ };
2809
+ }, [
2810
+ provider,
2811
+ model,
2812
+ isOwnProvider,
2813
+ call
2814
+ ]);
2815
+ (0, react.useEffect)(() => {
2816
+ setFailure(void 0);
2817
+ }, [model]);
2818
+ if (pair === void 0 || provider === void 0 || modelDirectory?.select === void 0) return null;
2819
+ const active = pair.wireId === model;
2820
+ const target = active ? pair.baseId : pair.wireId;
2821
+ if (target === void 0 || target === model) return null;
2822
+ const base = active ? "Fast mode is on; press to go back to the standard model" : "Fast mode: switch to the fast variant of this model";
2823
+ const title = failure === void 0 ? base : `${base}\nThe switch was refused — ${`${failure.code ?? ""} ${failure.message}`.trim()}`;
2824
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: fastModeCss }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2825
+ type: "button",
2826
+ className: "dsh-subscription-fast-mode",
2827
+ "data-active": active,
2828
+ "data-pending": pending,
2829
+ "data-failed": failure !== void 0,
2830
+ "aria-pressed": active,
2831
+ "aria-busy": pending,
2832
+ title,
2833
+ "aria-label": title,
2834
+ onClick: (event) => {
2835
+ event.stopPropagation();
2836
+ setPending(true);
2837
+ setFailure(void 0);
2838
+ Promise.resolve(modelDirectory.select?.({
2839
+ provider,
2840
+ model: target
2841
+ })).then((result) => {
2842
+ const outcome = result;
2843
+ if (outcome?.ok !== false) return;
2844
+ const code = outcome.error?.code;
2845
+ const message = outcome.error?.message ?? code ?? "the host refused the selection";
2846
+ setFailure({
2847
+ ...code === void 0 ? {} : { code },
2848
+ message
2849
+ });
2850
+ console.warn(`[subscription-oauth] fast mode ${provider}/${target} refused:`, outcome.error);
2851
+ }).catch((error) => {
2852
+ setFailure({ message: error instanceof Error ? error.message : String(error) });
2853
+ console.warn(`[subscription-oauth] fast mode ${provider}/${target} failed:`, error);
2854
+ }).finally(() => {
2855
+ setPending(false);
2856
+ });
2857
+ },
2858
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2859
+ className: "dsh-subscription-fast-mode-bolt",
2860
+ viewBox: "0 0 24 24",
2861
+ width: "16",
2862
+ height: "16",
2863
+ "aria-hidden": "true",
2864
+ fill: "none",
2865
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2866
+ d: "M13 2 3.6 13.2a1.2 1.2 0 0 0 .92 1.98H11l-.86 6.42a1.2 1.2 0 0 0 2.2.7L21.4 10.6a1.2 1.2 0 0 0-.92-1.98H13l.86-6.42A1.2 1.2 0 0 0 13 2z",
2867
+ fill: "currentColor",
2868
+ stroke: "currentColor",
2869
+ strokeWidth: "1.2",
2870
+ strokeLinejoin: "round"
2871
+ })
2872
+ }), failure !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2873
+ className: "dsh-subscription-fast-mode-reason",
2874
+ children: failure.code ?? failure.message
2875
+ })]
2876
+ })] });
2877
+ }
2878
+
2712
2879
  //#endregion
2713
2880
  //#region src/client/index.ts
2714
2881
  /**
@@ -2736,13 +2903,21 @@ const SECTION_ORDER = 92;
2736
2903
  */
2737
2904
  const CHIP_ORDER = 10;
2738
2905
  /**
2906
+ * Composer-control position: ahead of the shell's own entries in that row, if it ever
2907
+ * adds any, so the quick-mode button stays adjacent to the model selector it belongs to.
2908
+ */
2909
+ const FAST_MODE_ORDER = 10;
2910
+ /**
2739
2911
  * Required services: `slots` carries the registration seat, `connection` the
2740
- * RPC caller, and `locale` the copy dictionaries.
2912
+ * RPC caller, `locale` the copy dictionaries, and `modelDirectories` the picker's
2913
+ * per-session model directory (written when the quick-mode button routes to a fast
2914
+ * variant).
2741
2915
  */
2742
2916
  const inject = [
2743
2917
  "slots",
2744
2918
  "connection",
2745
- "locale"
2919
+ "locale",
2920
+ "modelDirectories"
2746
2921
  ];
2747
2922
  /**
2748
2923
  * Register the Subscriptions settings section.
@@ -2760,6 +2935,7 @@ function apply(ctx) {
2760
2935
  const connection = ctx.get("connection");
2761
2936
  const t = ctx.locale.bind(NS);
2762
2937
  const call = createSubscriptionsCall(connection.rpc);
2938
+ const modelDirectories = ctx.get("modelDirectories");
2763
2939
  ctx.slots.inject("settings.section", () => ctx.slots.register({
2764
2940
  name: "settings.section",
2765
2941
  id: "subscriptions",
@@ -2782,6 +2958,32 @@ function apply(ctx) {
2782
2958
  ccSwitchBalance: createCcSwitchBalanceCall(connection.rpc)
2783
2959
  })
2784
2960
  }, UsageChip));
2961
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
2962
+ name: "conversation.input.right",
2963
+ id: "subscription-fast-mode",
2964
+ order: FAST_MODE_ORDER,
2965
+ inject: (sessionId) => ({
2966
+ call,
2967
+ isOwnProvider: (provider) => OWN_PROVIDER_IDS.has(provider),
2968
+ modelDirectory: modelDirectoryFor(modelDirectories, sessionId)
2969
+ })
2970
+ }, FastModeButton));
2971
+ }
2972
+ /**
2973
+ * The session's model directory, in the shape the quick-mode button writes through.
2974
+ *
2975
+ * Absent when the host exposes no directory for the session, or exposes one that cannot
2976
+ * select — in which case the button renders nothing rather than a dead control.
2977
+ *
2978
+ * @param modelDirectories - the host's directory resolver, when it has one.
2979
+ * @param sessionId - the session whose directory is wanted.
2980
+ * @returns the reader/writer pair, or undefined.
2981
+ */
2982
+ function modelDirectoryFor(modelDirectories, sessionId) {
2983
+ if (sessionId === void 0 || modelDirectories?.directoryFor === void 0) return void 0;
2984
+ const directory = modelDirectories.directoryFor(sessionId);
2985
+ if (directory?.select === void 0) return void 0;
2986
+ return { select: (selection) => directory.select?.(selection) ?? Promise.resolve() };
2785
2987
  }
2786
2988
 
2787
2989
  //#endregion