dskcode 0.1.10 → 0.1.11

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,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DeepSeekProvider,
4
+ ModelNotSupportedError
5
+ } from "./chunk-FODNQIT4.js";
6
+ import {
7
+ calculateCost,
8
+ estimateTokens,
9
+ getModelMeta,
10
+ isSupportedModel
11
+ } from "./chunk-DIZMSV5H.js";
2
12
 
3
13
  // src/cli/index.tsx
4
14
  import { Command } from "commander";
@@ -65,6 +75,12 @@ function mergeConfig(base, overlay) {
65
75
  if (overlay.maxToolRounds !== void 0) {
66
76
  result.maxToolRounds = overlay.maxToolRounds;
67
77
  }
78
+ if (overlay.budgetLimit !== void 0) {
79
+ result.budgetLimit = overlay.budgetLimit;
80
+ }
81
+ if (overlay.tokenBudgetLimit !== void 0) {
82
+ result.tokenBudgetLimit = overlay.tokenBudgetLimit;
83
+ }
68
84
  if (overlay.providers !== void 0) {
69
85
  result.providers = overlay.providers;
70
86
  }
@@ -85,7 +101,9 @@ var ENV_MAP = {
85
101
  [`${ENV_PREFIX}VERBOSE`]: "verbose",
86
102
  [`${ENV_PREFIX}MAX_TOKENS`]: "maxTokens",
87
103
  [`${ENV_PREFIX}TEMPERATURE`]: "temperature",
88
- [`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds"
104
+ [`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds",
105
+ [`${ENV_PREFIX}BUDGET_LIMIT`]: "budgetLimit",
106
+ [`${ENV_PREFIX}TOKEN_BUDGET_LIMIT`]: "tokenBudgetLimit"
89
107
  };
90
108
  function applyEnvVars(config) {
91
109
  const result = { ...config, providers: [...config.providers] };
@@ -99,9 +117,11 @@ function applyEnvVars(config) {
99
117
  break;
100
118
  }
101
119
  case "maxTokens":
102
- case "maxToolRounds": {
120
+ case "maxToolRounds":
121
+ case "budgetLimit":
122
+ case "tokenBudgetLimit": {
103
123
  const n = Number(raw);
104
- if (Number.isFinite(n) && n > 0) {
124
+ if (Number.isFinite(n) && n >= 0) {
105
125
  result[configKey] = n;
106
126
  }
107
127
  break;
@@ -156,6 +176,12 @@ function applyCliOverrides(config, flags) {
156
176
  if (flags.temperature !== void 0 && flags.temperature >= 0 && flags.temperature <= 2) {
157
177
  result.temperature = flags.temperature;
158
178
  }
179
+ if (flags.budgetLimit !== void 0 && flags.budgetLimit >= 0) {
180
+ result.budgetLimit = flags.budgetLimit;
181
+ }
182
+ if (flags.tokenBudgetLimit !== void 0 && flags.tokenBudgetLimit >= 0) {
183
+ result.tokenBudgetLimit = flags.tokenBudgetLimit;
184
+ }
159
185
  return result;
160
186
  }
161
187
  function validateConfig(config) {
@@ -166,7 +192,7 @@ function validateConfig(config) {
166
192
  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
193
  });
168
194
  }
169
- const SUPPORTED_MODELS = ["deepseek-v4-flash", "deepseek-v4-pro"];
195
+ const SUPPORTED_MODELS2 = ["deepseek-v4-flash", "deepseek-v4-pro"];
170
196
  for (let i = 0; i < config.providers.length; i++) {
171
197
  const p = config.providers[i];
172
198
  if (!p.name) {
@@ -180,10 +206,10 @@ function validateConfig(config) {
180
206
  field: `providers[${i}].model`,
181
207
  message: `Provider "${p.name || i}" \u7F3A\u5C11 model \u5B57\u6BB5\u3002`
182
208
  });
183
- } else if (!SUPPORTED_MODELS.includes(p.model)) {
209
+ } else if (!SUPPORTED_MODELS2.includes(p.model)) {
184
210
  errors.push({
185
211
  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(", ")}`
212
+ message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${SUPPORTED_MODELS2.join(", ")}`
187
213
  });
188
214
  }
189
215
  }
@@ -216,6 +242,18 @@ function validateConfig(config) {
216
242
  message: "maxToolRounds \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
217
243
  });
218
244
  }
245
+ if (config.budgetLimit !== void 0 && config.budgetLimit < 0) {
246
+ errors.push({
247
+ field: "budgetLimit",
248
+ message: "budgetLimit \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 0\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09\u3002"
249
+ });
250
+ }
251
+ if (config.tokenBudgetLimit !== void 0 && config.tokenBudgetLimit < 0) {
252
+ errors.push({
253
+ field: "tokenBudgetLimit",
254
+ message: "tokenBudgetLimit \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 0\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09\u3002"
255
+ });
256
+ }
219
257
  return errors;
220
258
  }
221
259
  async function loadConfig(configPath) {
@@ -421,8 +459,8 @@ async function promptForApiKey() {
421
459
  }
422
460
 
423
461
  // src/cli/index.tsx
424
- import { readFile as readFile2 } from "fs/promises";
425
- import { join as join2 } from "path";
462
+ import { readFile as readFile3 } from "fs/promises";
463
+ import { join as join3 } from "path";
426
464
 
427
465
  // src/ui/RenderScope.tsx
428
466
  import { render } from "ink";
@@ -435,6 +473,15 @@ function renderApp(node) {
435
473
  import { Text } from "ink";
436
474
  import InkSpinner from "ink-spinner";
437
475
  import { jsx, jsxs } from "react/jsx-runtime";
476
+ function Spinner({ type = "dots", label }) {
477
+ return /* @__PURE__ */ jsxs(Text, { children: [
478
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(InkSpinner, { type }) }),
479
+ label ? /* @__PURE__ */ jsxs(Text, { children: [
480
+ " ",
481
+ label
482
+ ] }) : null
483
+ ] });
484
+ }
438
485
 
439
486
  // src/ui/StatusMessage.tsx
440
487
  import { Box, Text as Text2 } from "ink";
@@ -455,9 +502,9 @@ var LOGO_LINES = [
455
502
  ];
456
503
 
457
504
  // src/ui/ChatSession.tsx
458
- import { Box as Box3, Text as Text4, useInput } from "ink";
505
+ import { Box as Box5, Text as Text6, useInput, Static } from "ink";
459
506
  import TextInput from "ink-text-input";
460
- import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
507
+ import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2, useRef as useRef2 } from "react";
461
508
 
462
509
  // src/ui/useDoubleCtrlC.ts
463
510
  import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
@@ -487,8 +534,824 @@ function useDoubleCtrlC(onExit) {
487
534
  return { doubleCtrlC, handleCtrlC };
488
535
  }
489
536
 
490
- // src/ui/ChatSession.tsx
537
+ // src/ui/AssistantMessage.tsx
538
+ import { Box as Box4, Text as Text5 } from "ink";
539
+
540
+ // src/provider/cost-tracker.ts
541
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
542
+ import { join as join2 } from "path";
543
+ var CostTracker = class {
544
+ #costDir;
545
+ #budgetLimit;
546
+ #tokenBudgetLimit;
547
+ #onBudgetExceeded;
548
+ // 会话级统计(内存)
549
+ #sessionId;
550
+ #sessionStartedAt;
551
+ #sessionRecords = [];
552
+ // 日级统计(内存缓存,启动时从磁盘恢复)
553
+ #todayDate;
554
+ #todaySummary;
555
+ // 持久化标记
556
+ #dirty = false;
557
+ #flushInProgress = false;
558
+ constructor(options = {}) {
559
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
560
+ this.#costDir = options.costDir ?? join2(home, ".dskcode", "costs");
561
+ this.#budgetLimit = options.budgetLimit ?? 0;
562
+ this.#tokenBudgetLimit = options.tokenBudgetLimit ?? 0;
563
+ this.#onBudgetExceeded = options.onBudgetExceeded;
564
+ this.#sessionId = generateSessionId();
565
+ this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
566
+ const today = getTodayStr();
567
+ this.#todayDate = today;
568
+ this.#todaySummary = createEmptyDailySummary(today);
569
+ }
570
+ // -----------------------------------------------------------------------
571
+ // 核心方法
572
+ // -----------------------------------------------------------------------
573
+ /**
574
+ * 记录一次 API 调用的 Token 使用量和成本。
575
+ * 自动计算费用,累加到会话级和日级统计。
576
+ */
577
+ record(usage, model) {
578
+ const cost = calculateCost(usage, model);
579
+ const record = {
580
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
581
+ model,
582
+ usage: { ...usage },
583
+ cost: { ...cost }
584
+ };
585
+ this.#sessionRecords.push(record);
586
+ this.#ensureTodayBucket();
587
+ this.#addToDaily(record);
588
+ this.#dirty = true;
589
+ this.#checkBudget();
590
+ return cost;
591
+ }
592
+ // -----------------------------------------------------------------------
593
+ // 会话级查询
594
+ // -----------------------------------------------------------------------
595
+ /** 当前会话 ID */
596
+ get sessionId() {
597
+ return this.#sessionId;
598
+ }
599
+ /** 当前会话的所有成本记录 */
600
+ get records() {
601
+ return this.#sessionRecords;
602
+ }
603
+ /** 当前会话累计成本汇总 */
604
+ get sessionSummary() {
605
+ let totalPromptTokens = 0;
606
+ let totalCompletionTokens = 0;
607
+ let totalCachedTokens = 0;
608
+ let totalCost = 0;
609
+ for (const r of this.#sessionRecords) {
610
+ totalPromptTokens += r.usage.promptTokens;
611
+ totalCompletionTokens += r.usage.completionTokens;
612
+ totalCachedTokens += r.usage.cachedPromptTokens ?? 0;
613
+ totalCost += r.cost.totalCost;
614
+ }
615
+ return {
616
+ sessionId: this.#sessionId,
617
+ startedAt: this.#sessionStartedAt,
618
+ totalPromptTokens,
619
+ totalCompletionTokens,
620
+ totalCachedTokens,
621
+ totalCost,
622
+ records: [...this.#sessionRecords]
623
+ };
624
+ }
625
+ /** 当前会话总费用(元) */
626
+ get sessionTotalCost() {
627
+ return this.#sessionRecords.reduce((sum, r) => sum + r.cost.totalCost, 0);
628
+ }
629
+ /** 当前会话总 prompt token 数 */
630
+ get sessionPromptTokens() {
631
+ return this.#sessionRecords.reduce((sum, r) => sum + r.usage.promptTokens, 0);
632
+ }
633
+ /** 当前会话总 completion token 数 */
634
+ get sessionCompletionTokens() {
635
+ return this.#sessionRecords.reduce((sum, r) => sum + r.usage.completionTokens, 0);
636
+ }
637
+ /** 当前会话 API 调用次数 */
638
+ get sessionCallCount() {
639
+ return this.#sessionRecords.length;
640
+ }
641
+ // -----------------------------------------------------------------------
642
+ // 日级查询
643
+ // -----------------------------------------------------------------------
644
+ /** 今日成本汇总 */
645
+ get todaySummary() {
646
+ this.#ensureTodayBucket();
647
+ return { ...this.#todaySummary };
648
+ }
649
+ /** 今日总费用(元) */
650
+ get todayTotalCost() {
651
+ this.#ensureTodayBucket();
652
+ return this.#todaySummary.totalCost;
653
+ }
654
+ /** 今日 API 调用次数 */
655
+ get todayCallCount() {
656
+ this.#ensureTodayBucket();
657
+ return this.#todaySummary.totalCalls;
658
+ }
659
+ // -----------------------------------------------------------------------
660
+ // 预算检查
661
+ // -----------------------------------------------------------------------
662
+ /** 是否已超出预算 */
663
+ get isBudgetExceeded() {
664
+ if (this.#budgetLimit > 0 && this.todayTotalCost >= this.#budgetLimit) {
665
+ return true;
666
+ }
667
+ if (this.#tokenBudgetLimit > 0) {
668
+ const totalTokens = this.#todaySummary.totalPromptTokens + this.#todaySummary.totalCompletionTokens;
669
+ if (totalTokens >= this.#tokenBudgetLimit) return true;
670
+ }
671
+ return false;
672
+ }
673
+ /** 剩余预算(元),无限制时返回 Infinity */
674
+ get remainingBudget() {
675
+ if (this.#budgetLimit <= 0) return Infinity;
676
+ return Math.max(0, this.#budgetLimit - this.todayTotalCost);
677
+ }
678
+ // -----------------------------------------------------------------------
679
+ // 持久化
680
+ // -----------------------------------------------------------------------
681
+ /**
682
+ * 从磁盘加载历史成本数据。
683
+ * 启动时调用,用于恢复今日和历史数据。
684
+ */
685
+ async load() {
686
+ const store = await this.#loadStore();
687
+ const today = getTodayStr();
688
+ if (store.daily[today]) {
689
+ this.#todaySummary = store.daily[today];
690
+ this.#todayDate = today;
691
+ }
692
+ }
693
+ /**
694
+ * 将脏数据持久化到磁盘。
695
+ * 通常在每次 record() 后异步调用,或在会话结束时主动调用。
696
+ */
697
+ async flush() {
698
+ if (!this.#dirty || this.#flushInProgress) return;
699
+ this.#flushInProgress = true;
700
+ this.#dirty = false;
701
+ try {
702
+ await this.#saveStore();
703
+ } finally {
704
+ this.#flushInProgress = false;
705
+ }
706
+ }
707
+ /**
708
+ * 查询指定日期范围的成本汇总。
709
+ * @param startDate 起始日期(YYYY-MM-DD),包含
710
+ * @param endDate 截止日期(YYYY-MM-DD),包含,默认同 startDate
711
+ */
712
+ async queryRange(startDate, endDate) {
713
+ const store = await this.#loadStore();
714
+ const end = endDate ?? startDate;
715
+ const result = [];
716
+ for (const [date, summary] of Object.entries(store.daily)) {
717
+ if (date >= startDate && date <= end) {
718
+ result.push(summary);
719
+ }
720
+ }
721
+ result.sort((a, b) => a.date.localeCompare(b.date));
722
+ return result;
723
+ }
724
+ /** 重置会话(开始新会话,不影响日级累计) */
725
+ resetSession() {
726
+ this.#sessionId = generateSessionId();
727
+ this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
728
+ this.#sessionRecords.length = 0;
729
+ }
730
+ // -----------------------------------------------------------------------
731
+ // 内部方法
732
+ // -----------------------------------------------------------------------
733
+ /** 确保今日的统计桶存在(处理跨日情况) */
734
+ #ensureTodayBucket() {
735
+ const today = getTodayStr();
736
+ if (today !== this.#todayDate) {
737
+ this.#todayDate = today;
738
+ this.#todaySummary = createEmptyDailySummary(today);
739
+ this.#dirty = true;
740
+ }
741
+ }
742
+ /** 将一条记录累加到日级汇总 */
743
+ #addToDaily(record) {
744
+ this.#todaySummary.totalPromptTokens += record.usage.promptTokens;
745
+ this.#todaySummary.totalCompletionTokens += record.usage.completionTokens;
746
+ this.#todaySummary.totalCachedTokens += record.usage.cachedPromptTokens ?? 0;
747
+ this.#todaySummary.totalCost += record.cost.totalCost;
748
+ this.#todaySummary.totalCalls += 1;
749
+ const modelKey = record.model;
750
+ if (!this.#todaySummary.byModel[modelKey]) {
751
+ this.#todaySummary.byModel[modelKey] = {
752
+ model: record.model,
753
+ totalPromptTokens: 0,
754
+ totalCompletionTokens: 0,
755
+ totalCachedTokens: 0,
756
+ totalCost: 0,
757
+ totalCalls: 0
758
+ };
759
+ }
760
+ const modelSummary = this.#todaySummary.byModel[modelKey];
761
+ modelSummary.totalPromptTokens += record.usage.promptTokens;
762
+ modelSummary.totalCompletionTokens += record.usage.completionTokens;
763
+ modelSummary.totalCachedTokens += record.usage.cachedPromptTokens ?? 0;
764
+ modelSummary.totalCost += record.cost.totalCost;
765
+ modelSummary.totalCalls += 1;
766
+ }
767
+ /** 预算超限检查 */
768
+ #checkBudget() {
769
+ if (!this.isBudgetExceeded) return;
770
+ this.#onBudgetExceeded?.(this);
771
+ }
772
+ /** 从磁盘加载成本存储 */
773
+ async #loadStore() {
774
+ const filePath = join2(this.#costDir, "history.json");
775
+ try {
776
+ const raw = await readFile2(filePath, "utf-8");
777
+ return JSON.parse(raw);
778
+ } catch {
779
+ return { version: 1, daily: {} };
780
+ }
781
+ }
782
+ /** 保存成本存储到磁盘 */
783
+ async #saveStore() {
784
+ const store = await this.#loadStore();
785
+ store.daily[this.#todayDate] = this.#todaySummary;
786
+ const cutoffDate = getDateNDaysAgo(90);
787
+ const datesToRemove = Object.keys(store.daily).filter(
788
+ (date) => date < cutoffDate
789
+ );
790
+ for (const date of datesToRemove) {
791
+ delete store.daily[date];
792
+ }
793
+ await mkdir2(this.#costDir, { recursive: true });
794
+ const filePath = join2(this.#costDir, "history.json");
795
+ await writeFile2(filePath, JSON.stringify(store, null, 2), "utf-8");
796
+ }
797
+ };
798
+ function generateSessionId() {
799
+ const ts = Date.now().toString(36);
800
+ const rand = Math.random().toString(36).slice(2, 8);
801
+ return `${ts}-${rand}`;
802
+ }
803
+ function getTodayStr() {
804
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
805
+ }
806
+ function getDateNDaysAgo(n) {
807
+ const d = /* @__PURE__ */ new Date();
808
+ d.setDate(d.getDate() - n);
809
+ return d.toISOString().slice(0, 10);
810
+ }
811
+ function createEmptyDailySummary(date) {
812
+ return {
813
+ date,
814
+ totalPromptTokens: 0,
815
+ totalCompletionTokens: 0,
816
+ totalCachedTokens: 0,
817
+ totalCost: 0,
818
+ totalCalls: 0,
819
+ byModel: {}
820
+ };
821
+ }
822
+ function formatMoney(yuan) {
823
+ if (yuan === 0) return "\xA50.00";
824
+ if (yuan < 0.01) return `\xA5${yuan.toFixed(6)}`;
825
+ if (yuan < 1) return `\xA5${yuan.toFixed(4)}`;
826
+ return `\xA5${yuan.toFixed(2)}`;
827
+ }
828
+
829
+ // src/ui/ToolCallBlock.tsx
830
+ import { Box as Box3, Text as Text4 } from "ink";
491
831
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
832
+ function formatArgsSummary(args) {
833
+ try {
834
+ const parsed = JSON.parse(args);
835
+ const lines = Object.entries(parsed).map(([key, value]) => {
836
+ const val = String(value);
837
+ const truncated = val.length > 80 ? val.slice(0, 77) + "..." : val;
838
+ return ` ${key}: ${truncated}`;
839
+ });
840
+ return lines.join("\n");
841
+ } catch {
842
+ const truncated = args.length > 120 ? args.slice(0, 117) + "..." : args;
843
+ return ` ${truncated}`;
844
+ }
845
+ }
846
+ function ToolCallBlock({ call, showPendingHint = true }) {
847
+ const argsDisplay = formatArgsSummary(call.arguments);
848
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: [
849
+ /* @__PURE__ */ jsxs3(Box3, { children: [
850
+ /* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", bold: true, children: [
851
+ "\u{1F4E6} ",
852
+ call.name
853
+ ] }),
854
+ /* @__PURE__ */ jsxs3(Text4, { color: "#555555", children: [
855
+ " ",
856
+ "\u2500".repeat(Math.max(1, 30 - call.name.length))
857
+ ] })
858
+ ] }),
859
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { color: "#888888", children: argsDisplay }) }),
860
+ 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" }) })
861
+ ] });
862
+ }
863
+
864
+ // src/agent/message-builder.ts
865
+ function estimateMessageTokens(msg) {
866
+ let text = msg.content;
867
+ if (msg.toolCalls) {
868
+ for (const tc of msg.toolCalls) {
869
+ text += tc.name + tc.arguments;
870
+ }
871
+ }
872
+ return estimateTokens(text) + 10;
873
+ }
874
+ function trimMessages(messages, opts) {
875
+ const meta = getModelMeta(opts.model);
876
+ const maxInputTokens = meta.contextWindow - opts.reservedForOutput;
877
+ const systemTokens = estimateTokens(opts.systemPrompt);
878
+ let remaining = maxInputTokens - systemTokens;
879
+ const preserved = [];
880
+ let roundsPreserved = 0;
881
+ for (let i = messages.length - 1; i >= 0 && roundsPreserved < opts.preserveRecentRounds; i--) {
882
+ preserved.unshift(messages[i]);
883
+ if (messages[i].role === "user") {
884
+ roundsPreserved++;
885
+ }
886
+ }
887
+ for (const msg of preserved) {
888
+ remaining -= estimateMessageTokens(msg);
889
+ }
890
+ if (remaining < 0) {
891
+ while (preserved.length > 1 && remaining < 0) {
892
+ const removed = preserved.shift();
893
+ remaining += estimateMessageTokens(removed);
894
+ }
895
+ return [preserved, true];
896
+ }
897
+ const olderMessages = messages.slice(0, messages.length - preserved.length);
898
+ const kept = [];
899
+ for (let i = olderMessages.length - 1; i >= 0; i--) {
900
+ const cost = estimateMessageTokens(olderMessages[i]);
901
+ if (remaining - cost < 0) break;
902
+ remaining -= cost;
903
+ kept.unshift(olderMessages[i]);
904
+ }
905
+ const result = [...kept, ...preserved];
906
+ const trimmed = result.length < messages.length;
907
+ return [result, trimmed];
908
+ }
909
+ function buildApiMessages(systemPrompt, history) {
910
+ return [
911
+ { role: "system", content: systemPrompt },
912
+ ...history
913
+ ];
914
+ }
915
+ function formatUsageSummary(usage) {
916
+ const prompt = usage.promptTokens.toLocaleString();
917
+ const completion = usage.completionTokens.toLocaleString();
918
+ const total = (usage.promptTokens + usage.completionTokens).toLocaleString();
919
+ let summary = `\u{1F4E5} ${prompt} + \u{1F4E4} ${completion} = \u{1F4E6} ${total} tokens`;
920
+ if (usage.cachedPromptTokens && usage.cachedPromptTokens > 0) {
921
+ const cacheRate = (usage.cachedPromptTokens / usage.promptTokens * 100).toFixed(1);
922
+ summary += ` \u2502 \u{1F5C4}\uFE0F \u7F13\u5B58\u547D\u4E2D ${cacheRate}%`;
923
+ }
924
+ return summary;
925
+ }
926
+
927
+ // src/ui/AssistantMessage.tsx
928
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
929
+ function formatElapsed(ms) {
930
+ if (ms < 1e3) return `${ms}ms`;
931
+ const seconds = (ms / 1e3).toFixed(1);
932
+ return `${seconds}s`;
933
+ }
934
+ function AssistantMessage({
935
+ content,
936
+ toolCalls,
937
+ isStreaming = false,
938
+ usage,
939
+ elapsed,
940
+ cost,
941
+ model
942
+ }) {
943
+ if (!content && (!toolCalls || toolCalls.length === 0) && !isStreaming) {
944
+ return null;
945
+ }
946
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
947
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
948
+ /* @__PURE__ */ jsx4(Box4, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#ff00ff", children: "\u{1F916}" }) }),
949
+ /* @__PURE__ */ jsxs4(Box4, { flexGrow: 1, flexDirection: "column", children: [
950
+ content && /* @__PURE__ */ jsx4(Text5, { wrap: "wrap", children: content }),
951
+ isStreaming && !content && /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: "..." })
952
+ ] })
953
+ ] }),
954
+ toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsx4(ToolCallBlock, { call: tc }, tc.id ?? i)) }),
955
+ !isStreaming && (usage || elapsed !== void 0) && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, marginLeft: 3, children: [
956
+ /* @__PURE__ */ jsx4(Text5, { color: "#555555", children: "\u2500".repeat(36) }),
957
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", gap: 2, children: [
958
+ cost !== void 0 && cost > 0 && /* @__PURE__ */ jsxs4(Text5, { color: "yellow", children: [
959
+ "\u{1F4B0} \u672C\u6B21 ",
960
+ formatMoney(cost)
961
+ ] }),
962
+ elapsed !== void 0 && /* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
963
+ "\u{1F550} ",
964
+ formatElapsed(elapsed)
965
+ ] }),
966
+ usage && /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: formatUsageSummary(usage) })
967
+ ] })
968
+ ] })
969
+ ] });
970
+ }
971
+
972
+ // src/provider/registry.ts
973
+ var ProviderRegistry = class {
974
+ #factories = /* @__PURE__ */ new Map();
975
+ #instances = /* @__PURE__ */ new Map();
976
+ /** 注册一个 Provider 工厂 */
977
+ register(name, factory) {
978
+ this.#factories.set(name, factory);
979
+ }
980
+ /**
981
+ * 获取或创建一个 Provider 实例(单例)。
982
+ *
983
+ * 相同的 name + baseUrl + model 组合会复用同一实例。
984
+ * 如果 model 不在支持列表中,抛出 ModelNotSupportedError。
985
+ */
986
+ get(name, config) {
987
+ if (!isSupportedModel(config.model)) {
988
+ throw new ModelNotSupportedError(config.model);
989
+ }
990
+ const cacheKey = `${name}:${config.baseUrl}:${config.model}`;
991
+ const cached = this.#instances.get(cacheKey);
992
+ if (cached) return cached;
993
+ const factory = this.#factories.get(name);
994
+ if (!factory) {
995
+ const available = [...this.#factories.keys()].join(", ");
996
+ throw new Error(`\u672A\u6CE8\u518C\u7684 Provider: "${name}"\u3002\u53EF\u7528: ${available}`);
997
+ }
998
+ const instance = factory(config);
999
+ this.#instances.set(cacheKey, instance);
1000
+ return instance;
1001
+ }
1002
+ /** 列出已注册的 Provider 名称 */
1003
+ list() {
1004
+ return [...this.#factories.keys()];
1005
+ }
1006
+ /** 清除缓存的实例(用于配置热重载) */
1007
+ clear() {
1008
+ this.#instances.clear();
1009
+ }
1010
+ };
1011
+ var defaultRegistry = new ProviderRegistry();
1012
+ defaultRegistry.register("deepseek", (config) => {
1013
+ return new DeepSeekProvider({
1014
+ apiKey: config.apiKey,
1015
+ baseUrl: config.baseUrl,
1016
+ model: config.model
1017
+ });
1018
+ });
1019
+ function createProvider(config) {
1020
+ return defaultRegistry.get(config.name, config);
1021
+ }
1022
+
1023
+ // src/agent/extra-prompt.ts
1024
+ var EXTRA_PROMPT = [
1025
+ "\u3010\u7EC8\u7AEF\u8F93\u51FA\u7EA6\u675F\u3011",
1026
+ "",
1027
+ "\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",
1028
+ "\u8BF7\u4E25\u683C\u9075\u5B88\u4EE5\u4E0B\u683C\u5F0F\u89C4\u5219\u3002",
1029
+ "",
1030
+ "\u4E00\u3001\u7981\u6B62\u4F7F\u7528\u7684\u7B26\u53F7",
1031
+ " - \u4E0D\u8981\u4F7F\u7528 Markdown \u6807\u9898\u7B26\u53F7\uFF08\u5355\u72EC\u6216\u8FDE\u7EED\u7684\u4E95\u53F7\uFF09",
1032
+ " - \u4E0D\u8981\u4F7F\u7528\u7C97\u4F53\u6216\u659C\u4F53\u7B26\u53F7\uFF08\u661F\u53F7\u3001\u4E0B\u5212\u7EBF\uFF09",
1033
+ " - \u4E0D\u8981\u4F7F\u7528\u4EE3\u7801\u5757\u6807\u8BB0\uFF08\u8FDE\u7EED\u4E09\u4E2A\u53CD\u5F15\u53F7\uFF09",
1034
+ " - \u4E0D\u8981\u4F7F\u7528\u5757\u5F15\u7528\u7B26\u53F7\uFF08\u5927\u4E8E\u53F7\uFF09",
1035
+ " - \u4E0D\u8981\u4F7F\u7528\u5206\u9694\u7EBF\uFF08\u8FDE\u7EED\u4E09\u4E2A\u51CF\u53F7\u6216\u661F\u53F7\uFF09",
1036
+ " - \u4E0D\u8981\u4F7F\u7528\u884C\u5185\u4EE3\u7801\u6807\u8BB0\uFF08\u5355\u4E2A\u53CD\u5F15\u53F7\uFF09",
1037
+ "",
1038
+ "\u4E8C\u3001\u63A8\u8350\u7684\u7EC4\u7EC7\u65B9\u5F0F",
1039
+ ' - \u7528\u7EAF\u6587\u5B57\u63CF\u8FF0\u5C42\u7EA7\uFF0C\u4F8B\u5982"\u7B2C\u4E00\u70B9"\u3001"\u7B2C\u4E8C\u70B9"\u66FF\u4EE3\u6807\u9898\u7B26\u53F7',
1040
+ " - \u7528 emoji \u7B26\u53F7\u505A\u89C6\u89C9\u6807\u8BB0\uFF08\u5982 \u{1F4CC} \u{1F4E6} \u{1F4DD} \u26A0\uFE0F \u7B49\uFF09",
1041
+ " - \u7528\u6570\u5B57\u5E8F\u53F7\uFF081. 2. 3.\uFF09\u7EC4\u7EC7\u5217\u8868",
1042
+ " - \u4EE3\u7801\u793A\u4F8B\u7528\u7F29\u8FDB\uFF08\u6BCF\u884C\u524D\u52A0 4 \u4E2A\u7A7A\u683C\uFF09\u66FF\u4EE3\u4EE3\u7801\u5757\u6807\u8BB0",
1043
+ " - \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",
1044
+ "",
1045
+ "\u4E09\u3001\u6B63\u786E\u683C\u5F0F\u793A\u4F8B",
1046
+ "",
1047
+ " \u7B2C\u4E00\u70B9\uFF1A\u4F18\u5316\u6570\u636E\u5E93\u67E5\u8BE2",
1048
+ " 1. \u6DFB\u52A0\u7D22\u5F15\u5230 user_id \u5B57\u6BB5",
1049
+ " 2. \u7528\u8FDE\u63A5\u6C60\u66FF\u4EE3\u77ED\u8FDE\u63A5",
1050
+ "",
1051
+ " \u4EE3\u7801\u793A\u4F8B\uFF1A",
1052
+ " const pool = new Pool({ max: 20 });",
1053
+ ' await pool.query("SELECT * FROM users");',
1054
+ "",
1055
+ " \u8F93\u51FA\u63D0\u793A\uFF1A",
1056
+ " \u{1F4CC} \u4F18\u5316\u524D\uFF1A\u67E5\u8BE2\u8017\u65F6 3.2 \u79D2",
1057
+ "",
1058
+ "\u56DB\u3001\u9519\u8BEF\u683C\u5F0F\u793A\u4F8B\uFF08\u7981\u6B62\u8FD9\u6837\u5199\uFF09",
1059
+ "",
1060
+ " \u4E95\u53F7\u4E32 \u4F18\u5316\u6570\u636E\u5E93\u67E5\u8BE2\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
1061
+ " \u661F\u53F7 \u6DFB\u52A0\u7D22\u5F15\u5230 user_id \u5B57\u6BB5\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
1062
+ " \u4E09\u4E2A\u53CD\u5F15\u53F7\u5F00\u59CB\u6216\u7ED3\u675F\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
1063
+ "",
1064
+ "\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"
1065
+ ].join("\n");
1066
+
1067
+ // src/agent/system-prompt.ts
1068
+ function buildSystemPrompt(opts) {
1069
+ const sections = [];
1070
+ 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
1071
+
1072
+ ## \u6838\u5FC3\u539F\u5219
1073
+ - \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587
1074
+ - \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE
1075
+ - \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E
1076
+ - \u4EE3\u7801\u793A\u4F8B\u4FDD\u6301\u7B80\u6D01\uFF0C\u9644\u5E26\u5FC5\u8981\u7684\u6CE8\u91CA`);
1077
+ sections.push(`## \u5F53\u524D\u6A21\u578B
1078
+ - \u6A21\u578B\uFF1A${opts.model}`);
1079
+ const now = /* @__PURE__ */ new Date();
1080
+ const dateStr = now.toLocaleDateString("zh-CN", {
1081
+ year: "numeric",
1082
+ month: "long",
1083
+ day: "numeric",
1084
+ weekday: "long"
1085
+ });
1086
+ const timeStr = now.toLocaleTimeString("zh-CN", {
1087
+ hour: "2-digit",
1088
+ minute: "2-digit"
1089
+ });
1090
+ sections.push(`## \u65F6\u95F4\u4E0A\u4E0B\u6587
1091
+ - \u5F53\u524D\u65E5\u671F\uFF1A${dateStr}
1092
+ - \u5F53\u524D\u65F6\u95F4\uFF1A${timeStr}
1093
+ - \u5DE5\u4F5C\u76EE\u5F55\uFF1A${opts.cwd}`);
1094
+ if (opts.tools && opts.tools.length > 0) {
1095
+ const toolLines = opts.tools.map((t) => {
1096
+ const paramInfo = t.parameters ? Object.keys(t.parameters.properties ?? {}).join(", ") : "";
1097
+ return `- **${t.name}**\uFF1A${t.description}${paramInfo ? `\uFF08\u53C2\u6570\uFF1A${paramInfo}\uFF09` : ""}`;
1098
+ }).join("\n");
1099
+ sections.push(`## \u53EF\u7528\u5DE5\u5177
1100
+
1101
+ \u4F60\u53EF\u4EE5\u901A\u8FC7\u5DE5\u5177\u8C03\u7528\u6267\u884C\u64CD\u4F5C\u3002\u5F53\u524D\u53EF\u7528\u7684\u5DE5\u5177\uFF1A
1102
+
1103
+ ${toolLines}
1104
+
1105
+ \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`);
1106
+ }
1107
+ if (opts.projectContext) {
1108
+ sections.push(`## \u9879\u76EE\u4E0A\u4E0B\u6587
1109
+
1110
+ ${opts.projectContext}`);
1111
+ }
1112
+ sections.push(`## \u884C\u4E3A\u7EA6\u675F
1113
+ - \u5982\u679C\u7528\u6237\u8BF7\u6C42\u4E0D\u660E\u786E\uFF0C\u8BF7\u4E3B\u52A8\u8BE2\u95EE\u6F84\u6E05
1114
+ - \u6D89\u53CA\u6587\u4EF6\u64CD\u4F5C\u65F6\uFF0C\u5148\u786E\u8BA4\u6587\u4EF6\u8DEF\u5F84
1115
+ - \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
1116
+ - \u5F53\u5DE5\u5177\u8C03\u7528\u8FD4\u56DE\u9519\u8BEF\u65F6\uFF0C\u5206\u6790\u539F\u56E0\u5E76\u5C1D\u8BD5\u4FEE\u590D`);
1117
+ sections.push(EXTRA_PROMPT);
1118
+ return sections.join("\n\n");
1119
+ }
1120
+
1121
+ // src/agent/index.ts
1122
+ var Session = class {
1123
+ #messages = [];
1124
+ #provider;
1125
+ #tools;
1126
+ #costTracker;
1127
+ #options;
1128
+ #abortController = new AbortController();
1129
+ constructor(provider, tools = [], costTracker, options) {
1130
+ this.#provider = provider;
1131
+ this.#tools = tools;
1132
+ this.#costTracker = costTracker ?? new CostTracker();
1133
+ this.#options = {
1134
+ cwd: options?.cwd ?? process.cwd(),
1135
+ maxToolRounds: options?.maxToolRounds ?? 20,
1136
+ reservedForOutput: options?.reservedForOutput ?? 4096,
1137
+ preserveRecentRounds: options?.preserveRecentRounds ?? 10,
1138
+ projectContext: options?.projectContext
1139
+ };
1140
+ }
1141
+ // -------------------------------------------------------------------------
1142
+ // 公共只读属性
1143
+ // -------------------------------------------------------------------------
1144
+ get messages() {
1145
+ return this.#messages;
1146
+ }
1147
+ get accumulatedCost() {
1148
+ return this.#costTracker.sessionTotalCost;
1149
+ }
1150
+ get costTracker() {
1151
+ return this.#costTracker;
1152
+ }
1153
+ get model() {
1154
+ return this.#provider.model();
1155
+ }
1156
+ // -------------------------------------------------------------------------
1157
+ // 流式对话 — Agent 主循环
1158
+ // -------------------------------------------------------------------------
1159
+ /**
1160
+ * 执行一轮对话,以 AsyncGenerator 形式逐步 yield 事件。
1161
+ *
1162
+ * 调用方式:
1163
+ * ```ts
1164
+ * for await (const event of session.chat("你好")) {
1165
+ * switch (event.type) {
1166
+ * case "text_delta": // 追加文本
1167
+ * case "tool_calls": // 展示工具调用
1168
+ * case "usage": // 记录使用量
1169
+ * case "done": // 本轮完成
1170
+ * case "error": // 处理错误
1171
+ * }
1172
+ * }
1173
+ * ```
1174
+ */
1175
+ async *chat(userInput) {
1176
+ this.#messages.push({ role: "user", content: userInput });
1177
+ const systemPrompt = this.#buildSystemPrompt();
1178
+ const [trimmed, wasTrimmed] = trimMessages(
1179
+ [...this.#messages],
1180
+ {
1181
+ model: this.#provider.model(),
1182
+ reservedForOutput: this.#options.reservedForOutput,
1183
+ systemPrompt,
1184
+ preserveRecentRounds: this.#options.preserveRecentRounds
1185
+ }
1186
+ );
1187
+ if (wasTrimmed) {
1188
+ }
1189
+ const apiMessages = buildApiMessages(systemPrompt, trimmed);
1190
+ const toolDefs = this.#buildToolDefinitions();
1191
+ const startTime = Date.now();
1192
+ try {
1193
+ const stream = this.#provider.chat(apiMessages, {
1194
+ signal: this.#abortController.signal
1195
+ // 将工具定义传给 provider(如支持 function calling)
1196
+ // 注意:当前 chat() 签名不含 tools 参数,
1197
+ // 工具定义在 system prompt 中描述,由模型通过文本方式请求
1198
+ });
1199
+ let accumulatedText = "";
1200
+ let lastUsage;
1201
+ let lastToolCalls;
1202
+ let lastFinishReason = null;
1203
+ for await (const chunk of stream) {
1204
+ if (chunk.content) {
1205
+ accumulatedText += chunk.content;
1206
+ yield { type: "text_delta", content: chunk.content };
1207
+ }
1208
+ if (chunk.toolCalls && chunk.toolCalls.length > 0) {
1209
+ lastToolCalls = chunk.toolCalls;
1210
+ }
1211
+ if (chunk.usage) {
1212
+ lastUsage = chunk.usage;
1213
+ }
1214
+ if (chunk.finishReason) {
1215
+ lastFinishReason = chunk.finishReason;
1216
+ }
1217
+ }
1218
+ if (lastToolCalls && lastToolCalls.length > 0) {
1219
+ yield { type: "tool_calls", calls: lastToolCalls };
1220
+ }
1221
+ if (lastUsage) {
1222
+ const modelId = this.#provider.model();
1223
+ const costInfo = this.#costTracker.record(lastUsage, modelId);
1224
+ yield { type: "usage", usage: lastUsage, model: modelId };
1225
+ }
1226
+ const assistantMsg = {
1227
+ role: "assistant",
1228
+ content: accumulatedText
1229
+ };
1230
+ if (lastToolCalls && lastToolCalls.length > 0) {
1231
+ assistantMsg.toolCalls = lastToolCalls;
1232
+ }
1233
+ this.#messages.push(assistantMsg);
1234
+ if (lastToolCalls && lastToolCalls.length > 0) {
1235
+ for (const tc of lastToolCalls) {
1236
+ this.#messages.push({
1237
+ role: "tool",
1238
+ content: `\u26A0 \u5DE5\u5177 "${tc.name}" \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09`,
1239
+ toolCallId: tc.id,
1240
+ name: tc.name
1241
+ });
1242
+ }
1243
+ }
1244
+ const elapsed = Date.now() - startTime;
1245
+ yield { type: "done", elapsed };
1246
+ } catch (err) {
1247
+ if (err instanceof DOMException && err.name === "AbortError") {
1248
+ return;
1249
+ }
1250
+ if (err instanceof Error && err.name === "AbortError") {
1251
+ return;
1252
+ }
1253
+ yield {
1254
+ type: "error",
1255
+ error: err instanceof Error ? err : new Error(String(err))
1256
+ };
1257
+ }
1258
+ }
1259
+ // -------------------------------------------------------------------------
1260
+ // 会话管理
1261
+ // -------------------------------------------------------------------------
1262
+ /** 取消正在进行的流式请求 */
1263
+ abort() {
1264
+ this.#abortController.abort();
1265
+ }
1266
+ /** 重置会话历史(保留 provider/tools 配置,重置成本追踪) */
1267
+ reset() {
1268
+ this.#messages.length = 0;
1269
+ this.#costTracker.resetSession();
1270
+ }
1271
+ // -------------------------------------------------------------------------
1272
+ // 内部方法
1273
+ // -------------------------------------------------------------------------
1274
+ /** 构建系统提示词 */
1275
+ #buildSystemPrompt() {
1276
+ const toolDescs = this.#tools.map((t) => ({
1277
+ name: t.name,
1278
+ description: t.description,
1279
+ parameters: t.parameters
1280
+ }));
1281
+ const opts = {
1282
+ model: this.#provider.model(),
1283
+ tools: toolDescs.length > 0 ? toolDescs : void 0,
1284
+ projectContext: this.#options.projectContext ?? void 0,
1285
+ cwd: this.#options.cwd
1286
+ };
1287
+ return buildSystemPrompt(opts);
1288
+ }
1289
+ /** 将注册的工具转为 ToolDefinition 格式(预留给 function calling) */
1290
+ #buildToolDefinitions() {
1291
+ return this.#tools.map((t) => ({
1292
+ type: "function",
1293
+ function: {
1294
+ name: t.name,
1295
+ description: t.description,
1296
+ parameters: t.parameters
1297
+ }
1298
+ }));
1299
+ }
1300
+ };
1301
+
1302
+ // src/utils/gradient.ts
1303
+ var IDLE_GRADIENT_STOPS = [
1304
+ [0, 255, 65],
1305
+ // #00ff41
1306
+ [0, 255, 255],
1307
+ // #00ffff
1308
+ [189, 147, 249]
1309
+ // #bd93f9
1310
+ ];
1311
+ var STREAMING_GRADIENT_STOPS = [
1312
+ [255, 191, 0],
1313
+ // #ffbf00 琥珀金
1314
+ [210, 140, 60],
1315
+ // #d28c3c 焦糖
1316
+ [175, 70, 40]
1317
+ // #af4628 棕红
1318
+ ];
1319
+ var GRADIENT_ANIMATION = {
1320
+ /** 空闲占位符动画:每帧相位步进(值越大流速越快) */
1321
+ idlePhaseStep: 0.06,
1322
+ /** 空闲占位符动画:帧间隔(ms) */
1323
+ idleInterval: 40,
1324
+ /** 流式占位符动画:每帧相位步进 */
1325
+ streamingPhaseStep: 0.05,
1326
+ /** 流式占位符动画:帧间隔(ms) */
1327
+ streamingInterval: 40
1328
+ };
1329
+ var SKIP_CHARS = /* @__PURE__ */ new Set([" ", "~", ".", "\u3002", "\uFF01", "\u{1F447}"]);
1330
+ function lerpStopsToHex(t, stops) {
1331
+ const len = stops.length;
1332
+ const segment = t * (len - 1);
1333
+ const idx = Math.min(Math.floor(segment), len - 2);
1334
+ const frac = segment - idx;
1335
+ const stop = stops[idx];
1336
+ const nextStop = stops[idx + 1];
1337
+ if (!stop || !nextStop) return "#808080";
1338
+ const r = Math.round(stop[0] + (nextStop[0] - stop[0]) * frac);
1339
+ const g = Math.round(stop[1] + (nextStop[1] - stop[1]) * frac);
1340
+ const b = Math.round(stop[2] + (nextStop[2] - stop[2]) * frac);
1341
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
1342
+ }
1343
+ function getGradientColors(text, phase, stops) {
1344
+ const len = text.length;
1345
+ if (len === 0) return [];
1346
+ return text.split("").map((ch, i) => {
1347
+ if (SKIP_CHARS.has(ch)) return "";
1348
+ const t = (i / (len - 1) + phase) % 1;
1349
+ return lerpStopsToHex(t, stops);
1350
+ });
1351
+ }
1352
+
1353
+ // src/ui/ChatSession.tsx
1354
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
492
1355
  var commandRegistry = /* @__PURE__ */ new Map();
493
1356
  function registerCommand(name, cmd) {
494
1357
  commandRegistry.set(name, cmd);
@@ -510,24 +1373,89 @@ registerCommand("/help", {
510
1373
  }
511
1374
  });
512
1375
  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" }) });
1376
+ registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.1.10" }) });
514
1377
  registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
515
1378
  registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
516
- function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLaunchGame, onLaunchStock }) {
1379
+ var STREAMING_PLACEHOLDERS = [
1380
+ "\u8BA9\u5B50\u5F39\u98DE\u4E00\u4F1A\u513F...",
1381
+ "\u9A6C\u4E0A\u5C31\u597D...",
1382
+ "\u6B63\u5728\u618B\u5927\u62DB...",
1383
+ "\u7A0D\u7B49\u4E00\u4E0B\u4E0B~",
1384
+ "\u7801\u5B57\u4E2D...",
1385
+ "\u8111\u5B50\u8F6C\u5F97\u98DE\u5FEB..."
1386
+ ];
1387
+ var IDLE_PLACEHOLDERS = [
1388
+ "\u60F3\u5E72\u5565\uFF1F\u76F4\u63A5\u8BF4~",
1389
+ "\u6765\u5427\uFF0C\u5429\u5490\u70B9\u5565",
1390
+ "\u968F\u65F6\u5F85\u547D...",
1391
+ "\u6233\u8FD9\u91CC\u5F00\u804A \u{1F447}",
1392
+ "\u7B49\u4F60\u5F00\u53E3...",
1393
+ "\u5C3D\u7BA1\u4F7F\u5524~"
1394
+ ];
1395
+ function pickRandom(arr) {
1396
+ return arr[Math.floor(Math.random() * arr.length)];
1397
+ }
1398
+ function ChatSession({
1399
+ providerCount,
1400
+ toolCount,
1401
+ verbose,
1402
+ apiKey,
1403
+ baseUrl,
1404
+ costTracker: externalCostTracker,
1405
+ model = "deepseek-v4-flash",
1406
+ onLaunchGame,
1407
+ onLaunchStock
1408
+ }) {
1409
+ const termWidth = typeof process.stdout.columns === "number" ? process.stdout.columns : 80;
1410
+ const dividerWidth = Math.max(termWidth - 2, 1);
517
1411
  const [offset, setOffset] = useState3(0);
518
- const [messages, setMessages] = useState3([]);
1412
+ const [displayMessages, setDisplayMessages] = useState3([]);
519
1413
  const [input, setInput] = useState3("");
520
1414
  const [balance, setBalance] = useState3(null);
521
1415
  const [balanceLoading, setBalanceLoading] = useState3(false);
522
- const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => process.exit(0));
1416
+ const [todayCost, setTodayCost] = useState3(null);
1417
+ const [isStreaming, setIsStreaming] = useState3(false);
1418
+ const [streamingPlaceholder, setStreamingPlaceholder] = useState3("");
1419
+ const [idlePlaceholder, setIdlePlaceholder] = useState3(() => pickRandom(IDLE_PLACEHOLDERS));
1420
+ const [gradientColors, setGradientColors] = useState3([]);
1421
+ const gradientPhaseRef = useRef2(0);
1422
+ const [streamingGradientColors, setStreamingGradientColors] = useState3([]);
1423
+ const streamingPhaseRef = useRef2(0);
1424
+ const [currentContent, setCurrentContent] = useState3("");
1425
+ const [currentToolCalls, setCurrentToolCalls] = useState3([]);
1426
+ const [currentUsage, setCurrentUsage] = useState3(void 0);
1427
+ const [currentElapsed, setCurrentElapsed] = useState3(void 0);
1428
+ const [currentCost, setCurrentCost] = useState3(void 0);
1429
+ const [currentModel, setCurrentModel] = useState3(void 0);
1430
+ const [streamError, setStreamError] = useState3(void 0);
1431
+ const sessionRef = useRef2(null);
1432
+ const abortRef = useRef2(null);
1433
+ const currentContentRef = useRef2("");
1434
+ const currentToolCallsRef = useRef2([]);
1435
+ const currentUsageRef = useRef2(void 0);
1436
+ const currentElapsedRef = useRef2(void 0);
1437
+ const currentCostRef = useRef2(void 0);
1438
+ const currentModelRef = useRef2(void 0);
1439
+ const streamErrorRef = useRef2(void 0);
1440
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
1441
+ process.exit(0);
1442
+ });
523
1443
  useInput(
524
1444
  useCallback2(
525
- (input2, key) => {
526
- if (input2 === "c" && key.ctrl) {
527
- handleCtrlC();
1445
+ (_input, key) => {
1446
+ if (key.ctrl && _input === "c") {
1447
+ if (isStreaming) {
1448
+ abortRef.current?.abort();
1449
+ } else {
1450
+ handleCtrlC();
1451
+ }
1452
+ return;
1453
+ }
1454
+ if (!input && !isStreaming && _input) {
1455
+ setInput(_input);
528
1456
  }
529
1457
  },
530
- [handleCtrlC]
1458
+ [isStreaming, handleCtrlC, input]
531
1459
  )
532
1460
  );
533
1461
  useEffect3(() => {
@@ -536,12 +1464,29 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
536
1464
  }, 500);
537
1465
  return () => clearInterval(timer);
538
1466
  }, []);
1467
+ useEffect3(() => {
1468
+ if (!apiKey || !baseUrl) return;
1469
+ const provider = createProvider({
1470
+ name: "deepseek",
1471
+ apiKey,
1472
+ baseUrl,
1473
+ model
1474
+ });
1475
+ const tracker = externalCostTracker ?? new CostTracker();
1476
+ const session = new Session(provider, [], tracker, {
1477
+ cwd: process.cwd()
1478
+ });
1479
+ sessionRef.current = session;
1480
+ return () => {
1481
+ sessionRef.current = null;
1482
+ };
1483
+ }, [apiKey, baseUrl, model, externalCostTracker]);
539
1484
  useEffect3(() => {
540
1485
  if (!apiKey || !baseUrl) return;
541
1486
  let cancelled = false;
542
1487
  setBalanceLoading(true);
543
- import("./deepseek-7Y2G6EKM.js").then(({ DeepSeekProvider }) => {
544
- const provider = new DeepSeekProvider({
1488
+ import("./deepseek-OIJOG3N5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
1489
+ const provider = new DeepSeekProvider2({
545
1490
  apiKey,
546
1491
  baseUrl,
547
1492
  model: "deepseek-v4-flash"
@@ -561,7 +1506,51 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
561
1506
  cancelled = true;
562
1507
  };
563
1508
  }, [apiKey, baseUrl]);
564
- const handleSubmit = useCallback2((value) => {
1509
+ useEffect3(() => {
1510
+ if (!externalCostTracker) return;
1511
+ let cancelled = false;
1512
+ let timer;
1513
+ const refresh = () => {
1514
+ setTodayCost(externalCostTracker.todayTotalCost);
1515
+ };
1516
+ externalCostTracker.load().then(() => {
1517
+ if (cancelled) return;
1518
+ refresh();
1519
+ timer = setInterval(refresh, 5e3);
1520
+ }).catch(() => {
1521
+ });
1522
+ return () => {
1523
+ cancelled = true;
1524
+ if (timer) clearInterval(timer);
1525
+ };
1526
+ }, [externalCostTracker]);
1527
+ useEffect3(() => {
1528
+ if (isStreaming || !idlePlaceholder) {
1529
+ setGradientColors([]);
1530
+ return;
1531
+ }
1532
+ gradientPhaseRef.current = 0;
1533
+ setGradientColors(getGradientColors(idlePlaceholder, 0, IDLE_GRADIENT_STOPS));
1534
+ const interval = setInterval(() => {
1535
+ gradientPhaseRef.current = (gradientPhaseRef.current + GRADIENT_ANIMATION.idlePhaseStep) % 1;
1536
+ setGradientColors(getGradientColors(idlePlaceholder, gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
1537
+ }, GRADIENT_ANIMATION.idleInterval);
1538
+ return () => clearInterval(interval);
1539
+ }, [isStreaming, idlePlaceholder]);
1540
+ useEffect3(() => {
1541
+ if (!isStreaming || !streamingPlaceholder) {
1542
+ setStreamingGradientColors([]);
1543
+ return;
1544
+ }
1545
+ streamingPhaseRef.current = 0;
1546
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 0, STREAMING_GRADIENT_STOPS));
1547
+ const interval = setInterval(() => {
1548
+ streamingPhaseRef.current = (streamingPhaseRef.current + GRADIENT_ANIMATION.streamingPhaseStep) % 1;
1549
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
1550
+ }, GRADIENT_ANIMATION.streamingInterval);
1551
+ return () => clearInterval(interval);
1552
+ }, [isStreaming, streamingPlaceholder]);
1553
+ const handleSubmit = useCallback2(async (value) => {
565
1554
  const trimmed = value.trim();
566
1555
  if (!trimmed) return;
567
1556
  if (trimmed.startsWith("/")) {
@@ -573,8 +1562,9 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
573
1562
  process.exit(0);
574
1563
  return;
575
1564
  case "clear":
576
- setMessages([]);
1565
+ setDisplayMessages([]);
577
1566
  setInput("");
1567
+ sessionRef.current?.reset();
578
1568
  return;
579
1569
  case "navigate":
580
1570
  setInput("");
@@ -585,7 +1575,7 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
585
1575
  }
586
1576
  return;
587
1577
  case "text":
588
- setMessages((prev) => [
1578
+ setDisplayMessages((prev) => [
589
1579
  ...prev,
590
1580
  { role: "user", content: trimmed },
591
1581
  { role: "assistant", content: result.content }
@@ -594,7 +1584,7 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
594
1584
  return;
595
1585
  }
596
1586
  }
597
- setMessages((prev) => [
1587
+ setDisplayMessages((prev) => [
598
1588
  ...prev,
599
1589
  { role: "user", content: trimmed },
600
1590
  { role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
@@ -602,64 +1592,220 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
602
1592
  setInput("");
603
1593
  return;
604
1594
  }
605
- setMessages((prev) => [
1595
+ if (!sessionRef.current) {
1596
+ setDisplayMessages((prev) => [
1597
+ ...prev,
1598
+ { role: "user", content: trimmed },
1599
+ { role: "assistant", content: "\u26A0 \u65E0\u6CD5\u8FDE\u63A5\u5230 Provider\u3002\u8BF7\u68C0\u67E5 API Key \u548C\u7F51\u7EDC\u914D\u7F6E\u3002" }
1600
+ ]);
1601
+ setInput("");
1602
+ return;
1603
+ }
1604
+ setDisplayMessages((prev) => [
606
1605
  ...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" }
1606
+ { role: "user", content: trimmed }
609
1607
  ]);
610
1608
  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) => {
1609
+ setIsStreaming(true);
1610
+ setStreamingPlaceholder(pickRandom(STREAMING_PLACEHOLDERS));
1611
+ setCurrentContent("");
1612
+ setCurrentToolCalls([]);
1613
+ setCurrentUsage(void 0);
1614
+ setCurrentElapsed(void 0);
1615
+ setCurrentCost(void 0);
1616
+ setCurrentModel(void 0);
1617
+ setStreamError(void 0);
1618
+ currentContentRef.current = "";
1619
+ currentToolCallsRef.current = [];
1620
+ currentUsageRef.current = void 0;
1621
+ currentElapsedRef.current = void 0;
1622
+ currentCostRef.current = void 0;
1623
+ currentModelRef.current = void 0;
1624
+ streamErrorRef.current = void 0;
1625
+ const session = sessionRef.current;
1626
+ const abortController = new AbortController();
1627
+ abortRef.current = abortController;
1628
+ try {
1629
+ for await (const event of session.chat(trimmed)) {
1630
+ if (abortController.signal.aborted) break;
1631
+ switch (event.type) {
1632
+ case "text_delta":
1633
+ setCurrentContent((prev) => {
1634
+ const next = prev + event.content;
1635
+ currentContentRef.current = next;
1636
+ return next;
1637
+ });
1638
+ break;
1639
+ case "tool_calls":
1640
+ setCurrentToolCalls((prev) => {
1641
+ const next = [...prev, ...event.calls];
1642
+ currentToolCallsRef.current = next;
1643
+ return next;
1644
+ });
1645
+ break;
1646
+ case "usage":
1647
+ setCurrentUsage(event.usage);
1648
+ setCurrentModel(event.model);
1649
+ currentUsageRef.current = event.usage;
1650
+ currentModelRef.current = event.model;
1651
+ break;
1652
+ case "done":
1653
+ setCurrentElapsed(event.elapsed);
1654
+ currentElapsedRef.current = event.elapsed;
1655
+ break;
1656
+ case "error":
1657
+ setStreamError(event.error.message);
1658
+ streamErrorRef.current = event.error.message;
1659
+ break;
1660
+ }
1661
+ }
1662
+ } catch (err) {
1663
+ const msg = err instanceof Error ? err.message : String(err);
1664
+ setStreamError(msg);
1665
+ streamErrorRef.current = msg;
1666
+ } finally {
1667
+ setIsStreaming(false);
1668
+ setIdlePlaceholder(pickRandom(IDLE_PLACEHOLDERS));
1669
+ abortRef.current = null;
1670
+ const finContent = currentContentRef.current;
1671
+ const finToolCalls = currentToolCallsRef.current.length > 0 ? currentToolCallsRef.current : void 0;
1672
+ const finStreamError = streamErrorRef.current;
1673
+ if (finContent || finToolCalls || finStreamError) {
1674
+ const completed = {
1675
+ content: finStreamError ? `\u26A0 \u8BF7\u6C42\u51FA\u9519\uFF1A${finStreamError}` : finContent || "",
1676
+ toolCalls: finToolCalls,
1677
+ usage: currentUsageRef.current,
1678
+ elapsed: currentElapsedRef.current,
1679
+ cost: currentCostRef.current,
1680
+ model: currentModelRef.current
1681
+ };
1682
+ setDisplayMessages((prev) => [
1683
+ ...prev,
1684
+ {
1685
+ role: "assistant",
1686
+ content: completed.content,
1687
+ assistantDetail: completed
1688
+ }
1689
+ ]);
1690
+ }
1691
+ }
1692
+ }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls]);
1693
+ useEffect3(() => {
1694
+ if (!isStreaming && externalCostTracker) {
1695
+ setTodayCost(externalCostTracker.todayTotalCost);
1696
+ }
1697
+ }, [isStreaming, externalCostTracker]);
1698
+ useEffect3(() => {
1699
+ if (currentUsage && currentModel && !currentCost) {
1700
+ import("./models-NQGNKB4T.js").then(({ calculateCost: calculateCost2 }) => {
1701
+ const cost = calculateCost2(currentUsage, currentModel);
1702
+ setCurrentCost(cost.totalCost);
1703
+ currentCostRef.current = cost.totalCost;
1704
+ });
1705
+ }
1706
+ }, [currentUsage, currentModel, currentCost]);
1707
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
1708
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
1709
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
615
1710
  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);
1711
+ return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
617
1712
  }) }),
618
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", justifyContent: "center", children: [
619
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ff41", children: [
1713
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", justifyContent: "center", children: [
1714
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ff41", children: [
620
1715
  " \u2714 ",
621
1716
  "\u5DF2\u52A0\u8F7D ",
622
1717
  providerCount,
623
1718
  " \u4E2A Provider"
624
1719
  ] }),
625
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", children: [
1720
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
626
1721
  " \u2139 ",
627
1722
  "\u5DF2\u5C31\u7EEA ",
628
1723
  toolCount,
629
1724
  " \u4E2A\u5DE5\u5177"
630
1725
  ] }),
631
- verbose ? /* @__PURE__ */ jsx3(Text4, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
1726
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
1727
+ " \u{1F527} \u6A21\u578B ",
1728
+ model
1729
+ ] }),
1730
+ verbose ? /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
632
1731
  ] }),
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 })
1732
+ /* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
1733
+ balanceLoading && balance === null ? /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
1734
+ /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: "\u{1F4B0} " }),
1735
+ /* @__PURE__ */ jsxs5(Text6, { color: "yellow", children: [
1736
+ "\u4F59\u989D \xA5",
1737
+ balance.toFixed(2)
1738
+ ] })
1739
+ ] }) : null,
1740
+ todayCost !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
1741
+ /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: "\u{1F4CA} " }),
1742
+ /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
1743
+ "\u4ECA\u65E5 \xA5",
1744
+ formatMoney(todayCost).replace("\xA5", "")
1745
+ ] })
1746
+ ] }) : null
1747
+ ] })
637
1748
  ] }),
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(
1749
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1750
+ /* @__PURE__ */ jsx5(Static, { items: displayMessages, children: (msg, i) => {
1751
+ if (msg.role === "user") {
1752
+ return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1753
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
1754
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(Text6, { wrap: "wrap", children: msg.content }) })
1755
+ ] }, i);
1756
+ }
1757
+ const detail = msg.assistantDetail;
1758
+ return /* @__PURE__ */ jsx5(
1759
+ AssistantMessage,
1760
+ {
1761
+ content: msg.content,
1762
+ toolCalls: detail?.toolCalls,
1763
+ isStreaming: false,
1764
+ usage: detail?.usage,
1765
+ elapsed: detail?.elapsed,
1766
+ cost: detail?.cost,
1767
+ model: detail?.model
1768
+ },
1769
+ i
1770
+ );
1771
+ } }),
1772
+ isStreaming && /* @__PURE__ */ jsx5(
1773
+ AssistantMessage,
1774
+ {
1775
+ content: currentContent,
1776
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
1777
+ isStreaming: true
1778
+ }
1779
+ ),
1780
+ isStreaming && !currentContent && currentToolCalls.length === 0 && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 4, children: /* @__PURE__ */ jsx5(Spinner, { type: "dots", label: "\u601D\u8003\u4E2D..." }) }),
1781
+ !isStreaming && streamError && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
1782
+ "\u26A0 ",
1783
+ streamError
1784
+ ] }) })
1785
+ ] }),
1786
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1787
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1788
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
1789
+ /* @__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(
645
1790
  TextInput,
646
1791
  {
647
1792
  value: input,
648
1793
  onChange: setInput,
649
1794
  onSubmit: handleSubmit,
650
- placeholder: "\u8F93\u5165\u4F60\u7684\u95EE\u9898..."
1795
+ placeholder: ""
651
1796
  }
652
1797
  ) })
653
1798
  ] }),
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" }) })
1799
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1800
+ 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" }) }),
1801
+ 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
1802
  ] });
657
1803
  }
658
1804
 
659
1805
  // src/ui/GamePicker.tsx
660
- import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
1806
+ import { Box as Box6, Text as Text7, useInput as useInput2 } from "ink";
661
1807
  import { useState as useState4, useCallback as useCallback3 } from "react";
662
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1808
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
663
1809
  function GamePicker({ games, onSelect, onExit, onBackToChat }) {
664
1810
  const [selectedIndex, setSelectedIndex] = useState4(0);
665
1811
  const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
@@ -687,18 +1833,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
687
1833
  [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
688
1834
  )
689
1835
  );
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) => {
1836
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1837
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
1838
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: games.map((game, index) => {
693
1839
  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 }) })
1840
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", children: [
1841
+ /* @__PURE__ */ jsx6(Box6, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx6(Text7, { children: " " }) }),
1842
+ /* @__PURE__ */ jsx6(Box6, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
1843
+ /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text7, { color: "#888888", children: game.description }) })
698
1844
  ] }, game.id);
699
1845
  }) }),
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" }) })
1846
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
1847
+ 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
1848
  ] });
703
1849
  }
704
1850
 
@@ -715,9 +1861,9 @@ function listGames() {
715
1861
  }
716
1862
 
717
1863
  // 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";
1864
+ import { Box as Box7, Text as Text8, useInput as useInput3, render as render2 } from "ink";
1865
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
1866
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
721
1867
  var GAME_WIDTH = 40;
722
1868
  var GAME_HEIGHT = 18;
723
1869
  var PADDLE_WIDTH = 9;
@@ -850,9 +1996,9 @@ function buildBoard(state) {
850
1996
  function BrickBreakerGame({ onExit: _onExit }) {
851
1997
  const [initialLevel, setInitialLevel] = useState5(1);
852
1998
  const [selectingLevel, setSelectingLevel] = useState5(true);
853
- const stateRef = useRef2(createInitialState(initialLevel));
1999
+ const stateRef = useRef3(createInitialState(initialLevel));
854
2000
  const [tick, setTick] = useState5(0);
855
- const onExitRef = useRef2(_onExit);
2001
+ const onExitRef = useRef3(_onExit);
856
2002
  onExitRef.current = _onExit;
857
2003
  useEffect4(() => {
858
2004
  if (selectingLevel) return;
@@ -907,49 +2053,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
907
2053
  const board = buildBoard(s);
908
2054
  const def = getLevel(s.level);
909
2055
  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: [
2056
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2057
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
2058
+ /* @__PURE__ */ jsx7(Box7, { width: 20, children: /* @__PURE__ */ jsxs7(Text8, { children: [
913
2059
  "\u5173\u5361 ",
914
2060
  s.level,
915
2061
  ": ",
916
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: def.desc })
2062
+ /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: def.desc })
917
2063
  ] }) }),
918
- /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2064
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
919
2065
  "\u5206\u6570: ",
920
- /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: String(s.score).padStart(3, "0") })
2066
+ /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: String(s.score).padStart(3, "0") })
921
2067
  ] }) }),
922
- /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2068
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
923
2069
  "\u751F\u547D: ",
924
- /* @__PURE__ */ jsx5(Text6, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
2070
+ /* @__PURE__ */ jsx7(Text8, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
925
2071
  ] }) }),
926
- /* @__PURE__ */ jsx5(Box5, { width: 10, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2072
+ /* @__PURE__ */ jsx7(Box7, { width: 10, children: /* @__PURE__ */ jsxs7(Text8, { children: [
927
2073
  "\u7816\u5757: ",
928
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: aliveCount })
2074
+ /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: aliveCount })
929
2075
  ] }) }),
930
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text6, { color: s.paused ? "gray" : "green", children: [
2076
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: s.paused ? "gray" : "green", children: [
931
2077
  "[",
932
2078
  s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
933
2079
  "]"
934
2080
  ] }) })
935
2081
  ] }),
936
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
937
- /* @__PURE__ */ jsxs5(Text6, { children: [
2082
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2083
+ /* @__PURE__ */ jsxs7(Text8, { children: [
938
2084
  "\u250C",
939
2085
  "\u2500".repeat(GAME_WIDTH),
940
2086
  "\u2510"
941
2087
  ] }),
942
- /* @__PURE__ */ jsx5(Text6, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
943
- /* @__PURE__ */ jsxs5(Text6, { children: [
2088
+ /* @__PURE__ */ jsx7(Text8, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
2089
+ /* @__PURE__ */ jsxs7(Text8, { children: [
944
2090
  "\u2514",
945
2091
  "\u2500".repeat(GAME_WIDTH),
946
2092
  "\u2518"
947
2093
  ] })
948
2094
  ] }),
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) }),
2095
+ selectingLevel && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2096
+ /* @__PURE__ */ jsx7(Text8, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
2097
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx7(Box7, { width: 22, children: /* @__PURE__ */ jsxs7(Text8, { children: [
2098
+ /* @__PURE__ */ jsx7(Text8, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
953
2099
  ". ",
954
2100
  lv.desc,
955
2101
  " (",
@@ -958,16 +2104,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
958
2104
  lv.cols,
959
2105
  ")"
960
2106
  ] }) }, i)) }),
961
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
2107
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
962
2108
  ] }),
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: [
2109
+ !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
2110
+ /* @__PURE__ */ jsx7(Text8, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
2111
+ /* @__PURE__ */ jsxs7(Text8, { children: [
966
2112
  " \u5206\u6570: ",
967
- /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: s.score })
2113
+ /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: s.score })
968
2114
  ] })
969
2115
  ] }),
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" }) })
2116
+ !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
2117
  ] });
972
2118
  }
973
2119
  var brick_breaker_default = {
@@ -977,7 +2123,7 @@ var brick_breaker_default = {
977
2123
  play: async () => {
978
2124
  await new Promise((resolve) => {
979
2125
  const { unmount } = render2(
980
- /* @__PURE__ */ jsx5(BrickBreakerGame, { onExit: () => {
2126
+ /* @__PURE__ */ jsx7(BrickBreakerGame, { onExit: () => {
981
2127
  unmount();
982
2128
  resolve();
983
2129
  } })
@@ -987,9 +2133,9 @@ var brick_breaker_default = {
987
2133
  };
988
2134
 
989
2135
  // 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";
2136
+ import { Box as Box8, Text as Text9, useInput as useInput4, render as render3 } from "ink";
2137
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback5 } from "react";
2138
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
993
2139
  var GAME_W = 66;
994
2140
  var GAME_H = 20;
995
2141
  var SCORE_H = 6;
@@ -1380,10 +2526,10 @@ function buildGameView(s, scoreLines, scoreColor, message) {
1380
2526
  return rows;
1381
2527
  }
1382
2528
  function CoderCheck({ onExit: _onExit }) {
1383
- const stateRef = useRef3(createInitialState2());
2529
+ const stateRef = useRef4(createInitialState2());
1384
2530
  const [tick, setTick] = useState6(0);
1385
2531
  const [colorOffset, setColorOffset] = useState6(0);
1386
- const onExitRef = useRef3(_onExit);
2532
+ const onExitRef = useRef4(_onExit);
1387
2533
  onExitRef.current = _onExit;
1388
2534
  useEffect5(() => {
1389
2535
  const timer = setInterval(() => {
@@ -1460,44 +2606,44 @@ function CoderCheck({ onExit: _onExit }) {
1460
2606
  const scoreLines = buildScoreLines(scoreStr);
1461
2607
  const view = buildGameView(s, scoreLines, scoreColor, s.message);
1462
2608
  void tick;
1463
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
1464
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "row", children: /* @__PURE__ */ jsxs6(Text7, { children: [
2609
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
2610
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "row", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1465
2611
  "\u751F\u547D ",
1466
- /* @__PURE__ */ jsx6(Text7, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
2612
+ /* @__PURE__ */ jsx8(Text9, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
1467
2613
  " ",
1468
2614
  "\u901F\u5EA6 ",
1469
- /* @__PURE__ */ jsxs6(Text7, { color: "cyan", children: [
2615
+ /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
1470
2616
  "Lv.",
1471
2617
  Math.floor(s.speed * 10)
1472
2618
  ] })
1473
2619
  ] }) }),
1474
- !s.gameOver && s.target && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { children: [
2620
+ !s.gameOver && s.target && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
1475
2621
  "\u6253\u5B57: ",
1476
- /* @__PURE__ */ jsx6(Text7, { color: "green", children: s.typed }),
1477
- /* @__PURE__ */ jsx6(Text7, { color: "white", children: s.target.slice(s.typed.length) })
2622
+ /* @__PURE__ */ jsx8(Text9, { color: "green", children: s.typed }),
2623
+ /* @__PURE__ */ jsx8(Text9, { color: "white", children: s.target.slice(s.typed.length) })
1478
2624
  ] }) }),
1479
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
1480
- /* @__PURE__ */ jsxs6(Text7, { children: [
2625
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
2626
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1481
2627
  "\u250C",
1482
2628
  "\u2500".repeat(GAME_W),
1483
2629
  "\u2510"
1484
2630
  ] }),
1485
- view.map((row, i) => /* @__PURE__ */ jsx6(Text7, { children: `\u2502${row}\u2502` }, i)),
1486
- /* @__PURE__ */ jsxs6(Text7, { children: [
2631
+ view.map((row, i) => /* @__PURE__ */ jsx8(Text9, { children: `\u2502${row}\u2502` }, i)),
2632
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1487
2633
  "\u2514",
1488
2634
  "\u2500".repeat(GAME_W),
1489
2635
  "\u2518"
1490
2636
  ] })
1491
2637
  ] }),
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: [
2638
+ s.gameOver && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
2639
+ /* @__PURE__ */ jsx8(Text9, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
2640
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1495
2641
  " \u5F97\u5206: ",
1496
- /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: s.score }),
2642
+ /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: s.score }),
1497
2643
  " r \u91CD\u5F00 q \u9000\u51FA"
1498
2644
  ] })
1499
2645
  ] }),
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" }) })
2646
+ /* @__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
2647
  ] });
1502
2648
  }
1503
2649
  var coder_check_default = {
@@ -1507,7 +2653,7 @@ var coder_check_default = {
1507
2653
  play: async () => {
1508
2654
  await new Promise((resolve) => {
1509
2655
  const { unmount } = render3(
1510
- /* @__PURE__ */ jsx6(CoderCheck, { onExit: () => {
2656
+ /* @__PURE__ */ jsx8(CoderCheck, { onExit: () => {
1511
2657
  unmount();
1512
2658
  resolve();
1513
2659
  } })
@@ -1528,10 +2674,10 @@ import { render as render4 } from "ink";
1528
2674
  import chalk3 from "chalk";
1529
2675
 
1530
2676
  // src/stock/StockList.tsx
1531
- import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
2677
+ import { Box as Box9, Text as Text10, useInput as useInput5 } from "ink";
1532
2678
  import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
1533
2679
  import asciichart from "asciichart";
1534
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2680
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1535
2681
  var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
1536
2682
  function normalizeApiCode(code) {
1537
2683
  if (code.startsWith("sh") || code.startsWith("sz")) return code;
@@ -1728,57 +2874,57 @@ function StockList({ codes, onExit, onBackToChat }) {
1728
2874
  );
1729
2875
  if (detailView) {
1730
2876
  if (detailLoading) {
1731
- return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
2877
+ return /* @__PURE__ */ jsx9(Box9, { paddingLeft: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
1732
2878
  }
1733
2879
  return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
1734
2880
  }
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: [
2881
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2882
+ /* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
2883
+ /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
2884
+ /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1739
2885
  " \u{1F550} ",
1740
2886
  currentTime
1741
2887
  ] }),
1742
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
2888
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
1743
2889
  ] }),
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" }) })
2890
+ /* @__PURE__ */ jsxs9(Box9, { children: [
2891
+ /* @__PURE__ */ jsx9(Box9, { width: 3 }),
2892
+ /* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u4EE3\u7801" }) }),
2893
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u540D\u79F0" }) }),
2894
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
2895
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
2896
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
2897
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u9AD8" }) }),
2898
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u4F4E" }) }),
2899
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
1754
2900
  ] }),
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) => {
2901
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
2902
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: stocks.map((stock, index) => {
1757
2903
  const isSelected = index === selectedIndex;
1758
2904
  const isUp = stock.changePercent >= 0;
1759
2905
  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: [
2906
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
2907
+ /* @__PURE__ */ jsx9(Box9, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx9(Text10, { children: " " }) }),
2908
+ /* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
2909
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
2910
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color, children: formatPrice(stock.price) }) }),
2911
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
1766
2912
  isUp ? "+" : "",
1767
2913
  stock.changePercent.toFixed(2),
1768
2914
  "%"
1769
2915
  ] }) }),
1770
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
2916
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
1771
2917
  isUp ? "+" : "",
1772
2918
  stock.changeAmount.toFixed(3)
1773
2919
  ] }) }),
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) }) })
2920
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#cccccc", children: formatPrice(stock.high) }) }),
2921
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatPrice(stock.low) }) }),
2922
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatAmount(stock.amount) }) })
1777
2923
  ] }, stock.code);
1778
2924
  }) }),
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" }) })
2925
+ /* @__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` }) }),
2926
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
2927
+ 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
2928
  ] });
1783
2929
  }
1784
2930
  function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
@@ -1796,33 +2942,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1796
2942
  raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
1797
2943
  chartLines = raw.split("\n");
1798
2944
  }
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: [
2945
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, children: [
2946
+ /* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
2947
+ /* @__PURE__ */ jsxs9(Box9, { children: [
2948
+ /* @__PURE__ */ jsxs9(Text10, { bold: true, color: "#00ffff", children: [
1803
2949
  " \u{1F4CA} ",
1804
2950
  stock.name,
1805
2951
  " "
1806
2952
  ] }),
1807
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code }),
1808
- currentTime && /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
2953
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: stock.code }),
2954
+ currentTime && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1809
2955
  " \u{1F550} ",
1810
2956
  currentTime
1811
2957
  ] })
1812
2958
  ] }),
1813
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
2959
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
1814
2960
  ] }),
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: [
2961
+ /* @__PURE__ */ jsxs9(Box9, { children: [
2962
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
2963
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { bold: true, color: colorCode, children: [
1818
2964
  arrow,
1819
2965
  " ",
1820
2966
  formatPrice(stock.price)
1821
2967
  ] }) })
1822
2968
  ] }),
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: [
2969
+ /* @__PURE__ */ jsxs9(Box9, { children: [
2970
+ /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
2971
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: colorCode, children: [
1826
2972
  isUp ? "+" : "",
1827
2973
  stock.changePercent.toFixed(2),
1828
2974
  "%",
@@ -1831,13 +2977,13 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1831
2977
  stock.changeAmount.toFixed(3)
1832
2978
  ] }) })
1833
2979
  ] }),
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" }) })
2980
+ 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)) }),
2981
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
1836
2982
  ] });
1837
2983
  }
1838
2984
 
1839
2985
  // src/cli/index.tsx
1840
- import { jsx as jsx8 } from "react/jsx-runtime";
2986
+ import { jsx as jsx10 } from "react/jsx-runtime";
1841
2987
  var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
1842
2988
  function createCli() {
1843
2989
  const program2 = new Command();
@@ -1863,14 +3009,19 @@ function createCli() {
1863
3009
  const result = await loadAndValidate();
1864
3010
  ctx = { ...ctx, config: result.config };
1865
3011
  }
1866
- startChat(ctx);
3012
+ const costTracker = new CostTracker({
3013
+ budgetLimit: ctx?.config.budgetLimit ?? 0,
3014
+ tokenBudgetLimit: ctx?.config.tokenBudgetLimit ?? 0
3015
+ });
3016
+ startChat(ctx, costTracker);
1867
3017
  });
1868
- function startChat(ctx) {
3018
+ function startChat(ctx, costTracker) {
1869
3019
  const defaultProvider = ctx?.config.providers.find(
1870
3020
  (p) => p.name === (ctx?.config.defaultProvider ?? "deepseek")
1871
3021
  );
3022
+ const model = defaultProvider?.model ?? "deepseek-v4-flash";
1872
3023
  const chatApp = renderApp(
1873
- /* @__PURE__ */ jsx8(
3024
+ /* @__PURE__ */ jsx10(
1874
3025
  ChatSession,
1875
3026
  {
1876
3027
  providerCount: ctx?.config.providers.length ?? 1,
@@ -1878,24 +3029,26 @@ function createCli() {
1878
3029
  verbose: ctx?.verbose ?? false,
1879
3030
  apiKey: defaultProvider?.apiKey,
1880
3031
  baseUrl: defaultProvider?.baseUrl ?? "https://api.deepseek.com",
3032
+ costTracker,
3033
+ model,
1881
3034
  onLaunchGame: () => {
1882
3035
  chatApp.unmount();
1883
3036
  setImmediate(() => {
1884
3037
  initGames();
1885
3038
  const games = listGames();
1886
3039
  const { unmount } = render4(
1887
- /* @__PURE__ */ jsx8(
3040
+ /* @__PURE__ */ jsx10(
1888
3041
  GamePicker,
1889
3042
  {
1890
3043
  games,
1891
3044
  onSelect: async (game) => {
1892
3045
  unmount();
1893
3046
  await game.play();
1894
- startChat(ctx);
3047
+ startChat(ctx, costTracker);
1895
3048
  },
1896
3049
  onBackToChat: () => {
1897
3050
  unmount();
1898
- setImmediate(() => startChat(ctx));
3051
+ setImmediate(() => startChat(ctx, costTracker));
1899
3052
  }
1900
3053
  }
1901
3054
  ),
@@ -1908,13 +3061,13 @@ function createCli() {
1908
3061
  setImmediate(() => {
1909
3062
  const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1910
3063
  const stockApp = renderApp(
1911
- /* @__PURE__ */ jsx8(
3064
+ /* @__PURE__ */ jsx10(
1912
3065
  StockList,
1913
3066
  {
1914
3067
  codes: defaultStockCodes,
1915
3068
  onBackToChat: () => {
1916
3069
  stockApp.unmount();
1917
- setImmediate(() => startChat(ctx));
3070
+ setImmediate(() => startChat(ctx, costTracker));
1918
3071
  },
1919
3072
  onExit: () => process.exit(0)
1920
3073
  }
@@ -1972,10 +3125,10 @@ compdef _dskcode_completion dskcode`);
1972
3125
  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
3126
  const ctx = this.dskcodeCtx;
1974
3127
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
1975
- const globalConfigPath = join2(home, ".dskcode", "settings.json");
3128
+ const globalConfigPath = join3(home, ".dskcode", "settings.json");
1976
3129
  let globalConfigHasStock = false;
1977
3130
  try {
1978
- const raw = await readFile2(globalConfigPath, "utf-8");
3131
+ const raw = await readFile3(globalConfigPath, "utf-8");
1979
3132
  const parsed = JSON.parse(raw);
1980
3133
  const stock = parsed.stock;
1981
3134
  globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
@@ -1995,7 +3148,7 @@ compdef _dskcode_completion dskcode`);
1995
3148
  const freshResult = await loadAndValidate();
1996
3149
  const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1997
3150
  const app = renderApp(
1998
- /* @__PURE__ */ jsx8(
3151
+ /* @__PURE__ */ jsx10(
1999
3152
  StockList,
2000
3153
  {
2001
3154
  codes: codeList,
@@ -2024,7 +3177,7 @@ compdef _dskcode_completion dskcode`);
2024
3177
  }
2025
3178
  const selectedGame = await new Promise((resolve) => {
2026
3179
  const { unmount } = render4(
2027
- /* @__PURE__ */ jsx8(
3180
+ /* @__PURE__ */ jsx10(
2028
3181
  GamePicker,
2029
3182
  {
2030
3183
  games,