hiwork-core 0.0.30 → 0.0.37
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/browser-login.js +42 -16
- package/lib/client-policy.js +2 -2
- package/lib/client.js +2496 -500
- package/lib/image-rpc.js +306 -0
- package/lib/imagegen.js +424 -0
- package/lib/index.js +18639 -7060
- 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/browser-login.d.ts +10 -1
- package/lib/types/client/about-section.d.ts +27 -7
- package/lib/types/client/controller.d.ts +17 -9
- package/lib/types/client/daily-data.d.ts +31 -3
- 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/image-rpc.d.ts +142 -0
- package/lib/types/imagegen.d.ts +241 -0
- package/lib/types/index.d.ts +89 -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 +34 -24
package/lib/image-rpc.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/hiwork-image` loopback RPC 的 Host 实现(浏览器半边 ⇄ 生图的唯一通道)。
|
|
3
|
+
*
|
|
4
|
+
* 设计要点(与 `src/voice-rpc.ts` / `src/daily-rpc.ts` 完全同构):
|
|
5
|
+
* - 只注册**一个** channel,五个 endpoint 的白名单在 {@link IMAGE_RPC_ENDPOINTS},未知
|
|
6
|
+
* endpoint 直接 `bad-request`;一个能力一个频道,`list`/`fetch`/`save` 与
|
|
7
|
+
* `status`/`generate` 共用它(与 managed-mcp 的 snapshot/refresh/assertion 同款);
|
|
8
|
+
* - payload 严格校验:每个 endpoint 只放行它声明的字段与类型,任何其它字段一律
|
|
9
|
+
* `bad-request`(拼错字段的失败方式必须是响的,不是静默用默认值);
|
|
10
|
+
* - **网关会话 cookie 绝不离开 Host**:调用方只拿到端点结果,cookie 只在
|
|
11
|
+
* `resolveSession()` 内部出现;
|
|
12
|
+
* - 网关 `GatewayError` 的稳定 code 原样透传,message 先经
|
|
13
|
+
* {@link displayImageGatewayMessage} 消毒——baseURL / HTTP 状态码这类实现细节不进 UI;
|
|
14
|
+
* - 其余异常收敛为通用失败(code `internal`),Host 只记日志、不把堆栈或原始消息交给客户端。
|
|
15
|
+
*
|
|
16
|
+
* 两处**与 voice 不同**的语义,都写在这里免得后来的人「对齐」掉:
|
|
17
|
+
* 1. `status` 的失败**不是错误信封**——网关故障时回落 {@link IMAGE_FALLBACK_CONFIG}
|
|
18
|
+
* (`enabled:false`)。理由与语音逐字相同:这条端点回答的是「该不该显示生图能力」,
|
|
19
|
+
* 网关挂了就报红字,只会让一个本来就没有的功能显得像坏了;
|
|
20
|
+
* 2. `fetch` 有**体积闸**:超过 {@link MAX_IMAGE_BYTES} 直接拒绝(`IMAGE_TOO_LARGE`),
|
|
21
|
+
* 不把巨图塞进 loopback RPC 信封(语音那条的大对象在**上行**,本条的在下行)。
|
|
22
|
+
*
|
|
23
|
+
* 本模块不 import cordis 运行时,只依赖结构化的 {@link ImageRpcContext} 端口,便于单测伪造。
|
|
24
|
+
*/
|
|
25
|
+
import { GatewayError } from "./ai-router-client.js";
|
|
26
|
+
import { IMAGE_DEFAULT_SIZE, IMAGE_GENERIC_FAILURE_MESSAGE, IMAGE_SESSION_EXPIRED_MESSAGE, IMAGE_SIZES, MAX_IMAGE_BYTES, displayImageGatewayMessage, generateImageFile, readImageConfig, saveImageToWorkspace, } from "./imagegen.js";
|
|
27
|
+
/** RPC 频道;必须与 client 半边 `src/client/images-data.ts` 的 `IMAGE_CHANNEL` 一致(由测试锁定)。 */
|
|
28
|
+
export const IMAGE_RPC_CHANNEL = '/hiwork-image';
|
|
29
|
+
/** 端点白名单:生图(`status`/`generate`)与「我的图片」画廊页(`list`/`fetch`/`save`)。 */
|
|
30
|
+
export const IMAGE_RPC_ENDPOINTS = ['status', 'generate', 'list', 'fetch', 'save'];
|
|
31
|
+
/** 画廊页 `list` 的分页缺省与上限(Host 侧夹紧:网关的 page_size 是它自己的缺省)。 */
|
|
32
|
+
export const IMAGE_LIST_DEFAULT_PAGE = 1;
|
|
33
|
+
export const IMAGE_LIST_DEFAULT_PAGE_SIZE = 20;
|
|
34
|
+
export const IMAGE_LIST_MAX_PAGE_SIZE = 100;
|
|
35
|
+
/** 请求侧错误(只有 `bad-request`:未知端点 / 非法 payload)。 */
|
|
36
|
+
export class ImageRequestError extends Error {
|
|
37
|
+
code;
|
|
38
|
+
constructor(code, message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.name = 'ImageRequestError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** 各稳定错误码的用户可见文案(与卡片侧 `IMAGE_ERROR_TEXT` 逐字对齐)。 */
|
|
45
|
+
export { IMAGE_GENERIC_FAILURE_MESSAGE, IMAGE_MESSAGE_FALLBACK, IMAGE_SESSION_EXPIRED_MESSAGE, displayImageGatewayMessage, } from "./imagegen.js";
|
|
46
|
+
/**
|
|
47
|
+
* `status` 端点读不到配置时的回落值(**成功信封**,见模块头注释第 1 条):
|
|
48
|
+
* 能力消失,而不是弹一条用户看不懂的错误。
|
|
49
|
+
*/
|
|
50
|
+
export const IMAGE_FALLBACK_CONFIG = {
|
|
51
|
+
enabled: false,
|
|
52
|
+
model: '',
|
|
53
|
+
defaultSize: IMAGE_DEFAULT_SIZE,
|
|
54
|
+
sizes: [...IMAGE_SIZES],
|
|
55
|
+
quality: '',
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* 网关 message 消毒 / 错误码兜底文案都从 `imagegen.ts` 再导出(那里是唯一实现,
|
|
59
|
+
* 因为工具的失败收敛与这里的信封都要用它,而反向 import 会成环)。
|
|
60
|
+
*
|
|
61
|
+
* 为什么还要 `export`:`/hiwork-image` 的消费方(以及 spec §6.7 点名的用例)拿的是
|
|
62
|
+
* **频道模块**的公开面——消毒规则属于这条频道的契约,不该逼调用方去 import 服务层。
|
|
63
|
+
*/
|
|
64
|
+
/** 是否为白名单端点。 */
|
|
65
|
+
export function isImageRpcEndpoint(endpoint) {
|
|
66
|
+
return IMAGE_RPC_ENDPOINTS.includes(endpoint);
|
|
67
|
+
}
|
|
68
|
+
/** 正整数(页码 / 图片 id);非法即 undefined。 */
|
|
69
|
+
function readPositiveInt(value) {
|
|
70
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
71
|
+
}
|
|
72
|
+
/** 非空字符串(去空白后仍非空);非法即 undefined。 */
|
|
73
|
+
function readNonEmptyString(value) {
|
|
74
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* payload 校验:每个 endpoint 只放行它声明的字段,其余字段 / 类型不符一律 `bad-request`。
|
|
78
|
+
*
|
|
79
|
+
* 缺省 / null 一律等价于空对象(页面挂载时 `status` 的调用就是 `{}` 或不带 payload)。
|
|
80
|
+
* **不在这里做业务校验**(prompt 长度、size 枚举由网关与 `imagegen.ts` 负责)——
|
|
81
|
+
* 这里只做协议形状,越早失败越便宜,但重复实现一遍业务规则只会多一处漂移。
|
|
82
|
+
*/
|
|
83
|
+
export function parseImagePayload(endpoint, raw) {
|
|
84
|
+
if (raw === undefined || raw === null) {
|
|
85
|
+
if (endpoint === 'fetch' || endpoint === 'save') {
|
|
86
|
+
throw new ImageRequestError('bad-request', '图片请求缺少图片 id。');
|
|
87
|
+
}
|
|
88
|
+
if (endpoint === 'generate')
|
|
89
|
+
throw new ImageRequestError('bad-request', '生图请求缺少 prompt。');
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
if (typeof raw !== 'object' || Array.isArray(raw)) {
|
|
93
|
+
throw new ImageRequestError('bad-request', '图片请求参数不合法。');
|
|
94
|
+
}
|
|
95
|
+
const record = raw;
|
|
96
|
+
for (const key of Object.keys(record)) {
|
|
97
|
+
if (!allowedKeys(endpoint).includes(key)) {
|
|
98
|
+
throw new ImageRequestError('bad-request', `图片请求包含未知字段 '${key}'。`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (endpoint === 'status')
|
|
102
|
+
return {};
|
|
103
|
+
if (endpoint === 'generate') {
|
|
104
|
+
const prompt = readNonEmptyString(record.prompt);
|
|
105
|
+
if (prompt === undefined) {
|
|
106
|
+
throw new ImageRequestError('bad-request', '生图请求的 prompt 必须是非空字符串。');
|
|
107
|
+
}
|
|
108
|
+
const size = record.size;
|
|
109
|
+
if (size !== undefined && !IMAGE_SIZES.includes(String(size))) {
|
|
110
|
+
throw new ImageRequestError('bad-request', `生图请求的 size '${String(size)}' 不在允许的尺寸里。`);
|
|
111
|
+
}
|
|
112
|
+
return { prompt, ...(size === undefined ? {} : { size: String(size) }) };
|
|
113
|
+
}
|
|
114
|
+
if (endpoint === 'list') {
|
|
115
|
+
const page = record.page;
|
|
116
|
+
if (page !== undefined && readPositiveInt(page) === undefined) {
|
|
117
|
+
throw new ImageRequestError('bad-request', '图片列表的 page 必须是正整数。');
|
|
118
|
+
}
|
|
119
|
+
const pageSize = record.pageSize;
|
|
120
|
+
if (pageSize !== undefined && readPositiveInt(pageSize) === undefined) {
|
|
121
|
+
throw new ImageRequestError('bad-request', '图片列表的 pageSize 必须是正整数。');
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
...(page === undefined ? {} : { page: readPositiveInt(page) }),
|
|
125
|
+
...(pageSize === undefined ? {} : { pageSize: readPositiveInt(pageSize) }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const id = readPositiveInt(record.id);
|
|
129
|
+
if (id === undefined)
|
|
130
|
+
throw new ImageRequestError('bad-request', '图片请求的 id 必须是正整数。');
|
|
131
|
+
if (endpoint === 'fetch')
|
|
132
|
+
return { id };
|
|
133
|
+
const workspaceId = record.workspaceId;
|
|
134
|
+
if (workspaceId !== undefined && readNonEmptyString(workspaceId) === undefined) {
|
|
135
|
+
throw new ImageRequestError('bad-request', '保存请求的 workspaceId 必须是非空字符串。');
|
|
136
|
+
}
|
|
137
|
+
const prompt = record.prompt;
|
|
138
|
+
if (prompt !== undefined && typeof prompt !== 'string') {
|
|
139
|
+
throw new ImageRequestError('bad-request', '保存请求的 prompt 必须是字符串。');
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
...(workspaceId === undefined ? {} : { workspaceId: readNonEmptyString(workspaceId) }),
|
|
144
|
+
...(prompt === undefined ? {} : { prompt }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** 每个 endpoint 放行的字段集合(唯一事实源,解析与断言都读它)。 */
|
|
148
|
+
function allowedKeys(endpoint) {
|
|
149
|
+
switch (endpoint) {
|
|
150
|
+
case 'status': return [];
|
|
151
|
+
case 'generate': return ['prompt', 'size'];
|
|
152
|
+
case 'list': return ['page', 'pageSize'];
|
|
153
|
+
case 'fetch': return ['id'];
|
|
154
|
+
case 'save': return ['id', 'workspaceId', 'prompt'];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** 取消判定(用函数包一层,避免 TS 把属性窄化固化到 `await` 之后)。 */
|
|
158
|
+
function isAborted(signal) {
|
|
159
|
+
return signal?.aborted === true;
|
|
160
|
+
}
|
|
161
|
+
function failure(code, message) {
|
|
162
|
+
return { ok: false, error: { code, message, details: {} } };
|
|
163
|
+
}
|
|
164
|
+
/** base64 字符串对应的解码后字节数(不实际解码;用于信封体积闸的第二道网)。 */
|
|
165
|
+
function decodedByteSize(base64) {
|
|
166
|
+
return Math.floor((base64.length * 3) / 4);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 处理一次端点调用:返回 DSH Connection 的结果信封,**永不抛异常**。
|
|
170
|
+
* 导出以便单测直接驱动(无需伪造 `ctx.connection`)。
|
|
171
|
+
*
|
|
172
|
+
* `status` 走 {@link readImageConfigSafe} 的**降级**分支:网关故障 → 成功信封里的
|
|
173
|
+
* `enabled:false`。「没有会话」「网关地址未配置」两条**不是**降级——那时还没有任何功能
|
|
174
|
+
* 状态可言,照常回错误信封。
|
|
175
|
+
*/
|
|
176
|
+
export async function handleImageEndpoint(endpoint, rawPayload, deps, signal) {
|
|
177
|
+
try {
|
|
178
|
+
if (isAborted(signal))
|
|
179
|
+
return failure('cancelled', '图片请求已取消。');
|
|
180
|
+
if (!isImageRpcEndpoint(endpoint)) {
|
|
181
|
+
throw new ImageRequestError('bad-request', `未知的生图端点 '${endpoint}'`);
|
|
182
|
+
}
|
|
183
|
+
const payload = parseImagePayload(endpoint, rawPayload);
|
|
184
|
+
const sessionCookie = await deps.resolveSession();
|
|
185
|
+
if (sessionCookie === undefined || sessionCookie === '') {
|
|
186
|
+
return failure('SESSION_EXPIRED', IMAGE_SESSION_EXPIRED_MESSAGE);
|
|
187
|
+
}
|
|
188
|
+
const gateway = deps.gateway();
|
|
189
|
+
if (gateway === undefined) {
|
|
190
|
+
return failure('GATEWAY_UNREACHABLE', '网关地址未配置,请先在「网关模型」里完成登录');
|
|
191
|
+
}
|
|
192
|
+
if (isAborted(signal))
|
|
193
|
+
return failure('cancelled', '图片请求已取消。');
|
|
194
|
+
switch (endpoint) {
|
|
195
|
+
case 'status': return { ok: true, value: await readImageConfigSafe(deps, gateway, sessionCookie) };
|
|
196
|
+
case 'generate': return await generate(deps, gateway, sessionCookie, payload);
|
|
197
|
+
case 'list': return await list(gateway, sessionCookie, payload);
|
|
198
|
+
case 'fetch': return await fetchOne(gateway, sessionCookie, payload);
|
|
199
|
+
case 'save': return await save(deps, payload);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof ImageRequestError)
|
|
204
|
+
return failure(error.code, error.message);
|
|
205
|
+
if (error instanceof GatewayError) {
|
|
206
|
+
// code 原样透传(稳定契约),message 消毒后再给客户端。
|
|
207
|
+
return failure(error.code, displayImageGatewayMessage(error.code, error.message));
|
|
208
|
+
}
|
|
209
|
+
deps.log?.(`hiwork-core: 生图 RPC '${endpoint}' 失败(${error instanceof Error ? error.name : typeof error})`);
|
|
210
|
+
return failure('internal', IMAGE_GENERIC_FAILURE_MESSAGE);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* `status` 端点取数:**读不到配置就让能力消失**(返回 {@link IMAGE_FALLBACK_CONFIG}),
|
|
215
|
+
* 而不是回错误信封。
|
|
216
|
+
*
|
|
217
|
+
* 这条端点会被页面挂载时调一次,它的唯一用途是「该不该显示生图入口」;网关故障、
|
|
218
|
+
* 会话在途中过期、响应形状不对……对用户而言都只有同一个结论:现在用不了。
|
|
219
|
+
* 抛错误信封会让一个**默认关闭**的功能冒出红字。所以这里把全部异常咽成禁用配置,
|
|
220
|
+
* 只留一条 Host 日志(日志里也不带 cookie / 响应原文)。
|
|
221
|
+
*/
|
|
222
|
+
async function readImageConfigSafe(deps, gateway, sessionCookie) {
|
|
223
|
+
try {
|
|
224
|
+
// 缓存读取器(index.ts 注入)自己就是不抛的实现;这里再兜一层,防注入的实现违约。
|
|
225
|
+
const config = await (deps.imageConfig ?? ((cookie) => readImageConfig(gateway, cookie, deps.log)))(sessionCookie);
|
|
226
|
+
if (config === undefined)
|
|
227
|
+
return { ...IMAGE_FALLBACK_CONFIG };
|
|
228
|
+
return config;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
deps.log?.(`hiwork-core: 生图配置读取失败,按禁用回落(${error instanceof Error ? error.name : typeof error})`);
|
|
232
|
+
return { ...IMAGE_FALLBACK_CONFIG };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** `generate` 端点:交 `imagegen.ts` 生图 + 写盘;失败按稳定 code 回错误信封。 */
|
|
236
|
+
async function generate(deps, gateway, sessionCookie, payload) {
|
|
237
|
+
const outcome = await generateImageFile({
|
|
238
|
+
resolveSession: async () => sessionCookie,
|
|
239
|
+
gateway: () => gateway,
|
|
240
|
+
...(deps.imageConfig === undefined ? {} : { imageConfig: deps.imageConfig }),
|
|
241
|
+
...(deps.now === undefined ? {} : { now: deps.now }),
|
|
242
|
+
...(deps.log === undefined ? {} : { log: deps.log }),
|
|
243
|
+
}, { prompt: payload.prompt ?? '', ...(payload.size === undefined ? {} : { size: payload.size }) });
|
|
244
|
+
if (!outcome.ok)
|
|
245
|
+
return failure(outcome.code, outcome.message);
|
|
246
|
+
return { ok: true, value: outcome };
|
|
247
|
+
}
|
|
248
|
+
/** `list` 端点:原样透出网关分页视图(含 `size`),分页缺省与上限在 Host 侧夹紧。 */
|
|
249
|
+
async function list(gateway, sessionCookie, payload) {
|
|
250
|
+
const view = await gateway.getMyImages(sessionCookie, {
|
|
251
|
+
page: payload.page ?? IMAGE_LIST_DEFAULT_PAGE,
|
|
252
|
+
pageSize: Math.min(payload.pageSize ?? IMAGE_LIST_DEFAULT_PAGE_SIZE, IMAGE_LIST_MAX_PAGE_SIZE),
|
|
253
|
+
});
|
|
254
|
+
// `file_url` 在网关客户端层已被丢弃(见 GatewayImageRow);这里不再二次投影。
|
|
255
|
+
return { ok: true, value: view };
|
|
256
|
+
}
|
|
257
|
+
/** `fetch` 端点:**点哪张取哪张**,超过 20MiB 直接拒(不塞进 RPC 信封)。 */
|
|
258
|
+
async function fetchOne(gateway, sessionCookie, payload) {
|
|
259
|
+
const id = payload.id;
|
|
260
|
+
if (id === undefined)
|
|
261
|
+
throw new ImageRequestError('bad-request', '图片请求缺少图片 id。');
|
|
262
|
+
const file = await gateway.fetchMyImage(sessionCookie, id);
|
|
263
|
+
if (file.byteSize > MAX_IMAGE_BYTES || decodedByteSize(file.dataBase64) > MAX_IMAGE_BYTES) {
|
|
264
|
+
return failure('IMAGE_TOO_LARGE', '生成的图片太大了,换个小一点的尺寸(如 1024x1024)');
|
|
265
|
+
}
|
|
266
|
+
return { ok: true, value: { dataBase64: file.dataBase64, mimeType: file.mimeType, byteSize: file.byteSize } };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* `save` 端点:取本体 → 落工作区 → 回 `{ path }` 绝对路径。
|
|
270
|
+
*
|
|
271
|
+
* 两类失败都走**错误信封**(不是成功信封里的 `{ok:false}`):页面这半边拿的是
|
|
272
|
+
* `save(id) => {path}` 一个签名(`src/client/images-data.ts` 的 `ImageDataSource`),
|
|
273
|
+
* 业务失败要在**同一处**冒出来(页面把它显示在行内红字里),成功信封里塞一个失败对象
|
|
274
|
+
* 只会让页面渲染出一个空路径。
|
|
275
|
+
* - 网关 / 会话失败:`saveImageToWorkspace` 抛 `GatewayError` → 由
|
|
276
|
+
* {@link handleImageEndpoint} 统一映射成稳定 code;
|
|
277
|
+
* - 工作区不存在 / 越界 / 写盘失败:`{ok:false, code, reason}` → 这里转成同款错误信封,
|
|
278
|
+
* code 用 `IMAGE_WORKSPACE_NOT_FOUND` / `IMAGE_SAVE_FAILED`(冻结契约,页面按 code 分支)。
|
|
279
|
+
*/
|
|
280
|
+
async function save(deps, payload) {
|
|
281
|
+
const id = payload.id;
|
|
282
|
+
if (id === undefined)
|
|
283
|
+
throw new ImageRequestError('bad-request', '图片请求缺少图片 id。');
|
|
284
|
+
const outcome = await saveImageToWorkspace({
|
|
285
|
+
resolveSession: deps.resolveSession,
|
|
286
|
+
gateway: deps.gateway,
|
|
287
|
+
...(deps.workspaces === undefined ? {} : { workspaces: deps.workspaces }),
|
|
288
|
+
...(deps.now === undefined ? {} : { now: deps.now }),
|
|
289
|
+
...(deps.log === undefined ? {} : { log: deps.log }),
|
|
290
|
+
}, {
|
|
291
|
+
id,
|
|
292
|
+
...(payload.workspaceId === undefined ? {} : { workspaceId: payload.workspaceId }),
|
|
293
|
+
...(payload.prompt === undefined ? {} : { prompt: payload.prompt }),
|
|
294
|
+
});
|
|
295
|
+
if (!outcome.ok)
|
|
296
|
+
return failure(outcome.code, outcome.reason);
|
|
297
|
+
return { ok: true, value: { path: outcome.path } };
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 注册 `/hiwork-image` 频道,返回异步 disposer(交给 `ctx.effect` 管理生命周期)。
|
|
301
|
+
*/
|
|
302
|
+
export function registerImageRpc(ctx, deps) {
|
|
303
|
+
const handler = (endpoint, payload, signal) => handleImageEndpoint(endpoint, payload, deps, signal);
|
|
304
|
+
const remove = ctx.connection.rpc.handle(IMAGE_RPC_CHANNEL, handler, { authority: 'loopback' });
|
|
305
|
+
return async () => { await remove(); };
|
|
306
|
+
}
|