@adeildo/pi-providers 5.5.0 → 5.6.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/package.json +2 -2
- package/src/accounts/attach.ts +30 -17
- package/src/accounts/lock.ts +1 -1
- package/src/ui/picker.ts +19 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adeildo/pi-providers",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"description": "Provider features for Pi. Bills Anthropic OAuth requests to the Claude Pro or Max plan instead of extra usage.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anthropic",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@adeildo/pi-kit": "5.
|
|
40
|
+
"@adeildo/pi-kit": "5.6.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@earendil-works/pi-ai": "*",
|
package/src/accounts/attach.ts
CHANGED
|
@@ -49,7 +49,7 @@ export function attachProvider(
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function sessionFor(
|
|
52
|
+
export function sessionFor(
|
|
53
53
|
scope: FeatureScope,
|
|
54
54
|
store: AccountStore,
|
|
55
55
|
pins: Pins,
|
|
@@ -57,6 +57,16 @@ function sessionFor(
|
|
|
57
57
|
ctx: ExtensionContext,
|
|
58
58
|
providerId: string,
|
|
59
59
|
): AccountSession {
|
|
60
|
+
// The turn and the tasks' call hit a limit together; they share one dialog and its answer.
|
|
61
|
+
const deciding = new Map<string, Promise<AccountCredential | undefined>>();
|
|
62
|
+
function once(
|
|
63
|
+
key: string,
|
|
64
|
+
decide: () => Promise<AccountCredential | undefined>,
|
|
65
|
+
): Promise<AccountCredential | undefined> {
|
|
66
|
+
const decision = deciding.get(key) ?? decide().finally(() => deciding.delete(key));
|
|
67
|
+
deciding.set(key, decision);
|
|
68
|
+
return decision;
|
|
69
|
+
}
|
|
60
70
|
return {
|
|
61
71
|
resolve: () => requestOf(activeAccount(store, pins, providerId)),
|
|
62
72
|
subscription: () =>
|
|
@@ -73,23 +83,26 @@ function sessionFor(
|
|
|
73
83
|
lockPath: accountsLockPath,
|
|
74
84
|
noteUsage: (id, headers) =>
|
|
75
85
|
noteUsage(watch.usage, providerId, id, watch.quota.fromHeaders(providerId, headers)),
|
|
76
|
-
afterLimit:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
afterLimit: (currentId, detail) =>
|
|
87
|
+
once(`limit:${currentId}`, async () => {
|
|
88
|
+
const reading = await readingFor(watch, store, ctx, providerId, currentId);
|
|
89
|
+
const until = worstWindow(reading)?.window.resetsAt;
|
|
90
|
+
if (until !== undefined) store.markLimited(providerId, currentId, until);
|
|
91
|
+
return await afterLimit(
|
|
92
|
+
scope,
|
|
93
|
+
store,
|
|
94
|
+
pins,
|
|
95
|
+
ctx,
|
|
96
|
+
providerId,
|
|
97
|
+
currentId,
|
|
98
|
+
withReset(detail, reading),
|
|
99
|
+
watch.limited,
|
|
100
|
+
);
|
|
101
|
+
}),
|
|
91
102
|
afterAuthFailure: (currentId, detail) =>
|
|
92
|
-
|
|
103
|
+
once(`auth:${currentId}`, () =>
|
|
104
|
+
afterAuthFailure(scope, store, pins, ctx, providerId, currentId, detail),
|
|
105
|
+
),
|
|
93
106
|
};
|
|
94
107
|
}
|
|
95
108
|
|
package/src/accounts/lock.ts
CHANGED
|
@@ -14,7 +14,7 @@ export async function withLock<T>(
|
|
|
14
14
|
): Promise<T> {
|
|
15
15
|
const deadline = Date.now() + waitMs;
|
|
16
16
|
while (!take(path)) {
|
|
17
|
-
if (Date.now() >= deadline) return
|
|
17
|
+
if (Date.now() >= deadline) return work();
|
|
18
18
|
// oxlint-disable-next-line no-await-in-loop -- the lock is retried until its deadline.
|
|
19
19
|
await new Promise((resolve) => setTimeout(resolve, RETRY_MS));
|
|
20
20
|
}
|
package/src/ui/picker.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Our dialogs wear the same frame as the rest of the harness. A host that cannot draw a component,
|
|
2
2
|
// like RPC, falls back to pi's own select.
|
|
3
|
+
import { oneAtATime } from "@adeildo/pi-kit";
|
|
3
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import {
|
|
@@ -24,8 +25,24 @@ const MAX_VISIBLE = 10;
|
|
|
24
25
|
const GAP = 2;
|
|
25
26
|
const MIN_DESCRIPTION = 12;
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
28
|
+
/** Pi draws one dialog in the editor's place; a second one replaces the first without closing it. */
|
|
29
|
+
const dialogs = oneAtATime();
|
|
30
|
+
|
|
31
|
+
/** The options in our frame; undefined when the answer is cancelled. One opens at a time. */
|
|
32
|
+
export function pick(
|
|
33
|
+
ctx: ExtensionContext,
|
|
34
|
+
title: string,
|
|
35
|
+
options: readonly (string | PickOption)[],
|
|
36
|
+
dialog: { signal?: AbortSignal } = {},
|
|
37
|
+
): Promise<string | undefined> {
|
|
38
|
+
return dialogs(() => shown(ctx, title, options, dialog), dialog.signal).catch((error) => {
|
|
39
|
+
// Cancelled while it waited for another dialog to close.
|
|
40
|
+
if (dialog.signal?.aborted) return undefined;
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function shown(
|
|
29
46
|
ctx: ExtensionContext,
|
|
30
47
|
title: string,
|
|
31
48
|
options: readonly (string | PickOption)[],
|