dsh-opendesign-corouter 0.1.3 → 0.1.5

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/lib/login.d.ts CHANGED
@@ -1,74 +1,12 @@
1
- /** Console origin and dashboard entry the official desktop uses. */
2
- export declare const CONSOLE_ORIGIN = "https://open-design.ai";
3
- export declare const CONSOLE_DASHBOARD_URL = "https://open-design.ai/cloud/dashboard";
4
- /** The API host whose cookies matter; `context.cookies()` uses it to pick the right ones. */
5
- export declare const API_COOKIE_URL = "https://amr-api.open-design.ai/api/v1/workspaces/current";
6
- export interface RawCookie {
7
- name?: string;
8
- value?: string;
9
- expires?: number;
10
- }
11
- /** One usable console session: the exact `Cookie` header a browser would send. */
12
- export interface ExtractedCookie {
13
- cookie: string;
14
- expiresAt: number;
15
- }
16
- /**
17
- * Pick the session cookies by **URL scope**, never by name. The real cookie is
18
- * better-auth's `__Secure-better-auth.session_token` today, but it can be renamed at any
19
- * time and no third party can enumerate the alternatives; joining every unexpired cookie
20
- * that applies to the API URL reproduces exactly what the browser itself sends. Before
21
- * login completes this may also collect non-auth cookies (`od_attr`, `__cf_bm`), which is
22
- * harmless: such a bundle fails `verify` and is therefore never saved.
23
- */
24
- export declare function extractConsoleSession(cookies: readonly RawCookie[], now?: number): ExtractedCookie | null;
25
- export interface LoginSite {
26
- origin: string;
27
- loginUrl: string;
28
- cookieUrls: readonly string[];
29
- extract(cookies: readonly RawCookie[], now: number): ExtractedCookie | null;
30
- }
31
- /** Site parameters, overridable so a rebrand (for example a new console origin) needs no code fork. */
32
- export declare function consoleSite(overrides?: {
33
- origin?: string;
34
- loginUrl?: string;
35
- cookieUrls?: readonly string[];
36
- }): LoginSite;
37
- export interface VerifyResult {
38
- /** Whether the account was read at all; a read-but-quota-unknown result still verifies. */
39
- balanceOk: boolean;
40
- /** Set when the quota endpoint failed; the cookie itself is still valid, so the host may still save it. */
41
- usageError?: {
42
- code: string;
43
- } | undefined;
44
- /**
45
- * Caller-supplied context, echoed nowhere and used only for the success message. The Host
46
- * re-reads the real quota after commit, so these are never trusted as state.
47
- */
48
- coverage?: string | undefined;
49
- windows?: readonly unknown[] | undefined;
50
- }
51
- /** Host-supplied operations. No Host type is imported: the caller wires these. */
1
+ import { type DeviceTokens } from './device-auth.js';
2
+ export type { DeviceTokens } from './device-auth.js';
3
+ /** Host operations. No quota verification: the authorization server issues the identity. */
52
4
  export interface LoginCallbacks {
53
- /** Snapshot the state a commit must not overwrite, keyed by the revision the caller saw. */
54
5
  capture(expectedRevision: string): unknown | Promise<unknown>;
55
- /** Prove the cookie works. Any throw means "not usable yet"; abort is a cancel. */
56
- verify(cookie: string, signal: AbortSignal): Promise<VerifyResult>;
57
- /**
58
- * Persist the verified cookie. The Host's store must run its write inside a mutex and call
59
- * `signal.throwIfAborted()` there, so a cancel that landed before the mutex was acquired
60
- * wins, and one that lands afterwards does not.
61
- *
62
- * `expiresAt` is the cookie bundle's earliest declared expiry in epoch ms, or `0` when the
63
- * console declared none (a session cookie). It is passed through so the Host can store it and
64
- * let `resolveSession` expire a stale grant proactively instead of waiting for a 401.
65
- */
66
- commit(cookie: string, captured: unknown, signal: AbortSignal, expiresAt: number): Promise<unknown>;
6
+ /** Re-check signal under the Host's atomic store mutex before accepting the write.
7
+ * Once accepted, resolve: success is the linearization point, even after cancellation. */
8
+ commit(tokens: DeviceTokens, captured: unknown, signal: AbortSignal): Promise<unknown>;
67
9
  }
68
- /**
69
- * Attempt phases, matching the shared wire contract (`LoginPhase` in `remote.ts`) exactly;
70
- * `success`/`cancelled`/`error` are terminal. The Host maps `status()` onto the wire type.
71
- */
72
10
  export type LoginPhase = 'idle' | 'launching' | 'waiting' | 'verifying' | 'success' | 'cancelled' | 'error';
73
11
  export interface LoginStatus {
74
12
  jobId: string | null;
@@ -77,8 +15,9 @@ export interface LoginStatus {
77
15
  finishedAt?: string;
78
16
  message?: string;
79
17
  errorCode?: string;
80
- /** The page could not be opened; the UI may offer the URL instead. */
81
- browserOpenFailed?: boolean;
18
+ activationUrl?: string;
19
+ userCode?: string;
20
+ expiresAt?: string;
82
21
  }
83
22
  export interface LoginStartRequest {
84
23
  expectedRevision: string;
@@ -86,90 +25,49 @@ export interface LoginStartRequest {
86
25
  export interface LoginJobRequest {
87
26
  jobId: string;
88
27
  }
89
- /** Minimal structural browser surface, so tests need no Playwright and Host wiring stays loose. */
90
- export interface BrowserLike {
91
- on?(event: 'disconnected', listener: () => void): void;
92
- newContext(): Promise<ContextLike>;
93
- close(): Promise<void>;
94
- }
95
- export interface ContextLike {
96
- newPage(): Promise<PageLike>;
97
- cookies(urls: readonly string[]): Promise<readonly RawCookie[]>;
98
- }
99
- export interface PageLike {
100
- on?(event: 'close', listener: () => void): void;
101
- goto(url: string, options?: {
102
- waitUntil?: string;
103
- timeout?: number;
104
- }): Promise<unknown>;
105
- }
106
- export type Launcher = (signal: AbortSignal) => Promise<BrowserLike | null>;
107
28
  export interface LoginOptions {
108
- site?: LoginSite;
109
- /** Total life of one login attempt. Default five minutes. */
110
- timeoutMs?: number;
111
- pollMs?: number;
29
+ fetcher?: typeof fetch;
112
30
  now?: () => number;
113
- /** Injected browser launcher; the default uses `playwright-core` and a real Chrome window. */
114
- launch?: Launcher;
115
- /** Injected sleep, for tests. */
116
31
  delay?: (ms: number, signal: AbortSignal) => Promise<void>;
32
+ /** May shorten, but cannot extend, the local five-minute maximum. */
33
+ timeoutMs?: number;
34
+ /**
35
+ * Called once whenever an approval job leaves `waiting` — success, cancel, expiry or
36
+ * failure — including on a timer with nobody polling. Resource cleanup lives here
37
+ * instead of in a status getter, whose side effect depends on someone reading it.
38
+ */
39
+ onSettled?: () => void;
117
40
  }
118
41
  export declare class LoginError extends Error {
119
42
  readonly code: 'opendesign/login-state' | 'opendesign/login-disposed' | 'opendesign/login-capture';
120
43
  constructor(code: 'opendesign/login-state' | 'opendesign/login-disposed' | 'opendesign/login-capture');
121
44
  }
122
- /**
123
- * Chrome flags that let a *human* pass the console's bot check. The console looks at
124
- * `navigator.webdriver` when its Cloudflare challenge is submitted; `headless: false` alone
125
- * leaves it `true`, and this blink switch is what the official Playwright browser tool
126
- * appends for chromium. This is not a challenge bypass — the user still solves it — it only
127
- * avoids announcing automation before they can act. `ignoreDefaultArgs` is a no-op on
128
- * 1.63.0 and stays for forward compatibility.
129
- */
130
- export declare const CHROME_LAUNCH_OPTIONS: {
131
- readonly channel: "chrome";
132
- readonly headless: false;
133
- readonly ignoreDefaultArgs: readonly ["--enable-automation"];
134
- readonly args: readonly ["--disable-blink-features=AutomationControlled"];
135
- };
136
45
  export declare class ConsoleLogin {
137
- private job;
138
- private disposed;
139
46
  private readonly callbacks;
140
47
  private readonly options;
48
+ private job;
49
+ private disposed;
141
50
  private readonly now;
142
51
  private readonly delay;
52
+ private readonly fetcher;
143
53
  constructor(callbacks: LoginCallbacks, options?: LoginOptions);
144
- /** Idempotent, credential-free view of the current attempt. */
54
+ /** Explicit public allowlist. Codes are never available after waiting or expiry. */
145
55
  status(): LoginStatus;
146
- /**
147
- * Queue one attempt and return immediately: the RPC that starts a login must not wait on a
148
- * browser window or on a human. Re-entrant calls while an attempt is live return that
149
- * attempt's status instead of opening a second browser.
150
- */
56
+ /** Host-only: the live approval link, so the Host can open it in a bounded window. */
57
+ activationLink(): string | undefined;
58
+ /** Synchronous/non-blocking and idempotent while capture/network/commit is pending. */
151
59
  start(request: LoginStartRequest): LoginStatus;
152
- /**
153
- * Abandon the attempt. While `commit` is in flight this only aborts the signal and records
154
- * the request: the Host's atomic store decides, because the commit is the linearization
155
- * point. A cancel that wins that race reports `cancelled`; one that loses reports `success`
156
- * and the cookie really is stored.
157
- */
158
60
  cancel(request: LoginJobRequest): LoginStatus;
159
- /** Plugin teardown: abort the attempt and close the browser. */
160
61
  dispose(): void;
161
- /** Every cancellation source must let an in-flight atomic store decide the final outcome. */
162
- private requestCancellation;
163
62
  private alive;
164
63
  private stopTimer;
165
- private close;
64
+ private armExpiry;
65
+ private clearCode;
66
+ private expire;
67
+ private requestCancellation;
166
68
  private finish;
167
- private resolveBrowser;
69
+ /** Report one terminal transition per job, exactly once. */
70
+ private settle;
168
71
  private run;
169
- /**
170
- * Persist the verified session; `commit` resolving is the linearization point. Returns
171
- * `retry` when the store rejected the write transiently, so the caller decides when to try
172
- * again instead of claiming a success that did not happen.
173
- */
174
72
  private commit;
175
73
  }
package/lib/quota.d.ts CHANGED
@@ -2,10 +2,12 @@ export declare const API_BASE = "https://amr-api.open-design.ai/api/v1";
2
2
  export declare const CURRENT_URL = "https://amr-api.open-design.ai/api/v1/workspaces/current";
3
3
  export declare const SUMMARY_URL = "https://amr-api.open-design.ai/api/v1/billing/summary";
4
4
  export declare const usageUrl: (workspace: string) => string;
5
+ export declare const preflightUrl: (workspace: string) => string;
5
6
  export type SessionSource = 'desktop' | 'login' | 'environment';
6
7
  export interface Session {
7
8
  headers: Record<string, string>;
8
9
  source: SessionSource;
10
+ credentialType?: 'device';
9
11
  }
10
12
  export interface QuotaWindow {
11
13
  durationSeconds: number;
@@ -57,4 +59,8 @@ export declare function normalizeQuota(body: unknown, summary?: unknown): Pick<Q
57
59
  /** Billing period of the subscription pool; absent fields stay unknown, never derived. */
58
60
  export declare function normalizeSubscription(summary: unknown): Pick<QuotaSnapshot, 'subscriptionPeriodStart' | 'subscriptionPeriodEnd'>;
59
61
  export declare function normalizeBalances(summary: unknown): QuotaSnapshot['balances'];
62
+ /** Device contract: opencode-opendesign-auth 0.1.4, workspace preflight + personal summary.
63
+ * Unlike desktop reads, this path never invokes vela or borrows desktop account data.
64
+ */
65
+ export declare function readDeviceQuota(session: Session, signal?: AbortSignal, fetcher?: typeof fetch): Promise<QuotaSnapshot>;
60
66
  export declare function readQuota(session: Session, signal?: AbortSignal, fetcher?: typeof fetch): Promise<QuotaSnapshot>;
package/lib/remote.d.ts CHANGED
@@ -39,6 +39,10 @@ export interface LoginState {
39
39
  message?: string;
40
40
  startedAt?: string;
41
41
  finishedAt?: string;
42
+ /** Public device approval information; available only while waiting. */
43
+ activationUrl?: string;
44
+ userCode?: string;
45
+ expiresAt?: string;
42
46
  }
43
47
  /** Everything the settings/usage surface may read; no field is derived from a secret. */
44
48
  export interface Status {
@@ -66,6 +70,11 @@ export interface SaveApiKeyRequest {
66
70
  value: string;
67
71
  expectedRevision: string;
68
72
  }
73
+ /** Outcome of asking the Host to show the approval page in a temporary window. */
74
+ export interface ActivationOpenState {
75
+ code: 'opened' | 'no-link' | 'invalid-url' | 'unavailable' | 'failed';
76
+ opened: boolean;
77
+ }
69
78
  /** Rebuild a `QuotaSnapshot` from an allowlist; unknown keys are dropped. */
70
79
  export declare function parseQuotaSnapshot(value: unknown): QuotaSnapshot;
71
80
  /** Rebuild a `LoginState` from an allowlist; the job's secret material never had a field here. */
@@ -85,3 +94,5 @@ export declare function parseCancelLoginRequest(value: unknown): CancelLoginRequ
85
94
  * names a UTF-16 code point instead of the setting to fix.
86
95
  */
87
96
  export declare function parseSaveApiKeyRequest(value: unknown): SaveApiKeyRequest;
97
+ /** The opener reports one public code; an unknown value is refused, never guessed. */
98
+ export declare function parseActivationOpenState(value: unknown): ActivationOpenState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opendesign-corouter",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenDesign CoRouter:套餐赠送与付费模型双 Provider、桌面登录态额度及独立登录",
5
5
  "type": "module",
6
6
  "license": "MIT",