@edge-sky/dsh-oauth-adapter 0.1.1-rc.8 → 0.1.2-beta.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/README.i18n.yaml CHANGED
@@ -1,4 +1,4 @@
1
1
  # Bilingual-pair consistency record: the git blob hash of each side as of the
2
2
  # last confirmed-consistent state. Both languages carry equal authority.
3
- README.md: a39895b8270728ff304536ba2df6d927e21f6f20
4
- README.zh.md: b2d5ac29a0087cf4df1f7a7ce7757acbe780fc3c
3
+ README.md: ca7554635b97303928fe3ad09f3ee53c9ce96e94
4
+ README.zh.md: 8e53233889cfe60ca4c0c19b2a26064400eceaf4
package/README.md CHANGED
@@ -2,43 +2,49 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- An OAuth account page for the Web profile of DSH `0.1.1-rc.2`. It supports:
5
+ An OAuth account page for the DSH Web profile. With the pi-ai catalog bundled by DSH `0.1.1-rc.2`, it supports:
6
6
 
7
- - OpenAI Codex
8
- - GitHub Copilot
7
+ | Provider | pi-ai sign-in path |
8
+ | --- | --- |
9
+ | OpenAI Codex | Browser callback or device code |
10
+ | GitHub Copilot | GitHub or GitHub Enterprise device code |
11
+ | Anthropic | Browser callback with manual code/redirect fallback |
12
+ | Kimi For Coding | Device code |
13
+ | OpenRouter | Browser PKCE callback |
14
+ | Radius | Browser PKCE callback or device code |
15
+ | xAI | Device code |
9
16
 
10
- This release is intentionally limited to DSH `0.1.1-rc.2`. It does not support headless or ACP profiles, remote Web Hosts, or later DSH releases.
17
+ Each account is shown only as available when the installed `@deepseek-ai/dsh-llm-pi-ai` actually registers its OAuth flow. This keeps older or differently bundled pi-ai versions fail-closed instead of exposing a login button that cannot work.
18
+
19
+ The adapter has only been tested with DSH `0.1.1-rc.2`; compatibility with future versions is not guaranteed.
11
20
 
12
21
  ## Install
13
22
 
14
- Install the exact adapter version into the official rc2 Web profile:
23
+ ```sh
24
+ dsh plugin --profile web add @edge-sky/dsh-oauth-adapter
25
+ ```
26
+
27
+ or
15
28
 
16
29
  ```sh
17
- npx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add \
18
- @edge-sky/dsh-oauth-adapter@0.1.1-rc.6 \
19
- --save-exact
30
+ npx @deepseek-ai/dsh plugin --profile web add @edge-sky/dsh-oauth-adapter
20
31
  ```
21
32
 
22
33
  Start DSH:
23
34
 
24
35
  ```sh
25
- npx @deepseek-ai/dsh@0.1.1-rc.2 web
36
+ npx @deepseek-ai/dsh web
26
37
  ```
27
38
 
28
- Open **Settings → OAuth Accounts** to connect or disconnect an account.
29
-
30
- GitHub Copilot accepts an optional GitHub Enterprise domain during sign-in. Leave it blank to use `github.com`.
39
+ <img width="2270" height="1524" alt="image" src="https://github.com/user-attachments/assets/78a59190-8029-4c60-bc50-b763312540f7" />
31
40
 
32
- After sign-in, the adapter activates the matching official `llm-pi-ai` route. The model selector lists it as **OpenAI Codex (OAuth)** or **GitHub Copilot (OAuth)**, in a provider group separate from API-key and custom gateway routes. Reopen the model selector to refresh its catalog after connecting an account.
41
+ <img width="2270" height="1524" alt="image" src="https://github.com/user-attachments/assets/e0cc6591-73a4-4e87-9ef6-68950126b290" />
33
42
 
34
- The installer may report missing peers for `dsh-authorization`. In the rc2 profile layout, those services are supplied by the official DSH runtime outside the profile's package tree; the adapter intentionally leaves them as peers so pnpm does not install a second Cordis/DSH runtime.
35
43
 
36
44
  ## How it works
37
45
 
38
- The bundle mounts the official `@deepseek-ai/dsh-authorization@0.1.1-rc.2` service and this adapter. The official `dsh-llm-pi-ai` plugin detects that service and registers its existing `openai-codex` and `github-copilot` OAuth flows. This adapter does not implement or register provider flows itself.
39
-
40
- The adapter adds a separate Settings page and a loopback-only, same-origin WebSocket used for interactive notices and prompts. Credentials remain owned by the official DSH credential service under `llm-pi-ai/openai-codex` and `llm-pi-ai/github-copilot`; credential values are never returned to the browser. Secret prompt answers are held only while the prompt is active and are not logged or persisted by the adapter.
46
+ DSH integrates pi-ai through the `@deepseek-ai/dsh-llm-pi-ai` adapter, but does not provide an OAuth entry point. `@edge-sky/dsh-oauth-adapter` exposes pi-ai's existing provider login flows through the DSH Authorization Service. Browser links, device codes, text and secret input, method selection, cancellation, and withdrawn prompts all use the same provider-neutral transport.
41
47
 
42
- An authorized account needs both a stored credential and an enabled model route. The adapter writes only the matching OAuth provider entry under `llm-pi-ai.providers` and preserves unrelated provider configuration. Signing out removes a route created solely by the adapter; a route with additional user configuration is retained.
48
+ Because DSH does not currently mount the `ctx.authorization` service, this plugin also mounts it manually.
43
49
 
44
- Set `debug: true` in the plugin configuration only when diagnosing connection lifecycle. Debug output excludes credentials, prompt answers, and provider payloads.
50
+ The plugin itself does not implement provider OAuth protocols, manage OAuth credentials, or maintain login state. pi-ai owns login and token refresh; the DSH Credentials Service owns persistence; this adapter owns only the local Web UI bridge and model-route activation.
package/README.zh.md CHANGED
@@ -2,43 +2,49 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 为 DSH `0.1.1-rc.2` 的 Web profile 提供独立的 OAuth 账户页面。当前支持:
5
+ 为 DSH Web profile 提供独立的 OAuth 账户页面。配合 DSH `0.1.1-rc.2` 内置的 pi-ai catalog,当前支持:
6
6
 
7
- - OpenAI Codex
8
- - GitHub Copilot
7
+ | 提供商 | pi-ai 登录路径 |
8
+ | --- | --- |
9
+ | OpenAI Codex | 浏览器回调或设备码 |
10
+ | GitHub Copilot | GitHub 或 GitHub Enterprise 设备码 |
11
+ | Anthropic | 浏览器回调,并支持手工粘贴授权码/重定向地址 |
12
+ | Kimi For Coding | 设备码 |
13
+ | OpenRouter | 浏览器 PKCE 回调 |
14
+ | Radius | 浏览器 PKCE 回调或设备码 |
15
+ | xAI | 设备码 |
9
16
 
10
- 此版本仅兼容 DSH `0.1.1-rc.2`,不支持 headless、ACP、远程 Web Host 或更高版本的 DSH。
17
+ 只有当当前安装的 `@deepseek-ai/dsh-llm-pi-ai` 确实注册了对应 OAuth 流程时,账户才会显示为可用。这样旧版或采用不同 pi-ai bundle 的 DSH 会安全地禁用登录按钮,而不会暴露无法完成的流程。
18
+
19
+ 当前仅对 DSH`0.1.1-rc.2`进行了测试,不保证未来版本的兼容性
11
20
 
12
21
  ## 安装
13
22
 
14
- 将指定版本的适配器安装到官方 rc2 Web profile:
23
+ ```sh
24
+ dsh plugin --profile web add @edge-sky/dsh-oauth-adapter
25
+ ```
26
+
27
+ 或
15
28
 
16
29
  ```sh
17
- npx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add \
18
- @edge-sky/dsh-oauth-adapter@0.1.1-rc.6 \
19
- --save-exact
30
+ npx @deepseek-ai/dsh plugin --profile web add @edge-sky/dsh-oauth-adapter
20
31
  ```
21
32
 
22
33
  启动 DSH:
23
34
 
24
35
  ```sh
25
- npx @deepseek-ai/dsh@0.1.1-rc.2 web
36
+ npx @deepseek-ai/dsh web
26
37
  ```
27
38
 
28
- 打开**设置 → OAuth 账户**,即可连接或断开账户。
29
-
30
- GitHub Copilot 登录时可以填写 GitHub Enterprise 域名;留空即使用 `github.com`。
39
+ <img width="2270" height="1524" alt="image" src="https://github.com/user-attachments/assets/134ec856-e17b-4800-bb87-069851986d3e" />
31
40
 
32
- 登录后,适配器会启用对应的官方 `llm-pi-ai` 路由。模型选择器会将其显示为 **OpenAI Codex (OAuth)** 或 **GitHub Copilot (OAuth)**,并与 API 密钥及自定义网关路由分组显示。连接账户后,重新打开模型选择器即可刷新模型目录。
41
+ <img width="2270" height="1524" alt="image" src="https://github.com/user-attachments/assets/8007002a-a036-4cf4-b015-d4e50eff25d9" />
33
42
 
34
- 安装时可能出现 `dsh-authorization` 缺少 peer 的提示。rc2 profile 的这些服务由 profile 包目录之外的官方 DSH runtime 提供;适配器有意将它们保留为 peer,避免 pnpm 安装第二套 Cordis/DSH runtime。
35
43
 
36
44
  ## 工作原理
37
45
 
38
- Bundle 会挂载官方 `@deepseek-ai/dsh-authorization@0.1.1-rc.2` 服务和本适配器。官方 `dsh-llm-pi-ai` 插件检测到该服务后,会注册已有的 `openai-codex` 和 `github-copilot` OAuth flow。本适配器不自行实现或重复注册 provider flow。
39
-
40
- 适配器增加独立的设置页面,以及用于交互提示的仅限 loopback、要求同源的 WebSocket。凭证继续由官方 DSH credential 服务管理,key 分别为 `llm-pi-ai/openai-codex` 和 `llm-pi-ai/github-copilot`;凭证内容不会返回浏览器。Secret prompt 的回答只在 prompt 活跃期间保留,适配器不会记录或持久化这些内容。
46
+ DSH 通过 `@deepseek-ai/dsh-llm-pi-ai` 适配器接入 pi-ai,但并没有提供 OAuth 的入口。`@edge-sky/dsh-oauth-adapter` 通过 DSH Authorization Service 暴露 pi-ai 已有的 provider 登录流程。浏览器链接、设备码、文本与密码输入、登录方式选择、取消和撤回提示均通过同一套 provider-neutral 传输完成。
41
47
 
42
- 已授权账户需要同时具备已保存凭证和已启用模型路由。适配器只会在 `llm-pi-ai.providers` 下写入对应的 OAuth provider,并保留其他 provider 配置。退出登录时,仅由适配器创建的路由会被删除;含有其他用户配置的路由会继续保留。
48
+ 由于 DSH 目前并没有挂载`ctx.authorization`服务,因此该插件同时手动对其进行了挂载
43
49
 
44
- 仅在诊断连接生命周期时将插件配置中的 `debug` 设为 `true`。调试日志不会输出凭证、prompt 回答或 provider payload。
50
+ 插件本身不实现各提供商的 OAuth 协议,也不管理 OAuth 凭证或维持登录态。登录与 token 刷新由 pi-ai 负责,持久化由 DSH Credentials Service 负责,本适配器只负责本机 Web UI 桥接和模型路由启用。
package/cordis.patch.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  - insert:
2
- - id: authorization
3
- name: '@deepseek-ai/dsh-authorization'
2
+ - id: dsh-oauth-authorization
3
+ name: '@edge-sky/dsh-oauth-adapter/authorization-fallback'
4
4
 
5
5
  - id: dsh-oauth-adapter
6
6
  name: '@edge-sky/dsh-oauth-adapter'
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Best-effort authorization provider for DSH Web profiles that do not mount
3
+ * the official service themselves.
4
+ * @module @edge-sky/dsh-oauth-adapter/authorization-fallback
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ /** Cordis plugin name. */
8
+ export declare const name = "dsh-oauth-authorization-fallback";
9
+ /**
10
+ * Reuse a Host-provided authorization service or mount the official fallback.
11
+ *
12
+ * The microtask yield lets earlier Profile Bundle rows publish their services
13
+ * before this out-of-tree bundle decides whether a fallback is necessary.
14
+ *
15
+ * @param ctx - Loader context used to inspect and extend the Host service set.
16
+ * @returns A promise that settles after the fallback has been mounted or skipped.
17
+ */
18
+ export declare function apply(ctx: Context): Promise<void>;
19
+ //# sourceMappingURL=authorization-fallback.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Best-effort authorization provider for DSH Web profiles that do not mount
3
+ * the official service themselves.
4
+ * @module @edge-sky/dsh-oauth-adapter/authorization-fallback
5
+ */
6
+ import AuthorizationService from '@deepseek-ai/dsh-authorization';
7
+ /** Cordis plugin name. */
8
+ export const name = 'dsh-oauth-authorization-fallback';
9
+ /**
10
+ * Reuse a Host-provided authorization service or mount the official fallback.
11
+ *
12
+ * The microtask yield lets earlier Profile Bundle rows publish their services
13
+ * before this out-of-tree bundle decides whether a fallback is necessary.
14
+ *
15
+ * @param ctx - Loader context used to inspect and extend the Host service set.
16
+ * @returns A promise that settles after the fallback has been mounted or skipped.
17
+ */
18
+ export async function apply(ctx) {
19
+ await Promise.resolve();
20
+ if (ctx.get('authorization') !== undefined)
21
+ return;
22
+ await ctx.plugin(AuthorizationService);
23
+ }
24
+ //# sourceMappingURL=authorization-fallback.js.map
package/lib/client.js CHANGED
@@ -6,6 +6,55 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/protocol.ts
10
+ /** Shared browser/Host wire vocabulary for the OAuth account surface. */
11
+ const OAUTH_SOCKET_PATH = "/_edge-sky/dsh-oauth";
12
+ const OAUTH_SOCKET_PROTOCOL = "dsh-oauth-v1";
13
+ const PROVIDERS = [
14
+ {
15
+ id: "openai-codex",
16
+ key: "llm-pi-ai/openai-codex",
17
+ fallbackLabel: "OpenAI Codex",
18
+ modelGroupLabel: "OpenAI Codex (OAuth)"
19
+ },
20
+ {
21
+ id: "github-copilot",
22
+ key: "llm-pi-ai/github-copilot",
23
+ fallbackLabel: "GitHub Copilot",
24
+ modelGroupLabel: "GitHub Copilot (OAuth)"
25
+ },
26
+ {
27
+ id: "anthropic",
28
+ key: "llm-pi-ai/anthropic",
29
+ fallbackLabel: "Anthropic",
30
+ modelGroupLabel: "Anthropic (OAuth)"
31
+ },
32
+ {
33
+ id: "kimi-coding",
34
+ key: "llm-pi-ai/kimi-coding",
35
+ fallbackLabel: "Kimi For Coding",
36
+ modelGroupLabel: "Kimi For Coding (OAuth)"
37
+ },
38
+ {
39
+ id: "openrouter",
40
+ key: "llm-pi-ai/openrouter",
41
+ fallbackLabel: "OpenRouter",
42
+ modelGroupLabel: "OpenRouter (OAuth)"
43
+ },
44
+ {
45
+ id: "radius",
46
+ key: "llm-pi-ai/radius",
47
+ fallbackLabel: "Radius",
48
+ modelGroupLabel: "Radius (OAuth)"
49
+ },
50
+ {
51
+ id: "xai",
52
+ key: "llm-pi-ai/xai",
53
+ fallbackLabel: "xAI",
54
+ modelGroupLabel: "xAI (OAuth)"
55
+ }
56
+ ];
57
+ //#endregion
9
58
  //#region src/client/OAuthAccountsSection.tsx
10
59
  /** OAuth account settings page. */
11
60
  function safeHttpUrl(value) {
@@ -92,6 +141,16 @@ window.__ModuleLoader__.load({
92
141
  ]
93
142
  });
94
143
  }
144
+ function ErrorPanel(props) {
145
+ const collapsible = props.detail !== void 0 && (props.detail.includes("\n") || props.detail.length > 240);
146
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
147
+ className: "dsh-oauth-error",
148
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: props.message }), props.detail !== void 0 && (collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
149
+ className: "dsh-oauth-error-details",
150
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: props.t("technicalDetails") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: props.detail })]
151
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: props.detail }))]
152
+ });
153
+ }
95
154
  function AttemptPanel(props) {
96
155
  const [copied, setCopied] = (0, react.useState)(false);
97
156
  const url = safeHttpUrl(props.attempt.notice?.url);
@@ -125,9 +184,10 @@ window.__ModuleLoader__.load({
125
184
  controller: props.controller,
126
185
  t: props.t
127
186
  }, props.attempt.prompt.id),
128
- props.attempt.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
129
- className: "dsh-oauth-error",
130
- children: props.attempt.error
187
+ props.attempt.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorPanel, {
188
+ message: props.attempt.error,
189
+ ...props.attempt.errorDetail === void 0 ? {} : { detail: props.attempt.errorDetail },
190
+ t: props.t
131
191
  }),
132
192
  props.attempt.id !== void 0 && (props.attempt.phase === "running" || props.attempt.phase === "starting") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
133
193
  className: "dsh-oauth-button",
@@ -158,9 +218,10 @@ window.__ModuleLoader__.load({
158
218
  className: "dsh-oauth-intro",
159
219
  children: t("intro")
160
220
  }),
161
- snapshot.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
162
- className: "dsh-oauth-error",
163
- children: snapshot.error
221
+ snapshot.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorPanel, {
222
+ message: snapshot.error,
223
+ ...snapshot.errorDetail === void 0 ? {} : { detail: snapshot.errorDetail },
224
+ t
164
225
  }),
165
226
  snapshot.connection !== "open" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
166
227
  className: "dsh-oauth-button",
@@ -177,7 +238,7 @@ window.__ModuleLoader__.load({
177
238
  }),
178
239
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
179
240
  className: "dsh-oauth-list",
180
- children: ["openai-codex", "github-copilot"].map((provider) => {
241
+ children: PROVIDERS.map(({ id: provider }) => {
181
242
  const account = accountByProvider.get(provider);
182
243
  const attempt = snapshot.attempt?.provider === provider ? snapshot.attempt : void 0;
183
244
  const busy = account?.inFlight === true || attempt?.phase === "running" || attempt?.phase === "starting";
@@ -261,6 +322,7 @@ window.__ModuleLoader__.load({
261
322
  authorized: "Sign-in completed.",
262
323
  cancelled: "Sign-in was cancelled.",
263
324
  failed: "Sign-in failed.",
325
+ technicalDetails: "Technical details",
264
326
  submit: "Continue",
265
327
  copy: "Copy code",
266
328
  copied: "Copied",
@@ -292,6 +354,7 @@ window.__ModuleLoader__.load({
292
354
  authorized: "登录已完成。",
293
355
  cancelled: "登录已取消。",
294
356
  failed: "登录失败。",
357
+ technicalDetails: "技术详情",
295
358
  submit: "继续",
296
359
  copy: "复制代码",
297
360
  copied: "已复制",
@@ -303,11 +366,6 @@ window.__ModuleLoader__.load({
303
366
  hostDisconnected: "与 DSH 的 OAuth 连接已中断。"
304
367
  };
305
368
  //#endregion
306
- //#region src/protocol.ts
307
- /** Shared browser/Host wire vocabulary for the rc2 OAuth account surface. */
308
- const OAUTH_SOCKET_PATH = "/_edge-sky/dsh-oauth";
309
- const OAUTH_SOCKET_PROTOCOL = "dsh-oauth-v1";
310
- //#endregion
311
369
  //#region src/client/store.ts
312
370
  /** Browser state machine for the plugin-owned OAuth WebSocket. */
313
371
  function socketUrl() {
@@ -355,7 +413,7 @@ window.__ModuleLoader__.load({
355
413
  getSnapshot = () => this.snapshot;
356
414
  begin(provider) {
357
415
  if (this.snapshot.attempt?.phase === "starting" || this.snapshot.attempt?.phase === "running") return;
358
- const { error: _error, ...current } = this.snapshot;
416
+ const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
359
417
  this.update({
360
418
  ...current,
361
419
  attempt: {
@@ -426,7 +484,7 @@ window.__ModuleLoader__.load({
426
484
  socket.addEventListener("open", () => {
427
485
  if (this.socket !== socket) return;
428
486
  this.reconnectDelay = 500;
429
- const { error: _error, ...current } = this.snapshot;
487
+ const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
430
488
  this.update({
431
489
  ...current,
432
490
  connection: "open"
@@ -442,10 +500,12 @@ window.__ModuleLoader__.load({
442
500
  try {
443
501
  value = JSON.parse(event.data);
444
502
  } catch {
445
- this.update({
503
+ const next = {
446
504
  ...this.snapshot,
447
505
  error: "DSH returned an invalid OAuth message."
448
- });
506
+ };
507
+ delete next.errorDetail;
508
+ this.update(next);
449
509
  return;
450
510
  }
451
511
  if (isServerMessage(value)) this.receive(value);
@@ -461,7 +521,7 @@ window.__ModuleLoader__.load({
461
521
  return;
462
522
  }
463
523
  const attempt = this.snapshot.attempt;
464
- this.update({
524
+ const interrupted = {
465
525
  ...this.snapshot,
466
526
  connection: "reconnecting",
467
527
  error: "The OAuth connection to DSH was interrupted.",
@@ -470,7 +530,10 @@ window.__ModuleLoader__.load({
470
530
  phase: "failed",
471
531
  error: "The OAuth connection to DSH was interrupted."
472
532
  } }
473
- });
533
+ };
534
+ delete interrupted.errorDetail;
535
+ if (interrupted.attempt?.error === "The OAuth connection to DSH was interrupted.") delete interrupted.attempt.errorDetail;
536
+ this.update(interrupted);
474
537
  const delay = this.reconnectDelay;
475
538
  this.reconnectDelay = Math.min(this.reconnectDelay * 2, 5e3);
476
539
  this.reconnectTimer = window.setTimeout(() => {
@@ -482,10 +545,12 @@ window.__ModuleLoader__.load({
482
545
  }
483
546
  send(command) {
484
547
  if (this.socket?.readyState !== WebSocket.OPEN) {
485
- this.update({
548
+ const next = {
486
549
  ...this.snapshot,
487
550
  error: "The OAuth connection to DSH is not ready."
488
- });
551
+ };
552
+ delete next.errorDetail;
553
+ this.update(next);
489
554
  return;
490
555
  }
491
556
  this.socket.send(JSON.stringify(command));
@@ -504,7 +569,7 @@ window.__ModuleLoader__.load({
504
569
  this.update(nextSnapshot);
505
570
  return;
506
571
  case "started":
507
- const { error: _error, ...current } = this.snapshot;
572
+ const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
508
573
  this.update({
509
574
  ...current,
510
575
  attempt: {
@@ -567,17 +632,26 @@ window.__ModuleLoader__.load({
567
632
  }
568
633
  case "error": {
569
634
  const attempt = this.snapshot.attempt;
570
- if (message.attemptId !== void 0 && attempt?.id === message.attemptId) this.update({
571
- ...this.snapshot,
572
- attempt: {
635
+ if (message.attemptId !== void 0 && attempt?.id === message.attemptId) {
636
+ const nextAttempt = {
573
637
  ...attempt,
574
638
  error: message.message
575
- }
576
- });
577
- else this.update({
578
- ...this.snapshot,
579
- error: message.message
580
- });
639
+ };
640
+ if (message.detail === void 0) delete nextAttempt.errorDetail;
641
+ else nextAttempt.errorDetail = message.detail;
642
+ this.update({
643
+ ...this.snapshot,
644
+ attempt: nextAttempt
645
+ });
646
+ } else {
647
+ const next = {
648
+ ...this.snapshot,
649
+ error: message.message
650
+ };
651
+ if (message.detail === void 0) delete next.errorDetail;
652
+ else next.errorDetail = message.detail;
653
+ this.update(next);
654
+ }
581
655
  return;
582
656
  }
583
657
  case "ack": return;
@@ -595,7 +669,7 @@ window.__ModuleLoader__.load({
595
669
  .dsh-oauth-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}
596
670
  .dsh-oauth-title{margin:0;font-size:16px;line-height:24px;font-weight:500}
597
671
  .dsh-oauth-intro,.dsh-oauth-muted{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
598
- .dsh-oauth-error{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
672
+ .dsh-oauth-error{display:flex;flex-direction:column;gap:6px;margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.dsh-oauth-error p{margin:0}.dsh-oauth-error pre{box-sizing:border-box;max-width:100%;max-height:320px;margin:0;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;overflow:auto;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:11px/17px ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.dsh-oauth-error-details summary{cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none}.dsh-oauth-error-details[open] summary{margin-bottom:6px}
599
673
  .dsh-oauth-list{display:flex;flex-direction:column;gap:8px;margin:4px 0 0;padding:0;list-style:none}
600
674
  .dsh-oauth-card{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
601
675
  .dsh-oauth-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dsh-oauth-name{font-size:14px;font-weight:500}.dsh-oauth-state{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);font-size:12px;color:var(--dsw-alias-label-secondary)}
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH 0.1.1-rc.2 Web surface for authorization flows owned by the official
2
+ * DSH Web surface for authorization flows owned by the official
3
3
  * `dsh-llm-pi-ai` plugin.
4
4
  * @module @edge-sky/dsh-oauth-adapter
5
5
  */
@@ -19,7 +19,7 @@ export interface Config {
19
19
  export declare const Config: z<Config>;
20
20
  /** Cordis plugin name. */
21
21
  export declare const name = "dsh-oauth-adapter";
22
- /** Official rc2 services required by the compatibility surface. */
22
+ /** Official services required by the OAuth account surface. */
23
23
  export declare const inject: string[];
24
24
  type OAuthContext = Context & {
25
25
  authorization: AuthorizationService;
@@ -33,6 +33,8 @@ export declare function isLoopbackAddress(address: string | undefined): boolean;
33
33
  export declare function safeNoticeUrl(value: string | undefined): string | undefined;
34
34
  /** Validate the browser handshake before `ws` takes ownership of the socket. */
35
35
  export declare function acceptsBrowserUpgrade(request: IncomingMessage): boolean;
36
+ /** Format one Host failure for browser diagnostics without forwarding credentials or unbounded data. */
37
+ export declare function formatErrorDetail(error: unknown): string;
36
38
  /** Mount the loopback-only OAuth WebSocket over official rc2 services. */
37
39
  export declare function apply(ctx: OAuthContext, config: Config): () => Promise<void>;
38
40
  export {};
package/lib/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
- * DSH 0.1.1-rc.2 Web surface for authorization flows owned by the official
2
+ * DSH Web surface for authorization flows owned by the official
3
3
  * `dsh-llm-pi-ai` plugin.
4
4
  * @module @edge-sky/dsh-oauth-adapter
5
5
  */
6
6
  import { randomUUID } from 'node:crypto';
7
+ import { inspect } from 'node:util';
7
8
  import { settingsNamespace } from '@deepseek-ai/dsh-settings';
8
9
  import z from '@deepseek-ai/schemastery';
9
10
  import WebSocket, { WebSocketServer } from 'ws';
@@ -14,7 +15,7 @@ export const Config = z.object({
14
15
  });
15
16
  /** Cordis plugin name. */
16
17
  export const name = 'dsh-oauth-adapter';
17
- /** Official rc2 services required by the compatibility surface. */
18
+ /** Official services required by the OAuth account surface. */
18
19
  export const inject = ['authorization', 'credentials', 'settings', 'webServer'];
19
20
  const MODEL_SETTINGS_NS = settingsNamespace('llm-pi-ai');
20
21
  function providerMeta(providerId) {
@@ -55,6 +56,13 @@ function promptView(prompt, provider) {
55
56
  : {},
56
57
  };
57
58
  }
59
+ function providerPromptAnswer(prompt, provider, value) {
60
+ if (provider === 'github-copilot' && prompt.kind === 'text'
61
+ && prompt.message === GITHUB_DOMAIN_PROMPT && value.trim().length === 0) {
62
+ return 'github.com';
63
+ }
64
+ return value;
65
+ }
58
66
  /** Whether a socket address belongs to the local host. */
59
67
  export function isLoopbackAddress(address) {
60
68
  return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
@@ -101,6 +109,67 @@ function rawLength(raw) {
101
109
  return raw.reduce((total, chunk) => total + chunk.byteLength, 0);
102
110
  return raw.byteLength;
103
111
  }
112
+ const MAX_ERROR_DETAIL_LENGTH = 12 * 1024;
113
+ const MAX_ERROR_CAUSE_DEPTH = 6;
114
+ function redactErrorDetail(value) {
115
+ return value
116
+ .replace(/(\bAuthorization\b\s*["']?\s*[:=]\s*["']?)(?:Bearer\s+)?([^"'\s,}&]+)/giu, '$1[REDACTED]')
117
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/giu, 'Bearer [REDACTED]')
118
+ .replace(/(\b(?:access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|api[_-]?key)\b\s*["']?\s*[:=]\s*["']?)([^"'\s,}&]+)/giu, '$1[REDACTED]')
119
+ .replace(/\b(?:github_pat_|gh[opusr]_|sk-)[A-Za-z0-9_-]{12,}\b/gu, '[REDACTED]');
120
+ }
121
+ function errorProperties(value) {
122
+ const record = value;
123
+ const properties = ['code', 'status', 'statusCode']
124
+ .flatMap((key) => {
125
+ const entry = record[key];
126
+ return typeof entry === 'string' || typeof entry === 'number' ? [`${key}: ${String(entry)}`] : [];
127
+ });
128
+ return properties.length === 0 ? undefined : properties.join(', ');
129
+ }
130
+ /** Format one Host failure for browser diagnostics without forwarding credentials or unbounded data. */
131
+ export function formatErrorDetail(error) {
132
+ const sections = [];
133
+ const seen = new Set();
134
+ let current = error;
135
+ for (let depth = 0; current !== undefined && depth < MAX_ERROR_CAUSE_DEPTH; depth += 1) {
136
+ if (typeof current === 'object' && current !== null) {
137
+ if (seen.has(current)) {
138
+ sections.push('Caused by: [circular error cause]');
139
+ current = undefined;
140
+ break;
141
+ }
142
+ seen.add(current);
143
+ }
144
+ const prefix = depth === 0 ? '' : 'Caused by: ';
145
+ if (current instanceof Error) {
146
+ const properties = errorProperties(current);
147
+ const rendered = current.stack ?? `${current.name}: ${current.message}`;
148
+ sections.push(`${prefix}${rendered}${properties === undefined ? '' : `\n${properties}`}`);
149
+ current = current.cause;
150
+ continue;
151
+ }
152
+ sections.push(prefix + (typeof current === 'string'
153
+ ? current
154
+ : inspect(current, {
155
+ breakLength: 120,
156
+ compact: false,
157
+ customInspect: false,
158
+ depth: 4,
159
+ maxArrayLength: 40,
160
+ maxStringLength: 4096,
161
+ sorted: true,
162
+ })));
163
+ current = undefined;
164
+ break;
165
+ }
166
+ if (current !== undefined)
167
+ sections.push('Caused by: [additional causes omitted]');
168
+ const redacted = redactErrorDetail(sections.join('\n'));
169
+ if (redacted.length <= MAX_ERROR_DETAIL_LENGTH)
170
+ return redacted;
171
+ return `${redacted.slice(0, MAX_ERROR_DETAIL_LENGTH)}\n[diagnostic truncated]`;
172
+ }
104
173
  /** One ownership-scoped browser authorization connection. */
105
174
  class OAuthConnection {
106
175
  ctx;
@@ -124,7 +193,7 @@ class OAuthConnection {
124
193
  this.queue = this.queue.then(() => this.receive(raw.toString())).catch((error) => {
125
194
  this.ctx.logger.warn('dsh-oauth-adapter: command handling failed');
126
195
  this.ctx.logger.warn(error);
127
- this.sendError('operation-failed', 'the OAuth operation failed');
196
+ this.sendError('operation-failed', 'the OAuth operation failed', undefined, undefined, formatErrorDetail(error));
128
197
  });
129
198
  });
130
199
  socket.on('close', () => {
@@ -150,11 +219,12 @@ class OAuthConnection {
150
219
  if (this.socket.readyState === WebSocket.OPEN)
151
220
  this.socket.send(JSON.stringify(message));
152
221
  }
153
- sendError(code, message, requestId, attemptId) {
222
+ sendError(code, message, requestId, attemptId, detail) {
154
223
  this.send({
155
224
  type: 'error', code, message,
156
225
  ...requestId === undefined ? {} : { requestId },
157
226
  ...attemptId === undefined ? {} : { attemptId },
227
+ ...detail === undefined ? {} : { detail },
158
228
  });
159
229
  }
160
230
  async receive(raw) {
@@ -226,7 +296,7 @@ class OAuthConnection {
226
296
  catch (error) {
227
297
  this.ctx.logger.warn('dsh-oauth-adapter: credential status read failed');
228
298
  this.ctx.logger.warn(error);
229
- this.sendError('operation-failed', 'account status is unavailable', requestId);
299
+ this.sendError('operation-failed', 'account status is unavailable', requestId, undefined, formatErrorDetail(error));
230
300
  }
231
301
  }
232
302
  begin(command) {
@@ -278,14 +348,14 @@ class OAuthConnection {
278
348
  catch (error) {
279
349
  this.ctx.logger.warn('dsh-oauth-adapter: model route activation failed for %s', provider.id);
280
350
  this.ctx.logger.warn(error);
281
- this.sendError('model-route-unavailable', 'sign-in completed, but the OAuth model route could not be enabled', undefined, attempt.id);
351
+ this.sendError('model-route-unavailable', 'sign-in completed, but the OAuth model route could not be enabled', undefined, attempt.id, formatErrorDetail(error));
282
352
  }
283
353
  }
284
354
  this.finish(attempt, outcome.status);
285
355
  }, (error) => {
286
356
  this.ctx.logger.warn('dsh-oauth-adapter: provider sign-in failed for %s', attempt.provider);
287
357
  this.ctx.logger.warn(error);
288
- this.sendError('sign-in-failed', 'sign-in failed; inspect the DSH Host log', undefined, attempt.id);
358
+ this.sendError('sign-in-failed', 'sign-in failed', undefined, attempt.id, formatErrorDetail(error));
289
359
  this.finish(attempt, 'failed');
290
360
  });
291
361
  }
@@ -310,7 +380,7 @@ class OAuthConnection {
310
380
  });
311
381
  };
312
382
  attempt.prompts.set(promptId, {
313
- resolve: value => { finish(() => { resolve(value); }); },
383
+ resolve: value => { finish(() => { resolve(providerPromptAnswer(prompt, attempt.provider, value)); }); },
314
384
  reject: reason => { finish(() => { reject(reason); }); },
315
385
  });
316
386
  prompt.signal?.addEventListener('abort', withdraw, { once: true });
@@ -370,7 +440,7 @@ class OAuthConnection {
370
440
  catch (error) {
371
441
  this.ctx.logger.warn('dsh-oauth-adapter: sign-out failed for %s', provider.id);
372
442
  this.ctx.logger.warn(error);
373
- this.sendError('operation-failed', 'sign-out failed; inspect the DSH Host log', command.requestId);
443
+ this.sendError('operation-failed', 'sign-out failed', command.requestId, undefined, formatErrorDetail(error));
374
444
  }
375
445
  }
376
446
  finish(attempt, status) {
package/lib/protocol.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Shared browser/Host wire vocabulary for the rc2 OAuth account surface. */
1
+ /** Shared browser/Host wire vocabulary for the OAuth account surface. */
2
2
  export declare const OAUTH_SOCKET_PATH = "/_edge-sky/dsh-oauth";
3
3
  export declare const OAUTH_SOCKET_PROTOCOL = "dsh-oauth-v1";
4
4
  export declare const MAX_FRAME_BYTES: number;
@@ -13,6 +13,31 @@ export declare const PROVIDERS: readonly [{
13
13
  readonly key: "llm-pi-ai/github-copilot";
14
14
  readonly fallbackLabel: "GitHub Copilot";
15
15
  readonly modelGroupLabel: "GitHub Copilot (OAuth)";
16
+ }, {
17
+ readonly id: "anthropic";
18
+ readonly key: "llm-pi-ai/anthropic";
19
+ readonly fallbackLabel: "Anthropic";
20
+ readonly modelGroupLabel: "Anthropic (OAuth)";
21
+ }, {
22
+ readonly id: "kimi-coding";
23
+ readonly key: "llm-pi-ai/kimi-coding";
24
+ readonly fallbackLabel: "Kimi For Coding";
25
+ readonly modelGroupLabel: "Kimi For Coding (OAuth)";
26
+ }, {
27
+ readonly id: "openrouter";
28
+ readonly key: "llm-pi-ai/openrouter";
29
+ readonly fallbackLabel: "OpenRouter";
30
+ readonly modelGroupLabel: "OpenRouter (OAuth)";
31
+ }, {
32
+ readonly id: "radius";
33
+ readonly key: "llm-pi-ai/radius";
34
+ readonly fallbackLabel: "Radius";
35
+ readonly modelGroupLabel: "Radius (OAuth)";
36
+ }, {
37
+ readonly id: "xai";
38
+ readonly key: "llm-pi-ai/xai";
39
+ readonly fallbackLabel: "xAI";
40
+ readonly modelGroupLabel: "xAI (OAuth)";
16
41
  }];
17
42
  export type ProviderId = (typeof PROVIDERS)[number]['id'];
18
43
  export interface AccountView {
@@ -104,6 +129,8 @@ export type ServerMessage = {
104
129
  attemptId?: string;
105
130
  code: OAuthErrorCode;
106
131
  message: string;
132
+ /** Bounded, credential-redacted Host diagnostic for optional UI expansion. */
133
+ detail?: string;
107
134
  };
108
135
  export type CommandParseResult = {
109
136
  ok: true;
package/lib/protocol.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Shared browser/Host wire vocabulary for the rc2 OAuth account surface. */
1
+ /** Shared browser/Host wire vocabulary for the OAuth account surface. */
2
2
  export const OAUTH_SOCKET_PATH = '/_edge-sky/dsh-oauth';
3
3
  export const OAUTH_SOCKET_PROTOCOL = 'dsh-oauth-v1';
4
4
  export const MAX_FRAME_BYTES = 16 * 1024;
@@ -12,6 +12,26 @@ export const PROVIDERS = [
12
12
  id: 'github-copilot', key: 'llm-pi-ai/github-copilot', fallbackLabel: 'GitHub Copilot',
13
13
  modelGroupLabel: 'GitHub Copilot (OAuth)',
14
14
  },
15
+ {
16
+ id: 'anthropic', key: 'llm-pi-ai/anthropic', fallbackLabel: 'Anthropic',
17
+ modelGroupLabel: 'Anthropic (OAuth)',
18
+ },
19
+ {
20
+ id: 'kimi-coding', key: 'llm-pi-ai/kimi-coding', fallbackLabel: 'Kimi For Coding',
21
+ modelGroupLabel: 'Kimi For Coding (OAuth)',
22
+ },
23
+ {
24
+ id: 'openrouter', key: 'llm-pi-ai/openrouter', fallbackLabel: 'OpenRouter',
25
+ modelGroupLabel: 'OpenRouter (OAuth)',
26
+ },
27
+ {
28
+ id: 'radius', key: 'llm-pi-ai/radius', fallbackLabel: 'Radius',
29
+ modelGroupLabel: 'Radius (OAuth)',
30
+ },
31
+ {
32
+ id: 'xai', key: 'llm-pi-ai/xai', fallbackLabel: 'xAI',
33
+ modelGroupLabel: 'xAI (OAuth)',
34
+ },
15
35
  ];
16
36
  const ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
17
37
  function isRecord(value) {
@@ -1,4 +1,4 @@
1
- /** Browser half: standalone OAuth Accounts page for DSH 0.1.1-rc.2. */
1
+ /** Browser half of the standalone DSH OAuth Accounts page. */
2
2
  import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
3
3
  import { type OAuthLocaleKey } from './locales.js';
4
4
  export { OAuthAccountsSection } from './OAuthAccountsSection.js';
@@ -19,6 +19,7 @@ export declare const en: {
19
19
  readonly authorized: "Sign-in completed.";
20
20
  readonly cancelled: "Sign-in was cancelled.";
21
21
  readonly failed: "Sign-in failed.";
22
+ readonly technicalDetails: "Technical details";
22
23
  readonly submit: "Continue";
23
24
  readonly copy: "Copy code";
24
25
  readonly copied: "Copied";
@@ -16,12 +16,14 @@ export interface AttemptView {
16
16
  notice?: NoticeView;
17
17
  prompt?: ActivePromptView;
18
18
  error?: string;
19
+ errorDetail?: string;
19
20
  }
20
21
  export interface OAuthAccountsSnapshot {
21
22
  connection: 'connecting' | 'open' | 'reconnecting' | 'closed';
22
23
  accounts: readonly AccountView[];
23
24
  attempt?: AttemptView;
24
25
  error?: string;
26
+ errorDetail?: string;
25
27
  }
26
28
  type Listener = () => void;
27
29
  /** Remove interaction-only content once an authorization attempt settles. */
@@ -1,3 +1,3 @@
1
1
  /** Plugin-owned styles, injected and removed with the browser Cordis fiber. */
2
- export declare const styles = "\n.dsh-oauth-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}\n.dsh-oauth-title{margin:0;font-size:16px;line-height:24px;font-weight:500}\n.dsh-oauth-intro,.dsh-oauth-muted{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}\n.dsh-oauth-error{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}\n.dsh-oauth-list{display:flex;flex-direction:column;gap:8px;margin:4px 0 0;padding:0;list-style:none}\n.dsh-oauth-card{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}\n.dsh-oauth-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dsh-oauth-name{font-size:14px;font-weight:500}.dsh-oauth-state{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);font-size:12px;color:var(--dsw-alias-label-secondary)}\n.dsh-oauth-dot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dsh-oauth-state[data-state=ready] .dsh-oauth-dot{background:var(--dsw-alias-state-success-primary)}.dsh-oauth-state[data-state=warning] .dsh-oauth-dot{background:var(--dsw-alias-state-error-primary)}.dsh-oauth-state[data-state=authorizing] .dsh-oauth-dot{background:var(--dsw-alias-button-info-fill)}\n.dsh-oauth-actions{display:flex;gap:6px;margin-left:auto}.dsh-oauth-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:15px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}.dsh-oauth-button[data-variant=primary]{border-color:transparent;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]{border-color:transparent;color:var(--dsw-alias-state-error-primary)}.dsh-oauth-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-button[data-variant=primary]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.dsh-oauth-button:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.dsh-oauth-button:disabled{cursor:not-allowed;opacity:.4}\n.dsh-oauth-flow{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l3);font-size:13px;line-height:20px}.dsh-oauth-link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);text-decoration:none}.dsh-oauth-link:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-link-label{font-weight:500}.dsh-oauth-link-host{max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsh-oauth-link-icon{color:var(--dsw-alias-brand-text)}.dsh-oauth-code{display:flex;align-items:center;gap:8px}.dsh-oauth-code code{padding:5px 8px;border-radius:6px;background:var(--dsw-alias-bg-module-platform);font-size:14px;letter-spacing:.08em}\n.dsh-oauth-prompt{display:flex;flex-direction:column;gap:7px}.dsh-oauth-input,.dsh-oauth-select{box-sizing:border-box;width:100%;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}\n";
2
+ export declare const styles = "\n.dsh-oauth-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}\n.dsh-oauth-title{margin:0;font-size:16px;line-height:24px;font-weight:500}\n.dsh-oauth-intro,.dsh-oauth-muted{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}\n.dsh-oauth-error{display:flex;flex-direction:column;gap:6px;margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.dsh-oauth-error p{margin:0}.dsh-oauth-error pre{box-sizing:border-box;max-width:100%;max-height:320px;margin:0;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;overflow:auto;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:11px/17px ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.dsh-oauth-error-details summary{cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none}.dsh-oauth-error-details[open] summary{margin-bottom:6px}\n.dsh-oauth-list{display:flex;flex-direction:column;gap:8px;margin:4px 0 0;padding:0;list-style:none}\n.dsh-oauth-card{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}\n.dsh-oauth-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dsh-oauth-name{font-size:14px;font-weight:500}.dsh-oauth-state{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);font-size:12px;color:var(--dsw-alias-label-secondary)}\n.dsh-oauth-dot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dsh-oauth-state[data-state=ready] .dsh-oauth-dot{background:var(--dsw-alias-state-success-primary)}.dsh-oauth-state[data-state=warning] .dsh-oauth-dot{background:var(--dsw-alias-state-error-primary)}.dsh-oauth-state[data-state=authorizing] .dsh-oauth-dot{background:var(--dsw-alias-button-info-fill)}\n.dsh-oauth-actions{display:flex;gap:6px;margin-left:auto}.dsh-oauth-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:15px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}.dsh-oauth-button[data-variant=primary]{border-color:transparent;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]{border-color:transparent;color:var(--dsw-alias-state-error-primary)}.dsh-oauth-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-button[data-variant=primary]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.dsh-oauth-button:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.dsh-oauth-button:disabled{cursor:not-allowed;opacity:.4}\n.dsh-oauth-flow{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l3);font-size:13px;line-height:20px}.dsh-oauth-link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);text-decoration:none}.dsh-oauth-link:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-link-label{font-weight:500}.dsh-oauth-link-host{max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsh-oauth-link-icon{color:var(--dsw-alias-brand-text)}.dsh-oauth-code{display:flex;align-items:center;gap:8px}.dsh-oauth-code code{padding:5px 8px;border-radius:6px;background:var(--dsw-alias-bg-module-platform);font-size:14px;letter-spacing:.08em}\n.dsh-oauth-prompt{display:flex;flex-direction:column;gap:7px}.dsh-oauth-input,.dsh-oauth-select{box-sizing:border-box;width:100%;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}\n";
3
3
  //# sourceMappingURL=styles.d.ts.map
@@ -1,4 +1,4 @@
1
- /** Shared browser/Host wire vocabulary for the rc2 OAuth account surface. */
1
+ /** Shared browser/Host wire vocabulary for the OAuth account surface. */
2
2
  export declare const OAUTH_SOCKET_PATH = "/_edge-sky/dsh-oauth";
3
3
  export declare const OAUTH_SOCKET_PROTOCOL = "dsh-oauth-v1";
4
4
  export declare const MAX_FRAME_BYTES: number;
@@ -13,6 +13,31 @@ export declare const PROVIDERS: readonly [{
13
13
  readonly key: "llm-pi-ai/github-copilot";
14
14
  readonly fallbackLabel: "GitHub Copilot";
15
15
  readonly modelGroupLabel: "GitHub Copilot (OAuth)";
16
+ }, {
17
+ readonly id: "anthropic";
18
+ readonly key: "llm-pi-ai/anthropic";
19
+ readonly fallbackLabel: "Anthropic";
20
+ readonly modelGroupLabel: "Anthropic (OAuth)";
21
+ }, {
22
+ readonly id: "kimi-coding";
23
+ readonly key: "llm-pi-ai/kimi-coding";
24
+ readonly fallbackLabel: "Kimi For Coding";
25
+ readonly modelGroupLabel: "Kimi For Coding (OAuth)";
26
+ }, {
27
+ readonly id: "openrouter";
28
+ readonly key: "llm-pi-ai/openrouter";
29
+ readonly fallbackLabel: "OpenRouter";
30
+ readonly modelGroupLabel: "OpenRouter (OAuth)";
31
+ }, {
32
+ readonly id: "radius";
33
+ readonly key: "llm-pi-ai/radius";
34
+ readonly fallbackLabel: "Radius";
35
+ readonly modelGroupLabel: "Radius (OAuth)";
36
+ }, {
37
+ readonly id: "xai";
38
+ readonly key: "llm-pi-ai/xai";
39
+ readonly fallbackLabel: "xAI";
40
+ readonly modelGroupLabel: "xAI (OAuth)";
16
41
  }];
17
42
  export type ProviderId = (typeof PROVIDERS)[number]['id'];
18
43
  export interface AccountView {
@@ -104,6 +129,8 @@ export type ServerMessage = {
104
129
  attemptId?: string;
105
130
  code: OAuthErrorCode;
106
131
  message: string;
132
+ /** Bounded, credential-redacted Host diagnostic for optional UI expansion. */
133
+ detail?: string;
107
134
  };
108
135
  export type CommandParseResult = {
109
136
  ok: true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@edge-sky/dsh-oauth-adapter",
3
- "description": "Web OAuth account surface for the official DSH 0.1.1-rc.2 authorization flows",
4
- "version": "0.1.1-rc.8",
3
+ "description": "Web OAuth account surface for official DSH authorization flows",
4
+ "version": "0.1.2-beta.0",
5
5
  "author": {
6
6
  "name": "edge-sky",
7
7
  "url": "https://github.com/edge-sky"
@@ -11,8 +11,9 @@
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git+https://github.com/edge-sky/dsh-oauth.git"
14
+ "url": "git+https://github.com/edge-sky/dsh-oauth-adapter.git"
15
15
  },
16
+ "packageManager": "pnpm@11.7.0",
16
17
  "engines": {
17
18
  "node": "^22.19.0 || >=24.0.0"
18
19
  },
@@ -28,6 +29,10 @@
28
29
  "types": "./lib/invariant.d.ts",
29
30
  "default": "./lib/invariant.js"
30
31
  },
32
+ "./authorization-fallback": {
33
+ "types": "./lib/authorization-fallback.d.ts",
34
+ "default": "./lib/authorization-fallback.js"
35
+ },
31
36
  "./client": {
32
37
  "types": "./lib/types/client/index.d.ts",
33
38
  "default": "./lib/client.js"
@@ -40,6 +45,13 @@
40
45
  "lib/**/*.js",
41
46
  "lib/**/*.d.ts"
42
47
  ],
48
+ "scripts": {
49
+ "build": "tsdown && pnpm run build:host && pnpm run build:client:types",
50
+ "build:host": "tsc -p tsconfig.json",
51
+ "build:client:types": "tsc -p tsconfig.client.json",
52
+ "test": "pnpm run build && vitest run",
53
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit"
54
+ },
43
55
  "license": "MIT",
44
56
  "dsh": {
45
57
  "bundle": {
@@ -55,18 +67,19 @@
55
67
  }
56
68
  },
57
69
  "peerDependencies": {
58
- "@deepseek-ai/cordis": "^4.0.1",
59
- "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
60
- "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
61
- "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
62
- "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
63
- "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
64
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
65
- "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
66
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
67
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
68
- "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
69
- "react": "^18.2.0"
70
+ "@deepseek-ai/cordis": ">=4.0.1",
71
+ "@deepseek-ai/dsh-authorization": ">=0.1.1-rc.2 <0.2.0-0",
72
+ "@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.2 <0.2.0-0",
73
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2 <0.2.0-0",
74
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.2 <0.2.0-0",
75
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.2 <0.2.0-0",
76
+ "@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2 <0.2.0-0",
77
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.1-rc.2 <0.2.0-0",
78
+ "@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2 <0.2.0-0",
79
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2 <0.2.0-0",
80
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.1-rc.2 <0.2.0-0",
81
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2 <0.2.0-0",
82
+ "react": ">=18.2.0"
70
83
  },
71
84
  "peerDependenciesMeta": {
72
85
  "@deepseek-ai/cordis": {
@@ -107,12 +120,12 @@
107
120
  }
108
121
  },
109
122
  "dependencies": {
110
- "@deepseek-ai/dsh-authorization": "0.1.1-rc.2",
111
123
  "@deepseek-ai/schemastery": "^3.18.1",
112
124
  "ws": "^8.18.3"
113
125
  },
114
126
  "devDependencies": {
115
127
  "@deepseek-ai/cordis": "^4.0.1",
128
+ "@deepseek-ai/dsh-authorization": "0.1.1-rc.2",
116
129
  "@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
117
130
  "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
118
131
  "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
@@ -132,12 +145,5 @@
132
145
  "tsdown": "^0.22.2",
133
146
  "typescript": "^6.0.3",
134
147
  "vitest": "^4.1.8"
135
- },
136
- "scripts": {
137
- "build": "tsdown && pnpm run build:host && pnpm run build:client:types",
138
- "build:host": "tsc -p tsconfig.json",
139
- "build:client:types": "tsc -p tsconfig.client.json",
140
- "test": "pnpm run build && vitest run",
141
- "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit"
142
148
  }
143
- }
149
+ }