dskcode 0.1.9 → 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,6 +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
  }
195
+ const SUPPORTED_MODELS2 = ["deepseek-v4-flash", "deepseek-v4-pro"];
169
196
  for (let i = 0; i < config.providers.length; i++) {
170
197
  const p = config.providers[i];
171
198
  if (!p.name) {
@@ -179,6 +206,11 @@ function validateConfig(config) {
179
206
  field: `providers[${i}].model`,
180
207
  message: `Provider "${p.name || i}" \u7F3A\u5C11 model \u5B57\u6BB5\u3002`
181
208
  });
209
+ } else if (!SUPPORTED_MODELS2.includes(p.model)) {
210
+ errors.push({
211
+ field: `providers[${i}].model`,
212
+ message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${SUPPORTED_MODELS2.join(", ")}`
213
+ });
182
214
  }
183
215
  }
184
216
  if (config.defaultProvider) {
@@ -210,6 +242,18 @@ function validateConfig(config) {
210
242
  message: "maxToolRounds \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
211
243
  });
212
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
+ }
213
257
  return errors;
214
258
  }
215
259
  async function loadConfig(configPath) {
@@ -415,8 +459,8 @@ async function promptForApiKey() {
415
459
  }
416
460
 
417
461
  // src/cli/index.tsx
418
- import { readFile as readFile2 } from "fs/promises";
419
- import { join as join2 } from "path";
462
+ import { readFile as readFile3 } from "fs/promises";
463
+ import { join as join3 } from "path";
420
464
 
421
465
  // src/ui/RenderScope.tsx
422
466
  import { render } from "ink";
@@ -429,6 +473,15 @@ function renderApp(node) {
429
473
  import { Text } from "ink";
430
474
  import InkSpinner from "ink-spinner";
431
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
+ }
432
485
 
433
486
  // src/ui/StatusMessage.tsx
434
487
  import { Box, Text as Text2 } from "ink";
@@ -449,9 +502,9 @@ var LOGO_LINES = [
449
502
  ];
450
503
 
451
504
  // src/ui/ChatSession.tsx
452
- import { Box as Box3, Text as Text4, useInput } from "ink";
505
+ import { Box as Box5, Text as Text6, useInput, Static } from "ink";
453
506
  import TextInput from "ink-text-input";
454
- 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";
455
508
 
456
509
  // src/ui/useDoubleCtrlC.ts
457
510
  import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
@@ -481,8 +534,824 @@ function useDoubleCtrlC(onExit) {
481
534
  return { doubleCtrlC, handleCtrlC };
482
535
  }
483
536
 
484
- // 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";
485
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";
486
1355
  var commandRegistry = /* @__PURE__ */ new Map();
487
1356
  function registerCommand(name, cmd) {
488
1357
  commandRegistry.set(name, cmd);
@@ -504,22 +1373,89 @@ registerCommand("/help", {
504
1373
  }
505
1374
  });
506
1375
  registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
507
- 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" }) });
508
1377
  registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
509
1378
  registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
510
- function ChatSession({ providerCount, toolCount, verbose, 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);
511
1411
  const [offset, setOffset] = useState3(0);
512
- const [messages, setMessages] = useState3([]);
1412
+ const [displayMessages, setDisplayMessages] = useState3([]);
513
1413
  const [input, setInput] = useState3("");
514
- const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => process.exit(0));
1414
+ const [balance, setBalance] = useState3(null);
1415
+ const [balanceLoading, setBalanceLoading] = useState3(false);
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
+ });
515
1443
  useInput(
516
1444
  useCallback2(
517
- (input2, key) => {
518
- if (input2 === "c" && key.ctrl) {
519
- 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);
520
1456
  }
521
1457
  },
522
- [handleCtrlC]
1458
+ [isStreaming, handleCtrlC, input]
523
1459
  )
524
1460
  );
525
1461
  useEffect3(() => {
@@ -528,7 +1464,93 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
528
1464
  }, 500);
529
1465
  return () => clearInterval(timer);
530
1466
  }, []);
531
- const handleSubmit = useCallback2((value) => {
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]);
1484
+ useEffect3(() => {
1485
+ if (!apiKey || !baseUrl) return;
1486
+ let cancelled = false;
1487
+ setBalanceLoading(true);
1488
+ import("./deepseek-OIJOG3N5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
1489
+ const provider = new DeepSeekProvider2({
1490
+ apiKey,
1491
+ baseUrl,
1492
+ model: "deepseek-v4-flash"
1493
+ });
1494
+ return provider.getBalance();
1495
+ }).then((result) => {
1496
+ if (cancelled) return;
1497
+ const cny = result.balances.find((b) => b.currency === "CNY");
1498
+ if (cny) {
1499
+ setBalance(cny.totalBalance);
1500
+ }
1501
+ }).catch(() => {
1502
+ }).finally(() => {
1503
+ if (!cancelled) setBalanceLoading(false);
1504
+ });
1505
+ return () => {
1506
+ cancelled = true;
1507
+ };
1508
+ }, [apiKey, baseUrl]);
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) => {
532
1554
  const trimmed = value.trim();
533
1555
  if (!trimmed) return;
534
1556
  if (trimmed.startsWith("/")) {
@@ -540,8 +1562,9 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
540
1562
  process.exit(0);
541
1563
  return;
542
1564
  case "clear":
543
- setMessages([]);
1565
+ setDisplayMessages([]);
544
1566
  setInput("");
1567
+ sessionRef.current?.reset();
545
1568
  return;
546
1569
  case "navigate":
547
1570
  setInput("");
@@ -552,7 +1575,7 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
552
1575
  }
553
1576
  return;
554
1577
  case "text":
555
- setMessages((prev) => [
1578
+ setDisplayMessages((prev) => [
556
1579
  ...prev,
557
1580
  { role: "user", content: trimmed },
558
1581
  { role: "assistant", content: result.content }
@@ -561,7 +1584,7 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
561
1584
  return;
562
1585
  }
563
1586
  }
564
- setMessages((prev) => [
1587
+ setDisplayMessages((prev) => [
565
1588
  ...prev,
566
1589
  { role: "user", content: trimmed },
567
1590
  { role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
@@ -569,60 +1592,220 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
569
1592
  setInput("");
570
1593
  return;
571
1594
  }
572
- 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) => [
573
1605
  ...prev,
574
- { role: "user", content: trimmed },
575
- { 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 }
576
1607
  ]);
577
1608
  setInput("");
578
- }, [onLaunchGame, onLaunchStock]);
579
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
580
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", marginBottom: 1, children: [
581
- /* @__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) => {
582
1710
  const colorIndex = (i + offset) % CYBER_PALETTE.length;
583
- 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);
584
1712
  }) }),
585
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", justifyContent: "center", children: [
586
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ff41", children: [
1713
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", justifyContent: "center", children: [
1714
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ff41", children: [
587
1715
  " \u2714 ",
588
1716
  "\u5DF2\u52A0\u8F7D ",
589
1717
  providerCount,
590
1718
  " \u4E2A Provider"
591
1719
  ] }),
592
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", children: [
1720
+ /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
593
1721
  " \u2139 ",
594
1722
  "\u5DF2\u5C31\u7EEA ",
595
1723
  toolCount,
596
1724
  " \u4E2A\u5DE5\u5177"
597
1725
  ] }),
598
- 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
1731
+ ] }),
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
599
1747
  ] })
600
1748
  ] }),
601
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: messages.map((msg, i) => /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
602
- /* @__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}" }) }),
603
- /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, children: /* @__PURE__ */ jsx3(Text4, { wrap: "wrap", children: msg.content }) })
604
- ] }, i)) }),
605
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
606
- /* @__PURE__ */ jsx3(Box3, { width: 8, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text4, { bold: true, color: "#00ff41", children: " \u26A1" }) }),
607
- /* @__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(
608
1790
  TextInput,
609
1791
  {
610
1792
  value: input,
611
1793
  onChange: setInput,
612
1794
  onSubmit: handleSubmit,
613
- placeholder: "\u8F93\u5165\u4F60\u7684\u95EE\u9898..."
1795
+ placeholder: ""
614
1796
  }
615
1797
  ) })
616
1798
  ] }),
617
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#00ffff", dimColor: true, children: " " + "\u2500".repeat(36) }) }),
618
- 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" }) })
619
1802
  ] });
620
1803
  }
621
1804
 
622
1805
  // src/ui/GamePicker.tsx
623
- 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";
624
1807
  import { useState as useState4, useCallback as useCallback3 } from "react";
625
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1808
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
626
1809
  function GamePicker({ games, onSelect, onExit, onBackToChat }) {
627
1810
  const [selectedIndex, setSelectedIndex] = useState4(0);
628
1811
  const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
@@ -650,18 +1833,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
650
1833
  [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
651
1834
  )
652
1835
  );
653
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
654
- /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
655
- /* @__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) => {
656
1839
  const isSelected = index === selectedIndex;
657
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
658
- /* @__PURE__ */ jsx4(Box4, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx4(Text5, { children: " " }) }),
659
- /* @__PURE__ */ jsx4(Box4, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
660
- /* @__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 }) })
661
1844
  ] }, game.id);
662
1845
  }) }),
663
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
664
- 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" }) })
665
1848
  ] });
666
1849
  }
667
1850
 
@@ -678,9 +1861,9 @@ function listGames() {
678
1861
  }
679
1862
 
680
1863
  // src/game/brick-breaker/index.tsx
681
- import { Box as Box5, Text as Text6, useInput as useInput3, render as render2 } from "ink";
682
- import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback4 } from "react";
683
- 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";
684
1867
  var GAME_WIDTH = 40;
685
1868
  var GAME_HEIGHT = 18;
686
1869
  var PADDLE_WIDTH = 9;
@@ -813,9 +1996,9 @@ function buildBoard(state) {
813
1996
  function BrickBreakerGame({ onExit: _onExit }) {
814
1997
  const [initialLevel, setInitialLevel] = useState5(1);
815
1998
  const [selectingLevel, setSelectingLevel] = useState5(true);
816
- const stateRef = useRef2(createInitialState(initialLevel));
1999
+ const stateRef = useRef3(createInitialState(initialLevel));
817
2000
  const [tick, setTick] = useState5(0);
818
- const onExitRef = useRef2(_onExit);
2001
+ const onExitRef = useRef3(_onExit);
819
2002
  onExitRef.current = _onExit;
820
2003
  useEffect4(() => {
821
2004
  if (selectingLevel) return;
@@ -870,49 +2053,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
870
2053
  const board = buildBoard(s);
871
2054
  const def = getLevel(s.level);
872
2055
  void tick;
873
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
874
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
875
- /* @__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: [
876
2059
  "\u5173\u5361 ",
877
2060
  s.level,
878
2061
  ": ",
879
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: def.desc })
2062
+ /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: def.desc })
880
2063
  ] }) }),
881
- /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2064
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
882
2065
  "\u5206\u6570: ",
883
- /* @__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") })
884
2067
  ] }) }),
885
- /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2068
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
886
2069
  "\u751F\u547D: ",
887
- /* @__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)) })
888
2071
  ] }) }),
889
- /* @__PURE__ */ jsx5(Box5, { width: 10, children: /* @__PURE__ */ jsxs5(Text6, { children: [
2072
+ /* @__PURE__ */ jsx7(Box7, { width: 10, children: /* @__PURE__ */ jsxs7(Text8, { children: [
890
2073
  "\u7816\u5757: ",
891
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: aliveCount })
2074
+ /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: aliveCount })
892
2075
  ] }) }),
893
- /* @__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: [
894
2077
  "[",
895
2078
  s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
896
2079
  "]"
897
2080
  ] }) })
898
2081
  ] }),
899
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
900
- /* @__PURE__ */ jsxs5(Text6, { children: [
2082
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2083
+ /* @__PURE__ */ jsxs7(Text8, { children: [
901
2084
  "\u250C",
902
2085
  "\u2500".repeat(GAME_WIDTH),
903
2086
  "\u2510"
904
2087
  ] }),
905
- /* @__PURE__ */ jsx5(Text6, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
906
- /* @__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: [
907
2090
  "\u2514",
908
2091
  "\u2500".repeat(GAME_WIDTH),
909
2092
  "\u2518"
910
2093
  ] })
911
2094
  ] }),
912
- selectingLevel && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
913
- /* @__PURE__ */ jsx5(Text6, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
914
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx5(Box5, { width: 22, children: /* @__PURE__ */ jsxs5(Text6, { children: [
915
- /* @__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) }),
916
2099
  ". ",
917
2100
  lv.desc,
918
2101
  " (",
@@ -921,16 +2104,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
921
2104
  lv.cols,
922
2105
  ")"
923
2106
  ] }) }, i)) }),
924
- /* @__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" }) })
925
2108
  ] }),
926
- !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
927
- /* @__PURE__ */ jsx5(Text6, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
928
- /* @__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: [
929
2112
  " \u5206\u6570: ",
930
- /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: s.score })
2113
+ /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: s.score })
931
2114
  ] })
932
2115
  ] }),
933
- !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" }) })
934
2117
  ] });
935
2118
  }
936
2119
  var brick_breaker_default = {
@@ -940,7 +2123,7 @@ var brick_breaker_default = {
940
2123
  play: async () => {
941
2124
  await new Promise((resolve) => {
942
2125
  const { unmount } = render2(
943
- /* @__PURE__ */ jsx5(BrickBreakerGame, { onExit: () => {
2126
+ /* @__PURE__ */ jsx7(BrickBreakerGame, { onExit: () => {
944
2127
  unmount();
945
2128
  resolve();
946
2129
  } })
@@ -950,9 +2133,9 @@ var brick_breaker_default = {
950
2133
  };
951
2134
 
952
2135
  // src/game/coder-check/index.tsx
953
- import { Box as Box6, Text as Text7, useInput as useInput4, render as render3 } from "ink";
954
- import { useState as useState6, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback5 } from "react";
955
- 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";
956
2139
  var GAME_W = 66;
957
2140
  var GAME_H = 20;
958
2141
  var SCORE_H = 6;
@@ -1343,10 +2526,10 @@ function buildGameView(s, scoreLines, scoreColor, message) {
1343
2526
  return rows;
1344
2527
  }
1345
2528
  function CoderCheck({ onExit: _onExit }) {
1346
- const stateRef = useRef3(createInitialState2());
2529
+ const stateRef = useRef4(createInitialState2());
1347
2530
  const [tick, setTick] = useState6(0);
1348
2531
  const [colorOffset, setColorOffset] = useState6(0);
1349
- const onExitRef = useRef3(_onExit);
2532
+ const onExitRef = useRef4(_onExit);
1350
2533
  onExitRef.current = _onExit;
1351
2534
  useEffect5(() => {
1352
2535
  const timer = setInterval(() => {
@@ -1423,44 +2606,44 @@ function CoderCheck({ onExit: _onExit }) {
1423
2606
  const scoreLines = buildScoreLines(scoreStr);
1424
2607
  const view = buildGameView(s, scoreLines, scoreColor, s.message);
1425
2608
  void tick;
1426
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
1427
- /* @__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: [
1428
2611
  "\u751F\u547D ",
1429
- /* @__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)) }),
1430
2613
  " ",
1431
2614
  "\u901F\u5EA6 ",
1432
- /* @__PURE__ */ jsxs6(Text7, { color: "cyan", children: [
2615
+ /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
1433
2616
  "Lv.",
1434
2617
  Math.floor(s.speed * 10)
1435
2618
  ] })
1436
2619
  ] }) }),
1437
- !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: [
1438
2621
  "\u6253\u5B57: ",
1439
- /* @__PURE__ */ jsx6(Text7, { color: "green", children: s.typed }),
1440
- /* @__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) })
1441
2624
  ] }) }),
1442
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
1443
- /* @__PURE__ */ jsxs6(Text7, { children: [
2625
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
2626
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1444
2627
  "\u250C",
1445
2628
  "\u2500".repeat(GAME_W),
1446
2629
  "\u2510"
1447
2630
  ] }),
1448
- view.map((row, i) => /* @__PURE__ */ jsx6(Text7, { children: `\u2502${row}\u2502` }, i)),
1449
- /* @__PURE__ */ jsxs6(Text7, { children: [
2631
+ view.map((row, i) => /* @__PURE__ */ jsx8(Text9, { children: `\u2502${row}\u2502` }, i)),
2632
+ /* @__PURE__ */ jsxs8(Text9, { children: [
1450
2633
  "\u2514",
1451
2634
  "\u2500".repeat(GAME_W),
1452
2635
  "\u2518"
1453
2636
  ] })
1454
2637
  ] }),
1455
- s.gameOver && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1456
- /* @__PURE__ */ jsx6(Text7, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
1457
- /* @__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: [
1458
2641
  " \u5F97\u5206: ",
1459
- /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: s.score }),
2642
+ /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: s.score }),
1460
2643
  " r \u91CD\u5F00 q \u9000\u51FA"
1461
2644
  ] })
1462
2645
  ] }),
1463
- /* @__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" }) })
1464
2647
  ] });
1465
2648
  }
1466
2649
  var coder_check_default = {
@@ -1470,7 +2653,7 @@ var coder_check_default = {
1470
2653
  play: async () => {
1471
2654
  await new Promise((resolve) => {
1472
2655
  const { unmount } = render3(
1473
- /* @__PURE__ */ jsx6(CoderCheck, { onExit: () => {
2656
+ /* @__PURE__ */ jsx8(CoderCheck, { onExit: () => {
1474
2657
  unmount();
1475
2658
  resolve();
1476
2659
  } })
@@ -1491,10 +2674,10 @@ import { render as render4 } from "ink";
1491
2674
  import chalk3 from "chalk";
1492
2675
 
1493
2676
  // src/stock/StockList.tsx
1494
- 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";
1495
2678
  import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
1496
2679
  import asciichart from "asciichart";
1497
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2680
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1498
2681
  var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
1499
2682
  function normalizeApiCode(code) {
1500
2683
  if (code.startsWith("sh") || code.startsWith("sz")) return code;
@@ -1691,57 +2874,57 @@ function StockList({ codes, onExit, onBackToChat }) {
1691
2874
  );
1692
2875
  if (detailView) {
1693
2876
  if (detailLoading) {
1694
- 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..." }) });
1695
2878
  }
1696
2879
  return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
1697
2880
  }
1698
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1699
- /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1700
- /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
1701
- /* @__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: [
1702
2885
  " \u{1F550} ",
1703
2886
  currentTime
1704
2887
  ] }),
1705
- /* @__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` })
1706
2889
  ] }),
1707
- /* @__PURE__ */ jsxs7(Box7, { children: [
1708
- /* @__PURE__ */ jsx7(Box7, { width: 3 }),
1709
- /* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u4EE3\u7801" }) }),
1710
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u540D\u79F0" }) }),
1711
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
1712
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
1713
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
1714
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u9AD8" }) }),
1715
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u4F4E" }) }),
1716
- /* @__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" }) })
1717
2900
  ] }),
1718
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
1719
- /* @__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) => {
1720
2903
  const isSelected = index === selectedIndex;
1721
2904
  const isUp = stock.changePercent >= 0;
1722
2905
  const color = isUp ? "#ff1493" : "#00ff41";
1723
- return /* @__PURE__ */ jsxs7(Box7, { children: [
1724
- /* @__PURE__ */ jsx7(Box7, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx7(Text8, { children: " " }) }),
1725
- /* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
1726
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
1727
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color, children: formatPrice(stock.price) }) }),
1728
- /* @__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: [
1729
2912
  isUp ? "+" : "",
1730
2913
  stock.changePercent.toFixed(2),
1731
2914
  "%"
1732
2915
  ] }) }),
1733
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
2916
+ /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
1734
2917
  isUp ? "+" : "",
1735
2918
  stock.changeAmount.toFixed(3)
1736
2919
  ] }) }),
1737
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#cccccc", children: formatPrice(stock.high) }) }),
1738
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatPrice(stock.low) }) }),
1739
- /* @__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) }) })
1740
2923
  ] }, stock.code);
1741
2924
  }) }),
1742
- /* @__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` }) }),
1743
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
1744
- 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" }) })
1745
2928
  ] });
1746
2929
  }
1747
2930
  function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
@@ -1759,33 +2942,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1759
2942
  raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
1760
2943
  chartLines = raw.split("\n");
1761
2944
  }
1762
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
1763
- /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1764
- /* @__PURE__ */ jsxs7(Box7, { children: [
1765
- /* @__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: [
1766
2949
  " \u{1F4CA} ",
1767
2950
  stock.name,
1768
2951
  " "
1769
2952
  ] }),
1770
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code }),
1771
- currentTime && /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
2953
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: stock.code }),
2954
+ currentTime && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1772
2955
  " \u{1F550} ",
1773
2956
  currentTime
1774
2957
  ] })
1775
2958
  ] }),
1776
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
2959
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
1777
2960
  ] }),
1778
- /* @__PURE__ */ jsxs7(Box7, { children: [
1779
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
1780
- /* @__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: [
1781
2964
  arrow,
1782
2965
  " ",
1783
2966
  formatPrice(stock.price)
1784
2967
  ] }) })
1785
2968
  ] }),
1786
- /* @__PURE__ */ jsxs7(Box7, { children: [
1787
- /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
1788
- /* @__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: [
1789
2972
  isUp ? "+" : "",
1790
2973
  stock.changePercent.toFixed(2),
1791
2974
  "%",
@@ -1794,13 +2977,13 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1794
2977
  stock.changeAmount.toFixed(3)
1795
2978
  ] }) })
1796
2979
  ] }),
1797
- 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)) }),
1798
- /* @__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" }) })
1799
2982
  ] });
1800
2983
  }
1801
2984
 
1802
2985
  // src/cli/index.tsx
1803
- import { jsx as jsx8 } from "react/jsx-runtime";
2986
+ import { jsx as jsx10 } from "react/jsx-runtime";
1804
2987
  var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
1805
2988
  function createCli() {
1806
2989
  const program2 = new Command();
@@ -1826,34 +3009,46 @@ function createCli() {
1826
3009
  const result = await loadAndValidate();
1827
3010
  ctx = { ...ctx, config: result.config };
1828
3011
  }
1829
- startChat(ctx);
3012
+ const costTracker = new CostTracker({
3013
+ budgetLimit: ctx?.config.budgetLimit ?? 0,
3014
+ tokenBudgetLimit: ctx?.config.tokenBudgetLimit ?? 0
3015
+ });
3016
+ startChat(ctx, costTracker);
1830
3017
  });
1831
- function startChat(ctx) {
3018
+ function startChat(ctx, costTracker) {
3019
+ const defaultProvider = ctx?.config.providers.find(
3020
+ (p) => p.name === (ctx?.config.defaultProvider ?? "deepseek")
3021
+ );
3022
+ const model = defaultProvider?.model ?? "deepseek-v4-flash";
1832
3023
  const chatApp = renderApp(
1833
- /* @__PURE__ */ jsx8(
3024
+ /* @__PURE__ */ jsx10(
1834
3025
  ChatSession,
1835
3026
  {
1836
3027
  providerCount: ctx?.config.providers.length ?? 1,
1837
3028
  toolCount: ctx?.config.tools.length ?? 0,
1838
3029
  verbose: ctx?.verbose ?? false,
3030
+ apiKey: defaultProvider?.apiKey,
3031
+ baseUrl: defaultProvider?.baseUrl ?? "https://api.deepseek.com",
3032
+ costTracker,
3033
+ model,
1839
3034
  onLaunchGame: () => {
1840
3035
  chatApp.unmount();
1841
3036
  setImmediate(() => {
1842
3037
  initGames();
1843
3038
  const games = listGames();
1844
3039
  const { unmount } = render4(
1845
- /* @__PURE__ */ jsx8(
3040
+ /* @__PURE__ */ jsx10(
1846
3041
  GamePicker,
1847
3042
  {
1848
3043
  games,
1849
3044
  onSelect: async (game) => {
1850
3045
  unmount();
1851
3046
  await game.play();
1852
- startChat(ctx);
3047
+ startChat(ctx, costTracker);
1853
3048
  },
1854
3049
  onBackToChat: () => {
1855
3050
  unmount();
1856
- setImmediate(() => startChat(ctx));
3051
+ setImmediate(() => startChat(ctx, costTracker));
1857
3052
  }
1858
3053
  }
1859
3054
  ),
@@ -1866,13 +3061,13 @@ function createCli() {
1866
3061
  setImmediate(() => {
1867
3062
  const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1868
3063
  const stockApp = renderApp(
1869
- /* @__PURE__ */ jsx8(
3064
+ /* @__PURE__ */ jsx10(
1870
3065
  StockList,
1871
3066
  {
1872
3067
  codes: defaultStockCodes,
1873
3068
  onBackToChat: () => {
1874
3069
  stockApp.unmount();
1875
- setImmediate(() => startChat(ctx));
3070
+ setImmediate(() => startChat(ctx, costTracker));
1876
3071
  },
1877
3072
  onExit: () => process.exit(0)
1878
3073
  }
@@ -1930,10 +3125,10 @@ compdef _dskcode_completion dskcode`);
1930
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) {
1931
3126
  const ctx = this.dskcodeCtx;
1932
3127
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
1933
- const globalConfigPath = join2(home, ".dskcode", "settings.json");
3128
+ const globalConfigPath = join3(home, ".dskcode", "settings.json");
1934
3129
  let globalConfigHasStock = false;
1935
3130
  try {
1936
- const raw = await readFile2(globalConfigPath, "utf-8");
3131
+ const raw = await readFile3(globalConfigPath, "utf-8");
1937
3132
  const parsed = JSON.parse(raw);
1938
3133
  const stock = parsed.stock;
1939
3134
  globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
@@ -1953,7 +3148,7 @@ compdef _dskcode_completion dskcode`);
1953
3148
  const freshResult = await loadAndValidate();
1954
3149
  const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1955
3150
  const app = renderApp(
1956
- /* @__PURE__ */ jsx8(
3151
+ /* @__PURE__ */ jsx10(
1957
3152
  StockList,
1958
3153
  {
1959
3154
  codes: codeList,
@@ -1982,7 +3177,7 @@ compdef _dskcode_completion dskcode`);
1982
3177
  }
1983
3178
  const selectedGame = await new Promise((resolve) => {
1984
3179
  const { unmount } = render4(
1985
- /* @__PURE__ */ jsx8(
3180
+ /* @__PURE__ */ jsx10(
1986
3181
  GamePicker,
1987
3182
  {
1988
3183
  games,