dsh-openai-subscription 0.1.9 → 0.1.10

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/dist/client.js CHANGED
@@ -127,6 +127,7 @@ const ZH = {
127
127
  'error.network': '无法连接服务,请检查网络后重试。',
128
128
  'error.invalid-response': '服务返回了无效响应,请重试或更新 DSH。',
129
129
  'error.process-exited': '登录进程提前退出,请重新连接或更新 DSH。',
130
+ 'error.sign-in-timeout': '等待时间过长,本次登录已结束。请重新连接并在 9 分钟内完成验证。',
130
131
  'error.credential-write-failed': '无法保存本机授权,请检查本机权限后重试;也可断开以清理残留数据。',
131
132
  'error.credential-changed': '本机授权已被其他操作更改,请重新读取状态后重试。',
132
133
  'error.settings-unavailable': '设置服务不可用,请重启 DSH 后重试。',
@@ -263,6 +264,7 @@ const EN = {
263
264
  'error.network': 'The service could not be reached. Check the network and retry.',
264
265
  'error.invalid-response': 'The service returned an invalid response. Retry or update DSH.',
265
266
  'error.process-exited': 'The sign-in process exited early. Reconnect or update DSH.',
267
+ 'error.sign-in-timeout': 'Sign-in took too long, so this attempt ended. Reconnect and confirm within 9 minutes.',
266
268
  'error.credential-write-failed': 'Local authorization could not be saved. Check local permissions and retry, or disconnect to clean up remaining data.',
267
269
  'error.credential-changed': 'Another operation changed local authorization. Reload status and retry.',
268
270
  'error.settings-unavailable': 'The settings service is unavailable. Restart DSH and retry.',
@@ -305,6 +307,11 @@ window.__ModuleLoader__.load({
305
307
  .oasub-wrap {
306
308
  --oasub-section-inset: 20px;
307
309
  --oasub-card-border-width: 1px;
310
+ --oasub-dot-size: 8px;
311
+ /* Status rows and notices share one line box so a leading marker can be
312
+ centered on the first text line by arithmetic instead of guesswork. */
313
+ --oasub-status-line-height: 22px;
314
+ --oasub-notice-line-height: 18px;
308
315
  /* One control boundary for every button and picker: the host's own control
309
316
  border step, so plugin controls carry the same weight as DSH's. */
310
317
  --oasub-control-border: var(--dsw-alias-border-l3, rgba(15, 17, 21, .16));
@@ -346,9 +353,9 @@ window.__ModuleLoader__.load({
346
353
  }
347
354
  .oasub-status-line { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
348
355
  .oasub-status-copy { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
349
- .oasub-status-title { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600; line-height: 22px; }
356
+ .oasub-status-title { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600; line-height: var(--oasub-status-line-height); }
350
357
  .oasub-status-detail { color: var(--dsw-alias-label-tertiary, #81858c); font-size: 12px; line-height: 18px; }
351
- .oasub-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--dsw-alias-label-tertiary, #81858c); }
358
+ .oasub-dot { flex: none; width: var(--oasub-dot-size); height: var(--oasub-dot-size); border-radius: 999px; background: var(--dsw-alias-label-tertiary, #81858c); }
352
359
  .oasub-dot.success { background: var(--dsw-alias-state-success-primary, #22c55e); }
353
360
  .oasub-dot.warning { background: var(--dsw-alias-state-warn-primary, #f59e0b); }
354
361
  .oasub-pill {
@@ -475,8 +482,11 @@ window.__ModuleLoader__.load({
475
482
  color: var(--dsw-alias-label-secondary, #61666b);
476
483
  background: var(--dsw-alias-bg-layer-2, #f5f6f7);
477
484
  font-size: 12px;
478
- line-height: 18px;
485
+ line-height: var(--oasub-notice-line-height);
479
486
  }
487
+ /* The row stays top-aligned so wrapped text keeps a stable first line; the
488
+ marker therefore centers on that line instead of the row's top edge. */
489
+ .oasub-notice > .oasub-dot { margin-top: calc((var(--oasub-notice-line-height) - var(--oasub-dot-size)) / 2); }
480
490
  .oasub-notice.success { border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #22c55e) 40%, transparent); }
481
491
  .oasub-notice.warning { border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) 45%, transparent); }
482
492
  .oasub-notice.error { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #dc2626) 45%, transparent); color: var(--dsw-alias-state-error-primary, #b91c1c); }
@@ -548,7 +558,7 @@ window.__ModuleLoader__.load({
548
558
  'credentials-unavailable', 'shell-unavailable', 'timer-unavailable', 'component-unavailable',
549
559
  'runtime-unsupported', 'busy', 'invalid-method', 'not-connected', 'not-refreshable',
550
560
  'device-auth-disabled', 'access-denied', 'authorization-expired', 'rate-limited', 'network',
551
- 'timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed',
561
+ 'timeout', 'sign-in-timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed',
552
562
  'settings-unavailable', 'models-unavailable', 'models-empty', 'models-confirmation-required',
553
563
  'settings-conflict', 'settings-write-failed', 'ownership-save-failed', 'cancelled', 'unknown',
554
564
  'invalid-context-window', 'context-window-exceeded', 'model-not-found',
package/dist/driver.d.ts CHANGED
@@ -1,12 +1,23 @@
1
1
  import type { ShellExecutor } from '@deepseek-ai/dsh-shell';
2
2
  import { type OAuthCredential } from './oauth.js';
3
3
  export declare const VERIFICATION_URL = "https://auth.openai.com/codex/device";
4
+ /**
5
+ * Abandon the device-code wait just before the executor's own hard deadline.
6
+ * DSH's default bash executor arms `timeoutMs` with `onExpiry: 'kill'` and caps
7
+ * it at its `maxTimeoutMs` (600000ms today), so a request that never names a
8
+ * timeout is SIGKILLed after the 120000ms default — before any device flow
9
+ * could finish — and a silently killed child cannot report a reason of its own.
10
+ */
11
+ export declare const DEVICE_TIMEOUT_MS: number;
12
+ /** Requested executor deadline: the largest value DSH's bash executor accepts. */
13
+ export declare const DEVICE_SHELL_TIMEOUT_MS: number;
4
14
  export declare const DRIVER_DEVICE = "\nconst out = (o) => process.stdout.write(JSON.stringify(o) + '\\n')\nprocess.stdout.on('error', () => {})\ntry {\n const { pathToFileURL } = await import('node:url')\n const { openaiCodexOAuth } = await import(pathToFileURL(process.argv[1]).href)\n const credential = await openaiCodexOAuth.login({\n signal: AbortSignal.timeout(15 * 60 * 1000),\n notify: (n) => out({ ...n, type: 'notice' }),\n prompt: async (p) => {\n if (p.type === 'select') return 'device_code'\n throw new Error('Unsupported authorization interaction')\n },\n })\n out({ type: 'result', credential })\n} catch (error) {\n out({ type: 'error', message: error instanceof Error ? error.message : String(error) })\n}\n";
5
15
  export declare const DRIVER_REFRESH = "\nconst out = (o) => process.stdout.write(JSON.stringify(o) + '\\n')\nprocess.stdout.on('error', () => {})\ntry {\n const { readFileSync } = await import('node:fs')\n const { pathToFileURL } = await import('node:url')\n const { openaiCodexOAuth } = await import(pathToFileURL(process.argv[1]).href)\n const credential = JSON.parse(readFileSync(0, 'utf8') || '{}')\n const refreshed = await openaiCodexOAuth.refresh(credential, AbortSignal.timeout(90 * 1000))\n out({ type: 'result', credential: refreshed })\n} catch (error) {\n out({ type: 'error', message: error instanceof Error ? error.message : String(error) })\n}\n";
6
16
  export declare function parseDriverMessage(line: string): Record<string, unknown> | null;
7
17
  /** Bounded streaming reader; disposal/cancellation always reaps its process. */
8
18
  export declare function runDeviceDriver(shell: ShellExecutor, modulePath: string, signal: AbortSignal, notify: (code: string, url: string) => void, options?: {
9
19
  timeoutMs?: number;
20
+ shellTimeoutMs?: number;
10
21
  codeTimeoutMs?: number;
11
22
  pollMs?: number;
12
23
  }): Promise<OAuthCredential | null>;
package/dist/driver.js CHANGED
@@ -1,10 +1,19 @@
1
1
  import { setTimeout as delay } from 'node:timers/promises';
2
- import { SubscriptionError, failureCode, oauthFailureCode } from './errors.js';
2
+ import { SubscriptionError, failureCode, logDriverStderr, oauthFailureCode } from './errors.js';
3
3
  import { normalizeOAuthCredential } from './oauth.js';
4
4
  import { buildNodeCommand } from './platform.js';
5
5
  export const VERIFICATION_URL = 'https://auth.openai.com/codex/device';
6
6
  const MAX_OUTPUT = 256 * 1024;
7
- const DEVICE_TIMEOUT = 15 * 60 * 1000;
7
+ /**
8
+ * Abandon the device-code wait just before the executor's own hard deadline.
9
+ * DSH's default bash executor arms `timeoutMs` with `onExpiry: 'kill'` and caps
10
+ * it at its `maxTimeoutMs` (600000ms today), so a request that never names a
11
+ * timeout is SIGKILLed after the 120000ms default — before any device flow
12
+ * could finish — and a silently killed child cannot report a reason of its own.
13
+ */
14
+ export const DEVICE_TIMEOUT_MS = 570 * 1000;
15
+ /** Requested executor deadline: the largest value DSH's bash executor accepts. */
16
+ export const DEVICE_SHELL_TIMEOUT_MS = 600 * 1000;
8
17
  // Catch import failures as well as OAuth errors. Tokens only travel over stdin/stdout,
9
18
  // never in command arguments or environment variables.
10
19
  export const DRIVER_DEVICE = `
@@ -54,11 +63,14 @@ export function parseDriverMessage(line) {
54
63
  export async function runDeviceDriver(shell, modulePath, signal, notify, options = {}) {
55
64
  if (signal.aborted)
56
65
  return null;
57
- const timeout = AbortSignal.timeout(options.timeoutMs ?? DEVICE_TIMEOUT);
66
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? DEVICE_TIMEOUT_MS);
58
67
  const combined = AbortSignal.any([signal, timeout]);
59
68
  const spec = shell.resolve({
60
69
  command: buildNodeCommand(DRIVER_DEVICE, modulePath),
61
70
  signal: combined,
71
+ // Without an explicit request the executor applies its own short default
72
+ // deadline and kills the child while the user is still entering the code.
73
+ timeoutMs: options.shellTimeoutMs ?? DEVICE_SHELL_TIMEOUT_MS,
62
74
  stdoutMaxBytes: MAX_OUTPUT,
63
75
  });
64
76
  let proc;
@@ -80,12 +92,11 @@ export async function runDeviceDriver(shell, modulePath, signal, notify, options
80
92
  let codeReceived = false;
81
93
  let credential = null;
82
94
  const codeDeadline = Date.now() + (options.codeTimeoutMs ?? 45_000);
95
+ const diagnostics = proc;
83
96
  try {
84
97
  while (true) {
85
98
  if (signal.aborted)
86
99
  return null;
87
- if (timeout.aborted || (!codeReceived && Date.now() >= codeDeadline))
88
- throw new SubscriptionError('timeout');
89
100
  const read = proc.readOutput();
90
101
  total += Buffer.byteLength(read.delta);
91
102
  if (read.lossy || total > MAX_OUTPUT)
@@ -129,8 +140,21 @@ export async function runDeviceDriver(shell, modulePath, signal, notify, options
129
140
  buffer += tail.delta;
130
141
  continue;
131
142
  }
143
+ logDriverStderr(diagnostics.observed?.stderr, 'device sign-in');
144
+ // Our own wait is the only other ending: an expired device flow.
145
+ if (combined.aborted)
146
+ throw new SubscriptionError('sign-in-timeout');
147
+ // A handle may omit `signal` entirely, so only a real terminating signal
148
+ // (the executor's deadline SIGKILL/SIGTERM) or a code already on screen
149
+ // identifies an interrupted sign-in rather than a refused launch.
150
+ if (codeReceived || proc.signal != null)
151
+ throw new SubscriptionError('sign-in-timeout');
132
152
  throw new SubscriptionError(proc.sandbox?.denied ? 'access-denied' : 'process-exited');
133
153
  }
154
+ // Only a wait that produced no message may expire: an authorization that
155
+ // landed within this poll window is still a success, not a timeout.
156
+ if (timeout.aborted || (!codeReceived && Date.now() >= codeDeadline))
157
+ throw new SubscriptionError('timeout');
134
158
  await delay(options.pollMs ?? 250, undefined, { signal: combined }).catch(() => { });
135
159
  }
136
160
  }
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Stable, non-sensitive failures shared by the host's public boundaries. */
2
- export declare const FAILURE_CODES: readonly ['credentials-unavailable', 'shell-unavailable', 'timer-unavailable', 'component-unavailable', 'runtime-unsupported', 'busy', 'invalid-method', 'not-connected', 'not-refreshable', 'device-auth-disabled', 'access-denied', 'authorization-expired', 'rate-limited', 'network', 'timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed', 'settings-unavailable', 'models-unavailable', 'models-empty', 'models-confirmation-required', 'settings-conflict', 'settings-write-failed', 'ownership-save-failed', 'invalid-context-window', 'context-window-exceeded', 'model-not-found', 'cancelled', 'unknown'];
2
+ export declare const FAILURE_CODES: readonly ['credentials-unavailable', 'shell-unavailable', 'timer-unavailable', 'component-unavailable', 'runtime-unsupported', 'busy', 'invalid-method', 'not-connected', 'not-refreshable', 'device-auth-disabled', 'access-denied', 'authorization-expired', 'rate-limited', 'network', 'timeout', 'sign-in-timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed', 'settings-unavailable', 'models-unavailable', 'models-empty', 'models-confirmation-required', 'settings-conflict', 'settings-write-failed', 'ownership-save-failed', 'invalid-context-window', 'context-window-exceeded', 'model-not-found', 'cancelled', 'unknown'];
3
3
  export type FailureCode = typeof FAILURE_CODES[number];
4
4
  /** The marker survives RPC implementations which serialize only Error.message. */
5
5
  export declare class SubscriptionError extends Error {
@@ -11,3 +11,14 @@ export declare function failureCode(error: unknown, fallback?: FailureCode): Fai
11
11
  export declare function oauthFailureCode(error: unknown): FailureCode;
12
12
  /** Logs deliberately omit raw server responses, paths, tokens and account ids. */
13
13
  export declare function logFailure(operation: string, error: unknown): void;
14
+ /**
15
+ * One bounded, redacted line of a failed child's stderr. The driver protocol
16
+ * travels on stdout, so its stderr is what actually explains an early exit;
17
+ * dropping it entirely made such failures undiagnosable. Anything that looks
18
+ * like a credential is withheld rather than printed.
19
+ */
20
+ export declare function logDriverStderr(stderr: {
21
+ readFrom(fromByte: number): {
22
+ text: string;
23
+ };
24
+ } | undefined, operation: string): void;
package/dist/errors.js CHANGED
@@ -3,7 +3,7 @@ export const FAILURE_CODES = [
3
3
  'credentials-unavailable', 'shell-unavailable', 'timer-unavailable', 'component-unavailable',
4
4
  'runtime-unsupported', 'busy', 'invalid-method', 'not-connected', 'not-refreshable',
5
5
  'device-auth-disabled', 'access-denied', 'authorization-expired', 'rate-limited',
6
- 'network', 'timeout', 'invalid-response', 'process-exited', 'credential-write-failed',
6
+ 'network', 'timeout', 'sign-in-timeout', 'invalid-response', 'process-exited', 'credential-write-failed',
7
7
  'credential-changed', 'settings-unavailable', 'models-unavailable', 'models-empty',
8
8
  'models-confirmation-required', 'settings-conflict', 'settings-write-failed',
9
9
  'ownership-save-failed', 'invalid-context-window', 'context-window-exceeded', 'model-not-found',
@@ -57,3 +57,28 @@ export function oauthFailureCode(error) {
57
57
  export function logFailure(operation, error) {
58
58
  console.error(`[openai-subscription] ${operation}: ${failureCode(error)}`);
59
59
  }
60
+ /** Journal-like token shapes never reach the console, even from a failed child. */
61
+ const SENSITIVE_IN_DIAGNOSTIC = /(access|refresh|id|session|bearer|api)[_-]?token|authorization_code|code_verifier|eyJ[A-Za-z0-9_-]{8,}/i;
62
+ /**
63
+ * One bounded, redacted line of a failed child's stderr. The driver protocol
64
+ * travels on stdout, so its stderr is what actually explains an early exit;
65
+ * dropping it entirely made such failures undiagnosable. Anything that looks
66
+ * like a credential is withheld rather than printed.
67
+ */
68
+ export function logDriverStderr(stderr, operation) {
69
+ if (stderr === undefined)
70
+ return;
71
+ let text;
72
+ try {
73
+ text = stderr.readFrom(0).text;
74
+ }
75
+ catch {
76
+ return;
77
+ }
78
+ const line = text.split('\n')
79
+ .map((entry) => entry.replace(/[\u0000-\u001f\u007f]+/g, ' ').trim())
80
+ .find((entry) => entry !== '' && !/^\(node:\d+\)/.test(entry) && !/^at\s/.test(entry) && !/ExperimentalWarning|--trace-warnings/.test(entry));
81
+ if (line === undefined || SENSITIVE_IN_DIAGNOSTIC.test(line))
82
+ return;
83
+ console.error(`[openai-subscription] ${operation} child stderr: ${line.length > 200 ? line.slice(0, 200) + '…' : line}`);
84
+ }
package/dist/host.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // OAuth is delegated to the OpenAI Codex integration bundled with DSH.
3
3
  import { resolveAuthModule, buildNodeCommand, runtimeSupported } from './platform.js';
4
4
  export { authModuleCandidates } from './platform.js';
5
- import { DRIVER_REFRESH, parseDriverMessage, runDeviceDriver } from './driver.js';
5
+ import { DRIVER_REFRESH, DEVICE_SHELL_TIMEOUT_MS, DEVICE_TIMEOUT_MS, parseDriverMessage, runDeviceDriver } from './driver.js';
6
6
  import { SubscriptionError, failureCode, oauthFailureCode, logFailure } from './errors.js';
7
7
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
8
8
  import { normalizeOAuthCredential } from './oauth.js';
@@ -172,7 +172,7 @@ class OpenAISubscriptionController extends TypertRemoteService {
172
172
  notify({ kind: 'requesting-code', message: '正在向 OpenAI 请求设备登录码…' });
173
173
  const granted = await runDeviceDriver(shell, modulePath, control.signal, (code, url) => {
174
174
  notify({ kind: 'enter-code', message: '请在 OpenAI 验证页面输入设备码。', code, url });
175
- });
175
+ }, { timeoutMs: DEVICE_TIMEOUT_MS, shellTimeoutMs: DEVICE_SHELL_TIMEOUT_MS });
176
176
  if (granted === null || control.aborted())
177
177
  return null;
178
178
  await credentials.modifyRecord(KEY, async (current) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-openai-subscription",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "ChatGPT subscription sign-in for DeepSeek Harness via OpenAI device authorization.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.6.3",