@patimweb/pi-abap-fs 1.0.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/LICENSE +21 -0
- package/README.md +101 -0
- package/index.ts +82 -0
- package/package.json +51 -0
- package/screenshot.png +0 -0
- package/src/client.ts +177 -0
- package/src/config.ts +174 -0
- package/src/formatting/formatters.ts +254 -0
- package/src/tools/abap-activate.ts +73 -0
- package/src/tools/abap-lock.ts +79 -0
- package/src/tools/abap-node-contents.ts +57 -0
- package/src/tools/abap-object-structure.ts +60 -0
- package/src/tools/abap-object-types.ts +38 -0
- package/src/tools/abap-profile.ts +87 -0
- package/src/tools/abap-query.ts +50 -0
- package/src/tools/abap-read.ts +62 -0
- package/src/tools/abap-search.ts +67 -0
- package/src/tools/abap-setup.ts +81 -0
- package/src/tools/abap-status.ts +32 -0
- package/src/tools/abap-syntax-check.ts +76 -0
- package/src/tools/abap-table.ts +67 -0
- package/src/tools/abap-test-connection.ts +54 -0
- package/src/tools/abap-transports.ts +46 -0
- package/src/tools/abap-unit-test.ts +85 -0
- package/src/tools/abap-unlock.ts +71 -0
- package/src/tools/abap-where-used.ts +65 -0
- package/src/tools/abap-write.ts +120 -0
- package/src/types.ts +55 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure formatting functions that convert ADT data into display strings.
|
|
3
|
+
* No I/O, no state — just data in, string out.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
SearchResult,
|
|
8
|
+
AbapObjectStructure,
|
|
9
|
+
ActivationResult,
|
|
10
|
+
UnitTestClass,
|
|
11
|
+
QueryResult,
|
|
12
|
+
NodeStructure,
|
|
13
|
+
TransportsOfUser,
|
|
14
|
+
TransportTarget,
|
|
15
|
+
ObjectTypeDescriptor,
|
|
16
|
+
UsageReference,
|
|
17
|
+
AdtLock,
|
|
18
|
+
SyntaxCheckResult,
|
|
19
|
+
} from "abap-adt-api";
|
|
20
|
+
|
|
21
|
+
function isClassStructure(
|
|
22
|
+
s: AbapObjectStructure,
|
|
23
|
+
): s is AbapObjectStructure & { includes: unknown[] } {
|
|
24
|
+
return Array.isArray((s as { includes?: unknown[] }).includes);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Search
|
|
28
|
+
|
|
29
|
+
export function formatSearchResults(
|
|
30
|
+
results: ReadonlyArray<SearchResult>,
|
|
31
|
+
query: string,
|
|
32
|
+
): string {
|
|
33
|
+
if (results.length === 0) {
|
|
34
|
+
return `No objects found matching "${query}".`;
|
|
35
|
+
}
|
|
36
|
+
const lines = results.map((r) => {
|
|
37
|
+
const parts = [
|
|
38
|
+
r["adtcore:name"],
|
|
39
|
+
`type=${r["adtcore:type"]}`,
|
|
40
|
+
];
|
|
41
|
+
if (r["adtcore:packageName"]) parts.push(`package=${r["adtcore:packageName"]}`);
|
|
42
|
+
if (r["adtcore:description"]) parts.push(r["adtcore:description"]);
|
|
43
|
+
return `- ${parts.join(" | ")} (${r["adtcore:uri"]})`;
|
|
44
|
+
});
|
|
45
|
+
return `${results.length} object(s) matching "${query}":\n${lines.join("\n")}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Object structure
|
|
49
|
+
|
|
50
|
+
export function formatObjectStructure(s: AbapObjectStructure): string {
|
|
51
|
+
const meta = s.metaData;
|
|
52
|
+
const lines: string[] = [
|
|
53
|
+
`Name: ${meta["adtcore:name"]}`,
|
|
54
|
+
`Type: ${meta["adtcore:type"]}`,
|
|
55
|
+
];
|
|
56
|
+
if (meta["adtcore:description"]) lines.push(`Description: ${meta["adtcore:description"]}`);
|
|
57
|
+
lines.push(`Responsible: ${meta["adtcore:responsible"] ?? "?"}`);
|
|
58
|
+
lines.push(`Language: ${meta["adtcore:language"] ?? "?"}`);
|
|
59
|
+
if (isClassStructure(s)) {
|
|
60
|
+
lines.push("Includes:");
|
|
61
|
+
for (const inc of s.includes as Array<{ "class:includeType": string }>) {
|
|
62
|
+
lines.push(` - ${inc["class:includeType"]}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Activation
|
|
69
|
+
|
|
70
|
+
export function formatActivation(result: ActivationResult): string {
|
|
71
|
+
if (result.success) {
|
|
72
|
+
return "Activation successful.";
|
|
73
|
+
}
|
|
74
|
+
const lines = ["Activation failed."];
|
|
75
|
+
if (result.messages.length > 0) {
|
|
76
|
+
lines.push("Messages:");
|
|
77
|
+
for (const m of result.messages) {
|
|
78
|
+
lines.push(` - [${m.type}] ${m.shortText} (line ${m.line})`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (result.inactive.length > 0) {
|
|
82
|
+
lines.push("Inactive objects:");
|
|
83
|
+
for (const rec of result.inactive) {
|
|
84
|
+
if (rec.object) {
|
|
85
|
+
lines.push(` - ${rec.object["adtcore:name"]} (${rec.object["adtcore:type"]}) by ${rec.object.user}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Unit tests
|
|
93
|
+
|
|
94
|
+
export function formatUnitTests(classes: ReadonlyArray<UnitTestClass>): string {
|
|
95
|
+
if (classes.length === 0) {
|
|
96
|
+
return "No test classes found or no tests were executed.";
|
|
97
|
+
}
|
|
98
|
+
const lines: string[] = [];
|
|
99
|
+
let totalMethods = 0;
|
|
100
|
+
let failures = 0;
|
|
101
|
+
for (const c of classes) {
|
|
102
|
+
lines.push(`Class: ${c["adtcore:name"]} (risk=${c.riskLevel})`);
|
|
103
|
+
for (const m of c.testmethods) {
|
|
104
|
+
totalMethods++;
|
|
105
|
+
const alert = m.alerts.find((a) => a.severity === "critical" || a.severity === "fatal");
|
|
106
|
+
const status = alert ? `FAILED (${alert.severity})` : "passed";
|
|
107
|
+
if (alert) failures++;
|
|
108
|
+
lines.push(` - ${m["adtcore:name"]}: ${status}`);
|
|
109
|
+
if (alert) {
|
|
110
|
+
lines.push(` ${alert.title}`);
|
|
111
|
+
for (const d of alert.details.slice(0, 5)) {
|
|
112
|
+
lines.push(` ${d.split("\n").join("\n ")}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
lines.unshift(`${totalMethods} test method(s), ${failures} failure(s).`);
|
|
118
|
+
return lines.join("\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Query / table results
|
|
122
|
+
|
|
123
|
+
export function formatQueryResult(result: QueryResult): string {
|
|
124
|
+
const { columns, values } = result;
|
|
125
|
+
if (values.length === 0) {
|
|
126
|
+
return "Query returned no rows.";
|
|
127
|
+
}
|
|
128
|
+
const names = columns.map((c) => c.name);
|
|
129
|
+
const rows = values.map((row) =>
|
|
130
|
+
names.map((n) => {
|
|
131
|
+
const v = (row as Record<string, unknown>)[n];
|
|
132
|
+
return v === null || v === undefined ? "" : String(v);
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
// Simple aligned table
|
|
136
|
+
const widths = names.map((n, i) =>
|
|
137
|
+
Math.max(n.length, ...rows.map((r) => r[i]?.length ?? 0)),
|
|
138
|
+
);
|
|
139
|
+
const fmt = (cells: string[]) =>
|
|
140
|
+
cells.map((c, i) => c.padEnd(widths[i])).join(" | ");
|
|
141
|
+
const lines = [
|
|
142
|
+
fmt(names),
|
|
143
|
+
widths.map((w) => "-".repeat(w)).join("-+-"),
|
|
144
|
+
...rows.map(fmt),
|
|
145
|
+
];
|
|
146
|
+
lines.push(`\n${values.length} row(s).`);
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Node contents (package / object tree)
|
|
151
|
+
|
|
152
|
+
export function formatNodes(structure: NodeStructure): string {
|
|
153
|
+
if (structure.nodes.length === 0) {
|
|
154
|
+
return "No child objects found.";
|
|
155
|
+
}
|
|
156
|
+
const lines = structure.nodes.map((n) => {
|
|
157
|
+
const desc = n.DESCRIPTION ? ` (${n.DESCRIPTION})` : "";
|
|
158
|
+
return `- ${n.OBJECT_NAME} [${n.OBJECT_TYPE}]${desc}`;
|
|
159
|
+
});
|
|
160
|
+
return `${structure.nodes.length} node(s):\n${lines.join("\n")}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Object types
|
|
164
|
+
|
|
165
|
+
export function formatObjectTypes(types: ReadonlyArray<ObjectTypeDescriptor>): string {
|
|
166
|
+
if (types.length === 0) return "No object types available.";
|
|
167
|
+
const lines = types.map(
|
|
168
|
+
(t) => `- ${t.name} (${t.type}) — ${t.description ?? ""}`,
|
|
169
|
+
);
|
|
170
|
+
return `${types.length} object type(s):\n${lines.join("\n")}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Transports
|
|
174
|
+
|
|
175
|
+
function targetRequests(t: TransportTarget): string[] {
|
|
176
|
+
return [...t.modifiable, ...t.released].map((r) => {
|
|
177
|
+
return ` - ${r["tm:number"]} [${r["tm:status"]}] ${r["tm:desc"]} (owner=${r["tm:owner"]})`;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function formatTransports(transports: TransportsOfUser): string {
|
|
182
|
+
const allTargets = [
|
|
183
|
+
...transports.workbench.map((t) => ({ kind: "Workbench", t })),
|
|
184
|
+
...transports.customizing.map((t) => ({ kind: "Customizing", t })),
|
|
185
|
+
];
|
|
186
|
+
const lines: string[] = [];
|
|
187
|
+
for (const { kind, t } of allTargets) {
|
|
188
|
+
const requests = targetRequests(t);
|
|
189
|
+
if (requests.length === 0) continue;
|
|
190
|
+
lines.push(`${kind} target ${t["tm:name"]} (${t["tm:desc"]}):`);
|
|
191
|
+
lines.push(...requests);
|
|
192
|
+
}
|
|
193
|
+
if (lines.length === 0) return "No transport requests found for this user.";
|
|
194
|
+
return lines.join("\n");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Where-used
|
|
198
|
+
|
|
199
|
+
export function formatUsageReferences(references: ReadonlyArray<UsageReference>): string {
|
|
200
|
+
if (references.length === 0) return "No usage references found.";
|
|
201
|
+
const lines = references.map((r) => {
|
|
202
|
+
const name = r["adtcore:name"] || r.objectIdentifier;
|
|
203
|
+
return `- ${name} (${r["adtcore:type"] ?? "?"})`;
|
|
204
|
+
});
|
|
205
|
+
return `${references.length} usage reference(s):\n${lines.join("\n")}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Lock
|
|
209
|
+
|
|
210
|
+
export function formatLock(lock: AdtLock): string {
|
|
211
|
+
return [
|
|
212
|
+
`Locked. lockHandle=${lock.LOCK_HANDLE}`,
|
|
213
|
+
lock.CORRNR ? `Transport: ${lock.CORRNR} (${lock.CORRTEXT ?? ""})` : "Transport: (none)",
|
|
214
|
+
lock.CORRUSER ? `Correction user: ${lock.CORRUSER}` : "",
|
|
215
|
+
]
|
|
216
|
+
.filter(Boolean)
|
|
217
|
+
.join("\n");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Syntax check
|
|
221
|
+
|
|
222
|
+
export function formatProfileStatus(
|
|
223
|
+
profiles: Record<string, import("../types.ts").AbapProfile>,
|
|
224
|
+
activeProfile: string | null,
|
|
225
|
+
): string {
|
|
226
|
+
const names = Object.keys(profiles);
|
|
227
|
+
if (names.length === 0) {
|
|
228
|
+
return "No SAP connection configured. Use the abap_setup tool to configure a system.";
|
|
229
|
+
}
|
|
230
|
+
const lines: string[] = [
|
|
231
|
+
`${names.length} SAP profile${names.length === 1 ? "" : "s"} configured:`,
|
|
232
|
+
"",
|
|
233
|
+
];
|
|
234
|
+
for (const name of names) {
|
|
235
|
+
const cfg = profiles[name];
|
|
236
|
+
const marker = name === activeProfile ? "[active]" : " ";
|
|
237
|
+
lines.push(`${marker} ${name}: ${cfg.username}@${cfg.url}`);
|
|
238
|
+
const extra = [
|
|
239
|
+
cfg.client ? `client=${cfg.client}` : "",
|
|
240
|
+
cfg.language ? `lang=${cfg.language}` : "",
|
|
241
|
+
cfg.allowUnauthorized ? "insecure-tls" : "",
|
|
242
|
+
].filter(Boolean);
|
|
243
|
+
if (extra.length > 0) lines.push(` ${extra.join(", ")}`);
|
|
244
|
+
}
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function formatSyntaxCheck(results: ReadonlyArray<SyntaxCheckResult>): string {
|
|
249
|
+
if (results.length === 0) return "No syntax errors found.";
|
|
250
|
+
const lines = results.map(
|
|
251
|
+
(r) => `- [${r.severity}] line ${r.line}: ${r.text}`,
|
|
252
|
+
);
|
|
253
|
+
return `${results.length} message(s):\n${lines.join("\n")}`;
|
|
254
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_activate tool — Activate an ABAP object.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { resolveObject, withClient } from "../client.ts";
|
|
7
|
+
import { resolveConfig } from "../config.ts";
|
|
8
|
+
import { formatActivation } from "../formatting/formatters.ts";
|
|
9
|
+
|
|
10
|
+
export const AbapActivateTool = {
|
|
11
|
+
name: "abap_activate",
|
|
12
|
+
label: "Activate ABAP Object",
|
|
13
|
+
description:
|
|
14
|
+
"Activate an ABAP object after writing changes. Provide the object (name or objectUrl). Returns activation messages and any inactive objects that resulted.",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
profile: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "Profile name to use. Uses active profile if omitted.",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
name: Type.Optional(
|
|
22
|
+
Type.String({ description: "Object name, e.g. 'ZCL_MY_CLASS'" }),
|
|
23
|
+
),
|
|
24
|
+
objectUrl: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description:
|
|
27
|
+
"Object URL, e.g. '/sap/bc/adt/oo/classes/zcl_my_class'. Takes precedence over name.",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
type: Type.Optional(
|
|
31
|
+
Type.String({ description: "Optional type hint for search, e.g. 'CLAS/OC'." }),
|
|
32
|
+
),
|
|
33
|
+
preauditRequested: Type.Optional(
|
|
34
|
+
Type.Boolean({ description: "Request a pre-activation audit (default true)." }),
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
async execute(
|
|
39
|
+
_toolCallId: string,
|
|
40
|
+
params: {
|
|
41
|
+
profile?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
objectUrl?: string;
|
|
44
|
+
type?: string;
|
|
45
|
+
preauditRequested?: boolean;
|
|
46
|
+
},
|
|
47
|
+
_signal: AbortSignal,
|
|
48
|
+
) {
|
|
49
|
+
const config = resolveConfig(params.profile);
|
|
50
|
+
const { resolved, activation } = await withClient(config, async (client) => {
|
|
51
|
+
const resolved = await resolveObject(client, params);
|
|
52
|
+
const activation = await client.activate(
|
|
53
|
+
resolved.name,
|
|
54
|
+
resolved.objectUrl,
|
|
55
|
+
resolved.mainInclude,
|
|
56
|
+
params.preauditRequested ?? true,
|
|
57
|
+
);
|
|
58
|
+
return { resolved, activation };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{ type: "text" as const, text: formatActivation(activation) },
|
|
64
|
+
],
|
|
65
|
+
details: {
|
|
66
|
+
name: resolved.name,
|
|
67
|
+
success: activation.success,
|
|
68
|
+
messages: activation.messages,
|
|
69
|
+
inactive: activation.inactive,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_lock tool — Lock an ABAP object for editing (returns a lock handle).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { resolveObject, withClient } from "../client.ts";
|
|
7
|
+
import { resolveConfig } from "../config.ts";
|
|
8
|
+
import { formatLock } from "../formatting/formatters.ts";
|
|
9
|
+
|
|
10
|
+
export const AbapLockTool = {
|
|
11
|
+
name: "abap_lock",
|
|
12
|
+
label: "Lock ABAP Object",
|
|
13
|
+
description:
|
|
14
|
+
"Lock an ABAP object for editing. Returns a lock handle that must be passed to abap_write or released with abap_unlock when done. Provide either a name (with optional type hint) or an object URL.",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
profile: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "Profile name to use. Uses active profile if omitted.",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
name: Type.Optional(
|
|
22
|
+
Type.String({ description: "Object name, e.g. 'ZCL_MY_CLASS'" }),
|
|
23
|
+
),
|
|
24
|
+
objectUrl: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description:
|
|
27
|
+
"Object URL, e.g. '/sap/bc/adt/oo/classes/zcl_my_class'. Takes precedence over name.",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
type: Type.Optional(
|
|
31
|
+
Type.String({ description: "Optional type hint for search, e.g. 'CLAS/OC'." }),
|
|
32
|
+
),
|
|
33
|
+
accessMode: Type.Optional(
|
|
34
|
+
Type.String({ description: "Lock access mode, e.g. 'MODIFY' (default) or 'EDIT'." }),
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
async execute(
|
|
39
|
+
_toolCallId: string,
|
|
40
|
+
params: {
|
|
41
|
+
profile?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
objectUrl?: string;
|
|
44
|
+
type?: string;
|
|
45
|
+
accessMode?: string;
|
|
46
|
+
},
|
|
47
|
+
_signal: AbortSignal,
|
|
48
|
+
) {
|
|
49
|
+
const config = resolveConfig(params.profile);
|
|
50
|
+
const result = await withClient(
|
|
51
|
+
config,
|
|
52
|
+
async (client) => {
|
|
53
|
+
const resolved = await resolveObject(client, params);
|
|
54
|
+
const lock = await client.lock(
|
|
55
|
+
resolved.objectUrl,
|
|
56
|
+
params.accessMode ?? "MODIFY",
|
|
57
|
+
);
|
|
58
|
+
return { resolved, lock };
|
|
59
|
+
},
|
|
60
|
+
{ stateful: true },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text" as const,
|
|
67
|
+
text: formatLock(result.lock),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
details: {
|
|
71
|
+
name: result.resolved.name,
|
|
72
|
+
objectUrl: result.resolved.objectUrl,
|
|
73
|
+
mainInclude: result.resolved.mainInclude,
|
|
74
|
+
lockHandle: result.lock.LOCK_HANDLE,
|
|
75
|
+
corrNr: result.lock.CORRNR,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_node_contents tool — Browse the object tree of a package (or program / function group).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { withClient } from "../client.ts";
|
|
7
|
+
import { resolveConfig } from "../config.ts";
|
|
8
|
+
import { formatNodes } from "../formatting/formatters.ts";
|
|
9
|
+
|
|
10
|
+
export const AbapNodeContentsTool = {
|
|
11
|
+
name: "abap_node_contents",
|
|
12
|
+
label: "Browse ABAP Object Tree",
|
|
13
|
+
description:
|
|
14
|
+
"Browse the contents of an SAP package (or program / function group) to discover the objects inside it. parentType is usually 'DEVC/K' for packages; pass the package name as parentName. Use '$TMP' for local objects.",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
profile: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "Profile name to use. Uses active profile if omitted.",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
parentType: Type.String({
|
|
22
|
+
description: "Parent object type: DEVC/K (package), PROG/P (program), FUGR/F (function group), PROG/PI (include).",
|
|
23
|
+
}),
|
|
24
|
+
parentName: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description: "Parent object name, e.g. 'Z_MY_PACKAGE' or '$TMP'. Omit to list the top level.",
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
}),
|
|
30
|
+
|
|
31
|
+
async execute(
|
|
32
|
+
_toolCallId: string,
|
|
33
|
+
params: { profile?: string; parentType: string; parentName?: string },
|
|
34
|
+
_signal: AbortSignal,
|
|
35
|
+
) {
|
|
36
|
+
const config = resolveConfig(params.profile);
|
|
37
|
+
const structure = await withClient(config, (client) =>
|
|
38
|
+
client.nodeContents(
|
|
39
|
+
params.parentType as "DEVC/K" | "PROG/P" | "FUGR/F" | "PROG/PI",
|
|
40
|
+
params.parentName,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text" as const, text: formatNodes(structure) }],
|
|
46
|
+
details: {
|
|
47
|
+
nodeCount: structure.nodes.length,
|
|
48
|
+
nodes: structure.nodes.map((n) => ({
|
|
49
|
+
name: n.OBJECT_NAME,
|
|
50
|
+
type: n.OBJECT_TYPE,
|
|
51
|
+
description: n.DESCRIPTION,
|
|
52
|
+
uri: n.OBJECT_URI,
|
|
53
|
+
})),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_object_structure tool — Read an object's metadata and includes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { resolveObject, withClient } from "../client.ts";
|
|
7
|
+
import { resolveConfig } from "../config.ts";
|
|
8
|
+
import { formatObjectStructure } from "../formatting/formatters.ts";
|
|
9
|
+
|
|
10
|
+
export const AbapObjectStructureTool = {
|
|
11
|
+
name: "abap_object_structure",
|
|
12
|
+
label: "ABAP Object Structure",
|
|
13
|
+
description:
|
|
14
|
+
"Get metadata (type, description, responsible, language) and include list for an ABAP object, plus the main source URL. Provide either a name (with optional type hint) or an object URL.",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
profile: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "Profile name to use. Uses active profile if omitted.",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
name: Type.Optional(
|
|
22
|
+
Type.String({ description: "Object name, e.g. 'ZCL_MY_CLASS'" }),
|
|
23
|
+
),
|
|
24
|
+
objectUrl: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description:
|
|
27
|
+
"Object URL, e.g. '/sap/bc/adt/oo/classes/zcl_my_class'. Takes precedence over name.",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
type: Type.Optional(
|
|
31
|
+
Type.String({ description: "Optional type hint for search, e.g. 'CLAS/OC'." }),
|
|
32
|
+
),
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
async execute(
|
|
36
|
+
_toolCallId: string,
|
|
37
|
+
params: { profile?: string; name?: string; objectUrl?: string; type?: string },
|
|
38
|
+
_signal: AbortSignal,
|
|
39
|
+
) {
|
|
40
|
+
const config = resolveConfig(params.profile);
|
|
41
|
+
const resolved = await withClient(config, (client) =>
|
|
42
|
+
resolveObject(client, params),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text" as const,
|
|
49
|
+
text: formatObjectStructure(resolved.structure),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
details: {
|
|
53
|
+
name: resolved.name,
|
|
54
|
+
type: resolved.type,
|
|
55
|
+
objectUrl: resolved.objectUrl,
|
|
56
|
+
mainInclude: resolved.mainInclude,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_object_types tool — List searchable object types and their descriptors.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { withClient } from "../client.ts";
|
|
7
|
+
import { resolveConfig } from "../config.ts";
|
|
8
|
+
import { formatObjectTypes } from "../formatting/formatters.ts";
|
|
9
|
+
|
|
10
|
+
export const AbapObjectTypesTool = {
|
|
11
|
+
name: "abap_object_types",
|
|
12
|
+
label: "List ABAP Object Types",
|
|
13
|
+
description:
|
|
14
|
+
"List the object types available for searching on the connected system. Useful to discover the correct type hint for abap_search (e.g. CLAS, PROG, FUGR, INTF).",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
profile: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "Profile name to use. Uses active profile if omitted.",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
async execute(
|
|
24
|
+
_toolCallId: string,
|
|
25
|
+
params: { profile?: string },
|
|
26
|
+
_signal: AbortSignal,
|
|
27
|
+
) {
|
|
28
|
+
const config = resolveConfig(params.profile);
|
|
29
|
+
const types = await withClient(config, (client) => client.objectTypes());
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{ type: "text" as const, text: formatObjectTypes(types) },
|
|
34
|
+
],
|
|
35
|
+
details: { count: types.length, types },
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_profile tool — List, switch or delete configured SAP profiles.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import {
|
|
7
|
+
deleteProfile,
|
|
8
|
+
getActiveProfile,
|
|
9
|
+
getProfiles,
|
|
10
|
+
setActiveProfile,
|
|
11
|
+
} from "../config.ts";
|
|
12
|
+
import { formatProfileStatus } from "../formatting/formatters.ts";
|
|
13
|
+
|
|
14
|
+
export const AbapProfileTool = {
|
|
15
|
+
name: "abap_profile",
|
|
16
|
+
label: "Manage ABAP Profiles",
|
|
17
|
+
description:
|
|
18
|
+
"List configured SAP connection profiles, switch the active one, or delete a profile. Without arguments it lists all profiles.",
|
|
19
|
+
parameters: Type.Object({
|
|
20
|
+
action: Type.Optional(
|
|
21
|
+
Type.String({
|
|
22
|
+
description: "One of: list, use, delete. Defaults to list.",
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
name: Type.Optional(
|
|
26
|
+
Type.String({ description: "Profile name for 'use' and 'delete'." }),
|
|
27
|
+
),
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
async execute(
|
|
31
|
+
_toolCallId: string,
|
|
32
|
+
params: { action?: string; name?: string },
|
|
33
|
+
_signal: AbortSignal,
|
|
34
|
+
): Promise<{
|
|
35
|
+
content: { type: "text"; text: string }[];
|
|
36
|
+
details: Record<string, unknown>;
|
|
37
|
+
}> {
|
|
38
|
+
const action = (params.action || "list").toLowerCase();
|
|
39
|
+
|
|
40
|
+
if (action === "list") {
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text" as const,
|
|
45
|
+
text: formatProfileStatus(getProfiles(), getActiveProfile()),
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
details: {
|
|
49
|
+
profiles: Object.keys(getProfiles()),
|
|
50
|
+
activeProfile: getActiveProfile(),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!params.name) {
|
|
56
|
+
throw new Error(`The "${action}" action requires a profile name.`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (action === "use") {
|
|
60
|
+
setActiveProfile(params.name);
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text" as const,
|
|
65
|
+
text: `Active SAP profile is now "${params.name}".`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
details: { activeProfile: params.name },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action === "delete") {
|
|
73
|
+
const removed = deleteProfile(params.name);
|
|
74
|
+
const text = removed
|
|
75
|
+
? `Profile "${params.name}" deleted. Active profile is now ${getActiveProfile() ? `"${getActiveProfile()}"` : "unset"}.`
|
|
76
|
+
: `No profile named "${params.name}".`;
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text" as const, text }],
|
|
79
|
+
details: { deleted: removed, activeProfile: getActiveProfile() },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Unknown action "${params.action}". Use one of: list, use, delete.`,
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* abap_query tool — Run an ABAP SQL (freestyle) query on the connected system.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { withClient } from "../client.ts";
|
|
7
|
+
import { resolveConfig } from "../config.ts";
|
|
8
|
+
import { formatQueryResult } from "../formatting/formatters.ts";
|
|
9
|
+
|
|
10
|
+
export const AbapQueryTool = {
|
|
11
|
+
name: "abap_query",
|
|
12
|
+
label: "Run ABAP SQL Query",
|
|
13
|
+
description:
|
|
14
|
+
"Run an ABAP SQL SELECT query on the connected SAP system (freestyle data preview). Returns rows as text. Example: SELECT * FROM sflight UP TO 10 ROWS. Use with care on production systems.",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
profile: Type.Optional(
|
|
17
|
+
Type.String({
|
|
18
|
+
description: "Profile name to use. Uses active profile if omitted.",
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
sql: Type.String({
|
|
22
|
+
description:
|
|
23
|
+
"ABAP SQL SELECT statement, e.g. 'SELECT carrid, connid, fldate FROM sflight UP TO 20 ROWS'.",
|
|
24
|
+
}),
|
|
25
|
+
rowNumber: Type.Optional(
|
|
26
|
+
Type.Number({ description: "Maximum rows to return (default 100)." }),
|
|
27
|
+
),
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
async execute(
|
|
31
|
+
_toolCallId: string,
|
|
32
|
+
params: { profile?: string; sql: string; rowNumber?: number },
|
|
33
|
+
_signal: AbortSignal,
|
|
34
|
+
) {
|
|
35
|
+
const config = resolveConfig(params.profile);
|
|
36
|
+
const result = await withClient(config, (client) =>
|
|
37
|
+
client.runQuery(params.sql, params.rowNumber ?? 100),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{ type: "text" as const, text: formatQueryResult(result) },
|
|
43
|
+
],
|
|
44
|
+
details: {
|
|
45
|
+
rowCount: result.values.length,
|
|
46
|
+
columns: result.columns.map((c) => c.name),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|