@lisa-mcp/arc1-extension 0.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/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clément Ringot
4
+
5
+ This software depends on @arc-mcp/xsuaa-auth
6
+ (https://github.com/arc-mcp/xsuaa-auth), an MIT-licensed package authored by the
7
+ ARC-1 maintainers, for its XSUAA OAuth proxy, stateless DCR client store, OAuth
8
+ state codec, and BTP connectivity layer. That dependency carries its own license.
9
+
10
+ A portion still vendored in this repository — the OAuth callback-proxy request
11
+ handler — is derived from ARC-1 (https://github.com/arc-mcp/arc-1) and is used
12
+ under the MIT License:
13
+
14
+ Copyright (c) 2025-2026 Alice Vinogradova and contributors
15
+ Copyright (c) 2026 Marian Zeis
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining a copy
18
+ of this software and associated documentation files (the "Software"), to deal
19
+ in the Software without restriction, including without limitation the rights
20
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
+ copies of the Software, and to permit persons to whom the Software is
22
+ furnished to do so, subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all
25
+ copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @lisa-mcp/arc1-extension
2
+
3
+ LISA's SAP object-translation tools, packaged as an **[ARC-1](https://github.com/arc-mcp/arc-1) extension** —
4
+ loaded **in-process** by an ARC-1 instance so they reuse its authenticated SAP client, safety ceiling,
5
+ scope policy, audit trail, and per-user principal propagation. No second auth stack, no second deployment.
6
+
7
+ Three tools are exposed: `Custom_TranslateListLanguages`, `Custom_TranslateGetTexts`,
8
+ `Custom_TranslateSetTexts` — the same wire contract as the standalone [LISA MCP server](https://github.com/ClementRingot/LISA),
9
+ sharing `@lisa/core`.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @lisa-mcp/arc1-extension
15
+ ```
16
+
17
+ The published artifact is a **single self-contained ESM bundle** at `dist/index.js`
18
+ (`@lisa/core` is inlined). `arc-1` and `zod` are **peer dependencies**: they are provided by the
19
+ host ARC-1 process at runtime — `zod` in particular MUST resolve to ARC-1's own instance, since its
20
+ registry runs `z.toJSONSchema()` on the tool schemas and plugin + registry have to share one zod.
21
+
22
+ ## Use
23
+
24
+ ARC-1 loads code plugins from `ARC1_PLUGINS` — a CSV of **absolute** paths to each plugin's entry file.
25
+ Point it at the installed bundle:
26
+
27
+ ```bash
28
+ ARC1_PLUGINS=/abs/path/to/node_modules/@lisa-mcp/arc1-extension/dist/index.js
29
+ SAP_ALLOW_WRITES=true
30
+ SAP_ALLOW_PLUGIN_RAW_WRITES=true # all three tools POST → require the raw-write opt-in
31
+ SAP_I18N_SERVICE_PATH=/sap/bc/http/sap/zi18n_service # optional override
32
+ ```
33
+
34
+ > The plugin registers under the runtime name **`lisa-arc1-extension`** (its ARC-1 identity, kept
35
+ > stable across the npm rename). Requires LISA's `ZCL_I18N_SERVICE` (or `_CLOUD`) handler class
36
+ > installed on the target SAP system.
37
+
38
+ Full runbook — Docker image, Cloud Foundry, the write-flag rationale — in
39
+ [docs: ARC-1 extension deployment](https://github.com/ClementRingot/LISA/blob/main/docs_page/arc1-extension-deployment.md).
40
+
41
+ ## Versioning
42
+
43
+ Released on its own cadence, independent of the LISA product version. Git tag `arc1-extension-vX.Y.Z`;
44
+ see [docs: Releasing](https://github.com/ClementRingot/LISA/blob/main/docs_page/releasing.md).
45
+
46
+ ## License
47
+
48
+ [MIT](./LICENSE)
package/dist/index.js ADDED
@@ -0,0 +1,394 @@
1
+ // ../core/dist/wire.js
2
+ function compact(body) {
3
+ const out = {};
4
+ for (const [k, v] of Object.entries(body)) {
5
+ if (v !== void 0 && v !== "" && !(Array.isArray(v) && v.length === 0))
6
+ out[k] = v;
7
+ }
8
+ return out;
9
+ }
10
+ async function callAction(transport, action, body) {
11
+ const { status, body: respBody } = await transport.post(action, JSON.stringify(compact(body)));
12
+ let envelope;
13
+ try {
14
+ envelope = JSON.parse(respBody);
15
+ } catch {
16
+ throw new Error(`SAP HTTP ${status}: non-JSON response: ${respBody.slice(0, 300)}`);
17
+ }
18
+ if (!envelope.success || status < 200 || status >= 300) {
19
+ const code = envelope.error?.code ?? `HTTP_${status}`;
20
+ const message = envelope.error?.message ?? respBody.slice(0, 300);
21
+ if (code === "CLOUD_UNSUPPORTED") {
22
+ throw new Error(`Not available on the SAP ABAP Cloud (public cloud / BTP ABAP Environment) stack \u2014 ${message}`);
23
+ }
24
+ throw new Error(`SAP i18n error [${code}]: ${message}`);
25
+ }
26
+ if (envelope.data === void 0) {
27
+ throw new Error("SAP i18n response had success=true but no data");
28
+ }
29
+ return envelope.data;
30
+ }
31
+ var CDS_ENTITY_TARGET = "cds_entity";
32
+ var CDS_ENTITY_OWNERS = ["data_definition", "metadata_extension"];
33
+ var POSITION_SUFFIX = /\[(\d+)\]$/;
34
+ function normalizeListTextEntry(entry) {
35
+ const populated = typeof entry.populated === "boolean" ? entry.populated : entry.value !== "" && entry.value != null;
36
+ const match = POSITION_SUFFIX.exec(entry.attribute);
37
+ if (match) {
38
+ return {
39
+ ...entry,
40
+ attribute: entry.attribute.slice(0, match.index),
41
+ position: match[1],
42
+ populated
43
+ };
44
+ }
45
+ return { ...entry, populated };
46
+ }
47
+ function normalizeSetTextEntry(entry) {
48
+ const { owner: _owner, ...rest } = entry;
49
+ const match = POSITION_SUFFIX.exec(rest.attribute);
50
+ if (match) {
51
+ return {
52
+ ...rest,
53
+ attribute: rest.attribute.slice(0, match.index),
54
+ position: rest.position ?? match[1]
55
+ };
56
+ }
57
+ return rest;
58
+ }
59
+ function narrowListTexts(texts, selectors) {
60
+ let out = texts;
61
+ if (selectors.field_name) {
62
+ const fieldName = selectors.field_name.toUpperCase();
63
+ out = out.filter((t) => t.field_name.toUpperCase() === fieldName);
64
+ }
65
+ if (selectors.position) {
66
+ out = out.filter((t) => t.position === selectors.position);
67
+ }
68
+ return out;
69
+ }
70
+ function isTargetTypeSupported(capabilities, action, targetType) {
71
+ const allowed = capabilities?.[action];
72
+ if (!allowed)
73
+ return true;
74
+ return allowed.includes(targetType);
75
+ }
76
+ function unsupportedTargetMessage(action, targetType) {
77
+ return `target_type '${targetType}' is not available for '${action}' on this SAP system (not in its declared capabilities). Public cloud / BTP ABAP Environment and on-premise / private cloud support different object types.`;
78
+ }
79
+ var capabilitiesCache;
80
+ async function loadCapabilities(transport) {
81
+ if (capabilitiesCache !== void 0)
82
+ return capabilitiesCache;
83
+ try {
84
+ capabilitiesCache = await callAction(transport, "capabilities", {});
85
+ } catch (e) {
86
+ const msg = e instanceof Error ? e.message : "";
87
+ if (/\[(UNKNOWN_ACTION|HTTP_404)\]/.test(msg))
88
+ capabilitiesCache = null;
89
+ return capabilitiesCache ?? null;
90
+ }
91
+ return capabilitiesCache;
92
+ }
93
+ var I18nCore = class {
94
+ transport;
95
+ constructor(transport) {
96
+ this.transport = transport;
97
+ }
98
+ /** Reject a target_type the backend's allow-list does not include for this action. */
99
+ async assertActionSupported(action, targetType) {
100
+ const caps = await loadCapabilities(this.transport);
101
+ if (!isTargetTypeSupported(caps, action, targetType)) {
102
+ throw new Error(unsupportedTargetMessage(action, targetType));
103
+ }
104
+ }
105
+ /**
106
+ * The backend's per-action allow-list, so tool descriptions can advertise the object types THIS
107
+ * system actually supports (proactive guidance, not just the reactive assertActionSupported reject).
108
+ * Returns null when the backend declares no allow-list (older handler) OR the probe failed —
109
+ * callers then fall back to generic wording.
110
+ */
111
+ async getCapabilities() {
112
+ try {
113
+ return await loadCapabilities(this.transport);
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+ async listLanguages() {
119
+ const data = await callAction(this.transport, "list_languages", {});
120
+ return data.languages;
121
+ }
122
+ /**
123
+ * Whole-object reader (list_texts). `language` is optional: when omitted, nothing is sent and
124
+ * the ABAP reads in the object's original language, echoing the effective `language` back.
125
+ * Every entry is normalized (decomposed position + guaranteed `populated`).
126
+ */
127
+ async getTexts(params) {
128
+ await this.assertActionSupported("list_texts", params.target_type);
129
+ const data = await callAction(this.transport, "list_texts", { ...params });
130
+ return { ...data, texts: (data.texts ?? []).map(normalizeListTextEntry) };
131
+ }
132
+ /**
133
+ * Merged reader for a CDS entity (target_type `cds_entity`): fan out to BOTH physical objects
134
+ * — the data_definition view AND its metadata_extension DDLX — and concatenate their texts into
135
+ * one set. Every CDS row keeps the `owner` the backend stamped (read from the row, NOT derived
136
+ * from which call produced it), so the caller gets the whole translation surface in one shot and
137
+ * DDLX labels are included automatically. Rows are NOT deduplicated across owners: a view-owned
138
+ * and a DDLX-owned slot are distinct targets even when field_name/attribute coincide.
139
+ */
140
+ async getCdsEntityTexts(params) {
141
+ const texts = [];
142
+ const errors = [];
143
+ let language = params.language ?? "";
144
+ for (const owner of CDS_ENTITY_OWNERS) {
145
+ try {
146
+ const part = await this.getTexts({
147
+ target_type: owner,
148
+ object_name: params.object_name,
149
+ language: params.language
150
+ });
151
+ if (part.language)
152
+ language = part.language;
153
+ for (const t of part.texts)
154
+ texts.push(t.owner ? t : { ...t, owner });
155
+ } catch (e) {
156
+ errors.push({ target_type: owner, owner, error: e instanceof Error ? e.message : String(e) });
157
+ }
158
+ }
159
+ if (texts.length === 0 && errors.length > 0) {
160
+ throw new Error(`cds_entity read failed: ${errors.map((e) => `${e.target_type}: ${e.error}`).join("; ")}`);
161
+ }
162
+ const result = {
163
+ target_type: CDS_ENTITY_TARGET,
164
+ object_name: params.object_name,
165
+ language,
166
+ texts
167
+ };
168
+ return errors.length > 0 ? { ...result, errors } : result;
169
+ }
170
+ async setTranslation(params) {
171
+ await this.assertActionSupported("set_translation", params.target_type);
172
+ const texts = params.texts.map(normalizeSetTextEntry);
173
+ return callAction(this.transport, "set_translation", { ...params, texts });
174
+ }
175
+ /**
176
+ * Owner-routed writer for a CDS entity (target_type `cds_entity`). Every row MUST carry an
177
+ * `owner` of "data_definition" or "metadata_extension" — the call is rejected outright if any row
178
+ * lacks one, because guessing would write a label to the wrong physical object. Rows are grouped by
179
+ * owner and each NON-EMPTY bucket is written with exactly ONE backend set_translation to the
180
+ * matching real target (so each physical object is locked/transported once). Each row keeps its own
181
+ * field_name (EMPTY for the view's entity-level endusertext_label — never inherited) and its
182
+ * positional index (normalizeSetTextEntry); `owner` is stripped before the wire. Buckets are
183
+ * isolated: a failure in one is recorded and the other is still attempted, since the two objects
184
+ * are not written atomically — `success` is true only when every issued sub-call succeeded.
185
+ */
186
+ async setCdsEntityTexts(params) {
187
+ const buckets = /* @__PURE__ */ new Map();
188
+ for (const entry of params.texts) {
189
+ if (entry.owner !== "data_definition" && entry.owner !== "metadata_extension") {
190
+ throw new Error("cds_entity set requires an 'owner' on every text row (data_definition | metadata_extension)");
191
+ }
192
+ const bucket = buckets.get(entry.owner);
193
+ if (bucket)
194
+ bucket.push(entry);
195
+ else
196
+ buckets.set(entry.owner, [entry]);
197
+ }
198
+ const results = [];
199
+ for (const owner of CDS_ENTITY_OWNERS) {
200
+ const bucket = buckets.get(owner);
201
+ if (!bucket || bucket.length === 0)
202
+ continue;
203
+ try {
204
+ const r = await this.setTranslation({
205
+ target_type: owner,
206
+ object_name: params.object_name,
207
+ language: params.language,
208
+ transport: params.transport,
209
+ texts: bucket
210
+ });
211
+ results.push({ target_type: owner, owner, written: bucket.length, success: r.success !== false });
212
+ } catch (e) {
213
+ results.push({
214
+ target_type: owner,
215
+ owner,
216
+ written: 0,
217
+ success: false,
218
+ error: e instanceof Error ? e.message : String(e)
219
+ });
220
+ }
221
+ }
222
+ return { success: results.every((r) => r.success), results };
223
+ }
224
+ };
225
+
226
+ // ../core/dist/schemas.js
227
+ import { z } from "zod";
228
+ var TargetTypeSchema = z.enum([
229
+ "cds_entity",
230
+ "data_element",
231
+ "domain",
232
+ "data_definition",
233
+ "message_class",
234
+ "text_pool",
235
+ "metadata_extension",
236
+ "application_log_object",
237
+ "business_configuration_object",
238
+ "text_table"
239
+ ]).describe("XCO translation target type: data_element (DTEL), domain (DOMA fixed-value texts), data_definition (CDS DDLS entity/field labels), message_class (MSAG), text_pool (class/function-group text symbols), metadata_extension (DDLX UI labels), application_log_object (APLO), business_configuration_object (SMBC), text_table (a translatable text table \u2014 a DB table with delivery class C/S and exactly one LANG key field, e.g. T005T; its non-key character columns are the text attributes). cds_entity (VIRTUAL, LISA-only): a convenience target that bundles the texts of ONE named CDS entity. It is NOT a backend type \u2014 LISA fans it out to the two real targets that hold that entity\u2019s texts: data_definition (the view/DDLS itself) and metadata_extension (its DDLX). On read, LISA calls both and returns the merged texts, each row carrying an `owner` field (data_definition | metadata_extension). On write, LISA groups the provided texts by their `owner` and writes each group back to the matching real target in its own call (so each physical object is locked once); the per-row `owner` returned by a read routes the write, so pass the rows back unchanged. SCOPE: cds_entity covers the NAMED entity and its OWN DDLX ONLY. It does NOT reach the underlying/parent views of an `as projection on` chain \u2014 e.g. the interface view ZI_\u2026 behind a projection ZC_\u2026 \u2014 each of which carries its OWN @EndUserText.label and needs its OWN cds_entity (or data_definition) call. On a RAP stack, run ONE pass per distinct CDS entity (one view = one call), cds_entity on each. cds_entity is valid WITH or WITHOUT a DDLX: when the entity has no metadata extension the data_definition is still written and the DDLX sub-call simply finds 0 texts (the call stays valid but may report a partial status) \u2014 for a single-object view with no DDLX, data_definition direct is cleaner. data_definition and metadata_extension remain available to address ONE physical object explicitly (single-owner, unmerged). The object types actually available on the target system are stated per tool (see each tool description).");
240
+ var LanguageSchema = z.string().min(1).max(2).describe("Language \u2014 ISO 639-1 (EN, DE, FR\u2026) or SAP SPRAS single-char code (E, D, F\u2026)");
241
+ var SelectorShape = {
242
+ field_name: z.string().optional().describe("CDS field name (data_definition / metadata_extension) to scope to a single field"),
243
+ fixed_value: z.string().optional().describe("Domain fixed value (lower limit) \u2014 required for target_type=domain"),
244
+ message_number: z.string().optional().describe("Message number \u2014 required for target_type=message_class"),
245
+ text_symbol_id: z.string().optional().describe('Text symbol id (e.g. "001") \u2014 for target_type=text_pool'),
246
+ text_pool_owner_type: z.enum(["class", "function_group"]).optional().describe("Owner of the text pool \u2014 class (default) or function_group"),
247
+ subobject_name: z.string().optional().describe("Sub-object name (e.g. application-log sub-object)"),
248
+ position: z.string().optional().describe("1-based position for repeatable UI annotations (metadata_extension). Sent as a string."),
249
+ language_key_field_name: z.string().optional().describe("REQUIRED for target_type=text_table: the LANG key field of the text table (e.g. SPRAS). Ignored by every other target_type."),
250
+ master_key_fields: z.array(z.object({ name: z.string().min(1), value: z.string() })).optional().describe('REQUIRED for target_type=text_table: the master key fields that pin ONE record \u2014 every key field EXCEPT the language field \u2014 as [{ name, value }] (e.g. [{ "name": "LAND1", "value": "DE" }]). Must fix all master keys so a single record is targeted. Ignored by every other target_type.')
251
+ };
252
+ var ListLanguagesSchema = z.object({});
253
+ var GetTextsSchema = z.object({
254
+ target_type: TargetTypeSchema,
255
+ object_name: z.string().min(1).describe("Technical name of the SAP object, e.g. ZCL_MY_CLASS"),
256
+ language: LanguageSchema.optional().describe("Language to read in (EN, DE, FR\u2026). Optional \u2014 when omitted, the object is read in its original language and the effective language is returned in the response."),
257
+ ...SelectorShape
258
+ });
259
+ var SetTranslationSchema = z.object({
260
+ target_type: TargetTypeSchema,
261
+ object_name: z.string().min(1).describe("Technical name of the SAP object"),
262
+ language: LanguageSchema,
263
+ transport: z.string().min(1).describe("Transport request number, e.g. K900001"),
264
+ texts: z.array(z.object({
265
+ attribute: z.string().min(1).describe("XCO text attribute, e.g. short_field_label / medium_field_label / long_field_label / heading_field_label (data_element), endusertext_label (data_definition/metadata_extension), message_short_text (message_class), fixed_value_description (domain), or a text column name like LANDX (text_table)"),
266
+ value: z.string().describe("Translated text value"),
267
+ field_name: z.string().optional().describe("Per-entry CDS field name (data_definition / metadata_extension). When set it overrides the top-level field_name for THIS entry, letting one call address several fields of the same object. Omit (or leave the top-level field_name empty) for entity-level texts."),
268
+ position: z.string().optional().describe('Per-entry 1-based position for repeatable UI annotations (e.g. ui_lineitem_label). Overrides the top-level position for THIS entry. Sent as a string. Equivalently, the attribute may keep the bracketed form it was read in (e.g. "ui_lineitem_label[2]") \u2014 the index round-trips either way and is never renumbered.'),
269
+ owner: z.string().optional().describe('Required when target_type=cds_entity: which physical object this text belongs to \u2014 "data_definition" (view/DDLS) or "metadata_extension" (DDLX). LISA routes each row to the matching backend target by this value (a cds_entity write with any row missing `owner` is rejected \u2014 LISA never guesses). Pass through verbatim the `owner` that TranslateGetTexts stamped on the row. For entity-level texts (the view\u2019s own endusertext_label) leave field_name empty. Positional UI labels must keep their `position` (string, 1-based). Ignored for a single-target (data_definition / metadata_extension) or non-CDS write.')
270
+ })).min(1).describe("Array of text entries to write. Each entry may carry its own field_name/position so that translations for MULTIPLE fields of one object (e.g. several ui_lineitem_label labels of a CDS view) are written in a single call \u2014 the object is then locked only once."),
271
+ ...SelectorShape
272
+ });
273
+ var TOOLS = {
274
+ TranslateListLanguages: {
275
+ description: "List all languages installed on the SAP system.",
276
+ inputSchema: ListLanguagesSchema
277
+ },
278
+ TranslateGetTexts: {
279
+ description: 'Read all translatable texts of an SAP object in a given language, or in the object\'s original language when none is specified. Returns every text slot with its full key (level, field_name, attribute), its value, and a `populated` flag (false = the slot exists but is empty in this language, i.e. still to translate). To list only filled texts, keep entries with populated=true; to compare two languages, call it once per language and diff on (key, populated, value). For a CDS entity use target_type=cds_entity: a single call returns the merged texts of ONE named entity \u2014 the view itself (data_definition) AND its OWN metadata extension/DDLX (metadata_extension) together, so the DDLX labels come back without a separate metadata_extension call. This covers the NAMED entity ONLY: it does NOT read the underlying/parent views of an `as projection on` chain (e.g. the interface view ZI_\u2026 behind a projection ZC_\u2026), which carry their own labels \u2014 read EACH entity of the stack with its own cds_entity call. Each cds_entity row always includes an `owner` ("data_definition" or "metadata_extension") identifying the physical object it lives in, and a `position` (string, 1-based) for positional UI labels. Positional labels keep the BARE attribute (e.g. "ui_lineitem_label") plus that separate `position` \u2014 they are NOT merged into "ui_lineitem_label[1]". Pass `owner`, `position`, field_name and attribute back to TranslateSetTexts(cds_entity) VERBATIM for a correct round-trip.',
280
+ inputSchema: GetTextsSchema
281
+ },
282
+ TranslateSetTexts: {
283
+ description: 'Write or update translations for an SAP object. Provide the transport request and an array of { attribute, value } entries. Each entry may also carry its own field_name (and position) to target a specific CDS field \u2014 so all fields of one object (e.g. every ui_lineitem_label) can be written in a SINGLE call, locking the object only once. For target_type=cds_entity, EVERY row must carry the `owner` ("data_definition" or "metadata_extension") that TranslateGetTexts stamped: LISA groups the rows by `owner` and writes each group back to the matching physical object (view vs DDLX) in its own call, each locked/transported once. This writes the NAMED entity and its OWN DDLX ONLY \u2014 NOT the underlying/parent views of an `as projection on` chain (e.g. the interface view ZI_\u2026 behind a projection ZC_\u2026); translate EACH entity of the stack with its own cds_entity call. A cds_entity write with any row missing `owner` is REJECTED (LISA never guesses the object). Entity-level texts (the view\u2019s own endusertext_label) must go out with an EMPTY field_name; positional UI labels keep the bare attribute plus their `position` (string, 1-based) \u2014 never renumber or bracket it. The result reports, per owner, how many texts were written and the sub-call status; writes are not atomic across the two objects, so a partial write returns success=false with both outcomes (a cds_entity write to a view with no DDLX is still valid \u2014 the metadata_extension sub-call just finds nothing; for such single-object views, data_definition direct avoids the empty DDLX sub-call). For the single targets (data_definition / metadata_extension and non-CDS types) nothing changes: one 1:1 backend write, and entries without their own field_name/position fall back to the top-level field_name and `position`.',
284
+ inputSchema: SetTranslationSchema
285
+ }
286
+ };
287
+
288
+ // src/tools/Custom_TranslateGetTexts.ts
289
+ import { OperationType, defineTool } from "arc-1/public";
290
+
291
+ // src/transport.ts
292
+ var DEFAULT_SERVICE_PATH = "/sap/bc/http/sap/zi18n_service";
293
+ function servicePath() {
294
+ const p = process.env.SAP_I18N_SERVICE_PATH?.trim();
295
+ return (p && p.length > 0 ? p : DEFAULT_SERVICE_PATH).replace(/\/$/, "");
296
+ }
297
+ function ctxHttpTransport(http) {
298
+ const base = servicePath();
299
+ return {
300
+ async post(action, jsonBody) {
301
+ const res = await http.post(`${base}/${action}`, jsonBody, "application/json", {
302
+ Accept: "application/json"
303
+ });
304
+ return { status: res.statusCode, body: res.body };
305
+ }
306
+ };
307
+ }
308
+
309
+ // src/tools/Custom_TranslateGetTexts.ts
310
+ var Custom_TranslateGetTexts_default = defineTool({
311
+ name: "Custom_TranslateGetTexts",
312
+ description: TOOLS.TranslateGetTexts.description,
313
+ schema: GetTextsSchema,
314
+ policy: { scope: "write", opType: OperationType.Read },
315
+ availableOn: "all",
316
+ async handler(args, ctx) {
317
+ const a = args;
318
+ const core = new I18nCore(ctxHttpTransport(ctx.http));
319
+ const result = a.target_type === CDS_ENTITY_TARGET ? await core.getCdsEntityTexts({ object_name: a.object_name, language: a.language }) : await core.getTexts({
320
+ target_type: a.target_type,
321
+ object_name: a.object_name,
322
+ language: a.language,
323
+ text_pool_owner_type: a.text_pool_owner_type,
324
+ language_key_field_name: a.language_key_field_name,
325
+ master_key_fields: a.master_key_fields
326
+ });
327
+ const texts = narrowListTexts(result.texts, { field_name: a.field_name, position: a.position });
328
+ return { content: [{ type: "text", text: JSON.stringify({ ...result, texts }, null, 2) }] };
329
+ }
330
+ });
331
+
332
+ // src/tools/Custom_TranslateListLanguages.ts
333
+ import { OperationType as OperationType2, defineTool as defineTool2 } from "arc-1/public";
334
+ var Custom_TranslateListLanguages_default = defineTool2({
335
+ name: "Custom_TranslateListLanguages",
336
+ description: TOOLS.TranslateListLanguages.description,
337
+ schema: ListLanguagesSchema,
338
+ policy: { scope: "write", opType: OperationType2.Read },
339
+ availableOn: "all",
340
+ async handler(_args, ctx) {
341
+ const core = new I18nCore(ctxHttpTransport(ctx.http));
342
+ const languages = await core.listLanguages();
343
+ return { content: [{ type: "text", text: JSON.stringify(languages, null, 2) }] };
344
+ }
345
+ });
346
+
347
+ // src/tools/Custom_TranslateSetTexts.ts
348
+ import { OperationType as OperationType3, defineTool as defineTool3 } from "arc-1/public";
349
+ var Custom_TranslateSetTexts_default = defineTool3({
350
+ name: "Custom_TranslateSetTexts",
351
+ description: TOOLS.TranslateSetTexts.description,
352
+ schema: SetTranslationSchema,
353
+ policy: { scope: "write", opType: OperationType3.Update },
354
+ availableOn: "all",
355
+ async handler(args, ctx) {
356
+ const a = args;
357
+ const core = new I18nCore(ctxHttpTransport(ctx.http));
358
+ const result = a.target_type === CDS_ENTITY_TARGET ? await core.setCdsEntityTexts({
359
+ object_name: a.object_name,
360
+ language: a.language,
361
+ transport: a.transport,
362
+ texts: a.texts
363
+ }) : await core.setTranslation({
364
+ target_type: a.target_type,
365
+ object_name: a.object_name,
366
+ language: a.language,
367
+ transport: a.transport,
368
+ texts: a.texts,
369
+ field_name: a.field_name,
370
+ fixed_value: a.fixed_value,
371
+ message_number: a.message_number,
372
+ text_symbol_id: a.text_symbol_id,
373
+ text_pool_owner_type: a.text_pool_owner_type,
374
+ subobject_name: a.subobject_name,
375
+ position: a.position,
376
+ language_key_field_name: a.language_key_field_name,
377
+ master_key_fields: a.master_key_fields
378
+ });
379
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
380
+ }
381
+ });
382
+
383
+ // src/index.ts
384
+ var plugin = {
385
+ name: "lisa-arc1-extension",
386
+ version: "0.3.0",
387
+ apiVersion: 1,
388
+ tools: [Custom_TranslateListLanguages_default, Custom_TranslateGetTexts_default, Custom_TranslateSetTexts_default]
389
+ };
390
+ var index_default = plugin;
391
+ export {
392
+ index_default as default
393
+ };
394
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../core/src/wire.ts", "../../core/src/schemas.ts", "../src/tools/Custom_TranslateGetTexts.ts", "../src/transport.ts", "../src/tools/Custom_TranslateListLanguages.ts", "../src/tools/Custom_TranslateSetTexts.ts", "../src/index.ts"],
4
+ "sourcesContent": ["/**\n * Transport-agnostic wire contract for zi18n_service (ABAP handler class ZCL_I18N_SERVICE).\n *\n * Wire contract \u2014 mirrors ZCL_I18N_SERVICE exactly:\n * - The ACTION is the last segment of the URL path (handler reads `~path_info`),\n * lowercase. The wrapper drives list_languages | list_texts | set_translation, plus a\n * `capabilities` probe (cached) used to reject stack-unsupported (action, target_type) calls\n * up-front. list_texts is the whole-object reader (the client diffs its output locally to\n * \"list\" and \"compare\").\n * - ALL parameters are sent in the JSON request BODY (handler reads `request->get_text()`\n * and string-matches \"name\":\"value\"). We therefore POST every action with a JSON body.\n * - Object kinds are the XCO semantic `target_type` literals (data_element, domain, \u2026).\n * - Every response is wrapped: { \"success\": true, \"data\": {\u2026} } on success, or\n * { \"success\": false, \"error\": { \"code\", \"message\" } } with HTTP 400 on failure.\n *\n * POST {path}/list_languages body: {}\n * POST {path}/list_texts body: { target_type, object_name, language?, text_pool_owner_type? }\n * POST {path}/set_translation body: { target_type, object_name, language, transport,\n * texts: [{ attribute, value, field_name?, position? }, \u2026],\n * \u2026selectors }\n *\n * This module knows nothing about HOW a request reaches the backend (BTP destinations,\n * principal propagation, Cloud Connector, plain fetch, or an already-authenticated ARC-1\n * `ctx.http`) \u2014 that is the `I18nTransport` seam each distribution implements.\n */\n\n// \u2500\u2500\u2500 Transport seam \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface I18nHttpResponse {\n status: number;\n body: string;\n}\n\nexport type WireAction = 'list_languages' | 'list_texts' | 'set_translation' | 'capabilities';\n\n/**\n * The only thing a distribution (standalone MCP server, ARC-1 extension, \u2026) must implement.\n * `jsonBody` is already the final compacted+serialized JSON string \u2014 transports just POST it to\n * `{servicePath}/{action}` (sap-client as a query param) and return the raw status + body. All\n * wire-contract logic (compaction, serialization, envelope unwrap) stays in core.\n */\nexport interface I18nTransport {\n post(action: WireAction, jsonBody: string): Promise<I18nHttpResponse>;\n}\n\n// \u2500\u2500\u2500 Response types (match ZCL_I18N_SERVICE JSON exactly) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Envelope every handler wraps its payload in (build_success / build_error). */\ninterface SapEnvelope<T> {\n success: boolean;\n data?: T;\n error?: { code: string; message: string };\n}\n\nexport interface SapLanguage {\n sap_code: string; // SAP language key (SPRAS, 1 char)\n iso_code: string; // ISO 639-1 code\n name: string; // language name\n}\n\n/** A single XCO text attribute/value pair (append_text_entry). */\nexport interface TextEntry {\n attribute: string;\n value: string;\n}\n\n/**\n * A text entry for set_translation. Beyond { attribute, value } it may carry its own\n * `field_name`/`position` selectors: when present they override the top-level selectors for\n * THIS entry only, so one set_translation call can address several CDS fields of the same\n * object (the ABAP groups entries by field and writes each within a single change scenario,\n * locking the object once). Both are sent as strings to match the handler's parser.\n *\n * `owner` is the physical CDS object the slot belongs to (\"data_definition\" for a view-owned\n * slot, \"metadata_extension\" for a DDLX-owned slot). It is the round-trip companion of the\n * `owner` the reader stamps on each CDS row: `setCdsEntityTexts` groups entries by it and routes\n * each group to the matching backend target_type, so each physical object (DDLS vs DDLX) is\n * locked/transported exactly once. It is a LISA routing key only \u2014 it is NOT sent on the wire.\n */\nexport interface SetTextEntry extends TextEntry {\n field_name?: string;\n position?: string;\n owner?: string;\n}\n\n/**\n * list_texts entries carry extra level/field context (build_text_json_entry).\n *\n * `populated` is the canonical \"this text is filled in the requested language\" signal the ABAP\n * emits as `xsdbool( iv_value IS NOT INITIAL )` \u2014 false means \"to translate\". `position` is\n * decomposed by the wrapper from the ABAP `attribute` when it is encoded `name[n]` (e.g.\n * `ui_facet_label[1]` \u2192 attribute `ui_facet_label`, position `\"1\"`), so the\n * (field_name, position, attribute) triple round-trips straight into set_translation.\n */\nexport interface ListTextEntry extends TextEntry {\n level: string; // 'entity' | 'field' | 'fixed_value' | 'message' | 'text_symbol'\n field_name: string; // empty for entity-level texts\n position?: string; // 1-based position for repeatable annotations; absent when not positional\n populated: boolean; // true when the value is non-empty in the requested language\n // For CDS rows the backend stamps the physical object that owns the slot: \"data_definition\"\n // (the view/DDLS itself) or \"metadata_extension\" (its DDLX). Absent for non-CDS target_types.\n // READ this from the row \u2014 never derive it from which backend call produced the row. It feeds\n // set_translation routing so a merged CDS entity round-trips each slot to the right object.\n owner?: string;\n}\n\nexport interface ListTextsResult {\n target_type: string;\n object_name: string;\n language: string; // effective language the ABAP read in (original language when none was sent)\n texts: ListTextEntry[];\n}\n\nexport interface SetTranslationResult {\n target_type: string;\n object_name: string;\n language: string;\n transport: string;\n success: boolean;\n}\n\n/** One sub-read of a `cds_entity` get that failed, attached to the partial result. */\nexport interface CdsReadError {\n target_type: string; // the real backend target that failed (data_definition | metadata_extension)\n owner: string; // same value, as the routing key\n error: string;\n}\n\n/** A `cds_entity` get result: the merged rows, plus any per-owner read error (partial success). */\nexport type CdsEntityTextsResult = ListTextsResult & { errors?: CdsReadError[] };\n\n/** Outcome of the single backend set issued for one owner bucket of a `cds_entity` write. */\nexport interface CdsOwnerSetOutcome {\n target_type: string; // data_definition | metadata_extension\n owner: string; // same value, the routing key the rows carried\n written: number; // how many rows were sent to this physical object\n success: boolean;\n error?: string;\n}\n\n/**\n * Aggregated result of a `cds_entity` set. `success` is true only when EVERY issued sub-call\n * succeeded; writes are NOT atomic across the two physical objects, so a partial write reports\n * success=false with both outcomes so the caller sees exactly what landed.\n */\nexport interface CdsEntitySetResult {\n success: boolean;\n results: CdsOwnerSetOutcome[];\n}\n\n/** Optional selectors the handler reads to disambiguate sub-objects within a target. */\nexport interface I18nSelectors {\n field_name?: string;\n fixed_value?: string;\n message_number?: string;\n text_symbol_id?: string;\n text_pool_owner_type?: string;\n subobject_name?: string;\n position?: string;\n // text_table only: the LANG key field (e.g. SPRAS) and the master key fields that pin one record.\n language_key_field_name?: string;\n master_key_fields?: Array<{ name: string; value: string }>;\n}\n\n// \u2500\u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Drop undefined/empty fields so we only send what the handler should parse. */\nfunction compact(body: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(body)) {\n if (v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0)) out[k] = v;\n }\n return out;\n}\n\n/**\n * POST an action to {servicePath}/{action} via the injected transport and unwrap the\n * { success, data, error } envelope. The ABAP action is the last path segment,\n * so it must be lowercase (list_languages, list_texts, \u2026).\n */\nasync function callAction<T>(transport: I18nTransport, action: WireAction, body: Record<string, unknown>): Promise<T> {\n const { status, body: respBody } = await transport.post(action, JSON.stringify(compact(body)));\n\n let envelope: SapEnvelope<T>;\n try {\n envelope = JSON.parse(respBody) as SapEnvelope<T>;\n } catch {\n throw new Error(`SAP HTTP ${status}: non-JSON response: ${respBody.slice(0, 300)}`);\n }\n\n if (!envelope.success || status < 200 || status >= 300) {\n const code = envelope.error?.code ?? `HTTP_${status}`;\n const message = envelope.error?.message ?? respBody.slice(0, 300);\n // The cloud handler's backstop for operations the released APIs cannot serve.\n // Surface it as a clear stack-limitation message rather than a raw error code.\n if (code === 'CLOUD_UNSUPPORTED') {\n throw new Error(`Not available on the SAP ABAP Cloud (public cloud / BTP ABAP Environment) stack \u2014 ${message}`);\n }\n throw new Error(`SAP i18n error [${code}]: ${message}`);\n }\n if (envelope.data === undefined) {\n throw new Error('SAP i18n response had success=true but no data');\n }\n return envelope.data;\n}\n\n// \u2500\u2500\u2500 Merged CDS entity surface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// `data_definition` (the CDS view / DDLS) and `metadata_extension` (its DDLX) are two\n// PHYSICAL objects but one logical translation surface: a view often defines its UI labels\n// inline, a projection delegates them to a DDLX, and either may hold any given slot. LISA\n// exposes that surface under the synthetic target_type `cds_entity`: get fans out to BOTH\n// backend reads and concatenates, and the backend stamps every row with the `owner` that\n// tells set which physical object to write back to. `cds_entity` is a LISA concept \u2014 it is\n// never sent on the wire; only `data_definition` / `metadata_extension` reach the backend.\n\n/** Synthetic target_type for the merged CDS entity surface (view + its DDLX). */\nexport const CDS_ENTITY_TARGET = 'cds_entity';\n\n/** The physical CDS objects a `cds_entity` fans out to, in output order (view first, DDLX second). */\nexport const CDS_ENTITY_OWNERS = ['data_definition', 'metadata_extension'] as const;\n\n// \u2500\u2500\u2500 list_texts entry normalization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Trailing 1-based position encoded by the ABAP as `name[n]` (e.g. ui_facet_label[1]). */\nconst POSITION_SUFFIX = /\\[(\\d+)\\]$/;\n\n/**\n * Normalize one raw list_texts entry:\n * - decompose `attribute` \"name[n]\" \u2192 base attribute + `position` \"n\" so the\n * (field_name, position, attribute) key feeds set_translation unchanged;\n * - guarantee `populated` is present \u2014 older ABAP builds omit it, so fall back to\n * \"value is non-empty\" without inventing anything the server didn't say.\n */\nexport function normalizeListTextEntry(entry: ListTextEntry): ListTextEntry {\n const populated = typeof entry.populated === 'boolean' ? entry.populated : entry.value !== '' && entry.value != null;\n\n const match = POSITION_SUFFIX.exec(entry.attribute);\n if (match) {\n return {\n ...entry,\n attribute: entry.attribute.slice(0, match.index),\n position: match[1],\n populated,\n };\n }\n return { ...entry, populated };\n}\n\n/**\n * Prepare one set_translation entry for the wire. The backend reads `attribute` and `position`\n * as SEPARATE fields, so a positional slot must arrive split. A round-tripped read already comes\n * decomposed (normalizeListTextEntry), but a hand-built entry may still carry the bracketed form\n * `name[n]` the reader emits \u2014 accept it and move the index into `position` (an explicit `position`\n * on the entry wins). The index is never renumbered or dropped; it just travels in its own field so\n * set writes back to the exact slot. `owner` is a LISA routing key and is stripped here so it never\n * reaches the backend parser.\n */\nexport function normalizeSetTextEntry(entry: SetTextEntry): SetTextEntry {\n const { owner: _owner, ...rest } = entry;\n const match = POSITION_SUFFIX.exec(rest.attribute);\n if (match) {\n return {\n ...rest,\n attribute: rest.attribute.slice(0, match.index),\n position: rest.position ?? match[1],\n };\n }\n return rest;\n}\n\n/**\n * Narrow whole-object list_texts entries by the optional `field_name` (case-insensitive) /\n * `position` selectors. `list_texts` enumerates every field/position, so both distributions\n * (standalone server + ARC-1 extension) filter client-side on top of the reader \u2014 this is the\n * one shared implementation so the two can't drift.\n */\nexport function narrowListTexts(\n texts: ListTextEntry[],\n selectors: { field_name?: string; position?: string },\n): ListTextEntry[] {\n let out = texts;\n if (selectors.field_name) {\n const fieldName = selectors.field_name.toUpperCase();\n out = out.filter((t) => t.field_name.toUpperCase() === fieldName);\n }\n if (selectors.position) {\n out = out.filter((t) => t.position === selectors.position);\n }\n return out;\n}\n\n// \u2500\u2500\u2500 Backend capabilities (proactive object-type guard) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The ABAP `capabilities` action returns an ALLOW-LIST: the object types this stack\n// can translate, per action (e.g. { list_texts: [...], set_translation: [...] }).\n// Public cloud / BTP ABAP Environment and on-premise / private cloud support DIFFERENT\n// object types, and the set can also differ by system version. Because the handler class\n// DECLARES its own list, LISA follows whatever the bound handler reports with no code change.\n// The list is editable in the handler class (remove a type to disable it). LISA fetches it\n// once per process, caches it, and rejects a target_type not on the list up-front instead of\n// round-tripping to SAP only to hit the CLOUD_UNSUPPORTED backstop. Older handlers without the\n// action degrade gracefully to permissive (the backstop still fires).\n//\n// The cache is process-wide (module scope), not per-I18nCore-instance: the ARC-1 extension\n// constructs a fresh I18nCore per tool call, so an instance-level cache would never hit.\n\n/** Supported object types per wire action, as declared by the handler's allow-list. */\nexport type Capabilities = Record<string, string[]>;\n\n/**\n * Pure check: does the backend allow this target_type for this action? Permissive (true)\n * when the backend declares nothing for the action (older handler / unknown) \u2014 the ABAP\n * CLOUD_UNSUPPORTED backstop still catches real gaps in that case.\n */\nexport function isTargetTypeSupported(\n capabilities: Capabilities | null | undefined,\n action: string,\n targetType: string,\n): boolean {\n const allowed = capabilities?.[action];\n if (!allowed) return true;\n return allowed.includes(targetType);\n}\n\n/** Assistant-facing message for an object type the target system does not support. */\nexport function unsupportedTargetMessage(action: string, targetType: string): string {\n return `target_type '${targetType}' is not available for '${action}' on this SAP system (not in its declared capabilities). Public cloud / BTP ABAP Environment and on-premise / private cloud support different object types.`;\n}\n\n// Probed once per process (the backend is fixed for the app lifetime).\n// undefined = not probed; null = backend exposes no `capabilities` action (permissive).\nlet capabilitiesCache: Capabilities | null | undefined;\n\nasync function loadCapabilities(transport: I18nTransport): Promise<Capabilities | null> {\n if (capabilitiesCache !== undefined) return capabilitiesCache;\n try {\n capabilitiesCache = await callAction<Capabilities>(transport, 'capabilities', {});\n } catch (e) {\n // Handler without the `capabilities` action (older build) \u2192 permissive: rely on\n // the ABAP CLOUD_UNSUPPORTED backstop. Other (transient) errors stay permissive\n // for this call without poisoning the cache so a later call can retry.\n const msg = e instanceof Error ? e.message : '';\n if (/\\[(UNKNOWN_ACTION|HTTP_404)\\]/.test(msg)) capabilitiesCache = null;\n return capabilitiesCache ?? null;\n }\n return capabilitiesCache;\n}\n\n/** Test seam: reset the process-wide capabilities cache between unit tests. */\nexport function __resetCapabilitiesCache(): void {\n capabilitiesCache = undefined;\n}\n\n// \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class I18nCore {\n constructor(private readonly transport: I18nTransport) {}\n\n /** Reject a target_type the backend's allow-list does not include for this action. */\n private async assertActionSupported(action: WireAction, targetType: string): Promise<void> {\n const caps = await loadCapabilities(this.transport);\n if (!isTargetTypeSupported(caps, action, targetType)) {\n throw new Error(unsupportedTargetMessage(action, targetType));\n }\n }\n\n /**\n * The backend's per-action allow-list, so tool descriptions can advertise the object types THIS\n * system actually supports (proactive guidance, not just the reactive assertActionSupported reject).\n * Returns null when the backend declares no allow-list (older handler) OR the probe failed \u2014\n * callers then fall back to generic wording.\n */\n async getCapabilities(): Promise<Capabilities | null> {\n try {\n return await loadCapabilities(this.transport);\n } catch {\n return null;\n }\n }\n\n async listLanguages(): Promise<SapLanguage[]> {\n const data = await callAction<{ languages: SapLanguage[] }>(this.transport, 'list_languages', {});\n return data.languages;\n }\n\n /**\n * Whole-object reader (list_texts). `language` is optional: when omitted, nothing is sent and\n * the ABAP reads in the object's original language, echoing the effective `language` back.\n * Every entry is normalized (decomposed position + guaranteed `populated`).\n */\n async getTexts(params: {\n target_type: string;\n object_name: string;\n language?: string;\n text_pool_owner_type?: string;\n // text_table only: the LANG key field and the master key fields that pin one record.\n language_key_field_name?: string;\n master_key_fields?: Array<{ name: string; value: string }>;\n }): Promise<ListTextsResult> {\n await this.assertActionSupported('list_texts', params.target_type);\n const data = await callAction<ListTextsResult>(this.transport, 'list_texts', { ...params });\n return { ...data, texts: (data.texts ?? []).map(normalizeListTextEntry) };\n }\n\n /**\n * Merged reader for a CDS entity (target_type `cds_entity`): fan out to BOTH physical objects\n * \u2014 the data_definition view AND its metadata_extension DDLX \u2014 and concatenate their texts into\n * one set. Every CDS row keeps the `owner` the backend stamped (read from the row, NOT derived\n * from which call produced it), so the caller gets the whole translation surface in one shot and\n * DDLX labels are included automatically. Rows are NOT deduplicated across owners: a view-owned\n * and a DDLX-owned slot are distinct targets even when field_name/attribute coincide.\n */\n async getCdsEntityTexts(params: {\n object_name: string;\n language?: string;\n }): Promise<CdsEntityTextsResult> {\n // Read each physical object in turn (view/DDLS first, DDLX second) and concatenate, in that\n // order. Sequential, not parallel: the capabilities probe is a process-wide cache, so back-to-back\n // reads share one probe and the merged order is deterministic.\n const texts: ListTextEntry[] = [];\n const errors: CdsReadError[] = [];\n let language = params.language ?? '';\n for (const owner of CDS_ENTITY_OWNERS) {\n try {\n const part = await this.getTexts({\n target_type: owner,\n object_name: params.object_name,\n language: params.language,\n });\n if (part.language) language = part.language;\n // Trust the backend's stamp; only default `owner` from the producing call if a row lacks it.\n for (const t of part.texts) texts.push(t.owner ? t : { ...t, owner });\n } catch (e) {\n // Partial success: keep whatever the other call returned and attach this error.\n errors.push({ target_type: owner, owner, error: e instanceof Error ? e.message : String(e) });\n }\n }\n // Nothing came back AND something failed \u2192 there is no partial result to return; surface it.\n if (texts.length === 0 && errors.length > 0) {\n throw new Error(`cds_entity read failed: ${errors.map((e) => `${e.target_type}: ${e.error}`).join('; ')}`);\n }\n const result: CdsEntityTextsResult = {\n target_type: CDS_ENTITY_TARGET,\n object_name: params.object_name,\n language,\n texts,\n };\n return errors.length > 0 ? { ...result, errors } : result;\n }\n\n async setTranslation(\n params: {\n target_type: string;\n object_name: string;\n language: string;\n transport: string;\n texts: SetTextEntry[];\n } & I18nSelectors,\n ): Promise<SetTranslationResult> {\n await this.assertActionSupported('set_translation', params.target_type);\n const texts = params.texts.map(normalizeSetTextEntry);\n return callAction<SetTranslationResult>(this.transport, 'set_translation', { ...params, texts });\n }\n\n /**\n * Owner-routed writer for a CDS entity (target_type `cds_entity`). Every row MUST carry an\n * `owner` of \"data_definition\" or \"metadata_extension\" \u2014 the call is rejected outright if any row\n * lacks one, because guessing would write a label to the wrong physical object. Rows are grouped by\n * owner and each NON-EMPTY bucket is written with exactly ONE backend set_translation to the\n * matching real target (so each physical object is locked/transported once). Each row keeps its own\n * field_name (EMPTY for the view's entity-level endusertext_label \u2014 never inherited) and its\n * positional index (normalizeSetTextEntry); `owner` is stripped before the wire. Buckets are\n * isolated: a failure in one is recorded and the other is still attempted, since the two objects\n * are not written atomically \u2014 `success` is true only when every issued sub-call succeeded.\n */\n async setCdsEntityTexts(params: {\n object_name: string;\n language: string;\n transport: string;\n texts: SetTextEntry[];\n }): Promise<CdsEntitySetResult> {\n const buckets = new Map<string, SetTextEntry[]>();\n for (const entry of params.texts) {\n if (entry.owner !== 'data_definition' && entry.owner !== 'metadata_extension') {\n throw new Error(\"cds_entity set requires an 'owner' on every text row (data_definition | metadata_extension)\");\n }\n const bucket = buckets.get(entry.owner);\n if (bucket) bucket.push(entry);\n else buckets.set(entry.owner, [entry]);\n }\n\n const results: CdsOwnerSetOutcome[] = [];\n // Deterministic order: the view/DDLS first, then its DDLX.\n for (const owner of CDS_ENTITY_OWNERS) {\n const bucket = buckets.get(owner);\n if (!bucket || bucket.length === 0) continue;\n try {\n const r = await this.setTranslation({\n target_type: owner,\n object_name: params.object_name,\n language: params.language,\n transport: params.transport,\n texts: bucket,\n });\n results.push({ target_type: owner, owner, written: bucket.length, success: r.success !== false });\n } catch (e) {\n results.push({\n target_type: owner,\n owner,\n written: 0,\n success: false,\n error: e instanceof Error ? e.message : String(e),\n });\n }\n }\n return { success: results.every((r) => r.success), results };\n }\n}\n", "/**\n * MCP/tool input schemas for SAP object translation.\n *\n * These schemas mirror the wire contract of the ABAP handler class\n * `zcl_i18n_service` exactly:\n * - the action is the last segment of the URL path (handled in wire.ts),\n * - every parameter is sent in the JSON request body,\n * - object kinds are the XCO *semantic* target types (data_element, domain, \u2026),\n * NOT DDIC short codes (DTEL, DOMA, \u2026),\n * - text entries use { attribute, value }.\n */\n\nimport { z } from 'zod';\nimport { CDS_ENTITY_OWNERS, CDS_ENTITY_TARGET, type Capabilities } from './wire.js';\n\n// \u2500\u2500\u2500 Shared argument schemas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * XCO i18n target type. These are the exact literals the ABAP `CASE lv_target_type`\n * branches on \u2014 see ZCL_I18N_SERVICE. They are semantic object kinds, not DDIC codes.\n */\nexport const TargetTypeSchema = z\n .enum([\n 'cds_entity',\n 'data_element',\n 'domain',\n 'data_definition',\n 'message_class',\n 'text_pool',\n 'metadata_extension',\n 'application_log_object',\n 'business_configuration_object',\n 'text_table',\n ])\n .describe(\n 'XCO translation target type: data_element (DTEL), domain (DOMA fixed-value texts), ' +\n 'data_definition (CDS DDLS entity/field labels), message_class (MSAG), ' +\n 'text_pool (class/function-group text symbols), metadata_extension (DDLX UI labels), ' +\n 'application_log_object (APLO), business_configuration_object (SMBC), ' +\n 'text_table (a translatable text table \u2014 a DB table with delivery class C/S and exactly one ' +\n 'LANG key field, e.g. T005T; its non-key character columns are the text attributes). ' +\n 'cds_entity (VIRTUAL, LISA-only): a convenience target that bundles the texts of ONE named CDS ' +\n 'entity. It is NOT a backend type \u2014 LISA fans it out to the two real targets that hold that ' +\n 'entity\u2019s texts: data_definition (the view/DDLS itself) and metadata_extension (its DDLX). On read, ' +\n 'LISA calls both and returns the merged texts, each row carrying an `owner` field ' +\n '(data_definition | metadata_extension). On write, LISA groups the provided texts by their `owner` ' +\n 'and writes each group back to the matching real target in its own call (so each physical object is ' +\n 'locked once); the per-row `owner` returned by a read routes the write, so pass the rows back unchanged. ' +\n 'SCOPE: cds_entity covers the NAMED entity and its OWN DDLX ONLY. It does NOT reach the ' +\n 'underlying/parent views of an `as projection on` chain \u2014 e.g. the interface view ZI_\u2026 behind a ' +\n 'projection ZC_\u2026 \u2014 each of which carries its OWN @EndUserText.label and needs its OWN cds_entity (or ' +\n 'data_definition) call. On a RAP stack, run ONE pass per distinct CDS entity (one view = one call), ' +\n 'cds_entity on each. cds_entity is valid WITH or WITHOUT a DDLX: when the entity has no metadata ' +\n 'extension the data_definition is still written and the DDLX sub-call simply finds 0 texts (the call ' +\n 'stays valid but may report a partial status) \u2014 for a single-object view with no DDLX, data_definition ' +\n 'direct is cleaner. data_definition and metadata_extension remain available to address ONE physical ' +\n 'object explicitly (single-owner, unmerged). ' +\n 'The object types actually available on the target system are stated per tool (see each tool description).',\n );\n\nexport const LanguageSchema = z\n .string()\n .min(1)\n .max(2)\n .describe('Language \u2014 ISO 639-1 (EN, DE, FR\u2026) or SAP SPRAS single-char code (E, D, F\u2026)');\n\n/**\n * Optional selectors read by the ABAP handler to disambiguate sub-objects within a target.\n * Only the ones relevant to a given target_type are used; the rest are ignored server-side.\n */\nconst SelectorShape = {\n field_name: z\n .string()\n .optional()\n .describe('CDS field name (data_definition / metadata_extension) to scope to a single field'),\n fixed_value: z.string().optional().describe('Domain fixed value (lower limit) \u2014 required for target_type=domain'),\n message_number: z.string().optional().describe('Message number \u2014 required for target_type=message_class'),\n text_symbol_id: z.string().optional().describe('Text symbol id (e.g. \"001\") \u2014 for target_type=text_pool'),\n text_pool_owner_type: z\n .enum(['class', 'function_group'])\n .optional()\n .describe('Owner of the text pool \u2014 class (default) or function_group'),\n subobject_name: z.string().optional().describe('Sub-object name (e.g. application-log sub-object)'),\n position: z\n .string()\n .optional()\n .describe('1-based position for repeatable UI annotations (metadata_extension). Sent as a string.'),\n language_key_field_name: z\n .string()\n .optional()\n .describe(\n 'REQUIRED for target_type=text_table: the LANG key field of the text table (e.g. SPRAS). ' +\n 'Ignored by every other target_type.',\n ),\n master_key_fields: z\n .array(z.object({ name: z.string().min(1), value: z.string() }))\n .optional()\n .describe(\n 'REQUIRED for target_type=text_table: the master key fields that pin ONE record \u2014 every key ' +\n 'field EXCEPT the language field \u2014 as [{ name, value }] (e.g. [{ \"name\": \"LAND1\", \"value\": \"DE\" }]). ' +\n 'Must fix all master keys so a single record is targeted. Ignored by every other target_type.',\n ),\n} as const;\n\n// \u2500\u2500\u2500 Tool input schemas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const ListLanguagesSchema = z.object({});\n\nexport const GetTextsSchema = z.object({\n target_type: TargetTypeSchema,\n object_name: z.string().min(1).describe('Technical name of the SAP object, e.g. ZCL_MY_CLASS'),\n language: LanguageSchema.optional().describe(\n 'Language to read in (EN, DE, FR\u2026). Optional \u2014 when omitted, the object is read in its ' +\n 'original language and the effective language is returned in the response.',\n ),\n ...SelectorShape,\n});\n\nexport const SetTranslationSchema = z.object({\n target_type: TargetTypeSchema,\n object_name: z.string().min(1).describe('Technical name of the SAP object'),\n language: LanguageSchema,\n transport: z.string().min(1).describe('Transport request number, e.g. K900001'),\n texts: z\n .array(\n z.object({\n attribute: z\n .string()\n .min(1)\n .describe(\n 'XCO text attribute, e.g. short_field_label / medium_field_label / long_field_label / ' +\n 'heading_field_label (data_element), endusertext_label (data_definition/metadata_extension), ' +\n 'message_short_text (message_class), fixed_value_description (domain), ' +\n 'or a text column name like LANDX (text_table)',\n ),\n value: z.string().describe('Translated text value'),\n field_name: z\n .string()\n .optional()\n .describe(\n 'Per-entry CDS field name (data_definition / metadata_extension). When set it overrides the ' +\n 'top-level field_name for THIS entry, letting one call address several fields of the same ' +\n 'object. Omit (or leave the top-level field_name empty) for entity-level texts.',\n ),\n position: z\n .string()\n .optional()\n .describe(\n 'Per-entry 1-based position for repeatable UI annotations (e.g. ui_lineitem_label). Overrides ' +\n 'the top-level position for THIS entry. Sent as a string. Equivalently, the attribute may keep ' +\n 'the bracketed form it was read in (e.g. \"ui_lineitem_label[2]\") \u2014 the index round-trips either ' +\n 'way and is never renumbered.',\n ),\n owner: z\n .string()\n .optional()\n .describe(\n 'Required when target_type=cds_entity: which physical object this text belongs to \u2014 ' +\n '\"data_definition\" (view/DDLS) or \"metadata_extension\" (DDLX). LISA routes each row to the ' +\n 'matching backend target by this value (a cds_entity write with any row missing `owner` is ' +\n 'rejected \u2014 LISA never guesses). Pass through verbatim the `owner` that TranslateGetTexts ' +\n 'stamped on the row. For entity-level texts (the view\u2019s own endusertext_label) leave ' +\n 'field_name empty. Positional UI labels must keep their `position` (string, 1-based). ' +\n 'Ignored for a single-target (data_definition / metadata_extension) or non-CDS write.',\n ),\n }),\n )\n .min(1)\n .describe(\n 'Array of text entries to write. Each entry may carry its own field_name/position so that ' +\n 'translations for MULTIPLE fields of one object (e.g. several ui_lineitem_label labels of a CDS ' +\n 'view) are written in a single call \u2014 the object is then locked only once.',\n ),\n ...SelectorShape,\n});\n\n// \u2500\u2500\u2500 Tool metadata \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const TOOLS = {\n TranslateListLanguages: {\n description: 'List all languages installed on the SAP system.',\n inputSchema: ListLanguagesSchema,\n },\n TranslateGetTexts: {\n description:\n \"Read all translatable texts of an SAP object in a given language, or in the object's \" +\n 'original language when none is specified. Returns every text slot with its full key ' +\n '(level, field_name, attribute), its value, and a `populated` flag ' +\n '(false = the slot exists but is empty in this language, i.e. still to translate). ' +\n 'To list only filled texts, keep entries with populated=true; to compare two languages, ' +\n 'call it once per language and diff on (key, populated, value). ' +\n 'For a CDS entity use target_type=cds_entity: a single call returns the merged texts of ONE named ' +\n 'entity \u2014 the view itself (data_definition) AND its OWN metadata extension/DDLX (metadata_extension) ' +\n 'together, so the DDLX labels come back without a separate metadata_extension call. This covers the ' +\n 'NAMED entity ONLY: it does NOT read the underlying/parent views of an `as projection on` chain (e.g. ' +\n 'the interface view ZI_\u2026 behind a projection ZC_\u2026), which carry their own labels \u2014 read EACH entity of ' +\n 'the stack with its own cds_entity call. Each cds_entity row always includes an `owner` ' +\n '(\"data_definition\" or \"metadata_extension\") identifying the physical object it lives in, and a ' +\n '`position` (string, 1-based) for positional UI labels. Positional labels keep the BARE attribute ' +\n '(e.g. \"ui_lineitem_label\") plus that separate `position` \u2014 they are NOT merged into ' +\n '\"ui_lineitem_label[1]\". Pass `owner`, `position`, field_name and attribute back to ' +\n 'TranslateSetTexts(cds_entity) VERBATIM for a correct round-trip.',\n inputSchema: GetTextsSchema,\n },\n TranslateSetTexts: {\n description:\n 'Write or update translations for an SAP object. Provide the transport request and an array ' +\n 'of { attribute, value } entries. Each entry may also carry its own field_name (and position) ' +\n 'to target a specific CDS field \u2014 so all fields of one object (e.g. every ui_lineitem_label) ' +\n 'can be written in a SINGLE call, locking the object only once. ' +\n 'For target_type=cds_entity, EVERY row must carry the `owner` (\"data_definition\" or ' +\n '\"metadata_extension\") that TranslateGetTexts stamped: LISA groups the rows by `owner` and writes ' +\n 'each group back to the matching physical object (view vs DDLX) in its own call, each ' +\n 'locked/transported once. This writes the NAMED entity and its OWN DDLX ONLY \u2014 NOT the ' +\n 'underlying/parent views of an `as projection on` chain (e.g. the interface view ZI_\u2026 behind a ' +\n 'projection ZC_\u2026); translate EACH entity of the stack with its own cds_entity call. A cds_entity ' +\n 'write with any row missing `owner` is REJECTED (LISA never guesses the object). Entity-level texts ' +\n '(the view\u2019s own endusertext_label) must go out with an EMPTY field_name; positional UI labels keep ' +\n 'the bare attribute plus their `position` (string, 1-based) \u2014 never renumber or bracket it. The result ' +\n 'reports, per owner, how many texts were written and the sub-call status; writes are not atomic across ' +\n 'the two objects, so a partial write returns success=false with both outcomes (a cds_entity write to a ' +\n 'view with no DDLX is still valid \u2014 the metadata_extension sub-call just finds nothing; for such ' +\n 'single-object views, data_definition direct avoids the empty DDLX sub-call). ' +\n 'For the single targets (data_definition / metadata_extension and non-CDS types) nothing changes: ' +\n 'one 1:1 backend write, and entries without their own field_name/position fall back to the ' +\n 'top-level field_name and `position`.',\n inputSchema: SetTranslationSchema,\n },\n} as const;\n\nexport type ToolName = keyof typeof TOOLS;\n\n/**\n * Per-action sentence appended to a tool's description so the agent knows up-front which\n * target_type values THIS system accepts for the operation. When the backend declares an\n * allow-list for the action we state it concretely; otherwise we fall back to the generic\n * stack-differences caveat (older handler / capabilities probe unavailable).\n */\nexport function supportedTargetTypesNote(action: string, capabilities: Capabilities | null): string {\n const allowed = capabilities?.[action];\n if (allowed && allowed.length > 0) {\n // `cds_entity` is a LISA-synthesized target: it never reaches the backend (the MCP fans it out to\n // data_definition + metadata_extension \u2014 see intent.ts), so the handler's allow-list won't contain\n // it. Advertise it ourselves whenever BOTH physical owners are supported for this action, so the\n // agent knows the merged CDS surface is available for this operation (read AND write) without each\n // ABAP variant having to hardcode the virtual type. Guarded against duplication if a handler does\n // list it.\n const types = [...allowed];\n if (CDS_ENTITY_OWNERS.every((owner) => allowed.includes(owner)) && !types.includes(CDS_ENTITY_TARGET)) {\n types.push(CDS_ENTITY_TARGET);\n }\n return `On THIS SAP system, '${action}' supports these target_type values: ${types.join(', ')}. Any other target_type is rejected before reaching SAP.`;\n }\n return 'STACK DIFFERENCES: public cloud / BTP ABAP Environment and on-premise / private cloud support DIFFERENT object types per operation, and the supported set can also differ by system version. A target_type the target system does not support for this operation is rejected up-front.';\n}\n", "import { CDS_ENTITY_TARGET, GetTextsSchema, I18nCore, TOOLS, narrowListTexts } from '@lisa/core';\nimport { OperationType, defineTool } from 'arc-1/public';\nimport { ctxHttpTransport } from '../transport.js';\n\n/**\n * LISA TranslateGetTexts (whole-object reader, list_texts), as an ARC-1 extension tool.\n *\n * Reuses the core reader \u2014 including the position decomposition + guaranteed `populated`\n * (normalizeListTextEntry) \u2014 then applies the same client-side field_name/position narrowing\n * the standalone server does. READ op exposed as POST, hence scope:'write' (see ListLanguages note).\n */\nexport default defineTool({\n name: 'Custom_TranslateGetTexts',\n description: TOOLS.TranslateGetTexts.description,\n schema: GetTextsSchema,\n policy: { scope: 'write', opType: OperationType.Read },\n availableOn: 'all',\n async handler(args, ctx) {\n const a = args as {\n target_type: string;\n object_name: string;\n language?: string;\n text_pool_owner_type?: string;\n field_name?: string;\n position?: string;\n language_key_field_name?: string;\n master_key_fields?: Array<{ name: string; value: string }>;\n };\n const core = new I18nCore(ctxHttpTransport(ctx.http));\n\n // cds_entity = the merged CDS surface (view + its DDLX), each row owner-stamped by the backend.\n const result =\n a.target_type === CDS_ENTITY_TARGET\n ? await core.getCdsEntityTexts({ object_name: a.object_name, language: a.language })\n : await core.getTexts({\n target_type: a.target_type,\n object_name: a.object_name,\n language: a.language,\n text_pool_owner_type: a.text_pool_owner_type,\n language_key_field_name: a.language_key_field_name,\n master_key_fields: a.master_key_fields,\n });\n\n const texts = narrowListTexts(result.texts, { field_name: a.field_name, position: a.position });\n\n return { content: [{ type: 'text', text: JSON.stringify({ ...result, texts }, null, 2) }] };\n },\n});\n", "import type { I18nHttpResponse, I18nTransport, WireAction } from '@lisa/core';\nimport type { SafeHttpClient } from 'arc-1/public';\n\n/**\n * The ARC-1 side of the `I18nTransport` seam: perform the POST to LISA's custom ICF\n * service through `ctx.http`, ARC-1's authenticated SAP client (per-user principal propagation,\n * CSRF fetched + attached automatically).\n *\n * Path: `${servicePath}/${action}`, default servicePath `/sap/bc/http/sap/zi18n_service`\n * (override with SAP_I18N_SERVICE_PATH). The path is NON-ADT, so it rides ARC-1's gated raw-write\n * surface \u2014 which is why each tool declares `scope:'write'` and the deployment needs\n * `SAP_ALLOW_PLUGIN_RAW_WRITES` (see docs_page/arc1-extension-deployment.md).\n */\n\nconst DEFAULT_SERVICE_PATH = '/sap/bc/http/sap/zi18n_service';\n\nfunction servicePath(): string {\n const p = process.env.SAP_I18N_SERVICE_PATH?.trim();\n return (p && p.length > 0 ? p : DEFAULT_SERVICE_PATH).replace(/\\/$/, '');\n}\n\nexport function ctxHttpTransport(http: SafeHttpClient): I18nTransport {\n const base = servicePath();\n return {\n async post(action: WireAction, jsonBody: string): Promise<I18nHttpResponse> {\n const res = await http.post(`${base}/${action}`, jsonBody, 'application/json', {\n Accept: 'application/json',\n });\n return { status: res.statusCode, body: res.body };\n },\n };\n}\n", "import { I18nCore, ListLanguagesSchema, TOOLS } from '@lisa/core';\nimport { OperationType, defineTool } from 'arc-1/public';\nimport { ctxHttpTransport } from '../transport.js';\n\n/**\n * LISA TranslateListLanguages, as an ARC-1 extension tool.\n *\n * READ operation \u2014 but LISA exposes EVERY action as a POST (the ABAP handler reads the action\n * from ~path_info and parses params from the JSON body), and ctx.http gates by HTTP method, so\n * this still needs scope:'write' + SAP_ALLOW_PLUGIN_RAW_WRITES. `opType: Read` keeps the\n * DECLARED operation honest (it only reads). To make this a clean scope:'read', expose\n * list_languages over GET (query-string) in ZCL_I18N_SERVICE \u2014 see the package README.\n */\nexport default defineTool({\n name: 'Custom_TranslateListLanguages',\n description: TOOLS.TranslateListLanguages.description,\n schema: ListLanguagesSchema,\n policy: { scope: 'write', opType: OperationType.Read },\n availableOn: 'all',\n async handler(_args, ctx) {\n const core = new I18nCore(ctxHttpTransport(ctx.http));\n const languages = await core.listLanguages();\n return { content: [{ type: 'text', text: JSON.stringify(languages, null, 2) }] };\n },\n});\n", "import { CDS_ENTITY_TARGET, I18nCore, SetTranslationSchema, TOOLS } from '@lisa/core';\nimport type { SetTextEntry } from '@lisa/core';\nimport { OperationType, defineTool } from 'arc-1/public';\nimport { ctxHttpTransport } from '../transport.js';\n\n/**\n * LISA TranslateSetTexts, as an ARC-1 extension tool \u2014 the actual WRITE.\n *\n * Gated: refused unless SAP_ALLOW_PLUGIN_RAW_WRITES=true + SAP_ALLOW_WRITES=true and scope:'write'.\n * The write lands in the caller-supplied `transport`; the object is locked once even when the\n * `texts` array addresses several CDS fields (per-entry field_name/position). Non-ADT path, so\n * SAP_ALLOWED_PACKAGES does not apply \u2014 SAP-side auth on ZI18N_SERVICE is the backstop.\n */\nexport default defineTool({\n name: 'Custom_TranslateSetTexts',\n description: TOOLS.TranslateSetTexts.description,\n schema: SetTranslationSchema,\n policy: { scope: 'write', opType: OperationType.Update },\n availableOn: 'all',\n async handler(args, ctx) {\n const a = args as {\n target_type: string;\n object_name: string;\n language: string;\n transport: string;\n texts: SetTextEntry[];\n field_name?: string;\n fixed_value?: string;\n message_number?: string;\n text_symbol_id?: string;\n text_pool_owner_type?: string;\n subobject_name?: string;\n position?: string;\n language_key_field_name?: string;\n master_key_fields?: Array<{ name: string; value: string }>;\n };\n const core = new I18nCore(ctxHttpTransport(ctx.http));\n\n // cds_entity = the merged CDS surface: group rows by `owner` and write each group to its physical\n // object (one backend call each). Any other target_type is a single 1:1 write.\n const result =\n a.target_type === CDS_ENTITY_TARGET\n ? await core.setCdsEntityTexts({\n object_name: a.object_name,\n language: a.language,\n transport: a.transport,\n texts: a.texts,\n })\n : await core.setTranslation({\n target_type: a.target_type,\n object_name: a.object_name,\n language: a.language,\n transport: a.transport,\n texts: a.texts,\n field_name: a.field_name,\n fixed_value: a.fixed_value,\n message_number: a.message_number,\n text_symbol_id: a.text_symbol_id,\n text_pool_owner_type: a.text_pool_owner_type,\n subobject_name: a.subobject_name,\n position: a.position,\n language_key_field_name: a.language_key_field_name,\n master_key_fields: a.master_key_fields,\n });\n\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\n },\n});\n", "import type { Plugin } from 'arc-1/public';\nimport getTexts from './tools/Custom_TranslateGetTexts.js';\nimport listLanguages from './tools/Custom_TranslateListLanguages.js';\nimport setTexts from './tools/Custom_TranslateSetTexts.js';\n\n/**\n * LISA as an ARC-1 extension \u2014 the three translation tools loaded IN-PROCESS by an ARC-1\n * instance, reusing its authenticated SAP client, safety ceiling, scope policy, audit, and\n * per-user principal propagation. No second auth stack, no second deployment.\n *\n * Load it on the ARC-1 side with:\n * ARC1_PLUGINS=/abs/path/to/lisa-arc1-extension/dist/index.js\n * SAP_ALLOW_WRITES=true\n * SAP_ALLOW_PLUGIN_RAW_WRITES=true # all three tools POST \u2192 require the raw-write opt-in\n * SAP_I18N_SERVICE_PATH=/sap/bc/http/sap/zi18n_service # optional override\n *\n * Requires LISA's ZCL_I18N_SERVICE (or _CLOUD) handler class imported on the target SAP system.\n * See docs_page/arc1-extension-deployment.md for the full runbook.\n */\nconst plugin: Plugin = {\n name: 'lisa-arc1-extension',\n version: '0.3.0',\n apiVersion: 1,\n tools: [listLanguages, getTexts, setTexts],\n};\n\nexport default plugin;\n"],
5
+ "mappings": "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",
6
+ "names": ["OperationType", "defineTool", "defineTool", "OperationType", "OperationType", "defineTool", "defineTool", "OperationType"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@lisa-mcp/arc1-extension",
3
+ "version": "0.3.0",
4
+ "description": "LISA SAP-translation tools packaged as an ARC-1 extension (loaded in-process via ARC1_PLUGINS)",
5
+ "keywords": [
6
+ "arc-1",
7
+ "arc1",
8
+ "mcp",
9
+ "sap",
10
+ "translation",
11
+ "i18n",
12
+ "s4hana",
13
+ "plugin",
14
+ "extension"
15
+ ],
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/ClementRingot/LISA.git",
20
+ "directory": "packages/arc1-extension"
21
+ },
22
+ "homepage": "https://github.com/ClementRingot/LISA/tree/main/packages/arc1-extension#readme",
23
+ "bugs": "https://github.com/ClementRingot/LISA/issues",
24
+ "type": "module",
25
+ "main": "dist/index.js",
26
+ "module": "dist/index.js",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=22"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "provenance": true
44
+ },
45
+ "scripts": {
46
+ "build": "tsc --noEmit && node esbuild.config.mjs",
47
+ "prepack": "npm run build",
48
+ "test": "vitest run",
49
+ "lint": "biome check src/",
50
+ "clean": "rm -rf dist"
51
+ },
52
+ "peerDependencies": {
53
+ "arc-1": ">=0.9.20",
54
+ "zod": "^4.3.6"
55
+ },
56
+ "devDependencies": {
57
+ "@lisa/core": "*",
58
+ "arc-1": "^0.9.20",
59
+ "esbuild": "^0.28.1",
60
+ "typescript": "^5.7.2",
61
+ "vitest": "^4.1.9",
62
+ "zod": "^4.3.6"
63
+ }
64
+ }