@f5xc-salesdemos/xcsh 17.2.0 → 17.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "17.
|
|
4
|
+
"version": "17.3.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
48
48
|
"@mozilla/readability": "^0.6",
|
|
49
|
-
"@f5xc-salesdemos/xcsh-stats": "17.
|
|
50
|
-
"@f5xc-salesdemos/pi-agent-core": "17.
|
|
51
|
-
"@f5xc-salesdemos/pi-ai": "17.
|
|
52
|
-
"@f5xc-salesdemos/pi-natives": "17.
|
|
53
|
-
"@f5xc-salesdemos/pi-tui": "17.
|
|
54
|
-
"@f5xc-salesdemos/pi-utils": "17.
|
|
49
|
+
"@f5xc-salesdemos/xcsh-stats": "17.3.0",
|
|
50
|
+
"@f5xc-salesdemos/pi-agent-core": "17.3.0",
|
|
51
|
+
"@f5xc-salesdemos/pi-ai": "17.3.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-natives": "17.3.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-tui": "17.3.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-utils": "17.3.0",
|
|
55
55
|
"@sinclair/typebox": "^0.34",
|
|
56
56
|
"@xterm/headless": "^6.0",
|
|
57
57
|
"ajv": "^8.18",
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Handles `xcsh q`/`xcsh web-search` subcommands for testing web search providers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { buildAnthropicSearchHeaders, buildAnthropicUrl, findAnthropicAuth } from "@f5xc-salesdemos/pi-ai";
|
|
8
|
+
import { $env, APP_NAME } from "@f5xc-salesdemos/pi-utils";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
import { initTheme, theme } from "../modes/theme/theme";
|
|
10
11
|
import { runSearchQuery, type SearchQueryParams } from "../web/search/index";
|
|
11
12
|
import { SEARCH_PROVIDER_ORDER } from "../web/search/provider";
|
|
12
13
|
import { renderSearchResult } from "../web/search/render";
|
|
13
|
-
import type { SearchProviderId } from "../web/search/types";
|
|
14
|
+
import type { SearchProviderId, SearchResponse } from "../web/search/types";
|
|
14
15
|
|
|
15
16
|
export interface SearchCommandArgs {
|
|
16
17
|
query: string;
|
|
@@ -18,6 +19,8 @@ export interface SearchCommandArgs {
|
|
|
18
19
|
recency?: "day" | "week" | "month" | "year";
|
|
19
20
|
limit?: number;
|
|
20
21
|
expanded: boolean;
|
|
22
|
+
synthesize: boolean;
|
|
23
|
+
synthesizeModel?: string;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
const PROVIDERS: Array<SearchProviderId | "auto"> = ["auto", ...SEARCH_PROVIDER_ORDER];
|
|
@@ -36,6 +39,7 @@ export function parseSearchArgs(args: string[]): SearchCommandArgs | undefined {
|
|
|
36
39
|
const result: SearchCommandArgs = {
|
|
37
40
|
query: "",
|
|
38
41
|
expanded: true,
|
|
42
|
+
synthesize: true,
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
const positional: string[] = [];
|
|
@@ -50,6 +54,10 @@ export function parseSearchArgs(args: string[]): SearchCommandArgs | undefined {
|
|
|
50
54
|
result.limit = Number.parseInt(args[++i], 10);
|
|
51
55
|
} else if (arg === "--compact") {
|
|
52
56
|
result.expanded = false;
|
|
57
|
+
} else if (arg === "--no-synthesize") {
|
|
58
|
+
result.synthesize = false;
|
|
59
|
+
} else if (arg === "--model") {
|
|
60
|
+
result.synthesizeModel = args[++i];
|
|
53
61
|
} else if (!arg.startsWith("-")) {
|
|
54
62
|
positional.push(arg);
|
|
55
63
|
}
|
|
@@ -62,6 +70,54 @@ export function parseSearchArgs(args: string[]): SearchCommandArgs | undefined {
|
|
|
62
70
|
return result;
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
async function synthesizeResponse(
|
|
74
|
+
query: string,
|
|
75
|
+
searchResponse: SearchResponse,
|
|
76
|
+
model?: string,
|
|
77
|
+
): Promise<string | undefined> {
|
|
78
|
+
const auth = await findAnthropicAuth();
|
|
79
|
+
if (!auth) return undefined;
|
|
80
|
+
|
|
81
|
+
const sourcesContext = searchResponse.sources
|
|
82
|
+
.map((s, i) => `[${i + 1}] ${s.title}\n ${s.url}${s.snippet ? `\n ${s.snippet}` : ""}`)
|
|
83
|
+
.join("\n");
|
|
84
|
+
|
|
85
|
+
const citationsContext =
|
|
86
|
+
searchResponse.citations?.map((c, i) => `[${i + 1}] ${c.title}: "${c.citedText}"`).join("\n") ?? "";
|
|
87
|
+
|
|
88
|
+
const synthesisPrompt = `Based on the following web search results for the query "${query}", provide a comprehensive, well-structured answer. Cite sources by number.
|
|
89
|
+
|
|
90
|
+
## Search Results
|
|
91
|
+
${searchResponse.answer ? `### Initial Answer\n${searchResponse.answer}\n` : ""}
|
|
92
|
+
### Sources
|
|
93
|
+
${sourcesContext}
|
|
94
|
+
${citationsContext ? `\n### Citations\n${citationsContext}` : ""}
|
|
95
|
+
|
|
96
|
+
Provide a thorough answer with key facts, numbers, and source citations.`;
|
|
97
|
+
|
|
98
|
+
const url = buildAnthropicUrl(auth);
|
|
99
|
+
const headers = buildAnthropicSearchHeaders(auth);
|
|
100
|
+
const selectedModel = model ?? $env.ANTHROPIC_SEARCH_MODEL ?? "claude-haiku-4-5";
|
|
101
|
+
|
|
102
|
+
const response = await fetch(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers,
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
model: selectedModel,
|
|
107
|
+
max_tokens: 4096,
|
|
108
|
+
messages: [{ role: "user", content: synthesisPrompt }],
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!response.ok) return undefined;
|
|
113
|
+
|
|
114
|
+
const data = (await response.json()) as { content?: Array<{ type: string; text?: string }> };
|
|
115
|
+
return data.content
|
|
116
|
+
?.filter(b => b.type === "text" && b.text)
|
|
117
|
+
.map(b => b.text)
|
|
118
|
+
.join("\n\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
65
121
|
export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
66
122
|
if (!cmd.query) {
|
|
67
123
|
process.stderr.write(`${chalk.red("Error: Query is required")}\n`);
|
|
@@ -104,6 +160,18 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
104
160
|
const width = Math.max(60, process.stdout.columns ?? 100);
|
|
105
161
|
process.stdout.write(`${component.render(width).join("\n")}\n`);
|
|
106
162
|
|
|
163
|
+
if (cmd.synthesize && result.details?.response && result.details.response.sources.length > 0) {
|
|
164
|
+
try {
|
|
165
|
+
process.stderr.write(chalk.dim("\nSynthesizing response...\n"));
|
|
166
|
+
const synthesized = await synthesizeResponse(cmd.query, result.details.response, cmd.synthesizeModel);
|
|
167
|
+
if (synthesized) {
|
|
168
|
+
process.stdout.write(`\n${chalk.bold("Synthesized Answer:")}\n\n${synthesized}\n`);
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
process.stderr.write(chalk.dim("Synthesis unavailable — showing raw search results only.\n"));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
107
175
|
if (result.details?.error) {
|
|
108
176
|
process.exitCode = 1;
|
|
109
177
|
}
|
|
@@ -124,6 +192,8 @@ ${chalk.bold("Options:")}
|
|
|
124
192
|
--recency <value> Recency filter (Brave/Perplexity): ${RECENCY_OPTIONS.join(", ")}
|
|
125
193
|
-l, --limit <n> Max results to return
|
|
126
194
|
--compact Render condensed output
|
|
195
|
+
--no-synthesize Skip synthesis step (raw search only)
|
|
196
|
+
--model <name> Model for synthesis (default: ANTHROPIC_SEARCH_MODEL or claude-haiku-4-5)
|
|
127
197
|
-h, --help Show this help
|
|
128
198
|
|
|
129
199
|
${chalk.bold("Examples:")}
|
|
@@ -23,6 +23,8 @@ export default class Search extends Command {
|
|
|
23
23
|
recency: Flags.string({ description: "Recency filter", options: RECENCY }),
|
|
24
24
|
limit: Flags.integer({ char: "l", description: "Max results to return" }),
|
|
25
25
|
compact: Flags.boolean({ description: "Render condensed output" }),
|
|
26
|
+
"no-synthesize": Flags.boolean({ description: "Skip synthesis step (raw search only)" }),
|
|
27
|
+
model: Flags.string({ description: "Model for synthesis (default: claude-haiku-4-5)" }),
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
async run(): Promise<void> {
|
|
@@ -35,6 +37,8 @@ export default class Search extends Command {
|
|
|
35
37
|
recency: flags.recency as SearchCommandArgs["recency"],
|
|
36
38
|
limit: flags.limit,
|
|
37
39
|
expanded: !flags.compact,
|
|
40
|
+
synthesize: !flags["no-synthesize"],
|
|
41
|
+
synthesizeModel: flags.model,
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
await runSearchCommand(cmd);
|
package/src/web/search/index.ts
CHANGED
|
@@ -168,9 +168,10 @@ async function executeSearch(
|
|
|
168
168
|
_toolCallId: string,
|
|
169
169
|
params: SearchQueryParams,
|
|
170
170
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
171
|
-
const
|
|
171
|
+
const hasAnthropicOnlyParams =
|
|
172
|
+
params.allowed_domains?.length || params.blocked_domains?.length || params.max_uses || params.user_location;
|
|
172
173
|
const effectiveProvider =
|
|
173
|
-
|
|
174
|
+
hasAnthropicOnlyParams && (!params.provider || params.provider === "auto") ? "anthropic" : params.provider;
|
|
174
175
|
const providers =
|
|
175
176
|
effectiveProvider && effectiveProvider !== "auto"
|
|
176
177
|
? (await getSearchProvider(effectiveProvider).isAvailable())
|
|
@@ -27,6 +27,7 @@ import { SearchProvider } from "./base";
|
|
|
27
27
|
|
|
28
28
|
const DEFAULT_MODEL = "claude-haiku-4-5";
|
|
29
29
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
30
|
+
const DEFAULT_MAX_USES = 8;
|
|
30
31
|
const WEB_SEARCH_TOOL_NAME = "web_search";
|
|
31
32
|
const WEB_SEARCH_TOOL_TYPE = "web_search_20250305";
|
|
32
33
|
|
|
@@ -132,7 +133,7 @@ async function callSearch(
|
|
|
132
133
|
};
|
|
133
134
|
if (toolConfig?.allowed_domains?.length) tool.allowed_domains = toolConfig.allowed_domains;
|
|
134
135
|
if (toolConfig?.blocked_domains?.length) tool.blocked_domains = toolConfig.blocked_domains;
|
|
135
|
-
|
|
136
|
+
tool.max_uses = toolConfig?.max_uses ?? DEFAULT_MAX_USES;
|
|
136
137
|
if (toolConfig?.user_location) tool.user_location = toolConfig.user_location;
|
|
137
138
|
|
|
138
139
|
const body: Record<string, unknown> = {
|