dsh-opendesign-corouter 0.1.4 → 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/README.md CHANGED
@@ -32,7 +32,7 @@ dsh plugin --profile web add dsh-opendesign-corouter
32
32
  也可打开 [Actions → CI](https://github.com/KouzakiUmi/dsh-opendesign-corouter/actions/workflows/ci.yml),从成功构建下载 **`dsh-opendesign-corouter-build`** artifact。登录 GitHub 后解压 ZIP,取得 `.tgz` 与 `.sha256`,再安装本地包:
33
33
 
34
34
  ```console
35
- dsh plugin --profile web add /absolute/path/to/dsh-opendesign-corouter-0.1.4.tgz
35
+ dsh plugin --profile web add /absolute/path/to/dsh-opendesign-corouter-0.1.5.tgz
36
36
  ```
37
37
 
38
38
  `web` 只是示例,请替换为目标 Profile。Desktop 使用桌面安装器提供的 `dsh` 入口;独立公共 CLI 不能代管保留的 `desktop` Profile。安装后的启用与重载按目标 DSH 的管理流程进行。
@@ -40,7 +40,7 @@ dsh plugin --profile web add /absolute/path/to/dsh-opendesign-corouter-0.1.4.tgz
40
40
  ### 2. 配置连接
41
41
 
42
42
  1. 打开 **设置 → OpenDesign CoRouter → 连接与登录**。
43
- 2. 点击 **开始设备授权**,再用常用浏览器 **打开授权页**,核对授权码并确认;也可单独配置工作空间 API Key 或沿用 `COROUTER_API_KEY`。
43
+ 2. 点击 **开始设备授权**,再用 **在新窗口打开授权页** 显示官方页面,核对授权码并确认;也可复制链接到你自己的常用浏览器,或单独配置工作空间 API Key / 沿用 `COROUTER_API_KEY`。
44
44
  3. 返回设置页等待保存成功。额度默认优先读取桌面登录态;要明确使用设备授权的账号,可关闭桌面优先。
45
45
  4. 回到模型选择器,选择 **OpenDesign CoRouter · 套餐赠送** 或 **OpenDesign CoRouter · 付费模型**。
46
46
 
@@ -72,7 +72,7 @@ dsh plugin --profile web add /absolute/path/to/dsh-opendesign-corouter-0.1.4.tgz
72
72
 
73
73
  ### 桌面优先,也能独立登录
74
74
 
75
- 独立登录采用设备授权,在常用浏览器打开普通官方授权页,沿用现有配置、密码管理器和自动填充。确认后设备令牌保存到 DSH 凭据服务;取消、超时和卸载停止轮询,不关闭用户浏览器,也不修改桌面配置。
75
+ 独立登录采用设备授权,官方授权页可交给插件自己启动的临时 Chrome 窗口显示(不读取或改动你已有的浏览器配置),也可复制链接到你自己的浏览器沿用已有登录态、密码管理器和自动填充。确认后设备令牌保存到 DSH 凭据服务;取消、超时和卸载停止轮询并关闭临时窗口,不关闭你自己的浏览器,也不修改桌面配置。
76
76
 
77
77
  <picture>
78
78
  <source media="(prefers-color-scheme: dark)" srcset="docs/previews/theme-dark.png">
package/SECURITY.md CHANGED
@@ -6,7 +6,7 @@
6
6
  - 设备授权双令牌原子保存;runtimeKey 只自动提供给请求快照中的官方推理 origin,controlKey 只发往官方控制面。旧 Cookie grant 保留只读兼容。
7
7
  - 推理自定义地址由用户显式配置;除本机回环外要求 HTTPS,拒绝 URL 内嵌凭据、查询串与片段。
8
8
  - 额度分池不跨身份混合。桌面认证失效时,可回退到单独保存的控制台登录;权限不足和未知额度不伪装成 0。
9
- - 独立登录使用设备授权,普通浏览器仅打开官方激活页;插件不启动浏览器自动化、不读取浏览器配置或 Cookie。deviceSecret 只在 Host 内存用于轮询;授权码、激活链接和到期时间仅在等待期提供给 UI。取消、超时和卸载中止请求,不关闭用户浏览器。持久化成功是不可撤回的提交边界,之后取消不会假称已撤回。
9
+ - 独立登录使用设备授权。Host 可用 `playwright-core` 启动一个短暂的 Chrome 窗口显示官方激活页;这是插件自己创建的临时浏览器上下文,不创建、不驱动、不读取用户已有的浏览器配置或 Cookie,也不在该页面输入、点击或读取内容。由系统外壳拦截 `target="_blank"` 的宿主里,官方链接仍作为手动路径保留。deviceSecret 只在 Host 内存用于轮询;授权码、激活链接和到期时间仅在等待期提供给 UI,打开结果只返回固定代码,不返回 URL、浏览器句柄或错误正文。取消、超时和卸载中止请求,并有界请求关闭该临时窗口(每阶段最多约 5 秒,超时为尽力清理);不关闭用户自己的浏览器。持久化成功是不可撤回的提交边界,之后取消不会假称已撤回。
10
10
  - `OPENDESIGN_VELA_PATH` 是用户信任的本地程序路径。插件仅以固定 argv 执行只读查询,不通过 shell;不校验该程序的签名或下载它。不要将此变量指向来源不明的程序。
11
11
  - RPC 使用严格白名单解析;原始错误正文、Cookie、请求头、Key 和桌面本地路径不进入状态返回。并发保存有版本冲突检测。
12
12
  - 请求、流空闲、额度接口、CLI、登录和 RPC 均有有界超时。停用插件会撤销 Provider / RPC 并中止自身请求。
@@ -1,11 +1,32 @@
1
1
  # 第三方代码与资料
2
2
 
3
- 本项目的消息/图片兼容处理、套餐模型元数据参考 [dsh-opendesign-go 0.1.9](https://github.com/EliteOtaku/dsh-opendesign-go)。额度归一化及登录生命周期的基础处理改编自 [dsh-subusage 0.16.2](https://github.com/KouzakiUmi/dsh-subusage)。空轮判定参考 [dsh-empty-turn-guard 0.4.0](https://github.com/FYTXY/dsh-empty-turn-guard)。设置布局参考 [dsh-code-pill](https://github.com/KouzakiUmi/dsh-code-pill)。
3
+ 本项目的消息/图片兼容处理、套餐模型元数据参考 [dsh-opendesign-go 0.1.9](https://github.com/EliteOtaku/dsh-opendesign-go)。额度归一化及登录生命周期的基础处理改编自 [dsh-subusage 0.16.2](https://github.com/KouzakiUmi/dsh-subusage);临时授权窗口的 Chrome 启动策略改编自 [dsh-subusage 0.16.3](https://github.com/KouzakiUmi/dsh-subusage) 的 `lib/cookie-login.js`。空轮判定参考 [dsh-empty-turn-guard 0.4.0](https://github.com/FYTXY/dsh-empty-turn-guard)。设置布局参考 [dsh-code-pill](https://github.com/KouzakiUmi/dsh-code-pill)。
4
4
 
5
5
  离线主题夹具使用 [DeepSeek Harness 官方主题令牌](https://github.com/deepseek-ai/deepseek-harness/blob/22ddee3792402c3e70ec5f86a63c3e3e895a2f60/packages/client/ui-theme/src/styles/design-platform.css),仅供测试,不随插件安装包分发。
6
6
 
7
7
  Client bundle 内含 [Zod 4.6.6](https://github.com/colinhacks/zod) 与 DSH 协议辅助代码,分别保留 Colin McDonnell 与 DeepSeek 的 MIT 声明。
8
8
 
9
+ Host 侧运行时依赖 [playwright-core 1.63.0](https://github.com/microsoft/playwright)(Apache-2.0),只用于启动显示官方授权页的临时 Chrome 窗口:不下载浏览器,使用本机已安装的 Chrome。它按 npm 依赖安装,不并入本项目源码。
10
+
11
+ ```text
12
+ Apache License
13
+ Version 2.0, January 2004
14
+ http://www.apache.org/licenses/
15
+
16
+ Copyright (c) Microsoft Corporation.
17
+ Licensed under the Apache License, Version 2.0 (the "License");
18
+ you may not use this file except in compliance with the License.
19
+ You may obtain a copy of the License at
20
+
21
+ https://www.apache.org/licenses/LICENSE-2.0
22
+
23
+ Unless required by applicable law or agreed to in writing, software
24
+ distributed under the License is distributed on an "AS IS" BASIS,
25
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26
+ See the License for the specific language governing permissions and
27
+ limitations under the License.
28
+ ```
29
+
9
30
  以上项目为 MIT 许可。保留如下版权和许可声明,适用于相应移植与改编部分:
10
31
 
11
32
  ```text
@@ -0,0 +1,104 @@
1
+ /** Bounds on one navigation; the window stays open for the user afterwards. */
2
+ export declare const ACTIVATION_NAVIGATE_TIMEOUT_MS = 15000;
3
+ /**
4
+ * End-to-end budget for one open request: launch + newPage + navigate. Kept below the
5
+ * client's 30 s RPC wait so a successful open is reported to the user, not a timeout.
6
+ */
7
+ export declare const ACTIVATION_OPEN_BUDGET_MS = 20000;
8
+ /** Bound on closing a window; a wedged browser must not pin disposal. */
9
+ export declare const ACTIVATION_CLOSE_TIMEOUT_MS = 5000;
10
+ /** Why the window could not be offered. Codes are public; no URL or error text is echoed. */
11
+ export type ActivationOpenCode = 'opened' | 'invalid-url' | 'unavailable' | 'failed';
12
+ export interface ActivationOpenResult {
13
+ code: ActivationOpenCode;
14
+ /** True only when a Chrome window is showing the activation page. */
15
+ opened: boolean;
16
+ }
17
+ /** The subset of playwright-core this module uses, so tests can inject a stand-in. */
18
+ export interface ActivationPage {
19
+ goto(url: string, options: {
20
+ waitUntil: 'domcontentloaded';
21
+ timeout: number;
22
+ }): Promise<unknown>;
23
+ /** playwright's Page.close(); default does not run unload handlers. */
24
+ close?(): Promise<void>;
25
+ }
26
+ export interface ActivationBrowser {
27
+ close(): Promise<unknown>;
28
+ isConnected(): boolean;
29
+ newPage(): Promise<ActivationPage>;
30
+ on?(event: 'disconnected', listener: () => void): unknown;
31
+ }
32
+ export interface ActivationLauncherOptions {
33
+ /** Injected for offline tests; production resolves playwright-core lazily. */
34
+ launch?: () => Promise<ActivationBrowser>;
35
+ timeoutMs?: number;
36
+ /** Reports whether a launch is even possible, e.g. a missing Chrome install. */
37
+ available?: () => boolean;
38
+ }
39
+ /**
40
+ * Chrome without the automation flag subusage found counterproductive on guarded pages.
41
+ * `chromiumSandbox: true` is explicit on purpose: playwright otherwise appends
42
+ * `--no-sandbox` (verified in playwright-core 1.63.0), and this window renders a page
43
+ * this plugin does not control.
44
+ */
45
+ export declare function chromeLaunchOptions(): {
46
+ channel: "chrome";
47
+ headless: boolean;
48
+ chromiumSandbox: boolean;
49
+ ignoreDefaultArgs: string[];
50
+ args: string[];
51
+ };
52
+ export declare class ActivationLauncher {
53
+ private browser;
54
+ private page;
55
+ private pending;
56
+ private disposal;
57
+ /** In-flight window closure, shared so disposal can join it. */
58
+ private closing;
59
+ /** Closures of handles that were never adopted; disposal joins them too. */
60
+ private tracked;
61
+ /** Chrome starts in flight, so disposal waits for the handle they will produce. */
62
+ private starting;
63
+ private disposed;
64
+ /** Bumped by `close()`: an attempt from an older session must not resurrect a window. */
65
+ private session;
66
+ private readonly options;
67
+ constructor(options?: ActivationLauncherOptions);
68
+ /**
69
+ * Show `url` in the temporary window, reusing one already open. Concurrent calls share
70
+ * a single attempt: the caller's button stays honest about what actually happened.
71
+ */
72
+ open(url: string): Promise<ActivationOpenResult>;
73
+ /**
74
+ * Close the temporary window and stay usable: a cancelled or expired job takes its
75
+ * window away, while the next login may open a new one.
76
+ */
77
+ close(): Promise<void>;
78
+ /**
79
+ * Retire the launcher for good. Cleanup is best-effort and bounded: it joins in-flight
80
+ * starts and closures, but a browser that ignores `close()` may outlive the 5 s wait.
81
+ */
82
+ dispose(): Promise<void>;
83
+ /**
84
+ * Close the window this launcher owns right now. Ownership is taken before the async
85
+ * close, so a job that adopts a new window afterwards is not affected by this closure.
86
+ * The closure promise is shared so a later `dispose()` joins it.
87
+ */
88
+ private closeCurrent;
89
+ private attempt;
90
+ /** The job that started this attempt ended, or the launcher retired. */
91
+ private stale;
92
+ private ensureBrowser;
93
+ private ensurePage;
94
+ /** A handle that arrived too late is closed, and still counts as work disposal joins. */
95
+ private trackClose;
96
+ private trackPageClose;
97
+ private adopt;
98
+ /**
99
+ * Bound one launch step by the attempt's shared deadline. A step that settles only
100
+ * after that deadline still hands its handle back: dropping it would leak a Chrome
101
+ * process nobody can close.
102
+ */
103
+ private bounded;
104
+ }
package/lib/client.d.ts CHANGED
@@ -183,9 +183,15 @@ export interface AdvancedProps {
183
183
  export declare function Advanced({ status, formSnapshot, setFormField, pendingField }: AdvancedProps): React.FunctionComponentElement<{
184
184
  children?: React.ReactNode | undefined;
185
185
  }>;
186
- /** Approval opens through an ordinary link; no browser profile is created or controlled. */
187
- export declare function LoginApproval({ login }: {
186
+ /** Approval is an ordinary official link; the Host may also show it in a temporary window. */
187
+ export declare function LoginApproval({ login, onOpen, opening, notice }: {
188
188
  login: LoginState | undefined;
189
+ onOpen?: () => void;
190
+ opening?: boolean;
191
+ notice?: {
192
+ kind: 'status' | 'error';
193
+ text: string;
194
+ } | null;
189
195
  }): React.DetailedReactHTMLElement<{
190
196
  className: string;
191
197
  'aria-label': string;
package/lib/client.js CHANGED
@@ -20021,6 +20021,7 @@ var MAX_API_KEY = 16384;
20021
20021
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
20022
20022
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
20023
20023
  var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
20024
+ var ACTIVATION_OPEN_CODES = ["opened", "no-link", "invalid-url", "unavailable", "failed"];
20024
20025
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
20025
20026
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
20026
20027
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -20196,6 +20197,10 @@ function parseSaveApiKeyRequest(value) {
20196
20197
  if (key.length > MAX_API_KEY) throw new Error(`API Key \u4E0D\u80FD\u8D85\u8FC7 ${MAX_API_KEY} \u5B57\u7B26`);
20197
20198
  return { value: key, expectedRevision: text(value.expectedRevision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION) };
20198
20199
  }
20200
+ function parseActivationOpenState(value) {
20201
+ if (!record2(value)) throw new Error("\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C\u5FC5\u987B\u662F\u5BF9\u8C61");
20202
+ return { code: member(value.code, ACTIVATION_OPEN_CODES, "\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C"), opened: flag(value.opened, "\u6388\u6743\u9875\u662F\u5426\u5DF2\u6253\u5F00") };
20203
+ }
20199
20204
 
20200
20205
  // src/generated/typert.remote-client.ts
20201
20206
  var StatusCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#Status", create: () => external_exports.unknown().transform((value) => parseStatus(value)) };
@@ -20203,6 +20208,7 @@ var QuotaSnapshotCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#
20203
20208
  var LoginStateCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#LoginState", create: () => external_exports.unknown().transform((value) => parseLoginState(value)) };
20204
20209
  var StartLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#StartLoginRequest", create: () => external_exports.unknown().transform((value) => parseStartLoginRequest(value)) };
20205
20210
  var CancelLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#CancelLoginRequest", create: () => external_exports.unknown().transform((value) => parseCancelLoginRequest(value)) };
20211
+ var ActivationOpenStateCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#ActivationOpenState", create: () => external_exports.unknown().transform((value) => parseActivationOpenState(value)) };
20206
20212
  var SaveApiKeyRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#SaveApiKeyRequest", create: () => external_exports.unknown().transform((value) => parseSaveApiKeyRequest(value)) };
20207
20213
  var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter", methods: {
20208
20214
  "openDesignCoRouter/status": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
@@ -20212,6 +20218,7 @@ var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter",
20212
20218
  "openDesignCoRouter/startLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: StartLoginRequestCodec, strictCodec: StartLoginRequestCodec }], result: LoginStateCodec },
20213
20219
  "openDesignCoRouter/loginStatus": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: LoginStateCodec },
20214
20220
  "openDesignCoRouter/cancelLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: CancelLoginRequestCodec, strictCodec: CancelLoginRequestCodec }], result: LoginStateCodec },
20221
+ "openDesignCoRouter/openActivationPage": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: ActivationOpenStateCodec },
20215
20222
  "openDesignCoRouter/saveApiKey": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: SaveApiKeyRequestCodec, strictCodec: SaveApiKeyRequestCodec }], result: StatusCodec }
20216
20223
  } }, external_exports);
20217
20224
  var typert_remote_client_default = TYPERT_REMOTE;
@@ -20235,13 +20242,14 @@ var MAX_REQUEST_TIMEOUT_MS = 36e5;
20235
20242
  var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
20236
20243
  var MIN_STREAM_IDLE_TIMEOUT_MS = 1e3;
20237
20244
  var MAX_STREAM_IDLE_TIMEOUT_MS = 18e5;
20245
+ var RPC_TIMEOUT_MS = 3e4;
20238
20246
  var MAX_OUTPUT_TOKENS = 1e7;
20239
20247
  var MAX_CONTEXT_WINDOW = 1e8;
20240
20248
  var GENERIC_ERROR = "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
20241
20249
  var LOGIN_LABELS = {
20242
20250
  idle: "\u5C1A\u672A\u5F00\u59CB\u72EC\u7ACB\u767B\u5F55",
20243
20251
  launching: "\u6B63\u5728\u7533\u8BF7\u8BBE\u5907\u6388\u6743\u7801\u2026",
20244
- waiting: "\u8BF7\u6253\u5F00\u6388\u6743\u9875\uFF0C\u5728\u5E38\u7528\u6D4F\u89C8\u5668\u4E2D\u786E\u8BA4\u672C\u6B21\u767B\u5F55\u3002",
20252
+ waiting: "\u8BF7\u5728\u6388\u6743\u7A97\u53E3\u6216\u5E38\u7528\u6D4F\u89C8\u5668\u4E2D\u786E\u8BA4\u672C\u6B21\u767B\u5F55\u3002",
20245
20253
  verifying: "\u6B63\u5728\u4FDD\u5B58\u8BBE\u5907\u6388\u6743\u2026",
20246
20254
  success: "\u767B\u5F55\u6210\u529F\uFF0C\u8BBE\u5907\u6388\u6743\u5DF2\u4FDD\u5B58\u3002",
20247
20255
  cancelled: "\u5DF2\u53D6\u6D88\u672C\u6B21\u767B\u5F55\u3002",
@@ -20332,7 +20340,7 @@ function createCaller(ctx) {
20332
20340
  if (!operation) throw new Error("Unknown generated Remote method");
20333
20341
  const parameter = operation.parameters[0];
20334
20342
  const input2 = operation.input({ strict: true }).create().parse(parameter ? { [parameter.wire]: request } : {});
20335
- const response = await withTimeout(Promise.resolve(operation.invoke(remote, input2)), 25e3, controller.signal);
20343
+ const response = await withTimeout(Promise.resolve(operation.invoke(remote, input2)), RPC_TIMEOUT_MS, controller.signal);
20336
20344
  if (!("ok" in response) || !response.ok) throw new Error("RPC failed");
20337
20345
  return response.value;
20338
20346
  };
@@ -21101,7 +21109,14 @@ function Switch({ checked, disabled, label, hint, onChange }) {
21101
21109
  function loginInFlight(state) {
21102
21110
  return state === "launching" || state === "waiting" || state === "verifying";
21103
21111
  }
21104
- function LoginApproval({ login }) {
21112
+ var ACTIVATION_OPEN_LABELS = {
21113
+ opened: "\u5DF2\u5728\u65B0\u7A97\u53E3\u6253\u5F00\u5B98\u65B9\u6388\u6743\u9875\uFF1B\u8BF7\u5728\u8BE5\u7A97\u53E3\u786E\u8BA4\u8BBE\u5907\u7801\u3002",
21114
+ "no-link": "\u6388\u6743\u7801\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u7533\u8BF7\u3002",
21115
+ "invalid-url": "\u6388\u6743\u94FE\u63A5\u672A\u901A\u8FC7\u6821\u9A8C\uFF0C\u8BF7\u590D\u5236\u94FE\u63A5\u624B\u52A8\u6253\u5F00\u3002",
21116
+ unavailable: "\u65E0\u6CD5\u542F\u52A8\u4E34\u65F6\u6D4F\u89C8\u5668\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 Chrome\uFF1B\u4E5F\u53EF\u590D\u5236\u94FE\u63A5\u624B\u52A8\u6253\u5F00\u3002",
21117
+ failed: "\u4E34\u65F6\u6D4F\u89C8\u5668\u7A97\u53E3\u672A\u80FD\u6253\u5F00\uFF0C\u8BF7\u590D\u5236\u94FE\u63A5\u624B\u52A8\u6253\u5F00\u3002"
21118
+ };
21119
+ function LoginApproval({ login, onOpen, opening, notice }) {
21105
21120
  if (login?.state !== "waiting" || login.userCode === void 0 || login.expiresAt === void 0) return null;
21106
21121
  const url2 = validateActivationUrl(login.activationUrl, login.userCode);
21107
21122
  if (url2 === null) return null;
@@ -21113,10 +21128,19 @@ function LoginApproval({ login }) {
21113
21128
  h(
21114
21129
  "div",
21115
21130
  { className: "odc-actions" },
21116
- h("a", { className: "odc-button odc-button-primary", href: url2, target: "_blank", rel: "noreferrer noopener" }, "\u6253\u5F00\u6388\u6743\u9875")
21131
+ h("button", {
21132
+ className: "odc-button odc-button-primary",
21133
+ type: "button",
21134
+ disabled: opening === true || onOpen === void 0,
21135
+ onClick: () => {
21136
+ onOpen?.();
21137
+ }
21138
+ }, opening === true ? "\u6B63\u5728\u6253\u5F00\u2026" : "\u5728\u65B0\u7A97\u53E3\u6253\u5F00\u6388\u6743\u9875"),
21139
+ h("a", { className: "odc-button", href: url2, target: "_blank", rel: "noreferrer noopener" }, "\u590D\u5236\u94FE\u63A5\u624B\u52A8\u6253\u5F00")
21117
21140
  ),
21118
- h("p", { className: "odc-hint" }, `\u6388\u6743\u7801\u6709\u6548\u81F3 ${formatTime(login.expiresAt)}\u3002\u5728\u5E38\u7528\u6D4F\u89C8\u5668\u786E\u8BA4\u540E\uFF0C\u8FD4\u56DE\u6B64\u9875\u67E5\u770B\u7ED3\u679C\u3002`),
21119
- h("p", { className: "odc-hint" }, "\u53EF\u53F3\u952E\u590D\u5236\u6388\u6743\u94FE\u63A5\u5230\u5E38\u7528\u6D4F\u89C8\u5668\uFF1B\u5BC6\u7801\u7BA1\u7406\u5668\u548C\u81EA\u52A8\u586B\u5145\u7531\u6D4F\u89C8\u5668\u6B63\u5E38\u63D0\u4F9B\u3002\u4EC5\u786E\u8BA4\u4F60\u672C\u4EBA\u53D1\u8D77\u7684\u6388\u6743\u3002")
21141
+ notice ? h(NoticeView, { notice }) : null,
21142
+ h("p", { className: "odc-hint" }, `\u6388\u6743\u7801\u6709\u6548\u81F3 ${formatTime(login.expiresAt)}\u3002\u5728\u6388\u6743\u7A97\u53E3\u786E\u8BA4\u540E\uFF0C\u8FD4\u56DE\u6B64\u9875\u67E5\u770B\u7ED3\u679C\u3002`),
21143
+ h("p", { className: "odc-hint" }, "\u65B0\u7A97\u53E3\u662F\u72EC\u7ACB\u7684\u4E34\u65F6 Chrome\uFF0C\u4E0D\u4F1A\u8BFB\u53D6\u6216\u6539\u52A8\u4F60\u6B63\u5728\u4F7F\u7528\u7684\u6D4F\u89C8\u5668\u914D\u7F6E\uFF1B\u9700\u8981\u6CBF\u7528\u5DF2\u6709\u767B\u5F55\u6001\u65F6\uFF0C\u53F3\u952E\u590D\u5236\u94FE\u63A5\u5230\u5E38\u7528\u6D4F\u89C8\u5668\u3002\u4EC5\u786E\u8BA4\u4F60\u672C\u4EBA\u53D1\u8D77\u7684\u6388\u6743\u3002")
21120
21144
  );
21121
21145
  }
21122
21146
  function Connection({ status, formSnapshot, setFormField, pendingField, caller, loadStatus, refreshAfterLogin }) {
@@ -21126,10 +21150,22 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
21126
21150
  const [notice, setNotice] = React.useState(null);
21127
21151
  const [apiKey, setApiKey] = React.useState("");
21128
21152
  const [keyBusy, setKeyBusy] = React.useState(false);
21153
+ const [opening, setOpening] = React.useState(false);
21154
+ const [activationNotice, setActivationNotice] = React.useState(null);
21155
+ const activationJob = React.useRef(null);
21156
+ const activationToken = React.useRef(0);
21129
21157
  React.useEffect(() => () => {
21130
21158
  actionVersion.current += 1;
21131
21159
  }, []);
21132
21160
  const login = status?.login;
21161
+ React.useEffect(() => {
21162
+ const job = login?.jobId ?? null;
21163
+ if (activationJob.current === job) return;
21164
+ activationJob.current = job;
21165
+ activationToken.current += 1;
21166
+ setActivationNotice(null);
21167
+ setOpening(false);
21168
+ }, [login?.jobId]);
21133
21169
  const state = login?.state ?? "idle";
21134
21170
  const inFlight = loginInFlight(state);
21135
21171
  const startLogin = async () => {
@@ -21141,6 +21177,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
21141
21177
  const next = await caller("startLogin", { expectedRevision: status.revision });
21142
21178
  if (!mounted.current || version2 !== actionVersion.current) return;
21143
21179
  setNotice(null);
21180
+ setActivationNotice(null);
21144
21181
  await (next.state === "success" ? refreshAfterLogin() : loadStatus());
21145
21182
  } catch {
21146
21183
  if (mounted.current && version2 === actionVersion.current) setNotice({ kind: "error", text: "\u65E0\u6CD5\u542F\u52A8\u767B\u5F55\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002" });
@@ -21186,6 +21223,23 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
21186
21223
  if (mounted.current && version2 === actionVersion.current) setKeyBusy(false);
21187
21224
  }
21188
21225
  };
21226
+ const openActivation = async () => {
21227
+ if (status === void 0 || busy || keyBusy || opening || login?.state !== "waiting") return;
21228
+ const job = login.jobId;
21229
+ const token = ++activationToken.current;
21230
+ activationJob.current = job;
21231
+ setOpening(true);
21232
+ setActivationNotice(null);
21233
+ const current = () => mounted.current && activationToken.current === token && activationJob.current === job;
21234
+ try {
21235
+ const result = await caller("openActivationPage");
21236
+ if (current()) setActivationNotice({ kind: result.opened ? "status" : "error", text: ACTIVATION_OPEN_LABELS[result.code] });
21237
+ } catch {
21238
+ if (current()) setActivationNotice({ kind: "error", text: ACTIVATION_OPEN_LABELS.failed });
21239
+ } finally {
21240
+ if (mounted.current && activationToken.current === token) setOpening(false);
21241
+ }
21242
+ };
21189
21243
  const desktopChecked = formSnapshot.value?.useDesktopCredential ?? status?.config.useDesktopCredential ?? true;
21190
21244
  return h(
21191
21245
  React.Fragment,
@@ -21214,7 +21268,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
21214
21268
  "section",
21215
21269
  { className: "odc-card" },
21216
21270
  h("h3", null, "\u8BBE\u5907\u6388\u6743\u767B\u5F55"),
21217
- h("p", { className: "odc-hint" }, "\u7533\u8BF7\u6388\u6743\u7801\u540E\uFF0C\u5728\u5E38\u7528\u6D4F\u89C8\u5668\u6253\u5F00\u5B98\u65B9\u6388\u6743\u9875\u3002\u6CBF\u7528\u5DF2\u6709\u767B\u5F55\u72B6\u6001\u3001\u5BC6\u7801\u7BA1\u7406\u5668\u548C\u81EA\u52A8\u586B\u5145\uFF1B\u786E\u8BA4\u540E\u81EA\u52A8\u4FDD\u5B58\u8BBE\u5907\u4EE4\u724C\uFF0C\u7528\u4E8E\u67E5\u8BE2\u989D\u5EA6\u53CA\u5B98\u65B9\u7F51\u5173\u63A8\u7406\u3002"),
21271
+ h("p", { className: "odc-hint" }, "\u7533\u8BF7\u6388\u6743\u7801\u540E\uFF0C\u7531\u63D2\u4EF6\u542F\u52A8\u4E34\u65F6\u7A97\u53E3\u663E\u793A\u5B98\u65B9\u6388\u6743\u9875\uFF0C\u4E5F\u53EF\u590D\u5236\u94FE\u63A5\u5230\u4F60\u81EA\u5DF1\u7684\u5E38\u7528\u6D4F\u89C8\u5668\u3002\u786E\u8BA4\u540E\u81EA\u52A8\u4FDD\u5B58\u8BBE\u5907\u4EE4\u724C\uFF0C\u7528\u4E8E\u67E5\u8BE2\u989D\u5EA6\u53CA\u5B98\u65B9\u7F51\u5173\u63A8\u7406\u3002"),
21218
21272
  h(
21219
21273
  "div",
21220
21274
  { className: "odc-actions", style: { marginTop: 14 } },
@@ -21226,7 +21280,9 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
21226
21280
  } }, "\u53D6\u6D88\u767B\u5F55") : null,
21227
21281
  h("a", { className: "odc-link", href: OFFICIAL_SITE, target: "_blank", rel: "noreferrer noopener" }, "\u6253\u5F00 CoRouter \u5B98\u7F51")
21228
21282
  ),
21229
- h(LoginApproval, { login }),
21283
+ h(LoginApproval, { login, onOpen: () => {
21284
+ void openActivation();
21285
+ }, opening, notice: activationNotice }),
21230
21286
  h("p", { className: "odc-meta", role: state === "error" ? "alert" : "status" }, login?.message ?? LOGIN_LABELS[state]),
21231
21287
  h(NoticeView, { notice })
21232
21288
  ),
@@ -3,8 +3,10 @@ export interface DeviceTokens {
3
3
  runtimeKey: string;
4
4
  }
5
5
  export declare const DEVICE_AUTH_ENDPOINT = "https://amr-api.open-design.ai/api/v1/cli/device-authorizations";
6
- export declare const DEVICE_AUTH_PLUGIN_VERSION = "0.1.4";
6
+ export declare const DEVICE_AUTH_PLUGIN_VERSION = "0.1.5";
7
7
  export declare const DEVICE_REQUEST_TIMEOUT_MS = 20000;
8
+ /** One bounded launch step of the temporary activation window. */
9
+ export declare const DEVICE_OPEN_TIMEOUT_MS = 30000;
8
10
  export declare const DEVICE_RESPONSE_MAX_BYTES = 65536;
9
11
  export declare const DEVICE_POLL_MIN_MS = 1000;
10
12
  export declare const DEVICE_POLL_MAX_MS = 300000;
@@ -89,6 +89,7 @@ var MAX_API_KEY = 16384;
89
89
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
90
90
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
91
91
  var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
92
+ var ACTIVATION_OPEN_CODES = ["opened", "no-link", "invalid-url", "unavailable", "failed"];
92
93
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
93
94
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
94
95
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -264,6 +265,10 @@ function parseSaveApiKeyRequest(value) {
264
265
  if (key.length > MAX_API_KEY) throw new Error(`API Key \u4E0D\u80FD\u8D85\u8FC7 ${MAX_API_KEY} \u5B57\u7B26`);
265
266
  return { value: key, expectedRevision: text(value.expectedRevision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION) };
266
267
  }
268
+ function parseActivationOpenState(value) {
269
+ if (!record(value)) throw new Error("\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C\u5FC5\u987B\u662F\u5BF9\u8C61");
270
+ return { code: member(value.code, ACTIVATION_OPEN_CODES, "\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C"), opened: flag(value.opened, "\u6388\u6743\u9875\u662F\u5426\u5DF2\u6253\u5F00") };
271
+ }
267
272
 
268
273
  // src/generated/typert.remote-client.ts
269
274
  var StatusCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#Status", create: () => z.unknown().transform((value) => parseStatus(value)) };
@@ -271,6 +276,7 @@ var QuotaSnapshotCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#
271
276
  var LoginStateCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#LoginState", create: () => z.unknown().transform((value) => parseLoginState(value)) };
272
277
  var StartLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#StartLoginRequest", create: () => z.unknown().transform((value) => parseStartLoginRequest(value)) };
273
278
  var CancelLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#CancelLoginRequest", create: () => z.unknown().transform((value) => parseCancelLoginRequest(value)) };
279
+ var ActivationOpenStateCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#ActivationOpenState", create: () => z.unknown().transform((value) => parseActivationOpenState(value)) };
274
280
  var SaveApiKeyRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#SaveApiKeyRequest", create: () => z.unknown().transform((value) => parseSaveApiKeyRequest(value)) };
275
281
  var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter", methods: {
276
282
  "openDesignCoRouter/status": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
@@ -280,6 +286,7 @@ var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter",
280
286
  "openDesignCoRouter/startLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: StartLoginRequestCodec, strictCodec: StartLoginRequestCodec }], result: LoginStateCodec },
281
287
  "openDesignCoRouter/loginStatus": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: LoginStateCodec },
282
288
  "openDesignCoRouter/cancelLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: CancelLoginRequestCodec, strictCodec: CancelLoginRequestCodec }], result: LoginStateCodec },
289
+ "openDesignCoRouter/openActivationPage": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: ActivationOpenStateCodec },
283
290
  "openDesignCoRouter/saveApiKey": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: SaveApiKeyRequestCodec, strictCodec: SaveApiKeyRequestCodec }], result: StatusCodec }
284
291
  } }, z);
285
292
  var typert_remote_client_default = TYPERT_REMOTE;
@@ -1,5 +1,5 @@
1
1
  import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
2
- import type { Status, QuotaSnapshot, LoginState, StartLoginRequest, CancelLoginRequest, SaveApiKeyRequest } from '../remote.js';
2
+ import type { Status, QuotaSnapshot, LoginState, StartLoginRequest, CancelLoginRequest, ActivationOpenState, SaveApiKeyRequest } from '../remote.js';
3
3
  export type CoRouterRemote = {
4
4
  status: () => Promise<RemoteResult<Status>>;
5
5
  refreshModels: () => Promise<RemoteResult<Status>>;
@@ -8,6 +8,7 @@ export type CoRouterRemote = {
8
8
  startLogin: (input: StartLoginRequest) => Promise<RemoteResult<LoginState>>;
9
9
  loginStatus: () => Promise<RemoteResult<LoginState>>;
10
10
  cancelLogin: (input: CancelLoginRequest) => Promise<RemoteResult<LoginState>>;
11
+ openActivationPage: () => Promise<RemoteResult<ActivationOpenState>>;
11
12
  saveApiKey: (input: SaveApiKeyRequest) => Promise<RemoteResult<Status>>;
12
13
  };
13
14
  declare module '@deepseek-ai/dsh-typert-protocol' {
@@ -89,6 +89,7 @@ var MAX_API_KEY = 16384;
89
89
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
90
90
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
91
91
  var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
92
+ var ACTIVATION_OPEN_CODES = ["opened", "no-link", "invalid-url", "unavailable", "failed"];
92
93
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
93
94
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
94
95
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -264,6 +265,10 @@ function parseSaveApiKeyRequest(value) {
264
265
  if (key.length > MAX_API_KEY) throw new Error(`API Key \u4E0D\u80FD\u8D85\u8FC7 ${MAX_API_KEY} \u5B57\u7B26`);
265
266
  return { value: key, expectedRevision: text(value.expectedRevision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION) };
266
267
  }
268
+ function parseActivationOpenState(value) {
269
+ if (!record(value)) throw new Error("\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C\u5FC5\u987B\u662F\u5BF9\u8C61");
270
+ return { code: member(value.code, ACTIVATION_OPEN_CODES, "\u6388\u6743\u9875\u6253\u5F00\u7ED3\u679C"), opened: flag(value.opened, "\u6388\u6743\u9875\u662F\u5426\u5DF2\u6253\u5F00") };
271
+ }
267
272
 
268
273
  // src/generated/typert.remote-client.ts
269
274
  var StatusCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#Status", create: () => z.unknown().transform((value) => parseStatus(value)) };
@@ -271,6 +276,7 @@ var QuotaSnapshotCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#
271
276
  var LoginStateCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#LoginState", create: () => z.unknown().transform((value) => parseLoginState(value)) };
272
277
  var StartLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#StartLoginRequest", create: () => z.unknown().transform((value) => parseStartLoginRequest(value)) };
273
278
  var CancelLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#CancelLoginRequest", create: () => z.unknown().transform((value) => parseCancelLoginRequest(value)) };
279
+ var ActivationOpenStateCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#ActivationOpenState", create: () => z.unknown().transform((value) => parseActivationOpenState(value)) };
274
280
  var SaveApiKeyRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#SaveApiKeyRequest", create: () => z.unknown().transform((value) => parseSaveApiKeyRequest(value)) };
275
281
  var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter", methods: {
276
282
  "openDesignCoRouter/status": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
@@ -280,6 +286,7 @@ var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter",
280
286
  "openDesignCoRouter/startLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: StartLoginRequestCodec, strictCodec: StartLoginRequestCodec }], result: LoginStateCodec },
281
287
  "openDesignCoRouter/loginStatus": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: LoginStateCodec },
282
288
  "openDesignCoRouter/cancelLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: CancelLoginRequestCodec, strictCodec: CancelLoginRequestCodec }], result: LoginStateCodec },
289
+ "openDesignCoRouter/openActivationPage": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: ActivationOpenStateCodec },
283
290
  "openDesignCoRouter/saveApiKey": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: SaveApiKeyRequestCodec, strictCodec: SaveApiKeyRequestCodec }], result: StatusCodec }
284
291
  } }, z);
285
292
  var typert_remote_client_default = TYPERT_REMOTE;
package/lib/index.d.ts CHANGED
@@ -5,9 +5,10 @@ import type { CatalogModel } from './catalog.js';
5
5
  import { readConfig, type LiveConfig } from './config.js';
6
6
  import { resolveSession } from './auth.js';
7
7
  import { type LoginOptions } from './login.js';
8
+ import { type ActivationLauncherOptions } from './browser-open.js';
8
9
  import { readQuota, type QuotaSnapshot } from './quota.js';
9
10
  import { readDesktopQuota } from './desktop-quota.js';
10
- import { type Status, type LoginState, type StartLoginRequest, type CancelLoginRequest, type SaveApiKeyRequest } from './remote.js';
11
+ import { type Status, type LoginState, type StartLoginRequest, type CancelLoginRequest, type SaveApiKeyRequest, type ActivationOpenState } from './remote.js';
11
12
  export { Config } from './config.js';
12
13
  export { CoRouterAdapter } from './adapter.js';
13
14
  export { MODELS } from './catalog.js';
@@ -20,11 +21,13 @@ export interface HostDependencies {
20
21
  fetchModels?: typeof fetchModelCatalog;
21
22
  modelsChanged?: () => void;
22
23
  loginOptions?: LoginOptions;
24
+ activationOptions?: ActivationLauncherOptions;
23
25
  }
24
26
  export declare class OpenDesignCoRouterService extends TypertRemoteService {
25
27
  readonly dependencies: HostDependencies;
26
28
  private readonly _config;
27
29
  private readonly _login;
30
+ private readonly _activation;
28
31
  private readonly _lifetime;
29
32
  private _revision;
30
33
  private _fingerprint;
@@ -62,6 +65,7 @@ export declare class OpenDesignCoRouterService extends TypertRemoteService {
62
65
  startLogin(input: StartLoginRequest): Promise<LoginState>;
63
66
  loginStatus(): LoginState;
64
67
  cancelLogin(input: CancelLoginRequest): LoginState;
68
+ openActivationPage(): Promise<ActivationOpenState>;
65
69
  saveApiKey(input: SaveApiKeyRequest): Promise<Status>;
66
70
  }
67
71
  declare module '@deepseek-ai/cordis' {