@soba-so/react 0.0.26 → 0.0.28
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/dist/ProviderMark.d.ts +13 -0
- package/dist/SobaGate.d.ts +35 -0
- package/dist/context.d.ts +36 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.mjs +290 -8
- package/dist/styles.css +282 -0
- package/dist/types.d.ts +177 -3
- package/dist/useCurrentPlan.d.ts +9 -3
- package/dist/useFeature.d.ts +27 -0
- package/dist/useModel.d.ts +117 -4
- package/dist/usePlans.d.ts +9 -1
- package/package.json +1 -1
package/dist/styles.css
CHANGED
|
@@ -1533,6 +1533,7 @@ a.sc-btn {
|
|
|
1533
1533
|
|
|
1534
1534
|
.sc-mp-label {
|
|
1535
1535
|
min-width: 0;
|
|
1536
|
+
max-width: 24ch;
|
|
1536
1537
|
overflow: hidden;
|
|
1537
1538
|
text-overflow: ellipsis;
|
|
1538
1539
|
white-space: nowrap;
|
|
@@ -1544,6 +1545,274 @@ a.sc-btn {
|
|
|
1544
1545
|
color: var(--sc-faint, #a1a1aa);
|
|
1545
1546
|
}
|
|
1546
1547
|
|
|
1548
|
+
.sc-mm {
|
|
1549
|
+
position: fixed;
|
|
1550
|
+
inset: auto;
|
|
1551
|
+
z-index: 2147483647;
|
|
1552
|
+
display: flex;
|
|
1553
|
+
flex-direction: column;
|
|
1554
|
+
min-width: calc(240px * var(--sc-scale, 1));
|
|
1555
|
+
max-width: min(calc(340px * var(--sc-scale, 1)), calc(100vw - 16px));
|
|
1556
|
+
width: max-content;
|
|
1557
|
+
margin: 0;
|
|
1558
|
+
padding: 0;
|
|
1559
|
+
overflow: hidden;
|
|
1560
|
+
border: 1px solid var(--sc-line, #e9e9eb);
|
|
1561
|
+
border-radius: calc(12px * var(--sc-scale, 1));
|
|
1562
|
+
background: var(--sc-tray-card, #ffffff);
|
|
1563
|
+
box-shadow:
|
|
1564
|
+
0 12px 32px rgba(0, 0, 0, 0.12),
|
|
1565
|
+
0 2px 6px rgba(0, 0, 0, 0.06);
|
|
1566
|
+
color: var(--sc-text, #1a1a1c);
|
|
1567
|
+
font-size: var(--sc-fs-small, 13px);
|
|
1568
|
+
line-height: 1.3;
|
|
1569
|
+
outline: none;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
.sc-mm-search {
|
|
1573
|
+
display: flex;
|
|
1574
|
+
flex: none;
|
|
1575
|
+
align-items: center;
|
|
1576
|
+
gap: 8px;
|
|
1577
|
+
padding: 0 12px;
|
|
1578
|
+
border-bottom: 1px solid var(--sc-line, #e9e9eb);
|
|
1579
|
+
color: var(--sc-faint, #a1a1aa);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
.sc-mm-find {
|
|
1583
|
+
flex: none;
|
|
1584
|
+
width: 14px;
|
|
1585
|
+
height: 14px;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
.sc-mm-search input {
|
|
1589
|
+
flex: 1;
|
|
1590
|
+
min-width: 0;
|
|
1591
|
+
height: calc(38px * var(--sc-scale, 1));
|
|
1592
|
+
padding: 0;
|
|
1593
|
+
border: 0;
|
|
1594
|
+
background: transparent;
|
|
1595
|
+
font: inherit;
|
|
1596
|
+
|
|
1597
|
+
font-size: max(16px, var(--sc-fs-small, 13px));
|
|
1598
|
+
color: var(--sc-text, #1a1a1c);
|
|
1599
|
+
outline: none;
|
|
1600
|
+
}
|
|
1601
|
+
@media (pointer: fine) {
|
|
1602
|
+
.sc-mm-search input {
|
|
1603
|
+
font-size: inherit;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
.sc-mm-search input::placeholder {
|
|
1607
|
+
color: var(--sc-faint, #a1a1aa);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
.sc-mm-list {
|
|
1611
|
+
flex: 1;
|
|
1612
|
+
min-height: 0;
|
|
1613
|
+
padding: 4px;
|
|
1614
|
+
overflow-y: auto;
|
|
1615
|
+
overscroll-behavior: contain;
|
|
1616
|
+
scroll-padding-block: 4px;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
.sc-menu,
|
|
1620
|
+
.sc-mm-list,
|
|
1621
|
+
.sc-mm-flyout {
|
|
1622
|
+
scrollbar-width: none;
|
|
1623
|
+
}
|
|
1624
|
+
.sc-menu::-webkit-scrollbar,
|
|
1625
|
+
.sc-mm-list::-webkit-scrollbar,
|
|
1626
|
+
.sc-mm-flyout::-webkit-scrollbar {
|
|
1627
|
+
display: none;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.sc-mm-group + .sc-mm-group {
|
|
1631
|
+
margin-top: 4px;
|
|
1632
|
+
padding-top: 4px;
|
|
1633
|
+
border-top: 1px solid var(--sc-line, #e9e9eb);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.sc-mm-head {
|
|
1637
|
+
display: flex;
|
|
1638
|
+
align-items: center;
|
|
1639
|
+
gap: 6px;
|
|
1640
|
+
padding: 8px 8px 4px;
|
|
1641
|
+
font-size: var(--sc-fs-label);
|
|
1642
|
+
font-weight: 500;
|
|
1643
|
+
color: var(--sc-muted, #6b6b73);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.sc-mm-logo {
|
|
1647
|
+
flex: none;
|
|
1648
|
+
width: 14px;
|
|
1649
|
+
height: 14px;
|
|
1650
|
+
border-radius: 3px;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
.sc-mm-emoji {
|
|
1654
|
+
display: inline-grid;
|
|
1655
|
+
place-items: center;
|
|
1656
|
+
font-size: 12px;
|
|
1657
|
+
line-height: 1;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
.sc-mm-item {
|
|
1661
|
+
display: flex;
|
|
1662
|
+
align-items: center;
|
|
1663
|
+
gap: 8px;
|
|
1664
|
+
min-height: calc(32px * var(--sc-scale, 1));
|
|
1665
|
+
padding: 0 8px;
|
|
1666
|
+
border-radius: calc(7px * var(--sc-scale, 1));
|
|
1667
|
+
cursor: pointer;
|
|
1668
|
+
user-select: none;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
.sc-mm-item[data-active] {
|
|
1672
|
+
background: var(--sc-tray-sunk, #f4f4f5);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.sc-mm-inline {
|
|
1676
|
+
position: static;
|
|
1677
|
+
z-index: auto;
|
|
1678
|
+
max-height: 380px;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.sc-mm-item[data-locked] {
|
|
1682
|
+
cursor: default;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.sc-mm-item[data-locked] .sc-mm-label {
|
|
1686
|
+
color: var(--sc-muted, #6b6b73);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
.sc-mm-label {
|
|
1690
|
+
flex: none;
|
|
1691
|
+
max-width: 100%;
|
|
1692
|
+
overflow: hidden;
|
|
1693
|
+
text-overflow: ellipsis;
|
|
1694
|
+
white-space: nowrap;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
.sc-mm-item[aria-selected="true"] .sc-mm-label {
|
|
1698
|
+
font-weight: 500;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
.sc-mm-note {
|
|
1702
|
+
flex: 1;
|
|
1703
|
+
min-width: 0;
|
|
1704
|
+
overflow: hidden;
|
|
1705
|
+
text-overflow: ellipsis;
|
|
1706
|
+
white-space: nowrap;
|
|
1707
|
+
color: var(--sc-muted, #6b6b73);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
.sc-mm-item .sc-menu-check {
|
|
1711
|
+
margin-left: auto;
|
|
1712
|
+
width: 14px;
|
|
1713
|
+
height: 14px;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.sc-mm-empty {
|
|
1717
|
+
padding: 16px 8px;
|
|
1718
|
+
text-align: center;
|
|
1719
|
+
color: var(--sc-muted, #6b6b73);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
.sc-mm-tile {
|
|
1723
|
+
flex: none;
|
|
1724
|
+
width: 16px;
|
|
1725
|
+
height: 16px;
|
|
1726
|
+
border-radius: 4px;
|
|
1727
|
+
object-fit: cover;
|
|
1728
|
+
}
|
|
1729
|
+
.sc-mm-tile-box {
|
|
1730
|
+
display: inline-grid;
|
|
1731
|
+
place-items: center;
|
|
1732
|
+
background: color-mix(in srgb, currentColor 6%, transparent);
|
|
1733
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 10%, transparent);
|
|
1734
|
+
color: var(--sc-text, #1a1a1c);
|
|
1735
|
+
}
|
|
1736
|
+
.sc-mm-tile-glyph {
|
|
1737
|
+
width: 11px;
|
|
1738
|
+
height: 11px;
|
|
1739
|
+
}
|
|
1740
|
+
.sc-mm-tile-dim {
|
|
1741
|
+
opacity: 0.5;
|
|
1742
|
+
filter: grayscale(1);
|
|
1743
|
+
}
|
|
1744
|
+
.sc-mm-tile.sc-mm-emoji {
|
|
1745
|
+
font-size: 11px;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.sc-mm-sub .sc-chev {
|
|
1749
|
+
flex: none;
|
|
1750
|
+
width: 14px;
|
|
1751
|
+
height: 14px;
|
|
1752
|
+
margin-left: auto;
|
|
1753
|
+
color: var(--sc-muted, #6b6b73);
|
|
1754
|
+
}
|
|
1755
|
+
.sc-mm-sub .sc-mm-label {
|
|
1756
|
+
flex: 1;
|
|
1757
|
+
min-width: 0;
|
|
1758
|
+
}
|
|
1759
|
+
.sc-mm-sub[data-locked] .sc-mm-tile {
|
|
1760
|
+
opacity: 0.5;
|
|
1761
|
+
filter: grayscale(1);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.sc-mm-pill {
|
|
1765
|
+
flex: none;
|
|
1766
|
+
margin-left: auto;
|
|
1767
|
+
padding: 1px 6px;
|
|
1768
|
+
border-radius: 999px;
|
|
1769
|
+
box-shadow: inset 0 0 0 1px var(--sc-line, #e9e9eb);
|
|
1770
|
+
font-size: 10px;
|
|
1771
|
+
font-weight: 500;
|
|
1772
|
+
letter-spacing: 0.02em;
|
|
1773
|
+
color: var(--sc-muted, #6b6b73);
|
|
1774
|
+
}
|
|
1775
|
+
.sc-mm-sub .sc-mm-pill {
|
|
1776
|
+
margin-left: 0;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
.sc-mm-dot {
|
|
1780
|
+
flex: none;
|
|
1781
|
+
width: 6px;
|
|
1782
|
+
height: 6px;
|
|
1783
|
+
border-radius: 999px;
|
|
1784
|
+
background: color-mix(in srgb, var(--sc-text, #1a1a1c) 60%, transparent);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
.sc-mm-rule {
|
|
1788
|
+
height: 1px;
|
|
1789
|
+
margin: 4px -4px;
|
|
1790
|
+
background: var(--sc-line, #e9e9eb);
|
|
1791
|
+
}
|
|
1792
|
+
.sc-mm-label-row {
|
|
1793
|
+
padding: 6px 8px 4px;
|
|
1794
|
+
font-size: var(--sc-fs-label, 12px);
|
|
1795
|
+
color: var(--sc-muted, #6b6b73);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
.sc-mm-flyout {
|
|
1799
|
+
position: fixed;
|
|
1800
|
+
z-index: 1;
|
|
1801
|
+
padding: 4px;
|
|
1802
|
+
overflow-y: auto;
|
|
1803
|
+
overscroll-behavior: contain;
|
|
1804
|
+
border: 1px solid var(--sc-line, #e9e9eb);
|
|
1805
|
+
border-radius: calc(12px * var(--sc-scale, 1));
|
|
1806
|
+
background: var(--sc-tray-card, #ffffff);
|
|
1807
|
+
box-shadow:
|
|
1808
|
+
0 12px 32px rgba(0, 0, 0, 0.12),
|
|
1809
|
+
0 2px 6px rgba(0, 0, 0, 0.06);
|
|
1810
|
+
}
|
|
1811
|
+
.sc-mm-inline {
|
|
1812
|
+
position: relative;
|
|
1813
|
+
overflow: visible;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1547
1816
|
.sc-atile .sc-runtime-logo {
|
|
1548
1817
|
width: calc(25px * var(--sc-scale, 1));
|
|
1549
1818
|
height: calc(25px * var(--sc-scale, 1));
|
|
@@ -1936,6 +2205,19 @@ a.sc-btn {
|
|
|
1936
2205
|
color: var(--sc-muted, #6b6b73);
|
|
1937
2206
|
}
|
|
1938
2207
|
|
|
2208
|
+
.sc-plan-feature::before {
|
|
2209
|
+
content: "✓ ";
|
|
2210
|
+
color: var(--sc-text, currentColor);
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
.sc-plan-feature-absent {
|
|
2214
|
+
text-decoration: line-through;
|
|
2215
|
+
opacity: 0.6;
|
|
2216
|
+
}
|
|
2217
|
+
.sc-plan-feature-absent::before {
|
|
2218
|
+
content: "✕ ";
|
|
2219
|
+
}
|
|
2220
|
+
|
|
1939
2221
|
.sc-plan-badge {
|
|
1940
2222
|
font-size: var(--sc-fs-label, 11px);
|
|
1941
2223
|
padding: 2px 6px;
|
package/dist/types.d.ts
CHANGED
|
@@ -127,9 +127,10 @@ export type Launch =
|
|
|
127
127
|
* Derived from what they have connected rather than configured anywhere: see
|
|
128
128
|
* `useModel()`, and `models.ts` in the panel for the three facts it reconciles.
|
|
129
129
|
*/
|
|
130
|
-
export type ModelOptionKind = "auto" | "size" | "named";
|
|
130
|
+
export type ModelOptionKind = "auto" | "size" | "named" | "provider";
|
|
131
131
|
export interface ModelOption {
|
|
132
|
-
/** `auto`, `small`, `large`,
|
|
132
|
+
/** `auto`, `small`, `large`, `<runtime>/<model>`, or `@<provider>/<model>`
|
|
133
|
+
* for one of the app's own providers. */
|
|
133
134
|
id: string;
|
|
134
135
|
kind: ModelOptionKind;
|
|
135
136
|
/** `Automatic`, `Fast`, `Opus`, `llama3.2:3b`. */
|
|
@@ -153,6 +154,87 @@ export interface ModelOption {
|
|
|
153
154
|
costClass?: string;
|
|
154
155
|
/** The machine advertising it, when exactly one of theirs does. */
|
|
155
156
|
machine?: string;
|
|
157
|
+
/** The app's provider serving it, on a provider's model only. */
|
|
158
|
+
providerId?: string;
|
|
159
|
+
/** That provider's name, as the app's owner gave it. */
|
|
160
|
+
providerName?: string;
|
|
161
|
+
/** Whose API it is — `openai`, `groq` — for `<ProviderMark />`. */
|
|
162
|
+
providerMark?: string;
|
|
163
|
+
/** Listed and not pickable: the plan does not reach this provider, or its
|
|
164
|
+
* allowance is used. Draw it as an upgrade, never select it. */
|
|
165
|
+
locked?: "plan" | "allowance";
|
|
166
|
+
/** The app's own entry this was made from — a name set on Providers (its
|
|
167
|
+
* alias) or a `ModelChoice.id` in code: the app's words on a real model. */
|
|
168
|
+
choice?: string;
|
|
169
|
+
/** A name's run may land on any of these classes. */
|
|
170
|
+
costClasses?: string[];
|
|
171
|
+
/** A name's own logo, set on the dashboard: an https address or one emoji.
|
|
172
|
+
* Drawn in place of the provider's mark. */
|
|
173
|
+
icon?: string;
|
|
174
|
+
}
|
|
175
|
+
/** How a picker lays out the app's providers. See `UseModelOptions.layout`. */
|
|
176
|
+
export type ModelLayout = "providers" | "models" | "choices" | "automatic";
|
|
177
|
+
/** One entry the app writes itself, naming a model it exposes. */
|
|
178
|
+
export interface ModelChoice {
|
|
179
|
+
/** Stable; the pick is stored as `choice:<id>`. */
|
|
180
|
+
id: string;
|
|
181
|
+
/** "Fast", "Smart". */
|
|
182
|
+
label: string;
|
|
183
|
+
/** The quiet clause beside it. */
|
|
184
|
+
note?: string;
|
|
185
|
+
/** The model it runs, as the provider names it: `gpt-5-mini`. */
|
|
186
|
+
model: string;
|
|
187
|
+
/** Which provider, when two expose it: `openai`, its dashboard name, or its
|
|
188
|
+
* id. Any provider when left out. */
|
|
189
|
+
provider?: string;
|
|
190
|
+
/** Or one of the person's own agents, `claude-code`, for one of its models. */
|
|
191
|
+
runtime?: string;
|
|
192
|
+
}
|
|
193
|
+
/** One of the app's own providers, as `/v1/model-options` lists it. */
|
|
194
|
+
export interface ModelProviderWire {
|
|
195
|
+
id: string;
|
|
196
|
+
provider: string | null;
|
|
197
|
+
name: string;
|
|
198
|
+
cost_class: "open-weights" | "frontier";
|
|
199
|
+
locked: "plan" | "allowance" | null;
|
|
200
|
+
models: {
|
|
201
|
+
model: string;
|
|
202
|
+
default?: true;
|
|
203
|
+
}[];
|
|
204
|
+
}
|
|
205
|
+
/** One of the app's own names for a model, set on Providers. */
|
|
206
|
+
export interface ModelNameWire {
|
|
207
|
+
alias: string;
|
|
208
|
+
label: string;
|
|
209
|
+
note: string | null;
|
|
210
|
+
/** Its logo, set on the dashboard: an https address or one emoji. */
|
|
211
|
+
icon?: string;
|
|
212
|
+
cost_classes: string[];
|
|
213
|
+
provider: string | null;
|
|
214
|
+
locked: "plan" | "allowance" | null;
|
|
215
|
+
default?: true;
|
|
216
|
+
}
|
|
217
|
+
export interface ModelOptionsWire {
|
|
218
|
+
route: {
|
|
219
|
+
allow: string[];
|
|
220
|
+
prefer: string[];
|
|
221
|
+
};
|
|
222
|
+
providers: ModelProviderWire[];
|
|
223
|
+
names: ModelNameWire[];
|
|
224
|
+
layout: "providers" | "models" | "choices" | "automatic";
|
|
225
|
+
/** Whether Fast and Best are offered, as designed on Connect. Absent from a
|
|
226
|
+
* gateway older than the setting, which offered them. */
|
|
227
|
+
sizes?: boolean;
|
|
228
|
+
/** Which of Automatic, Fast and Best each layout offers, for an app whose
|
|
229
|
+
* code picks another than the one designed. Absent from an older gateway:
|
|
230
|
+
* `sizes` stands in for both sizes, and Automatic is always there. */
|
|
231
|
+
rowsByLayout?: Partial<Record<"providers" | "models" | "choices" | "automatic", {
|
|
232
|
+
auto: boolean;
|
|
233
|
+
fast: boolean;
|
|
234
|
+
best: boolean;
|
|
235
|
+
own?: boolean;
|
|
236
|
+
search?: boolean;
|
|
237
|
+
}>>;
|
|
156
238
|
}
|
|
157
239
|
export interface ModelGroup {
|
|
158
240
|
/** Null for the first group — Automatic and the sizes head nothing. */
|
|
@@ -209,7 +291,11 @@ export interface Plan {
|
|
|
209
291
|
price: number;
|
|
210
292
|
interval: string;
|
|
211
293
|
unit: string;
|
|
294
|
+
/** Runs a month on AI the app pays for. Null is unlimited. */
|
|
212
295
|
included_units: number | null;
|
|
296
|
+
/** Runs a month on the user's own AI, counted apart from `included_units`.
|
|
297
|
+
* Null is unlimited. Optional so an older server that omits it parses. */
|
|
298
|
+
own_included_units?: number | null;
|
|
213
299
|
/** What happens past the allowance: `stop` or `meter`. The difference
|
|
214
300
|
* between a wall and a bill, and not guessable from the price. */
|
|
215
301
|
overage: string;
|
|
@@ -232,10 +318,21 @@ export interface Plan {
|
|
|
232
318
|
ask_to_connect?: boolean;
|
|
233
319
|
/** Whether it can be taken without a payment rail. */
|
|
234
320
|
selectable: boolean;
|
|
321
|
+
/** What the app calls a run, plural: "tasks". Optional for older servers;
|
|
322
|
+
* absent reads "runs". */
|
|
323
|
+
task_noun?: string;
|
|
324
|
+
/** About how many runs a credit plan's allowance buys at this app's typical
|
|
325
|
+
* run cost: "About 265 tasks a month". Null when it cannot be said. */
|
|
326
|
+
estimated_tasks?: number | null;
|
|
327
|
+
/** A plan sold by conversation: "Talk to us" links here. */
|
|
328
|
+
contact_url?: string | null;
|
|
329
|
+
/** "Everything in" this plan id; its card lists that, then `features_own`. */
|
|
330
|
+
features_from?: string | null;
|
|
331
|
+
features_own?: PlanFeatureWire[] | null;
|
|
235
332
|
/**
|
|
236
333
|
* WHAT CONNECTING THEIR OWN COMPUTE EARNS THEM.
|
|
237
334
|
*
|
|
238
|
-
* `none` | `
|
|
335
|
+
* `none` | `included` | `credit`. The one dimension no other
|
|
239
336
|
* pricing component has a column for, and the reason a free plan on this
|
|
240
337
|
* product can be worth choosing.
|
|
241
338
|
*/
|
|
@@ -244,16 +341,49 @@ export interface Plan {
|
|
|
244
341
|
connected_included_units: number | null;
|
|
245
342
|
/** Dollars back per interval on a `credit` reward. Zero otherwise. */
|
|
246
343
|
connected_credit: number;
|
|
344
|
+
/** What the vendor's app enforces, as the card lists it (INT-91). Optional
|
|
345
|
+
* so an older server that omits it parses. */
|
|
346
|
+
features?: PlanFeatureWire[];
|
|
247
347
|
}
|
|
248
348
|
export interface CurrentPlan {
|
|
249
349
|
plan_id: string;
|
|
350
|
+
/** Spent against `included_units`. */
|
|
250
351
|
units_used: number;
|
|
352
|
+
/** Spent against `own_included_units`. Optional for older servers. */
|
|
353
|
+
own_units_used?: number;
|
|
354
|
+
/**
|
|
355
|
+
* WHAT SOBA ENFORCES: both allowances, what is left, when they reset. The
|
|
356
|
+
* gateway stops a run here; your app never has to. Optional for older
|
|
357
|
+
* servers, null when the plan is no longer in the catalogue.
|
|
358
|
+
*/
|
|
359
|
+
limits?: {
|
|
360
|
+
unit: string;
|
|
361
|
+
included_units: number | null;
|
|
362
|
+
units_used: number;
|
|
363
|
+
remaining: number | null;
|
|
364
|
+
own_included_units: number | null;
|
|
365
|
+
own_units_used: number;
|
|
366
|
+
own_remaining: number | null;
|
|
367
|
+
resets_at: string | null;
|
|
368
|
+
} | null;
|
|
369
|
+
/**
|
|
370
|
+
* WHAT YOUR APP ENFORCES (INT-91): feature id to value, as this person
|
|
371
|
+
* bought it. Soba stops nothing on these; read them and decide.
|
|
372
|
+
*/
|
|
373
|
+
features?: Record<string, boolean | number | string | null>;
|
|
374
|
+
/** How many of each priced feature are in use, as your app last reported
|
|
375
|
+
* (`{ seats: 12 }`). Separate from `features`, where `seats` is the cap. */
|
|
376
|
+
quantities?: Record<string, number>;
|
|
251
377
|
period_end: string | null;
|
|
252
378
|
trial_ends_at: string | null;
|
|
379
|
+
/** A free plan past its time limit: runs are refused, and every feature
|
|
380
|
+
* reads as off, so stop what the plan carried too. Absent on older servers. */
|
|
381
|
+
ended?: boolean;
|
|
253
382
|
}
|
|
254
383
|
export interface ConnectApi {
|
|
255
384
|
context(): Promise<ConnectContext>;
|
|
256
385
|
machines(): Promise<Machine[]>;
|
|
386
|
+
modelOptions(): Promise<ModelOptionsWire | null>;
|
|
257
387
|
plans(): Promise<{
|
|
258
388
|
plans: Plan[];
|
|
259
389
|
current: CurrentPlan | null;
|
|
@@ -347,3 +477,47 @@ export interface Message {
|
|
|
347
477
|
role: "user" | "assistant";
|
|
348
478
|
content: string;
|
|
349
479
|
}
|
|
480
|
+
/** A plan feature: what the vendor's app enforces, never Soba (INT-91). */
|
|
481
|
+
export interface PlanFeatureWire {
|
|
482
|
+
id: string;
|
|
483
|
+
name: string;
|
|
484
|
+
type: "boolean" | "number" | "text";
|
|
485
|
+
/** `true`/`false`, a whole number, or null for unlimited. */
|
|
486
|
+
value: boolean | number | string | null;
|
|
487
|
+
unit?: string;
|
|
488
|
+
/** "month": the count starts over each billing period. Absent: never. */
|
|
489
|
+
resets?: "month";
|
|
490
|
+
/** Seats: a price per unit, in micros, past `included` units. */
|
|
491
|
+
price?: {
|
|
492
|
+
amountMicros: number;
|
|
493
|
+
included: number;
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* WHY A FEATURE IS NOT AVAILABLE, in the words an app branches on — the same
|
|
498
|
+
* four `soba.users.check` answers with. Declared here, not imported from
|
|
499
|
+
* connect-ui, because connect-ui is private and bundled: a published type
|
|
500
|
+
* that pointed at it would not resolve in an app.
|
|
501
|
+
*/
|
|
502
|
+
export type FeatureDenial = "no_plan" | "plan_ended" | "not_included" | "limit_reached";
|
|
503
|
+
/** What the browser's check says about one feature. */
|
|
504
|
+
export interface FeatureCheck {
|
|
505
|
+
feature: string;
|
|
506
|
+
allowed: boolean;
|
|
507
|
+
/** Null when allowed. */
|
|
508
|
+
reason: FeatureDenial | null;
|
|
509
|
+
/** The plan's number for it: a cap, or null for unlimited. Null for a switch. */
|
|
510
|
+
limit: number | null;
|
|
511
|
+
/** How many are in use now. Zero for a switch. */
|
|
512
|
+
used: number;
|
|
513
|
+
/** What is left under the cap, never below zero. Null when unlimited or a switch. */
|
|
514
|
+
remaining: number | null;
|
|
515
|
+
unlimited: boolean;
|
|
516
|
+
}
|
|
517
|
+
/** One line of a plan's features on its card, and whether the plan has it —
|
|
518
|
+
* a "no" is drawn crossed out. Declared here for the reason `FeatureCheck`
|
|
519
|
+
* is: connect-ui is bundled, and a published type cannot name it. */
|
|
520
|
+
export interface FeatureItem {
|
|
521
|
+
text: string;
|
|
522
|
+
included: boolean;
|
|
523
|
+
}
|
package/dist/useCurrentPlan.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ import type { CurrentPlan, Plan } from "./types.js";
|
|
|
14
14
|
* - `connected_reward: "included"` RAISES the ceiling to
|
|
15
15
|
* `connected_included_units` while a machine is attached, awake or not,
|
|
16
16
|
* because a larger allowance is a promise about the month.
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* - Runs their own AI SERVES never count toward `included_units`: they have
|
|
18
|
+
* their own allowance, `own_included_units` (INT-20). So the number here
|
|
19
|
+
* only rises for runs the app pays for, whatever the reward.
|
|
20
20
|
* - `credit` touches neither: it settles at renewal.
|
|
21
21
|
*
|
|
22
22
|
* Those last three mirror `allowanceFor` in `@soba-platform/core`, which is the
|
|
@@ -94,5 +94,11 @@ export interface CurrentPlanView {
|
|
|
94
94
|
/** One sentence, already chosen from the state above. The thing a billing
|
|
95
95
|
* page has to get right and the reason this hook exists. */
|
|
96
96
|
summary: string;
|
|
97
|
+
/**
|
|
98
|
+
* WHAT YOUR APP ENFORCES (INT-91): feature id to value, as this person
|
|
99
|
+
* bought it — `features.projects`, `features.export`. Soba stops nothing on
|
|
100
|
+
* these; everything above is what it does stop. Empty with no plan.
|
|
101
|
+
*/
|
|
102
|
+
features: Record<string, boolean | number | string | null>;
|
|
97
103
|
}
|
|
98
104
|
export declare function useCurrentPlan(): CurrentPlanView;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FeatureCheck } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* MAY THIS PERSON DO THIS — for drawing the button, not for trusting it.
|
|
4
|
+
*
|
|
5
|
+
* The same rule the server's `soba.users.check` runs, applied in the browser
|
|
6
|
+
* to the plan the provider already holds: the features they bought, how many
|
|
7
|
+
* are in use, and whether a free plan's time is up. So a "New workspace"
|
|
8
|
+
* button can be drawn disabled, with the reason, before anyone presses it.
|
|
9
|
+
*
|
|
10
|
+
* It decides what to SHOW. What actually happens is still the server's call:
|
|
11
|
+
* create the workspace behind `soba.users.check(user, "workspaces", { use: 1 })`
|
|
12
|
+
* and the count and the limit are tested together. After a server-side
|
|
13
|
+
* `check` with `use` or a `track`, call `refresh()` for the new count.
|
|
14
|
+
*/
|
|
15
|
+
export interface FeatureView extends FeatureCheck {
|
|
16
|
+
/** The feature's name on the plan ("Workspaces"), or its id. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** What it counts ("workspaces"), for "3 of 3 workspaces". */
|
|
19
|
+
unit: string | null;
|
|
20
|
+
loading: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
/** Reload the plan and the counts. */
|
|
23
|
+
refresh(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare function useFeature(feature: string, options?: {
|
|
26
|
+
use?: number;
|
|
27
|
+
}): FeatureView;
|