@f5-sales-demo/xcsh 23.0.2 → 23.0.3

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.
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "23.0.2",
21
- "commit": "aa278eecbd560345b7571f285d77c88608b2cf01",
22
- "shortCommit": "aa278ee",
20
+ "version": "23.0.3",
21
+ "commit": "a1b667ce0f3621987ad2be76de041c08185614ee",
22
+ "shortCommit": "a1b667c",
23
23
  "branch": "main",
24
- "tag": "v23.0.2",
25
- "commitDate": "2026-10-08T14:30:28Z",
26
- "buildDate": "2026-10-08T15:12:38.904Z",
24
+ "tag": "v23.0.3",
25
+ "commitDate": "2026-10-08T19:11:22Z",
26
+ "buildDate": "2026-10-08T19:50:26.569Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5-sales-demo/xcsh",
30
30
  "repoSlug": "f5-sales-demo/xcsh",
31
- "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/aa278eecbd560345b7571f285d77c88608b2cf01",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v23.0.2"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/a1b667ce0f3621987ad2be76de041c08185614ee",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v23.0.3"
33
33
  };
@@ -634,6 +634,9 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
634
634
  queuedMessageCount: session.queuedMessageCount,
635
635
  todoPhases: session.getTodoPhases(),
636
636
  systemPrompt: session.systemPrompt,
637
+ registeredToolNames: session.getRegisteredToolNames(),
638
+ activeToolNames: session.getActiveToolNames(),
639
+ toolChoiceState: session.toolChoiceQueue.snapshot(),
637
640
  dumpTools: session.agent.state.tools.map(tool => ({
638
641
  name: tool.name,
639
642
  description: tool.description,
@@ -136,6 +136,10 @@ export interface RpcSessionState {
136
136
  todoPhases: TodoPhase[];
137
137
  /** For session dump / export (plain-text parity with /dump). */
138
138
  systemPrompt?: string;
139
+ /** Read-only execution diagnostics; no credentials, inputs, or callback state. */
140
+ registeredToolNames?: string[];
141
+ activeToolNames?: string[];
142
+ toolChoiceState?: ReturnType<import("../../session/tool-choice-queue").ToolChoiceQueue["snapshot"]>;
139
143
  dumpTools?: Array<{ name: string; description: string; parameters: unknown }>;
140
144
  }
141
145
 
@@ -3149,6 +3149,11 @@ export class AgentSession {
3149
3149
  return this.agent.state.tools.map(t => t.name);
3150
3150
  }
3151
3151
 
3152
+ /** Complete registry names, including dormant tools, without tool payloads. */
3153
+ getRegisteredToolNames(): string[] {
3154
+ return [...this.#toolRegistry.keys()];
3155
+ }
3156
+
3152
3157
  /** Numeric-only prompt/tool/provider-call profile. Raw context is never retained. */
3153
3158
  getContextProfile(): ContextProfile {
3154
3159
  this.#contextProfileCollector.setPrompt(this.systemPrompt, this.agent.state.tools);
@@ -206,6 +206,21 @@ export class ToolChoiceQueue {
206
206
  return label;
207
207
  }
208
208
 
209
+ /** Read-only diagnostics. Never advance an iterator or invoke callbacks. */
210
+ snapshot(): { queuedLabels: string[]; inFlight?: { label: string; choice: ToolChoice } } {
211
+ return {
212
+ queuedLabels: [...this.inspect()],
213
+ ...(this.#inFlight
214
+ ? {
215
+ inFlight: {
216
+ label: this.#inFlight.directive.label,
217
+ choice: structuredClone(this.#inFlight.yielded),
218
+ },
219
+ }
220
+ : {}),
221
+ };
222
+ }
223
+
209
224
  /** For tests/debug: labels of currently queued directives in order. */
210
225
  inspect(): readonly string[] {
211
226
  return this.#queue.map(d => d.label);
@@ -6,11 +6,12 @@
6
6
  *
7
7
  */
8
8
 
9
- import type {
10
- AgentTool,
11
- AgentToolContext,
12
- AgentToolResult,
13
- AgentToolUpdateCallback,
9
+ import {
10
+ type AgentTool,
11
+ type AgentToolContext,
12
+ AgentToolError,
13
+ type AgentToolResult,
14
+ type AgentToolUpdateCallback,
14
15
  } from "@f5-sales-demo/pi-agent-core";
15
16
  import { StringEnum } from "@f5-sales-demo/pi-ai";
16
17
  import { prompt } from "@f5-sales-demo/pi-utils";
@@ -185,13 +186,16 @@ function formatForLLM(response: SearchResponse): string {
185
186
  async function executeSearch(
186
187
  _toolCallId: string,
187
188
  params: SearchQueryParams,
188
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
189
+ signal?: AbortSignal,
190
+ ): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails; isError?: boolean }> {
191
+ signal?.throwIfAborted();
189
192
  params = { ...params, user_location: normalizeUserLocation(params.user_location) };
190
193
  const validation = validateWebSearchParams(params);
191
194
  if (!validation.valid) {
192
195
  const message = `web_search invalid parameter: ${validation.error}`;
193
196
  return {
194
197
  content: [{ type: "text" as const, text: `Error: ${message}` }],
198
+ isError: true,
195
199
  details: { response: { provider: "none", sources: [] }, error: message },
196
200
  };
197
201
  }
@@ -209,6 +213,7 @@ async function executeSearch(
209
213
  const message = "No web search provider configured.";
210
214
  return {
211
215
  content: [{ type: "text" as const, text: `Error: ${message}` }],
216
+ isError: true,
212
217
  details: { response: { provider: "none", sources: [] }, error: message },
213
218
  };
214
219
  }
@@ -221,6 +226,7 @@ async function executeSearch(
221
226
  try {
222
227
  const searchStart = performance.now();
223
228
  const response = await provider.search({
229
+ signal,
224
230
  query: params.query.replace(/202\d/g, String(new Date().getFullYear())), // LUL
225
231
  limit: params.limit,
226
232
  recency: params.recency,
@@ -243,6 +249,11 @@ async function executeSearch(
243
249
  details: { response },
244
250
  };
245
251
  } catch (error) {
252
+ if (
253
+ signal?.aborted ||
254
+ (error instanceof Error && (error.name === "AbortError" || error.name === "ToolAbortError"))
255
+ )
256
+ throw error;
246
257
  lastError = error;
247
258
  }
248
259
  }
@@ -255,6 +266,7 @@ async function executeSearch(
255
266
 
256
267
  return {
257
268
  content: [{ type: "text" as const, text: `Error: ${message}` }],
269
+ isError: true,
258
270
  details: { response: { provider: lastProvider.id, sources: [] }, error: message },
259
271
  };
260
272
  }
@@ -264,8 +276,9 @@ async function executeSearch(
264
276
  */
265
277
  export async function runSearchQuery(
266
278
  params: SearchQueryParams,
267
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
268
- return executeSearch("cli-web-search", params);
279
+ signal?: AbortSignal,
280
+ ): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails; isError?: boolean }> {
281
+ return executeSearch("cli-web-search", params, signal);
269
282
  }
270
283
 
271
284
  /**
@@ -292,7 +305,9 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
292
305
  _onUpdate?: AgentToolUpdateCallback<SearchRenderDetails>,
293
306
  _context?: AgentToolContext,
294
307
  ): Promise<AgentToolResult<SearchRenderDetails>> {
295
- return executeSearch(_toolCallId, params);
308
+ const result = await executeSearch(_toolCallId, params, _signal);
309
+ if (result.isError) throw new AgentToolError(result.details.error ?? "Web search failed", result);
310
+ return result;
296
311
  }
297
312
  }
298
313
 
@@ -310,7 +325,9 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
310
325
  _ctx: CustomToolContext,
311
326
  _signal?: AbortSignal,
312
327
  ) {
313
- return executeSearch(toolCallId, params);
328
+ const result = await executeSearch(toolCallId, params, _signal);
329
+ if (result.isError) throw new AgentToolError(result.details.error ?? "Web search failed", result);
330
+ return result;
314
331
  },
315
332
 
316
333
  renderCall(args: SearchToolParams, options: RenderResultOptions, theme: Theme) {
@@ -227,6 +227,9 @@ function parseResponse(response: AnthropicApiResponse): SearchResponse {
227
227
  searchQueries.push(block.input.query);
228
228
  }
229
229
  } else if (block.type === "web_search_tool_result" && block.content) {
230
+ if (!Array.isArray(block.content)) {
231
+ throw new SearchProviderError("anthropic", `Anthropic web search failed: ${block.content.error_code}`);
232
+ }
230
233
  // Search results
231
234
  for (const result of block.content) {
232
235
  if (result.type === "web_search_result") {
@@ -136,7 +136,7 @@ export interface AnthropicContentBlock {
136
136
  /** Tool input (for type="server_tool_use") */
137
137
  input?: { query: string };
138
138
  /** Search results (for type="web_search_tool_result") */
139
- content?: AnthropicSearchResult[];
139
+ content?: AnthropicSearchResult[] | { type: "web_search_tool_result_error"; error_code: string };
140
140
  }
141
141
 
142
142
  export interface AnthropicApiResponse {