dsh-kenari-plugin 0.1.1
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/LICENSE +21 -0
- package/README.en.md +281 -0
- package/README.md +283 -0
- package/assets/kenari-favicon-128.png +0 -0
- package/assets/kenari-favicon-256.png +0 -0
- package/client/index.js +1998 -0
- package/cordis.patch.yml +137 -0
- package/lib/billing.d.ts +120 -0
- package/lib/billing.d.ts.map +1 -0
- package/lib/billing.js +209 -0
- package/lib/billing.js.map +1 -0
- package/lib/catalog-view.d.ts +78 -0
- package/lib/catalog-view.d.ts.map +1 -0
- package/lib/catalog-view.js +113 -0
- package/lib/catalog-view.js.map +1 -0
- package/lib/catalog.d.ts +239 -0
- package/lib/catalog.d.ts.map +1 -0
- package/lib/catalog.js +445 -0
- package/lib/catalog.js.map +1 -0
- package/lib/default-route.d.ts +35 -0
- package/lib/default-route.d.ts.map +1 -0
- package/lib/default-route.js +111 -0
- package/lib/default-route.js.map +1 -0
- package/lib/errors.d.ts +29 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +74 -0
- package/lib/errors.js.map +1 -0
- package/lib/favicon.d.ts +24 -0
- package/lib/favicon.d.ts.map +1 -0
- package/lib/favicon.js +68 -0
- package/lib/favicon.js.map +1 -0
- package/lib/http.d.ts +104 -0
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +247 -0
- package/lib/http.js.map +1 -0
- package/lib/index.d.ts +83 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +285 -0
- package/lib/index.js.map +1 -0
- package/lib/llm/adapter.d.ts +78 -0
- package/lib/llm/adapter.d.ts.map +1 -0
- package/lib/llm/adapter.js +429 -0
- package/lib/llm/adapter.js.map +1 -0
- package/lib/llm/candidates.d.ts +57 -0
- package/lib/llm/candidates.d.ts.map +1 -0
- package/lib/llm/candidates.js +86 -0
- package/lib/llm/candidates.js.map +1 -0
- package/lib/llm/recovery.d.ts +48 -0
- package/lib/llm/recovery.d.ts.map +1 -0
- package/lib/llm/recovery.js +118 -0
- package/lib/llm/recovery.js.map +1 -0
- package/lib/llm/retry.d.ts +35 -0
- package/lib/llm/retry.d.ts.map +1 -0
- package/lib/llm/retry.js +53 -0
- package/lib/llm/retry.js.map +1 -0
- package/lib/plans.d.ts +59 -0
- package/lib/plans.d.ts.map +1 -0
- package/lib/plans.js +118 -0
- package/lib/plans.js.map +1 -0
- package/lib/session-title.d.ts +63 -0
- package/lib/session-title.d.ts.map +1 -0
- package/lib/session-title.js +280 -0
- package/lib/session-title.js.map +1 -0
- package/lib/settings.d.ts +35 -0
- package/lib/settings.d.ts.map +1 -0
- package/lib/settings.js +78 -0
- package/lib/settings.js.map +1 -0
- package/lib/tools/account.d.ts +11 -0
- package/lib/tools/account.d.ts.map +1 -0
- package/lib/tools/account.js +132 -0
- package/lib/tools/account.js.map +1 -0
- package/lib/tools/billing.d.ts +10 -0
- package/lib/tools/billing.d.ts.map +1 -0
- package/lib/tools/billing.js +100 -0
- package/lib/tools/billing.js.map +1 -0
- package/lib/tools/count-tokens.d.ts +11 -0
- package/lib/tools/count-tokens.d.ts.map +1 -0
- package/lib/tools/count-tokens.js +84 -0
- package/lib/tools/count-tokens.js.map +1 -0
- package/lib/tools/data.d.ts +12 -0
- package/lib/tools/data.d.ts.map +1 -0
- package/lib/tools/data.js +205 -0
- package/lib/tools/data.js.map +1 -0
- package/lib/tools/docs.d.ts +13 -0
- package/lib/tools/docs.d.ts.map +1 -0
- package/lib/tools/docs.js +209 -0
- package/lib/tools/docs.js.map +1 -0
- package/lib/tools/documents.d.ts +10 -0
- package/lib/tools/documents.d.ts.map +1 -0
- package/lib/tools/documents.js +118 -0
- package/lib/tools/documents.js.map +1 -0
- package/lib/tools/media.d.ts +28 -0
- package/lib/tools/media.d.ts.map +1 -0
- package/lib/tools/media.js +599 -0
- package/lib/tools/media.js.map +1 -0
- package/lib/tools/shared.d.ts +89 -0
- package/lib/tools/shared.d.ts.map +1 -0
- package/lib/tools/shared.js +128 -0
- package/lib/tools/shared.js.map +1 -0
- package/lib/tools/x-search.d.ts +11 -0
- package/lib/tools/x-search.d.ts.map +1 -0
- package/lib/tools/x-search.js +129 -0
- package/lib/tools/x-search.js.map +1 -0
- package/lib/web/fallback.d.ts +39 -0
- package/lib/web/fallback.d.ts.map +1 -0
- package/lib/web/fallback.js +79 -0
- package/lib/web/fallback.js.map +1 -0
- package/lib/web/fetch.d.ts +19 -0
- package/lib/web/fetch.d.ts.map +1 -0
- package/lib/web/fetch.js +41 -0
- package/lib/web/fetch.js.map +1 -0
- package/lib/web/search.d.ts +21 -0
- package/lib/web/search.d.ts.map +1 -0
- package/lib/web/search.js +47 -0
- package/lib/web/search.js.map +1 -0
- package/package.json +84 -0
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kenari REST 错误词汇:[OI] 线错误信封解析、状态码 → dsh 错误码映射,
|
|
3
|
+
* 以及高频错误的**可执行建议**(模型与 UI 读到的就是这段文本)。
|
|
4
|
+
*
|
|
5
|
+
* Kenari 处于 rc 期,信封字段可能变动;解析保持宽松,缺字段不抛错。
|
|
6
|
+
* @module dsh-kenari-plugin/errors
|
|
7
|
+
*/
|
|
8
|
+
import { WebError } from '@deepseek-ai/dsh-web';
|
|
9
|
+
/** [OI] 线错误信封:`{ error: { message, type, code, param } }`,字段缺省不报错。 */
|
|
10
|
+
export interface KenariErrorEnvelope {
|
|
11
|
+
error?: {
|
|
12
|
+
message?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 解析 Kenari 错误信封的 message,并附上该状态码的可执行建议。
|
|
19
|
+
* 非 JSON、超长或 HTML 体回退 HTTP 状态文本;调用方向:http.ts 非 2xx 时调用。
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractKenariErrorMessage(bodyText: string, status: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* 把 Kenari 状态码映射为回退语义:429/408/5xx 可重试(http.ts 已内联重试),
|
|
24
|
+
* 401/402/403 等 Kenari 侧不可恢复,由 fallback 层回退兜底 provider。
|
|
25
|
+
*/
|
|
26
|
+
export declare function classifyKenariStatus(status: number): 'retryable' | 'fallback';
|
|
27
|
+
/** 构造带 `WEB_KENARI_*` code 的 WebError,cause 保留原始错误供诊断。 */
|
|
28
|
+
export declare function kenariWebError(code: string, message: string, cause?: unknown): WebError;
|
|
29
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAkCD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAelF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAG7E;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAEvF"}
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kenari REST 错误词汇:[OI] 线错误信封解析、状态码 → dsh 错误码映射,
|
|
3
|
+
* 以及高频错误的**可执行建议**(模型与 UI 读到的就是这段文本)。
|
|
4
|
+
*
|
|
5
|
+
* Kenari 处于 rc 期,信封字段可能变动;解析保持宽松,缺字段不抛错。
|
|
6
|
+
* @module dsh-kenari-plugin/errors
|
|
7
|
+
*/
|
|
8
|
+
import { WebError } from '@deepseek-ai/dsh-web';
|
|
9
|
+
/** 纯文本错误体的最大采纳长度(401 实测返回纯文本 `invalid key`)。 */
|
|
10
|
+
const PLAIN_TEXT_LIMIT = 200;
|
|
11
|
+
/**
|
|
12
|
+
* 状态码 → 可执行建议。只在能给出**具体动作**时才附上,避免正确的废话。
|
|
13
|
+
* 生成类调用超时不在此列(http.ts 已按 retryTimeouts 处理,不会走到这里)。
|
|
14
|
+
*/
|
|
15
|
+
function guidanceFor(status, text) {
|
|
16
|
+
if (status === 401) {
|
|
17
|
+
return '鉴权失败:确认凭据引用解析出的 key 有效(重新复制 kn-... 或新开终端让环境变量生效),并确认 base URL 形状正确([OI]/Responses 用 https://kenari.id/v1,Anthropic 用 https://kenari.id 不带 /v1)。';
|
|
18
|
+
}
|
|
19
|
+
if (status === 402 || text.includes('insufficient_balance')) {
|
|
20
|
+
return '余额不足:改用免费模型(目录里 id 带 :free,如 step-3-7-flash:free)或充值(QRIS 最低 Rp 1.000)。';
|
|
21
|
+
}
|
|
22
|
+
if (status === 403 || text.includes('shared_key_not_allowed')) {
|
|
23
|
+
return '403:若这是分享页 key,则 balance / usage / quota 会被拒(它们读 key 所有者的账户数据),消费类工具仍可用;要读账户数字请改用自己账号的 key。';
|
|
24
|
+
}
|
|
25
|
+
if (status === 405) {
|
|
26
|
+
return '405 表示 base URL 形状错误(不是 404):[OI]/Responses 用 https://kenari.id/v1,Anthropic 用 https://kenari.id(不带 /v1)。';
|
|
27
|
+
}
|
|
28
|
+
if (status === 413) {
|
|
29
|
+
return '请求体过大:降低图像尺寸或数量、缩短输入后重试。';
|
|
30
|
+
}
|
|
31
|
+
if (status === 422) {
|
|
32
|
+
return '参数校验失败:按响应里的取值提示修正参数。';
|
|
33
|
+
}
|
|
34
|
+
if (status === 429) {
|
|
35
|
+
return '触发限流:按 Retry-After 退避后重试;生成类调用不要因客户端超时而重试(可能重复扣费)。';
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 解析 Kenari 错误信封的 message,并附上该状态码的可执行建议。
|
|
41
|
+
* 非 JSON、超长或 HTML 体回退 HTTP 状态文本;调用方向:http.ts 非 2xx 时调用。
|
|
42
|
+
*/
|
|
43
|
+
export function extractKenariErrorMessage(bodyText, status) {
|
|
44
|
+
const raw = bodyText.trim();
|
|
45
|
+
let message;
|
|
46
|
+
if (raw.length > 0) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
message = parsed.error?.message ?? parsed.error?.code;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// 401 等返回纯文本;HTML 兜底页(未知路由)不当错误文案用
|
|
53
|
+
if (raw.length <= PLAIN_TEXT_LIMIT && !raw.startsWith('<'))
|
|
54
|
+
message = raw;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const base = message !== undefined && message.length > 0 ? message : `HTTP ${status}`;
|
|
58
|
+
const guidance = guidanceFor(status, base);
|
|
59
|
+
return guidance === undefined ? base : `${base} — ${guidance}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 把 Kenari 状态码映射为回退语义:429/408/5xx 可重试(http.ts 已内联重试),
|
|
63
|
+
* 401/402/403 等 Kenari 侧不可恢复,由 fallback 层回退兜底 provider。
|
|
64
|
+
*/
|
|
65
|
+
export function classifyKenariStatus(status) {
|
|
66
|
+
if (status === 429 || status === 408 || status >= 500)
|
|
67
|
+
return 'retryable';
|
|
68
|
+
return 'fallback';
|
|
69
|
+
}
|
|
70
|
+
/** 构造带 `WEB_KENARI_*` code 的 WebError,cause 保留原始错误供诊断。 */
|
|
71
|
+
export function kenariWebError(code, message, cause) {
|
|
72
|
+
return new WebError(message, code, cause !== undefined ? { cause } : undefined);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAW/C,gDAAgD;AAChD,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAE5B;;;GAGG;AACH,SAAS,WAAW,CAAC,MAAc,EAAE,IAAY;IAC/C,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,iJAAiJ,CAAA;IAC1J,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;QAC5D,OAAO,yEAAyE,CAAA;IAClF,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;QAC9D,OAAO,6FAA6F,CAAA;IACtG,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,2GAA2G,CAAA;IACpH,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,0BAA0B,CAAA;IACnC,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,uBAAuB,CAAA;IAChC,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,oDAAoD,CAAA;IAC7D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,MAAc;IACxE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC3B,IAAI,OAA2B,CAAA;IAC/B,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAA;YACrD,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,CAAA;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;YACnC,IAAI,GAAG,CAAC,MAAM,IAAI,gBAAgB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,GAAG,GAAG,CAAA;QAC3E,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAA;IACrF,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC1C,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,QAAQ,EAAE,CAAA;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,WAAW,CAAA;IACzE,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAe,EAAE,KAAe;IAC3E,OAAO,IAAI,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AACjF,CAAC"}
|
package/lib/favicon.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器侧 Kenari 图标的同源端点:`GET /api/kenari.favicon`。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有这个端点:设置面板左侧导航的图标由 dsh 的 shell 画(`navIcon(row.id)`
|
|
5
|
+
* 是写死的 id 分支),而 section 注册项只有 `{ id, order, label }`,没有图标字段——
|
|
6
|
+
* 插件加不进去。能加进去的只有 DOM,所以图标由浏览器半边自己贴(见 `client/index.js`)。
|
|
7
|
+
* 但客户端 bundle 没有打包器:loader 只读 `exports["./client"]` 这一个文件,图片引用
|
|
8
|
+
* 拿不到可用的 URL。于是图标留在 `assets/` 里,浏览器像读模型视图一样向 Host 要它。
|
|
9
|
+
*
|
|
10
|
+
* 这与模型视图补的是同一种缺口,所以走同一条路:Connection 的 Fetch 路由。
|
|
11
|
+
* `connection` 是 web profile 才有的服务;没有它的部署不会提供图标,
|
|
12
|
+
* 浏览器半边会退回 dsh 自己的齿轮图标(不贴标签就什么都不改)。
|
|
13
|
+
* @module dsh-kenari-plugin/favicon
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
/** 浏览器读取图标的路径(Connection 的 Fetch 路由要求落在 `/api/` 下)。 */
|
|
17
|
+
export declare const KENARI_FAVICON_PATH = "/api/kenari.favicon";
|
|
18
|
+
/**
|
|
19
|
+
* 注册图标路由。
|
|
20
|
+
*
|
|
21
|
+
* 无 connection 服务时静默跳过——图标只是界面装饰,插件其余能力不依赖它。
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerFaviconRoute(ctx: Context): void;
|
|
24
|
+
//# sourceMappingURL=favicon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"favicon.d.ts","sourceRoot":"","sources":["../src/favicon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAuCxD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CASvD"}
|
package/lib/favicon.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器侧 Kenari 图标的同源端点:`GET /api/kenari.favicon`。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有这个端点:设置面板左侧导航的图标由 dsh 的 shell 画(`navIcon(row.id)`
|
|
5
|
+
* 是写死的 id 分支),而 section 注册项只有 `{ id, order, label }`,没有图标字段——
|
|
6
|
+
* 插件加不进去。能加进去的只有 DOM,所以图标由浏览器半边自己贴(见 `client/index.js`)。
|
|
7
|
+
* 但客户端 bundle 没有打包器:loader 只读 `exports["./client"]` 这一个文件,图片引用
|
|
8
|
+
* 拿不到可用的 URL。于是图标留在 `assets/` 里,浏览器像读模型视图一样向 Host 要它。
|
|
9
|
+
*
|
|
10
|
+
* 这与模型视图补的是同一种缺口,所以走同一条路:Connection 的 Fetch 路由。
|
|
11
|
+
* `connection` 是 web profile 才有的服务;没有它的部署不会提供图标,
|
|
12
|
+
* 浏览器半边会退回 dsh 自己的齿轮图标(不贴标签就什么都不改)。
|
|
13
|
+
* @module dsh-kenari-plugin/favicon
|
|
14
|
+
*/
|
|
15
|
+
import { readFile } from 'node:fs/promises';
|
|
16
|
+
/** 浏览器读取图标的路径(Connection 的 Fetch 路由要求落在 `/api/` 下)。 */
|
|
17
|
+
export const KENARI_FAVICON_PATH = '/api/kenari.favicon';
|
|
18
|
+
/**
|
|
19
|
+
* 图标本体:包内 `assets/kenari-favicon-128.png`(128×128)。
|
|
20
|
+
* `lib/favicon.js` 与 `src/favicon.ts` 都在包根下一层,所以 `../assets` 两条路都对。
|
|
21
|
+
*/
|
|
22
|
+
const FAVICON_FILE = new URL('../assets/kenari-favicon-128.png', import.meta.url);
|
|
23
|
+
/** 进程内文件不会变,第二次读没有意义;失败也不缓存,下次请求还会再试。 */
|
|
24
|
+
let cached;
|
|
25
|
+
function faviconBytes() {
|
|
26
|
+
const pending = cached ?? readFile(FAVICON_FILE);
|
|
27
|
+
cached = pending;
|
|
28
|
+
return pending.catch((err) => {
|
|
29
|
+
cached = undefined;
|
|
30
|
+
throw err;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function faviconResponse(request) {
|
|
34
|
+
let bytes;
|
|
35
|
+
try {
|
|
36
|
+
bytes = await faviconBytes();
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
// 打包漏掉 assets/ 时走到这里。回 404 而不是抛错:浏览器半边据此保持
|
|
40
|
+
// dsh 自己的图标,而"图标没贴上去"不该让设置页整个坏掉。
|
|
41
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42
|
+
return Response.json({ error: message }, { status: 404 });
|
|
43
|
+
}
|
|
44
|
+
const headers = {
|
|
45
|
+
'content-type': 'image/png',
|
|
46
|
+
'content-length': String(bytes.byteLength),
|
|
47
|
+
'cache-control': 'public, max-age=86400',
|
|
48
|
+
};
|
|
49
|
+
if (request.method === 'HEAD')
|
|
50
|
+
return new Response(null, { status: 200, headers });
|
|
51
|
+
return new Response(bytes, { status: 200, headers });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 注册图标路由。
|
|
55
|
+
*
|
|
56
|
+
* 无 connection 服务时静默跳过——图标只是界面装饰,插件其余能力不依赖它。
|
|
57
|
+
*/
|
|
58
|
+
export function registerFaviconRoute(ctx) {
|
|
59
|
+
ctx.inject(['connection'], (connectionCtx) => {
|
|
60
|
+
connectionCtx.connection.fetch.register({
|
|
61
|
+
path: KENARI_FAVICON_PATH,
|
|
62
|
+
methods: ['GET', 'HEAD'],
|
|
63
|
+
requestBody: 'buffered',
|
|
64
|
+
fetch: (request) => faviconResponse(request),
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=favicon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"favicon.js","sourceRoot":"","sources":["../src/favicon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAK3C,uDAAuD;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAA;AAExD;;;GAGG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,kCAAkC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEjF,yCAAyC;AACzC,IAAI,MAAmC,CAAA;AAEvC,SAAS,YAAY;IACnB,MAAM,OAAO,GAAG,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAA;IAChD,MAAM,GAAG,OAAO,CAAA;IAChB,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACpC,MAAM,GAAG,SAAS,CAAA;QAClB,MAAM,GAAG,CAAA;IACX,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,OAAgB;IAC7C,IAAI,KAAa,CAAA;IACjB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,YAAY,EAAE,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,2CAA2C;QAC3C,iCAAiC;QACjC,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChE,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAC3D,CAAC;IACD,MAAM,OAAO,GAAG;QACd,cAAc,EAAE,WAAW;QAC3B,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;QAC1C,eAAe,EAAE,uBAAuB;KACzC,CAAA;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;IAClF,OAAO,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,GAAG,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,EAAE;QAC3C,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;YACtC,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;YACxB,WAAW,EAAE,UAAU;YACvB,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC;SAC7C,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/lib/http.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kenari HTTP 客户端:web/fetch 两条 REST 调用共用的传输层。
|
|
3
|
+
* 集中超时、重试、key 现场解析与错误信封映射;后端 rc 期端点变动只改这里。
|
|
4
|
+
* @module dsh-kenari-plugin/http
|
|
5
|
+
*/
|
|
6
|
+
import type { Config as PluginConfig } from './index.js';
|
|
7
|
+
/** Kenari web 端点响应体:search 与 fetch 共有的计费字段之外各取所需。 */
|
|
8
|
+
export interface KenariSearchResponse {
|
|
9
|
+
results?: {
|
|
10
|
+
title?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
content?: string;
|
|
13
|
+
}[];
|
|
14
|
+
id?: string;
|
|
15
|
+
cost_micro_idr?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface KenariFetchResponse {
|
|
18
|
+
title?: string;
|
|
19
|
+
content?: string;
|
|
20
|
+
links?: string[];
|
|
21
|
+
id?: string;
|
|
22
|
+
cost_micro_idr?: number;
|
|
23
|
+
}
|
|
24
|
+
/** 每次操作现场解析 key(硬约束 9:禁止跨操作缓存)。 */
|
|
25
|
+
export type ResolveApiKey = () => Promise<string | undefined>;
|
|
26
|
+
/** 跨操作持有的运行时依赖,由 apply 装配。 */
|
|
27
|
+
export interface KenariHttpDeps {
|
|
28
|
+
readonly config: PluginConfig;
|
|
29
|
+
readonly resolveApiKey: ResolveApiKey;
|
|
30
|
+
readonly logger?: {
|
|
31
|
+
warn(msg: string): void;
|
|
32
|
+
info(msg: string): void;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** 单次请求的可选覆盖:媒体生成需要更长超时,且超时不得重试。 */
|
|
36
|
+
export interface KenariRequestOptions {
|
|
37
|
+
/** 每次尝试的超时覆盖(默认取 config.timeoutMs)。 */
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
/** 重试次数覆盖(默认取 config.maxRetries)。 */
|
|
40
|
+
retries?: number;
|
|
41
|
+
/**
|
|
42
|
+
* 尝试超时是否可重试(默认 true)。
|
|
43
|
+
* 生成类调用置 false:请求可能已在服务端完成并计费,重试有重复扣费风险。
|
|
44
|
+
*/
|
|
45
|
+
retryTimeouts?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* 是否带 `Authorization: Bearer <key>` 并因此要求 key(默认 true)。
|
|
48
|
+
* 站点公开端点(套餐表等,不在 /v1 下、不需要 key)置 false:
|
|
49
|
+
* 没有 key 的部署也应该能读到它们。
|
|
50
|
+
*/
|
|
51
|
+
auth?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* POST 一个 Kenari [OI] 线端点并解析 JSON 响应。
|
|
55
|
+
* 非法 URL → WEB_KENARI_NOT_CONFIGURED;key 缺失 → WEB_KENARI_NO_KEY;
|
|
56
|
+
* 网络失败 → WEB_KENARI_NETWORK;非 2xx → WEB_KENARI_HTTP(消息不含 key)。
|
|
57
|
+
*/
|
|
58
|
+
export declare function kenariPost<T>(deps: KenariHttpDeps, path: string, body: Record<string, unknown>, signal?: AbortSignal, opts?: KenariRequestOptions): Promise<T>;
|
|
59
|
+
/**
|
|
60
|
+
* GET 一个 Kenari [OI] 线端点并解析 JSON 响应(quota、models 等只读端点)。
|
|
61
|
+
* 错误语义与 kenariPost 一致。
|
|
62
|
+
*/
|
|
63
|
+
export declare function kenariGet<T>(deps: KenariHttpDeps, path: string, query?: Record<string, string>, signal?: AbortSignal, opts?: KenariRequestOptions): Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* GET 一个 Kenari **站点**端点(绝对 URL、无 key、不补 /v1)。
|
|
66
|
+
*
|
|
67
|
+
* 与 REST 线分开的理由:这类端点(`/api/plans` 之类)是站点自己的公开路由,
|
|
68
|
+
* 既不在 `/v1` 下也不认 `Authorization`。它们的形状比 API 线宽松(官网改版即可能变),
|
|
69
|
+
* 所以调用方必须把解析失败当成可选数据缺失,而不是插件故障。
|
|
70
|
+
*/
|
|
71
|
+
export declare function kenariPublicGet<T>(deps: KenariHttpDeps, url: string, signal?: AbortSignal, opts?: KenariRequestOptions): Promise<T>;
|
|
72
|
+
/**
|
|
73
|
+
* POST 一个 JSON 端点并把二进制响应体原样带回(audio/speech 等返回原始字节)。
|
|
74
|
+
* 返回体可能很大,调用方负责落到 attachment 存储。
|
|
75
|
+
*/
|
|
76
|
+
export declare function kenariPostBinary(deps: KenariHttpDeps, path: string, body: Record<string, unknown>, signal?: AbortSignal, opts?: KenariRequestOptions): Promise<{
|
|
77
|
+
bytes: Uint8Array;
|
|
78
|
+
contentType: string;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* GET 一个端点并把二进制响应体原样带回(videos/{id}/content 下载)。
|
|
82
|
+
* 端点需要 key:下载 URL 带 key 访问,故与 POST 共用鉴权与超时。
|
|
83
|
+
*/
|
|
84
|
+
export declare function kenariGetBinary(deps: KenariHttpDeps, path: string, signal?: AbortSignal, opts?: KenariRequestOptions): Promise<{
|
|
85
|
+
bytes: Uint8Array;
|
|
86
|
+
contentType: string;
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* POST multipart/form-data(images/edits、audio/transcriptions)并解析 JSON 响应。
|
|
90
|
+
* fields 里的二进制值以 Uint8Array 出现,其余转为字符串。
|
|
91
|
+
*/
|
|
92
|
+
export declare function kenariPostMultipart<T>(deps: KenariHttpDeps, path: string, fields: Record<string, string | Uint8Array>, signal?: AbortSignal, opts?: KenariRequestOptions): Promise<T>;
|
|
93
|
+
/**
|
|
94
|
+
* 调一次 Kenari MCP server 的工具(balance/usage 无 REST 端点,公开 MCP 是唯一程序化入口)。
|
|
95
|
+
* 单次 JSON-RPC,无会话(服务端是无状态模式);isError 结果转 WEB_KENARI_HTTP。
|
|
96
|
+
*/
|
|
97
|
+
export declare function kenariMcpCall(deps: KenariHttpDeps, tool: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* 站点端点拼装:baseURL 去掉 `/v1` 后再接路径。
|
|
100
|
+
* 用户把 baseURL 配成 `https://kenari.id/v1`([OI] 线的标准形状),
|
|
101
|
+
* 而站点路由在裸域下,所以这里必须反向归一。
|
|
102
|
+
*/
|
|
103
|
+
export declare function siteEndpoint(baseURL: string | undefined, path: string): string;
|
|
104
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,YAAY,CAAA;AAKxD,qDAAqD;AACrD,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,mCAAmC;AACnC,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;AAE7D,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACvE;AAED,oCAAoC;AACpC,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAgBD;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,CAAC,CAAC,CASZ;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,CAAC,CAAC,CAMZ;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,CAAC,CAAC,CAKZ;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAarD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CASrD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,EAC3C,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAgCjB;AAmFD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9E"}
|
package/lib/http.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kenari HTTP 客户端:web/fetch 两条 REST 调用共用的传输层。
|
|
3
|
+
* 集中超时、重试、key 现场解析与错误信封映射;后端 rc 期端点变动只改这里。
|
|
4
|
+
* @module dsh-kenari-plugin/http
|
|
5
|
+
*/
|
|
6
|
+
import { extractKenariErrorMessage, kenariWebError } from './errors.js';
|
|
7
|
+
/** Kenari MCP 端点(Streamable HTTP,JSON 模式)。balance/usage 无 REST 端点,只能走这里。 */
|
|
8
|
+
const MCP_ENDPOINT = 'https://kenari.id/mcp';
|
|
9
|
+
/** 瞬时失败(429/408/5xx/网络错误)可重试;其余状态一次即返。 */
|
|
10
|
+
function isRetryableStatus(status) {
|
|
11
|
+
return status === 429 || status === 408 || status >= 500;
|
|
12
|
+
}
|
|
13
|
+
/** 定时器触发的 AbortSignal 抛 TimeoutError;用它区分超时与外部取消。 */
|
|
14
|
+
function isTimeoutError(err) {
|
|
15
|
+
return err instanceof Error && err.name === 'TimeoutError';
|
|
16
|
+
}
|
|
17
|
+
function sleep(ms) {
|
|
18
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* POST 一个 Kenari [OI] 线端点并解析 JSON 响应。
|
|
22
|
+
* 非法 URL → WEB_KENARI_NOT_CONFIGURED;key 缺失 → WEB_KENARI_NO_KEY;
|
|
23
|
+
* 网络失败 → WEB_KENARI_NETWORK;非 2xx → WEB_KENARI_HTTP(消息不含 key)。
|
|
24
|
+
*/
|
|
25
|
+
export async function kenariPost(deps, path, body, signal, opts) {
|
|
26
|
+
return kenariRequest(deps, 'POST', path, signal, opts, async (init, endpoint) => {
|
|
27
|
+
const response = await fetch(endpoint, {
|
|
28
|
+
...init,
|
|
29
|
+
headers: { ...init.headers, 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
});
|
|
32
|
+
return finishJson(deps, response, endpoint);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* GET 一个 Kenari [OI] 线端点并解析 JSON 响应(quota、models 等只读端点)。
|
|
37
|
+
* 错误语义与 kenariPost 一致。
|
|
38
|
+
*/
|
|
39
|
+
export async function kenariGet(deps, path, query, signal, opts) {
|
|
40
|
+
const qs = query === undefined ? '' : `?${new URLSearchParams(query).toString()}`;
|
|
41
|
+
return kenariRequest(deps, 'GET', `${path}${qs}`, signal, opts, async (init, endpoint) => {
|
|
42
|
+
const response = await fetch(endpoint, init);
|
|
43
|
+
return finishJson(deps, response, endpoint);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* GET 一个 Kenari **站点**端点(绝对 URL、无 key、不补 /v1)。
|
|
48
|
+
*
|
|
49
|
+
* 与 REST 线分开的理由:这类端点(`/api/plans` 之类)是站点自己的公开路由,
|
|
50
|
+
* 既不在 `/v1` 下也不认 `Authorization`。它们的形状比 API 线宽松(官网改版即可能变),
|
|
51
|
+
* 所以调用方必须把解析失败当成可选数据缺失,而不是插件故障。
|
|
52
|
+
*/
|
|
53
|
+
export async function kenariPublicGet(deps, url, signal, opts) {
|
|
54
|
+
return kenariRequest(deps, 'GET', url, signal, { ...opts, auth: false }, async (init, endpoint) => {
|
|
55
|
+
const response = await fetch(endpoint, init);
|
|
56
|
+
return finishJson(deps, response, endpoint);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* POST 一个 JSON 端点并把二进制响应体原样带回(audio/speech 等返回原始字节)。
|
|
61
|
+
* 返回体可能很大,调用方负责落到 attachment 存储。
|
|
62
|
+
*/
|
|
63
|
+
export async function kenariPostBinary(deps, path, body, signal, opts) {
|
|
64
|
+
return kenariRequest(deps, 'POST', path, signal, opts, async (init, endpoint) => {
|
|
65
|
+
const response = await fetch(endpoint, {
|
|
66
|
+
...init,
|
|
67
|
+
headers: { ...init.headers, 'Content-Type': 'application/json' },
|
|
68
|
+
body: JSON.stringify(body),
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok)
|
|
71
|
+
throw await httpError(deps, response, endpoint);
|
|
72
|
+
return {
|
|
73
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
74
|
+
contentType: response.headers.get('content-type') ?? 'application/octet-stream',
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* GET 一个端点并把二进制响应体原样带回(videos/{id}/content 下载)。
|
|
80
|
+
* 端点需要 key:下载 URL 带 key 访问,故与 POST 共用鉴权与超时。
|
|
81
|
+
*/
|
|
82
|
+
export async function kenariGetBinary(deps, path, signal, opts) {
|
|
83
|
+
return kenariRequest(deps, 'GET', path, signal, opts, async (init, endpoint) => {
|
|
84
|
+
const response = await fetch(endpoint, init);
|
|
85
|
+
if (!response.ok)
|
|
86
|
+
throw await httpError(deps, response, endpoint);
|
|
87
|
+
return {
|
|
88
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
89
|
+
contentType: response.headers.get('content-type') ?? 'application/octet-stream',
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* POST multipart/form-data(images/edits、audio/transcriptions)并解析 JSON 响应。
|
|
95
|
+
* fields 里的二进制值以 Uint8Array 出现,其余转为字符串。
|
|
96
|
+
*/
|
|
97
|
+
export async function kenariPostMultipart(deps, path, fields, signal, opts) {
|
|
98
|
+
return kenariRequest(deps, 'POST', path, signal, opts, async (init, endpoint) => {
|
|
99
|
+
const form = new FormData();
|
|
100
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
101
|
+
if (typeof value === 'string')
|
|
102
|
+
form.append(name, value);
|
|
103
|
+
else
|
|
104
|
+
form.append(name, new Blob([new Uint8Array(value)]), name);
|
|
105
|
+
}
|
|
106
|
+
// fetch 为 FormData 自动补 Content-Type(含 boundary),只透传鉴权头
|
|
107
|
+
const response = await fetch(endpoint, { method: 'POST', headers: { Authorization: init.headers.Authorization }, body: form, signal: init.signal });
|
|
108
|
+
return finishJson(deps, response, endpoint);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 调一次 Kenari MCP server 的工具(balance/usage 无 REST 端点,公开 MCP 是唯一程序化入口)。
|
|
113
|
+
* 单次 JSON-RPC,无会话(服务端是无状态模式);isError 结果转 WEB_KENARI_HTTP。
|
|
114
|
+
*/
|
|
115
|
+
export async function kenariMcpCall(deps, tool, args = {}, signal) {
|
|
116
|
+
const body = {
|
|
117
|
+
jsonrpc: '2.0',
|
|
118
|
+
id: Date.now(),
|
|
119
|
+
method: 'tools/call',
|
|
120
|
+
params: { name: tool, arguments: args },
|
|
121
|
+
};
|
|
122
|
+
return kenariRequest(deps, 'POST', MCP_ENDPOINT, signal, undefined, async (init) => {
|
|
123
|
+
const response = await fetch(MCP_ENDPOINT, {
|
|
124
|
+
...init,
|
|
125
|
+
headers: {
|
|
126
|
+
...init.headers,
|
|
127
|
+
Accept: 'application/json, text/event-stream',
|
|
128
|
+
'Content-Type': 'application/json',
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(body),
|
|
131
|
+
});
|
|
132
|
+
const payload = await finishJson(deps, response, MCP_ENDPOINT);
|
|
133
|
+
if (payload.error !== undefined) {
|
|
134
|
+
throw kenariWebError('WEB_KENARI_HTTP', `kenari: ${payload.error.message ?? 'MCP 调用失败'}`);
|
|
135
|
+
}
|
|
136
|
+
const text = (payload.result?.content ?? [])
|
|
137
|
+
.filter((block) => block.type === 'text')
|
|
138
|
+
.map((block) => block.text ?? '')
|
|
139
|
+
.join('\n');
|
|
140
|
+
if (payload.result?.isError === true) {
|
|
141
|
+
throw kenariWebError('WEB_KENARI_HTTP', `kenari: ${text || 'MCP 工具返回错误'}`);
|
|
142
|
+
}
|
|
143
|
+
return text;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/** 组装鉴权与超时的公共骨架;低层各方法只差请求体与响应解析。 */
|
|
147
|
+
async function kenariRequest(deps, method, pathOrUrl, signal, opts, run) {
|
|
148
|
+
const { config } = deps;
|
|
149
|
+
// MCP 与站点端点走绝对 URL;REST 端点按 [OI] 线形状补 /v1
|
|
150
|
+
const endpoint = /^https?:\/\//i.test(pathOrUrl)
|
|
151
|
+
? pathOrUrl
|
|
152
|
+
: restEndpoint(config.baseURL, pathOrUrl);
|
|
153
|
+
if (!URL.canParse(endpoint)) {
|
|
154
|
+
throw kenariWebError('WEB_KENARI_NOT_CONFIGURED', `kenari: baseURL 无法解析:${config.baseURL ?? ''}`);
|
|
155
|
+
}
|
|
156
|
+
// 公开站点端点不要 key:没有 key 的部署也要能读到套餐表这类数据
|
|
157
|
+
const needsKey = opts?.auth ?? true;
|
|
158
|
+
const apiKey = needsKey ? await deps.resolveApiKey() : undefined;
|
|
159
|
+
if (needsKey && (apiKey === undefined || apiKey.length === 0)) {
|
|
160
|
+
throw kenariWebError('WEB_KENARI_NO_KEY', `kenari: 凭据 ${config.apiKeyEnv ?? 'KENARI_API_KEY'} 未配置`);
|
|
161
|
+
}
|
|
162
|
+
const timeoutMs = opts?.timeoutMs ?? config.timeoutMs ?? 30_000;
|
|
163
|
+
const maxRetries = opts?.retries ?? config.maxRetries ?? 2;
|
|
164
|
+
const retryTimeouts = opts?.retryTimeouts ?? true;
|
|
165
|
+
let lastError;
|
|
166
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
167
|
+
// 每次尝试独立超时:AbortSignal.any 组合外部取消与本次定时
|
|
168
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
169
|
+
const composed = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
170
|
+
const init = {
|
|
171
|
+
method,
|
|
172
|
+
headers: apiKey === undefined ? {} : { Authorization: `Bearer ${apiKey}` },
|
|
173
|
+
signal: composed,
|
|
174
|
+
};
|
|
175
|
+
try {
|
|
176
|
+
return await run(init, endpoint);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
// 已包装的 WebError 上抛;标记可重试的 HTTP 状态(429/408/5xx)按退避再试
|
|
180
|
+
if (err instanceof Error && err.name === 'WebError' && 'code' in err) {
|
|
181
|
+
const retryable = err.retryable === true;
|
|
182
|
+
if (retryable && attempt < maxRetries) {
|
|
183
|
+
lastError = err;
|
|
184
|
+
const delayMs = err.retryDelayMs;
|
|
185
|
+
await sleep(delayMs ?? 500 * 2 ** attempt);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
// 外部取消(signal)不该被重试吞掉
|
|
191
|
+
if (signal?.aborted) {
|
|
192
|
+
throw kenariWebError('WEB_CANCELLED', 'kenari: 请求已取消', err);
|
|
193
|
+
}
|
|
194
|
+
// 尝试超时且调用方声明不可重试(生成类)→ 立即上抛,避免重复扣费
|
|
195
|
+
if (isTimeoutError(err) && !retryTimeouts) {
|
|
196
|
+
throw kenariWebError('WEB_KENARI_TIMEOUT', `kenari: 请求超时(${timeoutMs}ms),生成类调用不自动重试以免重复计费`, err);
|
|
197
|
+
}
|
|
198
|
+
lastError = err;
|
|
199
|
+
if (attempt < maxRetries) {
|
|
200
|
+
await sleep(500 * 2 ** attempt);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
throw kenariWebError('WEB_KENARI_NETWORK', `kenari: 网络请求失败:${String(lastError)}`, lastError);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// 循环正常退出必然经过 throw;此处只为类型收敛
|
|
207
|
+
throw kenariWebError('WEB_KENARI_NETWORK', 'kenari: 请求失败', lastError);
|
|
208
|
+
}
|
|
209
|
+
/** [OI] 线 REST 端点拼装:用户配根域时补 /v1,配错形状报 405 的坑在这里消解。 */
|
|
210
|
+
function restEndpoint(baseURL, path) {
|
|
211
|
+
const base = baseURL ?? 'https://kenari.id';
|
|
212
|
+
const baseTrimmed = base.replace(/\/+$/, '');
|
|
213
|
+
return baseTrimmed.endsWith('/v1') ? `${baseTrimmed}${path}` : `${baseTrimmed}/v1${path}`;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 站点端点拼装:baseURL 去掉 `/v1` 后再接路径。
|
|
217
|
+
* 用户把 baseURL 配成 `https://kenari.id/v1`([OI] 线的标准形状),
|
|
218
|
+
* 而站点路由在裸域下,所以这里必须反向归一。
|
|
219
|
+
*/
|
|
220
|
+
export function siteEndpoint(baseURL, path) {
|
|
221
|
+
const base = (baseURL ?? 'https://kenari.id').replace(/\/+$/, '');
|
|
222
|
+
const bare = base.endsWith('/v1') ? base.slice(0, -3) : base;
|
|
223
|
+
return `${bare}${path}`;
|
|
224
|
+
}
|
|
225
|
+
/** 2xx → 解析 JSON;否则按错误信封抛 WEB_KENARI_HTTP。 */
|
|
226
|
+
async function finishJson(deps, response, endpoint) {
|
|
227
|
+
if (!response.ok)
|
|
228
|
+
throw await httpError(deps, response, endpoint);
|
|
229
|
+
return (await response.json());
|
|
230
|
+
}
|
|
231
|
+
/** 非 2xx → 解析 Kenari 错误信封成 WebError(消息不含 key);429/408/5xx 标记可重试。 */
|
|
232
|
+
async function httpError(deps, response, endpoint) {
|
|
233
|
+
const bodyText = await response.text().catch(() => '');
|
|
234
|
+
const message = extractKenariErrorMessage(bodyText, response.status);
|
|
235
|
+
const err = kenariWebError('WEB_KENARI_HTTP', `kenari: ${message}`, undefined);
|
|
236
|
+
// 记录端点便于诊断(不含 key)
|
|
237
|
+
deps.logger?.warn(`kenari: ${endpoint} → HTTP ${response.status}`);
|
|
238
|
+
if (isRetryableStatus(response.status)) {
|
|
239
|
+
err.retryable = true;
|
|
240
|
+
const ra = response.headers.get('retry-after');
|
|
241
|
+
if (ra !== null && Number.isFinite(Number(ra))) {
|
|
242
|
+
err.retryDelayMs = Number(ra) * 1000;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return err;
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=http.js.map
|
package/lib/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGvE,4EAA4E;AAC5E,MAAM,YAAY,GAAG,uBAAuB,CAAA;AA8C5C,0CAA0C;AAC1C,SAAS,iBAAiB,CAAC,MAAc;IACvC,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG,CAAA;AAC1D,CAAC;AAED,qDAAqD;AACrD,SAAS,cAAc,CAAC,GAAY;IAClC,OAAO,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,CAAA;AAC5D,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAoB,EACpB,IAAY,EACZ,IAA6B,EAC7B,MAAoB,EACpB,IAA2B;IAE3B,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAC9E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YACrC,GAAG,IAAI;YACP,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAChE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAA;QACF,OAAO,UAAU,CAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAoB,EACpB,IAAY,EACZ,KAA8B,EAC9B,MAAoB,EACpB,IAA2B;IAE3B,MAAM,EAAE,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAA;IACjF,OAAO,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QACvF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC5C,OAAO,UAAU,CAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAoB,EACpB,GAAW,EACX,MAAoB,EACpB,IAA2B;IAE3B,OAAO,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChG,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC5C,OAAO,UAAU,CAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAoB,EACpB,IAAY,EACZ,IAA6B,EAC7B,MAAoB,EACpB,IAA2B;IAE3B,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAC9E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YACrC,GAAG,IAAI;YACP,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAChE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACjE,OAAO;YACL,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YACnD,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,0BAA0B;SAChF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAoB,EACpB,IAAY,EACZ,MAAoB,EACpB,IAA2B;IAE3B,OAAO,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAC7E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC5C,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACjE,OAAO;YACL,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YACnD,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,0BAA0B;SAChF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAoB,EACpB,IAAY,EACZ,MAA2C,EAC3C,MAAoB,EACpB,IAA2B;IAE3B,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAC9E,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAA;QAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;;gBAClD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;QACjE,CAAC;QACD,uDAAuD;QACvD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QACnJ,OAAO,UAAU,CAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAoB,EACpB,IAAY,EACZ,OAAgC,EAAE,EAClC,MAAoB;IAEpB,MAAM,IAAI,GAAG;QACX,OAAO,EAAE,KAAK;QACd,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;QACd,MAAM,EAAE,YAAY;QACpB,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;KACxC,CAAA;IACD,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACjF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,YAAY,EAAE;YACzC,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,GAAG,IAAI,CAAC,OAAO;gBACf,MAAM,EAAE,qCAAqC;gBAC7C,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAA;QACF,MAAM,OAAO,GAAG,MAAM,UAAU,CAC9B,IAAI,EAAE,QAAQ,EAAE,YAAY,CAC7B,CAAA;QACD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,cAAc,CAAC,iBAAiB,EAAE,WAAW,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,UAAU,EAAE,CAAC,CAAA;QAC3F,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,CAAC;aACzC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;aACxC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;aAChC,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;YACrC,MAAM,cAAc,CAAC,iBAAiB,EAAE,WAAW,IAAI,IAAI,YAAY,EAAE,CAAC,CAAA;QAC5E,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,oCAAoC;AACpC,KAAK,UAAU,aAAa,CAC1B,IAAoB,EACpB,MAAsB,EACtB,SAAiB,EACjB,MAA+B,EAC/B,IAAsC,EACtC,GAA8F;IAE9F,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACvB,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC;QAC9C,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IAE3C,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,MAAM,cAAc,CAAC,2BAA2B,EAAE,wBAAwB,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAA;IACnG,CAAC;IAED,sCAAsC;IACtC,MAAM,QAAQ,GAAG,IAAI,EAAE,IAAI,IAAI,IAAI,CAAA;IACnC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAChE,IAAI,QAAQ,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QAC9D,MAAM,cAAc,CAAC,mBAAmB,EAAE,cAAc,MAAM,CAAC,SAAS,IAAI,gBAAgB,MAAM,CAAC,CAAA;IACrG,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAA;IAC/D,MAAM,UAAU,GAAG,IAAI,EAAE,OAAO,IAAI,MAAM,CAAC,UAAU,IAAI,CAAC,CAAA;IAC1D,MAAM,aAAa,GAAG,IAAI,EAAE,aAAa,IAAI,IAAI,CAAA;IACjD,IAAI,SAAkB,CAAA;IAEtB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,uCAAuC;QACvC,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAA;QAClF,MAAM,IAAI,GAAG;YACX,MAAM;YACN,OAAO,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;YAC1E,MAAM,EAAE,QAAQ;SACoC,CAAA;QACtD,IAAI,CAAC;YACH,OAAO,MAAM,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,oDAAoD;YACpD,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;gBACrE,MAAM,SAAS,GAAI,GAA8C,CAAC,SAAS,KAAK,IAAI,CAAA;gBACpF,IAAI,SAAS,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;oBACtC,SAAS,GAAG,GAAG,CAAA;oBACf,MAAM,OAAO,GAAI,GAAgD,CAAC,YAAY,CAAA;oBAC9E,MAAM,KAAK,CAAC,OAAO,IAAI,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,CAAA;oBAC1C,SAAQ;gBACV,CAAC;gBACD,MAAM,GAAG,CAAA;YACX,CAAC;YACD,sBAAsB;YACtB,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,cAAc,CAAC,eAAe,EAAE,eAAe,EAAE,GAAG,CAAC,CAAA;YAC7D,CAAC;YACD,mCAAmC;YACnC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC1C,MAAM,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,SAAS,sBAAsB,EAAE,GAAG,CAAC,CAAA;YAClG,CAAC;YACD,SAAS,GAAG,GAAG,CAAA;YACf,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBACzB,MAAM,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,CAAA;gBAC/B,SAAQ;YACV,CAAC;YACD,MAAM,cAAc,CAAC,oBAAoB,EAAE,kBAAkB,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QAC9F,CAAC;IACH,CAAC;IACD,4BAA4B;IAC5B,MAAM,cAAc,CAAC,oBAAoB,EAAE,cAAc,EAAE,SAAS,CAAC,CAAA;AACvE,CAAC;AAED,sDAAsD;AACtD,SAAS,YAAY,CAAC,OAA2B,EAAE,IAAY;IAC7D,MAAM,IAAI,GAAG,OAAO,IAAI,mBAAmB,CAAA;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC5C,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,MAAM,IAAI,EAAE,CAAA;AAC3F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAA2B,EAAE,IAAY;IACpE,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,mBAAmB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC5D,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAA;AACzB,CAAC;AAED,8CAA8C;AAC9C,KAAK,UAAU,UAAU,CAAI,IAAoB,EAAE,QAAkB,EAAE,QAAgB;IACrF,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,MAAM,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;IACjE,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAA;AACrC,CAAC;AAED,oEAAoE;AACpE,KAAK,UAAU,SAAS,CAAC,IAAoB,EAAE,QAAkB,EAAE,QAAgB;IACjF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACtD,MAAM,OAAO,GAAG,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IACpE,MAAM,GAAG,GAAG,cAAc,CAAC,iBAAiB,EAAE,WAAW,OAAO,EAAE,EAAE,SAAS,CAAC,CAAA;IAC9E,mBAAmB;IACnB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,QAAQ,WAAW,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAClE,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,GAA8C,CAAC,SAAS,GAAG,IAAI,CAAA;QAChE,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;QAC9C,IAAI,EAAE,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC9C,GAAgD,CAAC,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;QACpF,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|