dsh-codearts 1.1.1 → 1.1.4
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/CHANGELOG.md +48 -0
- package/INSTALL.en.md +24 -0
- package/INSTALL.md +22 -0
- package/lib/bin.mjs +2 -2
- package/lib/client/index.js +349 -342
- package/lib/estimate-BePoD-Np.mjs +113 -0
- package/lib/index.d.mts +762 -1
- package/lib/index.mjs +121 -7
- package/lib/models-CgKCibpk.mjs +377 -0
- package/lib/openai-completions-Cn5ZQvOO.mjs +19392 -0
- package/lib/rolldown-runtime-B4iAMlE-.mjs +35 -0
- package/lib/{usage-zgF0QuQ4.mjs → usage-B0NfG8vZ.mjs} +49 -20
- package/package.json +1 -2
package/lib/index.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { A as filterEnabledModels, C as rateLimitFrame, D as FALLBACK_CODEARTS_MODELS, E as CodeArtsCatalog, F as
|
|
1
|
+
import { A as filterEnabledModels, C as rateLimitFrame, D as FALLBACK_CODEARTS_MODELS, E as CodeArtsCatalog, F as MANUAL_ACCOUNT_ID, I as codeartsAccountsPath, L as codeartsAuthPath, M as isBenefitModel, N as CodeArtsAccountStore, O as clearBenefitBlocked, P as CodeArtsCredentialStore, R as startLogin, S as prepareChatBody, T as upstreamErrorMessage, _ as isQueueableRate, a as SNAP_HOST, b as maxTokensCeilingFor, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isParamInvalidFrame, h as isConcurrencyCap, i as refreshCatalog, j as isBenefitBlocked, k as creditRatioLabel, l as clearQueueStatus, m as fetchWelfareCampaigns, n as codeartsUsagePath, o as canonicalModel, p as fetchQueueStatus, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as degradeTokenCeiling, v as isTpmLimit, w as signedGet, x as pickDailyWelfareCampaign, y as isWelfareClaimedStatus } from "./usage-B0NfG8vZ.mjs";
|
|
2
|
+
import { i as lazyApi, r as createProvider } from "./models-CgKCibpk.mjs";
|
|
2
3
|
import z from "@deepseek-ai/schemastery";
|
|
3
4
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
5
|
import { createServer } from "node:http";
|
|
5
|
-
import { createProvider } from "@earendil-works/pi-ai";
|
|
6
|
-
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
7
6
|
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
8
7
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
9
8
|
import * as settingsModule from "@deepseek-ai/dsh-settings";
|
|
@@ -106,6 +105,22 @@ var CodeArtsAccountPool = class {
|
|
|
106
105
|
account.disabled = true;
|
|
107
106
|
account.lastError = reason;
|
|
108
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* 用户重新登录(凭据已换新)后调用:把账号从禁用/冷却里捞出来。
|
|
110
|
+
*
|
|
111
|
+
* `sync()` 只对齐成员、特意保留运行时状态(account-pool 不读凭据文档),所以
|
|
112
|
+
* 「同一账号重新登录」不会自动清掉 disabled —— 以前只能靠重启(见 issue #1)。
|
|
113
|
+
* 登录成功的语义就是「这个号现在该能用了」,由登录路径显式调这里复位。
|
|
114
|
+
*/
|
|
115
|
+
revive(id) {
|
|
116
|
+
const account = this.accounts.get(id);
|
|
117
|
+
if (account === void 0) return;
|
|
118
|
+
account.disabled = false;
|
|
119
|
+
account.coolUntil = 0;
|
|
120
|
+
account.cappedUntil = 0;
|
|
121
|
+
account.errorCount = 0;
|
|
122
|
+
account.lastError = void 0;
|
|
123
|
+
}
|
|
109
124
|
/** 上游 5xx / 软限流:短时冷却。 */
|
|
110
125
|
cooldown(id, ms, reason) {
|
|
111
126
|
const account = this.accounts.get(id);
|
|
@@ -529,6 +544,9 @@ var CodeArtsCreditCache = class {
|
|
|
529
544
|
}
|
|
530
545
|
};
|
|
531
546
|
//#endregion
|
|
547
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.lazy.js
|
|
548
|
+
const openAICompletionsApi = () => lazyApi(() => import("./openai-completions-Cn5ZQvOO.mjs"));
|
|
549
|
+
//#endregion
|
|
532
550
|
//#region src/adapter.ts
|
|
533
551
|
/**
|
|
534
552
|
* The `codearts` pi-ai provider.
|
|
@@ -706,6 +724,56 @@ var CodeArtsPiAiAdapter = class extends PiAiAdapter {
|
|
|
706
724
|
}
|
|
707
725
|
};
|
|
708
726
|
//#endregion
|
|
727
|
+
//#region src/pi-ai-compat.ts
|
|
728
|
+
/**
|
|
729
|
+
* `@earendil-works/pi-ai` 运行时自检。
|
|
730
|
+
*
|
|
731
|
+
* 本插件编译时对齐的是 1.3.0,而宿主(`dsh-llm-pi-ai` 0.2.1-rc.2 起)会先把系统提示
|
|
732
|
+
* 折叠成一条 `role:"system"`、`content` 为**字符串**的消息,再交给 provider。1.3.0 之前
|
|
733
|
+
* 的 `estimateMessageTokens` 没有 system 分支:字符串内容被按字符遍历,每个"块"是单个
|
|
734
|
+
* 字符,`block.name` 为 undefined,于是每条消息都死在
|
|
735
|
+
* `Cannot read properties of undefined (reading 'length')` 上 —— 而且是在出网之前,
|
|
736
|
+
* 用户只看得到一句「本轮运行失败」。
|
|
737
|
+
*
|
|
738
|
+
* 什么时候会拿到旧的那份:profile 顶层被别的插件 hoist 了一个低版本,而本插件的依赖
|
|
739
|
+
* 没落地到自己的 `node_modules`。1.1.4 起 pi-ai 直接打包进 `lib/`(不再经 npm 解析),
|
|
740
|
+
* 版本漂移在结构上不可能了;这里仍探测一次,把「产物损坏/不兼容」明说,而不是让
|
|
741
|
+
* 每条消息各自崩在一个看不懂的 TypeError 上。
|
|
742
|
+
*
|
|
743
|
+
* 探测用能力而不是版本号:不依赖 package.json 能不能读到,也对未来的版本漂移免疫。
|
|
744
|
+
*
|
|
745
|
+
* @module dsh-codearts/pi-ai-compat
|
|
746
|
+
*/
|
|
747
|
+
/** 折叠后的系统消息要能被估token;从这一版起才支持。 */
|
|
748
|
+
const MIN_PI_AI_VERSION = "1.3.0";
|
|
749
|
+
let probe;
|
|
750
|
+
/** 不兼容时的说明;兼容时 undefined。结果只算一次(模块加载后版本不会再变)。 */
|
|
751
|
+
function probePiAiCompat() {
|
|
752
|
+
probe ??= runProbe();
|
|
753
|
+
return probe;
|
|
754
|
+
}
|
|
755
|
+
async function runProbe() {
|
|
756
|
+
const hint = `需要 @earendil-works/pi-ai >= ${MIN_PI_AI_VERSION} 的行为;1.1.4 起它已打进插件产物,探测失败说明产物损坏或被改动,请重装 dsh-codearts`;
|
|
757
|
+
let estimate;
|
|
758
|
+
try {
|
|
759
|
+
({estimateMessageTokens: estimate} = await import("./estimate-BePoD-Np.mjs").then((n) => n.n));
|
|
760
|
+
} catch (error) {
|
|
761
|
+
return `pi-ai 不兼容:读不到 @earendil-works/pi-ai/utils/estimate(${messageOf(error)})。${hint}`;
|
|
762
|
+
}
|
|
763
|
+
if (typeof estimate !== "function") return `pi-ai 不兼容:这份 @earendil-works/pi-ai 没有 estimateMessageTokens。${hint}`;
|
|
764
|
+
try {
|
|
765
|
+
estimate({
|
|
766
|
+
role: "system",
|
|
767
|
+
content: "probe"
|
|
768
|
+
});
|
|
769
|
+
} catch (error) {
|
|
770
|
+
return `pi-ai 不兼容:这份 @earendil-works/pi-ai 处理不了宿主折叠出的 system 消息(${messageOf(error)}),每条请求都会在出网前失败。${hint}`;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function messageOf(error) {
|
|
774
|
+
return error instanceof Error ? error.message : String(error);
|
|
775
|
+
}
|
|
776
|
+
//#endregion
|
|
709
777
|
//#region src/loopback.ts
|
|
710
778
|
/**
|
|
711
779
|
* Shared loopback gates for the plugin's local HTTP surfaces: the loopback
|
|
@@ -816,6 +884,40 @@ function queuedMessage(model, gate) {
|
|
|
816
884
|
const upstream = gate.message.trim();
|
|
817
885
|
return `${upstream === "" ? `上游排队中:${model} 第 ${gate.position} 位(共 ${gate.length} 条)` : upstream} 插件没有一直等下去,请稍后重试。`;
|
|
818
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* 「一个账号都选不出来」时的报错:把真实原因说清 —— 是禁用(附最后错误)、冷却
|
|
889
|
+
* (附剩余秒数),还是本地槽位全满。以前三种都回同一句「没有可用账号」,禁用那
|
|
890
|
+
* 种还把真因(比如 billion-context 本地拒签的 403)整个吞掉(issue #1)。
|
|
891
|
+
*
|
|
892
|
+
* 全部被禁用时回 401(宿主只自动重试 429,401 一次就把「去重新登录」说清);
|
|
893
|
+
* 只要还有会自己恢复的(冷却/槽位满)就回 429,宿主重试有概率直接救回来。
|
|
894
|
+
*/
|
|
895
|
+
function noAccountError(accounts) {
|
|
896
|
+
const now = Date.now();
|
|
897
|
+
const disabled = accounts.filter((a) => a.disabled);
|
|
898
|
+
const cooling = accounts.filter((a) => !a.disabled && now < a.coolUntil);
|
|
899
|
+
const busy = accounts.filter((a) => !a.disabled && now >= a.coolUntil);
|
|
900
|
+
const parts = [];
|
|
901
|
+
if (disabled.length > 0) {
|
|
902
|
+
const reasons = disabled.map((a) => a.lastError === void 0 ? a.id : `${a.id}(${a.lastError.slice(0, 120)})`).join("、");
|
|
903
|
+
parts.push(`${disabled.length} 个被禁用:${reasons}——请到设置卡片重新登录(若在用 billion-context,先检查它的 resign 配置)`);
|
|
904
|
+
}
|
|
905
|
+
if (cooling.length > 0) {
|
|
906
|
+
const secs = Math.max(1, Math.ceil((Math.min(...cooling.map((a) => a.coolUntil)) - now) / 1e3));
|
|
907
|
+
parts.push(`${cooling.length} 个在短时冷却中(约 ${secs} 秒后恢复)`);
|
|
908
|
+
}
|
|
909
|
+
if (busy.length > 0) parts.push(`${busy.length} 个的本地并发槽位已满(每账号最多 ${busy[0]?.maxConcurrent ?? 2} 个在飞请求),等几秒再发`);
|
|
910
|
+
const message = `codearts 当前没有可用账号:${parts.join(";")}`;
|
|
911
|
+
return disabled.length === accounts.length ? {
|
|
912
|
+
status: 401,
|
|
913
|
+
kind: "session_dead",
|
|
914
|
+
message
|
|
915
|
+
} : {
|
|
916
|
+
status: 429,
|
|
917
|
+
kind: "soft_rate",
|
|
918
|
+
message
|
|
919
|
+
};
|
|
920
|
+
}
|
|
819
921
|
function readBody$1(req) {
|
|
820
922
|
return new Promise((resolve, reject) => {
|
|
821
923
|
const chunks = [];
|
|
@@ -1199,7 +1301,8 @@ function createCodeArtsShim(options) {
|
|
|
1199
1301
|
}
|
|
1200
1302
|
}
|
|
1201
1303
|
if (lastKind === void 0) {
|
|
1202
|
-
|
|
1304
|
+
const none = noAccountError(pool.list());
|
|
1305
|
+
writeOpenAIError(res, none.status, none.kind, none.message);
|
|
1203
1306
|
return;
|
|
1204
1307
|
}
|
|
1205
1308
|
const explanation = upstreamErrorMessage(lastMessage, lastStatus);
|
|
@@ -1483,11 +1586,14 @@ async function buildStatus(deps) {
|
|
|
1483
1586
|
const accounts = await deps.store.list();
|
|
1484
1587
|
const cred = await deps.store.current();
|
|
1485
1588
|
const usage = usageOf(deps);
|
|
1589
|
+
const piAi = await probePiAiCompat();
|
|
1590
|
+
const compat = piAi === void 0 ? {} : { piAiError: piAi };
|
|
1486
1591
|
if (accounts.length === 0 || cred === void 0) return {
|
|
1487
1592
|
status: "signed-out",
|
|
1488
1593
|
models,
|
|
1489
1594
|
accounts,
|
|
1490
|
-
usage
|
|
1595
|
+
usage,
|
|
1596
|
+
...compat
|
|
1491
1597
|
};
|
|
1492
1598
|
const usable = accounts.some(accountUsable);
|
|
1493
1599
|
const identity = {
|
|
@@ -1499,11 +1605,13 @@ async function buildStatus(deps) {
|
|
|
1499
1605
|
};
|
|
1500
1606
|
if (!usable) return {
|
|
1501
1607
|
status: "expired",
|
|
1502
|
-
...identity
|
|
1608
|
+
...identity,
|
|
1609
|
+
...compat
|
|
1503
1610
|
};
|
|
1504
1611
|
return {
|
|
1505
1612
|
status: "signed-in",
|
|
1506
1613
|
...identity,
|
|
1614
|
+
...compat,
|
|
1507
1615
|
...await creditsOf(deps)
|
|
1508
1616
|
};
|
|
1509
1617
|
}
|
|
@@ -1613,6 +1721,7 @@ function tokenPostHandler(deps) {
|
|
|
1613
1721
|
}
|
|
1614
1722
|
const label = typeof body?.label === "string" && body.label.trim() !== "" ? body.label.trim() : void 0;
|
|
1615
1723
|
await deps.store.set(token, label);
|
|
1724
|
+
deps.reviveAccount?.(MANUAL_ACCOUNT_ID);
|
|
1616
1725
|
json(res, 200, await buildStatus(deps));
|
|
1617
1726
|
} catch (error) {
|
|
1618
1727
|
json(res, 500, { error: safeMessage(error) });
|
|
@@ -1882,6 +1991,8 @@ function loginStartHandler(deps, active, recent) {
|
|
|
1882
1991
|
const identity = await probeIdentity(token);
|
|
1883
1992
|
const id = await deps.store.upsertFromOAuth(token, void 0, identity ?? void 0);
|
|
1884
1993
|
if (identity !== null && id !== identity.userId) await deps.store.adoptIdentity(id, identity);
|
|
1994
|
+
deps.reviveAccount?.(id);
|
|
1995
|
+
if (identity !== null && identity.userId !== id) deps.reviveAccount?.(identity.userId);
|
|
1885
1996
|
recordLoginOutcome(recent, session.url, "done");
|
|
1886
1997
|
} catch {
|
|
1887
1998
|
recordLoginOutcome(recent, session.url, "error");
|
|
@@ -2084,6 +2195,7 @@ function apply(ctx, rawConfig) {
|
|
|
2084
2195
|
enabledModels,
|
|
2085
2196
|
setEnabledModels,
|
|
2086
2197
|
pool: () => pool.list(),
|
|
2198
|
+
reviveAccount: (id) => pool.revive(id),
|
|
2087
2199
|
usage: () => usage.snapshot(),
|
|
2088
2200
|
queue: () => queue.snapshot(),
|
|
2089
2201
|
recentRequests: () => recentRequestShapes(),
|
|
@@ -2200,9 +2312,11 @@ function apply(ctx, rawConfig) {
|
|
|
2200
2312
|
logger: ctx.logger
|
|
2201
2313
|
});
|
|
2202
2314
|
usage.load().catch(() => {});
|
|
2203
|
-
shim.ready.then(() => {
|
|
2315
|
+
shim.ready.then(async () => {
|
|
2204
2316
|
if (stopped) return;
|
|
2205
2317
|
discover();
|
|
2318
|
+
const incompatible = await probePiAiCompat();
|
|
2319
|
+
if (incompatible !== void 0) ctx.logger.error(`dsh-codearts: ${incompatible}`);
|
|
2206
2320
|
const codearts = createCodeArtsAdapter({
|
|
2207
2321
|
shim,
|
|
2208
2322
|
catalog,
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/utils/event-stream.js
|
|
2
|
+
var FifoQueue = class {
|
|
3
|
+
incoming = [];
|
|
4
|
+
outgoing = [];
|
|
5
|
+
get length() {
|
|
6
|
+
return this.incoming.length + this.outgoing.length;
|
|
7
|
+
}
|
|
8
|
+
enqueue(value) {
|
|
9
|
+
this.incoming.push(value);
|
|
10
|
+
}
|
|
11
|
+
dequeue() {
|
|
12
|
+
if (this.outgoing.length === 0) while (this.incoming.length > 0) this.outgoing.push(this.incoming.pop());
|
|
13
|
+
return this.outgoing.pop();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var EventStream = class {
|
|
17
|
+
queue = new FifoQueue();
|
|
18
|
+
waiting = new FifoQueue();
|
|
19
|
+
done = false;
|
|
20
|
+
finalResultPromise;
|
|
21
|
+
resolveFinalResult;
|
|
22
|
+
isComplete;
|
|
23
|
+
extractResult;
|
|
24
|
+
constructor(isComplete, extractResult) {
|
|
25
|
+
this.isComplete = isComplete;
|
|
26
|
+
this.extractResult = extractResult;
|
|
27
|
+
this.finalResultPromise = new Promise((resolve) => {
|
|
28
|
+
this.resolveFinalResult = resolve;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
push(event) {
|
|
32
|
+
if (this.done) return;
|
|
33
|
+
if (this.isComplete(event)) {
|
|
34
|
+
this.done = true;
|
|
35
|
+
this.resolveFinalResult(this.extractResult(event));
|
|
36
|
+
}
|
|
37
|
+
const waiter = this.waiting.dequeue();
|
|
38
|
+
if (waiter) waiter({
|
|
39
|
+
value: event,
|
|
40
|
+
done: false
|
|
41
|
+
});
|
|
42
|
+
else this.queue.enqueue(event);
|
|
43
|
+
}
|
|
44
|
+
end(result) {
|
|
45
|
+
this.done = true;
|
|
46
|
+
if (result !== void 0) this.resolveFinalResult(result);
|
|
47
|
+
while (this.waiting.length > 0) this.waiting.dequeue()({
|
|
48
|
+
value: void 0,
|
|
49
|
+
done: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async *[Symbol.asyncIterator]() {
|
|
53
|
+
while (true) if (this.queue.length > 0) yield this.queue.dequeue();
|
|
54
|
+
else if (this.done) return;
|
|
55
|
+
else {
|
|
56
|
+
const result = await new Promise((resolve) => this.waiting.enqueue(resolve));
|
|
57
|
+
if (result.done) return;
|
|
58
|
+
yield result.value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
result() {
|
|
62
|
+
return this.finalResultPromise;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Event stream of one assistant response. It also times the response: the final message (`done` or `error` event, or
|
|
67
|
+
* the result passed to `end()`) gets `durationMs`, measured with a monotonic clock from the stream's creation, unless
|
|
68
|
+
* the message already has one or its `timestamp` predates the stream. A stream that forwards a response which started
|
|
69
|
+
* elsewhere, such as a deferred result fetched later, therefore leaves it untimed.
|
|
70
|
+
*/
|
|
71
|
+
var AssistantMessageEventStream = class extends EventStream {
|
|
72
|
+
#startedAt = Date.now();
|
|
73
|
+
#startedAtMonotonic = performance.now();
|
|
74
|
+
constructor() {
|
|
75
|
+
super((event) => event.type === "done" || event.type === "error", (event) => {
|
|
76
|
+
if (event.type === "done") return event.message;
|
|
77
|
+
else if (event.type === "error") return event.error;
|
|
78
|
+
throw new Error("Unexpected event type for final result");
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
push(event) {
|
|
82
|
+
if (event.type === "done") this.#time(event.message);
|
|
83
|
+
else if (event.type === "error") this.#time(event.error);
|
|
84
|
+
super.push(event);
|
|
85
|
+
}
|
|
86
|
+
end(result) {
|
|
87
|
+
if (result !== void 0) this.#time(result);
|
|
88
|
+
super.end(result);
|
|
89
|
+
}
|
|
90
|
+
#time(message) {
|
|
91
|
+
if (this.done || message.durationMs !== void 0 || message.timestamp < this.#startedAt) return;
|
|
92
|
+
message.durationMs = Math.max(0, Math.round(performance.now() - this.#startedAtMonotonic));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/api/lazy.js
|
|
97
|
+
function createSetupErrorMessage(model, error, timestamp) {
|
|
98
|
+
return {
|
|
99
|
+
role: "assistant",
|
|
100
|
+
content: [],
|
|
101
|
+
api: model.api,
|
|
102
|
+
provider: model.provider,
|
|
103
|
+
model: model.id,
|
|
104
|
+
usage: {
|
|
105
|
+
input: 0,
|
|
106
|
+
output: 0,
|
|
107
|
+
cacheRead: 0,
|
|
108
|
+
cacheWrite: 0,
|
|
109
|
+
totalTokens: 0,
|
|
110
|
+
cost: {
|
|
111
|
+
input: 0,
|
|
112
|
+
output: 0,
|
|
113
|
+
cacheRead: 0,
|
|
114
|
+
cacheWrite: 0,
|
|
115
|
+
total: 0
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
stopReason: "error",
|
|
119
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
120
|
+
timestamp
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function hasResult(source) {
|
|
124
|
+
return typeof source.result === "function";
|
|
125
|
+
}
|
|
126
|
+
async function forwardStream(target, source) {
|
|
127
|
+
for await (const event of source) target.push(event);
|
|
128
|
+
target.end(hasResult(source) ? await source.result() : void 0);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Returns a stream synchronously while running async setup (auth resolution,
|
|
132
|
+
* lazy module loading) behind it. Setup failures terminate the stream with an
|
|
133
|
+
* error event.
|
|
134
|
+
*/
|
|
135
|
+
function lazyStream(model, setup) {
|
|
136
|
+
const startedAt = Date.now();
|
|
137
|
+
const outer = new AssistantMessageEventStream();
|
|
138
|
+
setup().then((inner) => forwardStream(outer, inner)).catch((error) => {
|
|
139
|
+
const message = createSetupErrorMessage(model, error, startedAt);
|
|
140
|
+
outer.push({
|
|
141
|
+
type: "error",
|
|
142
|
+
reason: "error",
|
|
143
|
+
error: message
|
|
144
|
+
});
|
|
145
|
+
outer.end(message);
|
|
146
|
+
});
|
|
147
|
+
return outer;
|
|
148
|
+
}
|
|
149
|
+
function lazyApi(load, capabilities) {
|
|
150
|
+
const api = {
|
|
151
|
+
stream: (model, context, options) => lazyStream(model, async () => (await load()).stream(model, context, options)),
|
|
152
|
+
streamSimple: (model, context, options) => lazyStream(model, async () => (await load()).streamSimple(model, context, options))
|
|
153
|
+
};
|
|
154
|
+
if (capabilities?.fetchDeferred) api.fetchDeferred = (model, handle, options) => lazyStream(model, async () => {
|
|
155
|
+
const implementation = await load();
|
|
156
|
+
if (!implementation.fetchDeferred) throw new Error("API does not support deferred responses");
|
|
157
|
+
return implementation.fetchDeferred(model, handle, options);
|
|
158
|
+
});
|
|
159
|
+
if (capabilities?.cancelDeferred) api.cancelDeferred = async (model, handle, options) => {
|
|
160
|
+
const implementation = await load();
|
|
161
|
+
if (!implementation.cancelDeferred) throw new Error("API cannot cancel deferred responses");
|
|
162
|
+
await implementation.cancelDeferred(model, handle, options);
|
|
163
|
+
};
|
|
164
|
+
return api;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/utils/diagnostics.js
|
|
168
|
+
function formatThrownValue(value) {
|
|
169
|
+
if (value instanceof Error) return value.message || value.name;
|
|
170
|
+
if (typeof value === "string") return value;
|
|
171
|
+
return String(value);
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/utils/models-error.js
|
|
175
|
+
var ModelsError = class extends Error {
|
|
176
|
+
code;
|
|
177
|
+
constructor(code, message, options) {
|
|
178
|
+
super(withCauseDetail(message, options?.cause), options);
|
|
179
|
+
this.name = "ModelsError";
|
|
180
|
+
this.code = code;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
/** Callers surface `error.message` only, so keep the underlying reason in it. */
|
|
184
|
+
function withCauseDetail(message, cause) {
|
|
185
|
+
if (cause === void 0 || cause === null) return message;
|
|
186
|
+
const detail = formatThrownValue(cause).trim();
|
|
187
|
+
if (!detail || message.includes(detail)) return message;
|
|
188
|
+
return `${message}: ${detail}`;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/utils/model-operations.js
|
|
192
|
+
/** The type of a model. Models without `type` are chat models. */
|
|
193
|
+
function getModelType(model) {
|
|
194
|
+
return model.type ?? "chat";
|
|
195
|
+
}
|
|
196
|
+
/** Runtime-checked model type narrowing, including legacy chat models without `type`. */
|
|
197
|
+
function isModelType(model, type) {
|
|
198
|
+
return getModelType(model) === type;
|
|
199
|
+
}
|
|
200
|
+
function imageErrorResult(model, error, aborted = false) {
|
|
201
|
+
return {
|
|
202
|
+
api: model.api,
|
|
203
|
+
provider: model.provider,
|
|
204
|
+
model: model.id,
|
|
205
|
+
output: [],
|
|
206
|
+
stopReason: aborted ? "aborted" : "error",
|
|
207
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
208
|
+
timestamp: Date.now()
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function classifierErrorResult(model, error, aborted = false) {
|
|
212
|
+
return {
|
|
213
|
+
api: model.api,
|
|
214
|
+
provider: model.provider,
|
|
215
|
+
model: model.id,
|
|
216
|
+
answers: {},
|
|
217
|
+
stopReason: aborted ? "aborted" : "error",
|
|
218
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
219
|
+
timestamp: Date.now()
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region node_modules/.pnpm/@earendil-works+pi-ai@1.3.0_@aws-sdk+credential-provider-node@3.972.84_@smithy+signature-v4@5.7.4_ws@8.22.0_zod@4.6.5/node_modules/@earendil-works/pi-ai/dist/models.js
|
|
224
|
+
const KNOWN_MODEL_TYPES = {
|
|
225
|
+
chat: true,
|
|
226
|
+
image: true,
|
|
227
|
+
classifier: true
|
|
228
|
+
};
|
|
229
|
+
/** Models from stores and remote sources may have types that only newer versions know. */
|
|
230
|
+
function hasKnownModelType(model) {
|
|
231
|
+
return Object.hasOwn(KNOWN_MODEL_TYPES, getModelType(model));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Builds a provider from parts. Built-in provider factories and models.json
|
|
235
|
+
* custom providers both go through this. A single `api` streams all chat
|
|
236
|
+
* models; an `api` map dispatches on `model.api`, and a model whose api has
|
|
237
|
+
* no entry produces a stream error. One-shot operation maps dispatch on
|
|
238
|
+
* `model.api` the same way. At least one concrete implementation across
|
|
239
|
+
* `api`/`images`/`classifiers` is required; empty maps are rejected.
|
|
240
|
+
*/
|
|
241
|
+
function createProvider(input) {
|
|
242
|
+
const single = input.api && typeof input.api.stream === "function" ? input.api : void 0;
|
|
243
|
+
const byApi = single || !input.api ? void 0 : input.api;
|
|
244
|
+
const images = input.images;
|
|
245
|
+
const classifiers = input.classifiers;
|
|
246
|
+
const streams = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== void 0);
|
|
247
|
+
const imageImplementations = Object.values(images ?? {}).filter((entry) => entry !== void 0);
|
|
248
|
+
const classifierImplementations = Object.values(classifiers ?? {}).filter((entry) => entry !== void 0);
|
|
249
|
+
if (streams.length === 0 && imageImplementations.length === 0 && classifierImplementations.length === 0) throw new Error(`Provider ${input.id}: at least one of "api", "images", or "classifiers" is required.`);
|
|
250
|
+
const baselineModels = input.models;
|
|
251
|
+
let dynamicModels = [];
|
|
252
|
+
const fetchModels = input.fetchModels;
|
|
253
|
+
const currentModels = () => {
|
|
254
|
+
const merged = [...baselineModels];
|
|
255
|
+
for (const model of dynamicModels) {
|
|
256
|
+
const index = merged.findIndex((entry) => getModelType(entry) === getModelType(model) && entry.id === model.id);
|
|
257
|
+
if (index >= 0) merged[index] = model;
|
|
258
|
+
else merged.push(model);
|
|
259
|
+
}
|
|
260
|
+
return merged;
|
|
261
|
+
};
|
|
262
|
+
const apiFor = (model) => single ?? byApi?.[model.api];
|
|
263
|
+
const dispatch = (model, run) => {
|
|
264
|
+
const streams = apiFor(model);
|
|
265
|
+
if (!streams) return lazyStream(model, async () => {
|
|
266
|
+
throw new ModelsError("stream", `Provider ${input.id} has no API implementation for "${model.api}"`);
|
|
267
|
+
});
|
|
268
|
+
return run(streams);
|
|
269
|
+
};
|
|
270
|
+
const provider = {
|
|
271
|
+
id: input.id,
|
|
272
|
+
name: input.name ?? input.id,
|
|
273
|
+
baseUrl: input.baseUrl,
|
|
274
|
+
headers: input.headers,
|
|
275
|
+
auth: input.auth,
|
|
276
|
+
getModels: () => currentModels().filter((model) => isModelType(model, "chat")),
|
|
277
|
+
getAllModels: currentModels,
|
|
278
|
+
refreshModels: fetchModels ? async (context) => {
|
|
279
|
+
if (context.stored) {
|
|
280
|
+
const restored = context.stored.models.filter((model) => model.provider === input.id).map((model) => model);
|
|
281
|
+
if (!await context.publish({ update: () => {
|
|
282
|
+
dynamicModels = restored;
|
|
283
|
+
} })) return;
|
|
284
|
+
}
|
|
285
|
+
if (!context.allowNetwork || context.signal.aborted) return;
|
|
286
|
+
const fetched = await fetchModels(context);
|
|
287
|
+
if (context.signal.aborted) return;
|
|
288
|
+
const refreshed = fetched.filter(hasKnownModelType);
|
|
289
|
+
await context.publish({
|
|
290
|
+
persist: {
|
|
291
|
+
models: refreshed,
|
|
292
|
+
checkedAt: Date.now()
|
|
293
|
+
},
|
|
294
|
+
update: () => {
|
|
295
|
+
dynamicModels = refreshed;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
} : void 0,
|
|
299
|
+
filterModels: input.filterModels,
|
|
300
|
+
filterAllModels: input.filterAllModels,
|
|
301
|
+
stream: (model, context, options) => dispatch(model, (streams) => streams.stream(model, context, options)),
|
|
302
|
+
streamSimple: (model, context, options) => dispatch(model, (streams) => streams.streamSimple(model, context, options))
|
|
303
|
+
};
|
|
304
|
+
if (streams.some((entry) => entry.fetchDeferred !== void 0)) provider.fetchDeferred = (model, handle, options) => lazyStream(model, async () => {
|
|
305
|
+
const implementation = apiFor(model);
|
|
306
|
+
if (!implementation?.fetchDeferred) throw new ModelsError("provider", `Provider ${input.id} does not support deferred responses for "${model.api}"`);
|
|
307
|
+
return implementation.fetchDeferred(model, handle, options);
|
|
308
|
+
});
|
|
309
|
+
if (streams.some((entry) => entry.cancelDeferred !== void 0)) provider.cancelDeferred = async (model, handle, options) => {
|
|
310
|
+
const implementation = apiFor(model);
|
|
311
|
+
if (!implementation?.cancelDeferred) throw new ModelsError("provider", `Provider ${input.id} cannot cancel deferred responses for "${model.api}"`);
|
|
312
|
+
await implementation.cancelDeferred(model, handle, options);
|
|
313
|
+
};
|
|
314
|
+
if (images && imageImplementations.length > 0) provider.generateImages = async (model, context, options) => {
|
|
315
|
+
const implementation = images[model.api];
|
|
316
|
+
if (!implementation) return imageErrorResult(model, new ModelsError("provider", `Provider ${input.id} has no image generation implementation for "${model.api}"`));
|
|
317
|
+
return implementation.generateImages(model, context, options);
|
|
318
|
+
};
|
|
319
|
+
if (classifiers && classifierImplementations.length > 0) provider.classify = async (model, context, options) => {
|
|
320
|
+
const implementation = classifiers[model.api];
|
|
321
|
+
if (!implementation) return classifierErrorResult(model, new ModelsError("provider", `Provider ${input.id} has no classifier implementation for "${model.api}"`));
|
|
322
|
+
return implementation.classify(model, context, options);
|
|
323
|
+
};
|
|
324
|
+
return provider;
|
|
325
|
+
}
|
|
326
|
+
function calculateCost(model, usage) {
|
|
327
|
+
const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
328
|
+
let rates = model.cost;
|
|
329
|
+
let matchedThreshold = -1;
|
|
330
|
+
for (const tier of model.cost.tiers ?? []) if (inputTokens > tier.inputTokensAbove && tier.inputTokensAbove > matchedThreshold) {
|
|
331
|
+
rates = tier;
|
|
332
|
+
matchedThreshold = tier.inputTokensAbove;
|
|
333
|
+
}
|
|
334
|
+
const longWrite = usage.cacheWrite1h ?? 0;
|
|
335
|
+
const shortWrite = usage.cacheWrite - longWrite;
|
|
336
|
+
usage.cost.input = rates.input / 1e6 * usage.input;
|
|
337
|
+
usage.cost.output = rates.output / 1e6 * usage.output;
|
|
338
|
+
usage.cost.cacheRead = rates.cacheRead / 1e6 * usage.cacheRead;
|
|
339
|
+
usage.cost.cacheWrite = (rates.cacheWrite * shortWrite + rates.input * 2 * longWrite) / 1e6;
|
|
340
|
+
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
341
|
+
return usage.cost;
|
|
342
|
+
}
|
|
343
|
+
const EXTENDED_THINKING_LEVELS = [
|
|
344
|
+
"off",
|
|
345
|
+
"minimal",
|
|
346
|
+
"low",
|
|
347
|
+
"medium",
|
|
348
|
+
"high",
|
|
349
|
+
"xhigh",
|
|
350
|
+
"max"
|
|
351
|
+
];
|
|
352
|
+
function getSupportedThinkingLevels(model) {
|
|
353
|
+
if (!model.reasoning) return ["off"];
|
|
354
|
+
return EXTENDED_THINKING_LEVELS.filter((level) => {
|
|
355
|
+
const mapped = model.thinkingLevelMap?.[level];
|
|
356
|
+
if (mapped === null) return false;
|
|
357
|
+
if (level === "xhigh" || level === "max") return mapped !== void 0;
|
|
358
|
+
return true;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function clampThinkingLevel(model, level) {
|
|
362
|
+
const availableLevels = getSupportedThinkingLevels(model);
|
|
363
|
+
if (availableLevels.includes(level)) return level;
|
|
364
|
+
const requestedIndex = EXTENDED_THINKING_LEVELS.indexOf(level);
|
|
365
|
+
if (requestedIndex === -1) return availableLevels[0] ?? "off";
|
|
366
|
+
for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {
|
|
367
|
+
const candidate = EXTENDED_THINKING_LEVELS[i];
|
|
368
|
+
if (availableLevels.includes(candidate)) return candidate;
|
|
369
|
+
}
|
|
370
|
+
for (let i = requestedIndex - 1; i >= 0; i--) {
|
|
371
|
+
const candidate = EXTENDED_THINKING_LEVELS[i];
|
|
372
|
+
if (availableLevels.includes(candidate)) return candidate;
|
|
373
|
+
}
|
|
374
|
+
return availableLevels[0] ?? "off";
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
377
|
+
export { AssistantMessageEventStream as a, lazyApi as i, clampThinkingLevel as n, createProvider as r, calculateCost as t };
|