dsh-codearts 1.1.3 → 1.2.0
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 +29 -0
- package/INSTALL.en.md +24 -0
- package/INSTALL.md +22 -0
- package/lib/bin.mjs +2 -2
- package/lib/client/index.d.cts +16 -0
- package/lib/client/index.js +499 -86
- package/lib/estimate-BePoD-Np.mjs +113 -0
- package/lib/index.d.mts +759 -2
- package/lib/index.mjs +116 -15
- 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-5YK7bW-e.mjs → usage-B0NfG8vZ.mjs} +1 -12
- package/package.json +1 -4
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 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-
|
|
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";
|
|
@@ -545,6 +544,9 @@ var CodeArtsCreditCache = class {
|
|
|
545
544
|
}
|
|
546
545
|
};
|
|
547
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
|
|
548
550
|
//#region src/adapter.ts
|
|
549
551
|
/**
|
|
550
552
|
* The `codearts` pi-ai provider.
|
|
@@ -734,8 +736,8 @@ var CodeArtsPiAiAdapter = class extends PiAiAdapter {
|
|
|
734
736
|
* 用户只看得到一句「本轮运行失败」。
|
|
735
737
|
*
|
|
736
738
|
* 什么时候会拿到旧的那份:profile 顶层被别的插件 hoist 了一个低版本,而本插件的依赖
|
|
737
|
-
* 没落地到自己的 `node_modules`。1.1.
|
|
738
|
-
*
|
|
739
|
+
* 没落地到自己的 `node_modules`。1.1.4 起 pi-ai 直接打包进 `lib/`(不再经 npm 解析),
|
|
740
|
+
* 版本漂移在结构上不可能了;这里仍探测一次,把「产物损坏/不兼容」明说,而不是让
|
|
739
741
|
* 每条消息各自崩在一个看不懂的 TypeError 上。
|
|
740
742
|
*
|
|
741
743
|
* 探测用能力而不是版本号:不依赖 package.json 能不能读到,也对未来的版本漂移免疫。
|
|
@@ -751,10 +753,10 @@ function probePiAiCompat() {
|
|
|
751
753
|
return probe;
|
|
752
754
|
}
|
|
753
755
|
async function runProbe() {
|
|
754
|
-
const hint = `需要 @earendil-works/pi-ai >= ${MIN_PI_AI_VERSION}
|
|
756
|
+
const hint = `需要 @earendil-works/pi-ai >= ${MIN_PI_AI_VERSION} 的行为;1.1.4 起它已打进插件产物,探测失败说明产物损坏或被改动,请重装 dsh-codearts`;
|
|
755
757
|
let estimate;
|
|
756
758
|
try {
|
|
757
|
-
({estimateMessageTokens: estimate} = await import("
|
|
759
|
+
({estimateMessageTokens: estimate} = await import("./estimate-BePoD-Np.mjs").then((n) => n.n));
|
|
758
760
|
} catch (error) {
|
|
759
761
|
return `pi-ai 不兼容:读不到 @earendil-works/pi-ai/utils/estimate(${messageOf(error)})。${hint}`;
|
|
760
762
|
}
|
|
@@ -1402,6 +1404,11 @@ const CODEARTS_CLAIM_PATH = "/api/codearts/claim";
|
|
|
1402
1404
|
const CODEARTS_REFRESH_MODELS_PATH = "/api/codearts/refresh-models";
|
|
1403
1405
|
/** GET /api/codearts/queue — 上游排队位置(只在等开流期间有值,底部积分条旁边展示)。 */
|
|
1404
1406
|
const CODEARTS_QUEUE_PATH = "/api/codearts/queue";
|
|
1407
|
+
/**
|
|
1408
|
+
* GET/POST /api/codearts/dock — 对话底部那一行的显隐开关。
|
|
1409
|
+
* GET 给当前值与 writable,POST 收 `{credits?, queue?}` 写进 settings。
|
|
1410
|
+
*/
|
|
1411
|
+
const CODEARTS_DOCK_PATH = "/api/codearts/dock";
|
|
1405
1412
|
/** 最近出站请求的**形状**摘要(只有 role 名与计数,不含内容),排障用。 */
|
|
1406
1413
|
const CODEARTS_DEBUG_PATH = "/api/codearts/debug";
|
|
1407
1414
|
/**
|
|
@@ -1440,6 +1447,11 @@ function choicesOf(deps) {
|
|
|
1440
1447
|
};
|
|
1441
1448
|
});
|
|
1442
1449
|
}
|
|
1450
|
+
/** 开关的默认值:没有 settings provider(或老配置没这两个键)时保持原有行为 —— 都显示。 */
|
|
1451
|
+
const DEFAULT_DOCK_FLAGS = {
|
|
1452
|
+
credits: true,
|
|
1453
|
+
queue: true
|
|
1454
|
+
};
|
|
1443
1455
|
/**
|
|
1444
1456
|
* 把逐账号的领取结果汇总成一条:HTTP 层只看 ok,卡片看 results 逐号展示。
|
|
1445
1457
|
* 有一个号失败就算整批 ok=false,但别的号领到的结果照样在 results 里。
|
|
@@ -1561,11 +1573,15 @@ function usageOf(deps) {
|
|
|
1561
1573
|
*
|
|
1562
1574
|
* 积分是「锦上添花」:拉不到也绝不能把状态接口本身拖垮,所以失败一律降级成
|
|
1563
1575
|
* `creditsError` 交给卡片显示原因(超时由注入方在自己的闭包里保证)。
|
|
1576
|
+
*
|
|
1577
|
+
* `force` 是给对话底部弹层那个「刷新积分」按钮用的:不强制的话它会命中 60 秒缓存,
|
|
1578
|
+
* 按钮点了数字不动 —— 那还不如不给这个按钮。
|
|
1564
1579
|
*/
|
|
1565
|
-
async function creditsOf(deps) {
|
|
1566
|
-
|
|
1580
|
+
async function creditsOf(deps, force) {
|
|
1581
|
+
const load = force ? deps.refreshCredits ?? deps.credits : deps.credits;
|
|
1582
|
+
if (load === void 0) return {};
|
|
1567
1583
|
try {
|
|
1568
|
-
const entries = await
|
|
1584
|
+
const entries = await load();
|
|
1569
1585
|
const primary = entries.find((entry) => entry.credits !== void 0) ?? entries[0];
|
|
1570
1586
|
return {
|
|
1571
1587
|
...primary?.credits === void 0 ? {} : { credits: primary.credits },
|
|
@@ -1576,7 +1592,7 @@ async function creditsOf(deps) {
|
|
|
1576
1592
|
return { creditsError: safeMessage(error) };
|
|
1577
1593
|
}
|
|
1578
1594
|
}
|
|
1579
|
-
async function buildStatus(deps) {
|
|
1595
|
+
async function buildStatus(deps, force = false) {
|
|
1580
1596
|
const models = deps.models().map((m) => ({
|
|
1581
1597
|
id: m.id,
|
|
1582
1598
|
name: m.name
|
|
@@ -1584,6 +1600,7 @@ async function buildStatus(deps) {
|
|
|
1584
1600
|
const accounts = await deps.store.list();
|
|
1585
1601
|
const cred = await deps.store.current();
|
|
1586
1602
|
const usage = usageOf(deps);
|
|
1603
|
+
const dock = deps.dock === void 0 ? {} : { dock: deps.dock() };
|
|
1587
1604
|
const piAi = await probePiAiCompat();
|
|
1588
1605
|
const compat = piAi === void 0 ? {} : { piAiError: piAi };
|
|
1589
1606
|
if (accounts.length === 0 || cred === void 0) return {
|
|
@@ -1591,6 +1608,7 @@ async function buildStatus(deps) {
|
|
|
1591
1608
|
models,
|
|
1592
1609
|
accounts,
|
|
1593
1610
|
usage,
|
|
1611
|
+
...dock,
|
|
1594
1612
|
...compat
|
|
1595
1613
|
};
|
|
1596
1614
|
const usable = accounts.some(accountUsable);
|
|
@@ -1599,7 +1617,8 @@ async function buildStatus(deps) {
|
|
|
1599
1617
|
...cred.storedAt === void 0 ? {} : { storedAt: cred.storedAt },
|
|
1600
1618
|
models,
|
|
1601
1619
|
accounts,
|
|
1602
|
-
usage
|
|
1620
|
+
usage,
|
|
1621
|
+
...dock
|
|
1603
1622
|
};
|
|
1604
1623
|
if (!usable) return {
|
|
1605
1624
|
status: "expired",
|
|
@@ -1610,7 +1629,7 @@ async function buildStatus(deps) {
|
|
|
1610
1629
|
status: "signed-in",
|
|
1611
1630
|
...identity,
|
|
1612
1631
|
...compat,
|
|
1613
|
-
...await creditsOf(deps)
|
|
1632
|
+
...await creditsOf(deps, force)
|
|
1614
1633
|
};
|
|
1615
1634
|
}
|
|
1616
1635
|
/** GET /api/codearts/status */
|
|
@@ -1625,12 +1644,65 @@ function statusHandler(deps) {
|
|
|
1625
1644
|
return;
|
|
1626
1645
|
}
|
|
1627
1646
|
try {
|
|
1628
|
-
json(res, 200, await buildStatus(deps));
|
|
1647
|
+
json(res, 200, await buildStatus(deps, new URL(req.url ?? "", "http://localhost").searchParams.get("force") === "1"));
|
|
1629
1648
|
} catch (error) {
|
|
1630
1649
|
json(res, 500, { error: safeMessage(error) });
|
|
1631
1650
|
}
|
|
1632
1651
|
};
|
|
1633
1652
|
}
|
|
1653
|
+
/**
|
|
1654
|
+
* GET/POST /api/codearts/dock — 对话底部那一行的显隐开关读写。
|
|
1655
|
+
*
|
|
1656
|
+
* 和 enabled-models 同一套规矩:GET 给当前值 + writable,POST 只收布尔、
|
|
1657
|
+
* 没接 settings provider 时回 403 让卡片把勾选框置灰。
|
|
1658
|
+
*/
|
|
1659
|
+
function dockHandler(deps) {
|
|
1660
|
+
return async (req, res) => {
|
|
1661
|
+
if (!loopbackRequest(req)) {
|
|
1662
|
+
json(res, 403, { error: "request-not-trusted" });
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const current = deps.dock?.() ?? DEFAULT_DOCK_FLAGS;
|
|
1666
|
+
if (req.method === "GET") {
|
|
1667
|
+
json(res, 200, {
|
|
1668
|
+
dock: current,
|
|
1669
|
+
writable: deps.setDock !== void 0
|
|
1670
|
+
});
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (req.method === "POST") {
|
|
1674
|
+
if (deps.setDock === void 0) {
|
|
1675
|
+
json(res, 403, { error: "dock-not-writable" });
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const type = req.headers["content-type"];
|
|
1679
|
+
if (typeof type !== "string" || !type.trim().toLowerCase().startsWith("application/json")) {
|
|
1680
|
+
json(res, 415, { error: "content-type must be application/json" });
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
const body = JSON.parse(await readBody(req));
|
|
1685
|
+
const touched = ["credits", "queue"].filter((key) => body[key] !== void 0);
|
|
1686
|
+
if (touched.length === 0 || touched.some((key) => typeof body[key] !== "boolean")) {
|
|
1687
|
+
json(res, 400, { error: "expected {\"credits\"?: boolean, \"queue\"?: boolean}" });
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (!await deps.setDock(Object.fromEntries(touched.map((key) => [key, body[key]])))) {
|
|
1691
|
+
json(res, 403, { error: "dock-not-writable" });
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
json(res, 200, {
|
|
1695
|
+
dock: deps.dock?.() ?? DEFAULT_DOCK_FLAGS,
|
|
1696
|
+
writable: true
|
|
1697
|
+
});
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
json(res, 500, { error: safeMessage(error) });
|
|
1700
|
+
}
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
json(res, 405, { error: "method not allowed" });
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1634
1706
|
/** GET /api/codearts/models — currently offered model ids (honours allowlist). */
|
|
1635
1707
|
function modelsHandler(deps) {
|
|
1636
1708
|
return async (req, res) => {
|
|
@@ -1933,6 +2005,11 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
1933
2005
|
path: CODEARTS_QUEUE_PATH,
|
|
1934
2006
|
handler: queueHandler(deps)
|
|
1935
2007
|
});
|
|
2008
|
+
const disposeDock = ctx.webServer.register({
|
|
2009
|
+
kind: "exact",
|
|
2010
|
+
path: CODEARTS_DOCK_PATH,
|
|
2011
|
+
handler: dockHandler(deps)
|
|
2012
|
+
});
|
|
1936
2013
|
const disposeDebug = ctx.webServer.register({
|
|
1937
2014
|
kind: "exact",
|
|
1938
2015
|
path: CODEARTS_DEBUG_PATH,
|
|
@@ -1949,6 +2026,7 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
1949
2026
|
disposeClaim();
|
|
1950
2027
|
disposeRefresh();
|
|
1951
2028
|
disposeQueue();
|
|
2029
|
+
disposeDock();
|
|
1952
2030
|
disposeDebug();
|
|
1953
2031
|
for (const s of activeLogins.values()) s.close();
|
|
1954
2032
|
activeLogins.clear();
|
|
@@ -2113,7 +2191,11 @@ function unwrapConfig(value) {
|
|
|
2113
2191
|
for (const [key, field] of Object.entries(value)) out[key] = unwrapVolatileValue(field);
|
|
2114
2192
|
return out;
|
|
2115
2193
|
}
|
|
2116
|
-
const Config = z.object({
|
|
2194
|
+
const Config = z.object({
|
|
2195
|
+
enabledModels: z.array(z.string()).description("Model ids offered in the model pickers (empty means every model)").volatile(),
|
|
2196
|
+
showCreditDock: z.boolean().default(true).description("Show the remaining-credits line under the chat input box").volatile(),
|
|
2197
|
+
showQueueDock: z.boolean().default(true).description("Show the upstream queue position under the chat input box").volatile()
|
|
2198
|
+
});
|
|
2117
2199
|
function settingsCompatNamespace(ctx) {
|
|
2118
2200
|
return resolveSettingsNamespaceCompat(ctx, ctx.get("settings"), CODEARTS_SETTINGS_NS);
|
|
2119
2201
|
}
|
|
@@ -2185,6 +2267,19 @@ function apply(ctx, rawConfig) {
|
|
|
2185
2267
|
await settings.update(settingsCompatNamespace(ctx), { enabledModels: [...ids] });
|
|
2186
2268
|
return true;
|
|
2187
2269
|
};
|
|
2270
|
+
const dock = () => ({
|
|
2271
|
+
credits: current().showCreditDock !== false,
|
|
2272
|
+
queue: current().showQueueDock !== false
|
|
2273
|
+
});
|
|
2274
|
+
const setDock = async (patch) => {
|
|
2275
|
+
const settings = ctx.get("settings");
|
|
2276
|
+
if (settings === void 0) return false;
|
|
2277
|
+
await settings.update(settingsCompatNamespace(ctx), {
|
|
2278
|
+
...patch.credits === void 0 ? {} : { showCreditDock: patch.credits },
|
|
2279
|
+
...patch.queue === void 0 ? {} : { showQueueDock: patch.queue }
|
|
2280
|
+
});
|
|
2281
|
+
return true;
|
|
2282
|
+
};
|
|
2188
2283
|
/** 手动「刷新模型列表」的预算:三路来源各打一次,比每小时的后台刷新更该给足时间。 */
|
|
2189
2284
|
const MANUAL_REFRESH_MS = 6e4;
|
|
2190
2285
|
ctx.inject(["webServer"], (webCtx) => registerCodeArtsStatusRoute(webCtx, {
|
|
@@ -2198,6 +2293,12 @@ function apply(ctx, rawConfig) {
|
|
|
2198
2293
|
queue: () => queue.snapshot(),
|
|
2199
2294
|
recentRequests: () => recentRequestShapes(),
|
|
2200
2295
|
credits: (signal) => loadCredits(signal),
|
|
2296
|
+
refreshCredits: (signal) => {
|
|
2297
|
+
credits.invalidate();
|
|
2298
|
+
return loadCredits(signal);
|
|
2299
|
+
},
|
|
2300
|
+
dock,
|
|
2301
|
+
setDock,
|
|
2201
2302
|
claim: async (accountId) => {
|
|
2202
2303
|
const ids = accountId === void 0 ? await store.ids() : [accountId];
|
|
2203
2304
|
if (ids.length === 0) return {
|
|
@@ -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 };
|