paseo-smart-session 3.3.0 → 3.4.0
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/README.md +23 -7
- package/client/display.tsx +2 -2
- package/client/kit.tsx +2 -2
- package/client/live.ts +8 -1
- package/client/overview.tsx +59 -18
- package/client/popover.tsx +26 -7
- package/client/settings.tsx +3 -3
- package/client/subscriptions-view.tsx +201 -0
- package/client/subscriptions.ts +68 -0
- package/docs/media/before-after.png +0 -0
- package/docs/media/smart-session-mobile.png +0 -0
- package/docs/media/smart-session-pill.png +0 -0
- package/docs/media/smart-session-settings.png +0 -0
- package/docs/media/smart-session-subscriptions-light.png +0 -0
- package/docs/media/smart-session-subscriptions.png +0 -0
- package/docs/session-guard.md +2 -2
- package/index.client.tsx +4 -0
- package/index.server.ts +13 -0
- package/mcp.mjs +22 -0
- package/package.json +1 -1
- package/paseo-plugin.json +1 -0
- package/server/recorder.ts +12 -0
- package/server/store.ts +24 -1
- package/server/subscriptions.ts +208 -0
- package/shared/guard/zerosub.ts +18 -3
- package/shared/smart-session.ts +66 -0
- package/shared/subscriptions.ts +540 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every ZeroSub subscription's history and status, read-only.
|
|
3
|
+
*
|
|
4
|
+
* The recorder already keeps the CLI login's plan usage (`usage-*.jsonl`). Under
|
|
5
|
+
* ZeroSub the other accounts' readings exist only as the latest copy in ZeroSub's own
|
|
6
|
+
* `usage.json`, overwritten in place. So each new one is kept here in
|
|
7
|
+
* `accounts-*.jsonl`, keyed by ZeroSub's account id (never an email), which gives
|
|
8
|
+
* every account a pace and a forecast. ZeroSub's files are only read: nothing here
|
|
9
|
+
* writes them, opens an account home, or asks ZeroSub for a refresh, a switch or a
|
|
10
|
+
* reset.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readAgents, withDaemon } from "./daemon.ts";
|
|
14
|
+
import { readZeroSub } from "./guard/zerosub.ts";
|
|
15
|
+
import { appendAccount, readAccounts, readUsage, type AccountSample } from "./store.ts";
|
|
16
|
+
import { burnRatePctPerHour, segmentBlocks, thinPoints } from "../shared/blocks.ts";
|
|
17
|
+
import { accountOf, outUntil, type ZeroSubAccount, type ZeroSubReading, type ZeroSubSnapshot, type ZeroSubWindow } from "../shared/guard/zerosub.ts";
|
|
18
|
+
import { STALE_AFTER_MS, poolForecast, subscriptionName, type Subscription, type SubscriptionWindow } from "../shared/subscriptions.ts";
|
|
19
|
+
import { canonicalWindowId, effectiveAt, type UsageSample } from "../shared/usage.ts";
|
|
20
|
+
|
|
21
|
+
const DAY = 24 * 60 * 60_000;
|
|
22
|
+
/** Enough history for an open week and its pace. */
|
|
23
|
+
const HISTORY_MS = 8 * DAY;
|
|
24
|
+
/** The CLI's own Claude login: the recorder measures it, and more often than ZeroSub. */
|
|
25
|
+
const RECORDED_ACCOUNT = "claude-main";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A ZeroSub window under Smart Session's ids: `five_hour` and `seven_day` for both
|
|
29
|
+
* families (Codex's are told apart by length), so one set of rules reads both.
|
|
30
|
+
*/
|
|
31
|
+
export function canonicalAccountWindow(family: "claude" | "codex", window: ZeroSubWindow): string {
|
|
32
|
+
if (family === "claude") return canonicalWindowId(window.id);
|
|
33
|
+
if (window.minutes !== null) return window.minutes <= 6 * 60 ? "five_hour" : window.minutes >= 6 * DAY / 60_000 ? "seven_day" : window.id;
|
|
34
|
+
return window.id === "primary" ? "five_hour" : window.id === "secondary" ? "seven_day" : window.id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** ZeroSub's reading as an account sample, with the window ids Smart Session uses. */
|
|
38
|
+
export function accountSample(account: ZeroSubAccount, reading: ZeroSubReading, at: string): AccountSample {
|
|
39
|
+
const windows: Record<string, { pct: number; resetsAt: string | null }> = {};
|
|
40
|
+
for (const window of reading.windows) windows[canonicalAccountWindow(account.family, window)] = { pct: window.usedPct, resetsAt: window.resetsAt };
|
|
41
|
+
return { at, fetchedAt: reading.fetchedAt, src: "zerosub", account: null, accountId: account.id, family: account.family, windows, credits: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The newest reading already kept per account; filled from the log on first use. */
|
|
45
|
+
let lastKept: Map<string, string> | null = null;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Keeps every new ZeroSub reading. Runs on the recorder's tick; costs three small file
|
|
49
|
+
* reads when nothing changed and nothing at all without ZeroSub.
|
|
50
|
+
*/
|
|
51
|
+
export async function recordAccounts(now = Date.now()): Promise<number> {
|
|
52
|
+
const snapshot = await readZeroSub();
|
|
53
|
+
if (snapshot === null) return 0;
|
|
54
|
+
if (lastKept === null) {
|
|
55
|
+
lastKept = new Map();
|
|
56
|
+
for (const row of await readAccounts({ sinceMs: now - HISTORY_MS })) {
|
|
57
|
+
const previous = lastKept.get(row.accountId);
|
|
58
|
+
if (row.fetchedAt && (!previous || Date.parse(row.fetchedAt) > Date.parse(previous))) lastKept.set(row.accountId, row.fetchedAt);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let kept = 0;
|
|
62
|
+
for (const account of snapshot.accounts) {
|
|
63
|
+
if (account.id === RECORDED_ACCOUNT) continue;
|
|
64
|
+
const reading = snapshot.readings[account.id];
|
|
65
|
+
if (!reading || reading.windows.length === 0) continue;
|
|
66
|
+
const previous = lastKept.get(account.id);
|
|
67
|
+
if (previous && Date.parse(reading.fetchedAt) <= Date.parse(previous)) continue;
|
|
68
|
+
await appendAccount(accountSample(account, reading, new Date(now).toISOString()));
|
|
69
|
+
lastKept.set(account.id, reading.fetchedAt);
|
|
70
|
+
kept += 1;
|
|
71
|
+
}
|
|
72
|
+
return kept;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function forgetAccounts(): void {
|
|
76
|
+
lastKept = null;
|
|
77
|
+
cached = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** One account's windows from its history, with the live reading as the newest point. */
|
|
81
|
+
export function windowsFrom(samples: readonly UsageSample[], readAt: string | null, now: number): { windows: SubscriptionWindow[]; history: { t: string; pct: number }[] } {
|
|
82
|
+
const open = new Map<string, ReturnType<typeof segmentBlocks>[number]>();
|
|
83
|
+
for (const block of segmentBlocks(samples)) open.set(block.windowId, block);
|
|
84
|
+
const fresh = readAt !== null && now - Date.parse(readAt) <= STALE_AFTER_MS;
|
|
85
|
+
const windows = [...open.values()].map((block) => ({
|
|
86
|
+
id: block.windowId,
|
|
87
|
+
pct: block.finalPct,
|
|
88
|
+
resetsAt: block.resetsAt,
|
|
89
|
+
// A rate measured hours ago is not the pace now.
|
|
90
|
+
burnPctPerHour: fresh ? burnRatePctPerHour(block) : null,
|
|
91
|
+
}));
|
|
92
|
+
const session = open.get("five_hour");
|
|
93
|
+
return { windows, history: session ? thinPoints(session.points, 48) : [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface SubscriptionsView {
|
|
97
|
+
readonly zerosub: boolean;
|
|
98
|
+
readonly autoSwitch: boolean;
|
|
99
|
+
readonly accounts: Subscription[];
|
|
100
|
+
/** Which account each live agent runs on. */
|
|
101
|
+
readonly agents: Record<string, string>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const NONE: SubscriptionsView = { zerosub: false, autoSwitch: false, accounts: [], agents: {} };
|
|
105
|
+
|
|
106
|
+
export function buildView(
|
|
107
|
+
snapshot: ZeroSubSnapshot,
|
|
108
|
+
input: { recorded: readonly UsageSample[]; accountRows: readonly AccountSample[]; liveAgents: readonly { id: string; provider: string }[] },
|
|
109
|
+
now: number,
|
|
110
|
+
): SubscriptionsView {
|
|
111
|
+
const agents: Record<string, string> = {};
|
|
112
|
+
const counts = new Map<string, number>();
|
|
113
|
+
for (const agent of input.liveAgents) {
|
|
114
|
+
const account = accountOf(snapshot, agent.id, agent.provider);
|
|
115
|
+
if (!account) continue;
|
|
116
|
+
agents[agent.id] = account.id;
|
|
117
|
+
counts.set(account.id, (counts.get(account.id) ?? 0) + 1);
|
|
118
|
+
}
|
|
119
|
+
const defaults = (family: "claude" | "codex") => {
|
|
120
|
+
const chosen = snapshot.defaults[family];
|
|
121
|
+
const ofFamily = snapshot.accounts.filter((a) => a.family === family);
|
|
122
|
+
return ofFamily.find((a) => a.id === chosen)?.id ?? ofFamily.find((a) => a.kind === "main")?.id ?? ofFamily[0]?.id ?? null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const accounts = snapshot.accounts.map((account): Subscription => {
|
|
126
|
+
const reading = snapshot.readings[account.id];
|
|
127
|
+
const own = input.accountRows.filter((row) => row.accountId === account.id) as unknown as UsageSample[];
|
|
128
|
+
const live = reading ? [accountSample(account, reading, new Date(now).toISOString()) as unknown as UsageSample] : [];
|
|
129
|
+
// The CLI login's windows come from the recorder when it has them: fresher, and with history.
|
|
130
|
+
const recorded = account.id === RECORDED_ACCOUNT && input.recorded.length > 0;
|
|
131
|
+
const samples = recorded ? input.recorded : [...own, ...live];
|
|
132
|
+
const readAt = recorded ? effectiveAt(input.recorded[input.recorded.length - 1]!) : reading?.fetchedAt ?? null;
|
|
133
|
+
const { windows, history } = windowsFrom(samples, readAt, now);
|
|
134
|
+
const { name, named } = subscriptionName(account, snapshot.accounts);
|
|
135
|
+
return {
|
|
136
|
+
id: account.id,
|
|
137
|
+
family: account.family,
|
|
138
|
+
kind: account.kind,
|
|
139
|
+
name,
|
|
140
|
+
named,
|
|
141
|
+
isDefault: defaults(account.family) === account.id,
|
|
142
|
+
signedIn: account.signedIn,
|
|
143
|
+
disabled: account.disabled,
|
|
144
|
+
limitedUntil: account.limitedUntil,
|
|
145
|
+
outUntil: (() => {
|
|
146
|
+
// The guard's rule for "out", over the windows shown here.
|
|
147
|
+
const out = outUntil({ providerId: account.family, status: windows.length > 0 ? "available" : "unavailable", fetchedAt: readAt, windows: windows.map((w) => ({ id: w.id, usedPct: w.pct, remainingPct: null, resetsAt: w.resetsAt })) }, account, now);
|
|
148
|
+
return out === null ? null : new Date(out).toISOString();
|
|
149
|
+
})(),
|
|
150
|
+
createdAt: account.createdAt,
|
|
151
|
+
agents: counts.get(account.id) ?? 0,
|
|
152
|
+
readAt,
|
|
153
|
+
windows,
|
|
154
|
+
credits: reading?.credits ?? null,
|
|
155
|
+
history,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
return { zerosub: true, autoSwitch: snapshot.autoSwitch, accounts, agents };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Several clients poll this; the answer is good for a few seconds. */
|
|
162
|
+
let cached: { at: number; view: SubscriptionsView } | null = null;
|
|
163
|
+
const CACHE_MS = 10_000;
|
|
164
|
+
|
|
165
|
+
export async function subscriptionsView(now = Date.now()): Promise<SubscriptionsView> {
|
|
166
|
+
if (cached && now - cached.at < CACHE_MS) return cached.view;
|
|
167
|
+
const snapshot = await readZeroSub();
|
|
168
|
+
if (snapshot === null) {
|
|
169
|
+
cached = { at: now, view: NONE };
|
|
170
|
+
return NONE;
|
|
171
|
+
}
|
|
172
|
+
const since = now - HISTORY_MS;
|
|
173
|
+
const [recorded, accountRows, liveAgents] = await Promise.all([
|
|
174
|
+
readUsage({ sinceMs: since }),
|
|
175
|
+
readAccounts({ sinceMs: since }),
|
|
176
|
+
withDaemon((client) => readAgents(client)).then((rows) => rows.filter((row) => row.status !== "closed")).catch(() => []),
|
|
177
|
+
]);
|
|
178
|
+
const view = buildView(snapshot, { recorded, accountRows, liveAgents }, now);
|
|
179
|
+
cached = { at: now, view };
|
|
180
|
+
return view;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* What `budget_status` adds for an agent under ZeroSub when its provider has several
|
|
185
|
+
* subscriptions: the pool, by name. Null without ZeroSub or with one account.
|
|
186
|
+
*/
|
|
187
|
+
export async function budgetPool(agentId: string | undefined, now = Date.now()) {
|
|
188
|
+
const view = await subscriptionsView(now);
|
|
189
|
+
if (!view.zerosub) return null;
|
|
190
|
+
const own = agentId === undefined ? undefined : view.accounts.find((a) => a.id === view.agents[agentId]);
|
|
191
|
+
const family = own?.family ?? "claude";
|
|
192
|
+
const mine = view.accounts.filter((a) => a.family === family);
|
|
193
|
+
if (mine.length < 2) return null;
|
|
194
|
+
const pool = poolForecast(view.accounts, family, now, { autoSwitch: view.autoSwitch });
|
|
195
|
+
const name = (id: string | null) => mine.find((a) => a.id === id)?.name ?? "another account";
|
|
196
|
+
const at = (ms: number) => new Date(ms).toISOString();
|
|
197
|
+
const next = pool.takeovers[0];
|
|
198
|
+
return {
|
|
199
|
+
family,
|
|
200
|
+
accounts: pool.total,
|
|
201
|
+
withRoom: pool.withRoom,
|
|
202
|
+
pacePctPerHour: pool.pace === null ? null : Math.round(pool.pace * 10) / 10,
|
|
203
|
+
runsOutAt: pool.runsOutAt === null ? null : at(pool.runsOutAt),
|
|
204
|
+
next: next ? { name: name(next.accountId), at: at(next.at) } : null,
|
|
205
|
+
back: pool.backAt === null ? null : { name: name(pool.backAccount), at: at(pool.backAt) },
|
|
206
|
+
staleReadings: pool.stale,
|
|
207
|
+
};
|
|
208
|
+
}
|
package/shared/guard/zerosub.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface ZeroSubAccount {
|
|
|
30
30
|
readonly disabled: boolean;
|
|
31
31
|
readonly limitedUntil: string | null;
|
|
32
32
|
readonly limitKind: "window" | "budget" | null;
|
|
33
|
+
/** When it was added; ZeroSub's last tie-break between accounts with equal room. */
|
|
34
|
+
readonly createdAt: string | null;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface ZeroSubWindow {
|
|
@@ -43,6 +45,8 @@ export interface ZeroSubReading {
|
|
|
43
45
|
readonly fetchedAt: string;
|
|
44
46
|
readonly cached: boolean;
|
|
45
47
|
readonly windows: readonly ZeroSubWindow[];
|
|
48
|
+
/** Prepaid credits past full windows (ChatGPT credits for Codex), when reported. */
|
|
49
|
+
readonly credits: { readonly balance: number | null; readonly unlimited: boolean } | null;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export interface ZeroSubSnapshot {
|
|
@@ -54,6 +58,8 @@ export interface ZeroSubSnapshot {
|
|
|
54
58
|
readonly readings: Readonly<Record<string, ZeroSubReading>>;
|
|
55
59
|
/** ZeroSub's "Automatic switching" preference. */
|
|
56
60
|
readonly autoSwitch: boolean;
|
|
61
|
+
/** The account each family opens new sessions on, when the person chose one. */
|
|
62
|
+
readonly defaults: Readonly<Record<ZeroSubFamily, string | null>>;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
/** ZeroSub's ids for the CLI's own login (`server/state.ts` `MAIN_ACCOUNT_ID`). */
|
|
@@ -89,6 +95,7 @@ function parseAccount(value: unknown): ZeroSubAccount | null {
|
|
|
89
95
|
disabled: raw.disabled === true,
|
|
90
96
|
limitedUntil: text(raw.limitedUntil),
|
|
91
97
|
limitKind: raw.limitKind === "window" || raw.limitKind === "budget" ? raw.limitKind : null,
|
|
98
|
+
createdAt: text(raw.createdAt),
|
|
92
99
|
};
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -104,7 +111,13 @@ function parseReading(value: unknown): ZeroSubReading | null {
|
|
|
104
111
|
if (!window || !id || usedPct === null) continue;
|
|
105
112
|
windows.push({ id, usedPct, resetsAt: text(window.resetsAt), minutes: finite(window.minutes) });
|
|
106
113
|
}
|
|
107
|
-
|
|
114
|
+
const credits = record(raw.credits);
|
|
115
|
+
return {
|
|
116
|
+
fetchedAt,
|
|
117
|
+
cached: raw.cached === true,
|
|
118
|
+
windows,
|
|
119
|
+
credits: credits ? { balance: finite(credits.balance), unlimited: credits.unlimited === true } : null,
|
|
120
|
+
};
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
/**
|
|
@@ -141,7 +154,9 @@ export function parseZeroSub(state: unknown, usage: unknown, preferences: unknow
|
|
|
141
154
|
|
|
142
155
|
// Paseo stores plugin settings as `{ version, values }`; missing means ZeroSub's default (on).
|
|
143
156
|
const values = record(record(preferences)?.values);
|
|
144
|
-
|
|
157
|
+
const chosen = record(raw.defaults);
|
|
158
|
+
const defaults = { claude: text(chosen?.claude), codex: text(chosen?.codex) };
|
|
159
|
+
return { accounts, sessions, continuedElsewhere, readings, autoSwitch: values?.autoSwitch !== false, defaults };
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
/** The ZeroSub family a Paseo provider belongs to, if ZeroSub manages it. */
|
|
@@ -199,7 +214,7 @@ const pctOf = (window: UsageWindow): number | null =>
|
|
|
199
214
|
* window: the session window the guard watches can show room while the account is
|
|
200
215
|
* out for days. A recorded budget limit counts too: the account takes no turn.
|
|
201
216
|
*/
|
|
202
|
-
function outUntil(row: ProviderUsage, account: ZeroSubAccount, now: number): number | null {
|
|
217
|
+
export function outUntil(row: ProviderUsage, account: ZeroSubAccount, now: number): number | null {
|
|
203
218
|
const ends: number[] = [];
|
|
204
219
|
const until = time(account.limitedUntil);
|
|
205
220
|
if (until !== null && until > now) ends.push(until);
|
package/shared/smart-session.ts
CHANGED
|
@@ -66,6 +66,27 @@ export const budgetStatus = defineRpc({
|
|
|
66
66
|
readAt: z.string().nullable(),
|
|
67
67
|
})
|
|
68
68
|
.optional(),
|
|
69
|
+
/**
|
|
70
|
+
* All of the agent's provider's ZeroSub subscriptions together, when there are
|
|
71
|
+
* several: how many have room, how long they last at this pace, and which takes
|
|
72
|
+
* over next. Names only, never emails. Absent without ZeroSub.
|
|
73
|
+
*/
|
|
74
|
+
pool: z
|
|
75
|
+
.object({
|
|
76
|
+
family: z.enum(["claude", "codex"]),
|
|
77
|
+
accounts: z.number(),
|
|
78
|
+
withRoom: z.number(),
|
|
79
|
+
/** The pool's pace in session-window points per hour; null when nothing is in use. */
|
|
80
|
+
pacePctPerHour: z.number().nullable(),
|
|
81
|
+
/** When no account has room at this pace; null when it lasts the forecast's day. */
|
|
82
|
+
runsOutAt: z.string().nullable(),
|
|
83
|
+
next: z.object({ name: z.string(), at: z.string() }).nullable(),
|
|
84
|
+
/** After it runs dry, the first account back. */
|
|
85
|
+
back: z.object({ name: z.string(), at: z.string() }).nullable(),
|
|
86
|
+
/** Accounts whose reading is over 30 minutes old. */
|
|
87
|
+
staleReadings: z.number(),
|
|
88
|
+
})
|
|
89
|
+
.optional(),
|
|
69
90
|
/** Finished session windows from the last 14 days, oldest first. */
|
|
70
91
|
past: z.array(PastWindowSchema).optional(),
|
|
71
92
|
recorder: RecorderStatusSchema,
|
|
@@ -73,6 +94,51 @@ export const budgetStatus = defineRpc({
|
|
|
73
94
|
}),
|
|
74
95
|
});
|
|
75
96
|
|
|
97
|
+
const SubscriptionWindowSchema = z.object({
|
|
98
|
+
id: z.string(),
|
|
99
|
+
pct: z.number(),
|
|
100
|
+
resetsAt: z.string().nullable(),
|
|
101
|
+
burnPctPerHour: z.number().nullable(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/** One ZeroSub account. Names are the person's own or neutral ("CLI login", "Account 2"); never an email. */
|
|
105
|
+
export const SubscriptionSchema = z.object({
|
|
106
|
+
id: z.string(),
|
|
107
|
+
family: z.enum(["claude", "codex"]),
|
|
108
|
+
kind: z.enum(["main", "managed"]),
|
|
109
|
+
name: z.string(),
|
|
110
|
+
named: z.boolean(),
|
|
111
|
+
isDefault: z.boolean(),
|
|
112
|
+
signedIn: z.boolean(),
|
|
113
|
+
disabled: z.boolean(),
|
|
114
|
+
limitedUntil: z.string().nullable(),
|
|
115
|
+
/** When the account has room again (the guard's own rule); null while it has room. */
|
|
116
|
+
outUntil: z.string().nullable().optional(),
|
|
117
|
+
createdAt: z.string().nullable(),
|
|
118
|
+
agents: z.number(),
|
|
119
|
+
readAt: z.string().nullable(),
|
|
120
|
+
windows: z.array(SubscriptionWindowSchema),
|
|
121
|
+
credits: z.object({ balance: z.number().nullable(), unlimited: z.boolean() }).nullable(),
|
|
122
|
+
history: z.array(z.object({ t: z.string(), pct: z.number() })).optional(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Every ZeroSub subscription on this host, with its windows and pace. `zerosub` is
|
|
127
|
+
* false (and the lists empty) when ZeroSub is not enabled: then nothing changes.
|
|
128
|
+
*/
|
|
129
|
+
export const subscriptionsStatus = defineRpc({
|
|
130
|
+
name: "smart-session.subscriptions",
|
|
131
|
+
input: z.object({}),
|
|
132
|
+
output: z.object({
|
|
133
|
+
zerosub: z.boolean(),
|
|
134
|
+
autoSwitch: z.boolean(),
|
|
135
|
+
accounts: z.array(SubscriptionSchema),
|
|
136
|
+
/** Which account each live session runs on, by agent id. */
|
|
137
|
+
agents: z.record(z.string(), z.string()),
|
|
138
|
+
error: z.string().nullable(),
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
|
|
76
142
|
export const ContextStatusSchema = z.object({
|
|
77
143
|
agentId: z.string(),
|
|
78
144
|
title: z.string().nullable(),
|