-
Notifications
You must be signed in to change notification settings - Fork 112
Expand file tree
/
Copy pathget_component_api.ts
More file actions
215 lines (197 loc) · 8.48 KB
/
Copy pathget_component_api.ts
File metadata and controls
215 lines (197 loc) · 8.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/**
* @fileOverview get_component_api tool
* Get comprehensive API documentation for a UI5 Web Components React component
*/
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { z } from 'zod';
import type { ApiDataStore, ComponentApiData } from '../../types/index.js';
import { handleToolError } from '../../utils/index.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
type GetComponentApiPayload = {
componentName: string;
};
// Load API data at module initialization
let API_DATA: ApiDataStore = { components: {}, webComponents: {}, charts: {}, ai: {} };
try {
const apiDataPath = join(__dirname, 'component-apis.json');
API_DATA = JSON.parse(readFileSync(apiDataPath, 'utf-8'));
} catch (error) {
console.warn('Could not load component API data:', error);
}
/**
* Find a component by name (case-insensitive search across all categories)
*/
function findComponent(name: string): { name: string; data: ComponentApiData; category: string } | null {
const lowerName = name.toLowerCase();
for (const category of ['components', 'webComponents', 'charts', 'ai'] as const) {
const found = Object.entries(API_DATA[category]).find(([key]) => key.toLowerCase() === lowerName);
if (found) {
return {
name: found[0],
data: found[1],
category,
};
}
}
return null;
}
// ============================================================================
// TOOL DEFINITION
// ============================================================================
export const getComponentApiTool = {
name: 'get_component_api',
title: 'Component API Reference',
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
description: `Get comprehensive API documentation for a UI5 Web Components React component including props, ref methods, types, event detail parameters, and CSS parts for shadow DOM styling.
WHEN TO USE: You need exact prop names, types, default values, method signatures, event detail fields, or CSS part selectors for a specific component.
DO NOT USE FOR: General guidance on styling, events, or architecture — use get_documentation instead. To discover component names — use list_components first.
WORKFLOW: Use list_components to find the correct component name, then this tool for full API details.
LIMITS: Returns data for one component per call. Returns all props and methods for that component.
EXAMPLE INPUT: { "componentName": "Dialog" }
**Output Format (JSON):**
\`\`\`typescript
{
package: string, // NPM package name
description: string, // Full JSDoc description with @tags, examples, notes
props: {
[propName: string]: {
type: {
name: string, // Type category (e.g., "string", "boolean", "enum")
raw?: string, // Full TypeScript type string
value?: Array<{ // For enums: possible values with descriptions
value: string,
description?: string
}>
},
required: boolean, // Whether prop is required
defaultValue: any, // Default value if specified
description: string, // Full prop description with notes
eventDetail?: { // Present on event handler props (web components only)
params: Array<{ // Fields available on e.detail
name: string, // e.g., "targetRef", "escPressed", "item"
type: string, // e.g., "HTMLElement", "boolean"
description: string // What this detail field contains
}>,
cancelable: boolean, // true = e.preventDefault() prevents the action
bubbles: boolean // true = event bubbles through DOM
}
}
},
methods: Array<{ // Imperative handle methods (access via ref.current.methodName())
name: string,
description: string,
params: Array<{
name: string,
description: string,
type: any
}>,
returns: any
}>,
cssParts?: Array<{ // CSS ::part() selectors for shadow DOM styling (web components only)
name: string, // Use as: componentSelector::part(name) { ... }
description: string
}>,
isAbstract?: boolean, // true = abstract component: renders into its PARENT's DOM, not its own shadow root. For testing, target the node from getDomRef() (parent-owned), not the host placeholder. See the "Testing" knowledge-base section (get_documentation).
subTypeDocs?: string // Markdown docs for complex prop types (e.g. column definition properties)
docUrl?: string // Upstream docs link for complex behavioral concepts
}
\`\`\`
**Note:** Generic HTML attributes (className, style, onClick, etc.) are filtered out but available via CommonProps. Ref methods are accessed via \`componentRef.current.methodName()\`. Event detail fields (eventDetail.params) tell you what's on \`e.detail\` — access via \`e.detail.paramName\`.`,
inputSchema: {
componentName: z
.string()
.describe(
'Component name (e.g., "Button", "AnalyticalTable"). Case-insensitive. Must match a component from @ui5/webcomponents-react, @ui5/webcomponents-react-charts, or @ui5/webcomponents-ai-react.',
),
},
outputSchema: {
package: z.string().optional().describe('NPM package name'),
description: z.string().optional().describe('Full component description'),
props: z
.record(z.string(), z.any())
.optional()
.describe('Component props with types, descriptions, and eventDetail'),
methods: z.array(z.any()).optional().describe('Imperative methods accessible via ref'),
cssParts: z
.array(z.object({ name: z.string(), description: z.string() }))
.optional()
.describe('CSS ::part() selectors for shadow DOM styling'),
isAbstract: z
.boolean()
.optional()
.describe(
'True for abstract components (marked @abstract): they render into a parent’s DOM, not their own shadow root. When interacting in a real browser (tests), target the node returned by getDomRef() rather than the host placeholder. See the "Testing" knowledge-base section.',
),
subTypeDocs: z
.string()
.optional()
.describe('Additional documentation for complex prop types (e.g. AnalyticalTable column definition)'),
docUrl: z
.string()
.optional()
.describe('Link to upstream documentation for complex behavioral concepts (e.g. layout algorithms)'),
_meta: z
.object({
apiVersion: z.string(),
extractedAt: z.string().optional(),
})
.optional(),
error: z.string().optional().describe('Error message if component not found'),
errorType: z
.enum(['not_found', 'invalid_input'])
.optional()
.describe('Semantic error type for programmatic handling'),
availableComponents: z
.array(z.string())
.optional()
.describe('List of all valid component names (only present on not_found errors)'),
},
handler: ({ componentName }: GetComponentApiPayload) => {
try {
const normalizedName = componentName.trim();
const found = findComponent(normalizedName);
if (!found) {
const allComponents = [
...Object.keys(API_DATA.components),
...Object.keys(API_DATA.webComponents),
...Object.keys(API_DATA.charts),
...Object.keys(API_DATA.ai),
].sort();
return {
structuredContent: {
error: `Component '${componentName}' not found.`,
errorType: 'not_found' as const,
availableComponents: allComponents,
},
content: [
{
type: 'text' as const,
text: `Component '${componentName}' not found. Use list_components to discover available component names.`,
},
],
};
}
const response = {
...found.data,
_meta: {
apiVersion: API_DATA.metadata?.version ?? 'unknown',
extractedAt: API_DATA.metadata?.extractedAt,
},
};
return {
structuredContent: response,
content: [{ type: 'text' as const, text: JSON.stringify(response, null, 2) }],
};
} catch (error) {
return handleToolError(error, `Error getting API for component '${componentName}'`);
}
},
};