@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.
- package/CHANGELOG.md +16 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +14 -9
- package/src/cli/web-search-cli.ts +2 -0
- package/src/commands/web-search.ts +1 -0
- package/src/config/model-resolver.ts +102 -3
- package/src/config/settings-schema.ts +1 -1
- package/src/extensibility/extensions/index.ts +9 -0
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/extensions/types.ts +68 -5
- package/src/extensibility/plugins/git-url.ts +18 -7
- package/src/main.ts +45 -20
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/settings-defs.ts +7 -1
- package/src/modes/controllers/extension-ui-controller.ts +22 -0
- package/src/modes/controllers/selector-controller.ts +3 -1
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/rpc/rpc-mode.ts +5 -0
- package/src/prompts/system/system-prompt.md +46 -26
- package/src/session/agent-session.ts +84 -14
- package/src/task/render.ts +1 -1
- package/src/web/search/index.ts +15 -5
- package/src/web/search/provider.ts +3 -1
- package/src/web/search/providers/zai.ts +352 -0
- package/src/web/search/types.ts +1 -1
|
@@ -146,10 +146,16 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
146
146
|
],
|
|
147
147
|
// Provider options
|
|
148
148
|
"providers.webSearch": [
|
|
149
|
-
{
|
|
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(
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
{{#
|
|
116
|
-
-
|
|
117
|
-
{{/has}}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
**If
|
|
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
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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()
|
|
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
|
|
package/src/task/render.ts
CHANGED
|
@@ -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
|
|
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(
|
package/src/web/search/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Web Search Tool
|
|
3
3
|
*
|
|
4
|
-
* Single tool supporting Anthropic, Perplexity, Exa, Jina, Gemini, and
|
|
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 =
|
|
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
|
|
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];
|