arc-1 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,6 +63,26 @@ export declare function handleToolCall(client: AdtClient, config: ServerConfig,
63
63
  */
64
64
  export declare function warnCdsReservedKeywords(source: string): string | undefined;
65
65
  export declare function buildCreateXml(type: string, name: string, pkg: string, description: string, properties?: Record<string, unknown>): string;
66
+ /**
67
+ * Strip SAPGUI-style function-module parameter comment blocks from an FM source body.
68
+ *
69
+ * SAP rejects PUT-to-source/main with parameter comment blocks (verified live on a4h
70
+ * S/4HANA 2023 — issue #250):
71
+ * HTTP 400 / com.sap.adt.sedi / ExceptionResourceScanDuringSaveFailure
72
+ * "Parameter comment blocks are not allowed" (T100KEY FUNC_ADT028)
73
+ *
74
+ * The signature is metadata, not source. LLMs frequently emit the SAPGUI block out
75
+ * of muscle memory (every released FM ships with one). This helper strips lines whose
76
+ * first non-whitespace tokens are `*"` so the PUT succeeds, and reports back whether
77
+ * stripping occurred so the caller can append a warning to the response.
78
+ *
79
+ * Only `*"…` lines are stripped — single `*` ABAP comments and inline `"` comments
80
+ * are preserved. Exported for unit tests.
81
+ */
82
+ export declare function stripFmParamCommentBlock(source: string): {
83
+ source: string;
84
+ wasStripped: boolean;
85
+ };
66
86
  export declare const SLASH_TYPE_MAP: Record<string, string>;
67
87
  /**
68
88
  * Citation guard companion for SLASH_TYPE_MAP. Keys MUST stay key-equal to
@@ -1 +1 @@
1
- {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/handlers/intent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/handlers/intent.ts"],"names":[],"mappings":"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"}
@@ -1590,12 +1590,22 @@ const SERVICEBINDING_V2_CONTENT_TYPE = 'application/vnd.sap.adt.businessservices
1590
1590
  const BDEF_CONTENT_TYPE = 'application/vnd.sap.adt.blues.v1+xml';
1591
1591
  const MESSAGECLASS_CONTENT_TYPE = 'application/vnd.sap.adt.mc.messageclass+xml';
1592
1592
  const SKTD_V2_CONTENT_TYPE = 'application/vnd.sap.adt.sktdv2+xml';
1593
+ // Function group + function module content types — verified live on a4h S/4HANA 2023
1594
+ // (issue #250). FUGR uses the v3 group envelope; FUNC uses the unversioned fmodule envelope.
1595
+ const FUNCTION_GROUP_CONTENT_TYPE = 'application/vnd.sap.adt.functions.groups.v3+xml';
1596
+ const FUNCTION_MODULE_CONTENT_TYPE = 'application/vnd.sap.adt.functions.fmodules+xml';
1593
1597
  function isMetadataWriteType(type) {
1594
1598
  return type === 'DOMA' || type === 'DTEL' || type === 'MSAG' || type === 'SRVB';
1595
1599
  }
1596
1600
  /** Types that require a specific vendor content type for creation (not application/*) */
1597
1601
  function needsVendorContentType(type) {
1598
- return type === 'DOMA' || type === 'DTEL' || type === 'BDEF' || type === 'MSAG' || type === 'SKTD';
1602
+ return (type === 'DOMA' ||
1603
+ type === 'DTEL' ||
1604
+ type === 'BDEF' ||
1605
+ type === 'MSAG' ||
1606
+ type === 'SKTD' ||
1607
+ type === 'FUGR' ||
1608
+ type === 'FUNC');
1599
1609
  }
1600
1610
  /** Content type used for create POST */
1601
1611
  function createContentTypeForType(type) {
@@ -1634,6 +1644,10 @@ function vendorContentTypeForType(type) {
1634
1644
  return MESSAGECLASS_CONTENT_TYPE;
1635
1645
  case 'SKTD':
1636
1646
  return SKTD_V2_CONTENT_TYPE;
1647
+ case 'FUGR':
1648
+ return FUNCTION_GROUP_CONTENT_TYPE;
1649
+ case 'FUNC':
1650
+ return FUNCTION_MODULE_CONTENT_TYPE;
1637
1651
  default:
1638
1652
  // Wildcard lets the SAP server resolve the correct handler.
1639
1653
  // Sending 'application/xml' causes 415 on DDL-based endpoints
@@ -1684,6 +1698,9 @@ function getMetadataWriteProperties(input) {
1684
1698
  category: input.category,
1685
1699
  version: input.version,
1686
1700
  odataVersion: input.odataVersion,
1701
+ // Function-module create needs the parent function-group name for the
1702
+ // <adtcore:containerRef> in the create payload (issue #250).
1703
+ group: input.group,
1687
1704
  };
1688
1705
  return props;
1689
1706
  }
@@ -2082,6 +2099,30 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2082
2099
  };
2083
2100
  return buildMessageClassXml(params);
2084
2101
  }
2102
+ case 'FUGR':
2103
+ // Function group create envelope. POSTed to /sap/bc/adt/functions/groups
2104
+ // with Content-Type: application/vnd.sap.adt.functions.groups.v3+xml.
2105
+ // Verified live on a4h S/4HANA 2023 (issue #250).
2106
+ return `<?xml version="1.0" encoding="UTF-8"?>
2107
+ <group:abapFunctionGroup xmlns:group="http://www.sap.com/adt/functions/groups" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${escapeXml(description)}" adtcore:language="EN" adtcore:name="${escapeXml(name)}" adtcore:type="FUGR/F" adtcore:masterLanguage="EN">
2108
+ <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
2109
+ </group:abapFunctionGroup>`;
2110
+ case 'FUNC': {
2111
+ // Function module create envelope. POSTed to
2112
+ // /sap/bc/adt/functions/groups/{group_lc}/fmodules with
2113
+ // Content-Type: application/vnd.sap.adt.functions.fmodules+xml.
2114
+ // No <adtcore:packageRef> — FM inherits package from the parent FUGR.
2115
+ // adtcore:uri must be lowercase (verified live on a4h).
2116
+ const group = String(properties?.group ?? '').trim();
2117
+ if (!group) {
2118
+ throw new Error('FUNC create requires "group" property — pass it via SAPWrite args (the parent function group must already exist).');
2119
+ }
2120
+ const groupLc = encodeURIComponent(group.toLowerCase());
2121
+ return `<?xml version="1.0" encoding="UTF-8"?>
2122
+ <fmodule:abapFunctionModule xmlns:fmodule="http://www.sap.com/adt/functions/fmodules" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${escapeXml(description)}" adtcore:name="${escapeXml(name)}" adtcore:type="FUGR/FF">
2123
+ <adtcore:containerRef adtcore:name="${escapeXml(group)}" adtcore:type="FUGR/F" adtcore:uri="/sap/bc/adt/functions/groups/${groupLc}"/>
2124
+ </fmodule:abapFunctionModule>`;
2125
+ }
2085
2126
  default:
2086
2127
  // Fallback — generic objectReferences using the correct URL for the type
2087
2128
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -2090,6 +2131,27 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2090
2131
  </adtcore:objectReferences>`;
2091
2132
  }
2092
2133
  }
2134
+ /**
2135
+ * Strip SAPGUI-style function-module parameter comment blocks from an FM source body.
2136
+ *
2137
+ * SAP rejects PUT-to-source/main with parameter comment blocks (verified live on a4h
2138
+ * S/4HANA 2023 — issue #250):
2139
+ * HTTP 400 / com.sap.adt.sedi / ExceptionResourceScanDuringSaveFailure
2140
+ * "Parameter comment blocks are not allowed" (T100KEY FUNC_ADT028)
2141
+ *
2142
+ * The signature is metadata, not source. LLMs frequently emit the SAPGUI block out
2143
+ * of muscle memory (every released FM ships with one). This helper strips lines whose
2144
+ * first non-whitespace tokens are `*"` so the PUT succeeds, and reports back whether
2145
+ * stripping occurred so the caller can append a warning to the response.
2146
+ *
2147
+ * Only `*"…` lines are stripped — single `*` ABAP comments and inline `"` comments
2148
+ * are preserved. Exported for unit tests.
2149
+ */
2150
+ export function stripFmParamCommentBlock(source) {
2151
+ const lines = source.split('\n');
2152
+ const kept = lines.filter((line) => !/^\s*\*"/.test(line));
2153
+ return { source: kept.join('\n'), wasStripped: kept.length !== lines.length };
2154
+ }
2093
2155
  /** Escape special characters for XML attribute values */
2094
2156
  function escapeXml(s) {
2095
2157
  return s
@@ -2453,12 +2515,42 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
2453
2515
  // (transparent) or /structures/ (DDIC structure). Resolve once via the client's
2454
2516
  // cached URL probe. For 'create' the default /tables/ URL is correct (we only
2455
2517
  // create transparent tables today; structure creation is out of scope).
2518
+ //
2519
+ // For FUNC, the URL has the parent function group baked into the path:
2520
+ // /sap/bc/adt/functions/groups/{group_lc}/fmodules/{name_lc}
2521
+ // `objectBasePath('FUNC')` deliberately throws (PR #223 — generic URL builders
2522
+ // must fail loudly for FM since they can't know the parent group). Issue #250:
2523
+ // we pre-resolve the URL here from `args.group` (required for create; auto-
2524
+ // resolved via search for update/delete) so the action switch downstream uses
2525
+ // the correct URL. We also mirror the resolved group back onto args so
2526
+ // `buildCreateXml('FUNC', …, properties)` finds it.
2456
2527
  let objectUrl;
2457
2528
  let srcUrl;
2458
2529
  if (type === 'TABL' && action !== 'create' && action !== 'batch_create') {
2459
2530
  objectUrl = await client.resolveTablObjectUrl(name);
2460
2531
  srcUrl = `${objectUrl}/source/main`;
2461
2532
  }
2533
+ else if (type === 'FUNC') {
2534
+ let group = String(args.group ?? '').trim();
2535
+ if (!group) {
2536
+ if (action === 'create') {
2537
+ return errorResult('"group" is required to create a FUNC. Create the parent function group first (SAPWrite type=FUGR) or pass group explicitly.');
2538
+ }
2539
+ // For update/delete try to auto-resolve the group via search
2540
+ const resolved = cachingLayer
2541
+ ? await cachingLayer.resolveFuncGroup(client, name)
2542
+ : await client.resolveFunctionGroup(name);
2543
+ if (!resolved) {
2544
+ return errorResult(`Cannot resolve function group for FM "${name}". Provide the "group" parameter explicitly, or use SAPSearch to find the parent group.`);
2545
+ }
2546
+ group = resolved;
2547
+ }
2548
+ const groupLc = encodeURIComponent(group.toLowerCase());
2549
+ objectUrl = `/sap/bc/adt/functions/groups/${groupLc}/fmodules/${encodeURIComponent(name.toLowerCase())}`;
2550
+ srcUrl = `${objectUrl}/source/main`;
2551
+ // Pass the resolved group through to buildCreateXml via args.group
2552
+ args.group = group;
2553
+ }
2462
2554
  else {
2463
2555
  objectUrl = objectUrlForType(type, name);
2464
2556
  srcUrl = sourceUrlForType(type, name);
@@ -2514,19 +2606,34 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
2514
2606
  const cdsGuardUpdate = guardCdsSyntax(type, source, cachedFeatures);
2515
2607
  if (cdsGuardUpdate)
2516
2608
  return cdsGuardUpdate;
2517
- // Pre-write lint validation
2518
- const lintWarnings = runPreWriteLint(source, type, name, config, lintOverride);
2609
+ // FUNC-source sanitization: strip SAPGUI-style parameter comment blocks.
2610
+ // SAP rejects PUT-to-source/main with these blocks (HTTP 400 / FUNC_ADT028
2611
+ // "Parameter comment blocks are not allowed" — verified live a4h S/4HANA 2023,
2612
+ // issue #250). LLMs frequently emit them out of muscle memory because every
2613
+ // released FM has one. Strip and warn rather than fail.
2614
+ let effectiveSource = source;
2615
+ let fmParamStripWarning;
2616
+ if (type === 'FUNC') {
2617
+ const stripped = stripFmParamCommentBlock(source);
2618
+ effectiveSource = stripped.source;
2619
+ if (stripped.wasStripped) {
2620
+ fmParamStripWarning =
2621
+ 'Stripped *"…IMPORTING/EXPORTING…*" parameter comment blocks (SAP rejects them on PUT — manage FM parameters via SAPGUI/Eclipse).';
2622
+ }
2623
+ }
2624
+ // Pre-write lint validation (uses sanitized source for FUNC)
2625
+ const lintWarnings = runPreWriteLint(effectiveSource, type, name, config, lintOverride);
2519
2626
  if (lintWarnings.blocked)
2520
2627
  return lintWarnings.result;
2521
2628
  // Pre-write server-side syntax check (opt-in; never blocks — warnings only).
2522
- const checkNotes = await runPreWriteSyntaxCheck(client, type, source, objectUrl, config, checkOverride);
2629
+ const checkNotes = await runPreWriteSyntaxCheck(client, type, effectiveSource, objectUrl, config, checkOverride);
2523
2630
  // If safeUpdateSource throws (lock conflict, network error, etc.), checkNotes
2524
2631
  // is intentionally discarded — pre-check warnings only matter when the write succeeded.
2525
- await safeUpdateSource(client.http, client.safety, objectUrl, srcUrl, source, transport, cachedFeatures?.abapRelease);
2632
+ await safeUpdateSource(client.http, client.safety, objectUrl, srcUrl, effectiveSource, transport, cachedFeatures?.abapRelease);
2526
2633
  invalidateWrittenObject(type, name);
2527
2634
  const msg = `Successfully updated ${type} ${name}.`;
2528
2635
  const cdsUpdateHint = type === 'DDLS' ? await buildCdsUpdateCrudHint(client, name, objectUrl) : undefined;
2529
- const warnings = mergePreWriteWarnings(preflightWarnings.warnings, lintWarnings.warnings, checkNotes, cdsUpdateHint);
2636
+ const warnings = mergePreWriteWarnings(preflightWarnings.warnings, lintWarnings.warnings, checkNotes, cdsUpdateHint, fmParamStripWarning);
2530
2637
  return warnings ? textResult(`${msg}\n\n${warnings}`) : textResult(msg);
2531
2638
  }
2532
2639
  case 'create': {
@@ -2697,15 +2804,26 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
2697
2804
  }
2698
2805
  // Step 2: Write source code if provided
2699
2806
  if (source) {
2807
+ // FUNC: strip SAPGUI parameter comment blocks (see update path for rationale).
2808
+ let createSource = source;
2809
+ let fmParamStripWarning;
2810
+ if (type === 'FUNC') {
2811
+ const stripped = stripFmParamCommentBlock(source);
2812
+ createSource = stripped.source;
2813
+ if (stripped.wasStripped) {
2814
+ fmParamStripWarning =
2815
+ 'Stripped *"…IMPORTING/EXPORTING…*" parameter comment blocks (manage FM parameters via SAPGUI/Eclipse).';
2816
+ }
2817
+ }
2700
2818
  // Pre-write lint validation
2701
- const lintWarnings = runPreWriteLint(source, type, name, config, lintOverride);
2819
+ const lintWarnings = runPreWriteLint(createSource, type, name, config, lintOverride);
2702
2820
  if (lintWarnings.blocked) {
2703
2821
  return textResult(`Created ${type} ${name} in package ${pkg}, but source was rejected by lint:\n${lintWarnings.result.content[0].text}`);
2704
2822
  }
2705
- await safeUpdateSource(client.http, client.safety, objectUrl, srcUrl, source, effectiveTransport, cachedFeatures?.abapRelease);
2823
+ await safeUpdateSource(client.http, client.safety, objectUrl, srcUrl, createSource, effectiveTransport, cachedFeatures?.abapRelease);
2706
2824
  invalidateWrittenObject(type, name);
2707
2825
  const msg = `Created ${type} ${name} in package ${pkg} and wrote source code.`;
2708
- const warnings = mergePreWriteWarnings(preflightWarnings.warnings, lintWarnings.warnings);
2826
+ const warnings = mergePreWriteWarnings(preflightWarnings.warnings, lintWarnings.warnings, fmParamStripWarning);
2709
2827
  return warnings ? textResult(`${msg}\n\n${warnings}`) : textResult(msg);
2710
2828
  }
2711
2829
  return textResult(`Created ${type} ${name} in package ${pkg}.\n${result}`);
@@ -3421,10 +3539,33 @@ async function handleSAPActivate(client, args, cachingLayer) {
3421
3539
  // Resolve URLs sequentially. For TABL we await the URL resolver so DDIC
3422
3540
  // structures (which live at /sap/bc/adt/ddic/structures/) are addressed
3423
3541
  // correctly; the resolver short-circuits on its in-memory cache.
3542
+ // For FUNC the URL needs the parent function-group baked into the path
3543
+ // (issue #250); each batch entry must carry `group` or be auto-resolvable
3544
+ // by name.
3424
3545
  const objects = await Promise.all(rawObjects.map(async (o) => {
3425
3546
  const objType = normalizeObjectType(String(o.type ?? type));
3426
3547
  const objName = String(o.name ?? '');
3427
- const url = objType === 'TABL' ? await client.resolveTablObjectUrl(objName) : objectUrlForType(objType, objName);
3548
+ let url;
3549
+ if (objType === 'TABL') {
3550
+ url = await client.resolveTablObjectUrl(objName);
3551
+ }
3552
+ else if (objType === 'FUNC') {
3553
+ let group = String(o.group ?? args.group ?? '').trim();
3554
+ if (!group) {
3555
+ const resolved = cachingLayer
3556
+ ? await cachingLayer.resolveFuncGroup(client, objName)
3557
+ : await client.resolveFunctionGroup(objName);
3558
+ if (!resolved) {
3559
+ throw new Error(`Cannot resolve function group for FM "${objName}" in batch activate. Provide "group" on each FUNC entry.`);
3560
+ }
3561
+ group = resolved;
3562
+ }
3563
+ const groupLc = encodeURIComponent(group.toLowerCase());
3564
+ url = `/sap/bc/adt/functions/groups/${groupLc}/fmodules/${encodeURIComponent(objName.toLowerCase())}`;
3565
+ }
3566
+ else {
3567
+ url = objectUrlForType(objType, objName);
3568
+ }
3428
3569
  return { type: objType, name: objName, url };
3429
3570
  }));
3430
3571
  const result = await activateBatch(client.http, client.safety, objects, activateOpts);
@@ -3451,7 +3592,30 @@ async function handleSAPActivate(client, args, cachingLayer) {
3451
3592
  // Single activation (existing behavior). For TABL we resolve the URL because
3452
3593
  // the existing object may live at /tables/ (transparent) or /structures/
3453
3594
  // (DDIC structure); using the wrong one would produce a confusing 404.
3454
- const objectUrl = type === 'TABL' ? await client.resolveTablObjectUrl(name) : objectUrlForType(type, name);
3595
+ // For FUNC the URL needs the parent function group baked into the path
3596
+ // (issue #250) — `objectBasePath('FUNC')` deliberately throws so generic
3597
+ // builders fail loudly. Auto-resolve the group when omitted.
3598
+ let objectUrl;
3599
+ if (type === 'TABL') {
3600
+ objectUrl = await client.resolveTablObjectUrl(name);
3601
+ }
3602
+ else if (type === 'FUNC') {
3603
+ let group = String(args.group ?? '').trim();
3604
+ if (!group) {
3605
+ const resolved = cachingLayer
3606
+ ? await cachingLayer.resolveFuncGroup(client, name)
3607
+ : await client.resolveFunctionGroup(name);
3608
+ if (!resolved) {
3609
+ return errorResult(`Cannot resolve function group for FM "${name}". Provide the "group" parameter explicitly.`);
3610
+ }
3611
+ group = resolved;
3612
+ }
3613
+ const groupLc = encodeURIComponent(group.toLowerCase());
3614
+ objectUrl = `/sap/bc/adt/functions/groups/${groupLc}/fmodules/${encodeURIComponent(name.toLowerCase())}`;
3615
+ }
3616
+ else {
3617
+ objectUrl = objectUrlForType(type, name);
3618
+ }
3455
3619
  const result = await activate(client.http, client.safety, objectUrl, { ...activateOpts, name });
3456
3620
  if (result.success) {
3457
3621
  cachingLayer?.invalidate(type, name, 'all');