@goodandready/dsh-subscriptions 0.4.15 → 0.4.16

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.
@@ -10,6 +10,12 @@ export const id = 'codex'
10
10
  const AUTH = 'https://auth.openai.com/oauth/authorize'
11
11
  const TOKEN = 'https://auth.openai.com/oauth/token'
12
12
  const USAGE = 'https://chatgpt.com/backend-api/wham/usage'
13
+ // #90: device-code login (headless). OpenAI mints an authorization_code +
14
+ // code_verifier server-side; we finish with the normal PKCE exchange.
15
+ const DEVICE_USERCODE_URL = 'https://auth.openai.com/api/accounts/deviceauth/usercode'
16
+ const DEVICE_TOKEN_URL = 'https://auth.openai.com/api/accounts/deviceauth/token'
17
+ export const DEVICE_AUTH_URL = 'https://auth.openai.com/codex/device'
18
+ const DEVICE_REDIRECT_URI = 'https://auth.openai.com/deviceauth/callback'
13
19
  const SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke'
14
20
  const INSTRUCTIONS = 'You are a coding assistant using a ChatGPT Codex subscription.'
15
21
 
@@ -82,6 +88,45 @@ export async function exchangeCode(cfg, pkce, code, fetchImpl) {
82
88
  return decorate(tokenBlobFromOAuth(json), json)
83
89
  }
84
90
 
91
+ export async function deviceStart(cfg, fetchImpl) {
92
+ const impl = fetchImpl || fetch
93
+ const res = await impl(DEVICE_USERCODE_URL, {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
96
+ body: JSON.stringify({ client_id: cfg.clientId }),
97
+ })
98
+ if (!res.ok) throw httpError(res.status, await res.text().catch(() => ''), 'DEVICE')
99
+ const json = await readJson(res)
100
+ if (!json || typeof json.user_code !== 'string' || typeof json.device_auth_id !== 'string') {
101
+ throw httpError(502, 'device auth response is incomplete', 'DEVICE')
102
+ }
103
+ return {
104
+ userCode: json.user_code,
105
+ deviceAuthId: json.device_auth_id,
106
+ intervalMs: Math.max(parseInt(json.interval, 10) || 5, 1) * 1000,
107
+ authUrl: DEVICE_AUTH_URL,
108
+ }
109
+ }
110
+
111
+ export async function devicePoll(cfg, session, fetchImpl) {
112
+ const impl = fetchImpl || fetch
113
+ const res = await impl(DEVICE_TOKEN_URL, {
114
+ method: 'POST',
115
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
116
+ body: JSON.stringify({ device_auth_id: session.deviceAuthId, user_code: session.userCode }),
117
+ })
118
+ // 403/404 = not confirmed yet (matches reference implementations)
119
+ if (res.status === 403 || res.status === 404) return { status: 'pending' }
120
+ if (!res.ok) throw httpError(res.status, await res.text().catch(() => ''), 'DEVICE')
121
+ const json = await readJson(res)
122
+ if (!json || typeof json.authorization_code !== 'string' || typeof json.code_verifier !== 'string') {
123
+ throw httpError(502, 'device auth token response is incomplete', 'DEVICE')
124
+ }
125
+ const deviceCfg = { ...cfg, redirectUri: DEVICE_REDIRECT_URI }
126
+ const blob = await exchangeCode(deviceCfg, { verifier: json.code_verifier }, json.authorization_code, impl)
127
+ return { status: 'authorized', blob }
128
+ }
129
+
85
130
  export async function refresh(cfg, blob, fetchImpl) {
86
131
  const json = await formTokenRequest(TOKEN, {
87
132
  grant_type: 'refresh_token',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, and Antigravity subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,13 +51,17 @@
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@deepseek-ai/cordis": "^4.0.1",
54
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
55
54
  "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
56
55
  "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
56
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
57
57
  "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
58
58
  "@deepseek-ai/schemastery": "^3.18.1"
59
59
  },
60
60
  "publishConfig": {
61
61
  "access": "public"
62
+ },
63
+ "dependencies": {
64
+ "socks": "^2.8.9",
65
+ "undici": "^8.10.1"
62
66
  }
63
67
  }