deepagentsdk 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env bun
2
- import { St as init_limits, _t as DEFAULT_EVICTION_TOKEN_LIMIT, bt as DEFAULT_SUMMARIZATION_THRESHOLD, gt as CONTEXT_WINDOW, l as estimateMessagesTokens, n as parseModelString, o as createDeepAgent, r as LocalSandbox, t as FileSaver, vt as DEFAULT_KEEP_MESSAGES } from "../file-saver-CQWTIr8z.mjs";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { St as DEFAULT_SUMMARIZATION_THRESHOLD, a as LocalSandbox, bt as DEFAULT_KEEP_MESSAGES, c as createDeepAgent, d as estimateMessagesTokens, i as setProvidersConfig, n as getProvidersConfig, r as parseModelString, t as FileSaver, vt as CONTEXT_WINDOW, wt as init_limits, yt as DEFAULT_EVICTION_TOKEN_LIMIT } from "../file-saver-DoXRgKBv.mjs";
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { Box, Text, render, useApp, useInput } from "ink";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Badge, Spinner, StatusMessage } from "@inkjs/ui";
7
7
 
8
8
  //#region src/cli/hooks/useAgent.ts
9
- init_limits();
10
9
  /**
11
10
  * Hook for managing agent streaming and events.
12
11
  */
12
+ init_limits();
13
13
  let eventCounter = 0;
14
14
  function createEventId() {
15
15
  return `event-${++eventCounter}`;
@@ -533,8 +533,8 @@ function useAgent(options) {
533
533
  setSummarizationEnabled(enabled);
534
534
  const newConfig = enabled ? {
535
535
  enabled: true,
536
- tokenThreshold: options.summarization?.tokenThreshold,
537
- keepMessages: options.summarization?.keepMessages
536
+ tokenThreshold: options.summarization?.tokenThreshold ?? DEFAULT_SUMMARIZATION_THRESHOLD,
537
+ keepMessages: options.summarization?.keepMessages ?? DEFAULT_KEEP_MESSAGES
538
538
  } : void 0;
539
539
  setSummarizationConfig(newConfig);
540
540
  recreateAgent({ summarization: newConfig });
@@ -656,6 +656,11 @@ const SLASH_COMMANDS = [
656
656
  aliases: ["/key", "/api"],
657
657
  description: "Manage API keys (interactive)"
658
658
  },
659
+ {
660
+ command: "/baseurl",
661
+ aliases: ["/base-url"],
662
+ description: "Configure custom base URLs for providers (interactive)"
663
+ },
659
664
  {
660
665
  command: "/model",
661
666
  aliases: [],
@@ -1563,7 +1568,7 @@ function isCacheValid(provider) {
1563
1568
  const entry = modelCache[provider];
1564
1569
  if (!entry) return false;
1565
1570
  if (Date.now() - entry.timestamp > CACHE_TTL_MS) return false;
1566
- const currentKeyHash = provider === "anthropic" ? hashApiKey(process.env.ANTHROPIC_API_KEY) : hashApiKey(process.env.OPENAI_API_KEY);
1571
+ const currentKeyHash = provider === "anthropic" ? hashApiKey(process.env.ANTHROPIC_API_KEY) : provider === "openai" ? hashApiKey(process.env.OPENAI_API_KEY) : hashApiKey(process.env.ZHIPU_API_KEY);
1567
1572
  return entry.apiKeyHash === currentKeyHash;
1568
1573
  }
1569
1574
  /**
@@ -1577,7 +1582,7 @@ function getCachedModels(provider) {
1577
1582
  * Set cached models for a provider.
1578
1583
  */
1579
1584
  function setCachedModels(provider, models) {
1580
- const apiKeyHash = provider === "anthropic" ? hashApiKey(process.env.ANTHROPIC_API_KEY) : hashApiKey(process.env.OPENAI_API_KEY);
1585
+ const apiKeyHash = provider === "anthropic" ? hashApiKey(process.env.ANTHROPIC_API_KEY) : provider === "openai" ? hashApiKey(process.env.OPENAI_API_KEY) : hashApiKey(process.env.ZHIPU_API_KEY);
1581
1586
  modelCache[provider] = {
1582
1587
  models,
1583
1588
  timestamp: Date.now(),
@@ -1590,7 +1595,8 @@ function setCachedModels(provider, models) {
1590
1595
  function detectAvailableProviders() {
1591
1596
  return {
1592
1597
  anthropic: !!process.env.ANTHROPIC_API_KEY,
1593
- openai: !!process.env.OPENAI_API_KEY
1598
+ openai: !!process.env.OPENAI_API_KEY,
1599
+ zhipu: !!process.env.ZHIPU_API_KEY
1594
1600
  };
1595
1601
  }
1596
1602
  /**
@@ -1706,6 +1712,34 @@ function getOpenAIModelDescription(modelId) {
1706
1712
  return "";
1707
1713
  }
1708
1714
  /**
1715
+ * Known Zhipu GLM models with descriptions.
1716
+ * Zhipu doesn't have a public models list API, so we use a static list.
1717
+ */
1718
+ const ZHIPU_MODELS = [{
1719
+ id: "glm-4.7",
1720
+ description: "Most capable GLM models"
1721
+ }];
1722
+ /**
1723
+ * Get available Zhipu models.
1724
+ * Returns a static list of known models since Zhipu doesn't have a models list API.
1725
+ */
1726
+ async function fetchZhipuModels() {
1727
+ if (!process.env.ZHIPU_API_KEY) return {
1728
+ models: [],
1729
+ error: "No Zhipu API key configured"
1730
+ };
1731
+ const cached = getCachedModels("zhipu");
1732
+ if (cached) return { models: cached };
1733
+ const models = ZHIPU_MODELS.map((model) => ({
1734
+ id: `zhipu/${model.id}`,
1735
+ name: model.id,
1736
+ provider: "zhipu",
1737
+ description: model.description
1738
+ }));
1739
+ setCachedModels("zhipu", models);
1740
+ return { models };
1741
+ }
1742
+ /**
1709
1743
  * Get list of available models from all configured providers.
1710
1744
  * Only fetches from providers that have API keys configured.
1711
1745
  */
@@ -1728,6 +1762,13 @@ async function getAvailableModels() {
1728
1762
  error: result.error
1729
1763
  });
1730
1764
  }));
1765
+ if (providers.zhipu) promises.push(fetchZhipuModels().then((result) => {
1766
+ allModels.push(...result.models);
1767
+ if (result.error) errors.push({
1768
+ provider: "Zhipu",
1769
+ error: result.error
1770
+ });
1771
+ }));
1731
1772
  await Promise.all(promises);
1732
1773
  return {
1733
1774
  models: allModels,
@@ -1751,48 +1792,146 @@ async function getModelsByProvider() {
1751
1792
  //#endregion
1752
1793
  //#region src/cli/components/ModelSelection.tsx
1753
1794
  /**
1754
- * Model Selection Panel - Interactive model selection with arrow keys.
1795
+ * Model Selection Panel - Two-step interactive model selection.
1796
+ * Step 1: Select provider (Anthropic, OpenAI, Zhipu)
1797
+ * Step 2: Select model from that provider
1755
1798
  */
1756
1799
  function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1757
- const providers = detectAvailableProviders();
1758
- const hasAnyKey = providers.anthropic || providers.openai;
1800
+ const detectedProviders = detectAvailableProviders();
1801
+ const hasAnyKey = detectedProviders.anthropic || detectedProviders.openai || detectedProviders.zhipu;
1759
1802
  const [state, setState] = useState({
1760
1803
  loading: true,
1761
1804
  anthropicModels: [],
1762
1805
  openaiModels: [],
1806
+ zhipuModels: [],
1763
1807
  errors: []
1764
1808
  });
1765
- const [selectedIndex, setSelectedIndex] = useState(0);
1766
- const allModels = useMemo(() => {
1767
- const models = [];
1768
- if (state.anthropicModels.length > 0) models.push(...state.anthropicModels);
1769
- if (state.openaiModels.length > 0) models.push(...state.openaiModels);
1770
- return models;
1771
- }, [state.anthropicModels, state.openaiModels]);
1809
+ const [step, setStep] = useState("select-provider");
1810
+ const [selectedProviderIndex, setSelectedProviderIndex] = useState(0);
1811
+ const [selectedProvider, setSelectedProvider] = useState(null);
1812
+ const [selectedModelIndex, setSelectedModelIndex] = useState(0);
1813
+ const availableProviders = useMemo(() => {
1814
+ const providers = [];
1815
+ if (detectedProviders.anthropic) providers.push({
1816
+ key: "anthropic",
1817
+ name: "Anthropic Claude",
1818
+ description: "Claude models",
1819
+ hasKey: true,
1820
+ modelCount: state.anthropicModels.length
1821
+ });
1822
+ if (detectedProviders.openai) providers.push({
1823
+ key: "openai",
1824
+ name: "OpenAI GPT",
1825
+ description: "GPT models",
1826
+ hasKey: true,
1827
+ modelCount: state.openaiModels.length
1828
+ });
1829
+ if (detectedProviders.zhipu) providers.push({
1830
+ key: "zhipu",
1831
+ name: "Zhipu GLM",
1832
+ description: "GLM models",
1833
+ hasKey: true,
1834
+ modelCount: state.zhipuModels.length
1835
+ });
1836
+ return providers;
1837
+ }, [
1838
+ detectedProviders,
1839
+ state.anthropicModels.length,
1840
+ state.openaiModels.length,
1841
+ state.zhipuModels.length
1842
+ ]);
1843
+ const currentProviderModels = useMemo(() => {
1844
+ if (!selectedProvider) return [];
1845
+ switch (selectedProvider) {
1846
+ case "anthropic": return state.anthropicModels;
1847
+ case "openai": return state.openaiModels;
1848
+ case "zhipu": return state.zhipuModels;
1849
+ default: return [];
1850
+ }
1851
+ }, [
1852
+ selectedProvider,
1853
+ state.anthropicModels,
1854
+ state.openaiModels,
1855
+ state.zhipuModels
1856
+ ]);
1857
+ const hasInitializedProviderIndex = React.useRef(false);
1772
1858
  useEffect(() => {
1773
- if (allModels.length > 0 && currentModel) {
1774
- const currentIndex = allModels.findIndex((m) => isCurrentModel(currentModel, m));
1775
- if (currentIndex >= 0) setSelectedIndex(currentIndex);
1859
+ if (!hasInitializedProviderIndex.current && currentModel && availableProviders.length > 0) {
1860
+ const providerFromModel = currentModel.split("/")[0];
1861
+ const providerIndex = availableProviders.findIndex((p) => p.key === providerFromModel);
1862
+ if (providerIndex >= 0) {
1863
+ setSelectedProviderIndex(providerIndex);
1864
+ hasInitializedProviderIndex.current = true;
1865
+ }
1866
+ }
1867
+ }, [currentModel, availableProviders]);
1868
+ const prevStepRef = React.useRef(null);
1869
+ useEffect(() => {
1870
+ if (step === "select-model" && prevStepRef.current !== "select-model" && currentProviderModels.length > 0 && currentModel) {
1871
+ const currentIndex = currentProviderModels.findIndex((m) => isCurrentModel(currentModel, m));
1872
+ if (currentIndex >= 0) setSelectedModelIndex(currentIndex);
1873
+ else setSelectedModelIndex(0);
1776
1874
  }
1777
- }, [allModels, currentModel]);
1875
+ prevStepRef.current = step;
1876
+ }, [
1877
+ step,
1878
+ currentProviderModels,
1879
+ currentModel
1880
+ ]);
1881
+ const stateRef = React.useRef(state);
1882
+ const stepRef = React.useRef(step);
1883
+ const availableProvidersRef = React.useRef(availableProviders);
1884
+ const selectedProviderIndexRef = React.useRef(selectedProviderIndex);
1885
+ const currentProviderModelsRef = React.useRef(currentProviderModels);
1886
+ const selectedModelIndexRef = React.useRef(selectedModelIndex);
1887
+ React.useEffect(() => {
1888
+ stateRef.current = state;
1889
+ stepRef.current = step;
1890
+ availableProvidersRef.current = availableProviders;
1891
+ selectedProviderIndexRef.current = selectedProviderIndex;
1892
+ currentProviderModelsRef.current = currentProviderModels;
1893
+ selectedModelIndexRef.current = selectedModelIndex;
1894
+ });
1778
1895
  useInput((input, key) => {
1779
- if (state.loading) return;
1780
- if (key.upArrow) setSelectedIndex((prev) => prev > 0 ? prev - 1 : allModels.length - 1);
1781
- else if (key.downArrow) setSelectedIndex((prev) => prev < allModels.length - 1 ? prev + 1 : 0);
1782
- else if (key.return) {
1783
- const selectedModel = allModels[selectedIndex];
1784
- if (selectedModel) {
1785
- onModelSelect?.(selectedModel.id);
1786
- onClose?.();
1896
+ if (stateRef.current.loading) return;
1897
+ const currentStep = stepRef.current;
1898
+ const providers = availableProvidersRef.current;
1899
+ const providerIndex = selectedProviderIndexRef.current;
1900
+ const models = currentProviderModelsRef.current;
1901
+ const modelIndex = selectedModelIndexRef.current;
1902
+ if (currentStep === "select-provider") {
1903
+ if (key.upArrow) setSelectedProviderIndex((prev) => prev > 0 ? prev - 1 : providers.length - 1);
1904
+ else if (key.downArrow) setSelectedProviderIndex((prev) => prev < providers.length - 1 ? prev + 1 : 0);
1905
+ else if (key.return) {
1906
+ const provider = providers[providerIndex];
1907
+ if (provider && provider.modelCount > 0) {
1908
+ setSelectedProvider(provider.key);
1909
+ setSelectedModelIndex(0);
1910
+ setStep("select-model");
1911
+ }
1912
+ } else if (key.escape) onClose?.();
1913
+ } else if (currentStep === "select-model") {
1914
+ if (key.upArrow) setSelectedModelIndex((prev) => prev > 0 ? prev - 1 : models.length - 1);
1915
+ else if (key.downArrow) setSelectedModelIndex((prev) => prev < models.length - 1 ? prev + 1 : 0);
1916
+ else if (key.return) {
1917
+ const selectedModel = models[modelIndex];
1918
+ if (selectedModel) {
1919
+ onModelSelect?.(selectedModel.id);
1920
+ onClose?.();
1921
+ }
1922
+ } else if (key.escape || key.leftArrow) {
1923
+ setStep("select-provider");
1924
+ setSelectedProvider(null);
1787
1925
  }
1788
- } else if (key.escape) onClose?.();
1789
- });
1926
+ }
1927
+ }, { isActive: !state.loading });
1790
1928
  useEffect(() => {
1791
1929
  if (!hasAnyKey) {
1792
1930
  setState({
1793
1931
  loading: false,
1794
1932
  anthropicModels: [],
1795
1933
  openaiModels: [],
1934
+ zhipuModels: [],
1796
1935
  errors: []
1797
1936
  });
1798
1937
  return;
@@ -1805,6 +1944,7 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1805
1944
  loading: false,
1806
1945
  anthropicModels: result.anthropic || [],
1807
1946
  openaiModels: result.openai || [],
1947
+ zhipuModels: result.zhipu || [],
1808
1948
  errors: result.errors
1809
1949
  });
1810
1950
  } catch (error) {
@@ -1812,6 +1952,7 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1812
1952
  loading: false,
1813
1953
  anthropicModels: [],
1814
1954
  openaiModels: [],
1955
+ zhipuModels: [],
1815
1956
  errors: [{
1816
1957
  provider: "Unknown",
1817
1958
  error: String(error)
@@ -1856,6 +1997,10 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1856
1997
  /* @__PURE__ */ jsx(Text, {
1857
1998
  dimColor: true,
1858
1999
  children: " • OpenAI (GPT)"
2000
+ }),
2001
+ /* @__PURE__ */ jsx(Text, {
2002
+ dimColor: true,
2003
+ children: " • Zhipu (GLM)"
1859
2004
  })
1860
2005
  ]
1861
2006
  });
@@ -1876,7 +2021,7 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1876
2021
  /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Spinner, { label: "Fetching models from API..." }) })
1877
2022
  ]
1878
2023
  });
1879
- if (!(allModels.length > 0) && state.errors.length > 0) return /* @__PURE__ */ jsxs(Box, {
2024
+ if (availableProviders.length === 0 || availableProviders.every((p) => p.modelCount === 0)) return /* @__PURE__ */ jsxs(Box, {
1880
2025
  flexDirection: "column",
1881
2026
  borderStyle: "single",
1882
2027
  borderColor: colors.error,
@@ -1887,7 +2032,7 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1887
2032
  /* @__PURE__ */ jsxs(Text, {
1888
2033
  bold: true,
1889
2034
  color: colors.error,
1890
- children: [emoji.error, " Failed to Fetch Models"]
2035
+ children: [emoji.error, " No Models Available"]
1891
2036
  }),
1892
2037
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1893
2038
  state.errors.map((err, i) => /* @__PURE__ */ jsxs(Text, {
@@ -1905,9 +2050,7 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1905
2050
  })
1906
2051
  ]
1907
2052
  });
1908
- let anthropicOffset = 0;
1909
- let openaiOffset = state.anthropicModels.length;
1910
- return /* @__PURE__ */ jsxs(Box, {
2053
+ if (step === "select-provider") return /* @__PURE__ */ jsxs(Box, {
1911
2054
  flexDirection: "column",
1912
2055
  borderStyle: "single",
1913
2056
  borderColor: colors.primary,
@@ -1918,7 +2061,7 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1918
2061
  /* @__PURE__ */ jsxs(Text, {
1919
2062
  bold: true,
1920
2063
  color: colors.info,
1921
- children: [emoji.model, " Select Model"]
2064
+ children: [emoji.model, " Select Provider"]
1922
2065
  }),
1923
2066
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1924
2067
  state.errors.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [state.errors.map((err, i) => /* @__PURE__ */ jsxs(Text, {
@@ -1931,42 +2074,53 @@ function ModelSelectionPanel({ currentModel, onModelSelect, onClose }) {
1931
2074
  err.error
1932
2075
  ]
1933
2076
  }, i)), /* @__PURE__ */ jsx(Box, { height: 1 })] }),
1934
- state.anthropicModels.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1935
- /* @__PURE__ */ jsx(Text, {
1936
- bold: true,
1937
- color: colors.primary,
1938
- children: "Anthropic Claude"
1939
- }),
1940
- state.anthropicModels.map((model, index) => {
1941
- return /* @__PURE__ */ jsx(ModelItem, {
1942
- model,
1943
- isSelected: anthropicOffset + index === selectedIndex,
1944
- isCurrent: isCurrentModel(currentModel, model)
1945
- }, model.id);
1946
- }),
1947
- /* @__PURE__ */ jsx(Box, { height: 1 })
1948
- ] }),
1949
- state.openaiModels.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1950
- /* @__PURE__ */ jsx(Text, {
1951
- bold: true,
1952
- color: colors.primary,
1953
- children: "OpenAI GPT"
1954
- }),
1955
- state.openaiModels.map((model, index) => {
1956
- return /* @__PURE__ */ jsx(ModelItem, {
1957
- model,
1958
- isSelected: openaiOffset + index === selectedIndex,
1959
- isCurrent: isCurrentModel(currentModel, model)
1960
- }, model.id);
1961
- }),
1962
- /* @__PURE__ */ jsx(Box, { height: 1 })
1963
- ] }),
2077
+ availableProviders.map((provider, index) => {
2078
+ return /* @__PURE__ */ jsx(ProviderItem, {
2079
+ provider,
2080
+ isSelected: index === selectedProviderIndex,
2081
+ isCurrent: currentModel?.startsWith(`${provider.key}/`) ?? false
2082
+ }, provider.key);
2083
+ }),
2084
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1964
2085
  /* @__PURE__ */ jsx(Text, {
1965
2086
  dimColor: true,
1966
2087
  children: "↑/↓ Navigate • Enter Select • Esc Cancel"
1967
2088
  })
1968
2089
  ]
1969
2090
  });
2091
+ const selectedProviderInfo = availableProviders.find((p) => p.key === selectedProvider);
2092
+ return /* @__PURE__ */ jsxs(Box, {
2093
+ flexDirection: "column",
2094
+ borderStyle: "single",
2095
+ borderColor: colors.primary,
2096
+ paddingX: 2,
2097
+ paddingY: 1,
2098
+ marginY: 1,
2099
+ children: [
2100
+ /* @__PURE__ */ jsxs(Text, {
2101
+ bold: true,
2102
+ color: colors.info,
2103
+ children: [
2104
+ emoji.model,
2105
+ " Select Model - ",
2106
+ selectedProviderInfo?.name
2107
+ ]
2108
+ }),
2109
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2110
+ currentProviderModels.map((model, index) => {
2111
+ return /* @__PURE__ */ jsx(ModelItem, {
2112
+ model,
2113
+ isSelected: index === selectedModelIndex,
2114
+ isCurrent: isCurrentModel(currentModel, model)
2115
+ }, model.id);
2116
+ }),
2117
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2118
+ /* @__PURE__ */ jsx(Text, {
2119
+ dimColor: true,
2120
+ children: "↑/↓ Navigate • Enter Select • ←/Esc Back"
2121
+ })
2122
+ ]
2123
+ });
1970
2124
  }
1971
2125
  /**
1972
2126
  * Check if a model matches the current model.
@@ -1975,6 +2129,50 @@ function isCurrentModel(currentModel, model) {
1975
2129
  if (!currentModel) return false;
1976
2130
  return currentModel === model.id || currentModel === model.name || currentModel === `${model.provider}/${model.name}` || currentModel.startsWith(`${model.provider}/`) && currentModel === model.id;
1977
2131
  }
2132
+ function ProviderItem({ provider, isSelected, isCurrent }) {
2133
+ let indicator = " ";
2134
+ let textColor = void 0;
2135
+ let isBold = false;
2136
+ if (isSelected) {
2137
+ indicator = "▸ ";
2138
+ textColor = colors.primary;
2139
+ isBold = true;
2140
+ }
2141
+ if (isCurrent) {
2142
+ indicator = isSelected ? "▸✓" : " ✓";
2143
+ textColor = isSelected ? colors.primary : colors.success;
2144
+ }
2145
+ const hasModels = provider.modelCount > 0;
2146
+ return /* @__PURE__ */ jsxs(Box, {
2147
+ marginLeft: 1,
2148
+ children: [
2149
+ /* @__PURE__ */ jsx(Text, {
2150
+ color: isSelected ? colors.primary : isCurrent ? colors.success : void 0,
2151
+ children: indicator
2152
+ }),
2153
+ /* @__PURE__ */ jsx(Text, {
2154
+ color: hasModels ? textColor : colors.muted,
2155
+ bold: isBold,
2156
+ children: provider.name
2157
+ }),
2158
+ /* @__PURE__ */ jsxs(Text, {
2159
+ dimColor: true,
2160
+ children: [
2161
+ " ",
2162
+ "(",
2163
+ provider.modelCount,
2164
+ " model",
2165
+ provider.modelCount !== 1 ? "s" : "",
2166
+ ")"
2167
+ ]
2168
+ }),
2169
+ !hasModels && /* @__PURE__ */ jsx(Text, {
2170
+ color: colors.warning,
2171
+ children: " - loading..."
2172
+ })
2173
+ ]
2174
+ });
2175
+ }
1978
2176
  function ModelItem({ model, isSelected, isCurrent }) {
1979
2177
  let indicator = " ";
1980
2178
  let textColor = void 0;
@@ -1988,6 +2186,7 @@ function ModelItem({ model, isSelected, isCurrent }) {
1988
2186
  indicator = isSelected ? "▸✓" : " ✓";
1989
2187
  textColor = isSelected ? colors.primary : colors.success;
1990
2188
  }
2189
+ const displayName = model.name;
1991
2190
  return /* @__PURE__ */ jsxs(Box, {
1992
2191
  marginLeft: 1,
1993
2192
  children: [
@@ -1998,7 +2197,7 @@ function ModelItem({ model, isSelected, isCurrent }) {
1998
2197
  /* @__PURE__ */ jsx(Text, {
1999
2198
  color: textColor,
2000
2199
  bold: isBold,
2001
- children: model.id
2200
+ children: displayName
2002
2201
  }),
2003
2202
  model.description && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
2004
2203
  dimColor: true,
@@ -2024,6 +2223,7 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2024
2223
  const [error, setError] = useState(null);
2025
2224
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
2026
2225
  const openaiKey = process.env.OPENAI_API_KEY;
2226
+ const zhipuKey = process.env.ZHIPU_API_KEY;
2027
2227
  const maskKey = (key) => {
2028
2228
  if (!key) return null;
2029
2229
  if (key.length <= 8) return "•".repeat(key.length);
@@ -2041,6 +2241,11 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2041
2241
  setStep("enter-key");
2042
2242
  setError(null);
2043
2243
  if (openaiKey) setApiKey(openaiKey);
2244
+ } else if (input === "3" || input.toLowerCase() === "z") {
2245
+ setSelectedProvider("zhipu");
2246
+ setStep("enter-key");
2247
+ setError(null);
2248
+ if (zhipuKey) setApiKey(zhipuKey);
2044
2249
  } else if (key.escape) onClose?.();
2045
2250
  } else if (step === "enter-key") {
2046
2251
  if (key.escape) {
@@ -2053,16 +2258,9 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2053
2258
  setError("API key cannot be empty");
2054
2259
  return;
2055
2260
  }
2056
- if (selectedProvider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
2057
- setError("Anthropic API keys typically start with 'sk-ant-'");
2058
- return;
2059
- }
2060
- if (selectedProvider === "openai" && !apiKey.startsWith("sk-")) {
2061
- setError("OpenAI API keys typically start with 'sk-'");
2062
- return;
2063
- }
2064
2261
  if (selectedProvider === "anthropic") process.env.ANTHROPIC_API_KEY = apiKey.trim();
2065
2262
  else if (selectedProvider === "openai") process.env.OPENAI_API_KEY = apiKey.trim();
2263
+ else if (selectedProvider === "zhipu") process.env.ZHIPU_API_KEY = apiKey.trim();
2066
2264
  setStep("success");
2067
2265
  onKeySaved?.(selectedProvider, apiKey.trim());
2068
2266
  setTimeout(() => {
@@ -2156,6 +2354,30 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2156
2354
  })
2157
2355
  ] })
2158
2356
  }),
2357
+ /* @__PURE__ */ jsx(Box, {
2358
+ marginLeft: 2,
2359
+ children: zhipuKey ? /* @__PURE__ */ jsxs(Fragment, { children: [
2360
+ /* @__PURE__ */ jsx(Text, {
2361
+ color: colors.success,
2362
+ children: "✓ "
2363
+ }),
2364
+ /* @__PURE__ */ jsx(Text, { children: "Zhipu: " }),
2365
+ /* @__PURE__ */ jsx(Text, {
2366
+ dimColor: true,
2367
+ children: maskKey(zhipuKey)
2368
+ })
2369
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2370
+ /* @__PURE__ */ jsx(Text, {
2371
+ color: colors.warning,
2372
+ children: "✗ "
2373
+ }),
2374
+ /* @__PURE__ */ jsx(Text, { children: "Zhipu: " }),
2375
+ /* @__PURE__ */ jsx(Text, {
2376
+ dimColor: true,
2377
+ children: "not set"
2378
+ })
2379
+ ] })
2380
+ }),
2159
2381
  /* @__PURE__ */ jsx(Box, { height: 1 }),
2160
2382
  step === "select-provider" && /* @__PURE__ */ jsxs(Fragment, { children: [
2161
2383
  /* @__PURE__ */ jsx(Text, {
@@ -2191,10 +2413,24 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2191
2413
  })
2192
2414
  ]
2193
2415
  }),
2416
+ /* @__PURE__ */ jsxs(Box, {
2417
+ marginLeft: 2,
2418
+ children: [
2419
+ /* @__PURE__ */ jsx(Text, {
2420
+ color: colors.primary,
2421
+ children: "[3]"
2422
+ }),
2423
+ /* @__PURE__ */ jsx(Text, { children: " Zhipu (GLM)" }),
2424
+ zhipuKey && /* @__PURE__ */ jsx(Text, {
2425
+ dimColor: true,
2426
+ children: " (overwrite)"
2427
+ })
2428
+ ]
2429
+ }),
2194
2430
  /* @__PURE__ */ jsx(Box, { height: 1 }),
2195
2431
  /* @__PURE__ */ jsx(Text, {
2196
2432
  dimColor: true,
2197
- children: "Press 1 or 2 to select, Esc to close"
2433
+ children: "Press 1, 2, or 3 to select, Esc to close"
2198
2434
  })
2199
2435
  ] }),
2200
2436
  step === "enter-key" && selectedProvider && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -2203,10 +2439,10 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2203
2439
  " ",
2204
2440
  /* @__PURE__ */ jsx(Text, {
2205
2441
  color: colors.primary,
2206
- children: selectedProvider === "anthropic" ? "Anthropic" : "OpenAI"
2442
+ children: selectedProvider === "anthropic" ? "Anthropic" : selectedProvider === "openai" ? "OpenAI" : "Zhipu"
2207
2443
  }),
2208
2444
  " ",
2209
- "API key:",
2445
+ "API key",
2210
2446
  selectedProvider === "anthropic" && anthropicKey && /* @__PURE__ */ jsxs(Text, {
2211
2447
  dimColor: true,
2212
2448
  children: [
@@ -2222,7 +2458,16 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2222
2458
  maskKey(openaiKey),
2223
2459
  ")"
2224
2460
  ]
2225
- })
2461
+ }),
2462
+ selectedProvider === "zhipu" && zhipuKey && /* @__PURE__ */ jsxs(Text, {
2463
+ dimColor: true,
2464
+ children: [
2465
+ " (current: ",
2466
+ maskKey(zhipuKey),
2467
+ ")"
2468
+ ]
2469
+ }),
2470
+ ":"
2226
2471
  ] }),
2227
2472
  /* @__PURE__ */ jsx(Box, { height: 1 }),
2228
2473
  /* @__PURE__ */ jsxs(Box, { children: [
@@ -2260,7 +2505,270 @@ function ApiKeyInputPanel({ onKeySaved, onClose }) {
2260
2505
  emoji.completed,
2261
2506
  " API key saved for",
2262
2507
  " ",
2263
- selectedProvider === "anthropic" ? "Anthropic" : "OpenAI",
2508
+ selectedProvider === "anthropic" ? "Anthropic" : selectedProvider === "openai" ? "OpenAI" : "Zhipu",
2509
+ "!"
2510
+ ]
2511
+ }),
2512
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2513
+ /* @__PURE__ */ jsx(Text, {
2514
+ dimColor: true,
2515
+ children: "Press Enter or Esc to return to menu"
2516
+ })
2517
+ ] })
2518
+ ]
2519
+ });
2520
+ }
2521
+
2522
+ //#endregion
2523
+ //#region src/cli/components/BaseURLInput.tsx
2524
+ /**
2525
+ * BaseURLInput Component
2526
+ *
2527
+ * Interactive component for setting custom base URLs for AI providers.
2528
+ * Accessed via the /baseurl slash command in the CLI.
2529
+ */
2530
+ function BaseURLInput({ onSubmit, onCancel }) {
2531
+ const [step, setStep] = useState("select-provider");
2532
+ const [selectedProvider, setSelectedProvider] = useState(null);
2533
+ const [baseURL, setBaseURL] = useState("");
2534
+ const [error, setError] = useState(null);
2535
+ const anthropicBaseURL = process.env.ANTHROPIC_BASE_URL;
2536
+ const openaiBaseURL = process.env.OPENAI_BASE_URL;
2537
+ const zhipuBaseURL = process.env.ZHIPU_BASE_URL;
2538
+ useInput((input, key) => {
2539
+ if (step === "select-provider") {
2540
+ if (input === "1" || input.toLowerCase() === "a") {
2541
+ setSelectedProvider("anthropic");
2542
+ setStep("enter-url");
2543
+ setError(null);
2544
+ if (anthropicBaseURL) setBaseURL(anthropicBaseURL);
2545
+ else setBaseURL("");
2546
+ } else if (input === "2" || input.toLowerCase() === "o") {
2547
+ setSelectedProvider("openai");
2548
+ setStep("enter-url");
2549
+ setError(null);
2550
+ if (openaiBaseURL) setBaseURL(openaiBaseURL);
2551
+ else setBaseURL("");
2552
+ } else if (input === "3" || input.toLowerCase() === "z") {
2553
+ setSelectedProvider("zhipu");
2554
+ setStep("enter-url");
2555
+ setError(null);
2556
+ if (zhipuBaseURL) setBaseURL(zhipuBaseURL);
2557
+ else setBaseURL("");
2558
+ } else if (key.escape) onCancel();
2559
+ } else if (step === "enter-url") {
2560
+ if (key.escape) {
2561
+ setStep("select-provider");
2562
+ setBaseURL("");
2563
+ setSelectedProvider(null);
2564
+ setError(null);
2565
+ } else if (key.return) {
2566
+ if (!baseURL.trim()) {
2567
+ setError("Base URL cannot be empty");
2568
+ return;
2569
+ }
2570
+ try {
2571
+ new URL(baseURL.trim());
2572
+ } catch (err) {
2573
+ setError("Invalid URL format. Example: https://api.anthropic.com/v1");
2574
+ return;
2575
+ }
2576
+ setStep("success");
2577
+ onSubmit(selectedProvider, baseURL.trim());
2578
+ setTimeout(() => {
2579
+ setStep("select-provider");
2580
+ setBaseURL("");
2581
+ setSelectedProvider(null);
2582
+ setError(null);
2583
+ }, 1500);
2584
+ } else if (key.backspace || key.delete) {
2585
+ setBaseURL((prev) => prev.slice(0, -1));
2586
+ setError(null);
2587
+ } else if (input && !key.ctrl && !key.meta) {
2588
+ setBaseURL((prev) => prev + input);
2589
+ setError(null);
2590
+ }
2591
+ } else if (step === "success") {
2592
+ if (key.return || key.escape) {
2593
+ setStep("select-provider");
2594
+ setBaseURL("");
2595
+ setSelectedProvider(null);
2596
+ setError(null);
2597
+ }
2598
+ }
2599
+ });
2600
+ return /* @__PURE__ */ jsxs(Box, {
2601
+ flexDirection: "column",
2602
+ borderStyle: "single",
2603
+ borderColor: colors.primary,
2604
+ paddingX: 2,
2605
+ paddingY: 1,
2606
+ marginY: 1,
2607
+ children: [
2608
+ /* @__PURE__ */ jsx(Text, {
2609
+ bold: true,
2610
+ color: colors.info,
2611
+ children: "🌐 Base URL Configuration"
2612
+ }),
2613
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2614
+ /* @__PURE__ */ jsx(Text, {
2615
+ bold: true,
2616
+ children: "Current Base URLs:"
2617
+ }),
2618
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2619
+ /* @__PURE__ */ jsx(Box, {
2620
+ marginLeft: 2,
2621
+ children: anthropicBaseURL ? /* @__PURE__ */ jsxs(Fragment, { children: [
2622
+ /* @__PURE__ */ jsx(Text, {
2623
+ color: colors.success,
2624
+ children: "✓ "
2625
+ }),
2626
+ /* @__PURE__ */ jsx(Text, { children: "Anthropic: " }),
2627
+ /* @__PURE__ */ jsx(Text, {
2628
+ dimColor: true,
2629
+ children: anthropicBaseURL
2630
+ })
2631
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2632
+ /* @__PURE__ */ jsx(Text, {
2633
+ dimColor: true,
2634
+ children: "○ "
2635
+ }),
2636
+ /* @__PURE__ */ jsx(Text, { children: "Anthropic: " }),
2637
+ /* @__PURE__ */ jsx(Text, {
2638
+ dimColor: true,
2639
+ children: "default"
2640
+ })
2641
+ ] })
2642
+ }),
2643
+ /* @__PURE__ */ jsx(Box, {
2644
+ marginLeft: 2,
2645
+ children: openaiBaseURL ? /* @__PURE__ */ jsxs(Fragment, { children: [
2646
+ /* @__PURE__ */ jsx(Text, {
2647
+ color: colors.success,
2648
+ children: "✓ "
2649
+ }),
2650
+ /* @__PURE__ */ jsx(Text, { children: "OpenAI: " }),
2651
+ /* @__PURE__ */ jsx(Text, {
2652
+ dimColor: true,
2653
+ children: openaiBaseURL
2654
+ })
2655
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2656
+ /* @__PURE__ */ jsx(Text, {
2657
+ dimColor: true,
2658
+ children: "○ "
2659
+ }),
2660
+ /* @__PURE__ */ jsx(Text, { children: "OpenAI: " }),
2661
+ /* @__PURE__ */ jsx(Text, {
2662
+ dimColor: true,
2663
+ children: "default"
2664
+ })
2665
+ ] })
2666
+ }),
2667
+ /* @__PURE__ */ jsx(Box, {
2668
+ marginLeft: 2,
2669
+ children: zhipuBaseURL ? /* @__PURE__ */ jsxs(Fragment, { children: [
2670
+ /* @__PURE__ */ jsx(Text, {
2671
+ color: colors.success,
2672
+ children: "✓ "
2673
+ }),
2674
+ /* @__PURE__ */ jsx(Text, { children: "Zhipu: " }),
2675
+ /* @__PURE__ */ jsx(Text, {
2676
+ dimColor: true,
2677
+ children: zhipuBaseURL
2678
+ })
2679
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2680
+ /* @__PURE__ */ jsx(Text, {
2681
+ dimColor: true,
2682
+ children: "○ "
2683
+ }),
2684
+ /* @__PURE__ */ jsx(Text, { children: "Zhipu: " }),
2685
+ /* @__PURE__ */ jsx(Text, {
2686
+ dimColor: true,
2687
+ children: "default"
2688
+ })
2689
+ ] })
2690
+ }),
2691
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2692
+ step === "select-provider" && /* @__PURE__ */ jsxs(Fragment, { children: [
2693
+ /* @__PURE__ */ jsx(Text, {
2694
+ bold: true,
2695
+ children: "Set Custom Base URL:"
2696
+ }),
2697
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2698
+ /* @__PURE__ */ jsxs(Box, {
2699
+ marginLeft: 2,
2700
+ children: [/* @__PURE__ */ jsx(Text, {
2701
+ color: colors.primary,
2702
+ children: "[1]"
2703
+ }), /* @__PURE__ */ jsx(Text, { children: " Anthropic (Claude)" })]
2704
+ }),
2705
+ /* @__PURE__ */ jsxs(Box, {
2706
+ marginLeft: 2,
2707
+ children: [/* @__PURE__ */ jsx(Text, {
2708
+ color: colors.primary,
2709
+ children: "[2]"
2710
+ }), /* @__PURE__ */ jsx(Text, { children: " OpenAI (GPT)" })]
2711
+ }),
2712
+ /* @__PURE__ */ jsxs(Box, {
2713
+ marginLeft: 2,
2714
+ children: [/* @__PURE__ */ jsx(Text, {
2715
+ color: colors.primary,
2716
+ children: "[3]"
2717
+ }), /* @__PURE__ */ jsx(Text, { children: " Zhipu (GLM)" })]
2718
+ }),
2719
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2720
+ /* @__PURE__ */ jsx(Text, {
2721
+ dimColor: true,
2722
+ children: "Press 1, 2, or 3 to select, Esc to close"
2723
+ })
2724
+ ] }),
2725
+ step === "enter-url" && selectedProvider && /* @__PURE__ */ jsxs(Fragment, { children: [
2726
+ /* @__PURE__ */ jsxs(Text, { children: [
2727
+ "Enter base URL for",
2728
+ " ",
2729
+ /* @__PURE__ */ jsx(Text, {
2730
+ color: colors.primary,
2731
+ children: selectedProvider === "anthropic" ? "Anthropic" : selectedProvider === "openai" ? "OpenAI" : "Zhipu"
2732
+ }),
2733
+ ":"
2734
+ ] }),
2735
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2736
+ /* @__PURE__ */ jsxs(Box, { children: [
2737
+ /* @__PURE__ */ jsxs(Text, {
2738
+ dimColor: true,
2739
+ children: [">", " "]
2740
+ }),
2741
+ /* @__PURE__ */ jsx(Text, { children: baseURL || /* @__PURE__ */ jsx(Text, {
2742
+ dimColor: true,
2743
+ children: "Type URL here..."
2744
+ }) }),
2745
+ /* @__PURE__ */ jsx(Text, {
2746
+ color: colors.primary,
2747
+ children: "█"
2748
+ })
2749
+ ] }),
2750
+ error && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Box, { height: 1 }), /* @__PURE__ */ jsxs(Text, {
2751
+ color: colors.error,
2752
+ children: [
2753
+ emoji.warning,
2754
+ " ",
2755
+ error
2756
+ ]
2757
+ })] }),
2758
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2759
+ /* @__PURE__ */ jsx(Text, {
2760
+ dimColor: true,
2761
+ children: "Press Enter to save, Esc to go back"
2762
+ })
2763
+ ] }),
2764
+ step === "success" && selectedProvider && /* @__PURE__ */ jsxs(Fragment, { children: [
2765
+ /* @__PURE__ */ jsxs(Text, {
2766
+ color: colors.success,
2767
+ children: [
2768
+ emoji.completed,
2769
+ " Base URL saved for",
2770
+ " ",
2771
+ selectedProvider === "anthropic" ? "Anthropic" : selectedProvider === "openai" ? "OpenAI" : "Zhipu",
2264
2772
  "!"
2265
2773
  ]
2266
2774
  }),
@@ -2352,6 +2860,7 @@ function ToolApproval({ toolName, args, onApprove, onDeny, onApproveAll }) {
2352
2860
  * Or with options:
2353
2861
  * ANTHROPIC_API_KEY=xxx bunx deep-agent-ink --model anthropic/claude-sonnet-4-20250514
2354
2862
  */
2863
+ init_limits();
2355
2864
  const DEFAULT_PROMPT_CACHING = true;
2356
2865
  const DEFAULT_EVICTION_LIMIT = DEFAULT_EVICTION_TOKEN_LIMIT;
2357
2866
  const DEFAULT_SUMMARIZATION = true;
@@ -2400,7 +2909,13 @@ function parseArgs() {
2400
2909
  } else if (arg && arg.startsWith("--session=")) {
2401
2910
  const sessionVal = arg.split("=")[1];
2402
2911
  if (sessionVal) options.session = sessionVal;
2403
- } else if (arg === "--help" || arg === "-h") {
2912
+ } else if (arg === "--anthropic-base-url") options.anthropicBaseURL = args[++i];
2913
+ else if (arg === "--openai-base-url") options.openaiBaseURL = args[++i];
2914
+ else if (arg === "--zhipu-base-url") options.zhipuBaseURL = args[++i];
2915
+ else if (arg === "--anthropic-api-key") options.anthropicApiKey = args[++i];
2916
+ else if (arg === "--openai-api-key") options.openaiApiKey = args[++i];
2917
+ else if (arg === "--zhipu-api-key") options.zhipuApiKey = args[++i];
2918
+ else if (arg === "--help" || arg === "-h") {
2404
2919
  printHelp();
2405
2920
  process.exit(0);
2406
2921
  }
@@ -2443,6 +2958,14 @@ Options:
2443
2958
  --dir, -d <directory> Working directory for file operations (default: current dir)
2444
2959
  --help, -h Show this help
2445
2960
 
2961
+ Provider Configuration:
2962
+ --anthropic-base-url <url> Custom base URL for Anthropic API
2963
+ --openai-base-url <url> Custom base URL for OpenAI API
2964
+ --zhipu-base-url <url> Custom base URL for Zhipu API
2965
+ --anthropic-api-key <key> Anthropic API key (overrides environment variable)
2966
+ --openai-api-key <key> OpenAI API key (overrides environment variable)
2967
+ --zhipu-api-key <key> Zhipu AI API key (overrides environment variable)
2968
+
2446
2969
  Performance & Memory (all enabled by default):
2447
2970
  --no-cache Disable prompt caching (enabled by default for Anthropic)
2448
2971
  --no-eviction Disable tool result eviction (enabled by default: 20k tokens)
@@ -2459,12 +2982,16 @@ Runtime Commands:
2459
2982
 
2460
2983
  API Keys:
2461
2984
  The CLI automatically loads API keys from:
2462
- 1. Environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, TAVILY_API_KEY)
2985
+ 1. Environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, ZHIPU_API_KEY, TAVILY_API_KEY)
2463
2986
  2. .env or .env.local file in the working directory
2464
2987
 
2465
2988
  Example .env file:
2466
2989
  ANTHROPIC_API_KEY=sk-ant-...
2467
2990
  OPENAI_API_KEY=sk-...
2991
+ ZHIPU_API_KEY=...
2992
+ ANTHROPIC_BASE_URL=https://custom-endpoint.com/v1
2993
+ OPENAI_BASE_URL=https://custom-openai-endpoint.com/v1
2994
+ ZHIPU_BASE_URL=https://api.z.ai/api/coding/paas/v4
2468
2995
  TAVILY_API_KEY=tvly-... # For web_search tool
2469
2996
 
2470
2997
  Examples:
@@ -2472,14 +2999,20 @@ Examples:
2472
2999
  bun src/cli-ink/index.tsx --dir ./my-project # loads .env from ./my-project
2473
3000
  ANTHROPIC_API_KEY=xxx bun src/cli-ink/index.tsx # env var takes precedence
2474
3001
  bun src/cli-ink/index.tsx --model anthropic/claude-sonnet-4-20250514
3002
+
3003
+ # Use Zhipu with custom base URL
3004
+ bun src/cli-ink/index.tsx --model zhipu/glm-4-plus --zhipu-base-url https://api.z.ai/api/coding/paas/v4
3005
+
3006
+ # Use Anthropic with custom API key
3007
+ bun src/cli-ink/index.tsx --model anthropic/claude-3.5-sonnet --anthropic-api-key sk-custom-...
2475
3008
  `);
2476
3009
  }
2477
3010
  function App({ options, backend }) {
2478
3011
  const { exit } = useApp();
2479
3012
  const summarizationConfig = options.enableSummarization ? {
2480
3013
  enabled: true,
2481
- tokenThreshold: options.summarizationThreshold,
2482
- keepMessages: options.summarizationKeepMessages
3014
+ tokenThreshold: options.summarizationThreshold ?? DEFAULT_SUMMARIZATION_THRESHOLD,
3015
+ keepMessages: options.summarizationKeepMessages ?? DEFAULT_KEEP_MESSAGES
2483
3016
  } : void 0;
2484
3017
  const checkpointer = options.session ? new FileSaver({ dir: "./.checkpoints" }) : void 0;
2485
3018
  const agent = useAgent({
@@ -2555,6 +3088,10 @@ function App({ options, backend }) {
2555
3088
  case "api":
2556
3089
  setPanel({ view: "apikey-input" });
2557
3090
  break;
3091
+ case "baseurl":
3092
+ case "base-url":
3093
+ setPanel({ view: "baseurl-input" });
3094
+ break;
2558
3095
  case "model":
2559
3096
  if (args) agent.setModel(args.trim());
2560
3097
  else setPanel({ view: "models" });
@@ -2666,7 +3203,7 @@ function App({ options, backend }) {
2666
3203
  }
2667
3204
  };
2668
3205
  const isGenerating = agent.status !== "idle" && agent.status !== "done" && agent.status !== "error";
2669
- const isInteractivePanel = panel.view === "apikey-input" || panel.view === "models";
3206
+ const isInteractivePanel = panel.view === "apikey-input" || panel.view === "baseurl-input" || panel.view === "models";
2670
3207
  return /* @__PURE__ */ jsxs(Box, {
2671
3208
  flexDirection: "column",
2672
3209
  padding: 1,
@@ -2686,6 +3223,19 @@ function App({ options, backend }) {
2686
3223
  onKeySaved: () => {},
2687
3224
  onClose: () => setPanel({ view: "none" })
2688
3225
  }),
3226
+ panel.view === "baseurl-input" && /* @__PURE__ */ jsx(BaseURLInput, {
3227
+ onSubmit: (provider, baseURL) => {
3228
+ const providerConfig = getProvidersConfig()[provider] || {};
3229
+ setProvidersConfig({ [provider]: {
3230
+ ...providerConfig,
3231
+ baseURL
3232
+ } });
3233
+ const envVar = `${provider.toUpperCase()}_BASE_URL`;
3234
+ process.env[envVar] = baseURL;
3235
+ console.log(`\x1b[32m✓\x1b[0m Set ${provider} base URL: ${baseURL}`);
3236
+ },
3237
+ onCancel: () => setPanel({ view: "none" })
3238
+ }),
2689
3239
  panel.view === "features" && /* @__PURE__ */ jsx(FeaturesPanel, {
2690
3240
  features: agent.features,
2691
3241
  options
@@ -3069,7 +3619,14 @@ function TokensPanel({ tokenCount, messageCount }) {
3069
3619
  */
3070
3620
  async function loadEnvFile(workDir) {
3071
3621
  const envPaths = [`${workDir}/.env`, `${workDir}/.env.local`];
3072
- const keysToCheck = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
3622
+ const keysToCheck = [
3623
+ "ANTHROPIC_API_KEY",
3624
+ "OPENAI_API_KEY",
3625
+ "ZHIPU_API_KEY",
3626
+ "ANTHROPIC_BASE_URL",
3627
+ "OPENAI_BASE_URL",
3628
+ "ZHIPU_BASE_URL"
3629
+ ];
3073
3630
  const result = {
3074
3631
  loaded: false,
3075
3632
  keysFound: []
@@ -3107,7 +3664,41 @@ async function main() {
3107
3664
  const workDir = options.workDir || process.cwd();
3108
3665
  const envResult = await loadEnvFile(workDir);
3109
3666
  if (envResult.loaded && envResult.keysFound.length > 0) console.log(`\x1b[32m✓\x1b[0m Loaded API keys from ${envResult.path}: ${envResult.keysFound.join(", ")}`);
3110
- if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) console.log(`\x1b[33m⚠\x1b[0m No API keys found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY in environment or .env file.`);
3667
+ if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY && !process.env.ZHIPU_API_KEY) console.log(`\x1b[33m⚠\x1b[0m No API keys found. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or ZHIPU_API_KEY in environment or .env file.`);
3668
+ const providerConfig = {};
3669
+ if (process.env.ANTHROPIC_BASE_URL || process.env.ANTHROPIC_API_KEY) providerConfig.anthropic = {
3670
+ baseURL: process.env.ANTHROPIC_BASE_URL,
3671
+ apiKey: process.env.ANTHROPIC_API_KEY
3672
+ };
3673
+ if (process.env.OPENAI_BASE_URL || process.env.OPENAI_API_KEY) providerConfig.openai = {
3674
+ baseURL: process.env.OPENAI_BASE_URL,
3675
+ apiKey: process.env.OPENAI_API_KEY
3676
+ };
3677
+ if (process.env.ZHIPU_BASE_URL || process.env.ZHIPU_API_KEY) providerConfig.zhipu = {
3678
+ baseURL: process.env.ZHIPU_BASE_URL,
3679
+ apiKey: process.env.ZHIPU_API_KEY
3680
+ };
3681
+ setProvidersConfig(providerConfig);
3682
+ const loadedBaseUrls = Object.entries(providerConfig).filter(([_, config]) => config?.baseURL).map(([provider]) => provider);
3683
+ if (loadedBaseUrls.length > 0) console.log(`\x1b[32m✓\x1b[0m Loaded custom base URLs: ${loadedBaseUrls.join(", ")}`);
3684
+ const cliConfig = {};
3685
+ if (options.anthropicBaseURL || options.anthropicApiKey) cliConfig.anthropic = {
3686
+ baseURL: options.anthropicBaseURL,
3687
+ apiKey: options.anthropicApiKey
3688
+ };
3689
+ if (options.openaiBaseURL || options.openaiApiKey) cliConfig.openai = {
3690
+ baseURL: options.openaiBaseURL,
3691
+ apiKey: options.openaiApiKey
3692
+ };
3693
+ if (options.zhipuBaseURL || options.zhipuApiKey) cliConfig.zhipu = {
3694
+ baseURL: options.zhipuBaseURL,
3695
+ apiKey: options.zhipuApiKey
3696
+ };
3697
+ if (Object.keys(cliConfig).length > 0) {
3698
+ setProvidersConfig(cliConfig);
3699
+ const overriddenProviders = Object.keys(cliConfig).join(", ");
3700
+ console.log(`\x1b[32m✓\x1b[0m Applied CLI overrides for: ${overriddenProviders}`);
3701
+ }
3111
3702
  render(/* @__PURE__ */ jsx(App, {
3112
3703
  options,
3113
3704
  backend: new LocalSandbox({ cwd: workDir })