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/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-5YK7bW-e.mjs";
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.3 起 `@earendil-works/pi-ai` 已从 peerDependencies
738
- * 移进 dependencies,区间真正生效;这里再探测一次,把「版本不兼容」明说,而不是让
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},请重装 dsh-codearts(1.1.3 起它自带这份依赖),或把 profile 顶层那份升级到同一区间`;
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("@earendil-works/pi-ai/utils/estimate"));
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
- if (deps.credits === void 0) return {};
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 deps.credits();
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({ enabledModels: z.array(z.string()).description("Model ids offered in the model pickers (empty means every model)").volatile() });
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 };