dsh-opendesign-corouter 0.1.1 → 0.1.2

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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * OpenAI 兼容 `GET /models` 模型发现(与选定推理协议无关)。
3
+ *
4
+ * 契约:
5
+ * - 只请求 OpenAI 兼容 API 的 `/models`;裸网关 origin 自动补 `/v1`,显式前缀保留。使用 `Bearer` 鉴权 + 应用归因头,
6
+ * `redirect: 'error'`,20 秒上限,响应体上限 2 MiB。
7
+ * - 与推理协议解耦:本函数不接受 protocol 参数,也不根据协议挑选路径。
8
+ * - 归一化只接受 `data[]` 中可用且有界的 id;已知 id 继承 catalog 的分组与能力,
9
+ * 新 id 一律归 `paid` 且**不**凭名称推断能力(未识别的上游能力字段直接忽略)。
10
+ * - 媒体输出模型(图像/视频/语音,走 `/api/v1/media/*`)不进对话目录。
11
+ * - 所有失败折叠为 `ModelDiscoveryError` 的固定分类与固定文字,不回显凭据、
12
+ * 请求头或响应体。
13
+ */
14
+ import { type CatalogModel } from './catalog.js';
15
+ /** 发现请求的总时限(毫秒)。 */
16
+ export declare const DISCOVERY_TIMEOUT_MS = 20000;
17
+ /** 响应体上限(字节);超出即按格式错误拒绝,不解析部分 JSON。 */
18
+ export declare const MAX_DISCOVERY_BODY_BYTES: number;
19
+ /** Failure taxonomy; `status` is the HTTP status when one was received. */
20
+ export type ModelDiscoveryErrorKind = 'url' | 'credential' | 'network' | 'timeout' | 'aborted' | 'auth' | 'permission' | 'rate-limit' | 'server' | 'response';
21
+ /**
22
+ * Fixed-text discovery failure. The message is one of the constants above, so callers may
23
+ * log it verbatim: no credential, header, URL or upstream body is ever interpolated.
24
+ */
25
+ export declare class ModelDiscoveryError extends Error {
26
+ readonly kind: ModelDiscoveryErrorKind;
27
+ readonly status?: number | undefined;
28
+ constructor(kind: ModelDiscoveryErrorKind, status?: number | undefined);
29
+ }
30
+ /**
31
+ * Discover the chat models an OpenAI-compatible endpoint advertises.
32
+ *
33
+ * @param openAiBaseURL Absolute HTTP(S) base URL without credentials/query/fragment.
34
+ * @param key Bearer credential; trimmed once, never echoed or persisted here.
35
+ * @param signal Caller cancellation, combined with the internal 20 s deadline.
36
+ * @param fetcher Optional `fetch` replacement (tests, hosts with a custom transport).
37
+ * @throws {ModelDiscoveryError} Fixed classification and fixed message for every failure.
38
+ */
39
+ export declare function fetchModelCatalog(openAiBaseURL: string, key: string, signal?: AbortSignal, fetcher?: typeof fetch): Promise<readonly CatalogModel[]>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 逐模型设置:用户在目录行之上显式覆盖的发送参数与容量。
3
+ *
4
+ * 独立于模型发现与推理协议:这里只做形状校验与副本合成,不读凭据、不联网、
5
+ * 也不判断某个推理协议是否支持这些字段(兼容性由适配器在组装请求时决定)。
6
+ *
7
+ * 安全约定:
8
+ * - 采用字段白名单,任何未知字段一律拒绝,避免 `apiKey`/`headers` 之类秘密或
9
+ * 不可序列化值借道设置对象进入持久化。
10
+ * - 拒绝 `__proto__` / `constructor` / `prototype` 作为模型 ID,防原型污染。
11
+ * - 只返回新对象:不把外部引用(数组、嵌套对象、目录行)交给调用方,也不改入参。
12
+ * - 错误文字固定,不回显设置值或模型 ID。
13
+ */
14
+ import type { CatalogModel } from './catalog.js';
15
+ /** One model's optional overrides; every field absent means "inherit the catalog row". */
16
+ export interface ModelSettings {
17
+ /** 是否出现在模型选择器;缺省视为启用。 */
18
+ enabled?: boolean;
19
+ /** 采样温度,0–2。 */
20
+ temperature?: number;
21
+ /** 核采样,0–1。 */
22
+ topP?: number;
23
+ /** 单次输出上限;覆盖目录行的 maxOutputTokens。 */
24
+ maxTokens?: number;
25
+ /** 上下文窗口;覆盖目录行的 contextWindow。 */
26
+ contextWindow?: number;
27
+ /** 思考深度;只允许目录同一套档位。 */
28
+ reasoningEffort?: string;
29
+ }
30
+ /** Model id → settings. Keys are model ids, never provider-scoped. */
31
+ export type ModelSettingsMap = Record<string, ModelSettings>;
32
+ /**
33
+ * Validate an untrusted settings payload (settings store, credential-free Host state)
34
+ * into a fresh map. Rejects non-objects, oversized maps, reserved/unsafe ids, unknown
35
+ * fields and out-of-range values; never returns the input's objects or arrays.
36
+ */
37
+ export declare function parseModelSettingsMap(value: unknown): ModelSettingsMap;
38
+ /**
39
+ * Copy a catalog row and apply the user's explicit capacity overrides. The input row and
40
+ * its arrays are never mutated; other overrides (sampling, effort) belong to the request,
41
+ * not to the catalog row, so they are not folded in here.
42
+ */
43
+ export declare function applyModelSettings(row: CatalogModel, settings?: ModelSettings): CatalogModel;
44
+ /** A model is selectable unless the map explicitly disables it; unknown ids and an absent map stay enabled. */
45
+ export declare function isModelEnabled(id: string, settings?: ModelSettingsMap): boolean;
@@ -0,0 +1,11 @@
1
+ export declare const API_PROTOCOL_IDS: readonly ["chat-completions", "responses", "messages"];
2
+ export type ApiProtocol = typeof API_PROTOCOL_IDS[number];
3
+ /** The public gateway origin and an explicit versioned API base are both accepted. */
4
+ export declare function openAiBaseURL(baseURL: string): string;
5
+ /** Shared by request builders and the settings preview so their paths cannot diverge. */
6
+ export declare function inferenceEndpoint(baseURL: string, protocol: ApiProtocol): string;
7
+ export declare const API_PROTOCOL_OPTIONS: readonly {
8
+ id: ApiProtocol;
9
+ name: string;
10
+ description: string;
11
+ }[];
@@ -0,0 +1,10 @@
1
+ /**
2
+ * OpenAI Chat Completions 协议(既有实现的唯一包装点)。
3
+ *
4
+ * 请求投影继续复用 `conversion.ts` 的 chat 消息/工具投影与思考回放规则;
5
+ * 流式解析继续复用 `stream.ts#consumeSse`,因此该协议的行为与拆分前完全一致。
6
+ */
7
+ import type { Protocol, ProtocolContext, WireRequest } from './types.js';
8
+ /** 构造 Chat Completions 请求。 */
9
+ export declare function buildChatCompletionsRequest(ctx: ProtocolContext): WireRequest;
10
+ export declare const chatCompletions: Protocol;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 上游失败的稳定分类。
3
+ *
4
+ * 三种协议的信封不同,但对外只暴露固定的中文文字与机器码;上游 `message`、请求头、
5
+ * 凭据与响应体一律不进入错误文字,避免密钥或第三方内容进入日志与 UI。
6
+ *
7
+ * 判定顺序(前者优先):
8
+ * 1. 信封内结构化的 `code` / `type`:只认明确的枚举式写法;
9
+ * 2. 主题明确的 `message`:上下文溢出、额度耗尽、限流、超时、服务端、认证/权限;
10
+ * 3. HTTP 状态:仅作为补充事实,不覆盖上面更具体的分类;唯一例外是 5xx——
11
+ * 没有结构化代码时直接按服务端处理(504 仍按超时),不因正文措辞改判。
12
+ *
13
+ * 关键取舍:
14
+ * - 额度分两池:套餐/订阅/账户额度 → `ACCOUNT_QUOTA`;钱包/充值/积分/余额 → `QUOTA`。
15
+ * 只有“额度不足”而没有池信息时按账户额度处理。
16
+ * - 普通 401/403 只表达认证或权限语义(`AUTH` / `PERMISSION`),不推断 API Key 无效;
17
+ * 只有上游明确点名 Key/凭据无效或缺失(结构化 code 或明确文案)才是 `INVALID_CREDENTIAL`。
18
+ * 结构化只给出认证/权限语义时,点名 Key 的明确文案可以把结论细化为凭据错误。
19
+ * - `quota` / `context` 单独出现只是背景词;必须同时出现“耗尽/超出”等语义才算数,
20
+ * 因此 `rate_limit_exceeded` 不会被泛化成额度不足,也不会因为正文提到“上下文缓存”而误判。
21
+ * - HTTP 状态与结构化错误冲突时以结构化错误为准:典型的 429 + `insufficient_quota`
22
+ * 归入额度而不是限流,401 + 额度证据归入额度而不是认证。
23
+ */
24
+ import { LlmError } from '@deepseek-ai/dsh-llm';
25
+ /**
26
+ * OpenAI 风格信封:`{ error: { code, type, message } }`(Chat Completions 与 Responses 实测一致)。
27
+ *
28
+ * @param status 上游 HTTP 状态;流内错误事件没有状态时省略。
29
+ * @param body 已解析的响应体或流事件。
30
+ */
31
+ export declare function gatewayError(status: number | undefined, body: unknown): LlmError;
32
+ /**
33
+ * Anthropic 风格信封:`{ type: "error", error: { type, message } }`(Messages 端点实测)。
34
+ *
35
+ * @param status 上游 HTTP 状态;流内错误事件没有状态时省略。
36
+ * @param body 已解析的响应体或流事件。
37
+ */
38
+ export declare function anthropicError(status: number | undefined, body: unknown): LlmError;
39
+ /**
40
+ * 流内错误事件(HTTP 已是 200,错误只能作为事件到达)。
41
+ *
42
+ * @param error 事件里的错误对象(两种信封都可)。
43
+ * @param fallback 无法归类时使用的机器码,默认按格式错误处理。
44
+ */
45
+ export declare function inBandError(error: unknown, fallback?: string): LlmError;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 三种推理协议共用的请求头。
3
+ *
4
+ * 每个上游请求都必须携带应用归因头(`attributionHeaders()`);鉴权一律走
5
+ * `Authorization: Bearer`——CoRouter 官方站点对 OpenAI 兼容调用与 Claude Code
6
+ * (`ANTHROPIC_AUTH_TOKEN`)都使用该头,因此 Messages 协议不额外发送第二个密钥头。
7
+ */
8
+ /**
9
+ * 构造流式推理请求头。
10
+ *
11
+ * @param apiKey 已由适配器校验可用的凭据;此处只做拼接,不回显、不记录。
12
+ * @param extra 协议专属头(如 `anthropic-version`)。
13
+ */
14
+ export declare function streamHeaders(apiKey: string, extra?: Readonly<Record<string, string>>): Record<string, string>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 三种线协议的公开入口。
3
+ *
4
+ * 模型发现(OpenAI `GET /models`)与推理协议解耦:无论这里选中哪种协议,
5
+ * 目录拉取始终走 OpenAI 兼容端口,见 `model-discovery.ts`。
6
+ */
7
+ import type { ApiProtocol } from '../protocol-options.js';
8
+ import type { Protocol } from './types.js';
9
+ /**
10
+ * 按配置 id 取协议实现。
11
+ *
12
+ * @param id 设置里保存的协议 id(来自固定三种)。
13
+ * @throws {LlmError} 配置里出现未知 id(例如降级安装保留了更新版本的设置)。
14
+ */
15
+ export declare function protocolFor(id: ApiProtocol): Protocol;
16
+ export type { Protocol, ProtocolContext, WireRequest } from './types.js';
17
+ export { MESSAGES_DEFAULT_MAX_TOKENS, messagesURL } from './messages.js';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Anthropic Messages 协议。
3
+ *
4
+ * 端点:Base URL 可以是网关根(官方 `ANTHROPIC_BASE_URL` 形式)或已带 `/v1` 的推理 Base,
5
+ * 两种都会补成 `<base>/v1/messages`。鉴权沿用 CoRouter 官方 Claude Code 配置里的
6
+ * `Authorization: Bearer`,外加标准 `anthropic-version`;不额外发送第二个密钥头。
7
+ *
8
+ * 不支持项按显式失败处理,不静默丢弃:
9
+ * - 请求无 `max_tokens` 时用保守默认 4096(适配器元数据同步该默认值,用户可按模型覆盖);
10
+ * - `reasoningEffort` 按现代 Messages 标准投影为 `thinking: {type:'adaptive'}` +
11
+ * `output_config.effort`;`none` 投影为 `thinking: {type:'disabled'}`;`minimal`/`ultra`
12
+ * 等没有对应 effort 的档位抛 UNSUPPORTED_OPTION;
13
+ * - `temperature`/`top_p` 等采样参数原样透传,其取值限制(只针对特定 Claude 代际)与
14
+ * 「仅接受 enabled + budget_tokens 的 legacy 模型」能否使用,都由上游按实际模型判定,
15
+ * 本模块不做本地推断,而是把上游拒绝分类上报;
16
+ * - 历史 assistant 的 reasoning 块没有 Anthropic 签名,**不回放**(伪造签名会污染上下文),
17
+ * 只把本轮 `thinking_delta` 映射成 reasoning 输出块。
18
+ */
19
+ import { type StreamChunk } from '@deepseek-ai/dsh-llm';
20
+ import type { Protocol, ProtocolContext, WireRequest } from './types.js';
21
+ /** 未显式给出 `max_tokens` 时的保守默认值(与适配器元数据一致)。 */
22
+ export declare const MESSAGES_DEFAULT_MAX_TOKENS = 4096;
23
+ /** 网关根或 `/v1` Base 都补成 `/v1/messages`。 */
24
+ export declare function messagesURL(baseURL: string): string;
25
+ /** 构造 Messages 请求。 */
26
+ export declare function buildMessagesRequest(ctx: ProtocolContext): WireRequest;
27
+ /** 把 Messages 的 SSE 事件翻译成 DSH 流式块。 */
28
+ export declare function streamMessages(body: ReadableStream<Uint8Array>, signal: AbortSignal, idleMs: number): AsyncGenerator<StreamChunk>;
29
+ export declare const messages: Protocol;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 消息内容的模态投影(Responses / Messages 共用)。
3
+ *
4
+ * 与 Chat Completions 的 `conversion.ts#parts` 保持同一语义:文本直出,可解析的图片变成
5
+ * 图片 URL,无法解析或已 offload 的图片退化为命名占位文本,原始文件句柄直接拒绝。
6
+ */
7
+ import { type ContentBlock } from '@deepseek-ai/dsh-llm';
8
+ import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
9
+ /** 图片附件 → 请求体可用的 URL(通常是 data URL)。 */
10
+ export type ImageLookup = (ref: ImageAttachmentRef) => string | undefined;
11
+ /** 一段消息投影后的文本与图片。 */
12
+ export interface ProjectedContent {
13
+ text: string;
14
+ images: string[];
15
+ }
16
+ /**
17
+ * 投影一个消息的文本与图片。
18
+ *
19
+ * @param blocks 消息内容块。
20
+ * @param image 图片解析器;未提供时所有图片退化为占位文本。
21
+ */
22
+ export declare function projectContent(blocks: readonly ContentBlock[], image?: ImageLookup): ProjectedContent;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * OpenAI Responses 协议(无状态:`store: false` + 每次发送完整 `input`)。
3
+ *
4
+ * 为什么坚持无状态:DSH 的请求是会话日志的纯函数,适配器不持久化 `previous_response_id`,
5
+ * 因此重放、重试与并行分支都不会依赖服务端会话;代价是 reasoning 项无法回放——Responses
6
+ * 只在带 `encrypted_content` 时允许跨轮重放推理项,本插件不保存该内容,所以历史推理只映射
7
+ * 为本轮可见的 reasoning 块,再次请求时不会伪装成已签名的上下文。
8
+ *
9
+ * 终止语义(官方 `response.completed` / `response.incomplete` / `response.failed`):
10
+ * - `completed`:有 `function_call` 项 → tool-calls,否则 stop;
11
+ * - `incomplete`:`max_tokens`/`max_output_tokens` → max-tokens(截断**不**当成功全文),
12
+ * `content_filter` 与其它原因 → 直接失败,绝不产出 finish;
13
+ * - `failed` 或流内 `error` 事件 → 按固定分类抛错,不回显上游正文。
14
+ */
15
+ import { type StreamChunk } from '@deepseek-ai/dsh-llm';
16
+ import type { Protocol, ProtocolContext, WireRequest } from './types.js';
17
+ /**
18
+ * 构造 Responses 请求。
19
+ *
20
+ * 消息投影:system/developer 文本进 `instructions`(DSH 未声明 in-history 系统提示更新,
21
+ * 完整系统提示每轮都在开头,合并后语义不变);assistant 文本 + `function_call` 项;
22
+ * tool 结果变 `function_call_output`,其中的图片提升到紧随其后的 user 输入项。
23
+ */
24
+ export declare function buildResponsesRequest(ctx: ProtocolContext): WireRequest;
25
+ /**
26
+ * 把 Responses 的 SSE 事件翻译成 DSH 流式块。
27
+ *
28
+ * 支持交错输出项(文本 / 工具调用 / 推理),按首次出现顺序分配块索引;
29
+ * 只出现在终态 `output` 里的项也会补成块,避免“终态才给内容”的网关被误判为空响应。
30
+ */
31
+ export declare function streamResponses(body: ReadableStream<Uint8Array>, signal: AbortSignal, idleMs: number): AsyncGenerator<StreamChunk>;
32
+ export declare const responses: Protocol;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 协议无关的 SSE 分帧。
3
+ *
4
+ * 只负责传输层:把字节流按 SSE 行协议切成 `event:` + 多行 `data:` 帧,并统一强制
5
+ * 取消、空闲超时与大小上限。事件语义(JSON 结构、终止、usage)由各协议模块解释。
6
+ *
7
+ * 边界与既有 Chat Completions 实现一致,便于回归:
8
+ * - CRLF 可能被拆到两个传输块之间,保留尾部 CR。
9
+ * - 单事件累计 `data` 与单行缓冲都不得超过 4 MiB;超限按格式错误拒绝,不截断解析。
10
+ * - 空闲超过 `idleMs` 抛 TIMEOUT;`signal` 中止时抛出 signal 的原因。
11
+ * - 无论成功、异常还是消费方提前 `break`,都会 cancel 并释放上游 reader。
12
+ */
13
+ /** 一帧 SSE 事件:`event:` 名(可选)+ 合并后的 `data` 载荷。 */
14
+ export interface SseFrame {
15
+ readonly event?: string;
16
+ readonly data: string;
17
+ }
18
+ /**
19
+ * 逐帧产出 SSE 事件。
20
+ *
21
+ * @param body 上游响应体;结束时由本生成器负责 cancel。
22
+ * @param signal 调用方取消信号(已与超时合并)。
23
+ * @param idleMs 两次读到数据之间的最长等待毫秒数。
24
+ */
25
+ export declare function sseFrames(body: ReadableStream<Uint8Array>, signal: AbortSignal, idleMs: number): AsyncGenerator<SseFrame>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 协议模块的公共契约。
3
+ *
4
+ * 适配器只负责:选定模型、准备凭据、解析图片、收敛 `maxTokens`、合并每模型默认值,
5
+ * 然后把一次调用的**全部输入**交给协议模块。协议模块独占三件事:请求 URL/头/体、
6
+ * 流式事件到 `StreamChunk` 的翻译、以及该协议的错误信封。
7
+ */
8
+ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
9
+ import type { CatalogModel } from '../catalog.js';
10
+ import type { ApiProtocol } from '../protocol-options.js';
11
+ /** 一次推理调用的协议无关输入。 */
12
+ export interface ProtocolContext {
13
+ /** 上游 Base URL(已校验为绝对地址,去尾斜杠)。 */
14
+ baseURL: string;
15
+ /** 已校验可用的凭据。 */
16
+ apiKey: string;
17
+ /** 本次调用的目录模型(能力、限额来源)。 */
18
+ model: CatalogModel;
19
+ /** 已由适配器合并每模型默认值的请求。 */
20
+ options: GenerateOptions;
21
+ /** 附件 id → 可直接放进请求体的图片 URL。 */
22
+ images: ReadonlyMap<string, string>;
23
+ /** 每模型可选 `top_p`;未设置时不发送。 */
24
+ topP?: number;
25
+ }
26
+ /** 一个可直接 fetch 的请求。 */
27
+ export interface WireRequest {
28
+ url: string;
29
+ headers: Record<string, string>;
30
+ body: unknown;
31
+ }
32
+ /** 一种线协议的实现。 */
33
+ export interface Protocol {
34
+ readonly id: ApiProtocol;
35
+ /** 把协议无关调用投影成本协议的请求。 */
36
+ build(ctx: ProtocolContext): WireRequest;
37
+ /** 把上游 SSE 响应体翻译成 DSH 流式块。 */
38
+ stream(body: ReadableStream<Uint8Array>, signal: AbortSignal, idleMs: number): AsyncIterable<StreamChunk>;
39
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * usage 归一化:把上游计数折算成 DSH 的**不相交**口径。
3
+ *
4
+ * `TokenUsage.inputTokens` 只计未缓存输入;缓存读取/写入单独上报,三者相加才是计费输入。
5
+ * 两套上游口径不同,必须分开处理:
6
+ * - OpenAI(Chat / Responses)的输入总数**包含**缓存命中,需要减出来;
7
+ * - Anthropic Messages 的 `input_tokens` **不含**缓存读取与缓存写入,直接对应。
8
+ *
9
+ * 任何非法或自相矛盾的缓存计数都会被丢弃,而不是产生负数或凭空的输入量。
10
+ */
11
+ import type { TokenUsage } from '@deepseek-ai/dsh-llm';
12
+ /** 上游原始计数(缺省或非法一律视为未知)。 */
13
+ export interface RawUsage {
14
+ input?: number | undefined;
15
+ output?: number | undefined;
16
+ cacheRead?: number | undefined;
17
+ cacheWrite?: number | undefined;
18
+ reasoning?: number | undefined;
19
+ total?: number | undefined;
20
+ }
21
+ /**
22
+ * OpenAI 口径:`input` 含缓存命中。
23
+ *
24
+ * @param raw 上游计数。
25
+ * @returns 归一化 usage;输入或输出缺失、为负时返回 undefined。
26
+ */
27
+ export declare function openAiUsage(raw: RawUsage): TokenUsage | undefined;
28
+ /**
29
+ * Anthropic 口径:`input` 不含缓存,缓存读取/写入是独立计数。
30
+ *
31
+ * @param raw 上游计数。
32
+ * @returns 归一化 usage;输入或输出缺失、为负时返回 undefined。
33
+ */
34
+ export declare function anthropicUsage(raw: RawUsage): TokenUsage | undefined;
package/lib/quota.d.ts CHANGED
@@ -14,6 +14,10 @@ export interface QuotaWindow {
14
14
  remaining?: number;
15
15
  percent: number;
16
16
  resetsAt?: string;
17
+ /** Window start as reported by coding-plan-usage (ISO-8601); never derived from the reset time. */
18
+ windowStart?: string;
19
+ /** Service-side window policy (`activity_triggered`, `anchored_recurring`, ...), when reported. */
20
+ resetMode?: string;
17
21
  }
18
22
  export interface QuotaSnapshot {
19
23
  state: 'ok' | 'partial' | 'need-login' | 'error';
@@ -27,6 +31,20 @@ export interface QuotaSnapshot {
27
31
  rechargeUsd?: number;
28
32
  totalUsd?: number;
29
33
  };
34
+ /**
35
+ * Billing-period boundaries of the subscription pool (ISO-8601), from
36
+ * `billing/summary`'s `subscriptionCurrentPeriodStart/End`. `End` is when the
37
+ * plan model credits refresh — distinct from a rolling window's `resetsAt`.
38
+ */
39
+ subscriptionPeriodStart?: string;
40
+ subscriptionPeriodEnd?: string;
41
+ /** Next client-side read, owned by the host cache policy; this module never invents it. */
42
+ nextRefreshAt?: string;
43
+ /** A failed non-auth refresh retained the previous successful readings. */
44
+ stale?: boolean;
45
+ /** Consecutive failed automatic reads; manual refresh starts a new bounded sequence. */
46
+ refreshFailures?: number;
47
+ autoRefreshPaused?: boolean;
30
48
  note?: string;
31
49
  }
32
50
  export declare class ConsoleError extends Error {
@@ -35,6 +53,8 @@ export declare class ConsoleError extends Error {
35
53
  constructor(kind: 'auth' | 'permission' | 'network' | 'response' | 'rate-limit', status?: number | undefined);
36
54
  }
37
55
  export declare function consoleJson(url: string, session: Session, signal?: AbortSignal, fetcher?: typeof fetch): Promise<unknown>;
38
- export declare function normalizeQuota(body: unknown, summary?: unknown): Pick<QuotaSnapshot, 'windows' | 'balances' | 'tier' | 'eligible'>;
56
+ export declare function normalizeQuota(body: unknown, summary?: unknown): Pick<QuotaSnapshot, 'windows' | 'balances' | 'tier' | 'eligible' | 'subscriptionPeriodStart' | 'subscriptionPeriodEnd'>;
57
+ /** Billing period of the subscription pool; absent fields stay unknown, never derived. */
58
+ export declare function normalizeSubscription(summary: unknown): Pick<QuotaSnapshot, 'subscriptionPeriodStart' | 'subscriptionPeriodEnd'>;
39
59
  export declare function normalizeBalances(summary: unknown): QuotaSnapshot['balances'];
40
60
  export declare function readQuota(session: Session, signal?: AbortSignal, fetcher?: typeof fetch): Promise<QuotaSnapshot>;
package/lib/remote.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** Shared JSON value types and allowlist parsers; no credential or opaque Host state is returned. */
2
2
  import type { QuotaSnapshot, SessionSource } from './quota.js';
3
+ import { type ApiProtocol } from './protocol-options.js';
4
+ import { type ModelSettingsMap } from './model-settings.js';
3
5
  export type { QuotaSnapshot, QuotaWindow, SessionSource } from './quota.js';
4
6
  /** npm package that owns the Remote methods; must satisfy typert's segment rules. */
5
7
  export declare const REMOTE_PACKAGE = "dsh-opendesign-corouter";
@@ -12,12 +14,21 @@ export interface StatusConfig {
12
14
  baseURL: string;
13
15
  /** Credential reference name (an env var or a DSH credential ref), never the value. */
14
16
  apiKeyEnv: string;
17
+ apiProtocol?: ApiProtocol;
18
+ modelSettings?: ModelSettingsMap;
19
+ requestTimeoutMs?: number;
20
+ streamIdleTimeoutMs?: number;
21
+ quotaRefreshSeconds?: number;
15
22
  }
16
23
  /** One selectable model; `group` selects which of the two Providers owns it. */
17
24
  export interface StatusModel {
18
25
  id: string;
19
26
  name: string;
20
27
  group: 'plan' | 'paid';
28
+ enabled?: boolean;
29
+ contextWindow?: number;
30
+ maxOutputTokens?: number;
31
+ reasoningEfforts?: readonly string[];
21
32
  }
22
33
  /** Terminal-or-in-flight phases a login job may report. */
23
34
  export type LoginPhase = 'idle' | 'launching' | 'waiting' | 'verifying' | 'success' | 'cancelled' | 'error';
@@ -41,6 +52,9 @@ export interface Status {
41
52
  /** Turns the empty-turn guard actually intercepted since process start. */
42
53
  guardedTurns: number;
43
54
  models: readonly StatusModel[];
55
+ modelsSource?: 'builtin' | 'openai';
56
+ modelsUpdatedAt?: string;
57
+ modelsError?: string;
44
58
  }
45
59
  export interface StartLoginRequest {
46
60
  expectedRevision: string;
package/lib/stream.d.ts CHANGED
@@ -1,5 +1,13 @@
1
- import { LlmError, type StreamChunk, type TokenUsage } from '@deepseek-ai/dsh-llm';
2
- export declare function gatewayError(status: number | undefined, body: unknown): LlmError;
1
+ /**
2
+ * Chat Completions 的流式投影与共享错误分类。
3
+ *
4
+ * 分帧已抽到 `protocols/sse.ts`(三种协议共用同一套取消/空闲/大小边界),这里只保留
5
+ * Chat Completions 的事件语义;`gatewayError` 也从 `protocols/errors.ts` 统一提供,
6
+ * 以保持既有 `import { gatewayError } from './stream.js'` 的调用点不变。
7
+ */
8
+ import { type StreamChunk, type TokenUsage } from '@deepseek-ai/dsh-llm';
9
+ export { gatewayError } from './protocols/errors.js';
10
+ /** Chat Completions 的 `usage` 折算(`prompt_tokens` 含缓存,按不相交口径减出)。 */
3
11
  export declare function tokenUsage(value: Record<string, unknown>): TokenUsage | undefined;
4
- /** Bounded SSE framing; a broken/truncated response is never reported as success. */
12
+ /** Bounded SSE projection for Chat Completions; a broken/truncated response is never reported as success. */
5
13
  export declare function consumeSse(body: ReadableStream<Uint8Array>, signal: AbortSignal, idleMs: number): AsyncGenerator<StreamChunk>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opendesign-corouter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "OpenDesign CoRouter:套餐赠送与付费模型双 Provider、桌面登录态额度及独立登录",
5
5
  "type": "module",
6
6
  "license": "MIT",