@f5-sales-demo/xcsh 22.1.2 → 22.2.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": "@f5-sales-demo/xcsh",
4
- "version": "22.1.2",
4
+ "version": "22.2.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -67,13 +67,13 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@agentclientprotocol/sdk": "1.4.0",
70
- "@f5-sales-demo/pi-agent-core": "22.1.2",
71
- "@f5-sales-demo/pi-ai": "22.1.2",
72
- "@f5-sales-demo/pi-natives": "22.1.2",
73
- "@f5-sales-demo/pi-resource-management": "22.1.2",
74
- "@f5-sales-demo/pi-tui": "22.1.2",
75
- "@f5-sales-demo/pi-utils": "22.1.2",
76
- "@f5-sales-demo/xcsh-stats": "22.1.2",
70
+ "@f5-sales-demo/pi-agent-core": "22.2.0",
71
+ "@f5-sales-demo/pi-ai": "22.2.0",
72
+ "@f5-sales-demo/pi-natives": "22.2.0",
73
+ "@f5-sales-demo/pi-resource-management": "22.2.0",
74
+ "@f5-sales-demo/pi-tui": "22.2.0",
75
+ "@f5-sales-demo/pi-utils": "22.2.0",
76
+ "@f5-sales-demo/xcsh-stats": "22.2.0",
77
77
  "@mozilla/readability": "^0.6",
78
78
  "@resvg/resvg-wasm": "2.6.2",
79
79
  "@sinclair/typebox": "0.34.52",
@@ -95,6 +95,48 @@ export function matchesBaselineCatalogDiscoveryTerm(term: string, values: readon
95
95
  return values.some(value => normalizeApiCatalogDiscoveryTerm(value).includes(normalized));
96
96
  }
97
97
 
98
+ function categoryDocuments(category: ApiCatalogCategory): ApiCatalogDiscoveryDocument[] {
99
+ const destination = `xcsh://api-catalog/${category.name}`;
100
+ if (category.operations.length === 0) {
101
+ return [
102
+ {
103
+ id: `category:${category.name}`,
104
+ categoryName: category.name,
105
+ markdown: [
106
+ `# ${category.displayName}`,
107
+ "",
108
+ `- Category: ${category.name}`,
109
+ `- Destination: ${destination}`,
110
+ "",
111
+ "## Operations",
112
+ "- None",
113
+ "",
114
+ ].join("\n"),
115
+ },
116
+ ];
117
+ }
118
+ return category.operations
119
+ .slice()
120
+ .sort((left, right) => left.operationId.localeCompare(right.operationId))
121
+ .map(operation => ({
122
+ id: `operation:${category.name}:${operation.operationId}`,
123
+ categoryName: category.name,
124
+ markdown: [
125
+ `# ${category.displayName}: ${operation.name}`,
126
+ "",
127
+ `- Category: ${category.name}`,
128
+ `- Destination: ${destination}`,
129
+ `- Operation: ${operation.name}`,
130
+ `- Alias: ${(operation.operationAliases ?? []).join(", ")}`,
131
+ `- Description: ${operation.description}`,
132
+ `- Method: ${operation.method.toUpperCase()}`,
133
+ `- Path: ${operation.path}`,
134
+ `- Operation ID: ${operation.operationId}`,
135
+ "",
136
+ ].join("\n"),
137
+ }));
138
+ }
139
+
98
140
  function categoryDocument(category: ApiCatalogCategory): ApiCatalogDiscoveryDocument {
99
141
  const destination = `xcsh://api-catalog/${category.name}`;
100
142
  const operations = category.operations.flatMap(operation => [
@@ -105,7 +147,6 @@ function categoryDocument(category: ApiCatalogCategory): ApiCatalogDiscoveryDocu
105
147
  ` - Path: ${operation.path}`,
106
148
  ` - Operation ID: ${operation.operationId}`,
107
149
  ]);
108
-
109
150
  return {
110
151
  id: `category:${category.name}`,
111
152
  categoryName: category.name,
@@ -139,6 +180,20 @@ export function buildApiCatalogDiscoveryCorpus(
139
180
  };
140
181
  }
141
182
 
183
+ /** Candidate-only operation granularity; authoritative results remain category URLs. */
184
+ export function buildApiOperationDiscoveryCorpus(
185
+ index: ApiCatalogIndex,
186
+ data: Readonly<Record<string, ApiCatalogCategory>>,
187
+ ): ApiCatalogDiscoveryCorpus {
188
+ return {
189
+ catalogVersion: index.version,
190
+ documents: Object.values(data)
191
+ .slice()
192
+ .sort((left, right) => left.name.localeCompare(right.name))
193
+ .flatMap(categoryDocuments),
194
+ };
195
+ }
196
+
142
197
  export function fingerprintApiCatalogDiscoveryCorpus(
143
198
  corpus: ApiCatalogDiscoveryCorpus,
144
199
  sourceSha: string,
@@ -3,18 +3,20 @@ import path from "node:path";
3
3
  import { rankQmdBm25CatalogDiscovery } from "./api-catalog-discovery";
4
4
  import { QMD_API_CATALOG_PREBUILT_INDEX } from "./api-catalog-qmd-index.generated";
5
5
  import type { ApiSpecIndex } from "./api-spec-types";
6
+ import {
7
+ buildKnowledgeSearchPlan,
8
+ classifyKnowledgeRequest,
9
+ type KnowledgeClassifierResource,
10
+ type KnowledgeSearchPlan,
11
+ } from "./knowledge-classifier";
6
12
 
7
- export interface ApiCatalogPreflightResource {
8
- readonly name: string;
9
- readonly aliases: readonly string[];
10
- readonly domain: string;
11
- readonly categories: readonly string[];
12
- }
13
+ export interface ApiCatalogPreflightResource extends KnowledgeClassifierResource {}
13
14
 
14
15
  export interface ApiCatalogPreflightIntent {
15
16
  readonly resource: string;
16
17
  readonly domain: string;
17
18
  readonly queries: readonly string[];
19
+ readonly structuredQuery?: KnowledgeSearchPlan;
18
20
  }
19
21
 
20
22
  export interface ApiCatalogPreflightDestination {
@@ -45,16 +47,6 @@ interface RunApiCatalogPreflightOptions extends ApiCatalogPreflightOptions {
45
47
  readonly rank?: (query: string) => Promise<readonly string[]>;
46
48
  }
47
49
 
48
- const EXCLUDED_INTENT =
49
- /\b(?:troubleshoot|debug|not working|returning\s+\d{3}|pricing|price|quote|licen[cs]ing|sales)\b/i;
50
- const THIRD_PARTY_SCOPE = /\b(?:aws|amazon|azure|gcp|google cloud|kubernetes)\b/i;
51
- const F5_XC_SCOPE = /\b(?:f5(?:\s+distributed\s+cloud)?|xc)\b/i;
52
- const ANAPHORIC_RESOURCE = /\b(?:its|it|that|this)\b/i;
53
- const DOCUMENTATION_ROUTE = /xcsh:\/\/documentation(?:\/|\?)/i;
54
- const API_METADATA_INTENT =
55
- /\b(?:endpoint|api\s+path|http\s+method|method|payload|request\s+body|required\s+fields?|enum|allowed\s+values?|constraints?|limits?|maximum|max(?:imum)?|minimum|min(?:imum)?|how\s+many|create|creates|creating|get|gets|list|lists|update|updates|replace|replaces|delete|deletes|clone|import)\b/i;
56
- const EXPLICIT_API_INTENT = /\b(?:api|endpoint|method|payload|schema)\b/i;
57
-
58
50
  let generatedResources: readonly ApiCatalogPreflightResource[] | undefined;
59
51
  let generatedCatalogVersion: string | undefined;
60
52
 
@@ -67,10 +59,6 @@ function normalizedPhrase(value: string): string {
67
59
  .replace(/\s+/g, " ");
68
60
  }
69
61
 
70
- function preferredAlias(resource: { name: string; aliases: readonly string[] }): string {
71
- return normalizedPhrase(resource.aliases[0] ?? resource.name);
72
- }
73
-
74
62
  function defaultAliases(name: string, descriptionShort?: string): string[] {
75
63
  const canonical = normalizedPhrase(name);
76
64
  const aliases = [normalizedPhrase(descriptionShort ?? ""), canonical].filter(Boolean);
@@ -97,56 +85,40 @@ function loadGeneratedMetadata(): { resources: readonly ApiCatalogPreflightResou
97
85
  return { resources: generatedResources, catalogVersion: generatedCatalogVersion };
98
86
  }
99
87
 
100
- function crudVerb(prompt: string): string | undefined {
101
- const value = normalizedPhrase(prompt);
102
- if (/\b(?:create|creates|creating)\b/.test(value)) return "create";
103
- if (/\b(?:get|gets|read|fetch)\b/.test(value)) return "get";
104
- if (/\b(?:list|lists)\b/.test(value)) return "list";
105
- if (/\b(?:update|updates)\b/.test(value)) return "update";
106
- if (/\b(?:replace|replaces)\b/.test(value)) return "replace";
107
- if (/\b(?:delete|deletes|remove)\b/.test(value)) return "delete";
108
- if (/\bclone\b/.test(value)) return "clone";
109
- if (/\bimport\b/.test(value)) return "import";
110
- return undefined;
111
- }
112
-
113
88
  export function classifyApiCatalogPreflight(
114
89
  prompt: string,
115
90
  options: ApiCatalogPreflightOptions,
116
91
  ): ApiCatalogPreflightIntent | null {
117
- if (!options.toolsEnabled || DOCUMENTATION_ROUTE.test(prompt) || EXCLUDED_INTENT.test(prompt)) return null;
118
- if (THIRD_PARTY_SCOPE.test(prompt) && !F5_XC_SCOPE.test(prompt)) return null;
119
- if (/\bterraform\b/i.test(prompt) && !EXPLICIT_API_INTENT.test(prompt)) return null;
120
- if (!API_METADATA_INTENT.test(prompt)) return null;
121
-
122
92
  const resources = options.resources ?? loadGeneratedMetadata().resources;
123
- const normalizedPrompt = ` ${normalizedPhrase(prompt)} `;
124
- const matches = resources
125
- .flatMap(resource =>
126
- resource.aliases
127
- .map(alias => normalizedPhrase(alias))
128
- .filter(alias => alias && normalizedPrompt.includes(` ${alias} `))
129
- .map(alias => ({ resource, alias })),
130
- )
131
- .sort(
132
- (left, right) =>
133
- right.alias.length - left.alias.length ||
134
- right.resource.categories.length - left.resource.categories.length ||
135
- left.resource.name.localeCompare(right.resource.name) ||
136
- left.resource.domain.localeCompare(right.resource.domain),
137
- );
138
- const selected = matches[0]?.resource;
139
- if (!selected) {
140
- if (ANAPHORIC_RESOURCE.test(prompt) && options.previousResource) return options.previousResource;
141
- return null;
93
+ const previous = options.previousResource
94
+ ? {
95
+ route: "api" as const,
96
+ confidence: 0.99,
97
+ resource: options.previousResource.resource,
98
+ domain: options.previousResource.domain,
99
+ constraints: { domain: options.previousResource.domain },
100
+ query:
101
+ options.previousResource.structuredQuery ??
102
+ buildKnowledgeSearchPlan(
103
+ "api",
104
+ options.previousResource.queries[0] ?? options.previousResource.resource,
105
+ ),
106
+ }
107
+ : undefined;
108
+ const classified = classifyKnowledgeRequest(prompt, { toolsEnabled: options.toolsEnabled, resources, previous });
109
+ if (classified.route !== "api" || !classified.resource || !classified.domain) return null;
110
+ if (
111
+ options.previousResource &&
112
+ classified.resource === options.previousResource.resource &&
113
+ /\b(?:its|it|that|this|those|them|the same)\b/i.test(prompt)
114
+ ) {
115
+ return options.previousResource;
142
116
  }
143
-
144
- const alias = preferredAlias(selected);
145
- const verb = crudVerb(prompt);
146
117
  return {
147
- resource: selected.name,
148
- domain: selected.domain,
149
- queries: verb ? [alias, `${verb} ${alias}`] : [alias],
118
+ resource: classified.resource,
119
+ domain: classified.domain,
120
+ queries: classified.query.lex,
121
+ structuredQuery: classified.query,
150
122
  };
151
123
  }
152
124