hiwork-core 0.0.29 → 0.0.35
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/cordis.patch.yml +15 -1
- package/lib/ai-router-client.js +394 -2
- package/lib/client-policy.js +2 -2
- package/lib/client.js +2483 -448
- package/lib/image-rpc.js +306 -0
- package/lib/imagegen.js +424 -0
- package/lib/index.js +19212 -7694
- package/lib/knowledge-config.js +91 -1
- package/lib/provision-session.js +10 -2
- package/lib/tool-names.js +10 -0
- package/lib/tools.js +205 -0
- package/lib/types/ai-router-client.d.ts +255 -2
- package/lib/types/client/about-section.d.ts +27 -7
- package/lib/types/client/controller.d.ts +17 -9
- package/lib/types/client/feature-center.d.ts +46 -1
- package/lib/types/client/feature-registry.d.ts +36 -3
- package/lib/types/client/image-cards.d.ts +218 -0
- package/lib/types/client/image-styles.d.ts +9 -0
- package/lib/types/client/images-data.d.ts +127 -0
- package/lib/types/client/images-page.d.ts +97 -0
- package/lib/types/client/index.d.ts +0 -1
- package/lib/types/client/login-gate.d.ts +2 -2
- package/lib/types/client/logo.d.ts +7 -2
- package/lib/types/client/menu-preferences.d.ts +163 -0
- package/lib/types/client/menu-section.d.ts +5 -0
- package/lib/types/client/menu-store.d.ts +101 -0
- package/lib/types/client/styles.d.ts +16 -0
- package/lib/types/client/types.d.ts +9 -0
- package/lib/types/client/voice/entry.d.ts +29 -3
- package/lib/types/image-rpc.d.ts +142 -0
- package/lib/types/imagegen.d.ts +241 -0
- package/lib/types/index.d.ts +70 -8
- package/lib/types/knowledge-config.d.ts +50 -2
- package/lib/types/tool-names.d.ts +11 -0
- package/lib/types/tools.d.ts +80 -0
- package/lib/types/user-agent.d.ts +31 -0
- package/lib/user-agent.js +54 -0
- package/lib/web-search.js +2 -2
- package/package.json +35 -25
- package/lib/types/client/daily-items.d.ts +0 -25
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { type GatewayImageConfig, type GatewayImageFile, type GatewayImageGenerateResult, type GatewayImageRow, type GatewayImagesView } from './ai-router-client.ts';
|
|
2
|
+
/**
|
|
3
|
+
* 允许的尺寸三值。**跨仓冻结契约**(与网关 `image.default_size` 的枚举、Agent 工具 schema
|
|
4
|
+
* 逐项一致):`defineTool` 在注册期就把 schema 冻死,没法让枚举跟着 `image-config` 走。
|
|
5
|
+
*/
|
|
6
|
+
export declare const IMAGE_SIZES: readonly ["1024x1024", "1024x1536", "1536x1024"];
|
|
7
|
+
export type ImageSize = (typeof IMAGE_SIZES)[number];
|
|
8
|
+
/** 尺寸缺省值(网关 `image-config` 读不到时的兜底)。 */
|
|
9
|
+
export declare const IMAGE_DEFAULT_SIZE: ImageSize;
|
|
10
|
+
/** 工作区里存放生成图的目录名(设计 §6.2:`<会话工作区>/图片/`)。 */
|
|
11
|
+
export declare const IMAGE_DIR_NAME = "\u56FE\u7247";
|
|
12
|
+
/**
|
|
13
|
+
* 单张图上限 20 MiB(与桌面端附件上限同值)。`fetch` 端点超过它直接拒绝——
|
|
14
|
+
* 绝不把几十 MB 的 base64 塞进 loopback RPC 信封。
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_IMAGE_BYTES: number;
|
|
17
|
+
/** 本次调用用的工作区根取不到时的兜底(无会话上下文的画廊页 `save` 也用它)。 */
|
|
18
|
+
export declare const IMAGE_FALLBACK_WORKSPACE: string;
|
|
19
|
+
/** `image-config` 的 Host 侧缓存时长(设计 §6.3:≤5 分钟)。 */
|
|
20
|
+
export declare const IMAGE_CONFIG_CACHE_TTL_MS: number;
|
|
21
|
+
/** 落盘成功的一张图。`path` 是**绝对路径**,落在会话工作区内。 */
|
|
22
|
+
export interface GeneratedImageFile {
|
|
23
|
+
path: string;
|
|
24
|
+
mimeType: string;
|
|
25
|
+
byteSize: number;
|
|
26
|
+
revisedPrompt?: string;
|
|
27
|
+
}
|
|
28
|
+
/** 生图成功:图片已写进工作区。 */
|
|
29
|
+
export interface ImageGenerateSuccess {
|
|
30
|
+
ok: true;
|
|
31
|
+
files: GeneratedImageFile[];
|
|
32
|
+
prompt: string;
|
|
33
|
+
model: string;
|
|
34
|
+
/** 网关回显的实际尺寸(仅用于展示/日志)。 */
|
|
35
|
+
size: string;
|
|
36
|
+
generationMs: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 生图失败。`code` 直接透传网关的 `IMAGE_*`(卡片按 code 分文案,设计 §6.6)。
|
|
40
|
+
*
|
|
41
|
+
* 写盘失败也走这一支(`code: 'write-failed'`):上游已经计费、图片已经生成,
|
|
42
|
+
* 只把 `files` 置空并把上游信息带上,让卡片说「图片已生成但没能保存到工作区」——
|
|
43
|
+
* **不让员工以为白花了钱**。
|
|
44
|
+
*/
|
|
45
|
+
export interface ImageGenerateFailure {
|
|
46
|
+
ok: false;
|
|
47
|
+
code: string;
|
|
48
|
+
message: string;
|
|
49
|
+
files: readonly GeneratedImageFile[];
|
|
50
|
+
prompt?: string;
|
|
51
|
+
model?: string;
|
|
52
|
+
size?: string;
|
|
53
|
+
generationMs?: number;
|
|
54
|
+
}
|
|
55
|
+
export type GenerateOutcome = ImageGenerateSuccess | ImageGenerateFailure;
|
|
56
|
+
/** 画廊页 `save` 的结果:工作区/写盘这类**业务**失败用 `{ok:false, code, reason}`(设计 §6.5)。 */
|
|
57
|
+
export type SaveImageOutcome = {
|
|
58
|
+
ok: true;
|
|
59
|
+
path: string;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
code: 'IMAGE_WORKSPACE_NOT_FOUND' | 'IMAGE_SAVE_FAILED';
|
|
63
|
+
reason: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* 网关客户端的生图子集:只声明本模块真正调用的方法(结构兼容 `AiRouterClient` 实例),
|
|
67
|
+
* 单测因此不需要伪造整个客户端。**四**个方法——「存到工作区」不是网关端点,
|
|
68
|
+
* 它就是本模块的 {@link saveImageToWorkspace}(取本体 + 写盘)。
|
|
69
|
+
*/
|
|
70
|
+
export interface ImageGatewayPort {
|
|
71
|
+
getMyImageConfig(sessionCookie: string): Promise<GatewayImageConfig>;
|
|
72
|
+
generateImage(sessionCookie: string, req: {
|
|
73
|
+
prompt: string;
|
|
74
|
+
size?: string;
|
|
75
|
+
quality?: string;
|
|
76
|
+
}, keyId?: string, timeoutMs?: number): Promise<GatewayImageGenerateResult>;
|
|
77
|
+
getMyImages(sessionCookie: string, req: {
|
|
78
|
+
page?: number;
|
|
79
|
+
pageSize?: number;
|
|
80
|
+
}): Promise<GatewayImagesView>;
|
|
81
|
+
fetchMyImage(sessionCookie: string, id: number): Promise<GatewayImageFile>;
|
|
82
|
+
}
|
|
83
|
+
/** 工作区注册表的读取面(画廊页 `save` 的 `workspaceId` → 目录路径)。 */
|
|
84
|
+
export interface ImageWorkspacePort {
|
|
85
|
+
/** 取不到该 id 时返回 undefined(页面据此显示原因,而不是落进别的工作区)。 */
|
|
86
|
+
resolveWorkspacePath(workspaceId: string): string | undefined;
|
|
87
|
+
}
|
|
88
|
+
/** Host 侧生图依赖:会话 + 网关 + 工作区 + 日志。 */
|
|
89
|
+
export interface ImagegenDeps {
|
|
90
|
+
/**
|
|
91
|
+
* 解析网关会话 cookie。未登录 / 会话缺失返回 undefined。
|
|
92
|
+
* **返回值只在本模块内部使用,绝不出现在响应里。**
|
|
93
|
+
*/
|
|
94
|
+
resolveSession(): Promise<string | undefined>;
|
|
95
|
+
/** 当前 baseURL 对应的网关客户端;baseURL 未配置时返回 undefined。 */
|
|
96
|
+
gateway(): ImageGatewayPort | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* 工作区根(**本次调用**的会话工作区,由工具侧从 `exec.agent.session.header.cwd` 取)。
|
|
99
|
+
* 缺省回落 {@link IMAGE_FALLBACK_WORKSPACE}。`request.workspaceRoot` 优先于它。
|
|
100
|
+
*/
|
|
101
|
+
workspaceRoot?: string | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* `image-config` 读取器(Host 侧缓存 ≤5 分钟)。缺省直接读网关、失败即 undefined
|
|
104
|
+
* (尺寸拿不到就落 {@link IMAGE_DEFAULT_SIZE},不让配置读取失败挡住生图)。
|
|
105
|
+
*/
|
|
106
|
+
imageConfig?: ((sessionCookie: string) => Promise<GatewayImageConfig | undefined>) | undefined;
|
|
107
|
+
/** 工作区注册表读取面(画廊页 `save` 用)。 */
|
|
108
|
+
workspaces?: ImageWorkspacePort | undefined;
|
|
109
|
+
/** 时钟(测试注入;缺省 `new Date()`)。 */
|
|
110
|
+
now?: (() => Date) | undefined;
|
|
111
|
+
/** Host 日志出口(只记元数据:尺寸 / 耗时 / 字节数 / 错误类型)。 */
|
|
112
|
+
log?: ((message: string) => void) | undefined;
|
|
113
|
+
}
|
|
114
|
+
/** 一次生图请求。`size` / `quality` 缺省由 Host / 网关补默认值。 */
|
|
115
|
+
export interface ImageGenerateRequest {
|
|
116
|
+
prompt: string;
|
|
117
|
+
size?: string | undefined;
|
|
118
|
+
quality?: string | undefined;
|
|
119
|
+
/** 本次调用的工作区根(工具侧从会话 cwd 取);优先于 `deps.workspaceRoot`。 */
|
|
120
|
+
workspaceRoot?: string | undefined;
|
|
121
|
+
}
|
|
122
|
+
/** 越出工作区的路径(内部异常,映射成 `path-outside-workspace`)。 */
|
|
123
|
+
export declare class ImagePathError extends Error {
|
|
124
|
+
readonly target: string;
|
|
125
|
+
constructor(target: string);
|
|
126
|
+
}
|
|
127
|
+
/** 工作区里的图片目录。 */
|
|
128
|
+
export declare function imageDirectory(workspaceRoot: string): string;
|
|
129
|
+
/**
|
|
130
|
+
* `target` 是否严格落在 `workspaceRoot` 内(`workspaceRoot` 本身不算)。
|
|
131
|
+
*
|
|
132
|
+
* 用 `path.relative` 判定而不是字符串前缀:前缀法会被 `/tmp/ws-other` 这种「同前缀的
|
|
133
|
+
* 兄弟目录」骗过去。两侧都只做 `resolve`(不做 `realpath`)——拼路径与校验用同一套
|
|
134
|
+
* 拼法,符号链接的归一化是文件路由那边的事(设计 §1.3 路径 ①)。
|
|
135
|
+
*/
|
|
136
|
+
export declare function isInsideWorkspace(workspaceRoot: string, target: string): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* 拼一张图的落盘绝对路径并做越界校验(**唯一**的拼法,写盘与校验共用它)。
|
|
139
|
+
* 越界即抛 {@link ImagePathError}。
|
|
140
|
+
*
|
|
141
|
+
* **两道都要过**:
|
|
142
|
+
* 1. 落在工作区根内(设计 §6.2 的前缀校验);
|
|
143
|
+
* 2. 落在 `<工作区>/图片/` 内(「只写这个目录」)——`../x.png` 会滑进工作区根,
|
|
144
|
+
* 虽然没出工作区,但不在约定目录里,同样拒绝。
|
|
145
|
+
*/
|
|
146
|
+
export declare function imageFilePath(workspaceRoot: string, fileName: string): string;
|
|
147
|
+
/**
|
|
148
|
+
* prompt → 文件名 slug:取前 24 个字符(先 trim,免得名字以 `-` 开头)、非法字符换成 `-`。
|
|
149
|
+
* 结果为空 / 只剩 `-`(纯标点、纯空白)时用 `image` 兜底。
|
|
150
|
+
*
|
|
151
|
+
* 中文与 emoji 是**合法**字符,原样保留(文件系统支持)。
|
|
152
|
+
*/
|
|
153
|
+
export declare function slugFromPrompt(prompt: string): string;
|
|
154
|
+
/** `<yyyyMMdd-HHmmss>`(本地时区,与用户看到的文件时间一致)。 */
|
|
155
|
+
export declare function imageStamp(when: Date): string;
|
|
156
|
+
/**
|
|
157
|
+
* 扩展名:**按图片自身的格式**取,未知格式落 `png`。
|
|
158
|
+
*
|
|
159
|
+
* 刻意的偏离(设计 §6.2 写的是 `<…>.png`):客户端同源文件路由按**扩展名**定 MIME
|
|
160
|
+
* (设计 §1.3),一张 JPEG 存成 `.png` 会被当成 PNG 下发。上游常态是 PNG,
|
|
161
|
+
* 所以正常路径与文档逐字一致,只有非 PNG 才不同。
|
|
162
|
+
*/
|
|
163
|
+
export declare function imageExtension(format: string, mimeType: string): string;
|
|
164
|
+
/**
|
|
165
|
+
* 写一张图到 `<workspaceRoot>/图片/`:目录只 `mkdir recursive` 一次,同名加 `-2` / `-3` 后缀。
|
|
166
|
+
*
|
|
167
|
+
* 用 `flag: 'wx'`(不存在才创建)而不是「先 exists 再写」:两者之间没有 TOCTOU 窗口,
|
|
168
|
+
* 命名冲突与真正的写盘失败因此能被分开(前者换名字重试,后者如实上报)。
|
|
169
|
+
*
|
|
170
|
+
* @returns 写入的**绝对路径**。
|
|
171
|
+
*/
|
|
172
|
+
export declare function writeImageBytes(workspaceRoot: string, baseName: string, extension: string, bytes: Uint8Array): Promise<string>;
|
|
173
|
+
/** 直接读网关配置;**永不抛异常**(读不到返回 undefined,调用方落默认值)。 */
|
|
174
|
+
export declare function readImageConfig(gateway: ImageGatewayPort, sessionCookie: string, log?: ((message: string) => void) | undefined): Promise<GatewayImageConfig | undefined>;
|
|
175
|
+
/**
|
|
176
|
+
* 包一层 ≤5 分钟缓存的 `image-config` 读取器(设计 §6.3:工具补默认尺寸时读它)。
|
|
177
|
+
*
|
|
178
|
+
* 缓存只存**配置**(开关/模型名/尺寸枚举),不存 cookie、不存任何凭据;同一 Host 进程
|
|
179
|
+
* 只服务一个登录账号,所以缓存不需要按账号分键。
|
|
180
|
+
*
|
|
181
|
+
* 但**要按 `key` 分**:`key` 是调用方给的缓存键(生产传 gateway baseURL)——用户在
|
|
182
|
+
* 「网关模型」里换了地址之后,旧网关的默认尺寸与模型名不该继续生效 5 分钟。
|
|
183
|
+
* 只保留最近一个键(换地址是罕见操作,为此维护一张有界 LRU 不值)。
|
|
184
|
+
*/
|
|
185
|
+
export declare function createCachedImageConfigReader(options?: {
|
|
186
|
+
ttlMs?: number;
|
|
187
|
+
now?: () => number;
|
|
188
|
+
}): (gateway: ImageGatewayPort, sessionCookie: string, log?: ((message: string) => void) | undefined, key?: string) => Promise<GatewayImageConfig | undefined>;
|
|
189
|
+
/** 通用失败文案(未知 code / message 不可用时的兜底):不暴露任何网关细节。 */
|
|
190
|
+
export declare const IMAGE_GENERIC_FAILURE_MESSAGE = "\u56FE\u7247\u751F\u6210\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
191
|
+
/** 未登录 / 会话失效的统一文案(用量、语音、生图三条频道同口径)。 */
|
|
192
|
+
export declare const IMAGE_SESSION_EXPIRED_MESSAGE = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
193
|
+
/**
|
|
194
|
+
* 各稳定错误码的用户可见文案。
|
|
195
|
+
*
|
|
196
|
+
* **唯一的事实源**:`generate` 的失败收敛与 `/hiwork-image` 的消毒(`image-rpc.ts` 从这里
|
|
197
|
+
* 再导出 {@link displayImageGatewayMessage})都读它。文案与卡片侧
|
|
198
|
+
* `src/client/image-cards.tsx` 的 `IMAGE_ERROR_TEXT` **逐字对齐**——卡片按 code 分文案,
|
|
199
|
+
* 这里只是「网关 message 不可用时的兜底」,两处写着同一句话是有意的:
|
|
200
|
+
* 卡片不依赖 Host 文案,Host 也不依赖卡片(谁先加载都成立)。
|
|
201
|
+
*/
|
|
202
|
+
export declare const IMAGE_MESSAGE_FALLBACK: Record<string, string>;
|
|
203
|
+
/**
|
|
204
|
+
* 网关 message 消毒(与 `voice-rpc.ts` 的 `displayVoiceGatewayMessage` 同实现):
|
|
205
|
+
* 含 baseURL / HTTP 状态码 / URL 的原始消息一律换成稳定文案,其余(网关自己写好的
|
|
206
|
+
* 中文业务文案)原样保留。**code 不受影响**,始终原样透传。
|
|
207
|
+
*
|
|
208
|
+
* 放在 `imagegen.ts` 是因为**两条消费路径都要它**(工具的失败收敛 + RPC 的信封),
|
|
209
|
+
* 而 `image-rpc.ts` 依赖 `imagegen.ts`(反向 import 会成环)。
|
|
210
|
+
*/
|
|
211
|
+
export declare function displayImageGatewayMessage(code: string, message: unknown): string;
|
|
212
|
+
/**
|
|
213
|
+
* 生图并写盘(设计 §6.2)。
|
|
214
|
+
*
|
|
215
|
+
* 顺序:① prompt 校验 → ② 会话 → ③ 网关 → ④ 尺寸缺省时取 `image-config` 的默认值
|
|
216
|
+
* → ⑤ 调网关(带 timeoutMs 的那一条,见 `ai-router-client.ts`)→ ⑥ 解码写盘 → ⑦ 组装结果。
|
|
217
|
+
*
|
|
218
|
+
* 写盘失败**不回滚**:回 `code: 'write-failed'` + 上游信息(prompt/model/size/generationMs),
|
|
219
|
+
* `files` 置空。越界路径回 `code: 'path-outside-workspace'`。
|
|
220
|
+
*/
|
|
221
|
+
export declare function generateImageFile(deps: ImagegenDeps, request: ImageGenerateRequest): Promise<GenerateOutcome>;
|
|
222
|
+
/**
|
|
223
|
+
* 画廊页 `save`(设计 §6.5):取本体 → 按生图同一套规则落盘 → 回绝对路径。
|
|
224
|
+
*
|
|
225
|
+
* 落点是**默认工作区** `~/HiWork/Default`(画廊页是中央页,没有会话上下文);给了
|
|
226
|
+
* `workspaceId` 就用注册表里那个工作区的路径,**取不到即拒绝**(绝不落到别处)。
|
|
227
|
+
*
|
|
228
|
+
* 文件名沿用 §6.2 的规则,slug 来自该记录的 `prompt`。记录本体在网关,而冻结入参只有
|
|
229
|
+
* `{ id, workspaceId? }`(页面按契约只传 id),所以这里**尽力**回查一次列表拿 prompt:
|
|
230
|
+
* 查不到、或第一页里没有这条记录,就用 `image` 兜底——**不为一个文件名把保存变成硬依赖**。
|
|
231
|
+
*
|
|
232
|
+
* 失败分两类:网关/会话错误**抛** `GatewayError`(由 RPC 层映射成稳定 code);
|
|
233
|
+
* 工作区解析 / 写盘这类业务失败回 `{ok:false, code, reason}`。
|
|
234
|
+
*/
|
|
235
|
+
export declare function saveImageToWorkspace(deps: ImagegenDeps, request: {
|
|
236
|
+
id: number;
|
|
237
|
+
workspaceId?: string | undefined;
|
|
238
|
+
prompt?: string | undefined;
|
|
239
|
+
}): Promise<SaveImageOutcome>;
|
|
240
|
+
/** 画廊页 `list` 的一行(冻结契约 `ImageRow`,设计 §6.5)。 */
|
|
241
|
+
export type ImageRow = GatewayImageRow;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* hiwork-core:企业 ai-router 网关登录 + 模型自动加载插件(Host 半边)。
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* rc.2 起 dsh-settings 的 seam 重写:不再有 installSection / register —— 配置面改为
|
|
5
|
+
* 「插件 Config schema 的 `.volatile()` 字段经 configEditor 投影成表单」,settings 命名空间
|
|
6
|
+
* 就是本插件在 profile 里的 entry id(`ai-router`,见 cordis.patch.yml 的 insert 行,两处必须
|
|
7
|
+
* 逐字一致)。卡片写入的请求字段(pendingLogin / refreshRequest / logoutRequest)经
|
|
8
|
+
* `settings/document-updated` 事件触发编排,编排结果回写本 entry 的 volatile 字段。
|
|
6
9
|
*/
|
|
7
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { Context, Volatile } from '@deepseek-ai/cordis';
|
|
8
11
|
import z from '@deepseek-ai/schemastery';
|
|
9
|
-
import
|
|
12
|
+
import type SettingsForms from '@deepseek-ai/dsh-settings';
|
|
10
13
|
import { fetchClientPolicy, type ClientPolicy, type LoginMode } from './client-policy.ts';
|
|
11
14
|
import { type CredentialsPort, type DefaultModelPort, type AiRouterStatePort } from './orchestration.ts';
|
|
12
15
|
export declare const name = "hiwork-core";
|
|
@@ -14,9 +17,30 @@ export declare const name = "hiwork-core";
|
|
|
14
17
|
* 必需服务:`connection` 承载设置页「用量与成本」的 loopback RPC 频道
|
|
15
18
|
* (`/hiwork-usage`,见 `src/usage-rpc.ts`);`web` 承载联网搜索 provider 的注册
|
|
16
19
|
* (`ctx.web.registerSearchProvider`,见 `src/web-search.ts`;dsh-base 基座先于本
|
|
17
|
-
* 插件挂载 dsh-web
|
|
20
|
+
* 插件挂载 dsh-web);`agents` 是根 Agent 注册表——`image_generate` 工具要挂在根 Agent
|
|
21
|
+
* 上(`ctx.agents.roots()` / `agent/created`,见 `src/tools.ts`;与 knowledge 的
|
|
22
|
+
* `inject` 同源);其余三项是既有登录 / 供给依赖。
|
|
18
23
|
*/
|
|
19
24
|
export declare const inject: string[];
|
|
25
|
+
/** 侧栏菜单偏好的解析值(client 侧 `MenuPreferences` 的同一份形状)。 */
|
|
26
|
+
export interface MenuConfigValue {
|
|
27
|
+
/** 展示顺序(feature id)。 */
|
|
28
|
+
order: string[];
|
|
29
|
+
/** 明确显示在侧栏的 id。 */
|
|
30
|
+
sidebar: string[];
|
|
31
|
+
/** 明确收进「更多」的 id。 */
|
|
32
|
+
more: string[];
|
|
33
|
+
/** 明确隐藏的 id(侧栏与「更多」都不出现)。 */
|
|
34
|
+
hidden: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 菜单偏好 schema:四个 id 数组,缺省全空(= 用户从未自定义 → 由 client 按默认规则分组)。
|
|
38
|
+
*
|
|
39
|
+
* 这里**不做枚举校验**:合法的 id 集合由「当前装配了哪些 feature」决定,是运行期事实,写死在 host
|
|
40
|
+
* 的 schema 里会立刻过期(插件装了/卸了都会变)。归一化与清理在 client 侧完成(`normalizeMenuPreferences`),
|
|
41
|
+
* 未知 id 在渲染时被忽略而不是拒绝写入——否则用户装过一次又卸掉的插件会永久毒化这份偏好。
|
|
42
|
+
*/
|
|
43
|
+
export declare const MenuConfig: z<MenuConfigValue>;
|
|
20
44
|
/** 插件配置 + 运行时状态字段(全部可选;写操作按此 schema 校验)。 */
|
|
21
45
|
export interface Config {
|
|
22
46
|
baseURL?: string;
|
|
@@ -79,8 +103,36 @@ export interface Config {
|
|
|
79
103
|
max_concurrency?: number;
|
|
80
104
|
key_masked?: string;
|
|
81
105
|
}>;
|
|
106
|
+
/**
|
|
107
|
+
* 侧栏菜单偏好(rc.2 起并入本 entry;此前是独立的 `hiwork-menu` settings 命名空间,
|
|
108
|
+
* 而 rc.2 的 settings 只服务「有 profile entry 的 Config」,纯客户端偏好没有自己的 entry
|
|
109
|
+
* 可挂,遂改挂本 entry 的 volatile 字段。旧值由 `importLegacyMenuSection` 一次性搬迁)。
|
|
110
|
+
*/
|
|
111
|
+
menu?: MenuConfigValue;
|
|
82
112
|
}
|
|
83
113
|
export declare const Config: z<Config>;
|
|
114
|
+
/**
|
|
115
|
+
* apply 收到的运行时 config:volatile 字段是稳定引用(schemastery 解析期包装,值为
|
|
116
|
+
* undefined 时引用依然存在),随 profile 变更被框架原地更新——持有引用、每次现取,
|
|
117
|
+
* 绝不缓存 `.get()` 的结果。baseURL 仍是普通组合层配置。
|
|
118
|
+
*/
|
|
119
|
+
export type RuntimeConfig = {
|
|
120
|
+
baseURL?: string;
|
|
121
|
+
} & {
|
|
122
|
+
[K in Exclude<keyof Config, 'baseURL'>]?: Volatile<Config[K]>;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* settings.mutate 的路径操作(rc.2 的 dsh-settings 不再导出 `SettingsPathOp` 类型,
|
|
126
|
+
* 这里按其结构本地声明——形状逐字对齐上游 `packages/settings/settings/src/index.ts`)。
|
|
127
|
+
*/
|
|
128
|
+
export type SettingsPathOp = {
|
|
129
|
+
op: 'set';
|
|
130
|
+
path: readonly string[];
|
|
131
|
+
value: unknown;
|
|
132
|
+
} | {
|
|
133
|
+
op: 'unset';
|
|
134
|
+
path: readonly string[];
|
|
135
|
+
};
|
|
84
136
|
/** 校正默认模型要用到的 llm 注册表部分(便于单测注入假实现)。 */
|
|
85
137
|
export interface LlmRouteRegistry {
|
|
86
138
|
listProviders(): readonly {
|
|
@@ -99,7 +151,7 @@ export interface LlmRouteRegistry {
|
|
|
99
151
|
* @returns 供编排调用的默认模型端口。
|
|
100
152
|
*/
|
|
101
153
|
export declare function createDefaultModelPort(opts: {
|
|
102
|
-
settings:
|
|
154
|
+
settings: SettingsForms;
|
|
103
155
|
llm: LlmRouteRegistry | undefined;
|
|
104
156
|
log?: (message: string) => void;
|
|
105
157
|
}): DefaultModelPort;
|
|
@@ -174,4 +226,14 @@ export declare function projectLoginMode(opts: {
|
|
|
174
226
|
fetchPolicy?: (input: Parameters<typeof fetchClientPolicy>[0]) => Promise<ClientPolicy | undefined>;
|
|
175
227
|
log?: (message: string) => void;
|
|
176
228
|
}): Promise<LoginMode>;
|
|
177
|
-
|
|
229
|
+
/**
|
|
230
|
+
* 把运行时 config 的 volatile 引用解成普通值快照(浅层遍历;只处理本插件 Config 的
|
|
231
|
+
* 数据形状)。识别 Volatile 引用用鸭子类型:cosmokit 的 createVolatile 返回「只有一
|
|
232
|
+
* 个字符串键 get 的冻结对象」,本插件 Config 的数据形状里没有任何带 get 方法的成员,
|
|
233
|
+
* 不会误伤。rc.2 起 cordis 只 re-export Volatile 类型、不导出运行时 isVolatile,
|
|
234
|
+
* 为避免依赖树外的 cosmokit 直依赖,这里本地判定。
|
|
235
|
+
*
|
|
236
|
+
* 导出仅供 tests/plain-value.spec.ts 锁定判定契约(见该文件头),不是对外的插件 API。
|
|
237
|
+
*/
|
|
238
|
+
export declare function plainValue(value: unknown): unknown;
|
|
239
|
+
export declare function apply(ctx: Context, config: RuntimeConfig): void;
|
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
export declare const HIWORK_KNOWLEDGE_SERVICE = "hiworkKnowledge";
|
|
3
3
|
/** 网关侧端点(跨仓冻结;与 `/api/me/memory-config` 同一条会话鉴权)。 */
|
|
4
4
|
export declare const ME_KNOWLEDGE_CONFIG_PATH = "/api/me/knowledge-config";
|
|
5
|
-
/** 成功结果的缓存 TTL:5
|
|
5
|
+
/** 成功结果的缓存 TTL:5 分钟(与网关签发的令牌寿命同量级)。 */
|
|
6
6
|
export declare const KNOWLEDGE_CONFIG_TTL_MS: number;
|
|
7
|
+
/**
|
|
8
|
+
* **令牌**缓存的提前量:令牌寿命与缓存 TTL 都是 5 分钟时,缓存到期时刻恒晚于令牌 `exp`
|
|
9
|
+
* (响应到达 = 签发 + 一次往返),那段时间里命中的令牌**必然已过期** → 上游 401 → 插件
|
|
10
|
+
* 强制刷新一次再重试。留 30 秒余量把这条无谓的 401 往返消掉。
|
|
11
|
+
*
|
|
12
|
+
* (配置缓存的 TTL 不需要余量:`base_url`/`api_key` 没有自带寿命。)
|
|
13
|
+
*/
|
|
14
|
+
export declare const KNOWLEDGE_TOKEN_CACHE_MARGIN_MS = 30000;
|
|
15
|
+
/** 令牌缓存的 TTL:比令牌寿命短一点(见上面的理由)。 */
|
|
16
|
+
export declare const KNOWLEDGE_TOKEN_CACHE_TTL_MS: number;
|
|
7
17
|
/** 出网超时(与 `AiRouterClient` 的缺省一致)。 */
|
|
8
18
|
export declare const KNOWLEDGE_CONFIG_TIMEOUT_MS = 10000;
|
|
9
19
|
/**
|
|
@@ -27,15 +37,51 @@ export interface HiworkKnowledgeConfigView {
|
|
|
27
37
|
agentId?: string;
|
|
28
38
|
chatModelId?: string;
|
|
29
39
|
tenantId?: string;
|
|
40
|
+
/**
|
|
41
|
+
* 网关下发的**展示**范围(设计 §3.1 的第六个字段 `knowledge_bases`):去空 ID、按 ID 去重、
|
|
42
|
+
* 名称 trim。缺省即空数组——**展示字段不是权限依据**,权限依据是 `token()` 面的
|
|
43
|
+
* `knowledgeBaseIds`(设计 §3.1「展示名称不是权限依据」)。
|
|
44
|
+
*
|
|
45
|
+
* 为什么必须带出来:网关(hiwork-admin)确实在下发它(`me_knowledge_handler.go` 的
|
|
46
|
+
* `knowledge_bases`,同样做了去空/去重/trim),而这里过去只投影五个字段、把它丢了 ——
|
|
47
|
+
* 结果是插件侧读它的那段兼容代码**永远是死代码**,且宿主没有 token 面时(老 core 形状)
|
|
48
|
+
* 授权范围只能恒为空。展示与兜底都需要它,所以按视图口径翻成 camelCase 带出来。
|
|
49
|
+
*/
|
|
50
|
+
knowledgeBases?: readonly {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly name: string;
|
|
53
|
+
}[];
|
|
30
54
|
reason?: HiworkKnowledgeFailureReason;
|
|
31
55
|
message?: string;
|
|
32
56
|
}
|
|
33
57
|
/** 本服务对宿主的可调用面(只读、永不抛异常)。 */
|
|
58
|
+
export interface HiworkKnowledgeTokenView {
|
|
59
|
+
ok: boolean;
|
|
60
|
+
token?: string;
|
|
61
|
+
audience?: string;
|
|
62
|
+
knowledgeBaseIds?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* 授权范围的展示信息(`token()` 面:**只保留在 ID 范围内的项**,见下方测试)。
|
|
65
|
+
* 写成只读,与 `HiworkKnowledgeConfigView.knowledgeBases` 同形——两个视图在
|
|
66
|
+
* `failure()` 返回值上是同一份数据的两种键名,形状不一致会让 token 面的失败返回
|
|
67
|
+
* 连类型都过不去。
|
|
68
|
+
*/
|
|
69
|
+
knowledgeBases?: readonly {
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly name: string;
|
|
72
|
+
}[];
|
|
73
|
+
reason?: HiworkKnowledgeFailureReason;
|
|
74
|
+
message?: string;
|
|
75
|
+
}
|
|
34
76
|
export interface HiworkKnowledgeService {
|
|
35
|
-
/** 取当前登录员工的知识库配置;force=true
|
|
77
|
+
/** 取当前登录员工的知识库配置;force=true 绕过缓存。 */
|
|
36
78
|
config(opts?: {
|
|
37
79
|
force?: boolean;
|
|
38
80
|
}): Promise<HiworkKnowledgeConfigView>;
|
|
81
|
+
/** 取当前会话的短期知识库 token;仅 Host-to-Host 服务面可见。 */
|
|
82
|
+
token(opts?: {
|
|
83
|
+
force?: boolean;
|
|
84
|
+
}): Promise<HiworkKnowledgeTokenView>;
|
|
39
85
|
}
|
|
40
86
|
/**
|
|
41
87
|
* 网关侧原始响应(**snake_case**,跨仓冻结契约;本文件是唯一读它的地方)。
|
|
@@ -73,6 +119,8 @@ export type KnowledgeConfigOutcome = {
|
|
|
73
119
|
export interface KnowledgeConfigGatewayPort {
|
|
74
120
|
/** 调 `GET /api/me/knowledge-config`,返回 snake_case 原始响应或一个稳定失败原因。 */
|
|
75
121
|
getMyKnowledgeConfig(sessionCookie: string): Promise<KnowledgeConfigOutcome>;
|
|
122
|
+
/** 为当前会话签发短期知识库令牌;老 core 可省略。 */
|
|
123
|
+
getMyKnowledgeToken?: (sessionCookie: string) => Promise<unknown>;
|
|
76
124
|
}
|
|
77
125
|
/** Host 侧取数依赖:会话解析 + 当前 baseURL 下的网关出网端口。 */
|
|
78
126
|
export interface KnowledgeConfigDeps {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生图工具名的**单一事实源**。
|
|
3
|
+
*
|
|
4
|
+
* Web 半边(`src/client/ImageCards.tsx` 的 `tool.call.toolview` keyed 座位)与 Host 注册
|
|
5
|
+
* 都从这里读同一个常量:keyed 座位**按线上工具名认领**,名字拼错的失败方式是**静默**的
|
|
6
|
+
* ——卡片永不渲染、通用工具行照常出现,没有任何测试会因为它变红(knowledge 已踩过)。
|
|
7
|
+
* 所以这里既导出元组,也导出它的元素类型(`Record<ImageToolName, …>` 这种写法能在
|
|
8
|
+
* 编译期挡住「新增工具忘了补卡片」)。
|
|
9
|
+
*/
|
|
10
|
+
export declare const IMAGE_TOOL_NAMES: readonly ["image_generate"];
|
|
11
|
+
export type ImageToolName = (typeof IMAGE_TOOL_NAMES)[number];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type GenerateOutcome } from './imagegen.ts';
|
|
2
|
+
import { IMAGE_TOOL_NAMES } from './tool-names.ts';
|
|
3
|
+
export { IMAGE_TOOL_NAMES };
|
|
4
|
+
/**
|
|
5
|
+
* 生图工具的单次预算(**工具层护栏**,不是业务判据)。
|
|
6
|
+
*
|
|
7
|
+
* 它必须**大于**客户端真正会等的时间,否则工具会先掐断客户端请求,把一次本可成功的
|
|
8
|
+
* 生图变成工具超时。客户端等待上限 = 网关预算 + 15s(见 `imageGenerateTimeoutMs`),
|
|
9
|
+
* 而网关预算的合法上界是 600s、读不到配置时走 900s 兜底 ⇒ 工具预算取 960s(16 分钟),
|
|
10
|
+
* 比两者都高。真正的超时语义由网关的 504 / `IMAGE_TIMEOUT` 表达。
|
|
11
|
+
*
|
|
12
|
+
* 实测背景:原值 330s 是按「网关默认 180s」估的,而网关上界 600s、实测单张已到 415s
|
|
13
|
+
* —— 按那条估算会在合法的慢生图上误报。
|
|
14
|
+
*/
|
|
15
|
+
export declare const IMAGE_TOOL_TIMEOUT_MS = 960000;
|
|
16
|
+
/**
|
|
17
|
+
* 注册工具所需的最小 Agent 结构端口(不 import DSH agent 运行时)。
|
|
18
|
+
* 与 knowledge 的 `KnowledgeAgent` 同形:`id` 用于身份判定,`ctx.tools.register` 是落点。
|
|
19
|
+
*/
|
|
20
|
+
export type ImageAgent = {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly ctx: {
|
|
23
|
+
readonly tools: {
|
|
24
|
+
register(definition: unknown): () => void;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* 工具执行期读到的会话/取消面(结构端口,便于单测直接伪造:
|
|
30
|
+
* `{ agent: { session: { header: { cwd } } }, signal }`)。
|
|
31
|
+
*/
|
|
32
|
+
export interface ImageToolExec {
|
|
33
|
+
readonly agent?: {
|
|
34
|
+
readonly session?: {
|
|
35
|
+
readonly header?: {
|
|
36
|
+
readonly cwd?: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
} | undefined;
|
|
40
|
+
readonly signal: AbortSignal;
|
|
41
|
+
}
|
|
42
|
+
/** 工具对 Host 生图服务的全部要求(生产由 `src/imagegen.ts` 的 `generateImageFile` 满足)。 */
|
|
43
|
+
export interface ImageToolService {
|
|
44
|
+
generate(request: {
|
|
45
|
+
prompt: string;
|
|
46
|
+
size?: string | undefined;
|
|
47
|
+
workspaceRoot?: string | undefined;
|
|
48
|
+
}): Promise<GenerateOutcome>;
|
|
49
|
+
}
|
|
50
|
+
/** 工具的模型可见描述(设计 §6.3 逐字冻结)。 */
|
|
51
|
+
export declare const IMAGE_GENERATE_DESCRIPTION: string;
|
|
52
|
+
/** 卡片数据源(JSON 形态):与 `GenerateOutcome` 同形,外加给模型读的 `text`。 */
|
|
53
|
+
export interface ImageToolValue {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
/** 给模型读的那段话(见 {@link formatImageGenerateText})。 */
|
|
56
|
+
text: string;
|
|
57
|
+
files: Array<{
|
|
58
|
+
path: string;
|
|
59
|
+
mimeType: string;
|
|
60
|
+
byteSize: number;
|
|
61
|
+
revisedPrompt?: string;
|
|
62
|
+
}>;
|
|
63
|
+
prompt?: string;
|
|
64
|
+
model?: string;
|
|
65
|
+
size?: string;
|
|
66
|
+
generationMs?: number;
|
|
67
|
+
code?: string;
|
|
68
|
+
message?: string;
|
|
69
|
+
}
|
|
70
|
+
/** 模型可见的那段话(成功:路径 + 尺寸/模型/耗时 + 落盘说明;失败:原因 + 可执行提示)。 */
|
|
71
|
+
export declare function formatImageGenerateText(outcome: GenerateOutcome): string;
|
|
72
|
+
/** `GenerateOutcome` → 工具结果(卡片与模型共用同一份 JSON)。 */
|
|
73
|
+
export declare function toImageToolValue(outcome: GenerateOutcome): ImageToolValue;
|
|
74
|
+
/**
|
|
75
|
+
* 给一个根 Agent 注册生图工具。
|
|
76
|
+
* @param service - Host 侧生图服务(生产是 `src/imagegen.ts` 的 `generateImageFile`)。
|
|
77
|
+
* @param agent - 目标根 Agent。
|
|
78
|
+
* @returns 注销器(注销全部已注册工具)。
|
|
79
|
+
*/
|
|
80
|
+
export declare function registerImageTools(service: ImageToolService, agent: ImageAgent): () => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发往网关的 `User-Agent`:`hiwork/<桌面端版本>`。
|
|
3
|
+
*
|
|
4
|
+
* 「客户端」在网关侧就是这一条头(请求日志的客户端列、排障时的流量归属),所以它必须报
|
|
5
|
+
* **产品 + 版本**,而不是插件内部包名。版本号取桌面端版本:这是运维真正关心的维度
|
|
6
|
+
* (哪一版客户端),插件版本对同一台机器上的所有请求都相同,报它区分不出客户端版本。
|
|
7
|
+
*
|
|
8
|
+
* 版本来源的优先级(高 → 低):
|
|
9
|
+
* 1. 环境变量 `HIWORK_APP_VERSION`——桌面端拉起 DSH 时注入,**宿主进程一启动就有**;
|
|
10
|
+
* 2. 桌面端经 IPC 推来的设备信息里的 `appVersion`(见 `src/browser-login.ts`)——
|
|
11
|
+
* 推送发生在宿主就绪之后,早于它的请求拿不到,所以只作兜底;
|
|
12
|
+
* 3. 两者都没有(纯 `dsh web` / CLI 场景)——回报插件自身的 `hiwork-core/<插件版本>`,
|
|
13
|
+
* 不拿插件版本冒充桌面端版本。
|
|
14
|
+
*
|
|
15
|
+
* 值一律经 {@link headerSafeVersion} 过滤后再进 header:环境变量与 IPC 都可能带来空串、
|
|
16
|
+
* 前后空白或换行,而 fetch 遇到非法 header 值会直接抛错——那会让每一次网关请求都失败。
|
|
17
|
+
*/
|
|
18
|
+
/** 桌面端注入自身版本用的环境变量名;两侧契约,改这里必须同步 `hiwork-desktop/src/main.ts`。 */
|
|
19
|
+
export declare const DESKTOP_APP_VERSION_ENV = "HIWORK_APP_VERSION";
|
|
20
|
+
/**
|
|
21
|
+
* 桌面端版本号。
|
|
22
|
+
* @param env - 环境变量表;缺省 `process.env`(测试可注入)。
|
|
23
|
+
* @returns 版本号;环境变量与 IPC 都没有时 undefined。
|
|
24
|
+
*/
|
|
25
|
+
export declare function desktopAppVersion(env?: NodeJS.ProcessEnv): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* 网关请求的 User-Agent。
|
|
28
|
+
* @param version - 桌面端版本;缺省按 {@link desktopAppVersion} 解析(测试可注入)。
|
|
29
|
+
* @returns `hiwork/<桌面端版本>`;拿不到桌面端版本时 `hiwork-core/<插件版本>`。
|
|
30
|
+
*/
|
|
31
|
+
export declare function gatewayUserAgent(version?: string | undefined): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发往网关的 `User-Agent`:`hiwork/<桌面端版本>`。
|
|
3
|
+
*
|
|
4
|
+
* 「客户端」在网关侧就是这一条头(请求日志的客户端列、排障时的流量归属),所以它必须报
|
|
5
|
+
* **产品 + 版本**,而不是插件内部包名。版本号取桌面端版本:这是运维真正关心的维度
|
|
6
|
+
* (哪一版客户端),插件版本对同一台机器上的所有请求都相同,报它区分不出客户端版本。
|
|
7
|
+
*
|
|
8
|
+
* 版本来源的优先级(高 → 低):
|
|
9
|
+
* 1. 环境变量 `HIWORK_APP_VERSION`——桌面端拉起 DSH 时注入,**宿主进程一启动就有**;
|
|
10
|
+
* 2. 桌面端经 IPC 推来的设备信息里的 `appVersion`(见 `src/browser-login.ts`)——
|
|
11
|
+
* 推送发生在宿主就绪之后,早于它的请求拿不到,所以只作兜底;
|
|
12
|
+
* 3. 两者都没有(纯 `dsh web` / CLI 场景)——回报插件自身的 `hiwork-core/<插件版本>`,
|
|
13
|
+
* 不拿插件版本冒充桌面端版本。
|
|
14
|
+
*
|
|
15
|
+
* 值一律经 {@link headerSafeVersion} 过滤后再进 header:环境变量与 IPC 都可能带来空串、
|
|
16
|
+
* 前后空白或换行,而 fetch 遇到非法 header 值会直接抛错——那会让每一次网关请求都失败。
|
|
17
|
+
*/
|
|
18
|
+
import { PKG_VERSION } from "./version.js";
|
|
19
|
+
import { pushedAppVersion } from "./browser-login.js";
|
|
20
|
+
/** 桌面端注入自身版本用的环境变量名;两侧契约,改这里必须同步 `hiwork-desktop/src/main.ts`。 */
|
|
21
|
+
export const DESKTOP_APP_VERSION_ENV = 'HIWORK_APP_VERSION';
|
|
22
|
+
/**
|
|
23
|
+
* 版本号允许的形态:以字母或数字开头,其后只允许 semver 常见字符(`0-9 A-Z a-z . + - _`)。
|
|
24
|
+
*
|
|
25
|
+
* **必须是白名单,不能只过滤控制字符**:换成「删掉 `\n` 就放行」的写法时,
|
|
26
|
+
* `"1.0.7\nX-Injected: evil"` 会被拼成 `hiwork/1.0.7X-Injected: evil` —— 换行没了,
|
|
27
|
+
* 但被注入的文本原样进了 header,运维在网关看到的是一串看不懂的客户端名。
|
|
28
|
+
* 版本号本来就不该含空格/冒号,所以「不像版本号就当作没拿到」比「尽量修好它」正确。
|
|
29
|
+
*/
|
|
30
|
+
const versionPattern = /^[0-9A-Za-z][0-9A-Za-z.+_-]*$/;
|
|
31
|
+
/** 版本号安全化:只接受白名单形态;空串、含空白/控制字符/其他标点一律视为「没拿到版本」。 */
|
|
32
|
+
function headerSafeVersion(value) {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
return versionPattern.test(trimmed) ? trimmed : undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 桌面端版本号。
|
|
40
|
+
* @param env - 环境变量表;缺省 `process.env`(测试可注入)。
|
|
41
|
+
* @returns 版本号;环境变量与 IPC 都没有时 undefined。
|
|
42
|
+
*/
|
|
43
|
+
export function desktopAppVersion(env = process.env) {
|
|
44
|
+
return headerSafeVersion(env[DESKTOP_APP_VERSION_ENV]) ?? headerSafeVersion(pushedAppVersion());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 网关请求的 User-Agent。
|
|
48
|
+
* @param version - 桌面端版本;缺省按 {@link desktopAppVersion} 解析(测试可注入)。
|
|
49
|
+
* @returns `hiwork/<桌面端版本>`;拿不到桌面端版本时 `hiwork-core/<插件版本>`。
|
|
50
|
+
*/
|
|
51
|
+
export function gatewayUserAgent(version = desktopAppVersion()) {
|
|
52
|
+
const safe = headerSafeVersion(version);
|
|
53
|
+
return safe === undefined ? `hiwork-core/${PKG_VERSION}` : `hiwork/${safe}`;
|
|
54
|
+
}
|
package/lib/web-search.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* 端口,接线方(index.ts)传真实 ctx 侧依赖;单测直接伪造。
|
|
35
35
|
*/
|
|
36
36
|
import { WebError } from '@deepseek-ai/dsh-web';
|
|
37
|
-
import {
|
|
37
|
+
import { gatewayUserAgent } from "./user-agent.js";
|
|
38
38
|
/** 本 provider 注册进 `ctx.web` 用的稳定 id(cordis.patch.yml 的 searchProvider pin 与它逐字一致)。 */
|
|
39
39
|
export const HIWORK_SEARCH_PROVIDER_ID = 'hiwork-managed-search';
|
|
40
40
|
/** 网关侧端点(跨仓冻结;会话鉴权与 `/api/me/memory-config` 同一条)。 */
|
|
@@ -208,7 +208,7 @@ export function createWebSearchGateway(baseURL, opts = {}) {
|
|
|
208
208
|
res = await doFetch(`${root}${ME_WEB_SEARCH_PATH}`, {
|
|
209
209
|
method: 'POST',
|
|
210
210
|
headers: {
|
|
211
|
-
'user-agent':
|
|
211
|
+
'user-agent': gatewayUserAgent(),
|
|
212
212
|
cookie: sessionCookie,
|
|
213
213
|
'content-type': 'application/json',
|
|
214
214
|
},
|