@oh-my-pi/pi-coding-agent 12.6.0 → 12.7.1

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.
@@ -146,10 +146,16 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
146
146
  ],
147
147
  // Provider options
148
148
  "providers.webSearch": [
149
- { value: "auto", label: "Auto", description: "Priority: Exa > Perplexity > Anthropic" },
149
+ {
150
+ value: "auto",
151
+ label: "Auto",
152
+ description: "Priority: Exa > Jina > Perplexity > Anthropic > Gemini > Codex > Z.AI",
153
+ },
150
154
  { value: "exa", label: "Exa", description: "Requires EXA_API_KEY" },
155
+ { value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
151
156
  { value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_API_KEY" },
152
157
  { value: "anthropic", label: "Anthropic", description: "Uses Anthropic web search" },
158
+ { value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
153
159
  ],
154
160
  "providers.image": [
155
161
  { value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
@@ -9,6 +9,7 @@ import type {
9
9
  ExtensionError,
10
10
  ExtensionUIContext,
11
11
  ExtensionUIDialogOptions,
12
+ TerminalInputHandler,
12
13
  } from "../../extensibility/extensions";
13
14
  import { HookEditorComponent } from "../../modes/components/hook-editor";
14
15
  import { HookInputComponent } from "../../modes/components/hook-input";
@@ -20,6 +21,7 @@ import { setTerminalTitle } from "../../utils/title-generator";
20
21
  export class ExtensionUiController {
21
22
  #hookSelectorOverlay: OverlayHandle | undefined;
22
23
  #hookInputOverlay: OverlayHandle | undefined;
24
+ #extensionTerminalInputUnsubscribers = new Set<() => void>();
23
25
 
24
26
  readonly #dialogOverlayOptions = {
25
27
  anchor: "bottom-center",
@@ -41,6 +43,7 @@ export class ExtensionUiController {
41
43
  confirm: (title, message, _dialogOptions) => this.showHookConfirm(title, message),
42
44
  input: (title, placeholder, _dialogOptions) => this.showHookInput(title, placeholder),
43
45
  notify: (message, type) => this.showHookNotify(message, type),
46
+ onTerminalInput: handler => this.addExtensionTerminalInputListener(handler),
44
47
  setStatus: (key, text) => this.setHookStatus(key, text),
45
48
  setWorkingMessage: message => this.ctx.setWorkingMessage(message),
46
49
  setWidget: (key, content) => this.setHookWidget(key, content),
@@ -157,6 +160,7 @@ export class ExtensionUiController {
157
160
  this.ctx.statusContainer.clear();
158
161
 
159
162
  // Create new session
163
+ this.clearExtensionTerminalInputListeners();
160
164
  const success = await this.ctx.session.newSession({ parentSession: options?.parentSession });
161
165
  if (!success) {
162
166
  return { cancelled: true };
@@ -351,6 +355,7 @@ export class ExtensionUiController {
351
355
  this.ctx.statusContainer.clear();
352
356
 
353
357
  // Create new session
358
+ this.clearExtensionTerminalInputListeners();
354
359
  const success = await this.ctx.session.newSession({ parentSession: options?.parentSession });
355
360
  if (!success) {
356
361
  return { cancelled: true };
@@ -450,6 +455,7 @@ export class ExtensionUiController {
450
455
  confirm: async (_title: string, _message: string, _dialogOptions) => false,
451
456
  input: async (_title: string, _placeholder?: string, _dialogOptions?: unknown) => undefined,
452
457
  notify: () => {},
458
+ onTerminalInput: () => () => {},
453
459
  setStatus: () => {},
454
460
  setWorkingMessage: () => {},
455
461
  setWidget: () => {},
@@ -726,6 +732,22 @@ export class ExtensionUiController {
726
732
  /**
727
733
  * Show an extension error in the UI.
728
734
  */
735
+ addExtensionTerminalInputListener(handler: TerminalInputHandler): () => void {
736
+ const unsubscribe = this.ctx.ui.addInputListener(handler);
737
+ this.#extensionTerminalInputUnsubscribers.add(unsubscribe);
738
+ return () => {
739
+ unsubscribe();
740
+ this.#extensionTerminalInputUnsubscribers.delete(unsubscribe);
741
+ };
742
+ }
743
+
744
+ clearExtensionTerminalInputListeners(): void {
745
+ for (const unsubscribe of this.#extensionTerminalInputUnsubscribers) {
746
+ unsubscribe();
747
+ }
748
+ this.#extensionTerminalInputUnsubscribers.clear();
749
+ }
750
+
729
751
  showExtensionError(extensionPath: string, error: string): void {
730
752
  const errorText = new Text(theme.fg("error", `Extension "${extensionPath}" error: ${error}`), 1, 0);
731
753
  this.ctx.chatContainer.addChild(errorText);
@@ -279,7 +279,9 @@ export class SelectorController {
279
279
 
280
280
  // Provider settings - update runtime preferences
281
281
  case "webSearchProvider":
282
- setPreferredSearchProvider(value as "auto" | "exa" | "perplexity" | "anthropic");
282
+ setPreferredSearchProvider(
283
+ value as "auto" | "exa" | "jina" | "zai" | "perplexity" | "anthropic" | "gemini" | "codex",
284
+ );
283
285
  break;
284
286
  case "imageProvider":
285
287
  setPreferredImageProvider(value as "auto" | "gemini" | "openrouter");
@@ -720,6 +720,7 @@ export class InteractiveMode implements InteractiveModeContext {
720
720
  this.#sttController.dispose();
721
721
  this.#sttController = undefined;
722
722
  }
723
+ this.#extensionUiController.clearExtensionTerminalInputListeners();
723
724
  this.statusLine.dispose();
724
725
  if (this.unsubscribe) {
725
726
  this.unsubscribe();
@@ -919,6 +920,7 @@ export class InteractiveMode implements InteractiveModeContext {
919
920
  }
920
921
 
921
922
  handleClearCommand(): Promise<void> {
923
+ this.#extensionUiController.clearExtensionTerminalInputListeners();
922
924
  return this.#commandController.handleClearCommand();
923
925
  }
924
926
 
@@ -164,6 +164,11 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
164
164
  );
165
165
  }
166
166
 
167
+ onTerminalInput(): () => void {
168
+ // Raw terminal input not supported in RPC mode
169
+ return () => {};
170
+ }
171
+
167
172
  notify(message: string, type?: "info" | "warning" | "error"): void {
168
173
  // Fire and forget - no response needed
169
174
  this.output({
@@ -110,31 +110,43 @@ Don't open a file hoping. Hope is not a strategy.
110
110
 
111
111
  <procedure>
112
112
  ## Execution
113
- **Step 0 — CHECKPOINT** (mandatory for multi-step, multi-file, or ambiguous tasks):
114
- - Distinct work streams? Dependencies between them?
115
- {{#has tools "task"}}
116
- - Parallelizable via Task tool, or necessarily sequential?
117
- {{/has}}
118
- {{#if skills.length}}
119
- - Skill matches domain? Read it first.
120
- {{/if}}
121
- {{#if rules.length}}
122
- - Applicable rule? Read it first.
123
- {{/if}}
124
- - Skip only when: single-file, ≤3 edits, requirements explicit.
125
- **Plan** if task has weight: 3–7 bullets, no more.
126
- **After each tool call**: interpret result → decide next action → execute. No echoing output.
127
- **If blocked**: exhaust tools/context/files first. Only then ask — minimum viable question.
128
- **If requested change includes refactor**: remove now-unused elements. Note removals.
113
+
114
+ **Assess scope first.**
115
+ {{#if skills.length}}- If a skill matches the domain, read it before starting.{{/if}}
116
+ {{#if rules.length}}- If an applicable rule exists, read it before starting.{{/if}}
117
+ {{#has tools "task"}}- Consider if the task is parallelizable via Task tool? Make a conflict-free plan to delegate to subagents if possible.{{/has}}
118
+ - If the task is multi-file or ambiguous, write a 3–7 bullet plan.
119
+
120
+ **Do the work.**
121
+ Every turn must include at least one tool call that advances the deliverable (edit, write, run, delegate, etc.). Planning and tracking do not count.
122
+
123
+ **After each tool call**:
124
+ - Interpret result
125
+ - Decide next action
126
+ - Execute. No echoing output.
127
+
128
+ **If blocked**:
129
+ - Exhaust tools/context/files first.
130
+ - Only then ask — minimum viable question.
131
+
132
+ **If requested change includes refactor**:
133
+ Cleanup dead code and unused elements, do not yield before the codebase is pristine.
129
134
 
130
135
  {{#has tools "todo_write"}}
131
136
  ### Task Tracking
132
- - Use `todo_write` proactively for non-trivial, multi-step work so progress stays visible.
133
- - Initialize todos before implementation for complex tasks, then keep them current while working.
134
- - Mark todo items complete immediately after finishing them; do not batch completion updates.
135
- - Keep todo items as focused logical units (one coherent outcome per item); split broad work into smaller items.
136
- - Keep exactly one item `in_progress` at a time and complete in order unless requirements change.
137
- - Skip `todo_write` for single trivial or purely informational requests.
137
+ - Never create a todo list and then stop. A turn that contains only todo updates is a failed turn.
138
+ - Use todos as you make progress to make multi-step progress visible, don't batch.
139
+ - Skip entirely for single-step or trivial requests.
140
+ {{/has}}
141
+
142
+ {{#has tools "task"}}
143
+ ### Parallel Execution
144
+ Use the Task tool when work genuinely forks into independent streams:
145
+ - Editing 4+ files with no dependencies between edits
146
+ - Investigating 2+ independent subsystems
147
+ - Work that decomposes into pieces not needing each other's results
148
+
149
+ Task tool is for **parallel execution**, not deferred execution. If you can do it now, do it now. Sequential is fine when steps depend on each other — don't parallelize for its own sake.
138
150
  {{/has}}
139
151
 
140
152
  ### Verification
@@ -244,10 +256,10 @@ Sequential work requires justification. If you cannot articulate why B depends o
244
256
  {{/has}}
245
257
 
246
258
  <output_style>
247
- - No explanatory scaffolding. No summary closings ("In summary…").
248
- - No filler. No emojis. No ceremony.
249
- - User execution-mode instructions (do-it-yourself vs delegate) override tool-use defaults.
259
+ - State intent before tool calls in one sentence.
260
+ - No summary closings ("In summary…"). No filler. No emojis. No ceremony.
250
261
  - Suppress: "genuinely", "honestly", "straightforward".
262
+ - User execution-mode instructions (do-it-yourself vs delegate) override tool-use defaults.
251
263
  - Requirements conflict or are unclear → ask only after exhausting exploration.
252
264
  </output_style>
253
265
 
@@ -256,6 +268,8 @@ Complete the full request before yielding. Use tools for verifiable facts. Resul
256
268
 
257
269
  You have unlimited stamina; the user does not. Persist on hard problems. Don't burn their energy on problems you failed to think through.
258
270
 
271
+ This matters. Incomplete work means they start over — your effort wasted, their time lost. The person waiting deserves your best work.
272
+
259
273
  Tests you didn't write: bugs shipped. Assumptions you didn't state: incidents to debug. Edge cases you didn't name: pages at 3am.
260
274
 
261
275
  Write what you can defend.
@@ -270,9 +284,15 @@ The person waiting deserves to receive it.
270
284
  User works in a high-reliability industry—defense, finance, healthcare, infrastructure—where bugs have material impact on people's lives, even death.
271
285
  </stakes>
272
286
 
287
+ <prime_directive>
288
+ **GET THE WORK DONE.**
289
+ Everything else is subordinate to producing the requested output. If you find yourself emitting metadata (todos, plans, status updates) without having made a single edit or produced a single artifact, you have failed.
290
+ </prime_directive>
291
+
273
292
  <critical>
274
293
  Keep going until finished.
275
- - Quote only needed; rest noise.
294
+ - Every turn must advance the deliverable. Metadata-only turns are failures.
295
+ - Quote only what's needed; rest is noise.
276
296
  - Don't claim unverified correctness.
277
297
  - Do not ask when it may be obtained from available tools or repo context/files.
278
298
  - Touch only requested; no incidental refactors/cleanup.
@@ -51,10 +51,16 @@ import type {
51
51
  ExtensionCommandContext,
52
52
  ExtensionRunner,
53
53
  ExtensionUIContext,
54
+ MessageEndEvent,
55
+ MessageStartEvent,
56
+ MessageUpdateEvent,
54
57
  SessionBeforeBranchResult,
55
58
  SessionBeforeCompactResult,
56
59
  SessionBeforeSwitchResult,
57
60
  SessionBeforeTreeResult,
61
+ ToolExecutionEndEvent,
62
+ ToolExecutionStartEvent,
63
+ ToolExecutionUpdateEvent,
58
64
  TreePreparation,
59
65
  TurnEndEvent,
60
66
  TurnStartEvent,
@@ -102,6 +108,7 @@ import {
102
108
  pythonExecutionToText,
103
109
  } from "./messages";
104
110
  import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionManager } from "./session-manager";
111
+ import { getLatestCompactionEntry } from "./session-manager";
105
112
 
106
113
  /** Session-specific events that extend the core AgentEvent */
107
114
  export type AgentSessionEvent =
@@ -226,6 +233,7 @@ const noOpUIContext: ExtensionUIContext = {
226
233
  confirm: async (_title, _message, _dialogOptions) => false,
227
234
  input: async (_title, _placeholder, _dialogOptions) => undefined,
228
235
  notify: () => {},
236
+ onTerminalInput: () => () => {},
229
237
  setStatus: () => {},
230
238
  setWorkingMessage: () => {},
231
239
  setWidget: () => {},
@@ -874,6 +882,51 @@ export class AgentSession {
874
882
  };
875
883
  await this.#extensionRunner.emit(hookEvent);
876
884
  this.#turnIndex++;
885
+ } else if (event.type === "message_start") {
886
+ const extensionEvent: MessageStartEvent = {
887
+ type: "message_start",
888
+ message: event.message,
889
+ };
890
+ await this.#extensionRunner.emit(extensionEvent);
891
+ } else if (event.type === "message_update") {
892
+ const extensionEvent: MessageUpdateEvent = {
893
+ type: "message_update",
894
+ message: event.message,
895
+ assistantMessageEvent: event.assistantMessageEvent,
896
+ };
897
+ await this.#extensionRunner.emit(extensionEvent);
898
+ } else if (event.type === "message_end") {
899
+ const extensionEvent: MessageEndEvent = {
900
+ type: "message_end",
901
+ message: event.message,
902
+ };
903
+ await this.#extensionRunner.emit(extensionEvent);
904
+ } else if (event.type === "tool_execution_start") {
905
+ const extensionEvent: ToolExecutionStartEvent = {
906
+ type: "tool_execution_start",
907
+ toolCallId: event.toolCallId,
908
+ toolName: event.toolName,
909
+ args: event.args,
910
+ };
911
+ await this.#extensionRunner.emit(extensionEvent);
912
+ } else if (event.type === "tool_execution_update") {
913
+ const extensionEvent: ToolExecutionUpdateEvent = {
914
+ type: "tool_execution_update",
915
+ toolCallId: event.toolCallId,
916
+ toolName: event.toolName,
917
+ args: event.args,
918
+ partialResult: event.partialResult,
919
+ };
920
+ await this.#extensionRunner.emit(extensionEvent);
921
+ } else if (event.type === "tool_execution_end") {
922
+ const extensionEvent: ToolExecutionEndEvent = {
923
+ type: "tool_execution_end",
924
+ toolCallId: event.toolCallId,
925
+ toolName: event.toolName,
926
+ result: event.result,
927
+ isError: event.isError ?? false,
928
+ };
929
+ await this.#extensionRunner.emit(extensionEvent);
877
930
  } else if (event.type === "auto_compaction_start") {
878
931
  await this.#extensionRunner.emit({ type: "auto_compaction_start", reason: event.reason });
879
932
  } else if (event.type === "auto_compaction_end") {
@@ -2664,9 +2717,9 @@ Be thorough - include exact file paths, function names, error messages, and tech
2664
2717
  // The error shouldn't trigger another compaction since we already compacted.
2665
2718
  // Example: opus fails → switch to codex → compact → switch back to opus → opus error
2666
2719
  // is still in context but shouldn't trigger compaction again.
2667
- const compactionEntry = this.sessionManager.getBranch().find(e => e.type === "compaction");
2720
+ const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
2668
2721
  const errorIsFromBeforeCompaction =
2669
- compactionEntry && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
2722
+ compactionEntry !== null && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
2670
2723
 
2671
2724
  // Case 1: Overflow - LLM returned context overflow error
2672
2725
  if (sameModel && !errorIsFromBeforeCompaction && isContextOverflow(assistantMessage, contextWindow)) {
@@ -3959,6 +4012,35 @@ Be thorough - include exact file paths, function names, error messages, and tech
3959
4012
  const contextWindow = model.contextWindow ?? 0;
3960
4013
  if (contextWindow <= 0) return undefined;
3961
4014
 
4015
+ // After compaction, the last assistant usage reflects pre-compaction context size.
4016
+ // We can only trust usage from an assistant that responded after the latest compaction.
4017
+ // If no such assistant exists, context token count is unknown until the next LLM response.
4018
+ const branchEntries = this.sessionManager.getBranch();
4019
+ const latestCompaction = getLatestCompactionEntry(branchEntries);
4020
+
4021
+ if (latestCompaction) {
4022
+ // Check if there's a valid assistant usage after the compaction boundary
4023
+ const compactionIndex = branchEntries.lastIndexOf(latestCompaction);
4024
+ let hasPostCompactionUsage = false;
4025
+ for (let i = branchEntries.length - 1; i > compactionIndex; i--) {
4026
+ const entry = branchEntries[i];
4027
+ if (entry.type === "message" && entry.message.role === "assistant") {
4028
+ const assistant = entry.message;
4029
+ if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error") {
4030
+ const contextTokens = calculateContextTokens(assistant.usage);
4031
+ if (contextTokens > 0) {
4032
+ hasPostCompactionUsage = true;
4033
+ }
4034
+ break;
4035
+ }
4036
+ }
4037
+ }
4038
+
4039
+ if (!hasPostCompactionUsage) {
4040
+ return { tokens: null, contextWindow, percent: null };
4041
+ }
4042
+ }
4043
+
3962
4044
  const estimate = this.#estimateContextTokens();
3963
4045
  const percent = (estimate.tokens / contextWindow) * 100;
3964
4046
 
@@ -3966,9 +4048,6 @@ Be thorough - include exact file paths, function names, error messages, and tech
3966
4048
  tokens: estimate.tokens,
3967
4049
  contextWindow,
3968
4050
  percent,
3969
- usageTokens: estimate.usageTokens,
3970
- trailingTokens: estimate.trailingTokens,
3971
- lastUsageIndex: estimate.lastUsageIndex,
3972
4051
  };
3973
4052
  }
3974
4053
 
@@ -3985,9 +4064,6 @@ Be thorough - include exact file paths, function names, error messages, and tech
3985
4064
  */
3986
4065
  #estimateContextTokens(): {
3987
4066
  tokens: number;
3988
- usageTokens: number;
3989
- trailingTokens: number;
3990
- lastUsageIndex: number | null;
3991
4067
  } {
3992
4068
  const messages = this.messages;
3993
4069
 
@@ -4014,9 +4090,6 @@ Be thorough - include exact file paths, function names, error messages, and tech
4014
4090
  }
4015
4091
  return {
4016
4092
  tokens: estimated,
4017
- usageTokens: 0,
4018
- trailingTokens: estimated,
4019
- lastUsageIndex: null,
4020
4093
  };
4021
4094
  }
4022
4095
 
@@ -4028,9 +4101,6 @@ Be thorough - include exact file paths, function names, error messages, and tech
4028
4101
 
4029
4102
  return {
4030
4103
  tokens: usageTokens + trailingTokens,
4031
- usageTokens,
4032
- trailingTokens,
4033
- lastUsageIndex,
4034
4104
  };
4035
4105
  }
4036
4106
 
@@ -684,7 +684,7 @@ function renderFindings(
684
684
  const findingContinue = isLastFinding ? " " : `${theme.tree.vertical} `;
685
685
 
686
686
  const { color } = getPriorityInfo(finding.priority);
687
- const titleText = finding.title.replace(/^\[P\d\]\s*/, "");
687
+ const titleText = finding.title?.replace(/^\[P\d\]\s*/, "") ?? "Untitled";
688
688
  const loc = `${path.basename(finding.file_path)}:${finding.line_start}`;
689
689
 
690
690
  lines.push(
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unified Web Search Tool
3
3
  *
4
- * Single tool supporting Anthropic, Perplexity, Exa, Jina, Gemini, and Codex
4
+ * Single tool supporting Anthropic, Perplexity, Exa, Jina, Gemini, Codex, and Z.AI
5
5
  * providers with provider-specific parameters exposed conditionally.
6
6
  *
7
7
  * When EXA_API_KEY is available, additional specialized tools are exposed:
@@ -33,7 +33,7 @@ import { SearchProviderError } from "./types";
33
33
  export const webSearchSchema = Type.Object({
34
34
  query: Type.String({ description: "Search query" }),
35
35
  provider: Type.Optional(
36
- StringEnum(["auto", "exa", "jina", "anthropic", "perplexity", "gemini", "codex"], {
36
+ StringEnum(["auto", "exa", "jina", "zai", "anthropic", "perplexity", "gemini", "codex"], {
37
37
  description: "Search provider (default: auto)",
38
38
  }),
39
39
  ),
@@ -47,7 +47,7 @@ export const webSearchSchema = Type.Object({
47
47
 
48
48
  export type SearchParams = {
49
49
  query: string;
50
- provider?: "auto" | "exa" | "jina" | "anthropic" | "perplexity" | "gemini" | "codex";
50
+ provider?: "auto" | "exa" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex";
51
51
  recency?: "day" | "week" | "month" | "year";
52
52
  limit?: number;
53
53
  /** Maximum output tokens. Defaults to 4096. */
@@ -56,6 +56,8 @@ export type SearchParams = {
56
56
  temperature?: number;
57
57
  /** Number of search results to retrieve. Defaults to 10. */
58
58
  num_search_results?: number;
59
+ /** Disable provider fallback when explicit provider is selected (CLI/debug use). */
60
+ no_fallback?: boolean;
59
61
  };
60
62
 
61
63
  function formatProviderList(providers: SearchProvider[]): string {
@@ -68,6 +70,9 @@ function formatProviderError(error: unknown, provider: SearchProvider): string {
68
70
  return "Anthropic web search returned 404 (model or endpoint not found).";
69
71
  }
70
72
  if (error.status === 401 || error.status === 403) {
73
+ if (error.provider === "zai") {
74
+ return error.message;
75
+ }
71
76
  return `${getSearchProvider(error.provider).label} authorization failed (${error.status}). Check API key or base URL.`;
72
77
  }
73
78
  return error.message;
@@ -165,7 +170,12 @@ async function executeSearch(
165
170
  _toolCallId: string,
166
171
  params: SearchParams,
167
172
  ): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
168
- const providers = await resolveProviderChain(params.provider);
173
+ const providers =
174
+ params.provider && params.provider !== "auto" && params.no_fallback
175
+ ? (await getSearchProvider(params.provider).isAvailable())
176
+ ? [getSearchProvider(params.provider)]
177
+ : []
178
+ : await resolveProviderChain(params.provider);
169
179
 
170
180
  if (providers.length === 0) {
171
181
  const message = "No web search provider configured.";
@@ -226,7 +236,7 @@ export async function runSearchQuery(
226
236
  /**
227
237
  * Web search tool implementation.
228
238
  *
229
- * Supports Anthropic, Perplexity, Exa, Jina, Gemini, and Codex providers with automatic fallback.
239
+ * Supports Anthropic, Perplexity, Exa, Jina, Gemini, Codex, and Z.AI providers with automatic fallback.
230
240
  * Session is accepted for interface consistency but not used.
231
241
  */
232
242
  export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
@@ -5,6 +5,7 @@ import { ExaProvider } from "./providers/exa";
5
5
  import { GeminiProvider } from "./providers/gemini";
6
6
  import { JinaProvider } from "./providers/jina";
7
7
  import { PerplexityProvider } from "./providers/perplexity";
8
+ import { ZaiProvider } from "./providers/zai";
8
9
  import type { SearchProviderId } from "./types";
9
10
 
10
11
  export type { SearchParams } from "./providers/base";
@@ -14,12 +15,13 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
14
15
  exa: new ExaProvider(),
15
16
  jina: new JinaProvider(),
16
17
  perplexity: new PerplexityProvider(),
18
+ zai: new ZaiProvider(),
17
19
  anthropic: new AnthropicProvider(),
18
20
  gemini: new GeminiProvider(),
19
21
  codex: new CodexProvider(),
20
22
  } as const;
21
23
 
22
- const SEARCH_PROVIDER_ORDER: SearchProviderId[] = ["exa", "jina", "perplexity", "anthropic", "gemini", "codex"];
24
+ const SEARCH_PROVIDER_ORDER: SearchProviderId[] = ["exa", "jina", "perplexity", "anthropic", "gemini", "codex", "zai"];
23
25
 
24
26
  export function getSearchProvider(provider: SearchProviderId): SearchProvider {
25
27
  return SEARCH_PROVIDERS[provider];