cap-copilot-widget 0.1.0 → 0.1.2

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.
@@ -1,108 +1,114 @@
1
- const f = class f {
2
- static resolve(t) {
3
- if (t) return t.replace(/^Bearer\s+/i, "");
4
- const e = window.__BTP_COPILOT_TOKEN__;
1
+ const A = class A {
2
+ static resolve(e) {
5
3
  if (e) return e.replace(/^Bearer\s+/i, "");
6
- const n = f._fromCookie();
4
+ const t = window.__BTP_COPILOT_TOKEN__;
5
+ if (t) return t.replace(/^Bearer\s+/i, "");
6
+ const n = A._fromCookie();
7
7
  if (n) return n;
8
8
  try {
9
- const i = localStorage.getItem("access_token");
10
- if (i) return i;
9
+ const r = localStorage.getItem("access_token");
10
+ if (r) return r;
11
11
  } catch {
12
12
  }
13
13
  return null;
14
14
  }
15
- static getAuthHeader(t) {
16
- return t ? { Authorization: `Bearer ${t}` } : {};
15
+ static getAuthHeader(e) {
16
+ return e ? { Authorization: `Bearer ${e}` } : {};
17
17
  }
18
18
  static _fromCookie() {
19
19
  try {
20
- const t = document.cookie.split(";");
21
- for (const e of t) {
22
- const [n, ...i] = e.trim().split("=");
23
- if (f.XSUAA_COOKIE_NAMES.includes(n.trim()))
24
- return decodeURIComponent(i.join("=").trim());
20
+ const e = document.cookie.split(";");
21
+ for (const t of e) {
22
+ const [n, ...r] = t.trim().split("=");
23
+ if (A.XSUAA_COOKIE_NAMES.includes(n.trim()))
24
+ return decodeURIComponent(r.join("=").trim());
25
25
  }
26
26
  } catch {
27
27
  }
28
28
  return null;
29
29
  }
30
30
  };
31
- f.XSUAA_COOKIE_NAMES = [
31
+ A.XSUAA_COOKIE_NAMES = [
32
32
  "x-uaa-token",
33
33
  "xsuaa_token",
34
34
  "access_token",
35
35
  "X-Authorization"
36
36
  ];
37
- let d = f;
38
- class m {
37
+ let S = A;
38
+ class C {
39
39
  static getHash() {
40
- var t, e, n, i;
40
+ var e, t, n, r;
41
41
  try {
42
- const r = (n = (e = (t = sap == null ? void 0 : sap.ushell) == null ? void 0 : t.Container) == null ? void 0 : e.getService) == null ? void 0 : n.call(
43
- e,
42
+ const a = (n = (t = (e = sap == null ? void 0 : sap.ushell) == null ? void 0 : e.Container) == null ? void 0 : t.getService) == null ? void 0 : n.call(
43
+ t,
44
44
  "CrossApplicationNavigation"
45
45
  );
46
- if (r) {
47
- const s = (i = r.hrefForExternal) == null ? void 0 : i.call(r);
48
- if (s) return s;
46
+ if (a) {
47
+ const i = (r = a.hrefForExternal) == null ? void 0 : r.call(a);
48
+ if (i) return i;
49
49
  }
50
- } catch (r) {
51
- console.log(r);
50
+ } catch (a) {
51
+ console.log(a);
52
52
  }
53
53
  return window.location.hash || "";
54
54
  }
55
55
  static getCurrentAppId() {
56
56
  try {
57
- const e = m.getHash().match(/^#([^&?/]+)/);
58
- return e ? e[1] : null;
57
+ const t = C.getHash().match(/^#([^&?/]+)/);
58
+ return t ? t[1] : null;
59
59
  } catch {
60
60
  return null;
61
61
  }
62
62
  }
63
63
  static getUserLocale() {
64
- var t, e, n, i, r, s;
64
+ var e, t, n, r, a, i;
65
65
  try {
66
- return ((s = (r = (i = (n = (e = (t = sap == null ? void 0 : sap.ui) == null ? void 0 : t.getCore) == null ? void 0 : e.call(t)) == null ? void 0 : n.getConfiguration) == null ? void 0 : i.call(n)) == null ? void 0 : r.getLanguageTag) == null ? void 0 : s.call(r)) ?? navigator.language ?? "en";
66
+ return ((i = (a = (r = (n = (t = (e = sap == null ? void 0 : sap.ui) == null ? void 0 : e.getCore) == null ? void 0 : t.call(e)) == null ? void 0 : n.getConfiguration) == null ? void 0 : r.call(n)) == null ? void 0 : a.getLanguageTag) == null ? void 0 : i.call(a)) ?? navigator.language ?? "en";
67
67
  } catch {
68
68
  return navigator.language ?? "en";
69
69
  }
70
70
  }
71
71
  }
72
- class u {
72
+ class _ {
73
73
  static getCurrentEntity() {
74
- var t, e, n, i;
74
+ var e, t, n, r, a;
75
75
  try {
76
- const r = (e = (t = sap == null ? void 0 : sap.ui) == null ? void 0 : t.getCore) == null ? void 0 : e.call(t);
77
- if (!r) return null;
78
- let s = document.activeElement;
79
- for (; s; ) {
80
- const a = s.getAttribute("id") || s.getAttribute("data-sap-ui");
81
- if (a) {
82
- const o = r.byId(a), l = (n = o == null ? void 0 : o.getBindingContext) == null ? void 0 : n.call(o);
83
- if (l) {
84
- const p = (i = l.getObject) == null ? void 0 : i.call(l);
85
- if (p && typeof p == "object")
86
- return u._sanitize(p);
76
+ const i = (t = (e = sap == null ? void 0 : sap.ui) == null ? void 0 : e.getCore) == null ? void 0 : t.call(e);
77
+ if (!i) return null;
78
+ let o = document.activeElement;
79
+ for (; o; ) {
80
+ const l = o.getAttribute("id") || o.getAttribute("data-sap-ui");
81
+ if (l) {
82
+ const s = i.byId(l), c = (n = s == null ? void 0 : s.getBindingContext) == null ? void 0 : n.call(s);
83
+ if (c) {
84
+ const h = (r = c.getObject) == null ? void 0 : r.call(c);
85
+ if (h && typeof h == "object")
86
+ return _._withEntityName(
87
+ _._sanitize(h),
88
+ ((a = c.getPath) == null ? void 0 : a.call(c)) ?? ""
89
+ );
87
90
  }
88
91
  }
89
- s = s.parentElement;
92
+ o = o.parentElement;
90
93
  }
91
- return u._scanAllControls(r);
94
+ return _._scanAllControls(i);
92
95
  } catch {
93
96
  return null;
94
97
  }
95
98
  }
96
- static _scanAllControls(t) {
97
- var e, n;
99
+ static _scanAllControls(e) {
100
+ var t, n, r;
98
101
  try {
99
- const i = t.mElements ?? {};
100
- for (const r of Object.keys(i)) {
101
- const s = i[r], a = (e = s == null ? void 0 : s.getBindingContext) == null ? void 0 : e.call(s);
102
- if (a) {
103
- const o = (n = a.getObject) == null ? void 0 : n.call(a);
104
- if (o && typeof o == "object" && Object.keys(o).length > 0)
105
- return u._sanitize(o);
102
+ const a = e.mElements ?? {};
103
+ for (const i of Object.keys(a)) {
104
+ const o = a[i], l = (t = o == null ? void 0 : o.getBindingContext) == null ? void 0 : t.call(o);
105
+ if (l) {
106
+ const s = (n = l.getObject) == null ? void 0 : n.call(l);
107
+ if (s && typeof s == "object" && Object.keys(s).length > 0)
108
+ return _._withEntityName(
109
+ _._sanitize(s),
110
+ ((r = l.getPath) == null ? void 0 : r.call(l)) ?? ""
111
+ );
106
112
  }
107
113
  }
108
114
  } catch {
@@ -110,234 +116,588 @@ class u {
110
116
  return null;
111
117
  }
112
118
  static getServiceUrl() {
113
- var t, e;
119
+ var e, t;
114
120
  try {
115
- const n = (e = (t = sap == null ? void 0 : sap.ui) == null ? void 0 : t.getCore) == null ? void 0 : e.call(t);
121
+ const n = (t = (e = sap == null ? void 0 : sap.ui) == null ? void 0 : e.getCore) == null ? void 0 : t.call(e);
116
122
  if (!n) return null;
117
- const i = n.mElements ?? {};
118
- for (const r of Object.keys(i)) {
119
- const s = i[r];
120
- if (!(s != null && s.getModel)) continue;
121
- const a = s.getModel();
122
- if (!a) continue;
123
- const o = a.sServiceUrl ?? a._sServiceUrl;
124
- if (typeof o == "string" && o.length > 4)
125
- return o.replace(/\/$/, "");
123
+ const r = n.mElements ?? {};
124
+ for (const a of Object.keys(r)) {
125
+ const i = r[a];
126
+ if (!(i != null && i.getModel)) continue;
127
+ const o = i.getModel();
128
+ if (!o) continue;
129
+ const l = o.sServiceUrl ?? o._sServiceUrl;
130
+ if (typeof l == "string" && l.length > 4)
131
+ return l.replace(/\/$/, "");
126
132
  }
127
133
  } catch {
128
134
  }
129
135
  return null;
130
136
  }
131
- static _sanitize(t) {
132
- const e = {};
133
- for (const [n, i] of Object.entries(t))
134
- n.startsWith("__") || (i === null || typeof i != "object" ? e[n] = i : i.__deferred || (e[n] = i));
137
+ static _sanitize(e) {
138
+ var n;
139
+ const t = {};
140
+ for (const [r, a] of Object.entries(e))
141
+ if (!r.startsWith("__"))
142
+ if (a === null || typeof a != "object")
143
+ t[r] = a;
144
+ else if (a.__deferred) {
145
+ const i = (n = a.__deferred) == null ? void 0 : n.uri;
146
+ typeof i == "string" && i && (t[`${r}_navUri`] = i);
147
+ } else
148
+ t[r] = a;
149
+ return t;
150
+ }
151
+ /**
152
+ * Inject `_entity` into a sanitized binding context object by parsing the
153
+ * OData context path. The path looks like `/FertilizerBlend(orderID='2466',...)`
154
+ * or `/Farms('abc-123')` — the entity set name is the first path segment.
155
+ */
156
+ static _withEntityName(e, t) {
157
+ if (e._entity) return e;
158
+ try {
159
+ const n = t.match(/^\/([A-Za-z_][A-Za-z0-9_]*)(?:\(|$|\/)/);
160
+ if (n != null && n[1]) return { _entity: n[1], ...e };
161
+ } catch {
162
+ }
135
163
  return e;
136
164
  }
137
165
  }
138
- const c = class c {
139
- static async fetchSchemaDocuments(t, e) {
140
- const n = `btp-copilot-meta-${t}`;
141
- if (c._cache.has(n))
142
- return c._cache.get(n);
143
- const i = sessionStorage.getItem(n);
144
- if (i)
166
+ const m = class m {
167
+ // ── Public API ──────────────────────────────────────────────────────────────
168
+ /**
169
+ * Fetch and fully parse $metadata for a service URL.
170
+ * Result is memory-cached for the page lifetime and persisted to sessionStorage.
171
+ * Returns null on any network / parse failure.
172
+ */
173
+ static async fetchMetadata(e, t) {
174
+ const n = `btp-copilot-meta-v2-${e}`;
175
+ if (m._metaCache.has(n))
176
+ return m._metaCache.get(n);
177
+ const r = sessionStorage.getItem(n);
178
+ if (r)
145
179
  try {
146
- const r = JSON.parse(i);
147
- return c._cache.set(n, r), r;
180
+ const i = { ...JSON.parse(r), rawXml: "" };
181
+ return m._metaCache.set(n, i), i;
148
182
  } catch {
149
183
  }
150
184
  try {
151
- const r = `${t}/$metadata`, s = { Accept: "application/xml" };
152
- e && (s.Authorization = `Bearer ${e}`);
153
- const a = await fetch(r, { headers: s });
154
- if (!a.ok) return [];
155
- const o = await a.text(), l = c._parseEDMX(o), p = c._schemasToDocuments(l, t);
156
- return c._cache.set(n, p), sessionStorage.setItem(n, JSON.stringify(p)), p;
157
- } catch {
158
- return [];
159
- }
160
- }
161
- static _parseEDMX(t) {
162
- try {
163
- const i = new DOMParser().parseFromString(t, "text/xml").querySelectorAll("EntityType");
164
- return Array.from(i).map((r) => {
165
- const s = r.getAttribute("Name") ?? "", a = r.querySelectorAll("Key > PropertyRef"), o = new Set(
166
- Array.from(a).map((h) => h.getAttribute("Name") ?? "")
167
- ), l = Array.from(
168
- r.querySelectorAll("Property")
169
- ).map((h) => ({
170
- name: h.getAttribute("Name") ?? "",
171
- type: c._shortType(h.getAttribute("Type") ?? ""),
172
- isKey: o.has(h.getAttribute("Name") ?? ""),
173
- nullable: h.getAttribute("Nullable") !== "false"
174
- })), p = Array.from(
175
- r.querySelectorAll("NavigationProperty")
176
- ).map((h) => ({
177
- name: h.getAttribute("Name") ?? "",
178
- type: c._shortType(
179
- h.getAttribute("Type") ?? h.getAttribute("ToRole") ?? ""
180
- )
181
- }));
182
- return { name: s, properties: l, navProperties: p };
183
- });
185
+ const a = `${e.replace(/\/$/, "")}/$metadata`, i = { Accept: "application/xml" };
186
+ t && (i.Authorization = `Bearer ${t}`);
187
+ const o = await fetch(a, { headers: i });
188
+ if (!o.ok) return null;
189
+ const l = await o.text(), s = m._parseEDMX(l, e);
190
+ m._metaCache.set(n, s);
191
+ try {
192
+ const { rawXml: c, ...h } = s;
193
+ sessionStorage.setItem(n, JSON.stringify(h));
194
+ } catch {
195
+ }
196
+ return s;
184
197
  } catch {
185
- return [];
198
+ return null;
186
199
  }
187
200
  }
188
- static _shortType(t) {
189
- return t.startsWith("Collection(") ? `${t.slice(11, -1).split(".").pop()}[]` : t.split(".").pop() ?? t;
190
- }
191
- static _schemasToDocuments(t, e) {
201
+ /**
202
+ * Build schema hint documents from parsed metadata.
203
+ * Used to populate fiori_context.extra.schema_hint for each chat message.
204
+ */
205
+ static buildSchemaDocuments(e, t) {
192
206
  const n = [];
193
- for (const i of t) {
194
- if (!i.name) continue;
195
- const r = i.properties.filter((o) => o.isKey), s = i.properties.filter((o) => !o.isKey), a = [
196
- `Entity: ${i.name}`,
197
- `OData service: ${e}`,
198
- `Entity set path: ${e}/${i.name}`,
199
- `Count path: ${e}/${i.name}/$count`
207
+ for (const r of e.entities) {
208
+ if (!r.name) continue;
209
+ const a = r.entitySetName ?? r.name, i = r.properties.filter((s) => s.isKey), o = r.properties.filter((s) => !s.isKey), l = [
210
+ `Entity: ${r.name}`,
211
+ `Entity set: ${a}`,
212
+ `OData service: ${t}`,
213
+ `Entity set path: ${t}/${a}`,
214
+ `Count path: ${t}/${a}/$count`
200
215
  ];
201
- r.length && a.push(
202
- `Key fields: ${r.map((o) => `${o.name} (${o.type})`).join(", ")}`
203
- ), s.length && a.push(
204
- `Fields: ${s.map(
205
- (o) => `${o.name} (${o.type}${o.nullable ? "" : ", required"})`
206
- ).join(", ")}`
207
- ), i.navProperties.length && a.push(
208
- `Navigation: ${i.navProperties.map((o) => `${o.name} → ${o.type}`).join(", ")}`
209
- ), n.push({
210
- title: `${i.name} entity schema`,
211
- content: a.join(`
216
+ i.length && l.push(
217
+ `Key fields: ${i.map((s) => `${s.name} (${s.type})`).join(", ")}`
218
+ ), o.length && l.push(
219
+ `Fields: ${o.map((s) => `${s.name} (${s.type}${s.nullable ? "" : ", required"})`).join(", ")}`
220
+ ), r.navProperties.length && l.push(
221
+ `Navigation: ${r.navProperties.map((s) => `${s.name} → ${s.type}${s.cardinality === "*" ? "[]" : ""}`).join(", ")}`
222
+ ), n.push({ title: `${r.name} entity schema`, content: l.join(`
223
+ `) });
224
+ }
225
+ if (e.associations.length) {
226
+ const r = e.associations.map(
227
+ (a) => `${a.sourceEntity}.${a.navigationProperty} → ${a.targetEntity} (${a.cardinality === "*" ? "many" : "one"})`
228
+ );
229
+ n.push({
230
+ title: "Entity associations and navigation",
231
+ content: ["Associations:", ...r].join(`
232
+ `)
233
+ });
234
+ }
235
+ if (e.actions.length) {
236
+ const r = e.actions.map((a) => {
237
+ const i = a.parameters.map((o) => `${o.name}: ${o.type}`).join(", ");
238
+ return `${a.name}(${i})${a.bound && a.entity ? ` [bound to ${a.entity}]` : ""}`;
239
+ });
240
+ n.push({
241
+ title: "OData actions and functions",
242
+ content: ["Actions/Functions:", ...r].join(`
212
243
  `)
213
244
  });
214
245
  }
215
- return t.length > 0 && n.push({
246
+ return e.entities.length > 0 && n.push({
216
247
  title: "Available OData entity sets",
217
248
  content: [
218
- `OData service: ${e}`,
219
- `Entities: ${t.map((i) => i.name).join(", ")}`,
220
- "GET {entity}/$count | ?$filter=field eq 'value' | ?$top=10&$skip=0 | ?$orderby=field desc"
249
+ `OData service: ${t}`,
250
+ `Entities: ${e.entities.map((r) => r.entitySetName ?? r.name).join(", ")}`,
251
+ "Patterns: GET {entitySet}/$count | ?$filter=field eq 'value' | ?$top=10&$skip=0 | ?$orderby=field desc"
221
252
  ].join(`
222
253
  `)
223
254
  }), n;
224
255
  }
225
- static invalidate(t) {
226
- const e = `btp-copilot-meta-${t}`;
227
- c._cache.delete(e), sessionStorage.removeItem(e);
256
+ /**
257
+ * Legacy helper kept for backward compatibility.
258
+ * Prefer fetchMetadata() + buildSchemaDocuments() for new code.
259
+ */
260
+ static async fetchSchemaDocuments(e, t) {
261
+ const n = await m.fetchMetadata(e, t);
262
+ return n ? m.buildSchemaDocuments(n, e) : [];
263
+ }
264
+ /** Invalidate cached metadata for a service (call when metadata may have changed). */
265
+ static invalidate(e) {
266
+ const t = `btp-copilot-meta-v2-${e}`;
267
+ m._metaCache.delete(t), sessionStorage.removeItem(t);
268
+ }
269
+ /**
270
+ * Fetch live entity data from an OData service with full error capture.
271
+ *
272
+ * Unlike a raw fetch(), this method:
273
+ * - Returns { data, error } — never throws
274
+ * - Captures the HTTP status and error body on failure
275
+ * - Builds a human-readable error context string the chatbot can use to
276
+ * explain to the user why data is unavailable, instead of returning
277
+ * an empty result or crashing silently
278
+ *
279
+ * @example
280
+ * const { data, error, errorContext } = await ODataProbe.fetchEntityData(
281
+ * '/odata/v4/fertilizer-blend/Farms', token
282
+ * );
283
+ * if (error) ctx.extra.fetch_errors = [...(ctx.extra.fetch_errors ?? []), errorContext];
284
+ */
285
+ static async fetchEntityData(e, t, n) {
286
+ try {
287
+ const r = { Accept: "application/json" };
288
+ t && (r.Authorization = `Bearer ${t}`);
289
+ const a = await fetch(e, { ...n, headers: { ...r, ...(n == null ? void 0 : n.headers) ?? {} } });
290
+ if (!a.ok) {
291
+ let o = "";
292
+ try {
293
+ o = await a.text();
294
+ } catch {
295
+ }
296
+ const l = `HTTP ${a.status} ${a.statusText}`, s = [
297
+ `OData fetch failed: ${e}`,
298
+ `Status: ${l}`,
299
+ o ? `Response: ${o.slice(0, 500)}` : "",
300
+ "This entity's data cannot be displayed to the user right now.",
301
+ "The underlying service may have an API mismatch (e.g. invalid $expand)."
302
+ ].filter(Boolean).join(`
303
+ `);
304
+ return { data: null, error: l, errorContext: s };
305
+ }
306
+ return { data: await a.json(), error: null, errorContext: null };
307
+ } catch (r) {
308
+ const a = r instanceof Error ? r.message : String(r), i = [
309
+ `OData fetch failed: ${e}`,
310
+ `Error: ${a}`,
311
+ "This entity's data cannot be displayed to the user right now."
312
+ ].join(`
313
+ `);
314
+ return { data: null, error: a, errorContext: i };
315
+ }
316
+ }
317
+ /**
318
+ * Track an OData fetch error in sessionStorage so the ContextBridge can
319
+ * include it in the next context push to the chatbot iframe.
320
+ * Call this whenever a live data fetch in the host app fails.
321
+ */
322
+ static recordFetchError(e, t, n) {
323
+ try {
324
+ const r = "btp-copilot-fetch-errors", i = [
325
+ ...JSON.parse(sessionStorage.getItem(r) ?? "[]").filter((o) => o.entity !== e),
326
+ { entity: e, url: t, reason: n, at: (/* @__PURE__ */ new Date()).toISOString() }
327
+ ].slice(-10);
328
+ sessionStorage.setItem(r, JSON.stringify(i));
329
+ } catch {
330
+ }
331
+ }
332
+ /** Read all recorded fetch errors as a schema_hint-style string. */
333
+ static getFetchErrorContext() {
334
+ try {
335
+ const t = JSON.parse(sessionStorage.getItem("btp-copilot-fetch-errors") ?? "[]");
336
+ return t.length === 0 ? null : [
337
+ "The following entities currently have backend errors and may not show live data:",
338
+ ...t.map(
339
+ (r) => `- ${r.entity}: ${r.reason} (${r.url}) [at ${r.at}]`
340
+ )
341
+ ].join(`
342
+ `);
343
+ } catch {
344
+ return null;
345
+ }
346
+ }
347
+ /**
348
+ * Find all entities in the parsed metadata that link TO the given entity type.
349
+ * These are the "reverse" associations — i.e. other entities that have a FK
350
+ * pointing at `entityName`.
351
+ *
352
+ * For each such entity, returns the entity set name, the navigation property
353
+ * name, and the FK / referenced-key pair so the caller can build an OData
354
+ * $filter query like:
355
+ * `GET /Farms?$filter=to_FertilizerBlend_orderID eq '2466'`
356
+ *
357
+ * @example
358
+ * // User is viewing FertilizerBlend(orderID='2466')
359
+ * const links = ODataProbe.findLinkedEntities(parsed, "FertilizerBlend");
360
+ * // → [{ entitySetName: "Farms", navProperty: "to_FertilizerBlend",
361
+ * // foreignKey: "to_FertilizerBlend_orderID", referencedKey: "orderID" }]
362
+ */
363
+ static findLinkedEntities(e, t) {
364
+ const n = [];
365
+ for (const r of e.associations)
366
+ if (r.targetEntity === t && r.foreignKey && r.referencedKey) {
367
+ const a = e.entities.find(
368
+ (i) => i.name === r.sourceEntity
369
+ );
370
+ a && n.push({
371
+ entitySetName: a.entitySetName ?? a.name,
372
+ navProperty: r.navigationProperty,
373
+ foreignKey: r.foreignKey,
374
+ referencedKey: r.referencedKey
375
+ });
376
+ }
377
+ return n;
378
+ }
379
+ // ── EDMX Parser ─────────────────────────────────────────────────────────────
380
+ static _parseEDMX(e, t) {
381
+ const n = [], r = [], a = [];
382
+ try {
383
+ const i = new DOMParser().parseFromString(e, "text/xml"), o = /* @__PURE__ */ new Map();
384
+ i.querySelectorAll("EntitySet").forEach((s) => {
385
+ const c = s.getAttribute("EntityType") ?? "", h = c.split(".").pop() ?? c, p = s.getAttribute("Name") ?? h;
386
+ o.set(h, p);
387
+ }), i.querySelectorAll("EntityType").forEach((s) => {
388
+ const c = s.getAttribute("Name") ?? "";
389
+ if (!c) return;
390
+ const h = new Set(
391
+ Array.from(s.querySelectorAll("Key > PropertyRef")).map(
392
+ (d) => d.getAttribute("Name") ?? ""
393
+ )
394
+ ), p = Array.from(
395
+ s.querySelectorAll("Property")
396
+ ).map((d) => ({
397
+ name: d.getAttribute("Name") ?? "",
398
+ type: m._shortType(d.getAttribute("Type") ?? ""),
399
+ isKey: h.has(d.getAttribute("Name") ?? ""),
400
+ nullable: d.getAttribute("Nullable") !== "false"
401
+ })), b = Array.from(
402
+ s.querySelectorAll("NavigationProperty")
403
+ ).map((d) => {
404
+ const v = d.getAttribute("Type") ?? d.getAttribute("ToRole") ?? "", w = v.startsWith("Collection("), u = d.querySelector("ReferentialConstraint");
405
+ return {
406
+ name: d.getAttribute("Name") ?? "",
407
+ type: m._shortType(v),
408
+ cardinality: w ? "*" : "1",
409
+ partner: d.getAttribute("Partner") ?? void 0,
410
+ foreignKey: (u == null ? void 0 : u.getAttribute("Property")) ?? void 0,
411
+ referencedKey: (u == null ? void 0 : u.getAttribute("ReferencedProperty")) ?? void 0
412
+ };
413
+ });
414
+ n.push({
415
+ name: c,
416
+ entitySetName: o.get(c) ?? c,
417
+ properties: p,
418
+ navProperties: b
419
+ });
420
+ });
421
+ for (const s of n)
422
+ for (const c of s.navProperties) {
423
+ const h = c.type.replace(/\[\]$/, "");
424
+ r.push({
425
+ sourceEntity: s.name,
426
+ navigationProperty: c.name,
427
+ targetEntity: h,
428
+ cardinality: c.cardinality === "*" ? "*" : "1",
429
+ foreignKey: c.foreignKey,
430
+ referencedKey: c.referencedKey
431
+ });
432
+ }
433
+ const l = (s, c) => {
434
+ const h = s.getAttribute("Name") ?? "", p = [];
435
+ let b;
436
+ s.querySelectorAll("Parameter").forEach((w, u) => {
437
+ const x = w.getAttribute("Name") ?? "", g = m._shortType(w.getAttribute("Type") ?? "");
438
+ c && u === 0 ? b = g.replace(/\[\]$/, "") : p.push({ name: x, type: g });
439
+ });
440
+ const d = s.querySelector("ReturnType"), v = d ? m._shortType(d.getAttribute("Type") ?? "") : void 0;
441
+ return { name: h, bound: c, entity: b, parameters: p, returnType: v };
442
+ };
443
+ i.querySelectorAll("Action").forEach((s) => {
444
+ const c = s.getAttribute("IsBound") === "true";
445
+ a.push(l(s, c));
446
+ }), i.querySelectorAll("Function").forEach((s) => {
447
+ const c = s.getAttribute("IsBound") === "true";
448
+ a.push(l(s, c));
449
+ }), i.querySelectorAll("FunctionImport").forEach((s) => {
450
+ const c = s.getAttribute("Name") ?? "", h = [];
451
+ s.querySelectorAll("Parameter").forEach((p) => {
452
+ h.push({
453
+ name: p.getAttribute("Name") ?? "",
454
+ type: m._shortType(p.getAttribute("Type") ?? "")
455
+ });
456
+ }), a.push({ name: c, bound: !1, parameters: h });
457
+ });
458
+ } catch {
459
+ }
460
+ return { entities: n, associations: r, actions: a, rawXml: e };
461
+ }
462
+ static _shortType(e) {
463
+ return e.startsWith("Collection(") ? `${e.slice(11, -1).split(".").pop()}[]` : e.split(".").pop() ?? e;
228
464
  }
229
465
  };
230
- c._cache = /* @__PURE__ */ new Map();
231
- let b = c;
232
- class g {
233
- static capture(t) {
234
- const e = { ...t };
466
+ m._metaCache = /* @__PURE__ */ new Map();
467
+ let y = m;
468
+ const f = class f {
469
+ static capture(e) {
470
+ var n, r, a;
471
+ const t = { ...e };
235
472
  try {
236
- const n = m.getHash();
237
- n && (e.current_view = n);
473
+ const i = C.getHash();
474
+ i && (t.current_view = i);
238
475
  } catch {
239
476
  }
240
477
  try {
241
- const n = u.getCurrentEntity();
242
- n && Object.keys(n).length > 0 && (e.entity_data = n);
478
+ const i = _.getCurrentEntity();
479
+ i && Object.keys(i).length > 0 && (t.entity_data = i);
243
480
  } catch {
244
481
  }
245
482
  try {
246
- e.user_locale || (e.user_locale = m.getUserLocale());
483
+ t.user_locale || (t.user_locale = C.getUserLocale());
247
484
  } catch {
248
485
  }
249
486
  try {
250
- const n = window.__APP_CONTEXT__;
251
- n && (n.entity_data && (e.entity_data = { ...n.entity_data, ...e.entity_data }), n.current_view && !e.current_view && (e.current_view = n.current_view), n.extra && (e.extra = { ...n.extra, ...e.extra ?? {} }), n.service_url && !e.service_url && (e.service_url = n.service_url));
487
+ const i = window.location.pathname + window.location.search + window.location.hash;
488
+ (!t.current_view || t.current_view === "") && (t.current_view = i);
252
489
  } catch {
253
490
  }
254
- return e;
491
+ try {
492
+ t.user_locale || (t.user_locale = navigator.language || ((n = navigator.languages) == null ? void 0 : n[0]) || "en");
493
+ } catch {
494
+ }
495
+ try {
496
+ t.extra = {
497
+ ...t.extra ?? {},
498
+ page_title: ((r = t.extra) == null ? void 0 : r.page_title) ?? document.title ?? "",
499
+ page_url: ((a = t.extra) == null ? void 0 : a.page_url) ?? window.location.href ?? ""
500
+ };
501
+ } catch {
502
+ }
503
+ try {
504
+ const i = window.__APP_CONTEXT__;
505
+ i && (i.entity_data && (t.entity_data = f._safeMerge(t.entity_data ?? {}, i.entity_data)), i.current_view && !t.current_view && (t.current_view = i.current_view), i.extra && (t.extra = f._safeMerge(i.extra, t.extra)), i.service_url && !t.service_url && (t.service_url = i.service_url));
506
+ } catch {
507
+ }
508
+ return t;
255
509
  }
256
- static async captureAsync(t) {
257
- var n;
258
- const e = g.capture(t);
259
- if (e.service_url || (e.service_url = g._discoverServiceUrl() ?? void 0), e.service_url)
510
+ static async captureAsync(e) {
511
+ var n, r, a;
512
+ const t = f.capture(e);
513
+ if (t.service_url || (t.service_url = f._discoverServiceUrl() ?? void 0), t.service_url)
260
514
  try {
261
- const i = ((n = e.extra) == null ? void 0 : n.auth_token) ?? null, r = await b.fetchSchemaDocuments(
262
- e.service_url,
263
- i
264
- );
265
- if (r.length > 0) {
266
- const s = r.map((a) => `## ${a.title}
267
- ${a.content}`).join(`
515
+ const i = ((n = t.extra) == null ? void 0 : n.auth_token) ?? null, o = await y.fetchMetadata(t.service_url, i);
516
+ if (o) {
517
+ const l = y.buildSchemaDocuments(o, t.service_url);
518
+ if (l.length > 0) {
519
+ const h = l.map((p) => `## ${p.title}
520
+ ${p.content}`).join(`
268
521
 
269
522
  `);
270
- e.extra = { ...e.extra ?? {}, schema_hint: s };
523
+ t.extra = { ...t.extra ?? {}, schema_hint: h };
524
+ }
525
+ const s = (r = t.extra) == null ? void 0 : r.backend_url;
526
+ if (s && o.rawXml) {
527
+ const h = t.app_id, p = t.service_url;
528
+ f._registerMetadata(s, h, p, o.rawXml, i);
529
+ }
530
+ const c = (a = t.entity_data) == null ? void 0 : a._entity;
531
+ if (c && t.service_url)
532
+ try {
533
+ const h = await f._fetchLinkedEntities(
534
+ t.service_url,
535
+ c,
536
+ t.entity_data,
537
+ o,
538
+ i
539
+ );
540
+ Object.keys(h).length > 0 && (t.entity_data = { ...t.entity_data ?? {}, ...h });
541
+ } catch {
542
+ }
271
543
  }
272
544
  } catch {
273
545
  }
274
- return e;
546
+ try {
547
+ const i = y.getFetchErrorContext();
548
+ i && (t.extra = { ...t.extra ?? {}, fetch_errors: i });
549
+ } catch {
550
+ }
551
+ return t;
552
+ }
553
+ /**
554
+ * Safe shallow merge that blocks prototype pollution.
555
+ * Skips __proto__, constructor, and prototype keys from the incoming object.
556
+ */
557
+ static _safeMerge(e, t) {
558
+ if (!t || typeof t != "object" || Array.isArray(t)) return e;
559
+ const n = { ...e };
560
+ for (const [r, a] of Object.entries(t))
561
+ r === "__proto__" || r === "constructor" || r === "prototype" || (n[r] = a);
562
+ return n;
563
+ }
564
+ static async _fetchLinkedEntities(e, t, n, r, a) {
565
+ const i = r.entities.find(
566
+ (u) => u.name === t || u.entitySetName === t
567
+ );
568
+ if (!i) return {};
569
+ const o = i.properties.find((u) => u.isKey);
570
+ if (!o) return {};
571
+ const l = n[o.name];
572
+ if (l == null) return {};
573
+ const s = `${t}::${l}`, c = Date.now(), h = f._linkedEntityCache.get(s);
574
+ if (h && c - h.fetchedAt < f._LINKED_CACHE_TTL)
575
+ return h.data;
576
+ const p = {}, b = typeof l == "number" ? String(l) : `'${l}'`, d = y.findLinkedEntities(r, i.name);
577
+ await Promise.all(
578
+ d.map(async (u) => {
579
+ try {
580
+ const x = `${e}/${u.entitySetName}?$filter=${encodeURIComponent(`${u.foreignKey} eq ${b}`)}&$top=50`, { data: g, error: E, errorContext: $ } = await y.fetchEntityData(x, a);
581
+ if (E)
582
+ y.recordFetchError(u.entitySetName, x, E), $ && (p[`linked_${u.entitySetName}_error`] = $);
583
+ else if (g) {
584
+ const I = g.value ?? (Array.isArray(g) ? g : [g]);
585
+ I.length > 0 && (p[`linked_${u.entitySetName}`] = I);
586
+ }
587
+ } catch {
588
+ }
589
+ })
590
+ );
591
+ const v = i.entitySetName ?? i.name, w = i.navProperties.filter(
592
+ (u) => u.cardinality !== "*"
593
+ );
594
+ return await Promise.all(
595
+ w.map(async (u) => {
596
+ if (p[`linked_${u.name}`] == null)
597
+ try {
598
+ const x = `${e}/${v}(${b})/${u.name}`, { data: g, error: E } = await y.fetchEntityData(x, a);
599
+ if (!E && g) {
600
+ const $ = g.value ?? g;
601
+ $ != null && (p[`linked_${u.name}`] = $);
602
+ }
603
+ } catch {
604
+ }
605
+ })
606
+ ), f._linkedEntityCache.set(s, {
607
+ data: p,
608
+ fetchedAt: c
609
+ }), p;
610
+ }
611
+ static async _registerMetadata(e, t, n, r, a) {
612
+ const i = `${t}::${n}`;
613
+ if (!f._registeredKeys.has(i)) {
614
+ f._registeredKeys.add(i);
615
+ try {
616
+ const o = { "Content-Type": "application/json" };
617
+ a && (o.Authorization = `Bearer ${a}`), await fetch(`${e}/api/apps/register-metadata`, {
618
+ method: "POST",
619
+ headers: o,
620
+ body: JSON.stringify({ app_id: t, service_url: n, raw_xml: r })
621
+ });
622
+ } catch {
623
+ f._registeredKeys.delete(i);
624
+ }
625
+ }
275
626
  }
276
627
  static _discoverServiceUrl() {
277
628
  try {
278
- const t = u.getServiceUrl();
279
- if (t) return t;
629
+ const e = _.getServiceUrl();
630
+ if (e) return e;
280
631
  } catch {
281
632
  }
282
633
  try {
283
- const t = performance.getEntriesByType(
634
+ const e = performance.getEntriesByType(
284
635
  "resource"
285
636
  );
286
- for (const e of t) {
287
- const n = e.name.match(/^(https?:\/\/[^?#]+)\/\$metadata/);
637
+ for (const t of e) {
638
+ const n = t.name.match(/^(https?:\/\/[^?#]+)\/\$metadata/);
288
639
  if (n) return n[1];
289
640
  }
290
641
  } catch {
291
642
  }
292
643
  try {
293
- const t = window.location.href.match(
644
+ const e = window.location.href.match(
294
645
  /(https?:\/\/[^/]+(?:\/odata\/v[24]\/[^/?#]+|\/sap\/opu\/odata\/[^/?#]+))/
295
646
  );
296
- if (t) return t[1];
647
+ if (e) return e[1];
297
648
  } catch {
298
649
  }
299
650
  return null;
300
651
  }
301
652
  static async discoverFromManifest() {
302
- var t;
653
+ var e;
303
654
  try {
304
- const e = await fetch("/manifest.json");
305
- if (!e.ok) return null;
306
- const n = await e.json(), i = ((t = n == null ? void 0 : n["sap.app"]) == null ? void 0 : t.dataSources) ?? {};
307
- for (const r of Object.keys(i)) {
308
- const s = i[r];
309
- if ((s == null ? void 0 : s.type) === "ODataAnnotation") continue;
310
- const a = s == null ? void 0 : s.uri;
311
- if (typeof a == "string" && a.length > 0)
312
- return new URL(a, window.location.href).toString().replace(/\/$/, "");
655
+ const t = await fetch("/manifest.json");
656
+ if (!t.ok) return null;
657
+ const n = await t.json(), r = ((e = n == null ? void 0 : n["sap.app"]) == null ? void 0 : e.dataSources) ?? {};
658
+ for (const a of Object.keys(r)) {
659
+ const i = r[a];
660
+ if ((i == null ? void 0 : i.type) === "ODataAnnotation") continue;
661
+ const o = i == null ? void 0 : i.uri;
662
+ if (typeof o == "string" && o.length > 0)
663
+ return new URL(o, window.location.href).toString().replace(/\/$/, "");
313
664
  }
314
665
  } catch {
315
666
  }
316
667
  return null;
317
668
  }
318
- static listenForUpdates(t, e, n, i) {
319
- const r = (s) => {
320
- if (!s.data || typeof s.data != "object") return;
321
- const { type: a, payload: o, text: l } = s.data;
322
- switch (a) {
669
+ /**
670
+ * Listen for postMessage events from the BTP Copilot iframe.
671
+ *
672
+ * @param allowedOrigin The iframe's origin (e.g. "https://your-chatbot.cfapps.eu10.hana.ondemand.com").
673
+ * Messages from any other origin are silently ignored.
674
+ * Pass "*" only in trusted, same-origin deployments.
675
+ */
676
+ static listenForUpdates(e, t, n, r, a) {
677
+ const i = (o) => {
678
+ if (!o.data || typeof o.data != "object" || a && a !== "*" && o.origin !== a) return;
679
+ const { type: l, payload: s, text: c } = o.data;
680
+ switch (l) {
323
681
  case "btp-copilot:set-context":
324
- o && t(o);
682
+ s && e(s);
325
683
  break;
326
684
  case "btp-copilot:send-message":
327
- l && e(l);
685
+ c && t(c);
328
686
  break;
329
687
  case "btp-copilot:open":
330
688
  n();
331
689
  break;
332
690
  case "btp-copilot:close":
333
- i();
691
+ r();
334
692
  break;
335
693
  }
336
694
  };
337
- return window.addEventListener("message", r), () => window.removeEventListener("message", r);
695
+ return window.addEventListener("message", i), () => window.removeEventListener("message", i);
338
696
  }
339
- }
340
- const v = `/* ── Host element ─────────────────────────────────────────────────────────── */
697
+ };
698
+ f._registeredKeys = /* @__PURE__ */ new Set(), f._linkedEntityCache = /* @__PURE__ */ new Map(), f._LINKED_CACHE_TTL = 3e4;
699
+ let k = f;
700
+ const j = `/* ── Host element ─────────────────────────────────────────────────────────── */
341
701
  :host {
342
702
  position: fixed;
343
703
  z-index: 2147483647;
@@ -464,77 +824,113 @@ const v = `/* ── Host element ───────────────
464
824
  }
465
825
  .chat-iframe { border-radius: 1rem 1rem 0 0; }
466
826
  }
467
- `, x = [
827
+ `, O = [
468
828
  "iframe-url",
469
829
  "app-id",
470
830
  "app-name",
471
831
  "service-url",
832
+ "backend-url",
472
833
  "auto-context",
473
834
  "position",
474
835
  "theme",
475
836
  "token",
476
837
  "context-interval"
477
- ], _ = class _ extends HTMLElement {
838
+ ], N = class N extends HTMLElement {
478
839
  constructor() {
479
- super(...arguments), this._open = !1, this._isFullscreen = !1, this._iframeEl = null, this._contextTimer = null;
840
+ super(...arguments), this._open = !1, this._isFullscreen = !1, this._iframeEl = null, this._contextTimer = null, this._manualRecord = null;
480
841
  }
481
842
  connectedCallback() {
482
843
  this._config = this._readConfig(), this._buildShadow(), this._startContextPolling();
483
- const t = (e) => {
484
- if (!e.data || typeof e.data != "object") return;
485
- const { type: n } = e.data;
486
- n === "btp-copilot:close" && this.close(), n === "btp-copilot:open" && this.open(), n === "btp-copilot:fullscreen" && this._toggleFullscreen(), n === "btp-copilot:minimize" && this.close();
844
+ const e = (t) => {
845
+ if (!t.data || typeof t.data != "object") return;
846
+ const n = this._iframeOrigin();
847
+ if (n !== "*" && t.origin !== n) return;
848
+ const { type: r } = t.data;
849
+ r === "btp-copilot:close" && this.close(), r === "btp-copilot:open" && this.open(), r === "btp-copilot:fullscreen" && this._toggleFullscreen(), r === "btp-copilot:minimize" && this.close();
487
850
  };
488
- window.addEventListener("message", t), this._removeMessageListener = () => window.removeEventListener("message", t);
851
+ window.addEventListener("message", e), this._removeMessageListener = () => window.removeEventListener("message", e);
489
852
  }
490
853
  disconnectedCallback() {
491
- var t;
492
- (t = this._removeMessageListener) == null || t.call(this), this._stopContextPolling();
854
+ var e;
855
+ (e = this._removeMessageListener) == null || e.call(this), this._stopContextPolling();
493
856
  }
494
- attributeChangedCallback(t, e, n) {
495
- e === n || !this.isConnected || (this._config = this._readConfig(), this._updateAfterAttributeChange());
857
+ attributeChangedCallback(e, t, n) {
858
+ t === n || !this.isConnected || (this._config = this._readConfig(), this._updateAfterAttributeChange());
496
859
  }
497
860
  // ── Public API ─────────────────────────────────────────────────────────────
498
861
  open() {
499
862
  this._open = !0, this.shadowRoot.querySelector(".panel").classList.remove("closed"), this.shadowRoot.querySelector(".toggle-btn").setAttribute("aria-expanded", "true"), this._clearBadge(), this._pushContextToIframe();
500
863
  }
501
864
  close() {
502
- this._open = !1;
503
- const t = this.shadowRoot.querySelector(".panel");
504
- t.classList.add("closed"), t.classList.remove("fullscreen"), this._isFullscreen = !1, this.shadowRoot.querySelector(".toggle-btn").setAttribute("aria-expanded", "false");
865
+ this._open = !1, this._isFullscreen = !1;
866
+ const e = this.shadowRoot.querySelector(".panel");
867
+ e.classList.add("closed"), e.classList.remove("fullscreen");
868
+ const t = this.shadowRoot.querySelector(".toggle-btn");
869
+ t.style.display = "", t.setAttribute("aria-expanded", "false");
505
870
  }
506
871
  _toggleFullscreen() {
507
872
  this._isFullscreen = !this._isFullscreen;
508
- const t = this.shadowRoot.querySelector(".panel"), e = this.shadowRoot.querySelector(".toggle-btn");
509
- this._isFullscreen ? (t.classList.add("fullscreen"), e.style.display = "none") : (t.classList.remove("fullscreen"), e.style.display = "");
873
+ const e = this.shadowRoot.querySelector(".panel"), t = this.shadowRoot.querySelector(".toggle-btn");
874
+ this._isFullscreen ? (e.classList.add("fullscreen"), t.style.display = "none") : (e.classList.remove("fullscreen"), t.style.display = "");
510
875
  }
511
- sendMessage(t) {
876
+ sendMessage(e) {
512
877
  this._open || this.open(), setTimeout(() => {
513
- var e, n;
514
- (n = (e = this._iframeEl) == null ? void 0 : e.contentWindow) == null || n.postMessage(
515
- { type: "btp-copilot:send-message", text: t },
516
- "*"
878
+ var t, n;
879
+ (n = (t = this._iframeEl) == null ? void 0 : t.contentWindow) == null || n.postMessage(
880
+ { type: "btp-copilot:send-message", text: e },
881
+ this._iframeOrigin()
517
882
  );
518
883
  }, 100);
519
884
  }
885
+ /**
886
+ * Push the currently-viewed record into the chatbot context.
887
+ *
888
+ * Call this from your Fiori controller or CAP handler after a record is loaded.
889
+ * The chatbot will have all field values and can answer questions like
890
+ * "what is the applicator?" or "what status is this order?" immediately.
891
+ *
892
+ * @param entityName The OData entity set name, e.g. "FertilizerBlend"
893
+ * @param data Plain object of field → value pairs (the record)
894
+ *
895
+ * @example // Fiori UI5 controller (1 line):
896
+ * document.querySelector('btp-copilot')
897
+ * .setRecord('FertilizerBlend', this.getView().getBindingContext().getObject());
898
+ */
899
+ setRecord(e, t) {
900
+ this._manualRecord = { _entity: e, ...t }, this._pushContextToIframe();
901
+ }
902
+ /**
903
+ * Push arbitrary key/value pairs into the chatbot context (without an entity name).
904
+ * Useful for custom metadata, computed values, or non-OData context.
905
+ *
906
+ * @example
907
+ * widget.pushData({ currentUser: 'John', selectedRegion: 'EU' });
908
+ */
909
+ pushData(e) {
910
+ this._manualRecord = { ...this._manualRecord, ...e }, this._pushContextToIframe();
911
+ }
912
+ /** Remove the manually-set record so auto-context takes over again. */
913
+ clearRecord() {
914
+ this._manualRecord = null, this._pushContextToIframe();
915
+ }
520
916
  // ── Shadow DOM ─────────────────────────────────────────────────────────────
521
917
  _buildShadow() {
522
- var i;
523
- const t = this.attachShadow({ mode: "open" }), e = document.createElement("style");
524
- e.textContent = v, t.appendChild(e);
918
+ var r;
919
+ const e = this.attachShadow({ mode: "open" }), t = document.createElement("style");
920
+ t.textContent = j, e.appendChild(t);
525
921
  const n = document.createElement("div");
526
- for (n.innerHTML = this._template(); n.firstChild; ) t.appendChild(n.firstChild);
527
- t.querySelector(".toggle-btn").addEventListener("click", () => {
922
+ for (n.innerHTML = this._template(); n.firstChild; ) e.appendChild(n.firstChild);
923
+ e.querySelector(".toggle-btn").addEventListener("click", () => {
528
924
  this._open ? this.close() : this.open();
529
- }), this._iframeEl = t.querySelector(".chat-iframe"), (i = this._iframeEl) == null || i.addEventListener("load", () => {
925
+ }), this._iframeEl = e.querySelector(".chat-iframe"), (r = this._iframeEl) == null || r.addEventListener("load", () => {
530
926
  this._pushAuthToIframe(), this._pushContextToIframe();
531
927
  });
532
928
  }
533
929
  _template() {
534
930
  var n;
535
- const t = ((n = this._config) == null ? void 0 : n.position) ?? "bottom-right", e = this._esc(this._buildIframeUrl());
931
+ const e = ((n = this._config) == null ? void 0 : n.position) ?? "bottom-right", t = this._esc(this._buildIframeUrl());
536
932
  return `
537
- <button class="toggle-btn ${t}" aria-label="Open AI assistant" aria-expanded="false" aria-haspopup="dialog">
933
+ <button class="toggle-btn ${e}" aria-label="Open AI assistant" aria-expanded="false" aria-haspopup="dialog">
538
934
  <svg class="icon-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
539
935
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
540
936
  </svg>
@@ -544,10 +940,10 @@ const v = `/* ── Host element ───────────────
544
940
  <span class="badge" aria-hidden="true"></span>
545
941
  </button>
546
942
 
547
- <div class="panel closed ${t}" role="dialog" aria-label="AI Assistant" aria-modal="true">
943
+ <div class="panel closed ${e}" role="dialog" aria-label="AI Assistant" aria-modal="true">
548
944
  <iframe
549
945
  class="chat-iframe"
550
- src="${e}"
946
+ src="${t}"
551
947
  title="AI Assistant"
552
948
  allow="clipboard-read; clipboard-write"
553
949
  loading="lazy">
@@ -556,27 +952,30 @@ const v = `/* ── Host element ───────────────
556
952
  }
557
953
  // ── Context + Auth ─────────────────────────────────────────────────────────
558
954
  _buildBaseContext() {
559
- const t = d.resolve(this._config.token);
955
+ const e = S.resolve(this._config.token), t = this._config.serviceUrl ?? "", n = t ? t.startsWith("http") ? t : `${window.location.origin}${t}` : void 0;
560
956
  return {
561
957
  app_id: this._config.appId,
562
958
  app_name: this._config.appName,
563
- service_url: this._config.serviceUrl,
959
+ service_url: n,
960
+ // Manually set record (via setRecord / pushData) — overrides auto-context entity_data
961
+ ...this._manualRecord ? { entity_data: this._manualRecord } : {},
564
962
  // Include the resolved auth token in extra so the backend can
565
963
  // proxy OData calls on behalf of the user to fetch real data
566
- ...t ? { extra: { auth_token: t } } : {}
964
+ extra: {
965
+ ...e ? { auth_token: e } : {},
966
+ ...this._config.backendUrl ? { backend_url: this._config.backendUrl } : {}
967
+ }
567
968
  };
568
969
  }
569
970
  _buildIframeUrl() {
570
- var e;
571
- const t = ((e = this._config) == null ? void 0 : e.iframeUrl) ?? "";
572
- if (!t) return "";
971
+ var t;
972
+ const e = ((t = this._config) == null ? void 0 : t.iframeUrl) ?? "";
973
+ if (!e) return "";
573
974
  try {
574
- const n = new URL(t), { appId: i, appName: r, serviceUrl: s } = this._config;
575
- i && n.searchParams.set("appId", i), r && n.searchParams.set("appName", r), s && n.searchParams.set("serviceUrl", s);
576
- const a = d.resolve(this._config.token);
577
- return a && n.searchParams.set("token", a), n.toString();
975
+ const n = new URL(e), { appId: r, appName: a, serviceUrl: i } = this._config;
976
+ return r && n.searchParams.set("appId", r), a && n.searchParams.set("appName", a), i && n.searchParams.set("serviceUrl", i), n.toString();
578
977
  } catch {
579
- return t;
978
+ return e;
580
979
  }
581
980
  }
582
981
  /** Derive the allowed postMessage target origin from the configured iframe URL.
@@ -589,31 +988,31 @@ const v = `/* ── Host element ───────────────
589
988
  }
590
989
  }
591
990
  _pushContextToIframe() {
592
- var e;
593
- if (!((e = this._iframeEl) != null && e.contentWindow)) return;
594
- const t = this._iframeOrigin();
595
- this._config.autoContext ? g.captureAsync(this._buildBaseContext()).then((n) => {
596
- var i, r;
597
- (r = (i = this._iframeEl) == null ? void 0 : i.contentWindow) == null || r.postMessage(
991
+ var t;
992
+ if (!((t = this._iframeEl) != null && t.contentWindow)) return;
993
+ const e = this._iframeOrigin();
994
+ this._config.autoContext ? k.captureAsync(this._buildBaseContext()).then((n) => {
995
+ var r, a;
996
+ (a = (r = this._iframeEl) == null ? void 0 : r.contentWindow) == null || a.postMessage(
598
997
  { type: "btp-copilot:set-context", payload: n },
599
- t
998
+ e
600
999
  ), n.service_url && !this._config.serviceUrl && (this._config = { ...this._config, serviceUrl: n.service_url });
601
1000
  }).catch(() => {
602
- var n, i;
603
- (i = (n = this._iframeEl) == null ? void 0 : n.contentWindow) == null || i.postMessage(
1001
+ var n, r;
1002
+ (r = (n = this._iframeEl) == null ? void 0 : n.contentWindow) == null || r.postMessage(
604
1003
  { type: "btp-copilot:set-context", payload: this._buildBaseContext() },
605
- t
1004
+ e
606
1005
  );
607
1006
  }) : this._iframeEl.contentWindow.postMessage(
608
1007
  { type: "btp-copilot:set-context", payload: this._buildBaseContext() },
609
- t
1008
+ e
610
1009
  );
611
1010
  }
612
1011
  _pushAuthToIframe() {
613
- var e;
614
- const t = d.resolve(this._config.token);
615
- !t || !((e = this._iframeEl) != null && e.contentWindow) || this._iframeEl.contentWindow.postMessage(
616
- { type: "btp-copilot:auth", token: t },
1012
+ var t;
1013
+ const e = S.resolve(this._config.token);
1014
+ !e || !((t = this._iframeEl) != null && t.contentWindow) || this._iframeEl.contentWindow.postMessage(
1015
+ { type: "btp-copilot:auth", token: e },
617
1016
  this._iframeOrigin()
618
1017
  );
619
1018
  }
@@ -627,39 +1026,40 @@ const v = `/* ── Host element ───────────────
627
1026
  }
628
1027
  // ── Config ─────────────────────────────────────────────────────────────────
629
1028
  _readConfig() {
630
- const t = (e) => this.getAttribute(e);
1029
+ const e = (t) => this.getAttribute(t);
631
1030
  return {
632
- iframeUrl: t("iframe-url") ?? "",
633
- appId: t("app-id") ?? "default",
634
- appName: t("app-name") ?? void 0,
635
- serviceUrl: t("service-url") ?? void 0,
636
- autoContext: t("auto-context") !== "false",
637
- position: t("position") ?? "bottom-right",
638
- theme: t("theme") ?? "auto",
639
- token: t("token") ?? void 0,
640
- contextInterval: Math.max(500, Number(t("context-interval")) || 3e3)
1031
+ iframeUrl: e("iframe-url") ?? "",
1032
+ appId: e("app-id") ?? "default",
1033
+ appName: e("app-name") ?? void 0,
1034
+ serviceUrl: e("service-url") ?? void 0,
1035
+ backendUrl: e("backend-url") ?? void 0,
1036
+ autoContext: e("auto-context") !== "false",
1037
+ position: e("position") ?? "bottom-right",
1038
+ theme: e("theme") ?? "auto",
1039
+ token: e("token") ?? void 0,
1040
+ contextInterval: Math.max(500, Number(e("context-interval")) || 3e3)
641
1041
  };
642
1042
  }
643
1043
  _updateAfterAttributeChange() {
644
1044
  var n;
645
- const t = (n = this.shadowRoot) == null ? void 0 : n.querySelector("style");
646
- t && (t.textContent = v);
647
- const e = this._buildIframeUrl();
648
- this._iframeEl && this._iframeEl.src !== e && e && (this._iframeEl.src = e), this._stopContextPolling(), this._startContextPolling();
1045
+ const e = (n = this.shadowRoot) == null ? void 0 : n.querySelector("style");
1046
+ e && (e.textContent = j);
1047
+ const t = this._buildIframeUrl();
1048
+ this._iframeEl && this._iframeEl.src !== t && t && (this._iframeEl.src = t), this._stopContextPolling(), this._startContextPolling();
649
1049
  }
650
1050
  _clearBadge() {
651
- var t, e;
652
- (e = (t = this.shadowRoot) == null ? void 0 : t.querySelector(".badge")) == null || e.classList.remove("visible");
1051
+ var e, t;
1052
+ (t = (e = this.shadowRoot) == null ? void 0 : e.querySelector(".badge")) == null || t.classList.remove("visible");
653
1053
  }
654
- _esc(t) {
655
- return t.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1054
+ _esc(e) {
1055
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
656
1056
  }
657
1057
  };
658
- _.observedAttributes = [...x];
659
- let y = _;
660
- customElements.get("btp-copilot") || customElements.define("btp-copilot", y);
1058
+ N.observedAttributes = [...O];
1059
+ let T = N;
1060
+ customElements.get("btp-copilot") || customElements.define("btp-copilot", T);
661
1061
  export {
662
- y as BtpCopilotElement,
663
- g as ContextBridge
1062
+ T as BtpCopilotElement,
1063
+ k as ContextBridge
664
1064
  };
665
1065
  //# sourceMappingURL=btp-copilot.esm.js.map