@oh-my-pi/pi-coding-agent 12.18.3 → 12.19.2

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.
Files changed (233) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/package.json +35 -27
  3. package/src/async/index.ts +1 -0
  4. package/src/async/job-manager.ts +341 -0
  5. package/src/cli/file-processor.ts +3 -3
  6. package/src/cli/list-models.ts +3 -17
  7. package/src/cli/stats-cli.ts +3 -22
  8. package/src/cli/web-search-cli.ts +8 -16
  9. package/src/commit/agentic/agent.ts +6 -9
  10. package/src/commit/agentic/index.ts +44 -50
  11. package/src/commit/agentic/state.ts +0 -9
  12. package/src/commit/agentic/tools/propose-commit.ts +1 -30
  13. package/src/commit/agentic/tools/schemas.ts +31 -0
  14. package/src/commit/agentic/tools/split-commit.ts +1 -30
  15. package/src/commit/agentic/validation.ts +1 -18
  16. package/src/commit/analysis/conventional.ts +3 -50
  17. package/src/commit/analysis/summary.ts +2 -13
  18. package/src/commit/changelog/detect.ts +4 -1
  19. package/src/commit/changelog/generate.ts +2 -25
  20. package/src/commit/changelog/index.ts +1 -2
  21. package/src/commit/cli.ts +4 -12
  22. package/src/commit/map-reduce/reduce-phase.ts +2 -43
  23. package/src/commit/pipeline.ts +7 -15
  24. package/src/commit/utils.ts +44 -0
  25. package/src/config/prompt-templates.ts +1 -81
  26. package/src/config/settings-schema.ts +20 -1
  27. package/src/config.ts +2 -3
  28. package/src/debug/index.ts +1 -6
  29. package/src/debug/system-info.ts +2 -6
  30. package/src/discovery/builtin.ts +5 -9
  31. package/src/discovery/helpers.ts +0 -26
  32. package/src/discovery/ssh.ts +1 -8
  33. package/src/exa/company.ts +8 -39
  34. package/src/exa/factory.ts +64 -0
  35. package/src/exa/index.ts +0 -16
  36. package/src/exa/linkedin.ts +8 -39
  37. package/src/exa/mcp-client.ts +0 -64
  38. package/src/exa/researcher.ts +17 -59
  39. package/src/exa/search.ts +30 -154
  40. package/src/extensibility/custom-tools/loader.ts +3 -41
  41. package/src/extensibility/extensions/loader.ts +2 -9
  42. package/src/extensibility/hooks/loader.ts +3 -20
  43. package/src/extensibility/hooks/runner.ts +3 -19
  44. package/src/extensibility/plugins/installer.ts +2 -1
  45. package/src/extensibility/plugins/loader.ts +29 -117
  46. package/src/extensibility/skills.ts +2 -89
  47. package/src/extensibility/slash-commands.ts +1 -63
  48. package/src/extensibility/utils.ts +38 -0
  49. package/src/index.ts +9 -25
  50. package/src/internal-urls/index.ts +1 -0
  51. package/src/internal-urls/jobs-protocol.ts +118 -0
  52. package/src/ipy/kernel.ts +2 -0
  53. package/src/lsp/config.ts +1 -5
  54. package/src/lsp/lspmux.ts +0 -17
  55. package/src/lsp/utils.ts +2 -24
  56. package/src/main.ts +16 -24
  57. package/src/mcp/client.ts +1 -46
  58. package/src/mcp/render.ts +8 -1
  59. package/src/mcp/tool-cache.ts +1 -5
  60. package/src/mcp/transports/http.ts +2 -7
  61. package/src/mcp/transports/stdio.ts +2 -7
  62. package/src/modes/components/bash-execution.ts +2 -16
  63. package/src/modes/components/extensions/inspector-panel.ts +8 -18
  64. package/src/modes/components/footer.ts +10 -50
  65. package/src/modes/components/model-selector.ts +2 -21
  66. package/src/modes/components/python-execution.ts +2 -16
  67. package/src/modes/components/settings-selector.ts +1 -10
  68. package/src/modes/components/status-line/segments.ts +8 -25
  69. package/src/modes/components/status-line.ts +14 -31
  70. package/src/modes/components/tool-execution.ts +8 -2
  71. package/src/modes/controllers/command-controller.ts +71 -30
  72. package/src/modes/controllers/event-controller.ts +34 -4
  73. package/src/modes/controllers/mcp-command-controller.ts +3 -34
  74. package/src/modes/controllers/selector-controller.ts +2 -2
  75. package/src/modes/controllers/ssh-command-controller.ts +3 -34
  76. package/src/modes/interactive-mode.ts +6 -2
  77. package/src/modes/rpc/rpc-client.ts +1 -5
  78. package/src/modes/shared.ts +73 -0
  79. package/src/modes/types.ts +1 -0
  80. package/src/modes/utils/ui-helpers.ts +26 -2
  81. package/src/patch/hashline.ts +6 -286
  82. package/src/patch/index.ts +6 -57
  83. package/src/patch/normalize.ts +22 -65
  84. package/src/patch/shared.ts +16 -16
  85. package/src/prompts/system/custom-system-prompt.md +0 -10
  86. package/src/prompts/system/system-prompt.md +69 -89
  87. package/src/prompts/tools/async-result.md +5 -0
  88. package/src/prompts/tools/bash.md +5 -0
  89. package/src/prompts/tools/cancel-job.md +7 -0
  90. package/src/prompts/tools/hashline.md +0 -16
  91. package/src/prompts/tools/poll-jobs.md +7 -0
  92. package/src/prompts/tools/task.md +4 -0
  93. package/src/sdk.ts +70 -6
  94. package/src/session/agent-session.ts +43 -6
  95. package/src/session/agent-storage.ts +69 -278
  96. package/src/session/auth-storage.ts +14 -1430
  97. package/src/session/session-manager.ts +69 -5
  98. package/src/session/session-storage.ts +1 -5
  99. package/src/session/streaming-output.ts +637 -76
  100. package/src/slash-commands/builtin-registry.ts +8 -0
  101. package/src/ssh/connection-manager.ts +4 -12
  102. package/src/ssh/sshfs-mount.ts +3 -7
  103. package/src/ssh/utils.ts +8 -0
  104. package/src/system-prompt.ts +24 -90
  105. package/src/task/executor.ts +11 -1
  106. package/src/task/index.ts +258 -13
  107. package/src/task/parallel.ts +32 -0
  108. package/src/task/render.ts +15 -7
  109. package/src/task/types.ts +5 -0
  110. package/src/tools/ask.ts +4 -7
  111. package/src/tools/bash-interactive.ts +4 -5
  112. package/src/tools/bash.ts +125 -41
  113. package/src/tools/cancel-job.ts +93 -0
  114. package/src/tools/fetch.ts +7 -27
  115. package/src/tools/find.ts +3 -3
  116. package/src/tools/gemini-image.ts +15 -14
  117. package/src/tools/grep.ts +3 -3
  118. package/src/tools/index.ts +13 -29
  119. package/src/tools/json-tree.ts +12 -1
  120. package/src/tools/jtd-to-json-schema.ts +10 -74
  121. package/src/tools/jtd-to-typescript.ts +10 -72
  122. package/src/tools/jtd-utils.ts +102 -0
  123. package/src/tools/notebook.ts +4 -9
  124. package/src/tools/output-meta.ts +52 -26
  125. package/src/tools/path-utils.ts +13 -7
  126. package/src/tools/poll-jobs.ts +178 -0
  127. package/src/tools/python.ts +32 -35
  128. package/src/tools/read.ts +61 -82
  129. package/src/tools/render-utils.ts +8 -159
  130. package/src/tools/ssh.ts +7 -20
  131. package/src/tools/submit-result.ts +1 -1
  132. package/src/tools/tool-errors.ts +0 -30
  133. package/src/tools/tool-result.ts +1 -2
  134. package/src/tools/write.ts +8 -10
  135. package/src/tui/code-cell.ts +8 -3
  136. package/src/tui/status-line.ts +4 -4
  137. package/src/tui/types.ts +0 -1
  138. package/src/tui/utils.ts +1 -14
  139. package/src/utils/command-args.ts +76 -0
  140. package/src/utils/file-mentions.ts +15 -19
  141. package/src/utils/frontmatter.ts +5 -10
  142. package/src/utils/shell-snapshot.ts +0 -11
  143. package/src/utils/title-generator.ts +0 -12
  144. package/src/web/scrapers/artifacthub.ts +7 -16
  145. package/src/web/scrapers/arxiv.ts +3 -8
  146. package/src/web/scrapers/aur.ts +8 -22
  147. package/src/web/scrapers/biorxiv.ts +5 -14
  148. package/src/web/scrapers/bluesky.ts +13 -36
  149. package/src/web/scrapers/brew.ts +5 -10
  150. package/src/web/scrapers/cheatsh.ts +2 -12
  151. package/src/web/scrapers/chocolatey.ts +63 -26
  152. package/src/web/scrapers/choosealicense.ts +3 -18
  153. package/src/web/scrapers/cisa-kev.ts +4 -18
  154. package/src/web/scrapers/clojars.ts +6 -33
  155. package/src/web/scrapers/coingecko.ts +25 -33
  156. package/src/web/scrapers/crates-io.ts +7 -26
  157. package/src/web/scrapers/crossref.ts +4 -18
  158. package/src/web/scrapers/devto.ts +11 -41
  159. package/src/web/scrapers/discogs.ts +7 -10
  160. package/src/web/scrapers/discourse.ts +6 -31
  161. package/src/web/scrapers/dockerhub.ts +12 -35
  162. package/src/web/scrapers/fdroid.ts +8 -33
  163. package/src/web/scrapers/firefox-addons.ts +10 -34
  164. package/src/web/scrapers/flathub.ts +7 -24
  165. package/src/web/scrapers/github-gist.ts +2 -12
  166. package/src/web/scrapers/github.ts +9 -47
  167. package/src/web/scrapers/gitlab.ts +130 -185
  168. package/src/web/scrapers/go-pkg.ts +12 -22
  169. package/src/web/scrapers/hackage.ts +88 -43
  170. package/src/web/scrapers/hackernews.ts +25 -45
  171. package/src/web/scrapers/hex.ts +19 -36
  172. package/src/web/scrapers/huggingface.ts +26 -91
  173. package/src/web/scrapers/iacr.ts +3 -8
  174. package/src/web/scrapers/jetbrains-marketplace.ts +9 -20
  175. package/src/web/scrapers/lemmy.ts +5 -23
  176. package/src/web/scrapers/lobsters.ts +16 -28
  177. package/src/web/scrapers/mastodon.ts +24 -43
  178. package/src/web/scrapers/maven.ts +6 -21
  179. package/src/web/scrapers/mdn.ts +7 -11
  180. package/src/web/scrapers/metacpan.ts +9 -41
  181. package/src/web/scrapers/musicbrainz.ts +4 -28
  182. package/src/web/scrapers/npm.ts +8 -25
  183. package/src/web/scrapers/nuget.ts +14 -37
  184. package/src/web/scrapers/nvd.ts +6 -28
  185. package/src/web/scrapers/ollama.ts +7 -34
  186. package/src/web/scrapers/open-vsx.ts +5 -19
  187. package/src/web/scrapers/opencorporates.ts +30 -14
  188. package/src/web/scrapers/openlibrary.ts +49 -33
  189. package/src/web/scrapers/orcid.ts +4 -18
  190. package/src/web/scrapers/osv.ts +7 -24
  191. package/src/web/scrapers/packagist.ts +9 -24
  192. package/src/web/scrapers/pub-dev.ts +7 -50
  193. package/src/web/scrapers/pubmed.ts +54 -21
  194. package/src/web/scrapers/pypi.ts +8 -26
  195. package/src/web/scrapers/rawg.ts +11 -19
  196. package/src/web/scrapers/readthedocs.ts +4 -9
  197. package/src/web/scrapers/reddit.ts +5 -15
  198. package/src/web/scrapers/repology.ts +8 -20
  199. package/src/web/scrapers/rfc.ts +5 -14
  200. package/src/web/scrapers/rubygems.ts +6 -21
  201. package/src/web/scrapers/searchcode.ts +8 -36
  202. package/src/web/scrapers/sec-edgar.ts +4 -18
  203. package/src/web/scrapers/semantic-scholar.ts +15 -35
  204. package/src/web/scrapers/snapcraft.ts +5 -19
  205. package/src/web/scrapers/sourcegraph.ts +5 -43
  206. package/src/web/scrapers/spdx.ts +4 -18
  207. package/src/web/scrapers/spotify.ts +4 -23
  208. package/src/web/scrapers/stackoverflow.ts +8 -13
  209. package/src/web/scrapers/terraform.ts +9 -37
  210. package/src/web/scrapers/tldr.ts +3 -7
  211. package/src/web/scrapers/twitter.ts +3 -7
  212. package/src/web/scrapers/types.ts +105 -27
  213. package/src/web/scrapers/utils.ts +97 -103
  214. package/src/web/scrapers/vimeo.ts +7 -27
  215. package/src/web/scrapers/vscode-marketplace.ts +8 -17
  216. package/src/web/scrapers/w3c.ts +6 -14
  217. package/src/web/scrapers/wikidata.ts +5 -19
  218. package/src/web/scrapers/wikipedia.ts +2 -12
  219. package/src/web/scrapers/youtube.ts +5 -34
  220. package/src/web/search/index.ts +0 -9
  221. package/src/web/search/providers/anthropic.ts +3 -2
  222. package/src/web/search/providers/brave.ts +3 -18
  223. package/src/web/search/providers/exa.ts +1 -12
  224. package/src/web/search/providers/kimi.ts +5 -44
  225. package/src/web/search/providers/perplexity.ts +1 -12
  226. package/src/web/search/providers/synthetic.ts +3 -26
  227. package/src/web/search/providers/utils.ts +36 -0
  228. package/src/web/search/providers/zai.ts +9 -50
  229. package/src/web/search/types.ts +0 -28
  230. package/src/web/search/utils.ts +17 -0
  231. package/src/tools/output-utils.ts +0 -63
  232. package/src/tools/truncate.ts +0 -385
  233. package/src/web/search/auth.ts +0 -178
@@ -8,6 +8,7 @@ import { getEnvApiKey } from "@oh-my-pi/pi-ai";
8
8
  import { findApiKey as findExaKey } from "../../../exa/mcp-client";
9
9
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
10
10
  import { SearchProviderError } from "../../../web/search/types";
11
+ import { dateToAgeSeconds } from "../utils";
11
12
  import type { SearchParams } from "./base";
12
13
  import { SearchProvider } from "./base";
13
14
 
@@ -88,18 +89,6 @@ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<E
88
89
  return response.json() as Promise<ExaSearchResponse>;
89
90
  }
90
91
 
91
- /** Calculate age in seconds from ISO date string */
92
- function dateToAgeSeconds(dateStr: string | null | undefined): number | undefined {
93
- if (!dateStr) return undefined;
94
- try {
95
- const date = new Date(dateStr);
96
- if (Number.isNaN(date.getTime())) return undefined;
97
- return Math.floor((Date.now() - date.getTime()) / 1000);
98
- } catch {
99
- return undefined;
100
- }
101
- }
102
-
103
92
  /** Execute Exa web search */
104
93
  export async function searchExa(params: ExaSearchParams): Promise<SearchResponse> {
105
94
  const apiKey = getEnvApiKey("exa");
@@ -6,12 +6,12 @@
6
6
  */
7
7
  import { getEnvApiKey } from "@oh-my-pi/pi-ai";
8
8
  import { $env } from "@oh-my-pi/pi-utils";
9
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
10
- import { AgentStorage } from "../../../session/agent-storage";
11
9
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
12
10
  import { SearchProviderError } from "../../../web/search/types";
11
+ import { clampNumResults, dateToAgeSeconds } from "../utils";
13
12
  import type { SearchParams } from "./base";
14
13
  import { SearchProvider } from "./base";
14
+ import { findCredential } from "./utils";
15
15
 
16
16
  const KIMI_SEARCH_URL = "https://api.kimi.com/coding/v1/search";
17
17
 
@@ -51,53 +51,14 @@ function resolveBaseUrl(): string {
51
51
  return asTrimmed($env.MOONSHOT_SEARCH_BASE_URL) ?? asTrimmed($env.KIMI_SEARCH_BASE_URL) ?? KIMI_SEARCH_URL;
52
52
  }
53
53
 
54
- function clampNumResults(value: number | undefined): number {
55
- if (!value || Number.isNaN(value)) return DEFAULT_NUM_RESULTS;
56
- return Math.min(MAX_NUM_RESULTS, Math.max(1, value));
57
- }
58
-
59
- function dateToAgeSeconds(dateStr: string | undefined): number | undefined {
60
- if (!dateStr) return undefined;
61
- try {
62
- const date = new Date(dateStr);
63
- if (Number.isNaN(date.getTime())) return undefined;
64
- return Math.floor((Date.now() - date.getTime()) / 1000);
65
- } catch {
66
- return undefined;
67
- }
68
- }
69
-
70
- /**
71
- * Find Kimi search credentials from environment or agent.db credentials.
72
- * Priority: MOONSHOT_SEARCH_API_KEY / KIMI_SEARCH_API_KEY / MOONSHOT_API_KEY, then agent.db providers "moonshot" or "kimi-code".
73
- */
54
+ /** Find Kimi search credentials from environment or agent.db credentials. */
74
55
  async function findApiKey(): Promise<string | null> {
75
56
  const envKey =
76
57
  asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ??
77
58
  asTrimmed($env.KIMI_SEARCH_API_KEY) ??
78
59
  getEnvApiKey("moonshot") ??
79
60
  null;
80
- if (envKey) return envKey;
81
-
82
- try {
83
- const storage = await AgentStorage.open(getAgentDbPath());
84
- for (const provider of ["moonshot", "kimi-code"] as const) {
85
- const records = storage.listAuthCredentials(provider);
86
- for (const record of records) {
87
- const credential = record.credential;
88
- if (credential.type === "api_key" && credential.key.trim().length > 0) {
89
- return credential.key;
90
- }
91
- if (credential.type === "oauth" && credential.access.trim().length > 0) {
92
- return credential.access;
93
- }
94
- }
95
- }
96
- } catch {
97
- return null;
98
- }
99
-
100
- return null;
61
+ return findCredential(envKey, "moonshot", "kimi-code");
101
62
  }
102
63
 
103
64
  async function callKimiSearch(
@@ -143,7 +104,7 @@ export async function searchKimi(params: KimiSearchParams): Promise<SearchRespon
143
104
  );
144
105
  }
145
106
 
146
- const limit = clampNumResults(params.num_results);
107
+ const limit = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
147
108
  const { response, requestId } = await callKimiSearch(apiKey, {
148
109
  query: params.query,
149
110
  limit,
@@ -19,6 +19,7 @@ import type {
19
19
  SearchSource,
20
20
  } from "../../../web/search/types";
21
21
  import { SearchProviderError } from "../../../web/search/types";
22
+ import { dateToAgeSeconds } from "../utils";
22
23
  import type { SearchParams } from "./base";
23
24
  import { SearchProvider } from "./base";
24
25
 
@@ -383,18 +384,6 @@ async function callPerplexityOAuth(
383
384
  };
384
385
  }
385
386
 
386
- /** Calculate age in seconds from ISO date string */
387
- function dateToAgeSeconds(dateStr: string | null | undefined): number | undefined {
388
- if (!dateStr) return undefined;
389
- try {
390
- const date = new Date(dateStr);
391
- if (Number.isNaN(date.getTime())) return undefined;
392
- return Math.floor((Date.now() - date.getTime()) / 1000);
393
- } catch {
394
- return undefined;
395
- }
396
- }
397
-
398
387
  function messageContentToText(content: PerplexityMessageOutput["content"]): string {
399
388
  if (!content) return "";
400
389
  if (typeof content === "string") return content;
@@ -6,12 +6,11 @@
6
6
  */
7
7
 
8
8
  import { getEnvApiKey } from "@oh-my-pi/pi-ai";
9
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
10
- import { AgentStorage } from "../../../session/agent-storage";
11
9
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
12
10
  import { SearchProviderError } from "../../../web/search/types";
13
11
  import type { SearchParams } from "./base";
14
12
  import { SearchProvider } from "./base";
13
+ import { findCredential } from "./utils";
15
14
 
16
15
  const SYNTHETIC_SEARCH_URL = "https://api.synthetic.new/v2/search";
17
16
 
@@ -26,31 +25,9 @@ interface SyntheticSearchResponse {
26
25
  results: SyntheticSearchResult[];
27
26
  }
28
27
 
29
- /**
30
- * Find Synthetic API key from environment or agent.db credentials.
31
- * Priority: SYNTHETIC_API_KEY env var, then credentials stored under provider "synthetic".
32
- */
28
+ /** Find Synthetic API key from environment or agent.db credentials. */
33
29
  export async function findApiKey(): Promise<string | null> {
34
- const envKey = getEnvApiKey("synthetic");
35
- if (envKey) return envKey;
36
-
37
- try {
38
- const storage = await AgentStorage.open(getAgentDbPath());
39
- const records = storage.listAuthCredentials("synthetic");
40
- for (const record of records) {
41
- const credential = record.credential;
42
- if (credential.type === "api_key" && credential.key.trim().length > 0) {
43
- return credential.key;
44
- }
45
- if (credential.type === "oauth" && credential.access.trim().length > 0) {
46
- return credential.access;
47
- }
48
- }
49
- } catch {
50
- return null;
51
- }
52
-
53
- return null;
30
+ return findCredential(getEnvApiKey("synthetic"), "synthetic");
54
31
  }
55
32
 
56
33
  /** Call Synthetic search API. */
@@ -0,0 +1,36 @@
1
+ import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
2
+ import { AgentStorage } from "../../../session/agent-storage";
3
+
4
+ /**
5
+ * Search for an API credential by checking an env-derived key first,
6
+ * then falling back to agent.db stored credentials for the given providers.
7
+ *
8
+ * @param envKey - Pre-resolved environment variable value (or null)
9
+ * @param storageProviders - Provider names to look up in AgentStorage
10
+ */
11
+ export async function findCredential(
12
+ envKey: string | null | undefined,
13
+ ...storageProviders: string[]
14
+ ): Promise<string | null> {
15
+ if (envKey) return envKey;
16
+
17
+ try {
18
+ const storage = await AgentStorage.open(getAgentDbPath());
19
+ for (const provider of storageProviders) {
20
+ const records = storage.listAuthCredentials(provider);
21
+ for (const record of records) {
22
+ const credential = record.credential;
23
+ if (credential.type === "api_key" && credential.key.trim().length > 0) {
24
+ return credential.key;
25
+ }
26
+ if (credential.type === "oauth" && credential.access.trim().length > 0) {
27
+ return credential.access;
28
+ }
29
+ }
30
+ }
31
+ } catch {
32
+ return null;
33
+ }
34
+
35
+ return null;
36
+ }
@@ -5,13 +5,13 @@
5
5
  * the unified SearchResponse shape used by the web search tool.
6
6
  */
7
7
  import { getEnvApiKey } from "@oh-my-pi/pi-ai";
8
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
9
- import { AgentStorage } from "../../../session/agent-storage";
10
-
8
+ import { asRecord, asString } from "../../../web/scrapers/utils";
11
9
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
12
10
  import { SearchProviderError } from "../../../web/search/types";
11
+ import { dateToAgeSeconds } from "../utils";
13
12
  import type { SearchParams } from "./base";
14
13
  import { SearchProvider } from "./base";
14
+ import { findCredential } from "./utils";
15
15
 
16
16
  const ZAI_MCP_URL = "https://api.z.ai/api/mcp/web_search_prime/mcp";
17
17
  const ZAI_TOOL_NAME = "webSearchPrime";
@@ -50,50 +50,9 @@ interface JsonRpcPayload {
50
50
  error?: JsonRpcError;
51
51
  }
52
52
 
53
- function asRecord(value: unknown): Record<string, unknown> | null {
54
- return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
55
- }
56
-
57
- function asString(value: unknown): string | undefined {
58
- return typeof value === "string" && value.trim().length > 0 ? value : undefined;
59
- }
60
-
61
- /**
62
- * Finds Z.AI API credentials from environment or saved auth storage.
63
- * Priority: ZAI_API_KEY env var, then credentials stored under provider "zai".
64
- */
53
+ /** Find Z.AI API credentials from environment or saved auth storage. */
65
54
  export async function findApiKey(): Promise<string | null> {
66
- const envKey = getEnvApiKey("zai");
67
- if (envKey) return envKey;
68
-
69
- try {
70
- const storage = await AgentStorage.open(getAgentDbPath());
71
- const records = storage.listAuthCredentials("zai");
72
- for (const record of records) {
73
- const credential = record.credential;
74
- if (credential.type === "api_key" && credential.key.trim().length > 0) {
75
- return credential.key;
76
- }
77
- if (credential.type === "oauth" && credential.access.trim().length > 0) {
78
- return credential.access;
79
- }
80
- }
81
- } catch {
82
- return null;
83
- }
84
-
85
- return null;
86
- }
87
-
88
- function dateToAgeSeconds(dateStr: string | undefined): number | undefined {
89
- if (!dateStr) return undefined;
90
- try {
91
- const date = new Date(dateStr);
92
- if (Number.isNaN(date.getTime())) return undefined;
93
- return Math.floor((Date.now() - date.getTime()) / 1000);
94
- } catch {
95
- return undefined;
96
- }
55
+ return findCredential(getEnvApiKey("zai"), "zai");
97
56
  }
98
57
 
99
58
  async function callZaiTool(apiKey: string, args: Record<string, unknown>): Promise<unknown> {
@@ -172,7 +131,7 @@ async function callZaiTool(apiKey: string, args: Record<string, unknown>): Promi
172
131
  const content = Array.isArray(resultRecord.content) ? resultRecord.content : [];
173
132
  const errorText = content
174
133
  .map(item => asString(asRecord(item)?.text))
175
- .filter((text): text is string => text !== undefined)
134
+ .filter((text): text is string => text != null)
176
135
  .join("\n")
177
136
  .trim();
178
137
  const statusMatch = errorText.match(/MCP error\s*(-?\d+)/i);
@@ -298,10 +257,10 @@ function toSources(results: ZaiSearchResult[]): SearchSource[] {
298
257
  sources.push({
299
258
  title: asString(result.title) ?? url,
300
259
  url,
301
- snippet: asString(result.content),
302
- publishedDate,
260
+ snippet: asString(result.content) ?? undefined,
261
+ publishedDate: publishedDate ?? undefined,
303
262
  ageSeconds: dateToAgeSeconds(publishedDate),
304
- author: asString(result.media),
263
+ author: asString(result.media) ?? undefined,
305
264
  });
306
265
  }
307
266
  return sources;
@@ -81,34 +81,6 @@ export class SearchProviderError extends Error {
81
81
  }
82
82
  }
83
83
 
84
- /** Auth configuration for Anthropic */
85
- export interface AnthropicAuthConfig {
86
- apiKey: string;
87
- baseUrl: string;
88
- isOAuth: boolean;
89
- }
90
-
91
- /** models.json structure for provider resolution */
92
- export interface ModelsJson {
93
- providers?: Record<
94
- string,
95
- {
96
- baseUrl?: string;
97
- apiKey?: string;
98
- api?: string;
99
- }
100
- >;
101
- }
102
-
103
- /** OAuth credential for Anthropic API access */
104
- export interface AnthropicOAuthCredential {
105
- type: "oauth";
106
- access: string;
107
- refresh?: string;
108
- /** Expiry timestamp in milliseconds */
109
- expires: number;
110
- }
111
-
112
84
  /** Anthropic API response types */
113
85
  export interface AnthropicSearchResult {
114
86
  type: "web_search_result";
@@ -0,0 +1,17 @@
1
+ /** Calculate age in seconds from an ISO date string. Returns undefined on invalid input. */
2
+ export function dateToAgeSeconds(dateStr: string | null | undefined): number | undefined {
3
+ if (!dateStr) return undefined;
4
+ try {
5
+ const date = new Date(dateStr);
6
+ if (Number.isNaN(date.getTime())) return undefined;
7
+ return Math.floor((Date.now() - date.getTime()) / 1000);
8
+ } catch {
9
+ return undefined;
10
+ }
11
+ }
12
+
13
+ /** Clamp a result count to [1, maxVal], returning defaultVal when value is absent or NaN. */
14
+ export function clampNumResults(value: number | undefined, defaultVal: number, maxVal: number): number {
15
+ if (!value || Number.isNaN(value)) return defaultVal;
16
+ return Math.min(maxVal, Math.max(1, value));
17
+ }
@@ -1,63 +0,0 @@
1
- import { ArtifactManager } from "../session/artifacts";
2
- import type { ToolSession } from ".";
3
-
4
- export interface TailBuffer {
5
- append(chunk: string): void;
6
- text(): string;
7
- bytes(): number;
8
- }
9
-
10
- export function createTailBuffer(maxBytes: number): TailBuffer {
11
- let buffer = "";
12
- let bufferBytes = 0;
13
-
14
- const append = (text: string) => {
15
- if (!text) return;
16
- const chunkBytes = Buffer.byteLength(text, "utf-8");
17
- buffer += text;
18
- bufferBytes += chunkBytes;
19
-
20
- if (bufferBytes > maxBytes) {
21
- const buf = Buffer.from(buffer, "utf-8");
22
- let start = Math.max(0, buf.length - maxBytes);
23
- while (start < buf.length && (buf[start] & 0xc0) === 0x80) {
24
- start++;
25
- }
26
- buffer = buf.subarray(start).toString("utf-8");
27
- bufferBytes = Buffer.byteLength(buffer, "utf-8");
28
- }
29
- };
30
-
31
- return {
32
- append,
33
- text: () => buffer,
34
- bytes: () => bufferBytes,
35
- };
36
- }
37
-
38
- export function getArtifactManager(session: ToolSession): ArtifactManager | null {
39
- if (session.artifactManager) {
40
- return session.artifactManager;
41
- }
42
- const sessionFile = session.getSessionFile();
43
- if (!sessionFile) {
44
- return null;
45
- }
46
- const manager = new ArtifactManager(sessionFile);
47
- session.artifactManager = manager;
48
- return manager;
49
- }
50
-
51
- export async function allocateOutputArtifact(
52
- session: ToolSession,
53
- toolType: string,
54
- ): Promise<{ artifactPath?: string; artifactId?: string }> {
55
- const manager = getArtifactManager(session);
56
- if (!manager) return {};
57
- try {
58
- const allocation = await manager.allocatePath(toolType);
59
- return { artifactPath: allocation.path, artifactId: allocation.id };
60
- } catch {
61
- return {};
62
- }
63
- }