@oh-my-pi/pi-coding-agent 17.1.1 → 17.1.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 (96) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli.js +6216 -3984
  3. package/dist/types/cli/bench-cli.d.ts +1 -0
  4. package/dist/types/config/model-resolver.d.ts +6 -3
  5. package/dist/types/config/settings-schema.d.ts +4 -0
  6. package/dist/types/launch/broker-list-order.test.d.ts +1 -0
  7. package/dist/types/modes/interactive-mode.d.ts +2 -6
  8. package/dist/types/modes/types.d.ts +8 -5
  9. package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
  10. package/dist/types/task/executor.d.ts +3 -1
  11. package/dist/types/task/structured-subagent.d.ts +3 -0
  12. package/dist/types/task/types.d.ts +11 -11
  13. package/dist/types/thinking.d.ts +13 -0
  14. package/dist/types/tools/eval-format/index.d.ts +7 -0
  15. package/dist/types/tools/eval-format/javascript.d.ts +2 -0
  16. package/dist/types/tools/eval-format/julia.d.ts +2 -0
  17. package/dist/types/tools/eval-format/python.d.ts +2 -0
  18. package/dist/types/tools/eval-format/ruby.d.ts +2 -0
  19. package/dist/types/tools/resolve.d.ts +7 -0
  20. package/dist/types/tools/todo.d.ts +4 -1
  21. package/dist/types/web/search/providers/base.d.ts +16 -0
  22. package/dist/types/web/search/providers/brave.d.ts +2 -0
  23. package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
  24. package/dist/types/web/search/providers/gemini.d.ts +3 -0
  25. package/dist/types/web/search/providers/jina.d.ts +2 -0
  26. package/dist/types/web/search/providers/kagi.d.ts +2 -0
  27. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  28. package/dist/types/web/search/providers/parallel.d.ts +2 -0
  29. package/dist/types/web/search/providers/perplexity.d.ts +3 -0
  30. package/dist/types/web/search/providers/searxng.d.ts +10 -0
  31. package/dist/types/web/search/providers/tavily.d.ts +8 -0
  32. package/dist/types/web/search/query.d.ts +190 -0
  33. package/package.json +12 -12
  34. package/src/cli/bench-cli.ts +12 -1
  35. package/src/cli/web-search-cli.ts +7 -0
  36. package/src/config/model-resolver.ts +17 -6
  37. package/src/config/settings-schema.ts +5 -0
  38. package/src/debug/raw-sse-buffer.ts +157 -30
  39. package/src/export/share.ts +4 -3
  40. package/src/launch/broker-list-order.test.ts +89 -0
  41. package/src/launch/broker.ts +49 -8
  42. package/src/modes/controllers/input-controller.ts +8 -8
  43. package/src/modes/interactive-mode.ts +60 -5
  44. package/src/modes/types.ts +9 -4
  45. package/src/modes/utils/ui-helpers.ts +7 -8
  46. package/src/prompts/system/resolve-device-reminder.md +1 -1
  47. package/src/prompts/system/workflow-notice.md +1 -1
  48. package/src/prompts/tools/ast-edit.md +1 -0
  49. package/src/prompts/tools/bash.md +1 -0
  50. package/src/prompts/tools/eval.md +2 -2
  51. package/src/prompts/tools/task.md +5 -2
  52. package/src/prompts/tools/web-search.md +2 -0
  53. package/src/sdk.ts +8 -4
  54. package/src/session/agent-session.ts +14 -0
  55. package/src/session/queued-messages.ts +7 -1
  56. package/src/task/executor.ts +17 -9
  57. package/src/task/index.ts +14 -34
  58. package/src/task/structured-subagent.ts +4 -0
  59. package/src/task/types.ts +15 -13
  60. package/src/thinking.ts +27 -0
  61. package/src/tools/ast-edit.ts +4 -1
  62. package/src/tools/bash.ts +13 -0
  63. package/src/tools/browser/render.ts +2 -1
  64. package/src/tools/eval-format/index.ts +24 -0
  65. package/src/tools/eval-format/javascript.ts +952 -0
  66. package/src/tools/eval-format/julia.ts +446 -0
  67. package/src/tools/eval-format/python.ts +544 -0
  68. package/src/tools/eval-format/ruby.ts +380 -0
  69. package/src/tools/eval-render.ts +12 -6
  70. package/src/tools/resolve.ts +10 -1
  71. package/src/tools/todo.ts +58 -6
  72. package/src/web/search/index.ts +28 -5
  73. package/src/web/search/providers/anthropic.ts +62 -4
  74. package/src/web/search/providers/base.ts +16 -0
  75. package/src/web/search/providers/brave.ts +30 -3
  76. package/src/web/search/providers/codex.ts +14 -1
  77. package/src/web/search/providers/duckduckgo.ts +23 -1
  78. package/src/web/search/providers/ecosia.ts +5 -1
  79. package/src/web/search/providers/exa.ts +28 -1
  80. package/src/web/search/providers/firecrawl.ts +37 -3
  81. package/src/web/search/providers/gemini.ts +12 -2
  82. package/src/web/search/providers/google.ts +2 -1
  83. package/src/web/search/providers/jina.ts +31 -8
  84. package/src/web/search/providers/kagi.ts +10 -1
  85. package/src/web/search/providers/kimi.ts +16 -1
  86. package/src/web/search/providers/mojeek.ts +14 -1
  87. package/src/web/search/providers/parallel.ts +48 -2
  88. package/src/web/search/providers/perplexity.ts +94 -6
  89. package/src/web/search/providers/searxng.ts +145 -9
  90. package/src/web/search/providers/startpage.ts +8 -2
  91. package/src/web/search/providers/synthetic.ts +7 -1
  92. package/src/web/search/providers/tavily.ts +52 -3
  93. package/src/web/search/providers/tinyfish.ts +6 -1
  94. package/src/web/search/providers/xai.ts +49 -2
  95. package/src/web/search/providers/zai.ts +19 -1
  96. package/src/web/search/query.ts +850 -0
@@ -7,6 +7,7 @@
7
7
  import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
8
8
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
9
9
  import { SearchProviderError } from "../../../web/search/types";
10
+ import { formatQuery, parseSearchQuery } from "../query";
10
11
  import { clampNumResults, dateToAgeSeconds } from "../utils";
11
12
  import type { SearchParams } from "./base";
12
13
  import { SearchProvider } from "./base";
@@ -20,6 +21,14 @@ export interface TavilySearchParams {
20
21
  query: string;
21
22
  num_results?: number;
22
23
  recency?: "day" | "week" | "month" | "year";
24
+ /** `site:` hosts mapped to Tavily's `include_domains`. */
25
+ include_domains?: string[];
26
+ /** `-site:` hosts mapped to Tavily's `exclude_domains`. */
27
+ exclude_domains?: string[];
28
+ /** `after:` inclusive lower bound, ISO `YYYY-MM-DD`, mapped to `start_date`. */
29
+ start_date?: string;
30
+ /** `before:` upper bound, ISO `YYYY-MM-DD`, mapped to `end_date`. */
31
+ end_date?: string;
23
32
  signal?: AbortSignal;
24
33
  fetch?: FetchImpl;
25
34
  }
@@ -83,7 +92,21 @@ export function buildRequestBody(params: TavilySearchParams): Record<string, unk
83
92
  include_answer: "advanced",
84
93
  include_raw_content: false,
85
94
  };
86
- if (params.recency) {
95
+ if (params.include_domains?.length) {
96
+ body.include_domains = params.include_domains;
97
+ }
98
+ if (params.exclude_domains?.length) {
99
+ body.exclude_domains = params.exclude_domains;
100
+ }
101
+ if (params.start_date) {
102
+ body.start_date = params.start_date;
103
+ }
104
+ if (params.end_date) {
105
+ body.end_date = params.end_date;
106
+ }
107
+ // Explicit before:/after: bounds take precedence over the relative recency
108
+ // window; sending both would over-restrict.
109
+ if (params.recency && !params.start_date && !params.end_date) {
87
110
  body.time_range = params.recency;
88
111
  }
89
112
  return body;
@@ -148,8 +171,19 @@ function hasRenderableResponse(response: SearchResponse): boolean {
148
171
  return response.sources.length > 0;
149
172
  }
150
173
 
174
+ /** Bare hosts from `site:` values (path parts are enforced by the central lenient filter). */
175
+ function siteHosts(sites: readonly string[]): string[] {
176
+ const hosts = new Set<string>();
177
+ for (const site of sites) {
178
+ const host = site.split("/", 1)[0];
179
+ if (host) hosts.add(host);
180
+ }
181
+ return [...hosts];
182
+ }
183
+
151
184
  /** Execute Tavily web search. */
152
185
  export async function searchTavily(params: SearchParams): Promise<SearchResponse> {
186
+ const parsed = params.parsedQuery ?? parseSearchQuery(params.query);
153
187
  const tavilyParams: TavilySearchParams = {
154
188
  query: params.query,
155
189
  num_results: params.numSearchResults ?? params.limit,
@@ -157,6 +191,16 @@ export async function searchTavily(params: SearchParams): Promise<SearchResponse
157
191
  signal: params.signal,
158
192
  fetch: params.fetch,
159
193
  };
194
+ if (parsed.hasDirectives) {
195
+ // Tavily prefers clean natural text; re-emit only phrases and -exclusions.
196
+ tavilyParams.query = formatQuery(parsed, { phrases: true, negation: true });
197
+ const include = siteHosts(parsed.sites);
198
+ const exclude = siteHosts(parsed.excludedSites);
199
+ if (include.length > 0) tavilyParams.include_domains = include;
200
+ if (exclude.length > 0) tavilyParams.exclude_domains = exclude;
201
+ if (parsed.after) tavilyParams.start_date = parsed.after;
202
+ if (parsed.before) tavilyParams.end_date = parsed.before;
203
+ }
160
204
  const keyOrResolver: ApiKey = params.authStorage.resolver("tavily", {
161
205
  sessionId: params.sessionId,
162
206
  });
@@ -171,11 +215,16 @@ export async function searchTavily(params: SearchParams): Promise<SearchResponse
171
215
  withAuth(keyOrResolver, key => callTavilySearch(key, searchParams), authOptions);
172
216
 
173
217
  const response = toSearchResponse(await callWithAuth(tavilyParams), numResults);
174
- if (!tavilyParams.recency || hasRenderableResponse(response)) {
218
+ const hasTimeFilter = Boolean(tavilyParams.recency || tavilyParams.start_date || tavilyParams.end_date);
219
+ if (!hasTimeFilter || hasRenderableResponse(response)) {
175
220
  return response;
176
221
  }
177
222
 
178
- return toSearchResponse(await callWithAuth({ ...tavilyParams, recency: undefined }), numResults);
223
+ // Time filters commonly zero out results; retry once without them.
224
+ return toSearchResponse(
225
+ await callWithAuth({ ...tavilyParams, recency: undefined, start_date: undefined, end_date: undefined }),
226
+ numResults,
227
+ );
179
228
  }
180
229
 
181
230
  /** Search provider for Tavily web search. */
@@ -7,6 +7,7 @@
7
7
  import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
8
8
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
9
9
  import { SearchProviderError } from "../../../web/search/types";
10
+ import { formatQuery, parseSearchQuery, type QuerySyntax } from "../query";
10
11
  import { clampNumResults } from "../utils";
11
12
  import type { SearchParams } from "./base";
12
13
  import { SearchProvider } from "./base";
@@ -17,6 +18,9 @@ const DEFAULT_NUM_RESULTS = 10;
17
18
  const MAX_NUM_RESULTS = 20;
18
19
  const MAX_PAGE = 10;
19
20
 
21
+ /** TinyFish is SERP-backed: common Google-style operators pass through. */
22
+ const TINYFISH_QUERY_SYNTAX: QuerySyntax = { phrases: true, negation: true, site: true, filetype: true };
23
+
20
24
  const RECENCY_MINUTES: Record<NonNullable<SearchParams["recency"]>, number> = {
21
25
  day: 1440,
22
26
  week: 10080,
@@ -107,8 +111,9 @@ function appendTinyFishSources(sources: SearchSource[], results: readonly TinyFi
107
111
  export async function searchTinyFish(params: SearchParams): Promise<SearchResponse> {
108
112
  const numResults = clampNumResults(params.numSearchResults ?? params.limit, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
109
113
  const pageSize = Math.min(numResults, DEFAULT_NUM_RESULTS);
114
+ const parsed = params.parsedQuery ?? parseSearchQuery(params.query);
110
115
  const tinyFishParams: TinyFishSearchParams = {
111
- query: params.query,
116
+ query: parsed.hasDirectives ? formatQuery(parsed, TINYFISH_QUERY_SYNTAX) : params.query,
112
117
  num_results: pageSize,
113
118
  recency: params.recency,
114
119
  signal: params.signal,
@@ -3,6 +3,7 @@ import { $env } from "@oh-my-pi/pi-utils";
3
3
  import { resolveXAIHttpTransport, type XAIHttpProvider, type XAIHttpTransport } from "../../../lib/xai-http";
4
4
  import type { SearchCitation, SearchResponse, SearchSource, SearchUsage } from "../../../web/search/types";
5
5
  import { SearchProviderError } from "../../../web/search/types";
6
+ import { formatQuery, parseSearchQuery, type QuerySyntax } from "../query";
6
7
  import { clampNumResults } from "../utils";
7
8
  import type { SearchParams } from "./base";
8
9
  import { SearchProvider } from "./base";
@@ -57,14 +58,60 @@ interface XAIResponsesResponse {
57
58
  usage?: XAIResponsesUsage | null;
58
59
  }
59
60
 
61
+ /**
62
+ * Query syntax re-emitted for the Grok search agent. `site:`/`-site:` are
63
+ * stripped because hosts map natively onto the web_search domain filters;
64
+ * `before:`/`after:` stay in the query text — the Responses web_search tool
65
+ * has no date parameters (`from_date`/`to_date` exist only on `x_search` and
66
+ * the deprecated Live Search `search_parameters`, which now returns 410) and
67
+ * the agent honors the tokens as natural-language hints.
68
+ */
69
+ const XAI_QUERY_SYNTAX: QuerySyntax = {
70
+ phrases: true,
71
+ negation: true,
72
+ or: true,
73
+ inUrl: true,
74
+ inTitle: true,
75
+ filetype: true,
76
+ dateRange: true,
77
+ };
78
+
79
+ /** xAI web_search accepts at most 5 allowed or excluded domains per request. */
80
+ const MAX_DOMAIN_FILTERS = 5;
81
+
82
+ /** Bare hosts of `site:` values (`github.com/anthropics` → `github.com`), deduped, capped at 5; path parts are enforced by the central constraint filter. */
83
+ function domainFilterList(sites: readonly string[]): string[] {
84
+ const hosts = new Set<string>();
85
+ for (const site of sites) {
86
+ const slash = site.indexOf("/");
87
+ hosts.add(slash === -1 ? site : site.slice(0, slash));
88
+ if (hosts.size === MAX_DOMAIN_FILTERS) break;
89
+ }
90
+ return [...hosts];
91
+ }
92
+
60
93
  function buildRequestBody(params: SearchParams): Record<string, unknown> {
94
+ const parsed = params.parsedQuery ?? parseSearchQuery(params.query);
95
+ const webSearchTool: Record<string, unknown> = { type: "web_search" };
96
+ let query = params.query;
97
+ if (parsed.hasDirectives) {
98
+ query = formatQuery(parsed, XAI_QUERY_SYNTAX);
99
+ // allowed_domains and excluded_domains are mutually exclusive per
100
+ // request; prefer the allow list, the central filter enforces exclusions.
101
+ if (parsed.sites.length > 0) {
102
+ webSearchTool.filters = { allowed_domains: domainFilterList(parsed.sites) };
103
+ } else if (parsed.excludedSites.length > 0) {
104
+ webSearchTool.filters = { excluded_domains: domainFilterList(parsed.excludedSites) };
105
+ }
106
+ }
107
+
61
108
  const body: Record<string, unknown> = {
62
109
  model: XAI_WEB_SEARCH_MODEL,
63
110
  input: [
64
111
  { role: "system", content: params.systemPrompt },
65
- { role: "user", content: params.query },
112
+ { role: "user", content: query },
66
113
  ],
67
- tools: [{ type: "web_search" }],
114
+ tools: [webSearchTool],
68
115
  reasoning: { effort: XAI_WEB_SEARCH_REASONING_EFFORT },
69
116
  };
70
117
 
@@ -8,6 +8,7 @@ import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth }
8
8
  import { isRecord } from "@oh-my-pi/pi-utils";
9
9
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
10
10
  import { SearchProviderError } from "../../../web/search/types";
11
+ import { formatQuery, parseSearchQuery, type QuerySyntax } from "../query";
11
12
  import { dateToAgeSeconds } from "../utils";
12
13
  import type { SearchParams } from "./base";
13
14
  import { SearchProvider } from "./base";
@@ -17,6 +18,22 @@ const ZAI_MCP_URL = "https://api.z.ai/api/mcp/web_search_prime/mcp";
17
18
  const ZAI_TOOL_NAME = "web_search_prime";
18
19
  const DEFAULT_NUM_RESULTS = 10;
19
20
 
21
+ /**
22
+ * webSearchPrime exposes no native filter args (the Web Search API's
23
+ * `search_domain_filter`/`search_recency_filter` are scoped to the
24
+ * `search_pro_jina` engine, not `search-prime`), but its Bing-flavored
25
+ * backend parses the common inline operators. Dates and language are left
26
+ * to the central lenient post-filter.
27
+ */
28
+ const ZAI_QUERY_SYNTAX: QuerySyntax = {
29
+ phrases: true,
30
+ negation: true,
31
+ site: true,
32
+ inTitle: true,
33
+ inUrl: true,
34
+ filetype: true,
35
+ };
36
+
20
37
  export interface ZaiSearchParams {
21
38
  query: string;
22
39
  num_results?: number;
@@ -407,8 +424,9 @@ export class ZaiProvider extends SearchProvider {
407
424
 
408
425
  search(params: SearchParams): Promise<SearchResponse> {
409
426
  const { fetch: fetchOverride } = params as ZaiProviderSearchParams;
427
+ const parsed = params.parsedQuery ?? parseSearchQuery(params.query);
410
428
  return searchZai({
411
- query: params.query,
429
+ query: parsed.hasDirectives ? formatQuery(parsed, ZAI_QUERY_SYNTAX) : params.query,
412
430
  num_results: params.numSearchResults ?? params.limit,
413
431
  signal: params.signal,
414
432
  authStorage: params.authStorage,