arc-1-lsp 0.0.1

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.
Files changed (51) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +295 -0
  3. package/dist/adt-ls/cert.js +121 -0
  4. package/dist/adt-ls/cert.js.map +1 -0
  5. package/dist/adt-ls/destinations.js +116 -0
  6. package/dist/adt-ls/destinations.js.map +1 -0
  7. package/dist/adt-ls/discovery.js +59 -0
  8. package/dist/adt-ls/discovery.js.map +1 -0
  9. package/dist/adt-ls/driver.js +150 -0
  10. package/dist/adt-ls/driver.js.map +1 -0
  11. package/dist/adt-ls/lifecycle.js +96 -0
  12. package/dist/adt-ls/lifecycle.js.map +1 -0
  13. package/dist/adt-ls/mcp-federation.js +67 -0
  14. package/dist/adt-ls/mcp-federation.js.map +1 -0
  15. package/dist/adt-ls/mcp-lifecycle.js +10 -0
  16. package/dist/adt-ls/mcp-lifecycle.js.map +1 -0
  17. package/dist/adt-ls/repository.js +59 -0
  18. package/dist/adt-ls/repository.js.map +1 -0
  19. package/dist/adt-ls/session-retry.js +79 -0
  20. package/dist/adt-ls/session-retry.js.map +1 -0
  21. package/dist/adt-ls/tls-reverse-proxy.js +80 -0
  22. package/dist/adt-ls/tls-reverse-proxy.js.map +1 -0
  23. package/dist/btp/bridge.js +75 -0
  24. package/dist/btp/bridge.js.map +1 -0
  25. package/dist/btp/connectivity.js +27 -0
  26. package/dist/btp/connectivity.js.map +1 -0
  27. package/dist/btp/destination.js +22 -0
  28. package/dist/btp/destination.js.map +1 -0
  29. package/dist/btp/token.js +23 -0
  30. package/dist/btp/token.js.map +1 -0
  31. package/dist/btp/types.js +7 -0
  32. package/dist/btp/types.js.map +1 -0
  33. package/dist/btp/vcap.js +58 -0
  34. package/dist/btp/vcap.js.map +1 -0
  35. package/dist/index.js +35 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/server/auth.js +26 -0
  38. package/dist/server/auth.js.map +1 -0
  39. package/dist/server/config.js +53 -0
  40. package/dist/server/config.js.map +1 -0
  41. package/dist/server/engine.js +239 -0
  42. package/dist/server/engine.js.map +1 -0
  43. package/dist/server/http.js +52 -0
  44. package/dist/server/http.js.map +1 -0
  45. package/dist/server/logger.js +14 -0
  46. package/dist/server/logger.js.map +1 -0
  47. package/dist/server/safety.js +25 -0
  48. package/dist/server/safety.js.map +1 -0
  49. package/dist/server/server.js +186 -0
  50. package/dist/server/server.js.map +1 -0
  51. package/package.json +66 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * AdtLsDriver — spawn `adt-ls` headless and speak LSP over a named pipe, exactly
3
+ * as the sapse.adt-vscode extension does: args `-Djco.trace_path <dir> -data
4
+ * <dir> --pipe=<pipe>`; the client listens on the pipe and adt-ls connects to
5
+ * it. Uses vscode-jsonrpc for message framing.
6
+ *
7
+ * This is the engine: arc-1-lsp performs NO ADT HTTP/CSRF/locking/XML itself —
8
+ * everything goes through adt-ls via this driver (LSP) or its MCP endpoint.
9
+ */
10
+ import { spawn } from 'node:child_process';
11
+ import crypto from 'node:crypto';
12
+ import { promises as fsp } from 'node:fs';
13
+ import net from 'node:net';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import { StreamMessageReader, StreamMessageWriter, createMessageConnection, } from 'vscode-jsonrpc/node.js';
17
+ import { logger } from '../server/logger.js';
18
+ /**
19
+ * Route a server→client request to a registered handler, with safe defaults.
20
+ * Pure (no I/O) so it can be unit-tested directly.
21
+ * - registered handler wins (e.g. `adtLs/destinations/requestBrowserBasedLogon`)
22
+ * - `workspace/configuration` MUST return an array of nulls, one per item
23
+ * ("use defaults") — a bare null errors adt-ls's destination init.
24
+ * - everything else (client/registerCapability, window/workDoneProgress/create…)
25
+ * → null is accepted.
26
+ */
27
+ export function routeServerRequest(method, params, handlers) {
28
+ const handler = handlers[method];
29
+ if (handler)
30
+ return handler(params);
31
+ if (method === 'workspace/configuration') {
32
+ const items = params?.items ?? [];
33
+ return items.map(() => null);
34
+ }
35
+ return null;
36
+ }
37
+ function timeoutReject(ms) {
38
+ return new Promise((_, reject) => {
39
+ const t = setTimeout(() => reject(new Error(`adt-ls start timed out after ${ms}ms`)), ms);
40
+ t.unref();
41
+ });
42
+ }
43
+ export class AdtLsDriver {
44
+ binPath;
45
+ child;
46
+ server;
47
+ conn;
48
+ dataDir;
49
+ pipePath;
50
+ extraEnv;
51
+ requestHandlers;
52
+ initializeResult;
53
+ constructor(binPath, opts = {}) {
54
+ this.binPath = binPath;
55
+ const id = crypto.randomBytes(6).toString('hex');
56
+ this.dataDir = opts.dataDir ?? path.join(os.tmpdir(), `arc1lsp-${id}`);
57
+ this.extraEnv = opts.extraEnv ?? {};
58
+ this.requestHandlers = { ...opts.requestHandlers };
59
+ this.pipePath =
60
+ process.platform === 'win32'
61
+ ? path.join('\\\\.\\pipe\\', `arc1lsp-${id}`)
62
+ : path.join(os.tmpdir(), `arc1lsp-${id}.sock`);
63
+ }
64
+ /** Register/replace a server→client request handler (before or after start). */
65
+ setRequestHandler(method, handler) {
66
+ this.requestHandlers[method] = handler;
67
+ }
68
+ async start(timeoutMs = 60000) {
69
+ await fsp.mkdir(this.dataDir, { recursive: true });
70
+ if (process.platform !== 'win32') {
71
+ await fsp.rm(this.pipePath, { force: true });
72
+ }
73
+ const connected = new Promise((resolve, reject) => {
74
+ this.server = net.createServer((socket) => resolve(socket));
75
+ this.server.on('error', reject);
76
+ this.server.listen(this.pipePath);
77
+ });
78
+ const tail = [];
79
+ const capture = (d) => {
80
+ const s = d.toString();
81
+ tail.push(s);
82
+ if (tail.length > 60)
83
+ tail.shift();
84
+ };
85
+ this.child = spawn(this.binPath, ['-Djco.trace_path', this.dataDir, '-data', this.dataDir, `--pipe=${this.pipePath}`], {
86
+ stdio: ['ignore', 'pipe', 'pipe'],
87
+ env: { ...process.env, ...this.extraEnv },
88
+ });
89
+ this.child.stdout?.on('data', capture);
90
+ this.child.stderr?.on('data', (d) => {
91
+ capture(d);
92
+ logger.debug(`[adt-ls] ${d.toString().trimEnd()}`);
93
+ });
94
+ const exited = new Promise((_, reject) => {
95
+ this.child?.on('exit', (code) => reject(new Error(`adt-ls exited (code ${code}) before initialize. Last output:\n${tail.slice(-12).join('')}`)));
96
+ });
97
+ const socket = (await Promise.race([connected, exited, timeoutReject(timeoutMs)]));
98
+ this.conn = createMessageConnection(new StreamMessageReader(socket), new StreamMessageWriter(socket));
99
+ // Route every server->client request through the registered handlers (with
100
+ // safe defaults) so the handshake + logon never block. See routeServerRequest.
101
+ this.conn.onRequest((method, params) => routeServerRequest(method, params, this.requestHandlers));
102
+ this.conn.listen();
103
+ const result = (await this.conn.sendRequest('initialize', {
104
+ processId: process.pid,
105
+ clientInfo: { name: 'arc-1-lsp', version: '0.0.1' },
106
+ rootUri: null,
107
+ workspaceFolders: null,
108
+ capabilities: {},
109
+ // REQUIRED for any backend HTTP: adt-ls's UserAgentUtil builds the
110
+ // User-Agent from initializationOptions.userAgentInfos; if absent, its
111
+ // static initializer NPEs and every HTTP destination operation fails with
112
+ // "Could not initialize class …HttpRequestHeaderUtil". (Verified 2026-05-29.)
113
+ initializationOptions: { userAgentInfos: [{ name: 'arc-1-lsp', version: '0.0.1' }] },
114
+ }));
115
+ this.conn.sendNotification('initialized', {});
116
+ this.initializeResult = result;
117
+ logger.info(`adt-ls ready: ${result.serverInfo?.name} ${result.serverInfo?.version}`);
118
+ return result;
119
+ }
120
+ async sendRequest(method, params) {
121
+ if (!this.conn)
122
+ throw new Error('AdtLsDriver not started');
123
+ return this.conn.sendRequest(method, params);
124
+ }
125
+ async dispose() {
126
+ try {
127
+ this.conn?.dispose();
128
+ }
129
+ catch {
130
+ // best-effort
131
+ }
132
+ try {
133
+ this.child?.kill('SIGKILL');
134
+ }
135
+ catch {
136
+ // best-effort
137
+ }
138
+ try {
139
+ this.server?.close();
140
+ }
141
+ catch {
142
+ // best-effort
143
+ }
144
+ if (process.platform !== 'win32') {
145
+ await fsp.rm(this.pipePath, { force: true }).catch(() => { });
146
+ }
147
+ await fsp.rm(this.dataDir, { recursive: true, force: true }).catch(() => { });
148
+ }
149
+ }
150
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.js","sourceRoot":"","sources":["../../src/adt-ls/driver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { assertWriteAllowed } from '../server/safety.js';
2
+ import { deleteFile, getLsUri, includeAffUri, isUnsupportedPlaceholder, metadataAffUri, quickSearch, readFile, writeFile, } from './repository.js';
3
+ /** Unwrap a federated MCP result → structuredContent, or parsed text, or raw text. */
4
+ function parseFederated(res) {
5
+ const r = res;
6
+ const text = r?.content?.[0]?.text ?? '';
7
+ if (r?.structuredContent !== undefined)
8
+ return { ok: !r.isError, data: r.structuredContent, text };
9
+ try {
10
+ return { ok: !r?.isError, data: JSON.parse(text), text };
11
+ }
12
+ catch {
13
+ return { ok: !r?.isError, data: text, text };
14
+ }
15
+ }
16
+ export function createLifecycle(deps) {
17
+ const { driver, callTool, safety } = deps;
18
+ const dest = () => {
19
+ const d = deps.destination();
20
+ if (!d)
21
+ throw new Error('No ABAP destination is connected. Configure ARC1_SAP_* (see README).');
22
+ return d;
23
+ };
24
+ /** Resolve {name, objectType} → repotree AFF URI (search → getLsUri). */
25
+ async function resolveAffUri(ref) {
26
+ const d = dest();
27
+ const { references } = await quickSearch(driver, {
28
+ destination: d,
29
+ pattern: ref.name,
30
+ maxResults: 20,
31
+ types: [ref.objectType],
32
+ });
33
+ const hit = references.find((r) => r.name?.toUpperCase() === ref.name.toUpperCase() && r.uri) ??
34
+ references.find((r) => r.uri);
35
+ if (!hit?.uri) {
36
+ throw new Error(`Object ${ref.name} (${ref.objectType}) not found via search.`);
37
+ }
38
+ return getLsUri(driver, d, hit.uri);
39
+ }
40
+ return {
41
+ resolveAffUri,
42
+ async readSource(args) {
43
+ let uri = await resolveAffUri(args);
44
+ if (args.include)
45
+ uri = includeAffUri(uri, args.include);
46
+ const content = await readFile(driver, uri);
47
+ if (isUnsupportedPlaceholder(content)) {
48
+ throw new Error(`Object type ${args.objectType} is not served by adt-ls headless (classic ABAP). Use main ARC-1 for this type.`);
49
+ }
50
+ return content;
51
+ },
52
+ async createObject(args) {
53
+ assertWriteAllowed(safety, { action: 'create_object', packageName: args.packageName });
54
+ const res = await callTool('abap_creation-create_object', {
55
+ destination: dest(),
56
+ objectType: args.objectType,
57
+ objectContent: JSON.stringify({
58
+ name: args.name,
59
+ packageName: args.packageName,
60
+ description: args.description,
61
+ }),
62
+ });
63
+ const { ok, data, text } = parseFederated(res);
64
+ if (!ok)
65
+ throw new Error(`create_object failed: ${text}`);
66
+ const d = data;
67
+ return { message: d.message, filePath: d.filePath };
68
+ },
69
+ async updateSource(args) {
70
+ assertWriteAllowed(safety, { action: 'update_source' });
71
+ let uri = await resolveAffUri(args);
72
+ if (args.include)
73
+ uri = includeAffUri(uri, args.include);
74
+ await writeFile(driver, uri, args.source);
75
+ },
76
+ async activate(args) {
77
+ assertWriteAllowed(safety, { action: 'activate_object' });
78
+ const uri = await resolveAffUri(args);
79
+ const res = await callTool('abap_activate_objects', { destination: dest(), uris: [uri] });
80
+ const { data } = parseFederated(res);
81
+ const d = (data ?? {});
82
+ return { success: !!d.success, diagnostics: d.objectDiagnostics ?? [] };
83
+ },
84
+ async runUnitTests(args) {
85
+ const uri = await resolveAffUri(args);
86
+ const res = await callTool('abap_run_unit_tests', { destination: dest(), uris: [uri] });
87
+ return parseFederated(res).data;
88
+ },
89
+ async deleteObject(args) {
90
+ assertWriteAllowed(safety, { action: 'delete_object' });
91
+ const uri = await resolveAffUri(args);
92
+ await deleteFile(driver, metadataAffUri(uri));
93
+ },
94
+ };
95
+ }
96
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/adt-ls/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Minimal Streamable-HTTP MCP client to adt-ls's own `/mcp` endpoint. arc-1-lsp
3
+ * federates adt-ls's tools through this client (the stable, public channel),
4
+ * adding its own auth/scope/governance in front. Parses SSE-framed responses.
5
+ */
6
+ import { logger } from '../server/logger.js';
7
+ function parseSse(text) {
8
+ for (const line of text.split('\n')) {
9
+ const s = line.startsWith('data: ') ? line.slice(6) : line;
10
+ if (s.trim().startsWith('{')) {
11
+ try {
12
+ return JSON.parse(s);
13
+ }
14
+ catch {
15
+ /* not the JSON data line */
16
+ }
17
+ }
18
+ }
19
+ return null;
20
+ }
21
+ export class AdtLsMcpClient {
22
+ baseUrl;
23
+ token;
24
+ sessionId;
25
+ constructor(baseUrl, token) {
26
+ this.baseUrl = baseUrl;
27
+ this.token = token;
28
+ }
29
+ async rpc(body) {
30
+ const headers = {
31
+ Authorization: `Bearer ${this.token}`,
32
+ 'Content-Type': 'application/json',
33
+ Accept: 'application/json, text/event-stream',
34
+ };
35
+ if (this.sessionId)
36
+ headers['Mcp-Session-Id'] = this.sessionId;
37
+ const res = await fetch(this.baseUrl, { method: 'POST', headers, body: JSON.stringify(body) });
38
+ const text = await res.text();
39
+ return { json: parseSse(text), sessionId: res.headers.get('mcp-session-id') ?? undefined, status: res.status };
40
+ }
41
+ async connect() {
42
+ const init = await this.rpc({
43
+ jsonrpc: '2.0',
44
+ id: 1,
45
+ method: 'initialize',
46
+ params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'arc-1-lsp', version: '0.0.1' } },
47
+ });
48
+ if (init.status !== 200 || !init.sessionId) {
49
+ throw new Error(`adt-ls MCP initialize failed: HTTP ${init.status}`);
50
+ }
51
+ this.sessionId = init.sessionId;
52
+ await this.rpc({ jsonrpc: '2.0', method: 'notifications/initialized' });
53
+ logger.debug(`federation: connected to adt-ls MCP (session ${this.sessionId})`);
54
+ }
55
+ async listTools() {
56
+ const r = await this.rpc({ jsonrpc: '2.0', id: 2, method: 'tools/list' });
57
+ return r.json?.result?.tools ?? [];
58
+ }
59
+ async callTool(name, args = {}) {
60
+ const r = await this.rpc({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name, arguments: args } });
61
+ if (r.json?.error) {
62
+ throw new Error(`adt-ls tool ${name} error: ${r.json.error.message}`);
63
+ }
64
+ return r.json?.result;
65
+ }
66
+ }
67
+ //# sourceMappingURL=mcp-federation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-federation.js","sourceRoot":"","sources":["../../src/adt-ls/mcp-federation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export function startMcpServer(driver, opts) {
2
+ return driver.sendRequest('adtLs/mcp/startMCPServer', opts);
3
+ }
4
+ export function stopMcpServer(driver) {
5
+ return driver.sendRequest('adtLs/mcp/stopMCPServer');
6
+ }
7
+ export function setMcpDestination(driver, destinationId) {
8
+ return driver.sendRequest('adtLs/mcp/setDestination', { destinationId });
9
+ }
10
+ //# sourceMappingURL=mcp-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-lifecycle.js","sourceRoot":"","sources":["../../src/adt-ls/mcp-lifecycle.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,cAAc,CAC5B,MAAmB,EACnB,IAAqC;IAErC,OAAO,MAAM,CAAC,WAAW,CAAuB,0BAA0B,EAAE,IAAI,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAmB;IAC/C,OAAO,MAAM,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAmB,EAAE,aAAqB;IAC1E,OAAO,MAAM,CAAC,WAAW,CAAC,0BAA0B,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Repository object search (SAPSearch). NOTE the exact param names adt-ls wants:
3
+ * the search string is `pattern` (NOT `query`) and `destination` (NOT
4
+ * `destinationId`); `types` filters by object type (empty = all).
5
+ */
6
+ export function quickSearch(driver, params) {
7
+ return driver.sendRequest('adtLs/repository/quickSearch', {
8
+ destination: params.destination,
9
+ pattern: params.pattern,
10
+ maxResults: params.maxResults ?? 50,
11
+ types: params.types ?? [],
12
+ });
13
+ }
14
+ /** List inactive (draft) objects on a destination. Uses `destinationId`. */
15
+ export function getInactiveObjects(driver, destinationId) {
16
+ return driver.sendRequest('adtLs/activation/getInactiveObjects', { destinationId });
17
+ }
18
+ /**
19
+ * Resolve an ADT path (from a search result) to the canonical repotree AFF URI
20
+ * that readFile/writeFile/activate need. Param key MUST be `adtUri` (verified).
21
+ */
22
+ export async function getLsUri(driver, destination, adtUri) {
23
+ const r = await driver.sendRequest('adtLs/repository/getLsUri', { destination, adtUri });
24
+ if (!r.uri)
25
+ throw new Error(`getLsUri returned no uri for ${adtUri}`);
26
+ return r.uri;
27
+ }
28
+ /** Read an AFF file's content (the object source) by its repotree URI. */
29
+ export async function readFile(driver, uri) {
30
+ const r = await driver.sendRequest('adtLs/fileSystem/readFile', { uri });
31
+ return r.content ?? '';
32
+ }
33
+ /** Write an AFF file (update source). `content` must be plain multi-line text. */
34
+ export function writeFile(driver, uri, content) {
35
+ return driver.sendRequest('adtLs/fileSystem/writeFile', { uri, content });
36
+ }
37
+ /** Delete an object via its AFF metadata (`.json`) URI. */
38
+ export function deleteFile(driver, uri) {
39
+ return driver.sendRequest('adtLs/fileSystem/delete', { uri });
40
+ }
41
+ /** adt-ls returns this placeholder (not source) for object types it can't serve headless. */
42
+ export function isUnsupportedPlaceholder(content) {
43
+ return /not supported in ADT in VS Code/i.test(content);
44
+ }
45
+ /** Swap a class main-source AFF URI to one of its include files (CCDEF/CCIMP/…). */
46
+ export function includeAffUri(mainAffUri, include) {
47
+ // …/zcl_x.clas.abap → …/zcl_x.clas.<include>.abap
48
+ return mainAffUri.replace(/\.clas\.abap$/, `.clas.${include}.abap`);
49
+ }
50
+ /** Derive the AFF metadata (`.json`) URI from a main-source AFF URI (final ext → json). */
51
+ export function metadataAffUri(mainAffUri) {
52
+ return mainAffUri.replace(/\.[^./]+$/, '.json');
53
+ }
54
+ /** List system users (e.g. for ownership/transport context). Uses `destination`. */
55
+ export async function getUsers(driver, destination) {
56
+ const r = await driver.sendRequest('adtLs/repository/getUsers', { destination });
57
+ return r.users ?? [];
58
+ }
59
+ //# sourceMappingURL=repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/adt-ls/repository.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,MAAoB,EACpB,MAAuF;IAEvF,OAAO,MAAM,CAAC,WAAW,CAAoB,8BAA8B,EAAE;QAC3E,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,EAAE;QACnC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAAC,MAAoB,EAAE,aAAqB;IAC5E,OAAO,MAAM,CAAC,WAAW,CAAY,qCAAqC,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;AACjG,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAoB,EAAE,WAAmB,EAAE,MAAc;IACtF,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,WAAW,CAAmB,2BAA2B,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3G,IAAI,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,MAAM,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,CAAC,GAAG,CAAC;AACf,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAoB,EAAE,GAAW;IAC9D,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,WAAW,CAAuB,2BAA2B,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/F,OAAO,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAC,MAAoB,EAAE,GAAW,EAAE,OAAe;IAC1E,OAAO,MAAM,CAAC,WAAW,CAAC,4BAA4B,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,MAAoB,EAAE,GAAW;IAC1D,OAAO,MAAM,CAAC,WAAW,CAAC,yBAAyB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO,kCAAkC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,OAAe;IAC/D,kDAAkD;IAClD,OAAO,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,SAAS,OAAO,OAAO,CAAC,CAAC;AACtE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,OAAO,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AAQD,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAoB,EAAE,WAAmB;IACtE,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,WAAW,CAAwB,2BAA2B,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;IACxG,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Self-healing SAP session: detect adt-ls "logged off" failures and transparently
3
+ * re-logon + retry once.
4
+ *
5
+ * Why this exists: adt-ls holds a SAP *security session* for the destination
6
+ * after the reentrance-ticket logon. That session expires server-side (typically
7
+ * on inactivity). When it does, the next ADT call fails with "Your user was
8
+ * logged off" — and stays broken until the destination logs on again. Before this
9
+ * module the only cure was restarting the whole instance (observed on CF).
10
+ *
11
+ * The cure is cheap: call `ensureLoggedOn` again. Our reentrance-ticket handler
12
+ * stays registered on the driver for the process lifetime, so a fresh
13
+ * `ensureLoggedOn` re-fires it and re-establishes the session via the exact path
14
+ * proven at startup (ADR-0006). This module is the pure policy — detection +
15
+ * single-retry orchestration + re-logon de-duplication — wired to the real
16
+ * `ensureLoggedOn`/`setMcpDestination` in `engine.ts`.
17
+ */
18
+ /**
19
+ * Signatures of a lost SAP session. Deliberately specific (SAP's "logged off"
20
+ * phrase, session-expiry variants, explicit HTTP 401) rather than broad — a false
21
+ * positive would re-logon + retry a call that failed for an unrelated reason. A
22
+ * normal ABAP syntax/validation error never matches.
23
+ */
24
+ const LOGGED_OFF = /logged.?off|not logged on|logon (?:failed|required|denied|expired)|session\b[\w\s'"-]{0,24}?\b(?:expired|terminated|timed[- ]?out|invalid|no longer valid)\b|http[\s/]?401|401 unauthorized/i;
25
+ /** True when an error/message text indicates the SAP session is gone. */
26
+ export function isLoggedOffMessage(text) {
27
+ return LOGGED_OFF.test(text);
28
+ }
29
+ /**
30
+ * True when a *federated* MCP tool result is an error caused by a lost session.
31
+ * The federation client returns tool-level failures as `{isError:true, content}`
32
+ * (it only throws on JSON-RPC transport errors), so result inspection — not just
33
+ * catching throws — is required to catch a logged-off mid-tool.
34
+ */
35
+ export function isLoggedOffFederatedResult(res) {
36
+ const r = res;
37
+ if (!r?.isError)
38
+ return false;
39
+ return isLoggedOffMessage((r.content ?? []).map((c) => c?.text ?? '').join(' '));
40
+ }
41
+ /**
42
+ * Serialize re-logon. A session loss fails every outstanding call at once; without
43
+ * this they would each kick off their own `ensureLoggedOn`. Concurrent callers
44
+ * share one in-flight attempt; once it settles the next loss starts a fresh one.
45
+ */
46
+ export function makeRelogon(doRelogon) {
47
+ let inFlight;
48
+ return () => {
49
+ if (!inFlight) {
50
+ inFlight = doRelogon().finally(() => {
51
+ inFlight = undefined;
52
+ });
53
+ }
54
+ return inFlight;
55
+ };
56
+ }
57
+ /**
58
+ * Wrap a call so a lost-session failure triggers a single re-logon + retry.
59
+ * Detects loss two ways — a thrown error (LSP / JSON-RPC) or a federated result
60
+ * flagged `isError` with a logged-off message (pass `loggedOffInResult`). Retries
61
+ * at most once and only if re-logon reports success; never loops.
62
+ */
63
+ export function makeWithRelogon(relogon) {
64
+ return async function withRelogon(fn, loggedOffInResult) {
65
+ try {
66
+ const r = await fn();
67
+ if (loggedOffInResult?.(r) && (await relogon()))
68
+ return fn();
69
+ return r;
70
+ }
71
+ catch (e) {
72
+ const msg = e instanceof Error ? e.message : String(e);
73
+ if (isLoggedOffMessage(msg) && (await relogon()))
74
+ return fn();
75
+ throw e;
76
+ }
77
+ };
78
+ }
79
+ //# sourceMappingURL=session-retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-retry.js","sourceRoot":"","sources":["../../src/adt-ls/session-retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,GACd,8LAA8L,CAAC;AAEjM,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAY;IACrD,MAAM,CAAC,GAAG,GAA4E,CAAC;IACvF,IAAI,CAAC,CAAC,EAAE,OAAO;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiC;IAC3D,IAAI,QAAsC,CAAC;IAC3C,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,GAAG,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBAClC,QAAQ,GAAG,SAAS,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,OAAO,KAAK,UAAU,WAAW,CAAI,EAAoB,EAAE,iBAAqC;QAC9F,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,EAAE,EAAE,CAAC;YACrB,IAAI,iBAAiB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC;gBAAE,OAAO,EAAE,EAAE,CAAC;YAC7D,OAAO,CAAC,CAAC;QACX,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACvD,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC;gBAAE,OAAO,EAAE,EAAE,CAAC;YAC9D,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * TLS-terminating reverse proxy (ADR-0005 / ADR-0006).
3
+ *
4
+ * adt-ls requires an HTTPS `systemUrl` and validates the backend cert's hostname.
5
+ * SAP on-prem systems (e.g. a4h) present the default self-signed cert
6
+ * `CN=*.dummy.nodomain`, which fails hostname verification — and adt-ls's Apache
7
+ * HTTP client ignores `-Djdk.internal.httpclient.disableHostnameVerification`.
8
+ *
9
+ * The fix: point adt-ls's destination at `https://localhost:<port>` served by this
10
+ * proxy with a `CN=localhost` cert (added to the JVM truststore → trust ✓ +
11
+ * hostname ✓). The proxy re-originates each request to the real SAP host, where WE
12
+ * (Node) own the TLS and can accept the self-signed cert (`insecureUpstream`).
13
+ *
14
+ * Two upstream modes:
15
+ * - DIRECT (local): connect straight to the backend over HTTPS (a4h:50001).
16
+ * - FORWARD-PROXY (CF): re-emit each request as a standard absolute-form HTTP
17
+ * proxy request to `forwardProxy` (the connectivity bridge), which adds the
18
+ * connectivity token + Cloud-Connector header → CC → backend. Same TLS
19
+ * termination for adt-ls; only the backend hop differs.
20
+ *
21
+ * Request headers are forwarded as-is so SAP builds the reentrance `logonUrl`
22
+ * against `localhost:<port>` (which our logon handler then GETs back through here).
23
+ */
24
+ import http from 'node:http';
25
+ import https from 'node:https';
26
+ import { logger } from '../server/logger.js';
27
+ export async function startTlsReverseProxy(opts) {
28
+ const insecureUpstream = opts.insecureUpstream ?? true;
29
+ const bindHost = opts.bindHost ?? '127.0.0.1';
30
+ const fwd = opts.forwardProxy;
31
+ const scheme = opts.target.protocol ?? (fwd ? 'http' : 'https');
32
+ const server = https.createServer({ key: opts.key, cert: opts.cert }, (req, res) => {
33
+ const onUpstream = (upRes) => {
34
+ res.writeHead(upRes.statusCode ?? 502, upRes.headers);
35
+ upRes.pipe(res);
36
+ };
37
+ const upstream = fwd
38
+ ? // FORWARD-PROXY mode: absolute-form request to the connectivity bridge.
39
+ http.request({
40
+ host: fwd.host,
41
+ port: fwd.port,
42
+ method: req.method,
43
+ path: `${scheme}://${opts.target.host}:${opts.target.port}${req.url}`,
44
+ headers: { ...req.headers, host: `${opts.target.host}:${opts.target.port}` },
45
+ }, onUpstream)
46
+ : // DIRECT mode: straight to the backend over HTTPS.
47
+ https.request({
48
+ host: opts.target.host,
49
+ port: opts.target.port,
50
+ method: req.method,
51
+ path: req.url,
52
+ headers: req.headers,
53
+ rejectUnauthorized: !insecureUpstream,
54
+ }, onUpstream);
55
+ upstream.on('error', (err) => {
56
+ logger.warn(`tls-reverse-proxy: upstream error: ${err.message}`);
57
+ if (!res.headersSent)
58
+ res.writeHead(502, { 'content-type': 'text/plain' });
59
+ res.end(`reverse-proxy upstream error: ${err.message}`);
60
+ });
61
+ req.pipe(upstream);
62
+ });
63
+ await new Promise((resolve, reject) => {
64
+ server.once('error', reject);
65
+ server.listen(0, bindHost, () => resolve());
66
+ });
67
+ const port = server.address().port;
68
+ // CN=localhost cert ⇒ advertise the host as `localhost`, not the bind IP.
69
+ const url = `https://localhost:${port}`;
70
+ const via = fwd ? ` via connectivity bridge ${fwd.host}:${fwd.port}` : '';
71
+ logger.info(`tls-reverse-proxy: ${url} → ${scheme}://${opts.target.host}:${opts.target.port}${via}`);
72
+ return {
73
+ port,
74
+ url,
75
+ close: () => new Promise((resolve) => {
76
+ server.close(() => resolve());
77
+ }),
78
+ };
79
+ }
80
+ //# sourceMappingURL=tls-reverse-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tls-reverse-proxy.js","sourceRoot":"","sources":["../../src/adt-ls/tls-reverse-proxy.ts"],"names":[],"mappings":"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"}