dskcode 0.1.10 → 0.1.12

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/dist/index.js CHANGED
@@ -1,4 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DeepSeekProvider,
4
+ ModelNotSupportedError
5
+ } from "./chunk-FODNQIT4.js";
6
+ import {
7
+ SUPPORTED_MODELS,
8
+ calculateCost,
9
+ estimateTokens,
10
+ getModelMeta,
11
+ isSupportedModel
12
+ } from "./chunk-DIZMSV5H.js";
2
13
 
3
14
  // src/cli/index.tsx
4
15
  import { Command } from "commander";
@@ -65,6 +76,12 @@ function mergeConfig(base, overlay) {
65
76
  if (overlay.maxToolRounds !== void 0) {
66
77
  result.maxToolRounds = overlay.maxToolRounds;
67
78
  }
79
+ if (overlay.budgetLimit !== void 0) {
80
+ result.budgetLimit = overlay.budgetLimit;
81
+ }
82
+ if (overlay.tokenBudgetLimit !== void 0) {
83
+ result.tokenBudgetLimit = overlay.tokenBudgetLimit;
84
+ }
68
85
  if (overlay.providers !== void 0) {
69
86
  result.providers = overlay.providers;
70
87
  }
@@ -85,7 +102,9 @@ var ENV_MAP = {
85
102
  [`${ENV_PREFIX}VERBOSE`]: "verbose",
86
103
  [`${ENV_PREFIX}MAX_TOKENS`]: "maxTokens",
87
104
  [`${ENV_PREFIX}TEMPERATURE`]: "temperature",
88
- [`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds"
105
+ [`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds",
106
+ [`${ENV_PREFIX}BUDGET_LIMIT`]: "budgetLimit",
107
+ [`${ENV_PREFIX}TOKEN_BUDGET_LIMIT`]: "tokenBudgetLimit"
89
108
  };
90
109
  function applyEnvVars(config) {
91
110
  const result = { ...config, providers: [...config.providers] };
@@ -99,9 +118,11 @@ function applyEnvVars(config) {
99
118
  break;
100
119
  }
101
120
  case "maxTokens":
102
- case "maxToolRounds": {
121
+ case "maxToolRounds":
122
+ case "budgetLimit":
123
+ case "tokenBudgetLimit": {
103
124
  const n = Number(raw);
104
- if (Number.isFinite(n) && n > 0) {
125
+ if (Number.isFinite(n) && n >= 0) {
105
126
  result[configKey] = n;
106
127
  }
107
128
  break;
@@ -156,6 +177,12 @@ function applyCliOverrides(config, flags) {
156
177
  if (flags.temperature !== void 0 && flags.temperature >= 0 && flags.temperature <= 2) {
157
178
  result.temperature = flags.temperature;
158
179
  }
180
+ if (flags.budgetLimit !== void 0 && flags.budgetLimit >= 0) {
181
+ result.budgetLimit = flags.budgetLimit;
182
+ }
183
+ if (flags.tokenBudgetLimit !== void 0 && flags.tokenBudgetLimit >= 0) {
184
+ result.tokenBudgetLimit = flags.tokenBudgetLimit;
185
+ }
159
186
  return result;
160
187
  }
161
188
  function validateConfig(config) {
@@ -166,7 +193,7 @@ function validateConfig(config) {
166
193
  message: "\u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A Provider\u3002\u8BF7\u901A\u8FC7\u914D\u7F6E\u6587\u4EF6\u6216 DEEPSEEK_API_KEY \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u3002"
167
194
  });
168
195
  }
169
- const SUPPORTED_MODELS = ["deepseek-v4-flash", "deepseek-v4-pro"];
196
+ const SUPPORTED_MODELS2 = ["deepseek-v4-flash", "deepseek-v4-pro"];
170
197
  for (let i = 0; i < config.providers.length; i++) {
171
198
  const p = config.providers[i];
172
199
  if (!p.name) {
@@ -180,10 +207,10 @@ function validateConfig(config) {
180
207
  field: `providers[${i}].model`,
181
208
  message: `Provider "${p.name || i}" \u7F3A\u5C11 model \u5B57\u6BB5\u3002`
182
209
  });
183
- } else if (!SUPPORTED_MODELS.includes(p.model)) {
210
+ } else if (!SUPPORTED_MODELS2.includes(p.model)) {
184
211
  errors.push({
185
212
  field: `providers[${i}].model`,
186
- message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${SUPPORTED_MODELS.join(", ")}`
213
+ message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${SUPPORTED_MODELS2.join(", ")}`
187
214
  });
188
215
  }
189
216
  }
@@ -216,6 +243,18 @@ function validateConfig(config) {
216
243
  message: "maxToolRounds \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
217
244
  });
218
245
  }
246
+ if (config.budgetLimit !== void 0 && config.budgetLimit < 0) {
247
+ errors.push({
248
+ field: "budgetLimit",
249
+ message: "budgetLimit \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 0\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09\u3002"
250
+ });
251
+ }
252
+ if (config.tokenBudgetLimit !== void 0 && config.tokenBudgetLimit < 0) {
253
+ errors.push({
254
+ field: "tokenBudgetLimit",
255
+ message: "tokenBudgetLimit \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 0\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09\u3002"
256
+ });
257
+ }
219
258
  return errors;
220
259
  }
221
260
  async function loadConfig(configPath) {
@@ -265,6 +304,34 @@ async function saveApiKey(apiKey) {
265
304
  await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
266
305
  return configFile;
267
306
  }
307
+ async function saveModelConfig(model) {
308
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
309
+ const configDir = join(home, ".dskcode");
310
+ const configFile = join(configDir, "settings.json");
311
+ await mkdir(configDir, { recursive: true });
312
+ let configData;
313
+ try {
314
+ const raw = await readFile(configFile, "utf-8");
315
+ configData = JSON.parse(raw);
316
+ } catch {
317
+ configData = structuredClone(defaultConfig);
318
+ }
319
+ const providers = configData.providers ?? [];
320
+ const defaultProviderName = configData.defaultProvider ?? "deepseek";
321
+ const existing = providers.find((p) => p.name === defaultProviderName);
322
+ if (existing) {
323
+ existing.model = model;
324
+ } else {
325
+ providers.push({
326
+ name: defaultProviderName,
327
+ baseUrl: "https://api.deepseek.com",
328
+ model
329
+ });
330
+ }
331
+ configData.providers = providers;
332
+ await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
333
+ return configFile;
334
+ }
268
335
  async function saveStockConfig(symbols) {
269
336
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
270
337
  const configDir = join(home, ".dskcode");
@@ -421,8 +488,8 @@ async function promptForApiKey() {
421
488
  }
422
489
 
423
490
  // src/cli/index.tsx
424
- import { readFile as readFile2 } from "fs/promises";
425
- import { join as join2 } from "path";
491
+ import { readFile as readFile3 } from "fs/promises";
492
+ import { join as join3 } from "path";
426
493
 
427
494
  // src/ui/RenderScope.tsx
428
495
  import { render } from "ink";
@@ -435,6 +502,15 @@ function renderApp(node) {
435
502
  import { Text } from "ink";
436
503
  import InkSpinner from "ink-spinner";
437
504
  import { jsx, jsxs } from "react/jsx-runtime";
505
+ function Spinner({ type = "dots", label }) {
506
+ return /* @__PURE__ */ jsxs(Text, { children: [
507
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(InkSpinner, { type }) }),
508
+ label ? /* @__PURE__ */ jsxs(Text, { children: [
509
+ " ",
510
+ label
511
+ ] }) : null
512
+ ] });
513
+ }
438
514
 
439
515
  // src/ui/StatusMessage.tsx
440
516
  import { Box, Text as Text2 } from "ink";
@@ -455,9 +531,9 @@ var LOGO_LINES = [
455
531
  ];
456
532
 
457
533
  // src/ui/ChatSession.tsx
458
- import { Box as Box3, Text as Text4, useInput } from "ink";
534
+ import { Box as Box5, Text as Text6, useInput, Static } from "ink";
459
535
  import TextInput from "ink-text-input";
460
- import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
536
+ import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2, useRef as useRef2 } from "react";
461
537
 
462
538
  // src/ui/useDoubleCtrlC.ts
463
539
  import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
@@ -487,8 +563,836 @@ function useDoubleCtrlC(onExit) {
487
563
  return { doubleCtrlC, handleCtrlC };
488
564
  }
489
565
 
490
- // src/ui/ChatSession.tsx
566
+ // src/ui/AssistantMessage.tsx
567
+ import { Box as Box4, Text as Text5 } from "ink";
568
+
569
+ // src/provider/cost-tracker.ts
570
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
571
+ import { join as join2 } from "path";
572
+ var CostTracker = class {
573
+ #costDir;
574
+ #budgetLimit;
575
+ #tokenBudgetLimit;
576
+ #onBudgetExceeded;
577
+ // 会话级统计(内存)
578
+ #sessionId;
579
+ #sessionStartedAt;
580
+ #sessionRecords = [];
581
+ // 日级统计(内存缓存,启动时从磁盘恢复)
582
+ #todayDate;
583
+ #todaySummary;
584
+ // 持久化标记
585
+ #dirty = false;
586
+ #flushInProgress = false;
587
+ constructor(options = {}) {
588
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
589
+ this.#costDir = options.costDir ?? join2(home, ".dskcode", "costs");
590
+ this.#budgetLimit = options.budgetLimit ?? 0;
591
+ this.#tokenBudgetLimit = options.tokenBudgetLimit ?? 0;
592
+ this.#onBudgetExceeded = options.onBudgetExceeded;
593
+ this.#sessionId = generateSessionId();
594
+ this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
595
+ const today = getTodayStr();
596
+ this.#todayDate = today;
597
+ this.#todaySummary = createEmptyDailySummary(today);
598
+ }
599
+ // -----------------------------------------------------------------------
600
+ // 核心方法
601
+ // -----------------------------------------------------------------------
602
+ /**
603
+ * 记录一次 API 调用的 Token 使用量和成本。
604
+ * 自动计算费用,累加到会话级和日级统计。
605
+ */
606
+ record(usage, model) {
607
+ const cost = calculateCost(usage, model);
608
+ const record = {
609
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
610
+ model,
611
+ usage: { ...usage },
612
+ cost: { ...cost }
613
+ };
614
+ this.#sessionRecords.push(record);
615
+ this.#ensureTodayBucket();
616
+ this.#addToDaily(record);
617
+ this.#dirty = true;
618
+ this.#checkBudget();
619
+ return cost;
620
+ }
621
+ // -----------------------------------------------------------------------
622
+ // 会话级查询
623
+ // -----------------------------------------------------------------------
624
+ /** 当前会话 ID */
625
+ get sessionId() {
626
+ return this.#sessionId;
627
+ }
628
+ /** 当前会话的所有成本记录 */
629
+ get records() {
630
+ return this.#sessionRecords;
631
+ }
632
+ /** 当前会话累计成本汇总 */
633
+ get sessionSummary() {
634
+ let totalPromptTokens = 0;
635
+ let totalCompletionTokens = 0;
636
+ let totalCachedTokens = 0;
637
+ let totalCost = 0;
638
+ for (const r of this.#sessionRecords) {
639
+ totalPromptTokens += r.usage.promptTokens;
640
+ totalCompletionTokens += r.usage.completionTokens;
641
+ totalCachedTokens += r.usage.cachedPromptTokens ?? 0;
642
+ totalCost += r.cost.totalCost;
643
+ }
644
+ return {
645
+ sessionId: this.#sessionId,
646
+ startedAt: this.#sessionStartedAt,
647
+ totalPromptTokens,
648
+ totalCompletionTokens,
649
+ totalCachedTokens,
650
+ totalCost,
651
+ records: [...this.#sessionRecords]
652
+ };
653
+ }
654
+ /** 当前会话总费用(元) */
655
+ get sessionTotalCost() {
656
+ return this.#sessionRecords.reduce((sum, r) => sum + r.cost.totalCost, 0);
657
+ }
658
+ /** 当前会话总 prompt token 数 */
659
+ get sessionPromptTokens() {
660
+ return this.#sessionRecords.reduce((sum, r) => sum + r.usage.promptTokens, 0);
661
+ }
662
+ /** 当前会话总 completion token 数 */
663
+ get sessionCompletionTokens() {
664
+ return this.#sessionRecords.reduce((sum, r) => sum + r.usage.completionTokens, 0);
665
+ }
666
+ /** 当前会话 API 调用次数 */
667
+ get sessionCallCount() {
668
+ return this.#sessionRecords.length;
669
+ }
670
+ // -----------------------------------------------------------------------
671
+ // 日级查询
672
+ // -----------------------------------------------------------------------
673
+ /** 今日成本汇总 */
674
+ get todaySummary() {
675
+ this.#ensureTodayBucket();
676
+ return { ...this.#todaySummary };
677
+ }
678
+ /** 今日总费用(元) */
679
+ get todayTotalCost() {
680
+ this.#ensureTodayBucket();
681
+ return this.#todaySummary.totalCost;
682
+ }
683
+ /** 今日 API 调用次数 */
684
+ get todayCallCount() {
685
+ this.#ensureTodayBucket();
686
+ return this.#todaySummary.totalCalls;
687
+ }
688
+ // -----------------------------------------------------------------------
689
+ // 预算检查
690
+ // -----------------------------------------------------------------------
691
+ /** 是否已超出预算 */
692
+ get isBudgetExceeded() {
693
+ if (this.#budgetLimit > 0 && this.todayTotalCost >= this.#budgetLimit) {
694
+ return true;
695
+ }
696
+ if (this.#tokenBudgetLimit > 0) {
697
+ const totalTokens = this.#todaySummary.totalPromptTokens + this.#todaySummary.totalCompletionTokens;
698
+ if (totalTokens >= this.#tokenBudgetLimit) return true;
699
+ }
700
+ return false;
701
+ }
702
+ /** 剩余预算(元),无限制时返回 Infinity */
703
+ get remainingBudget() {
704
+ if (this.#budgetLimit <= 0) return Infinity;
705
+ return Math.max(0, this.#budgetLimit - this.todayTotalCost);
706
+ }
707
+ // -----------------------------------------------------------------------
708
+ // 持久化
709
+ // -----------------------------------------------------------------------
710
+ /**
711
+ * 从磁盘加载历史成本数据。
712
+ * 启动时调用,用于恢复今日和历史数据。
713
+ */
714
+ async load() {
715
+ const store = await this.#loadStore();
716
+ const today = getTodayStr();
717
+ if (store.daily[today]) {
718
+ this.#todaySummary = store.daily[today];
719
+ this.#todayDate = today;
720
+ }
721
+ }
722
+ /**
723
+ * 将脏数据持久化到磁盘。
724
+ * 通常在每次 record() 后异步调用,或在会话结束时主动调用。
725
+ */
726
+ async flush() {
727
+ if (!this.#dirty || this.#flushInProgress) return;
728
+ this.#flushInProgress = true;
729
+ this.#dirty = false;
730
+ try {
731
+ await this.#saveStore();
732
+ } finally {
733
+ this.#flushInProgress = false;
734
+ }
735
+ }
736
+ /**
737
+ * 查询指定日期范围的成本汇总。
738
+ * @param startDate 起始日期(YYYY-MM-DD),包含
739
+ * @param endDate 截止日期(YYYY-MM-DD),包含,默认同 startDate
740
+ */
741
+ async queryRange(startDate, endDate) {
742
+ const store = await this.#loadStore();
743
+ const end = endDate ?? startDate;
744
+ const result = [];
745
+ for (const [date, summary] of Object.entries(store.daily)) {
746
+ if (date >= startDate && date <= end) {
747
+ result.push(summary);
748
+ }
749
+ }
750
+ result.sort((a, b) => a.date.localeCompare(b.date));
751
+ return result;
752
+ }
753
+ /** 重置会话(开始新会话,不影响日级累计) */
754
+ resetSession() {
755
+ this.#sessionId = generateSessionId();
756
+ this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
757
+ this.#sessionRecords.length = 0;
758
+ }
759
+ // -----------------------------------------------------------------------
760
+ // 内部方法
761
+ // -----------------------------------------------------------------------
762
+ /** 确保今日的统计桶存在(处理跨日情况) */
763
+ #ensureTodayBucket() {
764
+ const today = getTodayStr();
765
+ if (today !== this.#todayDate) {
766
+ this.#todayDate = today;
767
+ this.#todaySummary = createEmptyDailySummary(today);
768
+ this.#dirty = true;
769
+ }
770
+ }
771
+ /** 将一条记录累加到日级汇总 */
772
+ #addToDaily(record) {
773
+ this.#todaySummary.totalPromptTokens += record.usage.promptTokens;
774
+ this.#todaySummary.totalCompletionTokens += record.usage.completionTokens;
775
+ this.#todaySummary.totalCachedTokens += record.usage.cachedPromptTokens ?? 0;
776
+ this.#todaySummary.totalCost += record.cost.totalCost;
777
+ this.#todaySummary.totalCalls += 1;
778
+ const modelKey = record.model;
779
+ if (!this.#todaySummary.byModel[modelKey]) {
780
+ this.#todaySummary.byModel[modelKey] = {
781
+ model: record.model,
782
+ totalPromptTokens: 0,
783
+ totalCompletionTokens: 0,
784
+ totalCachedTokens: 0,
785
+ totalCost: 0,
786
+ totalCalls: 0
787
+ };
788
+ }
789
+ const modelSummary = this.#todaySummary.byModel[modelKey];
790
+ modelSummary.totalPromptTokens += record.usage.promptTokens;
791
+ modelSummary.totalCompletionTokens += record.usage.completionTokens;
792
+ modelSummary.totalCachedTokens += record.usage.cachedPromptTokens ?? 0;
793
+ modelSummary.totalCost += record.cost.totalCost;
794
+ modelSummary.totalCalls += 1;
795
+ }
796
+ /** 预算超限检查 */
797
+ #checkBudget() {
798
+ if (!this.isBudgetExceeded) return;
799
+ this.#onBudgetExceeded?.(this);
800
+ }
801
+ /** 从磁盘加载成本存储 */
802
+ async #loadStore() {
803
+ const filePath = join2(this.#costDir, "history.json");
804
+ try {
805
+ const raw = await readFile2(filePath, "utf-8");
806
+ return JSON.parse(raw);
807
+ } catch {
808
+ return { version: 1, daily: {} };
809
+ }
810
+ }
811
+ /** 保存成本存储到磁盘 */
812
+ async #saveStore() {
813
+ const store = await this.#loadStore();
814
+ store.daily[this.#todayDate] = this.#todaySummary;
815
+ const cutoffDate = getDateNDaysAgo(90);
816
+ const datesToRemove = Object.keys(store.daily).filter(
817
+ (date) => date < cutoffDate
818
+ );
819
+ for (const date of datesToRemove) {
820
+ delete store.daily[date];
821
+ }
822
+ await mkdir2(this.#costDir, { recursive: true });
823
+ const filePath = join2(this.#costDir, "history.json");
824
+ await writeFile2(filePath, JSON.stringify(store, null, 2), "utf-8");
825
+ }
826
+ };
827
+ function generateSessionId() {
828
+ const ts = Date.now().toString(36);
829
+ const rand = Math.random().toString(36).slice(2, 8);
830
+ return `${ts}-${rand}`;
831
+ }
832
+ function getTodayStr() {
833
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
834
+ }
835
+ function getDateNDaysAgo(n) {
836
+ const d = /* @__PURE__ */ new Date();
837
+ d.setDate(d.getDate() - n);
838
+ return d.toISOString().slice(0, 10);
839
+ }
840
+ function createEmptyDailySummary(date) {
841
+ return {
842
+ date,
843
+ totalPromptTokens: 0,
844
+ totalCompletionTokens: 0,
845
+ totalCachedTokens: 0,
846
+ totalCost: 0,
847
+ totalCalls: 0,
848
+ byModel: {}
849
+ };
850
+ }
851
+ function formatMoney(yuan) {
852
+ if (yuan === 0) return "\xA50.00";
853
+ if (yuan < 0.01) return `\xA5${yuan.toFixed(6)}`;
854
+ if (yuan < 1) return `\xA5${yuan.toFixed(4)}`;
855
+ return `\xA5${yuan.toFixed(2)}`;
856
+ }
857
+
858
+ // src/ui/ToolCallBlock.tsx
859
+ import { Box as Box3, Text as Text4 } from "ink";
491
860
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
861
+ function formatArgsSummary(args) {
862
+ try {
863
+ const parsed = JSON.parse(args);
864
+ const lines = Object.entries(parsed).map(([key, value]) => {
865
+ const val = String(value);
866
+ const truncated = val.length > 80 ? val.slice(0, 77) + "..." : val;
867
+ return ` ${key}: ${truncated}`;
868
+ });
869
+ return lines.join("\n");
870
+ } catch {
871
+ const truncated = args.length > 120 ? args.slice(0, 117) + "..." : args;
872
+ return ` ${truncated}`;
873
+ }
874
+ }
875
+ function ToolCallBlock({ call, showPendingHint = true }) {
876
+ const argsDisplay = formatArgsSummary(call.arguments);
877
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: [
878
+ /* @__PURE__ */ jsxs3(Box3, { children: [
879
+ /* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", bold: true, children: [
880
+ "\u{1F4E6} ",
881
+ call.name
882
+ ] }),
883
+ /* @__PURE__ */ jsxs3(Text4, { color: "#555555", children: [
884
+ " ",
885
+ "\u2500".repeat(Math.max(1, 30 - call.name.length))
886
+ ] })
887
+ ] }),
888
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { color: "#888888", children: argsDisplay }) }),
889
+ showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { color: "yellow", children: "\u23F3 \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09" }) })
890
+ ] });
891
+ }
892
+
893
+ // src/agent/message-builder.ts
894
+ function estimateMessageTokens(msg) {
895
+ let text = msg.content;
896
+ if (msg.toolCalls) {
897
+ for (const tc of msg.toolCalls) {
898
+ text += tc.name + tc.arguments;
899
+ }
900
+ }
901
+ return estimateTokens(text) + 10;
902
+ }
903
+ function trimMessages(messages, opts) {
904
+ const meta = getModelMeta(opts.model);
905
+ const maxInputTokens = meta.contextWindow - opts.reservedForOutput;
906
+ const systemTokens = estimateTokens(opts.systemPrompt);
907
+ let remaining = maxInputTokens - systemTokens;
908
+ const preserved = [];
909
+ let roundsPreserved = 0;
910
+ for (let i = messages.length - 1; i >= 0 && roundsPreserved < opts.preserveRecentRounds; i--) {
911
+ preserved.unshift(messages[i]);
912
+ if (messages[i].role === "user") {
913
+ roundsPreserved++;
914
+ }
915
+ }
916
+ for (const msg of preserved) {
917
+ remaining -= estimateMessageTokens(msg);
918
+ }
919
+ if (remaining < 0) {
920
+ while (preserved.length > 1 && remaining < 0) {
921
+ const removed = preserved.shift();
922
+ remaining += estimateMessageTokens(removed);
923
+ }
924
+ return [preserved, true];
925
+ }
926
+ const olderMessages = messages.slice(0, messages.length - preserved.length);
927
+ const kept = [];
928
+ for (let i = olderMessages.length - 1; i >= 0; i--) {
929
+ const cost = estimateMessageTokens(olderMessages[i]);
930
+ if (remaining - cost < 0) break;
931
+ remaining -= cost;
932
+ kept.unshift(olderMessages[i]);
933
+ }
934
+ const result = [...kept, ...preserved];
935
+ const trimmed = result.length < messages.length;
936
+ return [result, trimmed];
937
+ }
938
+ function buildApiMessages(systemPrompt, history) {
939
+ return [
940
+ { role: "system", content: systemPrompt },
941
+ ...history
942
+ ];
943
+ }
944
+ function formatUsageSummary(usage) {
945
+ const prompt = usage.promptTokens.toLocaleString();
946
+ const completion = usage.completionTokens.toLocaleString();
947
+ const total = (usage.promptTokens + usage.completionTokens).toLocaleString();
948
+ let summary = `\u{1F4E5} ${prompt} + \u{1F4E4} ${completion} = \u{1F4E6} ${total} tokens`;
949
+ if (usage.cachedPromptTokens && usage.cachedPromptTokens > 0) {
950
+ const cacheRate = (usage.cachedPromptTokens / usage.promptTokens * 100).toFixed(1);
951
+ summary += ` \u2502 \u{1F5C4}\uFE0F \u7F13\u5B58\u547D\u4E2D ${cacheRate}%`;
952
+ }
953
+ return summary;
954
+ }
955
+
956
+ // src/ui/AssistantMessage.tsx
957
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
958
+ function formatElapsed(ms) {
959
+ if (ms < 1e3) return `${ms}ms`;
960
+ const seconds = (ms / 1e3).toFixed(1);
961
+ return `${seconds}s`;
962
+ }
963
+ function AssistantMessage({
964
+ content,
965
+ toolCalls,
966
+ isStreaming = false,
967
+ usage,
968
+ elapsed,
969
+ cost,
970
+ model
971
+ }) {
972
+ if (!content && (!toolCalls || toolCalls.length === 0) && !isStreaming) {
973
+ return null;
974
+ }
975
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
976
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
977
+ /* @__PURE__ */ jsx4(Box4, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#ff00ff", children: "\u{1F916}" }) }),
978
+ /* @__PURE__ */ jsxs4(Box4, { flexGrow: 1, flexDirection: "column", children: [
979
+ content && /* @__PURE__ */ jsx4(Text5, { wrap: "wrap", children: content }),
980
+ isStreaming && !content && /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: "..." })
981
+ ] })
982
+ ] }),
983
+ toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsx4(ToolCallBlock, { call: tc }, tc.id ?? i)) }),
984
+ !isStreaming && (usage || elapsed !== void 0) && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, marginLeft: 3, children: [
985
+ /* @__PURE__ */ jsx4(Text5, { color: "#555555", children: "\u2500".repeat(36) }),
986
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", gap: 2, children: [
987
+ cost !== void 0 && cost > 0 && /* @__PURE__ */ jsxs4(Text5, { color: "yellow", children: [
988
+ "\u{1F4B0} \u672C\u6B21 ",
989
+ formatMoney(cost)
990
+ ] }),
991
+ elapsed !== void 0 && /* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
992
+ "\u{1F550} ",
993
+ formatElapsed(elapsed)
994
+ ] }),
995
+ usage && /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: formatUsageSummary(usage) })
996
+ ] })
997
+ ] })
998
+ ] });
999
+ }
1000
+
1001
+ // src/provider/registry.ts
1002
+ var ProviderRegistry = class {
1003
+ #factories = /* @__PURE__ */ new Map();
1004
+ #instances = /* @__PURE__ */ new Map();
1005
+ /** 注册一个 Provider 工厂 */
1006
+ register(name, factory) {
1007
+ this.#factories.set(name, factory);
1008
+ }
1009
+ /**
1010
+ * 获取或创建一个 Provider 实例(单例)。
1011
+ *
1012
+ * 相同的 name + baseUrl + model 组合会复用同一实例。
1013
+ * 如果 model 不在支持列表中,抛出 ModelNotSupportedError。
1014
+ */
1015
+ get(name, config) {
1016
+ if (!isSupportedModel(config.model)) {
1017
+ throw new ModelNotSupportedError(config.model);
1018
+ }
1019
+ const cacheKey = `${name}:${config.baseUrl}:${config.model}`;
1020
+ const cached = this.#instances.get(cacheKey);
1021
+ if (cached) return cached;
1022
+ const factory = this.#factories.get(name);
1023
+ if (!factory) {
1024
+ const available = [...this.#factories.keys()].join(", ");
1025
+ throw new Error(`\u672A\u6CE8\u518C\u7684 Provider: "${name}"\u3002\u53EF\u7528: ${available}`);
1026
+ }
1027
+ const instance = factory(config);
1028
+ this.#instances.set(cacheKey, instance);
1029
+ return instance;
1030
+ }
1031
+ /** 列出已注册的 Provider 名称 */
1032
+ list() {
1033
+ return [...this.#factories.keys()];
1034
+ }
1035
+ /** 清除缓存的实例(用于配置热重载) */
1036
+ clear() {
1037
+ this.#instances.clear();
1038
+ }
1039
+ };
1040
+ var defaultRegistry = new ProviderRegistry();
1041
+ defaultRegistry.register("deepseek", (config) => {
1042
+ return new DeepSeekProvider({
1043
+ apiKey: config.apiKey,
1044
+ baseUrl: config.baseUrl,
1045
+ model: config.model
1046
+ });
1047
+ });
1048
+ function createProvider(config) {
1049
+ return defaultRegistry.get(config.name, config);
1050
+ }
1051
+
1052
+ // src/agent/extra-prompt.ts
1053
+ var EXTRA_PROMPT = [
1054
+ "\u3010\u7EC8\u7AEF\u8F93\u51FA\u7EA6\u675F\u3011",
1055
+ "",
1056
+ "\u4F60\u7684\u56DE\u590D\u5C06\u76F4\u63A5\u6E32\u67D3\u5728\u7528\u6237\u7EC8\u7AEF\u7684\u547D\u4EE4\u884C\u754C\u9762\u4E2D\uFF0C\u4E0D\u652F\u6301 Markdown \u6E32\u67D3\u3002",
1057
+ "\u8BF7\u4E25\u683C\u9075\u5B88\u4EE5\u4E0B\u683C\u5F0F\u89C4\u5219\u3002",
1058
+ "",
1059
+ "\u4E00\u3001\u7981\u6B62\u4F7F\u7528\u7684\u7B26\u53F7",
1060
+ " - \u4E0D\u8981\u4F7F\u7528 Markdown \u6807\u9898\u7B26\u53F7\uFF08\u5355\u72EC\u6216\u8FDE\u7EED\u7684\u4E95\u53F7\uFF09",
1061
+ " - \u4E0D\u8981\u4F7F\u7528\u7C97\u4F53\u6216\u659C\u4F53\u7B26\u53F7\uFF08\u661F\u53F7\u3001\u4E0B\u5212\u7EBF\uFF09",
1062
+ " - \u4E0D\u8981\u4F7F\u7528\u4EE3\u7801\u5757\u6807\u8BB0\uFF08\u8FDE\u7EED\u4E09\u4E2A\u53CD\u5F15\u53F7\uFF09",
1063
+ " - \u4E0D\u8981\u4F7F\u7528\u5757\u5F15\u7528\u7B26\u53F7\uFF08\u5927\u4E8E\u53F7\uFF09",
1064
+ " - \u4E0D\u8981\u4F7F\u7528\u5206\u9694\u7EBF\uFF08\u8FDE\u7EED\u4E09\u4E2A\u51CF\u53F7\u6216\u661F\u53F7\uFF09",
1065
+ " - \u4E0D\u8981\u4F7F\u7528\u884C\u5185\u4EE3\u7801\u6807\u8BB0\uFF08\u5355\u4E2A\u53CD\u5F15\u53F7\uFF09",
1066
+ "",
1067
+ "\u4E8C\u3001\u63A8\u8350\u7684\u7EC4\u7EC7\u65B9\u5F0F",
1068
+ ' - \u7528\u7EAF\u6587\u5B57\u63CF\u8FF0\u5C42\u7EA7\uFF0C\u4F8B\u5982"\u7B2C\u4E00\u70B9"\u3001"\u7B2C\u4E8C\u70B9"\u66FF\u4EE3\u6807\u9898\u7B26\u53F7',
1069
+ " - \u7528 emoji \u7B26\u53F7\u505A\u89C6\u89C9\u6807\u8BB0\uFF08\u5982 \u{1F4CC} \u{1F4E6} \u{1F4DD} \u26A0\uFE0F \u7B49\uFF09",
1070
+ " - \u7528\u6570\u5B57\u5E8F\u53F7\uFF081. 2. 3.\uFF09\u7EC4\u7EC7\u5217\u8868",
1071
+ " - \u4EE3\u7801\u793A\u4F8B\u7528\u7F29\u8FDB\uFF08\u6BCF\u884C\u524D\u52A0 4 \u4E2A\u7A7A\u683C\uFF09\u66FF\u4EE3\u4EE3\u7801\u5757\u6807\u8BB0",
1072
+ " - \u6587\u4EF6\u8DEF\u5F84\u3001\u547D\u4EE4\u3001\u53D8\u91CF\u540D\u76F4\u63A5\u4E66\u5199\uFF0C\u4E0D\u8981\u7528\u4EFB\u4F55\u7B26\u53F7\u5305\u88F9",
1073
+ "",
1074
+ "\u4E09\u3001\u6B63\u786E\u683C\u5F0F\u793A\u4F8B",
1075
+ "",
1076
+ " \u7B2C\u4E00\u70B9\uFF1A\u4F18\u5316\u6570\u636E\u5E93\u67E5\u8BE2",
1077
+ " 1. \u6DFB\u52A0\u7D22\u5F15\u5230 user_id \u5B57\u6BB5",
1078
+ " 2. \u7528\u8FDE\u63A5\u6C60\u66FF\u4EE3\u77ED\u8FDE\u63A5",
1079
+ "",
1080
+ " \u4EE3\u7801\u793A\u4F8B\uFF1A",
1081
+ " const pool = new Pool({ max: 20 });",
1082
+ ' await pool.query("SELECT * FROM users");',
1083
+ "",
1084
+ " \u8F93\u51FA\u63D0\u793A\uFF1A",
1085
+ " \u{1F4CC} \u4F18\u5316\u524D\uFF1A\u67E5\u8BE2\u8017\u65F6 3.2 \u79D2",
1086
+ "",
1087
+ "\u56DB\u3001\u9519\u8BEF\u683C\u5F0F\u793A\u4F8B\uFF08\u7981\u6B62\u8FD9\u6837\u5199\uFF09",
1088
+ "",
1089
+ " \u4E95\u53F7\u4E32 \u4F18\u5316\u6570\u636E\u5E93\u67E5\u8BE2\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
1090
+ " \u661F\u53F7 \u6DFB\u52A0\u7D22\u5F15\u5230 user_id \u5B57\u6BB5\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
1091
+ " \u4E09\u4E2A\u53CD\u5F15\u53F7\u5F00\u59CB\u6216\u7ED3\u675F\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
1092
+ "",
1093
+ "\u6CE8\u610F\uFF1A\u4E0A\u8FF0\u793A\u4F8B\u4E2D\u7684\u9519\u8BEF\u5199\u6CD5\u4EC5\u4F5C\u4E3A\u8BF4\u660E\uFF0C\u4F60\u7684\u56DE\u590D\u4E2D\u4E0D\u8981\u51FA\u73B0\u8FD9\u4E9B\u683C\u5F0F\u3002"
1094
+ ].join("\n");
1095
+
1096
+ // src/agent/system-prompt.ts
1097
+ function buildSystemPrompt(opts) {
1098
+ const sections = [];
1099
+ sections.push(`\u4F60\u662F dskcode\uFF0C\u4E00\u4E2A\u57FA\u4E8E DeepSeek \u7684 AI \u7F16\u7A0B\u52A9\u624B\uFF0C\u8FD0\u884C\u5728\u7528\u6237\u7EC8\u7AEF\u4E2D\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u52A9\u5F00\u53D1\u8005\u7F16\u5199\u3001\u7406\u89E3\u3001\u8C03\u8BD5\u548C\u91CD\u6784\u4EE3\u7801\u3002
1100
+
1101
+ ## \u6838\u5FC3\u539F\u5219
1102
+ - \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587
1103
+ - \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE
1104
+ - \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E
1105
+ - \u4EE3\u7801\u793A\u4F8B\u4FDD\u6301\u7B80\u6D01\uFF0C\u9644\u5E26\u5FC5\u8981\u7684\u6CE8\u91CA`);
1106
+ sections.push(`## \u5F53\u524D\u6A21\u578B
1107
+ - \u6A21\u578B\uFF1A${opts.model}`);
1108
+ const now = /* @__PURE__ */ new Date();
1109
+ const dateStr = now.toLocaleDateString("zh-CN", {
1110
+ year: "numeric",
1111
+ month: "long",
1112
+ day: "numeric",
1113
+ weekday: "long"
1114
+ });
1115
+ const timeStr = now.toLocaleTimeString("zh-CN", {
1116
+ hour: "2-digit",
1117
+ minute: "2-digit"
1118
+ });
1119
+ sections.push(`## \u65F6\u95F4\u4E0A\u4E0B\u6587
1120
+ - \u5F53\u524D\u65E5\u671F\uFF1A${dateStr}
1121
+ - \u5F53\u524D\u65F6\u95F4\uFF1A${timeStr}
1122
+ - \u5DE5\u4F5C\u76EE\u5F55\uFF1A${opts.cwd}`);
1123
+ if (opts.tools && opts.tools.length > 0) {
1124
+ const toolLines = opts.tools.map((t) => {
1125
+ const paramInfo = t.parameters ? Object.keys(t.parameters.properties ?? {}).join(", ") : "";
1126
+ return `- **${t.name}**\uFF1A${t.description}${paramInfo ? `\uFF08\u53C2\u6570\uFF1A${paramInfo}\uFF09` : ""}`;
1127
+ }).join("\n");
1128
+ sections.push(`## \u53EF\u7528\u5DE5\u5177
1129
+
1130
+ \u4F60\u53EF\u4EE5\u901A\u8FC7\u5DE5\u5177\u8C03\u7528\u6267\u884C\u64CD\u4F5C\u3002\u5F53\u524D\u53EF\u7528\u7684\u5DE5\u5177\uFF1A
1131
+
1132
+ ${toolLines}
1133
+
1134
+ \u8C03\u7528\u5DE5\u5177\u65F6\uFF0C\u8BF7\u4F7F\u7528\u6807\u51C6 function_call \u683C\u5F0F\u3002\u5DE5\u5177\u5C06\u7531\u7CFB\u7EDF\u6267\u884C\u5E76\u8FD4\u56DE\u7ED3\u679C\u3002`);
1135
+ }
1136
+ if (opts.projectContext) {
1137
+ sections.push(`## \u9879\u76EE\u4E0A\u4E0B\u6587
1138
+
1139
+ ${opts.projectContext}`);
1140
+ }
1141
+ sections.push(`## \u884C\u4E3A\u7EA6\u675F
1142
+ - \u5982\u679C\u7528\u6237\u8BF7\u6C42\u4E0D\u660E\u786E\uFF0C\u8BF7\u4E3B\u52A8\u8BE2\u95EE\u6F84\u6E05
1143
+ - \u6D89\u53CA\u6587\u4EF6\u64CD\u4F5C\u65F6\uFF0C\u5148\u786E\u8BA4\u6587\u4EF6\u8DEF\u5F84
1144
+ - \u4E0D\u6267\u884C\u53EF\u80FD\u9020\u6210\u4E0D\u53EF\u9006\u635F\u5BB3\u7684\u64CD\u4F5C\uFF08\u5982 rm -rf\uFF09\u9664\u975E\u7528\u6237\u660E\u786E\u786E\u8BA4
1145
+ - \u5F53\u5DE5\u5177\u8C03\u7528\u8FD4\u56DE\u9519\u8BEF\u65F6\uFF0C\u5206\u6790\u539F\u56E0\u5E76\u5C1D\u8BD5\u4FEE\u590D`);
1146
+ sections.push(EXTRA_PROMPT);
1147
+ return sections.join("\n\n");
1148
+ }
1149
+
1150
+ // src/agent/index.ts
1151
+ var Session = class {
1152
+ #messages = [];
1153
+ #provider;
1154
+ #tools;
1155
+ #costTracker;
1156
+ #options;
1157
+ #abortController = new AbortController();
1158
+ constructor(provider, tools = [], costTracker, options) {
1159
+ this.#provider = provider;
1160
+ this.#tools = tools;
1161
+ this.#costTracker = costTracker ?? new CostTracker();
1162
+ this.#options = {
1163
+ cwd: options?.cwd ?? process.cwd(),
1164
+ maxToolRounds: options?.maxToolRounds ?? 20,
1165
+ reservedForOutput: options?.reservedForOutput ?? 4096,
1166
+ preserveRecentRounds: options?.preserveRecentRounds ?? 10,
1167
+ projectContext: options?.projectContext
1168
+ };
1169
+ }
1170
+ // -------------------------------------------------------------------------
1171
+ // 公共只读属性
1172
+ // -------------------------------------------------------------------------
1173
+ get messages() {
1174
+ return this.#messages;
1175
+ }
1176
+ get accumulatedCost() {
1177
+ return this.#costTracker.sessionTotalCost;
1178
+ }
1179
+ get costTracker() {
1180
+ return this.#costTracker;
1181
+ }
1182
+ get model() {
1183
+ return this.#provider.model();
1184
+ }
1185
+ // -------------------------------------------------------------------------
1186
+ // 流式对话 — Agent 主循环
1187
+ // -------------------------------------------------------------------------
1188
+ /**
1189
+ * 执行一轮对话,以 AsyncGenerator 形式逐步 yield 事件。
1190
+ *
1191
+ * 调用方式:
1192
+ * ```ts
1193
+ * for await (const event of session.chat("你好")) {
1194
+ * switch (event.type) {
1195
+ * case "text_delta": // 追加文本
1196
+ * case "tool_calls": // 展示工具调用
1197
+ * case "usage": // 记录使用量
1198
+ * case "done": // 本轮完成
1199
+ * case "error": // 处理错误
1200
+ * }
1201
+ * }
1202
+ * ```
1203
+ */
1204
+ async *chat(userInput) {
1205
+ this.#messages.push({ role: "user", content: userInput });
1206
+ const systemPrompt = this.#buildSystemPrompt();
1207
+ const [trimmed, wasTrimmed] = trimMessages(
1208
+ [...this.#messages],
1209
+ {
1210
+ model: this.#provider.model(),
1211
+ reservedForOutput: this.#options.reservedForOutput,
1212
+ systemPrompt,
1213
+ preserveRecentRounds: this.#options.preserveRecentRounds
1214
+ }
1215
+ );
1216
+ if (wasTrimmed) {
1217
+ }
1218
+ const apiMessages = buildApiMessages(systemPrompt, trimmed);
1219
+ const toolDefs = this.#buildToolDefinitions();
1220
+ const startTime = Date.now();
1221
+ try {
1222
+ const stream = this.#provider.chat(apiMessages, {
1223
+ signal: this.#abortController.signal
1224
+ // 将工具定义传给 provider(如支持 function calling)
1225
+ // 注意:当前 chat() 签名不含 tools 参数,
1226
+ // 工具定义在 system prompt 中描述,由模型通过文本方式请求
1227
+ });
1228
+ let accumulatedText = "";
1229
+ let lastUsage;
1230
+ let lastToolCalls;
1231
+ let lastFinishReason = null;
1232
+ for await (const chunk of stream) {
1233
+ if (chunk.content) {
1234
+ accumulatedText += chunk.content;
1235
+ yield { type: "text_delta", content: chunk.content };
1236
+ }
1237
+ if (chunk.toolCalls && chunk.toolCalls.length > 0) {
1238
+ lastToolCalls = chunk.toolCalls;
1239
+ }
1240
+ if (chunk.usage) {
1241
+ lastUsage = chunk.usage;
1242
+ }
1243
+ if (chunk.finishReason) {
1244
+ lastFinishReason = chunk.finishReason;
1245
+ }
1246
+ }
1247
+ if (lastToolCalls && lastToolCalls.length > 0) {
1248
+ yield { type: "tool_calls", calls: lastToolCalls };
1249
+ }
1250
+ if (lastUsage) {
1251
+ const modelId = this.#provider.model();
1252
+ const costInfo = this.#costTracker.record(lastUsage, modelId);
1253
+ yield { type: "usage", usage: lastUsage, model: modelId };
1254
+ }
1255
+ const assistantMsg = {
1256
+ role: "assistant",
1257
+ content: accumulatedText
1258
+ };
1259
+ if (lastToolCalls && lastToolCalls.length > 0) {
1260
+ assistantMsg.toolCalls = lastToolCalls;
1261
+ }
1262
+ this.#messages.push(assistantMsg);
1263
+ if (lastToolCalls && lastToolCalls.length > 0) {
1264
+ for (const tc of lastToolCalls) {
1265
+ this.#messages.push({
1266
+ role: "tool",
1267
+ content: `\u26A0 \u5DE5\u5177 "${tc.name}" \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09`,
1268
+ toolCallId: tc.id,
1269
+ name: tc.name
1270
+ });
1271
+ }
1272
+ }
1273
+ const elapsed = Date.now() - startTime;
1274
+ yield { type: "done", elapsed };
1275
+ } catch (err) {
1276
+ if (err instanceof DOMException && err.name === "AbortError") {
1277
+ return;
1278
+ }
1279
+ if (err instanceof Error && err.name === "AbortError") {
1280
+ return;
1281
+ }
1282
+ yield {
1283
+ type: "error",
1284
+ error: err instanceof Error ? err : new Error(String(err))
1285
+ };
1286
+ }
1287
+ }
1288
+ // -------------------------------------------------------------------------
1289
+ // 会话管理
1290
+ // -------------------------------------------------------------------------
1291
+ /** 取消正在进行的流式请求 */
1292
+ abort() {
1293
+ this.#abortController.abort();
1294
+ }
1295
+ /** 重置会话历史(保留 provider/tools 配置,重置成本追踪) */
1296
+ reset() {
1297
+ this.#messages.length = 0;
1298
+ this.#costTracker.resetSession();
1299
+ }
1300
+ // -------------------------------------------------------------------------
1301
+ // 内部方法
1302
+ // -------------------------------------------------------------------------
1303
+ /** 构建系统提示词 */
1304
+ #buildSystemPrompt() {
1305
+ const toolDescs = this.#tools.map((t) => ({
1306
+ name: t.name,
1307
+ description: t.description,
1308
+ parameters: t.parameters
1309
+ }));
1310
+ const opts = {
1311
+ model: this.#provider.model(),
1312
+ tools: toolDescs.length > 0 ? toolDescs : void 0,
1313
+ projectContext: this.#options.projectContext ?? void 0,
1314
+ cwd: this.#options.cwd
1315
+ };
1316
+ return buildSystemPrompt(opts);
1317
+ }
1318
+ /** 将注册的工具转为 ToolDefinition 格式(预留给 function calling) */
1319
+ #buildToolDefinitions() {
1320
+ return this.#tools.map((t) => ({
1321
+ type: "function",
1322
+ function: {
1323
+ name: t.name,
1324
+ description: t.description,
1325
+ parameters: t.parameters
1326
+ }
1327
+ }));
1328
+ }
1329
+ };
1330
+
1331
+ // src/utils/gradient.ts
1332
+ var IDLE_GRADIENT_STOPS = [
1333
+ [0, 255, 65],
1334
+ // #00ff41
1335
+ [0, 255, 255],
1336
+ // #00ffff
1337
+ [189, 147, 249]
1338
+ // #bd93f9
1339
+ ];
1340
+ var CMD_TIP_GRADIENT_STOPS = [
1341
+ [255, 245, 180],
1342
+ // #fff5b4 浅柠黄
1343
+ [255, 210, 80],
1344
+ // #ffd250 暖黄
1345
+ [255, 150, 50]
1346
+ // #ff9632 橙色
1347
+ ];
1348
+ var STREAMING_GRADIENT_STOPS = [
1349
+ [255, 191, 0],
1350
+ // #ffbf00 琥珀金
1351
+ [210, 140, 60],
1352
+ // #d28c3c 焦糖
1353
+ [175, 70, 40]
1354
+ // #af4628 棕红
1355
+ ];
1356
+ var GRADIENT_ANIMATION = {
1357
+ /** 空闲占位符动画:每帧相位步进(值越大流速越快) */
1358
+ idlePhaseStep: 0.06,
1359
+ /** 空闲占位符动画:帧间隔(ms) */
1360
+ idleInterval: 40,
1361
+ /** 流式占位符动画:每帧相位步进 */
1362
+ streamingPhaseStep: 0.05,
1363
+ /** 流式占位符动画:帧间隔(ms) */
1364
+ streamingInterval: 40,
1365
+ /** 命令提示条动画:每帧相位步进 */
1366
+ cmdTipPhaseStep: 0.05,
1367
+ /** 命令提示条动画:帧间隔(ms) */
1368
+ cmdTipInterval: 40
1369
+ };
1370
+ var SKIP_CHARS = /* @__PURE__ */ new Set([" ", "~", ".", "\u3002", "\uFF01", "\u{1F447}"]);
1371
+ function lerpStopsToHex(t, stops) {
1372
+ const len = stops.length;
1373
+ const segment = t * (len - 1);
1374
+ const idx = Math.min(Math.floor(segment), len - 2);
1375
+ const frac = segment - idx;
1376
+ const stop = stops[idx];
1377
+ const nextStop = stops[idx + 1];
1378
+ if (!stop || !nextStop) return "#808080";
1379
+ const r = Math.round(stop[0] + (nextStop[0] - stop[0]) * frac);
1380
+ const g = Math.round(stop[1] + (nextStop[1] - stop[1]) * frac);
1381
+ const b = Math.round(stop[2] + (nextStop[2] - stop[2]) * frac);
1382
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
1383
+ }
1384
+ function getGradientColors(text, phase, stops) {
1385
+ const len = text.length;
1386
+ if (len === 0) return [];
1387
+ return text.split("").map((ch, i) => {
1388
+ if (SKIP_CHARS.has(ch)) return "";
1389
+ const t = (i / (len - 1) + phase) % 1;
1390
+ return lerpStopsToHex(t, stops);
1391
+ });
1392
+ }
1393
+
1394
+ // src/ui/ChatSession.tsx
1395
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
492
1396
  var commandRegistry = /* @__PURE__ */ new Map();
493
1397
  function registerCommand(name, cmd) {
494
1398
  commandRegistry.set(name, cmd);
@@ -510,38 +1414,179 @@ registerCommand("/help", {
510
1414
  }
511
1415
  });
512
1416
  registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
513
- registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.0.0" }) });
1417
+ registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.1.10" }) });
1418
+ registerCommand("/model", { desc: "\u5207\u6362\u6A21\u578B", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /model \u8FDB\u5165\u9009\u62E9\u754C\u9762" }) });
514
1419
  registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
515
1420
  registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
516
- function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLaunchGame, onLaunchStock }) {
1421
+ var STREAMING_PLACEHOLDERS = [
1422
+ "\u8BA9\u5B50\u5F39\u98DE\u4E00\u4F1A\u513F...",
1423
+ "\u9A6C\u4E0A\u5C31\u597D...",
1424
+ "\u6B63\u5728\u618B\u5927\u62DB...",
1425
+ "\u7A0D\u7B49\u4E00\u4E0B\u4E0B~",
1426
+ "\u7801\u5B57\u4E2D...",
1427
+ "\u8111\u5B50\u8F6C\u5F97\u98DE\u5FEB..."
1428
+ ];
1429
+ var IDLE_PLACEHOLDERS = [
1430
+ "\u60F3\u5E72\u5565\uFF1F\u76F4\u63A5\u8BF4~",
1431
+ "\u6765\u5427\uFF0C\u5429\u5490\u70B9\u5565",
1432
+ "\u968F\u65F6\u5F85\u547D...",
1433
+ "\u6233\u8FD9\u91CC\u5F00\u804A...",
1434
+ "\u7B49\u4F60\u5F00\u53E3...",
1435
+ "\u5C3D\u7BA1\u4F7F\u5524~"
1436
+ ];
1437
+ function pickRandom(arr) {
1438
+ return arr[Math.floor(Math.random() * arr.length)];
1439
+ }
1440
+ function ChatSession({
1441
+ providerCount,
1442
+ toolCount,
1443
+ verbose,
1444
+ apiKey,
1445
+ baseUrl,
1446
+ costTracker: externalCostTracker,
1447
+ model = "deepseek-v4-flash",
1448
+ onLaunchGame,
1449
+ onLaunchStock
1450
+ }) {
1451
+ const termWidth = typeof process.stdout.columns === "number" ? process.stdout.columns : 80;
1452
+ const dividerWidth = Math.max(termWidth - 2, 1);
517
1453
  const [offset, setOffset] = useState3(0);
518
- const [messages, setMessages] = useState3([]);
1454
+ const [displayMessages, setDisplayMessages] = useState3([]);
519
1455
  const [input, setInput] = useState3("");
520
1456
  const [balance, setBalance] = useState3(null);
521
1457
  const [balanceLoading, setBalanceLoading] = useState3(false);
522
- const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => process.exit(0));
1458
+ const [todayCost, setTodayCost] = useState3(null);
1459
+ const [isStreaming, setIsStreaming] = useState3(false);
1460
+ const [streamingPlaceholder, setStreamingPlaceholder] = useState3("");
1461
+ const [idlePlaceholder, setIdlePlaceholder] = useState3(() => pickRandom(IDLE_PLACEHOLDERS));
1462
+ const [gradientColors, setGradientColors] = useState3([]);
1463
+ const gradientPhaseRef = useRef2(0);
1464
+ const [streamingGradientColors, setStreamingGradientColors] = useState3([]);
1465
+ const streamingPhaseRef = useRef2(0);
1466
+ const [currentContent, setCurrentContent] = useState3("");
1467
+ const [currentToolCalls, setCurrentToolCalls] = useState3([]);
1468
+ const [currentUsage, setCurrentUsage] = useState3(void 0);
1469
+ const [currentElapsed, setCurrentElapsed] = useState3(void 0);
1470
+ const [currentCost, setCurrentCost] = useState3(void 0);
1471
+ const [activeModel, setActiveModel] = useState3(model);
1472
+ const [streamingModel, setStreamingModel] = useState3(void 0);
1473
+ const [streamError, setStreamError] = useState3(void 0);
1474
+ const cmdTips = Array.from(getRegisteredCommands()).filter(([name]) => name !== "/exit" && name !== "/quit").map(([name, cmd]) => ({ name, desc: cmd.desc }));
1475
+ const [cmdTipIndex, setCmdTipIndex] = useState3(0);
1476
+ const [cmdTipGradientColors, setCmdTipGradientColors] = useState3([]);
1477
+ const cmdTipPhaseRef = useRef2(0);
1478
+ const [selectingModel, setSelectingModel] = useState3(false);
1479
+ const [modelSelectIndex, setModelSelectIndex] = useState3(0);
1480
+ const modelOptions = ["deepseek-v4-flash", "deepseek-v4-pro"];
1481
+ const sessionRef = useRef2(null);
1482
+ const abortRef = useRef2(null);
1483
+ const currentContentRef = useRef2("");
1484
+ const currentToolCallsRef = useRef2([]);
1485
+ const currentUsageRef = useRef2(void 0);
1486
+ const currentElapsedRef = useRef2(void 0);
1487
+ const currentCostRef = useRef2(void 0);
1488
+ const currentModelRef = useRef2(void 0);
1489
+ const streamErrorRef = useRef2(void 0);
1490
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
1491
+ process.exit(0);
1492
+ });
523
1493
  useInput(
524
1494
  useCallback2(
525
- (input2, key) => {
526
- if (input2 === "c" && key.ctrl) {
527
- handleCtrlC();
1495
+ (_input, key) => {
1496
+ if (selectingModel) {
1497
+ if (key.upArrow) {
1498
+ setModelSelectIndex((prev) => (prev - 1 + modelOptions.length) % modelOptions.length);
1499
+ } else if (key.downArrow) {
1500
+ setModelSelectIndex((prev) => (prev + 1) % modelOptions.length);
1501
+ } else if (key.return) {
1502
+ const selected = modelOptions[modelSelectIndex];
1503
+ if (selected === activeModel) {
1504
+ setDisplayMessages((prev) => [
1505
+ ...prev,
1506
+ { role: "assistant", content: `\u5DF2\u7ECF\u5728\u4F7F\u7528 ${SUPPORTED_MODELS[selected].displayName}` }
1507
+ ]);
1508
+ } else {
1509
+ setActiveModel(selected);
1510
+ sessionRef.current?.reset();
1511
+ saveModelConfig(selected).catch(() => {
1512
+ });
1513
+ setDisplayMessages((prev) => [
1514
+ ...prev,
1515
+ { role: "assistant", content: `\u6A21\u578B\u5DF2\u5207\u6362\u4E3A ${SUPPORTED_MODELS[selected].displayName}\uFF08${selected}\uFF09` }
1516
+ ]);
1517
+ }
1518
+ setSelectingModel(false);
1519
+ } else if (key.escape) {
1520
+ setSelectingModel(false);
1521
+ }
1522
+ return;
1523
+ }
1524
+ if (key.ctrl && _input === "c") {
1525
+ if (isStreaming) {
1526
+ abortRef.current?.abort();
1527
+ } else {
1528
+ handleCtrlC();
1529
+ }
1530
+ return;
1531
+ }
1532
+ if (!input && !isStreaming && _input) {
1533
+ setInput(_input);
528
1534
  }
529
1535
  },
530
- [handleCtrlC]
1536
+ [selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input]
531
1537
  )
532
1538
  );
1539
+ useEffect3(() => {
1540
+ if (cmdTips.length <= 1) return;
1541
+ const timer = setInterval(() => {
1542
+ setCmdTipIndex((prev) => (prev + 1) % cmdTips.length);
1543
+ }, 5e3);
1544
+ return () => clearInterval(timer);
1545
+ }, [cmdTips.length]);
1546
+ useEffect3(() => {
1547
+ const tip = cmdTips[cmdTipIndex % cmdTips.length];
1548
+ if (!tip) {
1549
+ setCmdTipGradientColors([]);
1550
+ return;
1551
+ }
1552
+ const text = `${tip.name} ${tip.desc}`;
1553
+ cmdTipPhaseRef.current = 0;
1554
+ setCmdTipGradientColors(getGradientColors(text, 1, CMD_TIP_GRADIENT_STOPS));
1555
+ const interval = setInterval(() => {
1556
+ cmdTipPhaseRef.current = (cmdTipPhaseRef.current + GRADIENT_ANIMATION.cmdTipPhaseStep) % 1;
1557
+ setCmdTipGradientColors(getGradientColors(text, 1 - cmdTipPhaseRef.current, CMD_TIP_GRADIENT_STOPS));
1558
+ }, GRADIENT_ANIMATION.cmdTipInterval);
1559
+ return () => clearInterval(interval);
1560
+ }, [cmdTipIndex, cmdTips.length]);
533
1561
  useEffect3(() => {
534
1562
  const timer = setInterval(() => {
535
1563
  setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
536
1564
  }, 500);
537
1565
  return () => clearInterval(timer);
538
1566
  }, []);
1567
+ useEffect3(() => {
1568
+ if (!apiKey || !baseUrl) return;
1569
+ const provider = createProvider({
1570
+ name: "deepseek",
1571
+ apiKey,
1572
+ baseUrl,
1573
+ model: activeModel
1574
+ });
1575
+ const tracker = externalCostTracker ?? new CostTracker();
1576
+ const session = new Session(provider, [], tracker, {
1577
+ cwd: process.cwd()
1578
+ });
1579
+ sessionRef.current = session;
1580
+ return () => {
1581
+ sessionRef.current = null;
1582
+ };
1583
+ }, [apiKey, baseUrl, activeModel, externalCostTracker]);
539
1584
  useEffect3(() => {
540
1585
  if (!apiKey || !baseUrl) return;
541
1586
  let cancelled = false;
542
1587
  setBalanceLoading(true);
543
- import("./deepseek-7Y2G6EKM.js").then(({ DeepSeekProvider }) => {
544
- const provider = new DeepSeekProvider({
1588
+ import("./deepseek-OIJOG3N5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
1589
+ const provider = new DeepSeekProvider2({
545
1590
  apiKey,
546
1591
  baseUrl,
547
1592
  model: "deepseek-v4-flash"
@@ -561,10 +1606,61 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
561
1606
  cancelled = true;
562
1607
  };
563
1608
  }, [apiKey, baseUrl]);
564
- const handleSubmit = useCallback2((value) => {
1609
+ useEffect3(() => {
1610
+ if (!externalCostTracker) return;
1611
+ let cancelled = false;
1612
+ let timer;
1613
+ const refresh = () => {
1614
+ setTodayCost(externalCostTracker.todayTotalCost);
1615
+ };
1616
+ externalCostTracker.load().then(() => {
1617
+ if (cancelled) return;
1618
+ refresh();
1619
+ timer = setInterval(refresh, 5e3);
1620
+ }).catch(() => {
1621
+ });
1622
+ return () => {
1623
+ cancelled = true;
1624
+ if (timer) clearInterval(timer);
1625
+ };
1626
+ }, [externalCostTracker]);
1627
+ useEffect3(() => {
1628
+ if (isStreaming || !idlePlaceholder) {
1629
+ setGradientColors([]);
1630
+ return;
1631
+ }
1632
+ gradientPhaseRef.current = 0;
1633
+ setGradientColors(getGradientColors(idlePlaceholder, 1, IDLE_GRADIENT_STOPS));
1634
+ const interval = setInterval(() => {
1635
+ gradientPhaseRef.current = (gradientPhaseRef.current + GRADIENT_ANIMATION.idlePhaseStep) % 1;
1636
+ setGradientColors(getGradientColors(idlePlaceholder, 1 - gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
1637
+ }, GRADIENT_ANIMATION.idleInterval);
1638
+ return () => clearInterval(interval);
1639
+ }, [isStreaming, idlePlaceholder]);
1640
+ useEffect3(() => {
1641
+ if (!isStreaming || !streamingPlaceholder) {
1642
+ setStreamingGradientColors([]);
1643
+ return;
1644
+ }
1645
+ streamingPhaseRef.current = 0;
1646
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1, STREAMING_GRADIENT_STOPS));
1647
+ const interval = setInterval(() => {
1648
+ streamingPhaseRef.current = (streamingPhaseRef.current + GRADIENT_ANIMATION.streamingPhaseStep) % 1;
1649
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1 - streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
1650
+ }, GRADIENT_ANIMATION.streamingInterval);
1651
+ return () => clearInterval(interval);
1652
+ }, [isStreaming, streamingPlaceholder]);
1653
+ const handleSubmit = useCallback2(async (value) => {
565
1654
  const trimmed = value.trim();
566
1655
  if (!trimmed) return;
567
1656
  if (trimmed.startsWith("/")) {
1657
+ if (trimmed.toLowerCase() === "/model") {
1658
+ const curIdx = modelOptions.indexOf(activeModel);
1659
+ setModelSelectIndex(curIdx >= 0 ? curIdx : 0);
1660
+ setSelectingModel(true);
1661
+ setInput("");
1662
+ return;
1663
+ }
568
1664
  const cmd = commandRegistry.get(trimmed.toLowerCase());
569
1665
  if (cmd) {
570
1666
  const result = cmd.handler();
@@ -573,8 +1669,9 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
573
1669
  process.exit(0);
574
1670
  return;
575
1671
  case "clear":
576
- setMessages([]);
1672
+ setDisplayMessages([]);
577
1673
  setInput("");
1674
+ sessionRef.current?.reset();
578
1675
  return;
579
1676
  case "navigate":
580
1677
  setInput("");
@@ -585,7 +1682,7 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
585
1682
  }
586
1683
  return;
587
1684
  case "text":
588
- setMessages((prev) => [
1685
+ setDisplayMessages((prev) => [
589
1686
  ...prev,
590
1687
  { role: "user", content: trimmed },
591
1688
  { role: "assistant", content: result.content }
@@ -594,7 +1691,7 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
594
1691
  return;
595
1692
  }
596
1693
  }
597
- setMessages((prev) => [
1694
+ setDisplayMessages((prev) => [
598
1695
  ...prev,
599
1696
  { role: "user", content: trimmed },
600
1697
  { role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
@@ -602,64 +1699,263 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
602
1699
  setInput("");
603
1700
  return;
604
1701
  }
605
- setMessages((prev) => [
1702
+ if (!sessionRef.current) {
1703
+ setDisplayMessages((prev) => [
1704
+ ...prev,
1705
+ { role: "user", content: trimmed },
1706
+ { role: "assistant", content: "\u26A0 \u65E0\u6CD5\u8FDE\u63A5\u5230 Provider\u3002\u8BF7\u68C0\u67E5 API Key \u548C\u7F51\u7EDC\u914D\u7F6E\u3002" }
1707
+ ]);
1708
+ setInput("");
1709
+ return;
1710
+ }
1711
+ setDisplayMessages((prev) => [
606
1712
  ...prev,
607
- { role: "user", content: trimmed },
608
- { role: "assistant", content: "dskcode AI \u2014 \u5F85\u5B9E\u73B0\uFF08\u7B2C07\u7AE0\uFF09\u3002\u5F53\u524D\u4E3A CLI \u6846\u67B6\u6F14\u793A\u6A21\u5F0F\u3002" }
1713
+ { role: "user", content: trimmed }
609
1714
  ]);
610
1715
  setInput("");
611
- }, [onLaunchGame, onLaunchStock]);
612
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
613
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", marginBottom: 1, children: [
614
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
1716
+ setIsStreaming(true);
1717
+ setStreamingPlaceholder(pickRandom(STREAMING_PLACEHOLDERS));
1718
+ setCurrentContent("");
1719
+ setCurrentToolCalls([]);
1720
+ setCurrentUsage(void 0);
1721
+ setCurrentElapsed(void 0);
1722
+ setCurrentCost(void 0);
1723
+ setStreamingModel(void 0);
1724
+ setStreamError(void 0);
1725
+ currentContentRef.current = "";
1726
+ currentToolCallsRef.current = [];
1727
+ currentUsageRef.current = void 0;
1728
+ currentElapsedRef.current = void 0;
1729
+ currentCostRef.current = void 0;
1730
+ currentModelRef.current = void 0;
1731
+ streamErrorRef.current = void 0;
1732
+ const session = sessionRef.current;
1733
+ const abortController = new AbortController();
1734
+ abortRef.current = abortController;
1735
+ try {
1736
+ for await (const event of session.chat(trimmed)) {
1737
+ if (abortController.signal.aborted) break;
1738
+ switch (event.type) {
1739
+ case "text_delta":
1740
+ setCurrentContent((prev) => {
1741
+ const next = prev + event.content;
1742
+ currentContentRef.current = next;
1743
+ return next;
1744
+ });
1745
+ break;
1746
+ case "tool_calls":
1747
+ setCurrentToolCalls((prev) => {
1748
+ const next = [...prev, ...event.calls];
1749
+ currentToolCallsRef.current = next;
1750
+ return next;
1751
+ });
1752
+ break;
1753
+ case "usage":
1754
+ setCurrentUsage(event.usage);
1755
+ setStreamingModel(event.model);
1756
+ currentUsageRef.current = event.usage;
1757
+ currentModelRef.current = event.model;
1758
+ break;
1759
+ case "done":
1760
+ setCurrentElapsed(event.elapsed);
1761
+ currentElapsedRef.current = event.elapsed;
1762
+ break;
1763
+ case "error":
1764
+ setStreamError(event.error.message);
1765
+ streamErrorRef.current = event.error.message;
1766
+ break;
1767
+ }
1768
+ }
1769
+ } catch (err) {
1770
+ const msg = err instanceof Error ? err.message : String(err);
1771
+ setStreamError(msg);
1772
+ streamErrorRef.current = msg;
1773
+ } finally {
1774
+ setIsStreaming(false);
1775
+ setIdlePlaceholder(pickRandom(IDLE_PLACEHOLDERS));
1776
+ abortRef.current = null;
1777
+ const finContent = currentContentRef.current;
1778
+ const finToolCalls = currentToolCallsRef.current.length > 0 ? currentToolCallsRef.current : void 0;
1779
+ const finStreamError = streamErrorRef.current;
1780
+ if (finContent || finToolCalls || finStreamError) {
1781
+ const completed = {
1782
+ content: finStreamError ? `\u26A0 \u8BF7\u6C42\u51FA\u9519\uFF1A${finStreamError}` : finContent || "",
1783
+ toolCalls: finToolCalls,
1784
+ usage: currentUsageRef.current,
1785
+ elapsed: currentElapsedRef.current,
1786
+ cost: currentCostRef.current,
1787
+ model: currentModelRef.current
1788
+ };
1789
+ setDisplayMessages((prev) => [
1790
+ ...prev,
1791
+ {
1792
+ role: "assistant",
1793
+ content: completed.content,
1794
+ assistantDetail: completed
1795
+ }
1796
+ ]);
1797
+ }
1798
+ }
1799
+ }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls]);
1800
+ useEffect3(() => {
1801
+ if (!isStreaming && externalCostTracker) {
1802
+ setTodayCost(externalCostTracker.todayTotalCost);
1803
+ }
1804
+ }, [isStreaming, externalCostTracker]);
1805
+ useEffect3(() => {
1806
+ if (currentUsage && streamingModel && !currentCost) {
1807
+ import("./models-NQGNKB4T.js").then(({ calculateCost: calculateCost2 }) => {
1808
+ const cost = calculateCost2(currentUsage, streamingModel);
1809
+ setCurrentCost(cost.totalCost);
1810
+ currentCostRef.current = cost.totalCost;
1811
+ });
1812
+ }
1813
+ }, [currentUsage, streamingModel, currentCost]);
1814
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
1815
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
1816
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
615
1817
  const colorIndex = (i + offset) % CYBER_PALETTE.length;
616
- return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
1818
+ return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
617
1819
  }) }),
618
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", justifyContent: "center", children: [
619
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ff41", children: [
1820
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", justifyContent: "center", children: [
1821
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ff41", children: [
620
1822
  " \u2714 ",
621
1823
  "\u5DF2\u52A0\u8F7D ",
622
1824
  providerCount,
623
1825
  " \u4E2A Provider"
624
1826
  ] }),
625
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", children: [
1827
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
626
1828
  " \u2139 ",
627
1829
  "\u5DF2\u5C31\u7EEA ",
628
1830
  toolCount,
629
1831
  " \u4E2A\u5DE5\u5177"
630
1832
  ] }),
631
- verbose ? /* @__PURE__ */ jsx3(Text4, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
1833
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
1834
+ " \u{1F527} \u6A21\u578B ",
1835
+ SUPPORTED_MODELS[activeModel]?.displayName ?? activeModel
1836
+ ] }),
1837
+ cmdTips.length > 0 && (() => {
1838
+ const tip = cmdTips[cmdTipIndex % cmdTips.length];
1839
+ if (!tip) return null;
1840
+ const text = `${tip.name} ${tip.desc}`;
1841
+ return /* @__PURE__ */ jsxs5(Text6, { children: [
1842
+ /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: " \u{1F4A1} " }),
1843
+ cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: text })
1844
+ ] });
1845
+ })(),
1846
+ verbose ? /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
632
1847
  ] }),
633
- /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, flexDirection: "column", alignItems: "flex-end", justifyContent: "center", children: balanceLoading && balance === null ? /* @__PURE__ */ jsx3(Text4, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
634
- "\u{1F4B0} \xA5",
635
- balance.toFixed(2)
636
- ] }) : null })
1848
+ /* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
1849
+ balanceLoading && balance === null ? /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
1850
+ /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: "\u{1F4B0} " }),
1851
+ /* @__PURE__ */ jsxs5(Text6, { color: "yellow", children: [
1852
+ "\u4F59\u989D \xA5",
1853
+ balance.toFixed(2)
1854
+ ] })
1855
+ ] }) : null,
1856
+ todayCost !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
1857
+ /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: "\u{1F4CA} " }),
1858
+ /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
1859
+ "\u4ECA\u65E5 \xA5",
1860
+ formatMoney(todayCost).replace("\xA5", "")
1861
+ ] })
1862
+ ] }) : null
1863
+ ] })
637
1864
  ] }),
638
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: messages.map((msg, i) => /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
639
- /* @__PURE__ */ jsx3(Box3, { width: 8, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text4, { bold: true, color: msg.role === "user" ? "#00ff41" : "#ff00ff", children: msg.role === "user" ? " \u{1F464}" : " \u{1F916}" }) }),
640
- /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, children: /* @__PURE__ */ jsx3(Text4, { wrap: "wrap", children: msg.content }) })
641
- ] }, i)) }),
642
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
643
- /* @__PURE__ */ jsx3(Box3, { width: 8, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text4, { bold: true, color: "#00ff41", children: " \u26A1" }) }),
644
- /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, children: /* @__PURE__ */ jsx3(
645
- TextInput,
1865
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1866
+ /* @__PURE__ */ jsx5(Static, { items: displayMessages, children: (msg, i) => {
1867
+ if (msg.role === "user") {
1868
+ return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1869
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
1870
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(Text6, { wrap: "wrap", children: msg.content }) })
1871
+ ] }, i);
1872
+ }
1873
+ const detail = msg.assistantDetail;
1874
+ return /* @__PURE__ */ jsx5(
1875
+ AssistantMessage,
1876
+ {
1877
+ content: msg.content,
1878
+ toolCalls: detail?.toolCalls,
1879
+ isStreaming: false,
1880
+ usage: detail?.usage,
1881
+ elapsed: detail?.elapsed,
1882
+ cost: detail?.cost,
1883
+ model: detail?.model
1884
+ },
1885
+ i
1886
+ );
1887
+ } }),
1888
+ isStreaming && /* @__PURE__ */ jsx5(
1889
+ AssistantMessage,
646
1890
  {
647
- value: input,
648
- onChange: setInput,
649
- onSubmit: handleSubmit,
650
- placeholder: "\u8F93\u5165\u4F60\u7684\u95EE\u9898..."
1891
+ content: currentContent,
1892
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
1893
+ isStreaming: true
651
1894
  }
652
- ) })
1895
+ ),
1896
+ isStreaming && !currentContent && currentToolCalls.length === 0 && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 4, children: /* @__PURE__ */ jsx5(Spinner, { type: "dots", label: "\u601D\u8003\u4E2D..." }) }),
1897
+ !isStreaming && streamError && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
1898
+ "\u26A0 ",
1899
+ streamError
1900
+ ] }) })
653
1901
  ] }),
654
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#00ffff", dimColor: true, children: " " + "\u2500".repeat(36) }) }),
655
- doubleCtrlC && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
1902
+ selectingModel ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1903
+ /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
1904
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1905
+ /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
1906
+ modelOptions.map((id, i) => {
1907
+ const meta = SUPPORTED_MODELS[id];
1908
+ const isCurrent = id === activeModel;
1909
+ const isSelected = i === modelSelectIndex;
1910
+ const marker = isSelected ? " > " : " ";
1911
+ const suffix = isCurrent ? " (\u5F53\u524D)" : "";
1912
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
1913
+ /* @__PURE__ */ jsxs5(
1914
+ Text6,
1915
+ {
1916
+ color: isSelected ? "#00ff41" : void 0,
1917
+ bold: isSelected,
1918
+ children: [
1919
+ marker,
1920
+ meta.displayName,
1921
+ suffix
1922
+ ]
1923
+ }
1924
+ ),
1925
+ isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "#808080", children: [
1926
+ " \u2014 ",
1927
+ id
1928
+ ] })
1929
+ ] }, id);
1930
+ }),
1931
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
1932
+ ] }),
1933
+ /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
1934
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1935
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1936
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1937
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
1938
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx5(
1939
+ TextInput,
1940
+ {
1941
+ value: input,
1942
+ onChange: setInput,
1943
+ onSubmit: handleSubmit,
1944
+ placeholder: ""
1945
+ }
1946
+ ) })
1947
+ ] }),
1948
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) })
1949
+ ] }),
1950
+ doubleCtrlC && !isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) }),
1951
+ isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
656
1952
  ] });
657
1953
  }
658
1954
 
659
1955
  // src/ui/GamePicker.tsx
660
- import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
1956
+ import { Box as Box6, Text as Text7, useInput as useInput2 } from "ink";
661
1957
  import { useState as useState4, useCallback as useCallback3 } from "react";
662
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1958
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
663
1959
  function GamePicker({ games, onSelect, onExit, onBackToChat }) {
664
1960
  const [selectedIndex, setSelectedIndex] = useState4(0);
665
1961
  const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
@@ -687,18 +1983,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
687
1983
  [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
688
1984
  )
689
1985
  );
690
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
691
- /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
692
- /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: games.map((game, index) => {
1986
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1987
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
1988
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: games.map((game, index) => {
693
1989
  const isSelected = index === selectedIndex;
694
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
695
- /* @__PURE__ */ jsx4(Box4, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx4(Text5, { children: " " }) }),
696
- /* @__PURE__ */ jsx4(Box4, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
697
- /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: game.description }) })
1990
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", children: [
1991
+ /* @__PURE__ */ jsx6(Box6, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx6(Text7, { children: " " }) }),
1992
+ /* @__PURE__ */ jsx6(Box6, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
1993
+ /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text7, { color: "#888888", children: game.description }) })
698
1994
  ] }, game.id);
699
1995
  }) }),
700
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
701
- doubleCtrlC && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
1996
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
1997
+ doubleCtrlC && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
702
1998
  ] });
703
1999
  }
704
2000
 
@@ -715,9 +2011,9 @@ function listGames() {
715
2011
  }
716
2012
 
717
2013
  // src/game/brick-breaker/index.tsx
718
- import { Box as Box5, Text as Text6, useInput as useInput3, render as render2 } from "ink";
719
- import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback4 } from "react";
720
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2014
+ import { Box as Box7, Text as Text8, useInput as useInput3, render as render2 } from "ink";
2015
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
2016
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
721
2017
  var GAME_WIDTH = 40;
722
2018
  var GAME_HEIGHT = 18;
723
2019
  var PADDLE_WIDTH = 9;
@@ -850,9 +2146,9 @@ function buildBoard(state) {
850
2146
  function BrickBreakerGame({ onExit: _onExit }) {
851
2147
  const [initialLevel, setInitialLevel] = useState5(1);
852
2148
  const [selectingLevel, setSelectingLevel] = useState5(true);
853
- const stateRef = useRef2(createInitialState(initialLevel));
2149
+ const stateRef = useRef3(createInitialState(initialLevel));
854
2150
  const [tick, setTick] = useState5(0);
855
- const onExitRef = useRef2(_onExit);
2151
+ const onExitRef = useRef3(_onExit);
856
2152
  onExitRef.current = _onExit;
857
2153
  useEffect4(() => {
858
2154
  if (selectingLevel) return;
@@ -907,49 +2203,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
907
2203
  const board = buildBoard(s);
908
2204
  const def = getLevel(s.level);
909
2205
  void tick;
910
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
911
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
912
- /* @__PURE__ */ jsx5(Box5, { width: 20, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2206
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2207
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
2208
+ /* @__PURE__ */ jsx7(Box7, { width: 20, children: /* @__PURE__ */ jsxs7(Text8, { children: [
913
2209
  "\u5173\u5361 ",
914
2210
  s.level,
915
2211
  ": ",
916
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: def.desc })
2212
+ /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: def.desc })
917
2213
  ] }) }),
918
- /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2214
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
919
2215
  "\u5206\u6570: ",
920
- /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: String(s.score).padStart(3, "0") })
2216
+ /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: String(s.score).padStart(3, "0") })
921
2217
  ] }) }),
922
- /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2218
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
923
2219
  "\u751F\u547D: ",
924
- /* @__PURE__ */ jsx5(Text6, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
2220
+ /* @__PURE__ */ jsx7(Text8, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
925
2221
  ] }) }),
926
- /* @__PURE__ */ jsx5(Box5, { width: 10, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2222
+ /* @__PURE__ */ jsx7(Box7, { width: 10, children: /* @__PURE__ */ jsxs7(Text8, { children: [
927
2223
  "\u7816\u5757: ",
928
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: aliveCount })
2224
+ /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: aliveCount })
929
2225
  ] }) }),
930
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text6, { color: s.paused ? "gray" : "green", children: [
2226
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: s.paused ? "gray" : "green", children: [
931
2227
  "[",
932
2228
  s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
933
2229
  "]"
934
2230
  ] }) })
935
2231
  ] }),
936
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
937
- /* @__PURE__ */ jsxs5(Text6, { children: [
2232
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2233
+ /* @__PURE__ */ jsxs7(Text8, { children: [
938
2234
  "\u250C",
939
2235
  "\u2500".repeat(GAME_WIDTH),
940
2236
  "\u2510"
941
2237
  ] }),
942
- /* @__PURE__ */ jsx5(Text6, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
943
- /* @__PURE__ */ jsxs5(Text6, { children: [
2238
+ /* @__PURE__ */ jsx7(Text8, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
2239
+ /* @__PURE__ */ jsxs7(Text8, { children: [
944
2240
  "\u2514",
945
2241
  "\u2500".repeat(GAME_WIDTH),
946
2242
  "\u2518"
947
2243
  ] })
948
2244
  ] }),
949
- selectingLevel && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
950
- /* @__PURE__ */ jsx5(Text6, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
951
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx5(Box5, { width: 22, children: /* @__PURE__ */ jsxs5(Text6, { children: [
952
- /* @__PURE__ */ jsx5(Text6, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
2245
+ selectingLevel && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2246
+ /* @__PURE__ */ jsx7(Text8, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
2247
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx7(Box7, { width: 22, children: /* @__PURE__ */ jsxs7(Text8, { children: [
2248
+ /* @__PURE__ */ jsx7(Text8, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
953
2249
  ". ",
954
2250
  lv.desc,
955
2251
  " (",
@@ -958,16 +2254,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
958
2254
  lv.cols,
959
2255
  ")"
960
2256
  ] }) }, i)) }),
961
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
2257
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
962
2258
  ] }),
963
- !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
964
- /* @__PURE__ */ jsx5(Text6, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
965
- /* @__PURE__ */ jsxs5(Text6, { children: [
2259
+ !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
2260
+ /* @__PURE__ */ jsx7(Text8, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
2261
+ /* @__PURE__ */ jsxs7(Text8, { children: [
966
2262
  " \u5206\u6570: ",
967
- /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: s.score })
2263
+ /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: s.score })
968
2264
  ] })
969
2265
  ] }),
970
- !selectingLevel && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx5(Text6, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx5(Text6, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
2266
+ !selectingLevel && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
971
2267
  ] });
972
2268
  }
973
2269
  var brick_breaker_default = {
@@ -977,7 +2273,7 @@ var brick_breaker_default = {
977
2273
  play: async () => {
978
2274
  await new Promise((resolve) => {
979
2275
  const { unmount } = render2(
980
- /* @__PURE__ */ jsx5(BrickBreakerGame, { onExit: () => {
2276
+ /* @__PURE__ */ jsx7(BrickBreakerGame, { onExit: () => {
981
2277
  unmount();
982
2278
  resolve();
983
2279
  } })
@@ -987,9 +2283,9 @@ var brick_breaker_default = {
987
2283
  };
988
2284
 
989
2285
  // src/game/coder-check/index.tsx
990
- import { Box as Box6, Text as Text7, useInput as useInput4, render as render3 } from "ink";
991
- import { useState as useState6, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback5 } from "react";
992
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2286
+ import { Box as Box8, Text as Text9, useInput as useInput4, render as render3 } from "ink";
2287
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback5 } from "react";
2288
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
993
2289
  var GAME_W = 66;
994
2290
  var GAME_H = 20;
995
2291
  var SCORE_H = 6;
@@ -1380,10 +2676,10 @@ function buildGameView(s, scoreLines, scoreColor, message) {
1380
2676
  return rows;
1381
2677
  }
1382
2678
  function CoderCheck({ onExit: _onExit }) {
1383
- const stateRef = useRef3(createInitialState2());
2679
+ const stateRef = useRef4(createInitialState2());
1384
2680
  const [tick, setTick] = useState6(0);
1385
2681
  const [colorOffset, setColorOffset] = useState6(0);
1386
- const onExitRef = useRef3(_onExit);
2682
+ const onExitRef = useRef4(_onExit);
1387
2683
  onExitRef.current = _onExit;
1388
2684
  useEffect5(() => {
1389
2685
  const timer = setInterval(() => {
@@ -1460,44 +2756,44 @@ function CoderCheck({ onExit: _onExit }) {
1460
2756
  const scoreLines = buildScoreLines(scoreStr);
1461
2757
  const view = buildGameView(s, scoreLines, scoreColor, s.message);
1462
2758
  void tick;
1463
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
1464
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "row", children: /* @__PURE__ */ jsxs6(Text7, { children: [
2759
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
2760
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "row", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1465
2761
  "\u751F\u547D ",
1466
- /* @__PURE__ */ jsx6(Text7, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
2762
+ /* @__PURE__ */ jsx8(Text9, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
1467
2763
  " ",
1468
2764
  "\u901F\u5EA6 ",
1469
- /* @__PURE__ */ jsxs6(Text7, { color: "cyan", children: [
2765
+ /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
1470
2766
  "Lv.",
1471
2767
  Math.floor(s.speed * 10)
1472
2768
  ] })
1473
2769
  ] }) }),
1474
- !s.gameOver && s.target && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { children: [
2770
+ !s.gameOver && s.target && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
1475
2771
  "\u6253\u5B57: ",
1476
- /* @__PURE__ */ jsx6(Text7, { color: "green", children: s.typed }),
1477
- /* @__PURE__ */ jsx6(Text7, { color: "white", children: s.target.slice(s.typed.length) })
2772
+ /* @__PURE__ */ jsx8(Text9, { color: "green", children: s.typed }),
2773
+ /* @__PURE__ */ jsx8(Text9, { color: "white", children: s.target.slice(s.typed.length) })
1478
2774
  ] }) }),
1479
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
1480
- /* @__PURE__ */ jsxs6(Text7, { children: [
2775
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
2776
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1481
2777
  "\u250C",
1482
2778
  "\u2500".repeat(GAME_W),
1483
2779
  "\u2510"
1484
2780
  ] }),
1485
- view.map((row, i) => /* @__PURE__ */ jsx6(Text7, { children: `\u2502${row}\u2502` }, i)),
1486
- /* @__PURE__ */ jsxs6(Text7, { children: [
2781
+ view.map((row, i) => /* @__PURE__ */ jsx8(Text9, { children: `\u2502${row}\u2502` }, i)),
2782
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1487
2783
  "\u2514",
1488
2784
  "\u2500".repeat(GAME_W),
1489
2785
  "\u2518"
1490
2786
  ] })
1491
2787
  ] }),
1492
- s.gameOver && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1493
- /* @__PURE__ */ jsx6(Text7, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
1494
- /* @__PURE__ */ jsxs6(Text7, { children: [
2788
+ s.gameOver && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
2789
+ /* @__PURE__ */ jsx8(Text9, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
2790
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1495
2791
  " \u5F97\u5206: ",
1496
- /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: s.score }),
2792
+ /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: s.score }),
1497
2793
  " r \u91CD\u5F00 q \u9000\u51FA"
1498
2794
  ] })
1499
2795
  ] }),
1500
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
2796
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
1501
2797
  ] });
1502
2798
  }
1503
2799
  var coder_check_default = {
@@ -1507,7 +2803,7 @@ var coder_check_default = {
1507
2803
  play: async () => {
1508
2804
  await new Promise((resolve) => {
1509
2805
  const { unmount } = render3(
1510
- /* @__PURE__ */ jsx6(CoderCheck, { onExit: () => {
2806
+ /* @__PURE__ */ jsx8(CoderCheck, { onExit: () => {
1511
2807
  unmount();
1512
2808
  resolve();
1513
2809
  } })
@@ -1528,10 +2824,10 @@ import { render as render4 } from "ink";
1528
2824
  import chalk3 from "chalk";
1529
2825
 
1530
2826
  // src/stock/StockList.tsx
1531
- import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
2827
+ import { Box as Box9, Text as Text10, useInput as useInput5 } from "ink";
1532
2828
  import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
1533
2829
  import asciichart from "asciichart";
1534
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2830
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1535
2831
  var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
1536
2832
  function normalizeApiCode(code) {
1537
2833
  if (code.startsWith("sh") || code.startsWith("sz")) return code;
@@ -1728,57 +3024,57 @@ function StockList({ codes, onExit, onBackToChat }) {
1728
3024
  );
1729
3025
  if (detailView) {
1730
3026
  if (detailLoading) {
1731
- return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
3027
+ return /* @__PURE__ */ jsx9(Box9, { paddingLeft: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
1732
3028
  }
1733
3029
  return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
1734
3030
  }
1735
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1736
- /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1737
- /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
1738
- /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
3031
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3032
+ /* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
3033
+ /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
3034
+ /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1739
3035
  " \u{1F550} ",
1740
3036
  currentTime
1741
3037
  ] }),
1742
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
3038
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
1743
3039
  ] }),
1744
- /* @__PURE__ */ jsxs7(Box7, { children: [
1745
- /* @__PURE__ */ jsx7(Box7, { width: 3 }),
1746
- /* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u4EE3\u7801" }) }),
1747
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u540D\u79F0" }) }),
1748
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
1749
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
1750
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
1751
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u9AD8" }) }),
1752
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u4F4E" }) }),
1753
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
3040
+ /* @__PURE__ */ jsxs9(Box9, { children: [
3041
+ /* @__PURE__ */ jsx9(Box9, { width: 3 }),
3042
+ /* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u4EE3\u7801" }) }),
3043
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u540D\u79F0" }) }),
3044
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
3045
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
3046
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
3047
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u9AD8" }) }),
3048
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u4F4E" }) }),
3049
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
1754
3050
  ] }),
1755
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
1756
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: stocks.map((stock, index) => {
3051
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
3052
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: stocks.map((stock, index) => {
1757
3053
  const isSelected = index === selectedIndex;
1758
3054
  const isUp = stock.changePercent >= 0;
1759
3055
  const color = isUp ? "#ff1493" : "#00ff41";
1760
- return /* @__PURE__ */ jsxs7(Box7, { children: [
1761
- /* @__PURE__ */ jsx7(Box7, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx7(Text8, { children: " " }) }),
1762
- /* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
1763
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
1764
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color, children: formatPrice(stock.price) }) }),
1765
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
3056
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
3057
+ /* @__PURE__ */ jsx9(Box9, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx9(Text10, { children: " " }) }),
3058
+ /* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
3059
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
3060
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color, children: formatPrice(stock.price) }) }),
3061
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
1766
3062
  isUp ? "+" : "",
1767
3063
  stock.changePercent.toFixed(2),
1768
3064
  "%"
1769
3065
  ] }) }),
1770
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
3066
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
1771
3067
  isUp ? "+" : "",
1772
3068
  stock.changeAmount.toFixed(3)
1773
3069
  ] }) }),
1774
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#cccccc", children: formatPrice(stock.high) }) }),
1775
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatPrice(stock.low) }) }),
1776
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatAmount(stock.amount) }) })
3070
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#cccccc", children: formatPrice(stock.high) }) }),
3071
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatPrice(stock.low) }) }),
3072
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatAmount(stock.amount) }) })
1777
3073
  ] }, stock.code);
1778
3074
  }) }),
1779
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 q \u8FD4\u56DE` }) }),
1780
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
1781
- doubleCtrlC && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
3075
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 q \u8FD4\u56DE` }) }),
3076
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
3077
+ doubleCtrlC && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
1782
3078
  ] });
1783
3079
  }
1784
3080
  function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
@@ -1796,33 +3092,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1796
3092
  raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
1797
3093
  chartLines = raw.split("\n");
1798
3094
  }
1799
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
1800
- /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1801
- /* @__PURE__ */ jsxs7(Box7, { children: [
1802
- /* @__PURE__ */ jsxs7(Text8, { bold: true, color: "#00ffff", children: [
3095
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, children: [
3096
+ /* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
3097
+ /* @__PURE__ */ jsxs9(Box9, { children: [
3098
+ /* @__PURE__ */ jsxs9(Text10, { bold: true, color: "#00ffff", children: [
1803
3099
  " \u{1F4CA} ",
1804
3100
  stock.name,
1805
3101
  " "
1806
3102
  ] }),
1807
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code }),
1808
- currentTime && /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
3103
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: stock.code }),
3104
+ currentTime && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1809
3105
  " \u{1F550} ",
1810
3106
  currentTime
1811
3107
  ] })
1812
3108
  ] }),
1813
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
3109
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
1814
3110
  ] }),
1815
- /* @__PURE__ */ jsxs7(Box7, { children: [
1816
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
1817
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { bold: true, color: colorCode, children: [
3111
+ /* @__PURE__ */ jsxs9(Box9, { children: [
3112
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
3113
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { bold: true, color: colorCode, children: [
1818
3114
  arrow,
1819
3115
  " ",
1820
3116
  formatPrice(stock.price)
1821
3117
  ] }) })
1822
3118
  ] }),
1823
- /* @__PURE__ */ jsxs7(Box7, { children: [
1824
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
1825
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: colorCode, children: [
3119
+ /* @__PURE__ */ jsxs9(Box9, { children: [
3120
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
3121
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: colorCode, children: [
1826
3122
  isUp ? "+" : "",
1827
3123
  stock.changePercent.toFixed(2),
1828
3124
  "%",
@@ -1831,13 +3127,13 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1831
3127
  stock.changeAmount.toFixed(3)
1832
3128
  ] }) })
1833
3129
  ] }),
1834
- chartLines.length > 0 && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { color: colorCode, children: line || " " }) }, i)) }),
1835
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
3130
+ chartLines.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: colorCode, children: line || " " }) }, i)) }),
3131
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
1836
3132
  ] });
1837
3133
  }
1838
3134
 
1839
3135
  // src/cli/index.tsx
1840
- import { jsx as jsx8 } from "react/jsx-runtime";
3136
+ import { jsx as jsx10 } from "react/jsx-runtime";
1841
3137
  var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
1842
3138
  function createCli() {
1843
3139
  const program2 = new Command();
@@ -1863,14 +3159,19 @@ function createCli() {
1863
3159
  const result = await loadAndValidate();
1864
3160
  ctx = { ...ctx, config: result.config };
1865
3161
  }
1866
- startChat(ctx);
3162
+ const costTracker = new CostTracker({
3163
+ budgetLimit: ctx?.config.budgetLimit ?? 0,
3164
+ tokenBudgetLimit: ctx?.config.tokenBudgetLimit ?? 0
3165
+ });
3166
+ startChat(ctx, costTracker);
1867
3167
  });
1868
- function startChat(ctx) {
3168
+ function startChat(ctx, costTracker) {
1869
3169
  const defaultProvider = ctx?.config.providers.find(
1870
3170
  (p) => p.name === (ctx?.config.defaultProvider ?? "deepseek")
1871
3171
  );
3172
+ const model = defaultProvider?.model ?? "deepseek-v4-flash";
1872
3173
  const chatApp = renderApp(
1873
- /* @__PURE__ */ jsx8(
3174
+ /* @__PURE__ */ jsx10(
1874
3175
  ChatSession,
1875
3176
  {
1876
3177
  providerCount: ctx?.config.providers.length ?? 1,
@@ -1878,24 +3179,26 @@ function createCli() {
1878
3179
  verbose: ctx?.verbose ?? false,
1879
3180
  apiKey: defaultProvider?.apiKey,
1880
3181
  baseUrl: defaultProvider?.baseUrl ?? "https://api.deepseek.com",
3182
+ costTracker,
3183
+ model,
1881
3184
  onLaunchGame: () => {
1882
3185
  chatApp.unmount();
1883
3186
  setImmediate(() => {
1884
3187
  initGames();
1885
3188
  const games = listGames();
1886
3189
  const { unmount } = render4(
1887
- /* @__PURE__ */ jsx8(
3190
+ /* @__PURE__ */ jsx10(
1888
3191
  GamePicker,
1889
3192
  {
1890
3193
  games,
1891
3194
  onSelect: async (game) => {
1892
3195
  unmount();
1893
3196
  await game.play();
1894
- startChat(ctx);
3197
+ startChat(ctx, costTracker);
1895
3198
  },
1896
3199
  onBackToChat: () => {
1897
3200
  unmount();
1898
- setImmediate(() => startChat(ctx));
3201
+ setImmediate(() => startChat(ctx, costTracker));
1899
3202
  }
1900
3203
  }
1901
3204
  ),
@@ -1908,13 +3211,13 @@ function createCli() {
1908
3211
  setImmediate(() => {
1909
3212
  const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1910
3213
  const stockApp = renderApp(
1911
- /* @__PURE__ */ jsx8(
3214
+ /* @__PURE__ */ jsx10(
1912
3215
  StockList,
1913
3216
  {
1914
3217
  codes: defaultStockCodes,
1915
3218
  onBackToChat: () => {
1916
3219
  stockApp.unmount();
1917
- setImmediate(() => startChat(ctx));
3220
+ setImmediate(() => startChat(ctx, costTracker));
1918
3221
  },
1919
3222
  onExit: () => process.exit(0)
1920
3223
  }
@@ -1972,10 +3275,10 @@ compdef _dskcode_completion dskcode`);
1972
3275
  program2.command("stock").description("\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5").argument("[codes...]", "\u80A1\u7968\u4EE3\u7801\uFF08\u7A7A\u683C\u5206\u9694\uFF09\uFF0C\u5982 513090 600519").action(async function(codes) {
1973
3276
  const ctx = this.dskcodeCtx;
1974
3277
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
1975
- const globalConfigPath = join2(home, ".dskcode", "settings.json");
3278
+ const globalConfigPath = join3(home, ".dskcode", "settings.json");
1976
3279
  let globalConfigHasStock = false;
1977
3280
  try {
1978
- const raw = await readFile2(globalConfigPath, "utf-8");
3281
+ const raw = await readFile3(globalConfigPath, "utf-8");
1979
3282
  const parsed = JSON.parse(raw);
1980
3283
  const stock = parsed.stock;
1981
3284
  globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
@@ -1995,7 +3298,7 @@ compdef _dskcode_completion dskcode`);
1995
3298
  const freshResult = await loadAndValidate();
1996
3299
  const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1997
3300
  const app = renderApp(
1998
- /* @__PURE__ */ jsx8(
3301
+ /* @__PURE__ */ jsx10(
1999
3302
  StockList,
2000
3303
  {
2001
3304
  codes: codeList,
@@ -2024,7 +3327,7 @@ compdef _dskcode_completion dskcode`);
2024
3327
  }
2025
3328
  const selectedGame = await new Promise((resolve) => {
2026
3329
  const { unmount } = render4(
2027
- /* @__PURE__ */ jsx8(
3330
+ /* @__PURE__ */ jsx10(
2028
3331
  GamePicker,
2029
3332
  {
2030
3333
  games,