dsh-claude-subscription 0.3.1 → 0.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.
@@ -0,0 +1,282 @@
1
+ import { assertClaudeAuthUrl } from "./external-url.js";
2
+ const LOGIN_METHODS = new Set(["browser"]);
3
+ const TERMINAL_PHASES = new Set(["authenticated", "failed", "cancelled"]);
4
+ function publicClone(value) {
5
+ return structuredClone(value);
6
+ }
7
+ function asObject(value) {
8
+ return value !== null && typeof value === "object"
9
+ ? value
10
+ : {};
11
+ }
12
+ function ok(value) {
13
+ return {
14
+ ok: true,
15
+ value,
16
+ };
17
+ }
18
+ function badRequest(message) {
19
+ return {
20
+ ok: false,
21
+ error: {
22
+ code: "bad-request",
23
+ message,
24
+ details: { issues: [] },
25
+ },
26
+ };
27
+ }
28
+ function deferred() {
29
+ let resolve;
30
+ let reject;
31
+ return {
32
+ promise: new Promise((onResolve, onReject) => {
33
+ resolve = onResolve;
34
+ reject = onReject;
35
+ }),
36
+ resolve,
37
+ reject,
38
+ };
39
+ }
40
+ function publicPrompt(prompt) {
41
+ return {
42
+ type: prompt.type,
43
+ message: String(prompt.message ?? ""),
44
+ ...(typeof prompt.placeholder === "string" ? { placeholder: prompt.placeholder } : {}),
45
+ };
46
+ }
47
+ /** Own one host-side login without exposing tokens to the browser client. */
48
+ export class ClaudeLoginCoordinator {
49
+ auth;
50
+ createId;
51
+ #sessions = new Map();
52
+ #activeId;
53
+ constructor(auth, options = {}) {
54
+ this.auth = auth;
55
+ this.createId = options.createId ?? (() => crypto.randomUUID());
56
+ }
57
+ async accountStatus(options) {
58
+ return publicClone(await this.auth.status(options));
59
+ }
60
+ async start({ method }) {
61
+ if (!LOGIN_METHODS.has(method))
62
+ throw new Error(`unsupported Claude login method: ${String(method)}`);
63
+ const active = this.#activeId === undefined ? undefined : this.#sessions.get(this.#activeId);
64
+ if (active !== undefined && !TERMINAL_PHASES.has(active.view.phase))
65
+ throw new Error("a Claude login is already active");
66
+ if (active !== undefined)
67
+ this.#sessions.delete(active.view.id);
68
+ const id = this.createId();
69
+ const ready = deferred();
70
+ const controller = new AbortController();
71
+ const session = {
72
+ controller,
73
+ prompt: undefined,
74
+ ready,
75
+ view: {
76
+ id,
77
+ provider: "anthropic",
78
+ method,
79
+ phase: "starting",
80
+ authenticated: false,
81
+ },
82
+ };
83
+ this.#sessions.set(id, session);
84
+ this.#activeId = id;
85
+ const publishReady = () => {
86
+ ready.resolve(this.read(id));
87
+ };
88
+ const interaction = {
89
+ signal: controller.signal,
90
+ prompt: async (prompt) => {
91
+ controller.signal.throwIfAborted();
92
+ if (prompt.type === "select")
93
+ return method;
94
+ if (!["manual_code", "text", "secret"].includes(prompt.type))
95
+ throw new Error(`unsupported Claude auth prompt: ${String(prompt.type)}`);
96
+ const answer = deferred();
97
+ session.prompt = answer;
98
+ session.view = {
99
+ ...session.view,
100
+ phase: "waiting_input",
101
+ prompt: publicPrompt(prompt),
102
+ };
103
+ const abortPrompt = () => {
104
+ answer.reject(controller.signal.reason ?? new Error("login cancelled"));
105
+ };
106
+ controller.signal.addEventListener("abort", abortPrompt, { once: true });
107
+ prompt.signal?.addEventListener("abort", abortPrompt, { once: true });
108
+ publishReady();
109
+ try {
110
+ return await answer.promise;
111
+ }
112
+ finally {
113
+ controller.signal.removeEventListener("abort", abortPrompt);
114
+ prompt.signal?.removeEventListener("abort", abortPrompt);
115
+ if (session.prompt === answer)
116
+ session.prompt = undefined;
117
+ }
118
+ },
119
+ notify: (event) => {
120
+ if (controller.signal.aborted)
121
+ return;
122
+ if (event.type === "auth_url")
123
+ session.view = {
124
+ ...session.view,
125
+ phase: "waiting_browser",
126
+ authUrl: assertClaudeAuthUrl(event.url),
127
+ ...(typeof event.instructions === "string" ? { instructions: event.instructions } : {}),
128
+ };
129
+ else
130
+ session.view = {
131
+ ...session.view,
132
+ message: String(event.message ?? ""),
133
+ };
134
+ publishReady();
135
+ },
136
+ };
137
+ session.run = Promise.resolve()
138
+ .then(() => this.auth.login(interaction))
139
+ .then(async () => {
140
+ if (controller.signal.aborted)
141
+ return;
142
+ const status = (await this.auth.status());
143
+ session.view = {
144
+ id,
145
+ provider: "anthropic",
146
+ method,
147
+ phase: "authenticated",
148
+ authenticated: status.authenticated === true,
149
+ ...(typeof status.expiresAt === "number" ? { expiresAt: status.expiresAt } : {}),
150
+ };
151
+ })
152
+ .catch((error) => {
153
+ if (controller.signal.aborted) {
154
+ session.view = {
155
+ id,
156
+ provider: "anthropic",
157
+ method,
158
+ phase: "cancelled",
159
+ authenticated: false,
160
+ };
161
+ return;
162
+ }
163
+ session.view = {
164
+ id,
165
+ provider: "anthropic",
166
+ method,
167
+ phase: "failed",
168
+ authenticated: false,
169
+ error: "Claude login failed",
170
+ };
171
+ session.hostError = error;
172
+ })
173
+ .finally(publishReady);
174
+ return ready.promise;
175
+ }
176
+ read(id) {
177
+ const session = this.#sessions.get(id);
178
+ if (session === undefined)
179
+ throw new Error("unknown Claude login");
180
+ return publicClone(session.view);
181
+ }
182
+ async submit({ id, value }) {
183
+ const session = this.#sessions.get(id);
184
+ if (session === undefined)
185
+ throw new Error("unknown Claude login");
186
+ if (session.prompt === undefined || session.view.phase !== "waiting_input")
187
+ throw new Error("Claude login is not waiting for input");
188
+ if (typeof value !== "string" || value.trim() === "")
189
+ throw new Error("Claude login input is empty");
190
+ const answer = session.prompt;
191
+ session.prompt = undefined;
192
+ session.view = {
193
+ ...session.view,
194
+ phase: session.view.authUrl === undefined ? "starting" : "waiting_browser",
195
+ prompt: undefined,
196
+ };
197
+ answer.resolve(value);
198
+ return this.read(id);
199
+ }
200
+ async cancel(id) {
201
+ const session = this.#sessions.get(id);
202
+ if (session === undefined)
203
+ throw new Error("unknown Claude login");
204
+ if (!TERMINAL_PHASES.has(session.view.phase)) {
205
+ session.view = {
206
+ id,
207
+ provider: "anthropic",
208
+ method: session.view.method,
209
+ phase: "cancelled",
210
+ authenticated: false,
211
+ };
212
+ session.controller.abort(new Error("Claude login cancelled"));
213
+ }
214
+ await Promise.resolve(session.run).catch(() => undefined);
215
+ return this.read(id);
216
+ }
217
+ async logout(options) {
218
+ if (this.#activeId !== undefined) {
219
+ const active = this.#sessions.get(this.#activeId);
220
+ if (active !== undefined && !TERMINAL_PHASES.has(active.view.phase))
221
+ await this.cancel(active.view.id);
222
+ }
223
+ await this.auth.logout(options);
224
+ return this.accountStatus(options);
225
+ }
226
+ }
227
+ /** Map the authenticated DSH Connection channel onto the coordinator. */
228
+ export function createClaudeRpcHandler(coordinator, options = {}) {
229
+ const openExternal = options.openExternal;
230
+ return async (endpoint, payload, signal) => {
231
+ try {
232
+ signal.throwIfAborted();
233
+ const input = asObject(payload);
234
+ if (endpoint === "status")
235
+ return ok(await coordinator.accountStatus({ signal }));
236
+ if (endpoint === "login/start") {
237
+ const started = await coordinator.start({ method: input.method });
238
+ if (input.openExternal !== true)
239
+ return ok(started);
240
+ const url = started.authUrl;
241
+ if (typeof url !== "string" || openExternal === undefined)
242
+ return ok({
243
+ ...started,
244
+ externalOpened: false,
245
+ });
246
+ try {
247
+ await openExternal(url);
248
+ return ok({
249
+ ...started,
250
+ externalOpened: true,
251
+ });
252
+ }
253
+ catch {
254
+ return ok({
255
+ ...started,
256
+ externalOpened: false,
257
+ });
258
+ }
259
+ }
260
+ if (endpoint === "login/status")
261
+ return ok(coordinator.read(input.id));
262
+ if (endpoint === "login/submit")
263
+ return ok(await coordinator.submit({
264
+ id: input.id,
265
+ value: input.value,
266
+ }));
267
+ if (endpoint === "login/cancel")
268
+ return ok(await coordinator.cancel(input.id));
269
+ if (endpoint === "logout")
270
+ return ok(await coordinator.logout({ signal }));
271
+ return badRequest(`unknown Claude auth endpoint: ${endpoint}`);
272
+ }
273
+ catch (error) {
274
+ if (signal.aborted)
275
+ throw error;
276
+ const message = error instanceof Error && /^(unknown|unsupported|a Claude|Claude login)/.test(error.message)
277
+ ? error.message
278
+ : "Claude request failed";
279
+ return badRequest(message);
280
+ }
281
+ };
282
+ }
package/lib/money.js ADDED
@@ -0,0 +1,9 @@
1
+ export function money(value, currency, decimalPlaces) {
2
+ return new Intl.NumberFormat(undefined, {
3
+ style: "currency",
4
+ currency,
5
+ currencyDisplay: "code",
6
+ minimumFractionDigits: decimalPlaces,
7
+ maximumFractionDigits: decimalPlaces,
8
+ }).format(value / 10 ** decimalPlaces);
9
+ }
@@ -0,0 +1,61 @@
1
+ import { anthropicProvider as createAnthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
2
+ import { access } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { resolve as resolvePath } from "node:path";
5
+ /**
6
+ * Preserve pi-ai's native Anthropic OAuth provider (Claude Pro/Max) while
7
+ * allowing DSH's generic PiAiAdapter to pass the access token resolved by the
8
+ * host credential store.
9
+ *
10
+ * PiAiAdapter owns a request-local Models collection without a credential
11
+ * store. A pure OAuth provider ignores its `apiKey` request override and fails
12
+ * before dispatch with "Provider is not configured". This non-interactive
13
+ * bridge teaches that collection how to consume only the already-refreshed
14
+ * token for this request; login, refresh, persistence, headers, transport, and
15
+ * model behavior remain owned by the original provider.
16
+ */
17
+ export function anthropicSubscriptionProvider() {
18
+ const provider = createAnthropicProvider();
19
+ let models = Object.freeze([]);
20
+ const requestToken = Object.freeze({
21
+ name: "DSH-managed Anthropic OAuth request token",
22
+ async resolve({ credential }) {
23
+ const token = credential?.type === "api_key" ? credential.key : undefined;
24
+ if (typeof token !== "string" || token.length === 0)
25
+ return undefined;
26
+ return {
27
+ auth: { apiKey: token },
28
+ source: "DSH-managed OAuth request",
29
+ };
30
+ },
31
+ });
32
+ return Object.freeze({
33
+ ...provider,
34
+ auth: Object.freeze({
35
+ ...(provider.auth ?? {}),
36
+ apiKey: requestToken,
37
+ }),
38
+ getModels: () => models,
39
+ setModels(value) {
40
+ models = Object.freeze([...value]);
41
+ },
42
+ });
43
+ }
44
+ export const PI_AI_RUNTIME_VERSION = "0.85.1";
45
+ export const PI_AI_AUTH_CONTEXT = Object.freeze({
46
+ async env(name) {
47
+ return process.env[name];
48
+ },
49
+ async fileExists(path) {
50
+ const expanded = path === "~" || path.startsWith("~/")
51
+ ? resolvePath(homedir(), path.slice(1).replace(/^\//, ""))
52
+ : path;
53
+ try {
54
+ await access(expanded);
55
+ return true;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ },
61
+ });
@@ -0,0 +1,3 @@
1
+ export const SETTINGS_NAMESPACE = "claude-subscription";
2
+ export const QUICK_QUOTA_FIELD = "quickQuotaVisible";
3
+ Object.freeze({ [QUICK_QUOTA_FIELD]: false });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ export interface QuotaWindow {
2
+ readonly id: string;
3
+ readonly label?: string | undefined;
4
+ readonly remainingPercent: number;
5
+ readonly resetsAt?: number | undefined;
6
+ readonly appliesTo?: string | undefined;
7
+ }
8
+ export interface QuotaUsage {
9
+ readonly windows?: readonly QuotaWindow[] | undefined;
10
+ }
11
+ export interface ModelQuota {
12
+ readonly id: string;
13
+ readonly remainingPercent: number;
14
+ readonly resetsAt?: number | undefined;
15
+ }
16
+ /** Pick the tightest API-reported quota window that applies to the model. */
17
+ export declare function selectModelQuota(usage: QuotaUsage | undefined | null, model: string): ModelQuota | undefined;
@@ -0,0 +1,45 @@
1
+ import type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from "@earendil-works/pi-ai";
2
+ import type { CredentialRef } from "@deepseek-ai/dsh-credentials";
3
+ declare const PROVIDER = "anthropic";
4
+ export interface DshCredentialRecord {
5
+ readonly value?: string | undefined;
6
+ }
7
+ export interface DshCredentialsService {
8
+ readonly resolve: (ref: CredentialRef) => Promise<DshCredentialRecord | undefined>;
9
+ readonly set: (ref: CredentialRef, value: string) => Promise<void>;
10
+ readonly unset: (ref: CredentialRef) => Promise<void>;
11
+ }
12
+ export interface ClaudeModelsLike {
13
+ readonly login: (providerId: string, type: "oauth", interaction: unknown) => Promise<unknown>;
14
+ readonly logout: (providerId: string, options?: AuthOperationOptions) => Promise<unknown>;
15
+ }
16
+ export interface ClaudeAccountStatus {
17
+ readonly authenticated: boolean;
18
+ readonly provider: typeof PROVIDER;
19
+ readonly type?: "oauth" | undefined;
20
+ readonly expiresAt?: number | undefined;
21
+ }
22
+ export interface ClaudeAuthService {
23
+ readonly status: (options?: AuthOperationOptions) => Promise<ClaudeAccountStatus>;
24
+ readonly login: (interaction: unknown) => Promise<unknown>;
25
+ readonly logout: (options?: AuthOperationOptions) => Promise<unknown>;
26
+ }
27
+ /**
28
+ * Adapt DSH's managed string credential service to pi-ai's typed OAuth store.
29
+ * Refresh/login/logout operations are serialized so an older refresh response
30
+ * cannot overwrite a newer rotated token.
31
+ */
32
+ export declare class DshOAuthCredentialStore implements CredentialStore {
33
+ #private;
34
+ readonly credentials: DshCredentialsService;
35
+ readonly ref: CredentialRef;
36
+ readonly legacyRefs: readonly CredentialRef[];
37
+ constructor(credentials: DshCredentialsService, ref: CredentialRef, legacyRefs?: readonly CredentialRef[]);
38
+ read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined>;
39
+ list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]>;
40
+ modify(providerId: string, update: (current: Credential | undefined) => Promise<Credential | undefined>, options?: AuthOperationOptions): Promise<Credential | undefined>;
41
+ delete(providerId: string, options?: AuthOperationOptions): Promise<void>;
42
+ }
43
+ /** Return only account state that is safe to expose to the browser client. */
44
+ export declare function createClaudeAuthService(models: ClaudeModelsLike, store: DshOAuthCredentialStore): ClaudeAuthService;
45
+ export {};
@@ -0,0 +1,17 @@
1
+ import { spawn } from "node:child_process";
2
+ export declare const CLAUDE_AUTH_ORIGIN = "https://claude.ai";
3
+ export interface ClaudeAuthOpenerCommand {
4
+ readonly file: string;
5
+ readonly args: readonly string[];
6
+ readonly shell: false;
7
+ }
8
+ export type ClaudeAuthPlatform = "win32" | "darwin" | "linux" | (string & {});
9
+ export interface OpenClaudeAuthUrlOptions {
10
+ readonly platform?: string | undefined;
11
+ readonly spawn?: typeof spawn | undefined;
12
+ }
13
+ /** Validate the only external origin this plugin may launch. */
14
+ export declare function assertClaudeAuthUrl(value: unknown): string;
15
+ /** Return a shell-free native opener command for the current desktop. */
16
+ export declare function commandForClaudeAuthUrl(value: unknown, platform?: string): ClaudeAuthOpenerCommand;
17
+ export declare function openClaudeAuthUrl(value: unknown, options?: OpenClaudeAuthUrlOptions): Promise<void>;
@@ -0,0 +1,85 @@
1
+ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
2
+ import type { PiAiAdapterOptions } from "@deepseek-ai/dsh-llm-pi-ai";
3
+ import { DshOAuthCredentialStore, createClaudeAuthService } from "./credential-store.js";
4
+ import { assertClaudeAuthUrl, openClaudeAuthUrl } from "./external-url.js";
5
+ import { ClaudeLoginCoordinator, createClaudeRpcHandler } from "./login-coordinator.js";
6
+ import type { ClaudeRpcHandler } from "./login-coordinator.js";
7
+ import { anthropicSubscriptionProvider, PI_AI_RUNTIME_VERSION } from "./pi-ai-runtime.js";
8
+ import { CLAUDE_MODELS_URL, CLAUDE_USAGE_URL, parseClaudeModels, parseClaudeUsage, createClaudeModelReader, createClaudeUsageReader } from "./usage.js";
9
+ import type { UsageReader, ModelReader, FetchedUsage } from "./usage.js";
10
+ import { selectModelQuota } from "./composer-quota.js";
11
+ export { CLAUDE_MODELS_URL, CLAUDE_USAGE_URL, ClaudeLoginCoordinator, DshOAuthCredentialStore, PI_AI_RUNTIME_VERSION, anthropicSubscriptionProvider, assertClaudeAuthUrl, createClaudeAuthService, createClaudeModelReader, createClaudeRpcHandler, createClaudeUsageReader, parseClaudeModels, parseClaudeUsage, selectModelQuota, openClaudeAuthUrl, };
12
+ export declare const name = "claude-subscription";
13
+ export declare const inject: readonly string[];
14
+ export interface RpcErrorEnvelope {
15
+ readonly ok: false;
16
+ readonly error: {
17
+ readonly code: string;
18
+ readonly message: string;
19
+ readonly details: {
20
+ readonly issues: readonly unknown[];
21
+ };
22
+ };
23
+ }
24
+ export interface RpcOkEnvelope<T> {
25
+ readonly ok: true;
26
+ readonly value: T;
27
+ }
28
+ export type RpcEnvelope<T> = RpcOkEnvelope<T> | RpcErrorEnvelope;
29
+ export interface PreferenceStatus {
30
+ readonly quickQuotaVisible: boolean;
31
+ readonly writable: boolean;
32
+ }
33
+ export interface PreferenceStore {
34
+ readonly status: () => PreferenceStatus;
35
+ readonly update: (patch: {
36
+ readonly quickQuotaVisible: boolean;
37
+ }) => Promise<unknown>;
38
+ }
39
+ interface SubscriptionRpcDeps {
40
+ readonly authHandler: ClaudeRpcHandler;
41
+ readonly usageReader: UsageReader;
42
+ readonly modelReader?: ModelReader | undefined;
43
+ readonly preferences: PreferenceStore;
44
+ }
45
+ /**
46
+ * Extend the account RPC with the preference toggle and the quota projection.
47
+ * Unknown endpoints stay owned by the login coordinator.
48
+ */
49
+ export declare function createSubscriptionRpcHandler({ authHandler, usageReader, modelReader, preferences, }: SubscriptionRpcDeps): ClaudeRpcHandler;
50
+ interface HostSettings {
51
+ readonly writable: boolean;
52
+ readonly installSection: (ctx: unknown, namespace: string, schema: unknown, defaults: unknown, hooks: {
53
+ readonly setSource: (current: any) => void;
54
+ readonly onChange: () => void;
55
+ }) => void;
56
+ readonly update: (namespace: string, patch: unknown) => Promise<void>;
57
+ }
58
+ interface HostContext {
59
+ readonly settings: HostSettings;
60
+ readonly credentials: never;
61
+ readonly llm: {
62
+ readonly registerAdapter: (providers: readonly string[], adapter: unknown) => unknown;
63
+ };
64
+ readonly inject: (services: readonly string[], callback: (ctx: {
65
+ readonly connection: {
66
+ readonly rpc: {
67
+ readonly handle: (channel: string, handler: unknown) => unknown;
68
+ };
69
+ };
70
+ readonly effect: (fn: () => unknown, label?: string) => unknown;
71
+ }) => unknown) => unknown;
72
+ readonly [key: string]: unknown;
73
+ }
74
+ export declare function apply(ctx: HostContext): void;
75
+ export declare class ClaudeApiAdapter extends PiAiAdapter {
76
+ readonly modelReader: ModelReader;
77
+ constructor(options: PiAiAdapterOptions, modelReader: ModelReader);
78
+ listModels(provider: string): Promise<readonly never[]>;
79
+ resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<never>;
80
+ prepareCall(provider: string, model: string, signal?: AbortSignal): Promise<never>;
81
+ stream(options: {
82
+ readonly signal?: AbortSignal;
83
+ }): AsyncIterable<never>;
84
+ }
85
+ export type { FetchedUsage };
@@ -0,0 +1,66 @@
1
+ import type { ClaudeAuthService } from "./credential-store.js";
2
+ export type LoginPhase = "starting" | "waiting_input" | "waiting_browser" | "authenticated" | "failed" | "cancelled";
3
+ export interface LoginPromptView {
4
+ readonly type: string;
5
+ readonly message: string;
6
+ readonly placeholder?: string | undefined;
7
+ }
8
+ export interface LoginView {
9
+ readonly id: string;
10
+ readonly provider: "anthropic";
11
+ readonly method: string;
12
+ readonly phase: LoginPhase;
13
+ readonly authenticated: boolean;
14
+ readonly authUrl?: string | undefined;
15
+ readonly instructions?: string | undefined;
16
+ readonly message?: string | undefined;
17
+ readonly prompt?: LoginPromptView | undefined;
18
+ readonly expiresAt?: number | undefined;
19
+ readonly error?: string | undefined;
20
+ }
21
+ export interface LoginCoordinatorOptions {
22
+ readonly createId?: (() => string) | undefined;
23
+ }
24
+ export interface RpcOk<T> {
25
+ readonly ok: true;
26
+ readonly value: T;
27
+ }
28
+ export interface RpcErr {
29
+ readonly ok: false;
30
+ readonly error: {
31
+ readonly code: string;
32
+ readonly message: string;
33
+ readonly details: {
34
+ readonly issues: readonly unknown[];
35
+ };
36
+ };
37
+ }
38
+ export type RpcResult<T> = RpcOk<T> | RpcErr;
39
+ /** Own one host-side login without exposing tokens to the browser client. */
40
+ export declare class ClaudeLoginCoordinator {
41
+ #private;
42
+ readonly auth: ClaudeAuthService;
43
+ readonly createId: () => string;
44
+ constructor(auth: ClaudeAuthService, options?: LoginCoordinatorOptions);
45
+ accountStatus(options?: {
46
+ readonly signal?: AbortSignal | undefined;
47
+ }): Promise<unknown>;
48
+ start({ method }: {
49
+ readonly method: string;
50
+ }): Promise<LoginView>;
51
+ read(id: string): LoginView;
52
+ submit({ id, value }: {
53
+ readonly id: string;
54
+ readonly value: unknown;
55
+ }): Promise<LoginView>;
56
+ cancel(id: string): Promise<LoginView>;
57
+ logout(options?: {
58
+ readonly signal?: AbortSignal | undefined;
59
+ }): Promise<unknown>;
60
+ }
61
+ export interface RpcHandlerOptions {
62
+ readonly openExternal?: ((url: string) => Promise<void>) | undefined;
63
+ }
64
+ export type ClaudeRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<RpcResult<unknown>>;
65
+ /** Map the authenticated DSH Connection channel onto the coordinator. */
66
+ export declare function createClaudeRpcHandler(coordinator: ClaudeLoginCoordinator, options?: RpcHandlerOptions): ClaudeRpcHandler;
@@ -0,0 +1 @@
1
+ export declare function money(value: number, currency: string, decimalPlaces: number): string;
@@ -0,0 +1,22 @@
1
+ import type { AuthContext } from "@earendil-works/pi-ai";
2
+ import type { ClaudeModel } from "./usage.js";
3
+ export interface SubscriptionProvider {
4
+ readonly getModels: () => readonly ClaudeModel[];
5
+ readonly setModels: (value: readonly ClaudeModel[]) => void;
6
+ readonly [key: string]: unknown;
7
+ }
8
+ /**
9
+ * Preserve pi-ai's native Anthropic OAuth provider (Claude Pro/Max) while
10
+ * allowing DSH's generic PiAiAdapter to pass the access token resolved by the
11
+ * host credential store.
12
+ *
13
+ * PiAiAdapter owns a request-local Models collection without a credential
14
+ * store. A pure OAuth provider ignores its `apiKey` request override and fails
15
+ * before dispatch with "Provider is not configured". This non-interactive
16
+ * bridge teaches that collection how to consume only the already-refreshed
17
+ * token for this request; login, refresh, persistence, headers, transport, and
18
+ * model behavior remain owned by the original provider.
19
+ */
20
+ export declare function anthropicSubscriptionProvider(): SubscriptionProvider;
21
+ export declare const PI_AI_RUNTIME_VERSION = "0.85.1";
22
+ export declare const PI_AI_AUTH_CONTEXT: AuthContext;
@@ -0,0 +1,6 @@
1
+ export declare const SETTINGS_NAMESPACE = "claude-subscription";
2
+ export declare const QUICK_QUOTA_FIELD: "quickQuotaVisible";
3
+ export type QuickQuotaField = typeof QUICK_QUOTA_FIELD;
4
+ export interface ClaudeSubscriptionPreferences {
5
+ readonly quickQuotaVisible: boolean;
6
+ }