cap-copilot-widget 0.1.1 → 0.1.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.
@@ -1,9 +1,9 @@
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
9
  const r = localStorage.getItem("access_token");
@@ -12,15 +12,15 @@ const f = class f {
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, ...r] = e.trim().split("=");
23
- if (f.XSUAA_COOKIE_NAMES.includes(n.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
24
  return decodeURIComponent(r.join("=").trim());
25
25
  }
26
26
  } catch {
@@ -28,81 +28,87 @@ const f = class f {
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, r;
40
+ var e, t, n, r;
41
41
  try {
42
- const o = (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 (o) {
47
- const i = (r = o.hrefForExternal) == null ? void 0 : r.call(o);
46
+ if (a) {
47
+ const i = (r = a.hrefForExternal) == null ? void 0 : r.call(a);
48
48
  if (i) return i;
49
49
  }
50
- } catch (o) {
51
- console.log(o);
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, r, o, i;
64
+ var e, t, n, r, a, i;
65
65
  try {
66
- return ((i = (o = (r = (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 : r.call(n)) == null ? void 0 : o.getLanguageTag) == null ? void 0 : i.call(o)) ?? 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, r;
74
+ var e, t, n, r, a;
75
75
  try {
76
- const o = (e = (t = sap == null ? void 0 : sap.ui) == null ? void 0 : t.getCore) == null ? void 0 : e.call(t);
77
- if (!o) return null;
78
- let i = document.activeElement;
79
- for (; i; ) {
80
- const a = i.getAttribute("id") || i.getAttribute("data-sap-ui");
81
- if (a) {
82
- const s = o.byId(a), l = (n = s == null ? void 0 : s.getBindingContext) == null ? void 0 : n.call(s);
83
- if (l) {
84
- const p = (r = l.getObject) == null ? void 0 : r.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
- i = i.parentElement;
92
+ o = o.parentElement;
90
93
  }
91
- return u._scanAllControls(o);
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 r = t.mElements ?? {};
100
- for (const o of Object.keys(r)) {
101
- const i = r[o], a = (e = i == null ? void 0 : i.getBindingContext) == null ? void 0 : e.call(i);
102
- if (a) {
103
- const s = (n = a.getObject) == null ? void 0 : n.call(a);
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);
104
107
  if (s && typeof s == "object" && Object.keys(s).length > 0)
105
- return u._sanitize(s);
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,239 +116,573 @@ 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
123
  const r = n.mElements ?? {};
118
- for (const o of Object.keys(r)) {
119
- const i = r[o];
124
+ for (const a of Object.keys(r)) {
125
+ const i = r[a];
120
126
  if (!(i != null && i.getModel)) continue;
121
- const a = i.getModel();
122
- if (!a) continue;
123
- const s = a.sServiceUrl ?? a._sServiceUrl;
124
- if (typeof s == "string" && s.length > 4)
125
- return s.replace(/\/$/, "");
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, r] of Object.entries(t))
134
- n.startsWith("__") || (r === null || typeof r != "object" ? e[n] = r : r.__deferred || (e[n] = r));
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);
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);
143
177
  const r = sessionStorage.getItem(n);
144
178
  if (r)
145
179
  try {
146
- const o = JSON.parse(r);
147
- return c._cache.set(n, o), o;
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 o = `${t}/$metadata`, i = { Accept: "application/xml" };
152
- e && (i.Authorization = `Bearer ${e}`);
153
- const a = await fetch(o, { headers: i });
154
- if (!a.ok) return [];
155
- const s = await a.text(), l = c._parseEDMX(s), 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 r = new DOMParser().parseFromString(t, "text/xml").querySelectorAll("EntityType");
164
- return Array.from(r).map((o) => {
165
- const i = o.getAttribute("Name") ?? "", a = o.querySelectorAll("Key > PropertyRef"), s = new Set(
166
- Array.from(a).map((h) => h.getAttribute("Name") ?? "")
167
- ), l = Array.from(
168
- o.querySelectorAll("Property")
169
- ).map((h) => ({
170
- name: h.getAttribute("Name") ?? "",
171
- type: c._shortType(h.getAttribute("Type") ?? ""),
172
- isKey: s.has(h.getAttribute("Name") ?? ""),
173
- nullable: h.getAttribute("Nullable") !== "false"
174
- })), p = Array.from(
175
- o.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: i, 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 r of t) {
207
+ for (const r of e.entities) {
194
208
  if (!r.name) continue;
195
- const o = r.properties.filter((s) => s.isKey), i = r.properties.filter((s) => !s.isKey), a = [
209
+ const a = r.entitySetName ?? r.name, i = r.properties.filter((s) => s.isKey), o = r.properties.filter((s) => !s.isKey), l = [
196
210
  `Entity: ${r.name}`,
197
- `OData service: ${e}`,
198
- `Entity set path: ${e}/${r.name}`,
199
- `Count path: ${e}/${r.name}/$count`
211
+ `Entity set: ${a}`,
212
+ `OData service: ${t}`,
213
+ `Entity set path: ${t}/${a}`,
214
+ `Count path: ${t}/${a}/$count`
200
215
  ];
201
- o.length && a.push(
202
- `Key fields: ${o.map((s) => `${s.name} (${s.type})`).join(", ")}`
203
- ), i.length && a.push(
204
- `Fields: ${i.map(
205
- (s) => `${s.name} (${s.type}${s.nullable ? "" : ", required"})`
206
- ).join(", ")}`
207
- ), r.navProperties.length && a.push(
208
- `Navigation: ${r.navProperties.map((s) => `${s.name} → ${s.type}`).join(", ")}`
209
- ), n.push({
210
- title: `${r.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((r) => r.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
- var n, r, o;
235
- 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 };
236
472
  try {
237
- const i = m.getHash();
238
- i && (e.current_view = i);
473
+ const i = C.getHash();
474
+ i && (t.current_view = i);
239
475
  } catch {
240
476
  }
241
477
  try {
242
- const i = u.getCurrentEntity();
243
- i && Object.keys(i).length > 0 && (e.entity_data = i);
478
+ const i = _.getCurrentEntity();
479
+ i && Object.keys(i).length > 0 && (t.entity_data = i);
244
480
  } catch {
245
481
  }
246
482
  try {
247
- e.user_locale || (e.user_locale = m.getUserLocale());
483
+ t.user_locale || (t.user_locale = C.getUserLocale());
248
484
  } catch {
249
485
  }
250
486
  try {
251
487
  const i = window.location.pathname + window.location.search + window.location.hash;
252
- (!e.current_view || e.current_view === "") && (e.current_view = i);
488
+ (!t.current_view || t.current_view === "") && (t.current_view = i);
253
489
  } catch {
254
490
  }
255
491
  try {
256
- e.user_locale || (e.user_locale = navigator.language || ((n = navigator.languages) == null ? void 0 : n[0]) || "en");
492
+ t.user_locale || (t.user_locale = navigator.language || ((n = navigator.languages) == null ? void 0 : n[0]) || "en");
257
493
  } catch {
258
494
  }
259
495
  try {
260
- e.extra = {
261
- ...e.extra ?? {},
262
- page_title: ((r = e.extra) == null ? void 0 : r.page_title) ?? document.title ?? "",
263
- page_url: ((o = e.extra) == null ? void 0 : o.page_url) ?? window.location.href ?? ""
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 ?? ""
264
500
  };
265
501
  } catch {
266
502
  }
267
503
  try {
268
504
  const i = window.__APP_CONTEXT__;
269
- i && (i.entity_data && (e.entity_data = { ...i.entity_data, ...e.entity_data }), i.current_view && !e.current_view && (e.current_view = i.current_view), i.extra && (e.extra = { ...i.extra, ...e.extra ?? {} }), i.service_url && !e.service_url && (e.service_url = i.service_url));
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));
270
506
  } catch {
271
507
  }
272
- return e;
508
+ return t;
273
509
  }
274
- static async captureAsync(t) {
275
- var n;
276
- const e = g.capture(t);
277
- 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)
278
514
  try {
279
- const r = ((n = e.extra) == null ? void 0 : n.auth_token) ?? null, o = await b.fetchSchemaDocuments(
280
- e.service_url,
281
- r
282
- );
283
- if (o.length > 0) {
284
- const i = o.map((a) => `## ${a.title}
285
- ${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(`
286
521
 
287
522
  `);
288
- e.extra = { ...e.extra ?? {}, schema_hint: i };
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
+ }
289
543
  }
290
544
  } catch {
291
545
  }
292
- 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
+ }
293
626
  }
294
627
  static _discoverServiceUrl() {
295
628
  try {
296
- const t = u.getServiceUrl();
297
- if (t) return t;
629
+ const e = _.getServiceUrl();
630
+ if (e) return e;
298
631
  } catch {
299
632
  }
300
633
  try {
301
- const t = performance.getEntriesByType(
634
+ const e = performance.getEntriesByType(
302
635
  "resource"
303
636
  );
304
- for (const e of t) {
305
- const n = e.name.match(/^(https?:\/\/[^?#]+)\/\$metadata/);
637
+ for (const t of e) {
638
+ const n = t.name.match(/^(https?:\/\/[^?#]+)\/\$metadata/);
306
639
  if (n) return n[1];
307
640
  }
308
641
  } catch {
309
642
  }
310
643
  try {
311
- const t = window.location.href.match(
644
+ const e = window.location.href.match(
312
645
  /(https?:\/\/[^/]+(?:\/odata\/v[24]\/[^/?#]+|\/sap\/opu\/odata\/[^/?#]+))/
313
646
  );
314
- if (t) return t[1];
647
+ if (e) return e[1];
315
648
  } catch {
316
649
  }
317
650
  return null;
318
651
  }
319
652
  static async discoverFromManifest() {
320
- var t;
653
+ var e;
321
654
  try {
322
- const e = await fetch("/manifest.json");
323
- if (!e.ok) return null;
324
- const n = await e.json(), r = ((t = n == null ? void 0 : n["sap.app"]) == null ? void 0 : t.dataSources) ?? {};
325
- for (const o of Object.keys(r)) {
326
- const i = r[o];
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];
327
660
  if ((i == null ? void 0 : i.type) === "ODataAnnotation") continue;
328
- const a = i == null ? void 0 : i.uri;
329
- if (typeof a == "string" && a.length > 0)
330
- return new URL(a, window.location.href).toString().replace(/\/$/, "");
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(/\/$/, "");
331
664
  }
332
665
  } catch {
333
666
  }
334
667
  return null;
335
668
  }
336
- static listenForUpdates(t, e, n, r) {
337
- const o = (i) => {
338
- if (!i.data || typeof i.data != "object") return;
339
- const { type: a, payload: s, text: l } = i.data;
340
- 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) {
341
681
  case "btp-copilot:set-context":
342
- s && t(s);
682
+ s && e(s);
343
683
  break;
344
684
  case "btp-copilot:send-message":
345
- l && e(l);
685
+ c && t(c);
346
686
  break;
347
687
  case "btp-copilot:open":
348
688
  n();
@@ -352,10 +692,12 @@ ${a.content}`).join(`
352
692
  break;
353
693
  }
354
694
  };
355
- return window.addEventListener("message", o), () => window.removeEventListener("message", o);
695
+ return window.addEventListener("message", i), () => window.removeEventListener("message", i);
356
696
  }
357
- }
358
- 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 ─────────────────────────────────────────────────────────── */
359
701
  :host {
360
702
  position: fixed;
361
703
  z-index: 2147483647;
@@ -482,35 +824,38 @@ const v = `/* ── Host element ───────────────
482
824
  }
483
825
  .chat-iframe { border-radius: 1rem 1rem 0 0; }
484
826
  }
485
- `, x = [
827
+ `, O = [
486
828
  "iframe-url",
487
829
  "app-id",
488
830
  "app-name",
489
831
  "service-url",
832
+ "backend-url",
490
833
  "auto-context",
491
834
  "position",
492
835
  "theme",
493
836
  "token",
494
837
  "context-interval"
495
- ], y = class y extends HTMLElement {
838
+ ], N = class N extends HTMLElement {
496
839
  constructor() {
497
- 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;
498
841
  }
499
842
  connectedCallback() {
500
843
  this._config = this._readConfig(), this._buildShadow(), this._startContextPolling();
501
- const t = (e) => {
502
- if (!e.data || typeof e.data != "object") return;
503
- const { type: n } = e.data;
504
- 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();
505
850
  };
506
- window.addEventListener("message", t), this._removeMessageListener = () => window.removeEventListener("message", t);
851
+ window.addEventListener("message", e), this._removeMessageListener = () => window.removeEventListener("message", e);
507
852
  }
508
853
  disconnectedCallback() {
509
- var t;
510
- (t = this._removeMessageListener) == null || t.call(this), this._stopContextPolling();
854
+ var e;
855
+ (e = this._removeMessageListener) == null || e.call(this), this._stopContextPolling();
511
856
  }
512
- attributeChangedCallback(t, e, n) {
513
- 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());
514
859
  }
515
860
  // ── Public API ─────────────────────────────────────────────────────────────
516
861
  open() {
@@ -518,43 +863,74 @@ const v = `/* ── Host element ───────────────
518
863
  }
519
864
  close() {
520
865
  this._open = !1, this._isFullscreen = !1;
521
- const t = this.shadowRoot.querySelector(".panel");
522
- t.classList.add("closed"), t.classList.remove("fullscreen");
523
- const e = this.shadowRoot.querySelector(".toggle-btn");
524
- e.style.display = "", e.setAttribute("aria-expanded", "false");
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");
525
870
  }
526
871
  _toggleFullscreen() {
527
872
  this._isFullscreen = !this._isFullscreen;
528
- const t = this.shadowRoot.querySelector(".panel"), e = this.shadowRoot.querySelector(".toggle-btn");
529
- 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 = "");
530
875
  }
531
- sendMessage(t) {
876
+ sendMessage(e) {
532
877
  this._open || this.open(), setTimeout(() => {
533
- var e, n;
534
- (n = (e = this._iframeEl) == null ? void 0 : e.contentWindow) == null || n.postMessage(
535
- { type: "btp-copilot:send-message", text: t },
536
- "*"
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()
537
882
  );
538
883
  }, 100);
539
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
+ }
540
916
  // ── Shadow DOM ─────────────────────────────────────────────────────────────
541
917
  _buildShadow() {
542
918
  var r;
543
- const t = this.attachShadow({ mode: "open" }), e = document.createElement("style");
544
- e.textContent = v, t.appendChild(e);
919
+ const e = this.attachShadow({ mode: "open" }), t = document.createElement("style");
920
+ t.textContent = j, e.appendChild(t);
545
921
  const n = document.createElement("div");
546
- for (n.innerHTML = this._template(); n.firstChild; ) t.appendChild(n.firstChild);
547
- 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", () => {
548
924
  this._open ? this.close() : this.open();
549
- }), this._iframeEl = t.querySelector(".chat-iframe"), (r = this._iframeEl) == null || r.addEventListener("load", () => {
925
+ }), this._iframeEl = e.querySelector(".chat-iframe"), (r = this._iframeEl) == null || r.addEventListener("load", () => {
550
926
  this._pushAuthToIframe(), this._pushContextToIframe();
551
927
  });
552
928
  }
553
929
  _template() {
554
930
  var n;
555
- 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());
556
932
  return `
557
- <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">
558
934
  <svg class="icon-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
559
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"/>
560
936
  </svg>
@@ -564,10 +940,10 @@ const v = `/* ── Host element ───────────────
564
940
  <span class="badge" aria-hidden="true"></span>
565
941
  </button>
566
942
 
567
- <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">
568
944
  <iframe
569
945
  class="chat-iframe"
570
- src="${e}"
946
+ src="${t}"
571
947
  title="AI Assistant"
572
948
  allow="clipboard-read; clipboard-write"
573
949
  loading="lazy">
@@ -576,27 +952,30 @@ const v = `/* ── Host element ───────────────
576
952
  }
577
953
  // ── Context + Auth ─────────────────────────────────────────────────────────
578
954
  _buildBaseContext() {
579
- 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;
580
956
  return {
581
957
  app_id: this._config.appId,
582
958
  app_name: this._config.appName,
583
- 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 } : {},
584
962
  // Include the resolved auth token in extra so the backend can
585
963
  // proxy OData calls on behalf of the user to fetch real data
586
- ...t ? { extra: { auth_token: t } } : {}
964
+ extra: {
965
+ ...e ? { auth_token: e } : {},
966
+ ...this._config.backendUrl ? { backend_url: this._config.backendUrl } : {}
967
+ }
587
968
  };
588
969
  }
589
970
  _buildIframeUrl() {
590
- var e;
591
- const t = ((e = this._config) == null ? void 0 : e.iframeUrl) ?? "";
592
- if (!t) return "";
971
+ var t;
972
+ const e = ((t = this._config) == null ? void 0 : t.iframeUrl) ?? "";
973
+ if (!e) return "";
593
974
  try {
594
- const n = new URL(t), { appId: r, appName: o, serviceUrl: i } = this._config;
595
- r && n.searchParams.set("appId", r), o && n.searchParams.set("appName", o), i && n.searchParams.set("serviceUrl", i);
596
- const a = d.resolve(this._config.token);
597
- 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();
598
977
  } catch {
599
- return t;
978
+ return e;
600
979
  }
601
980
  }
602
981
  /** Derive the allowed postMessage target origin from the configured iframe URL.
@@ -609,31 +988,31 @@ const v = `/* ── Host element ───────────────
609
988
  }
610
989
  }
611
990
  _pushContextToIframe() {
612
- var e;
613
- if (!((e = this._iframeEl) != null && e.contentWindow)) return;
614
- const t = this._iframeOrigin();
615
- this._config.autoContext ? g.captureAsync(this._buildBaseContext()).then((n) => {
616
- var r, o;
617
- (o = (r = this._iframeEl) == null ? void 0 : r.contentWindow) == null || o.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(
618
997
  { type: "btp-copilot:set-context", payload: n },
619
- t
998
+ e
620
999
  ), n.service_url && !this._config.serviceUrl && (this._config = { ...this._config, serviceUrl: n.service_url });
621
1000
  }).catch(() => {
622
1001
  var n, r;
623
1002
  (r = (n = this._iframeEl) == null ? void 0 : n.contentWindow) == null || r.postMessage(
624
1003
  { type: "btp-copilot:set-context", payload: this._buildBaseContext() },
625
- t
1004
+ e
626
1005
  );
627
1006
  }) : this._iframeEl.contentWindow.postMessage(
628
1007
  { type: "btp-copilot:set-context", payload: this._buildBaseContext() },
629
- t
1008
+ e
630
1009
  );
631
1010
  }
632
1011
  _pushAuthToIframe() {
633
- var e;
634
- const t = d.resolve(this._config.token);
635
- !t || !((e = this._iframeEl) != null && e.contentWindow) || this._iframeEl.contentWindow.postMessage(
636
- { 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 },
637
1016
  this._iframeOrigin()
638
1017
  );
639
1018
  }
@@ -647,39 +1026,40 @@ const v = `/* ── Host element ───────────────
647
1026
  }
648
1027
  // ── Config ─────────────────────────────────────────────────────────────────
649
1028
  _readConfig() {
650
- const t = (e) => this.getAttribute(e);
1029
+ const e = (t) => this.getAttribute(t);
651
1030
  return {
652
- iframeUrl: t("iframe-url") ?? "",
653
- appId: t("app-id") ?? "default",
654
- appName: t("app-name") ?? void 0,
655
- serviceUrl: t("service-url") ?? void 0,
656
- autoContext: t("auto-context") !== "false",
657
- position: t("position") ?? "bottom-right",
658
- theme: t("theme") ?? "auto",
659
- token: t("token") ?? void 0,
660
- 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)
661
1041
  };
662
1042
  }
663
1043
  _updateAfterAttributeChange() {
664
1044
  var n;
665
- const t = (n = this.shadowRoot) == null ? void 0 : n.querySelector("style");
666
- t && (t.textContent = v);
667
- const e = this._buildIframeUrl();
668
- 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();
669
1049
  }
670
1050
  _clearBadge() {
671
- var t, e;
672
- (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");
673
1053
  }
674
- _esc(t) {
675
- 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;");
676
1056
  }
677
1057
  };
678
- y.observedAttributes = [...x];
679
- let _ = y;
680
- customElements.get("btp-copilot") || customElements.define("btp-copilot", _);
1058
+ N.observedAttributes = [...O];
1059
+ let T = N;
1060
+ customElements.get("btp-copilot") || customElements.define("btp-copilot", T);
681
1061
  export {
682
- _ as BtpCopilotElement,
683
- g as ContextBridge
1062
+ T as BtpCopilotElement,
1063
+ k as ContextBridge
684
1064
  };
685
1065
  //# sourceMappingURL=btp-copilot.esm.js.map