@jameslovespancakes/pi-plus 1.0.11 → 1.0.12
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 +44 -20
- package/config/skills/workflow-code-review-actions/SKILL.md +44 -0
- package/package.json +6 -3
- package/src/core/accounts/oauth-pool.ts +102 -0
- package/src/core/accounts/registry.ts +8 -25
- package/src/core/accounts/routing.ts +122 -0
- package/src/core/anthropic/client-identity.ts +9 -64
- package/src/core/anthropic/quota.ts +8 -53
- package/src/core/anthropic/routing.ts +40 -115
- package/src/core/anthropic/store.ts +7 -9
- package/src/core/catalog/quality.ts +33 -15
- package/src/core/codex/quota.ts +8 -2
- package/src/core/codex/store.ts +12 -9
- package/src/core/config.ts +4 -17
- package/src/core/store.ts +13 -12
- package/src/domains/models/provider-picker.ts +1 -1
- package/src/domains/setup/index.ts +23 -23
- package/src/domains/subscriptions/accounts-picker.ts +18 -37
- package/src/domains/subscriptions/accounts.ts +23 -29
- package/src/domains/subscriptions/footer.ts +48 -29
- package/src/domains/subscriptions/index.ts +11 -27
- package/src/domains/subscriptions/provider.ts +38 -103
- package/src/domains/subscriptions/providers/anthropic.ts +5 -17
- package/src/domains/subscriptions/providers/codex.ts +171 -4
- package/src/domains/subscriptions/providers/hosted.ts +18 -0
- package/src/domains/subscriptions/providers/oauth-pool.ts +273 -0
- package/src/domains/subscriptions/routing.ts +19 -11
- package/src/domains/workflows/LICENSE.md +21 -0
- package/src/domains/workflows/index.ts +836 -0
- package/src/domains/workflows/runtime/advisory-challenge.ts +75 -0
- package/src/domains/workflows/runtime/advisory-evidence.ts +90 -0
- package/src/domains/workflows/runtime/advisory-schema.ts +83 -0
- package/src/domains/workflows/runtime/agent-attempt.ts +147 -0
- package/src/domains/workflows/runtime/agent-limits.ts +43 -0
- package/src/domains/workflows/runtime/agent-replay.ts +399 -0
- package/src/domains/workflows/runtime/agent-retry.ts +116 -0
- package/src/domains/workflows/runtime/agent-runner-types.ts +96 -0
- package/src/domains/workflows/runtime/agent-runner.ts +203 -0
- package/src/domains/workflows/runtime/agent-session-identity.ts +256 -0
- package/src/domains/workflows/runtime/agent-session-providers.ts +50 -0
- package/src/domains/workflows/runtime/agent-session.ts +382 -0
- package/src/domains/workflows/runtime/agent-skills.ts +270 -0
- package/src/domains/workflows/runtime/agent-workspace.ts +103 -0
- package/src/domains/workflows/runtime/background-workflow-tool.ts +75 -0
- package/src/domains/workflows/runtime/background-workflows.ts +492 -0
- package/src/domains/workflows/runtime/budget.ts +53 -0
- package/src/domains/workflows/runtime/cancellation.ts +87 -0
- package/src/domains/workflows/runtime/command-completions.ts +36 -0
- package/src/domains/workflows/runtime/concurrency.ts +403 -0
- package/src/domains/workflows/runtime/debug.ts +3 -0
- package/src/domains/workflows/runtime/diff-capture.ts +81 -0
- package/src/domains/workflows/runtime/discovery.ts +137 -0
- package/src/domains/workflows/runtime/dynamax-shortcuts.ts +122 -0
- package/src/domains/workflows/runtime/dynamax.ts +330 -0
- package/src/domains/workflows/runtime/engine.ts +543 -0
- package/src/domains/workflows/runtime/filesystem-error.ts +4 -0
- package/src/domains/workflows/runtime/finalizers.ts +66 -0
- package/src/domains/workflows/runtime/identity-canonicalization.ts +138 -0
- package/src/domains/workflows/runtime/identity-fingerprint.ts +15 -0
- package/src/domains/workflows/runtime/inline-workflow.ts +403 -0
- package/src/domains/workflows/runtime/journal.ts +313 -0
- package/src/domains/workflows/runtime/model-profiles.ts +310 -0
- package/src/domains/workflows/runtime/options.ts +157 -0
- package/src/domains/workflows/runtime/perf.ts +146 -0
- package/src/domains/workflows/runtime/pi-compat.ts +32 -0
- package/src/domains/workflows/runtime/process-runner.ts +260 -0
- package/src/domains/workflows/runtime/progress-types.ts +59 -0
- package/src/domains/workflows/runtime/progress.ts +400 -0
- package/src/domains/workflows/runtime/provider-usage-limit.ts +191 -0
- package/src/domains/workflows/runtime/replay-path-identity.ts +48 -0
- package/src/domains/workflows/runtime/research-contract.ts +89 -0
- package/src/domains/workflows/runtime/research-evidence.ts +289 -0
- package/src/domains/workflows/runtime/resume-context.ts +751 -0
- package/src/domains/workflows/runtime/review/code-review-orchestration.ts +24 -0
- package/src/domains/workflows/runtime/review/github-pr-comments.ts +249 -0
- package/src/domains/workflows/runtime/review/patch-validation.ts +120 -0
- package/src/domains/workflows/runtime/review/review-actions.ts +180 -0
- package/src/domains/workflows/runtime/review/review-budget.ts +45 -0
- package/src/domains/workflows/runtime/review/review-fix-workflow.ts +153 -0
- package/src/domains/workflows/runtime/review/review-format.ts +133 -0
- package/src/domains/workflows/runtime/review/review-handoff.ts +38 -0
- package/src/domains/workflows/runtime/review/review-issues.ts +87 -0
- package/src/domains/workflows/runtime/review/review-report.ts +42 -0
- package/src/domains/workflows/runtime/review/review-results-flow.ts +52 -0
- package/src/domains/workflows/runtime/review/review-results-viewer.ts +312 -0
- package/src/domains/workflows/runtime/review/review-session-coordinator.ts +153 -0
- package/src/domains/workflows/runtime/review/review-snapshot.ts +294 -0
- package/src/domains/workflows/runtime/review-diff-target.ts +130 -0
- package/src/domains/workflows/runtime/session-identity.ts +6 -0
- package/src/domains/workflows/runtime/structured-output.ts +32 -0
- package/src/domains/workflows/runtime/tool-capabilities.ts +44 -0
- package/src/domains/workflows/runtime/tool-source-identity.ts +150 -0
- package/src/domains/workflows/runtime/tree-fingerprint.ts +404 -0
- package/src/domains/workflows/runtime/types.ts +255 -0
- package/src/domains/workflows/runtime/ui/display-text.ts +13 -0
- package/src/domains/workflows/runtime/ui/dynamax-editor-decoration.ts +220 -0
- package/src/domains/workflows/runtime/ui/workflow-format.ts +165 -0
- package/src/domains/workflows/runtime/ui/workflow-inspector.ts +398 -0
- package/src/domains/workflows/runtime/ui/workflow-result-renderer.ts +171 -0
- package/src/domains/workflows/runtime/ui/workflow-viewer-layout.ts +51 -0
- package/src/domains/workflows/runtime/ui/workflow-widget.ts +78 -0
- package/src/domains/workflows/runtime/unknown-error.ts +8 -0
- package/src/domains/workflows/runtime/usage.ts +341 -0
- package/src/domains/workflows/runtime/workflow-advisory-utils.ts +245 -0
- package/src/domains/workflows/runtime/workflow-execution.ts +121 -0
- package/src/domains/workflows/runtime/workflow-module.ts +75 -0
- package/src/domains/workflows/runtime/workflow-run-background.ts +72 -0
- package/src/domains/workflows/runtime/workflow-run-controller.ts +342 -0
- package/src/domains/workflows/runtime/workflow-run-history.ts +162 -0
- package/src/domains/workflows/runtime/workflow-run-record.ts +635 -0
- package/src/domains/workflows/runtime/workflow-run-store.ts +176 -0
- package/src/domains/workflows/runtime/workflow-usage-limit-scheduler.ts +80 -0
- package/src/domains/workflows/runtime/workflows.ts +40 -0
- package/src/domains/workflows/runtime/worktree.ts +581 -0
- package/src/domains/workflows/workflows/code-review.ts +232 -0
- package/src/domains/workflows/workflows/diagnose.ts +154 -0
- package/src/domains/workflows/workflows/perf-review.ts +149 -0
- package/src/domains/workflows/workflows/refactor-scout.ts +143 -0
- package/src/domains/workflows/workflows/research.ts +169 -0
- package/src/services/usage-service.ts +6 -30
|
@@ -35,37 +35,37 @@ async function inspect(ctx: any): Promise<Feature[]> {
|
|
|
35
35
|
|
|
36
36
|
/* subscriptions */
|
|
37
37
|
const providers = accountProviders();
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let primary = false;
|
|
38
|
+
const accountStates = await Promise.all(providers.map(async (provider) => {
|
|
39
|
+
const primaryAuth = Promise.resolve()
|
|
40
|
+
.then(() => ctx.modelRegistry.getProviderAuth(provider.id))
|
|
41
|
+
.then((result: any) => !!result?.auth?.apiKey)
|
|
42
|
+
.catch(() => false);
|
|
44
43
|
try {
|
|
45
|
-
primary =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
44
|
+
const [primary, accounts, routing] = await Promise.all([
|
|
45
|
+
primaryAuth,
|
|
46
|
+
provider.list(),
|
|
47
|
+
provider.routing?.get(),
|
|
48
|
+
]);
|
|
49
|
+
const signedIn = primary || accounts.length > 0;
|
|
51
50
|
const pooled = accounts.length + (primary ? 1 : 0);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
? `${provider.id}: ${pooled} account(s), routing=${routing}`
|
|
51
|
+
return {
|
|
52
|
+
signedIn,
|
|
53
|
+
summary: signedIn
|
|
54
|
+
? `${provider.id}: ${pooled} account(s), routing=${routing ?? "n/a"}`
|
|
56
55
|
: `${provider.id}: not signed in`,
|
|
57
|
-
|
|
56
|
+
};
|
|
58
57
|
} catch {
|
|
59
|
-
|
|
58
|
+
return { signedIn: await primaryAuth, summary: `${provider.id}: unreadable` };
|
|
60
59
|
}
|
|
61
|
-
}
|
|
60
|
+
}));
|
|
61
|
+
const anySignedIn = accountStates.some((state) => state.signedIn);
|
|
62
62
|
features.push({
|
|
63
63
|
name: "Subscriptions",
|
|
64
64
|
ready: anySignedIn,
|
|
65
|
-
detail:
|
|
66
|
-
commands: ["/
|
|
67
|
-
setup: anySignedIn ? undefined : "/
|
|
68
|
-
open: "/
|
|
65
|
+
detail: accountStates.map((state) => state.summary).join("; ") || "no account providers registered",
|
|
66
|
+
commands: ["/accounts", "/accounts add <provider>", "/routing sequential|quota-aware", "/usage"],
|
|
67
|
+
setup: anySignedIn ? undefined : "/accounts add anthropic",
|
|
68
|
+
open: "/accounts",
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
/* benchmarks */
|
|
@@ -2,14 +2,7 @@ import type { Component } from "@earendil-works/pi-tui";
|
|
|
2
2
|
import { hasTruecolor, levelColor } from "../../ui/format.ts";
|
|
3
3
|
import type { AccountProvider, ManagedAccount } from "../../core/accounts/registry.ts";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* The `/accounts` list.
|
|
7
|
-
*
|
|
8
|
-
* Mirrors the provider picker: an inline SettingsList inside pi's own rule
|
|
9
|
-
* chrome, toggled in place so nothing redraws the screen. Rows are grouped by
|
|
10
|
-
* provider, because an account label alone ("Personal") does not say which
|
|
11
|
-
* subscription it belongs to.
|
|
12
|
-
*/
|
|
5
|
+
/** Inline account picker grouped by provider. */
|
|
13
6
|
|
|
14
7
|
export type AccountState = "enabled" | "disabled";
|
|
15
8
|
|
|
@@ -36,28 +29,25 @@ export interface AccountRow {
|
|
|
36
29
|
detail?: string;
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
/** Flattens
|
|
32
|
+
/** Flattens provider accounts while loading providers in parallel. */
|
|
40
33
|
export async function accountRows(providers: AccountProvider[]): Promise<AccountRow[]> {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
let accounts: ManagedAccount[] = [];
|
|
34
|
+
const groups = await Promise.all(providers.map(async (provider) => {
|
|
35
|
+
let accounts: ManagedAccount[];
|
|
44
36
|
try {
|
|
45
37
|
accounts = await provider.list();
|
|
46
38
|
} catch {
|
|
47
|
-
|
|
39
|
+
return [];
|
|
48
40
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return rows;
|
|
41
|
+
return accounts.map((account) => ({
|
|
42
|
+
id: `${provider.id}:${account.id}`,
|
|
43
|
+
providerId: provider.id,
|
|
44
|
+
providerLabel: provider.label,
|
|
45
|
+
label: account.label,
|
|
46
|
+
state: account.enabled ? "enabled" as const : "disabled" as const,
|
|
47
|
+
detail: account.primary ? "primary" : undefined,
|
|
48
|
+
}));
|
|
49
|
+
}));
|
|
50
|
+
return groups.flat();
|
|
61
51
|
}
|
|
62
52
|
|
|
63
53
|
function labelFor(theme: any, row: AccountRow): string {
|
|
@@ -79,13 +69,7 @@ function framed(theme: any, list: any, title: string): Component {
|
|
|
79
69
|
} as Component;
|
|
80
70
|
}
|
|
81
71
|
|
|
82
|
-
/**
|
|
83
|
-
* What closing the picker asked for.
|
|
84
|
-
*
|
|
85
|
-
* The wizards run AFTER the picker closes rather than inside it, so they can
|
|
86
|
-
* use pi's ordinary select/input prompts instead of being reimplemented as
|
|
87
|
-
* nested TUI components.
|
|
88
|
-
*/
|
|
72
|
+
/** Wizard requested when the picker closes. */
|
|
89
73
|
export type PickerAction = { kind: "add" } | { kind: "rename" } | undefined;
|
|
90
74
|
|
|
91
75
|
/** Action row ids are namespaced so they cannot collide with an account id. */
|
|
@@ -94,8 +78,7 @@ const RENAME_ID = "__action_rename";
|
|
|
94
78
|
|
|
95
79
|
export interface AccountPickerDeps {
|
|
96
80
|
rows: () => Promise<AccountRow[]>;
|
|
97
|
-
/** Applies a toggle
|
|
98
|
-
* label, dot and value all change in one render. */
|
|
81
|
+
/** Applies a toggle synchronously for one-render updates. */
|
|
99
82
|
toggle: (providerId: string, accountId: string) => AccountState;
|
|
100
83
|
}
|
|
101
84
|
|
|
@@ -118,9 +101,7 @@ export async function openAccountsPicker(ctx: any, deps: AccountPickerDeps): Pro
|
|
|
118
101
|
description: row.detail,
|
|
119
102
|
}));
|
|
120
103
|
|
|
121
|
-
//
|
|
122
|
-
// discoverable. A single-entry `values` makes Enter fire onChange without
|
|
123
|
-
// the row appearing to cycle through anything.
|
|
104
|
+
// Single-value action rows trigger onChange without cycling.
|
|
124
105
|
items.push({
|
|
125
106
|
id: ADD_ID,
|
|
126
107
|
label: theme.fg("accent", "+ Add account"),
|
|
@@ -35,8 +35,10 @@ async function openBrowser(pi: ExtensionAPI, url: string): Promise<void> {
|
|
|
35
35
|
function bridge(pi: ExtensionAPI, ctx: any): AccountContext {
|
|
36
36
|
return {
|
|
37
37
|
hasUI: ctx.hasUI,
|
|
38
|
+
signal: ctx.signal,
|
|
38
39
|
ui: {
|
|
39
40
|
input: (title, placeholder) => ctx.ui.input(title, placeholder),
|
|
41
|
+
select: (title, options) => ctx.ui.select(title, options),
|
|
40
42
|
confirm: (title, message) => ctx.ui.confirm(title, message),
|
|
41
43
|
notify: (message, type) => ctx.ui.notify(message, type ?? "info"),
|
|
42
44
|
},
|
|
@@ -58,34 +60,34 @@ async function listAll(ctx: any): Promise<void> {
|
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
const blocks: string[]
|
|
62
|
-
for (const provider of providers) {
|
|
63
|
+
const blocks = await Promise.all(providers.map(async (provider): Promise<string[]> => {
|
|
63
64
|
try {
|
|
64
|
-
const accounts = await
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const [accounts, mode] = await Promise.all([
|
|
66
|
+
provider.list(),
|
|
67
|
+
provider.routing?.get(),
|
|
68
|
+
]);
|
|
69
|
+
const routing = mode ? ` · routing: ${mode}` : "";
|
|
70
|
+
return [
|
|
67
71
|
`${provider.label} (${provider.id})${routing}`,
|
|
68
72
|
...(accounts.length > 0
|
|
69
73
|
? accounts.map((account) => ` ${describe(account)}`)
|
|
70
|
-
: [` no additional accounts, add one with /
|
|
71
|
-
|
|
74
|
+
: [` no additional accounts, add one with /accounts add ${provider.id}`]),
|
|
75
|
+
];
|
|
72
76
|
} catch (error) {
|
|
73
|
-
|
|
77
|
+
return [`${provider.label} (${provider.id}): ${error instanceof Error ? error.message : String(error)}`];
|
|
74
78
|
}
|
|
75
|
-
}
|
|
76
|
-
ctx.ui.notify(blocks.join("\n"), "info");
|
|
79
|
+
}));
|
|
80
|
+
ctx.ui.notify(blocks.flat().join("\n"), "info");
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
83
|
async function pickAccount(ctx: any, provider: AccountProvider): Promise<string | undefined> {
|
|
82
84
|
const accounts = await provider.list();
|
|
83
85
|
if (accounts.length === 0) {
|
|
84
|
-
ctx.ui.notify(`No ${provider.label} accounts yet. Add one with /
|
|
86
|
+
ctx.ui.notify(`No ${provider.label} accounts yet. Add one with /accounts add ${provider.id}.`, "info");
|
|
85
87
|
return undefined;
|
|
86
88
|
}
|
|
87
89
|
if (!ctx.hasUI) {
|
|
88
|
-
ctx.ui.notify(`
|
|
90
|
+
ctx.ui.notify(`Choose an account with /accounts reauth ${provider.id}.`, "warning");
|
|
89
91
|
return undefined;
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -96,19 +98,15 @@ async function pickAccount(ctx: any, provider: AccountProvider): Promise<string
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
99
|
-
/** Toggles one account and returns the state it ended in. */
|
|
100
101
|
const toggle = (providerId: string, accountId: string): AccountState => {
|
|
101
102
|
const provider = accountProvider(providerId);
|
|
102
103
|
if (!provider?.setEnabled) throw new Error(`${providerId} accounts cannot be disabled.`);
|
|
103
104
|
const next = pendingState.get(`${providerId}:${accountId}`) === "enabled" ? "disabled" : "enabled";
|
|
104
105
|
pendingState.set(`${providerId}:${accountId}`, next);
|
|
105
|
-
// Adapters persist asynchronously; the picker needs the answer now, so the
|
|
106
|
-
// write is fired off and failures surface as a notification.
|
|
107
106
|
void provider.setEnabled(accountId, next === "enabled").catch(() => {});
|
|
108
107
|
return next;
|
|
109
108
|
};
|
|
110
109
|
|
|
111
|
-
/** Mirror of on-disk state, so the synchronous toggle can answer instantly. */
|
|
112
110
|
const pendingState = new Map<string, AccountState>();
|
|
113
111
|
|
|
114
112
|
const rows = async () => {
|
|
@@ -118,8 +116,7 @@ export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
|
118
116
|
return list;
|
|
119
117
|
};
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
async function addAccount(ctx: any, providerId?: string): Promise<void> {
|
|
119
|
+
async function addAccount(ctx: any, providerId?: string, requestedLabel?: string): Promise<void> {
|
|
123
120
|
if (!ctx.hasUI) {
|
|
124
121
|
ctx.ui.notify("Adding an account requires interactive Pi mode.", "error");
|
|
125
122
|
return;
|
|
@@ -144,7 +141,8 @@ export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
|
144
141
|
}
|
|
145
142
|
if (!provider) return;
|
|
146
143
|
|
|
147
|
-
const label =
|
|
144
|
+
const label = requestedLabel?.trim()
|
|
145
|
+
|| await ctx.ui.input(`${provider.label} account name`, "Work, Personal, etc.");
|
|
148
146
|
if (!label?.trim()) return;
|
|
149
147
|
|
|
150
148
|
try {
|
|
@@ -155,7 +153,6 @@ export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
155
|
|
|
158
|
-
/** Rename wizard: pick an account, type a new name. */
|
|
159
156
|
async function renameAccount(ctx: any): Promise<void> {
|
|
160
157
|
if (!ctx.hasUI) {
|
|
161
158
|
ctx.ui.notify("Renaming an account requires interactive Pi mode.", "error");
|
|
@@ -179,7 +176,6 @@ export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
|
179
176
|
|
|
180
177
|
const provider = accountProvider(row.providerId);
|
|
181
178
|
try {
|
|
182
|
-
// Row ids are "<providerId>:<accountId>"; strip the prefix.
|
|
183
179
|
await provider!.rename!(row.id.slice(row.providerId.length + 1), next.trim());
|
|
184
180
|
ctx.ui.notify(`Renamed to “${next.trim()}”.`, "info");
|
|
185
181
|
} catch (error) {
|
|
@@ -202,15 +198,15 @@ export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
|
202
198
|
.map((id) => ({ value: `${parts[0]} ${id}`, label: id }));
|
|
203
199
|
},
|
|
204
200
|
handler: async (args, ctx) => {
|
|
205
|
-
const [action, providerId] = args.trim().split(/\s+/).filter(Boolean);
|
|
201
|
+
const [action, providerId, ...rest] = args.trim().split(/\s+/).filter(Boolean);
|
|
206
202
|
|
|
207
|
-
if (action === "add") return addAccount(ctx, providerId);
|
|
203
|
+
if (action === "add") return addAccount(ctx, providerId, rest.join(" ") || undefined);
|
|
208
204
|
if (action === "rename") return renameAccount(ctx);
|
|
209
205
|
|
|
210
206
|
if (action === "reauth") {
|
|
211
207
|
const provider = providerId ? accountProvider(providerId) : accountProviders()[0];
|
|
212
208
|
if (!provider) { ctx.ui.notify("No subscription providers are registered.", "error"); return; }
|
|
213
|
-
const target = await pickAccount(ctx, provider);
|
|
209
|
+
const target = rest.join(" ") || await pickAccount(ctx, provider);
|
|
214
210
|
if (!target) return;
|
|
215
211
|
try {
|
|
216
212
|
const done = await provider.reauth(bridge(pi, ctx), target);
|
|
@@ -226,11 +222,9 @@ export function registerAccountCommands(pi: ExtensionAPI): void {
|
|
|
226
222
|
return;
|
|
227
223
|
}
|
|
228
224
|
|
|
229
|
-
// Bare /accounts: the picker interactively, plain text headless.
|
|
230
225
|
if (!ctx.hasUI) return listAll(ctx);
|
|
231
226
|
|
|
232
|
-
//
|
|
233
|
-
// visible immediately. Bounded so a misbehaving action cannot spin.
|
|
227
|
+
// Reopen after each wizard so changes appear immediately.
|
|
234
228
|
for (let step = 0; step < 24; step++) {
|
|
235
229
|
const requested = await openAccountsPicker(ctx, { rows, toggle });
|
|
236
230
|
if (!requested) return;
|
|
@@ -4,20 +4,55 @@ import { refreshUsage, startPolling, stopPolling, subscribe, usageState } from "
|
|
|
4
4
|
import { renderUsageLines, usageSummaryText } from "../../ui/usage-bars.ts";
|
|
5
5
|
import { formatTokens, sanitize } from "../../ui/format.ts";
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Single-line session footer (no working-directory line) with the subscription
|
|
9
|
-
* bars rendered directly underneath it.
|
|
10
|
-
*
|
|
11
|
-
* The footer no longer owns the poll loop; it subscribes to usage-service and
|
|
12
|
-
* re-renders on change. That is what lets non-UI consumers get fresh data.
|
|
13
|
-
*/
|
|
7
|
+
/** Compact session footer with shared subscription usage bars. */
|
|
14
8
|
|
|
15
|
-
const SUBSCRIPTION_PROVIDERS = new Set(["anthropic", "openai-codex", "kimi-coding"]);
|
|
9
|
+
const SUBSCRIPTION_PROVIDERS = new Set(["anthropic", "openai-codex", "kimi-coding", "xai"]);
|
|
10
|
+
|
|
11
|
+
interface SessionTotals {
|
|
12
|
+
input: number;
|
|
13
|
+
output: number;
|
|
14
|
+
cacheRead: number;
|
|
15
|
+
cacheWrite: number;
|
|
16
|
+
cost: number;
|
|
17
|
+
latestHitRate?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function usageFor(entry: any): any {
|
|
21
|
+
if (entry.type === "message") {
|
|
22
|
+
return entry.message.role === "assistant" || entry.message.role === "toolResult" ? entry.message.usage : undefined;
|
|
23
|
+
}
|
|
24
|
+
return entry.type === "branch_summary" || entry.type === "compaction" ? entry.usage : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sumSessionUsage(entries: any[]): SessionTotals {
|
|
28
|
+
const totals: SessionTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const usage = usageFor(entry);
|
|
31
|
+
if (!usage) continue;
|
|
32
|
+
totals.input += usage.input ?? 0;
|
|
33
|
+
totals.output += usage.output ?? 0;
|
|
34
|
+
totals.cacheRead += usage.cacheRead ?? 0;
|
|
35
|
+
totals.cacheWrite += usage.cacheWrite ?? 0;
|
|
36
|
+
totals.cost += usage.cost?.total ?? 0;
|
|
37
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
38
|
+
const prompt = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
39
|
+
totals.latestHitRate = prompt > 0 ? ((usage.cacheRead ?? 0) / prompt) * 100 : undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return totals;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function usageSignature(entries: any[]): string {
|
|
46
|
+
const last = entries.at(-1);
|
|
47
|
+
const usage = last ? usageFor(last) : undefined;
|
|
48
|
+
return [entries.length, last?.id, usage?.input, usage?.output, usage?.cacheRead, usage?.cacheWrite, usage?.cost?.total].join(":");
|
|
49
|
+
}
|
|
16
50
|
|
|
17
51
|
export function registerFooter(pi: ExtensionAPI): void {
|
|
18
52
|
let showUsage = true;
|
|
19
53
|
let requestRender: (() => void) | undefined;
|
|
20
54
|
let unsubscribe: (() => void) | undefined;
|
|
55
|
+
let cachedUsage: { signature: string; totals: SessionTotals } | undefined;
|
|
21
56
|
|
|
22
57
|
const apply = (ctx: any) => {
|
|
23
58
|
if (!ctx.hasUI) return;
|
|
@@ -34,28 +69,12 @@ export function registerFooter(pi: ExtensionAPI): void {
|
|
|
34
69
|
const width = Math.max(1, Math.floor(Number(rawWidth) || 0));
|
|
35
70
|
const lines: string[] = [];
|
|
36
71
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let cost = 0;
|
|
42
|
-
let latestHitRate: number | undefined;
|
|
43
|
-
|
|
44
|
-
for (const entry of ctx.sessionManager.getEntries()) {
|
|
45
|
-
const usage = entry.type === "message"
|
|
46
|
-
? (entry.message.role === "assistant" || entry.message.role === "toolResult" ? entry.message.usage : undefined)
|
|
47
|
-
: (entry.type === "branch_summary" || entry.type === "compaction" ? entry.usage : undefined);
|
|
48
|
-
if (!usage) continue;
|
|
49
|
-
input += usage.input ?? 0;
|
|
50
|
-
output += usage.output ?? 0;
|
|
51
|
-
cacheRead += usage.cacheRead ?? 0;
|
|
52
|
-
cacheWrite += usage.cacheWrite ?? 0;
|
|
53
|
-
cost += usage.cost?.total ?? 0;
|
|
54
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
55
|
-
const prompt = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
56
|
-
latestHitRate = prompt > 0 ? ((usage.cacheRead ?? 0) / prompt) * 100 : undefined;
|
|
57
|
-
}
|
|
72
|
+
const entries = ctx.sessionManager.getEntries();
|
|
73
|
+
const signature = usageSignature(entries);
|
|
74
|
+
if (cachedUsage?.signature !== signature) {
|
|
75
|
+
cachedUsage = { signature, totals: sumSessionUsage(entries) };
|
|
58
76
|
}
|
|
77
|
+
const { input, output, cacheRead, cacheWrite, cost, latestHitRate } = cachedUsage.totals;
|
|
59
78
|
|
|
60
79
|
const parts: string[] = [];
|
|
61
80
|
if (input) parts.push(`↑${formatTokens(input)}`);
|
|
@@ -1,40 +1,24 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { registerAccountProvider } from "../../core/accounts/registry.ts";
|
|
3
3
|
import { anthropicAccounts } from "./providers/anthropic.ts";
|
|
4
|
-
import { codexAccounts } from "./providers/codex.ts";
|
|
4
|
+
import { codexAccounts, registerCodexProvider } from "./providers/codex.ts";
|
|
5
|
+
import { kimiAccounts, KIMI_SPEC, xaiAccounts, XAI_SPEC } from "./providers/hosted.ts";
|
|
6
|
+
import { registerPooledOAuthProvider } from "./providers/oauth-pool.ts";
|
|
5
7
|
import { registerAnthropicProvider } from "./provider.ts";
|
|
6
8
|
import { registerAccountCommands } from "./accounts.ts";
|
|
7
9
|
import { registerRoutingCommands } from "./routing.ts";
|
|
8
10
|
import { registerFooter } from "./footer.ts";
|
|
9
11
|
|
|
10
|
-
/**
|
|
11
|
-
* Subscriptions domain: Claude OAuth accounts, routing mode, and the quota HUD.
|
|
12
|
-
*
|
|
13
|
-
* This replaces the old `claude-multi-account.ts` + `compact-footer.ts` pair.
|
|
14
|
-
* Account and routing commands are provider-agnostic: `/account <provider>` and
|
|
15
|
-
* `/routing` dispatch through core/accounts/registry.ts, so adding a second
|
|
16
|
-
* provider means writing one adapter, not new commands.
|
|
17
|
-
* Authentication and model catalogue are separate concerns here; they were only
|
|
18
|
-
* ever colocated because they shared an OAuth token.
|
|
19
|
-
*
|
|
20
|
-
* Relationship to @cortexkit/pi-anthropic-auth:
|
|
21
|
-
* That package stays installed and keeps ownership of the Anthropic provider,
|
|
22
|
-
* its stream implementation, and its own commands (/claude-account,
|
|
23
|
-
* /claude-routing, /claude-quota, ...). Absorbing it fully was evaluated and
|
|
24
|
-
* rejected, because its `dist/commands.js` exports only `registerCommands(pi)` as a
|
|
25
|
-
* unit, so taking over individual commands would mean copying ~130 lines of
|
|
26
|
-
* provider/model specs that drift on every vendor upgrade. This domain adds
|
|
27
|
-
* the multi-account surface the vendor does not provide, and everything it
|
|
28
|
-
* touches goes through src/vendor/anthropic.ts.
|
|
29
|
-
*/
|
|
12
|
+
/** Registers subscription providers, pooled accounts, routing, and usage UI. */
|
|
30
13
|
export default function subscriptions(pi: ExtensionAPI) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
14
|
+
for (const provider of [anthropicAccounts, codexAccounts, kimiAccounts, xaiAccounts]) {
|
|
15
|
+
registerAccountProvider(provider);
|
|
16
|
+
}
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
registerAnthropicProvider(pi);
|
|
19
|
+
registerCodexProvider(pi);
|
|
20
|
+
registerPooledOAuthProvider(pi, KIMI_SPEC);
|
|
21
|
+
registerPooledOAuthProvider(pi, XAI_SPEC);
|
|
38
22
|
|
|
39
23
|
registerAccountCommands(pi);
|
|
40
24
|
registerRoutingCommands(pi);
|