@kieksme/csp-plugin-chat 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,3 +21,7 @@ Der Assistent unterscheidet das Unternehmen von seinem Serviceportal: „Service
21
21
  Der System-Prompt begrenzt den Chat auf Support und Portalbedienung. Fachfremde Aufgaben werden kurz zum Support-Zweck zurückgeführt; unklare Anliegen werden durch eine Rückfrage eingeordnet.
22
22
 
23
23
  Optional persisted conversations and real human support are available through the Teams plugin. The UI uses `chatSupport` from the public profile and identifies real support separately from the digital assistant. See [Teams support](../../docs/teams-support.md).
24
+
25
+ For portals with an existing same-origin authenticated gateway, set `chatSupport.auth` to `session` to reuse the login without browser tokens. The API still requires a validated delegated access token supplied by the internal gateway. See [Teams support](../../docs/teams-support.md#vorhandene-portal-anmeldung-verwenden).
26
+
27
+ WebMCP tool: `chat_ask` streams one answer from `POST /api/v1/chat` and returns text, responder and sources. It changes no data but uses the chat rate limit and provider quota, so it is not marked read-only. See [WebMCP](../../docs/webmcp.md).
package/dist/browser.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import * as react from 'react';
1
+ import * as _kieksme_csp_sdk from '@kieksme/csp-sdk';
2
2
  import { BrowserContext } from '@kieksme/csp-sdk';
3
+ import * as react from 'react';
3
4
 
4
5
  declare function Chat(ctx: BrowserContext): react.JSX.Element;
5
6
  declare const _default: {
6
7
  id: string;
7
8
  sdkVersion: string;
9
+ tools: _kieksme_csp_sdk.WebMcpTool[];
8
10
  sections: {
9
11
  id: string;
10
12
  placement: "hero";
package/dist/browser.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import {
2
2
  cspPlugin
3
- } from "./chunk-ALGUQ3T5.js";
3
+ } from "./chunk-R6G6BYF4.js";
4
4
 
5
- // src/browser.tsx
6
- import { useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState2 } from "react";
5
+ // src/tools.ts
6
+ import {
7
+ UNTRUSTED_NOTE,
8
+ webMcpError,
9
+ webMcpJson
10
+ } from "@kieksme/csp-sdk";
7
11
 
8
12
  // src/answer.ts
9
13
  import { safeUrl } from "@kieksme/csp-sdk";
@@ -33,10 +37,143 @@ function answerParts(text, sources, streaming = false) {
33
37
  });
34
38
  }
35
39
 
40
+ // src/tools.ts
41
+ function parseMessages(value) {
42
+ if (!Array.isArray(value) || value.length < 1 || value.length > 20)
43
+ return "messages muss 1 bis 20 Eintr\xE4ge enthalten.";
44
+ let total = 0;
45
+ const messages = [];
46
+ for (const item of value) {
47
+ const { role, content } = item ?? {};
48
+ if (role !== "user" && role !== "assistant" || typeof content !== "string" || content.length < 1 || content.length > 4e3)
49
+ return "Jede Nachricht braucht role (user|assistant) und content (1 bis 4000 Zeichen).";
50
+ total += content.length;
51
+ messages.push({ role, content });
52
+ }
53
+ if (messages.at(-1).role !== "user")
54
+ return "Die letzte Nachricht muss von user stammen.";
55
+ if (total > 16e3)
56
+ return "Der Verlauf darf h\xF6chstens 16000 Zeichen enthalten.";
57
+ return messages;
58
+ }
59
+ async function askChat(ctx, messages, signal) {
60
+ const response = await (ctx.request ?? fetch)(
61
+ ctx.config.apiUrl + "/api/v1/chat",
62
+ {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ messages }),
66
+ signal
67
+ }
68
+ );
69
+ if (!response.ok || !response.body)
70
+ throw new Error(
71
+ response.status === 429 ? "Zu viele Anfragen. Bitte eine Minute warten." : "Chat derzeit nicht erreichbar."
72
+ );
73
+ const reader = response.body.getReader();
74
+ const decoder = new TextDecoder();
75
+ let buffer = "";
76
+ let answer = "";
77
+ let complete = false;
78
+ let responder;
79
+ let sources = [];
80
+ try {
81
+ while (true) {
82
+ const { done, value } = await reader.read();
83
+ buffer += decoder.decode(value, { stream: !done });
84
+ let boundary;
85
+ while ((boundary = buffer.indexOf("\n\n")) >= 0) {
86
+ const block = buffer.slice(0, boundary);
87
+ buffer = buffer.slice(boundary + 2);
88
+ const type = block.match(/^event: (.+)$/m)?.[1];
89
+ const payload = block.match(/^data: (.+)$/m)?.[1];
90
+ if (!payload) continue;
91
+ const event = JSON.parse(payload);
92
+ if (type === "delta") answer += event.text;
93
+ if (type === "responder") responder = event;
94
+ if (type === "sources") sources = event;
95
+ if (type === "error") throw new Error(event.message);
96
+ if (type === "done") complete = true;
97
+ }
98
+ if (done) break;
99
+ }
100
+ } finally {
101
+ await reader.cancel().catch(() => void 0);
102
+ reader.releaseLock();
103
+ }
104
+ if (!complete)
105
+ throw new Error(
106
+ "Die Verbindung wurde unterbrochen. Antwort unvollst\xE4ndig."
107
+ );
108
+ return { answer, responder, sources };
109
+ }
110
+ var chatTools = [
111
+ {
112
+ name: "chat_ask",
113
+ description: "Fragt den digitalen Assistenten des Portals (kennt Hilfe-Inhalte, Team, Schichten und Status). Verbraucht Chat-Kontingent (Rate-Limit pro IP) und ruft einen KI-Anbieter auf; nur nutzen, wenn die anderen Tools nicht reichen. \xC4ndert keine Daten. Die letzte Nachricht muss von user stammen.",
114
+ inputSchema: {
115
+ type: "object",
116
+ properties: {
117
+ messages: {
118
+ type: "array",
119
+ minItems: 1,
120
+ maxItems: 20,
121
+ items: {
122
+ type: "object",
123
+ properties: {
124
+ role: { type: "string", enum: ["user", "assistant"] },
125
+ content: { type: "string", minLength: 1, maxLength: 4e3 }
126
+ },
127
+ required: ["role", "content"],
128
+ additionalProperties: false
129
+ }
130
+ }
131
+ },
132
+ required: ["messages"],
133
+ additionalProperties: false
134
+ },
135
+ annotations: { readOnlyHint: false },
136
+ execute: async (args, ctx) => {
137
+ if (ctx.config.chatSupport)
138
+ return webMcpError(
139
+ "Der Chat l\xE4uft im Supportmodus mit Anmeldung und ist per Tool nicht verf\xFCgbar. Bitte die Hotline nutzen."
140
+ );
141
+ const messages = parseMessages(args.messages);
142
+ if (typeof messages === "string") return webMcpError(messages);
143
+ try {
144
+ const { answer, responder, sources } = await askChat(
145
+ ctx,
146
+ messages,
147
+ AbortSignal.timeout(9e4)
148
+ );
149
+ return webMcpJson({
150
+ notice: UNTRUSTED_NOTE,
151
+ responder: responder?.role === "on-duty" ? responder.name : "Service-Team",
152
+ answer,
153
+ sources: sources.map((s) => ({
154
+ id: s.id,
155
+ title: s.title,
156
+ href: sourceHref(s.href),
157
+ stale: s.stale ?? false
158
+ }))
159
+ });
160
+ } catch (error) {
161
+ return webMcpError(
162
+ error instanceof Error ? error.message : "Chat nicht verf\xFCgbar."
163
+ );
164
+ }
165
+ }
166
+ }
167
+ ];
168
+
169
+ // src/browser.tsx
170
+ import { useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState2 } from "react";
171
+
36
172
  // src/support-chat.tsx
37
173
  import { useEffect, useRef, useState } from "react";
38
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
39
- async function session(ctx) {
174
+
175
+ // src/support-session.ts
176
+ async function supportSession(ctx) {
40
177
  const c = ctx.config.chatSupport;
41
178
  if (c.mode === "demo")
42
179
  return {
@@ -45,6 +182,7 @@ async function session(ctx) {
45
182
  },
46
183
  name: "Portal-Nutzer Demo"
47
184
  };
185
+ if (c.auth === "session") return cookieSession(ctx);
48
186
  const { PublicClientApplication } = await import("@azure/msal-browser");
49
187
  const client = new PublicClientApplication({
50
188
  auth: {
@@ -68,6 +206,37 @@ async function session(ctx) {
68
206
  }
69
207
  };
70
208
  }
209
+ function cookieSession(ctx) {
210
+ let loaded = false;
211
+ const current = {
212
+ name: "Portal-Nutzer",
213
+ async token() {
214
+ if (!loaded) {
215
+ const response = await (ctx.request ?? fetch)(
216
+ ctx.config.apiUrl + "/api/v1/conversations/identity",
217
+ { credentials: "same-origin", cache: "no-store" }
218
+ );
219
+ if (!response.ok)
220
+ throw new Error(
221
+ response.status === 401 ? "Bitte erneuern Sie Ihre Portal-Anmeldung." : "Portal-Anmeldung derzeit nicht verf\xFCgbar."
222
+ );
223
+ const identity = await response.json();
224
+ if (typeof identity.name !== "string" || !identity.name.trim() || identity.name.length > 200)
225
+ throw new Error("Portal-Anmeldung derzeit nicht verf\xFCgbar.");
226
+ current.name = identity.name;
227
+ loaded = true;
228
+ }
229
+ return "";
230
+ },
231
+ async login() {
232
+ window.location.reload();
233
+ }
234
+ };
235
+ return current;
236
+ }
237
+
238
+ // src/support-chat.tsx
239
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
71
240
  function SupportChat(ctx) {
72
241
  const [current, setCurrent] = useState(null), [input, setInput] = useState(""), [error, setError] = useState(""), [busy, setBusy] = useState(false), [ready, setReady] = useState(false), [auth, setAuth] = useState(null);
73
242
  const seq = useRef(0), requestRef = useRef(null);
@@ -77,7 +246,7 @@ function SupportChat(ctx) {
77
246
  const controller = new AbortController();
78
247
  async function init() {
79
248
  try {
80
- const identity = await session(ctx);
249
+ const identity = await supportSession(ctx);
81
250
  if (!alive) return;
82
251
  setAuth(identity);
83
252
  const request = async (path, init2 = {}) => {
@@ -86,6 +255,7 @@ function SupportChat(ctx) {
86
255
  ctx.config.apiUrl + "/api/v1/conversations" + path,
87
256
  {
88
257
  ...init2,
258
+ credentials: "same-origin",
89
259
  headers: {
90
260
  ...token ? { Authorization: `Bearer ${token}` } : {},
91
261
  ...init2.body !== void 0 ? { "Content-Type": "application/json" } : {},
@@ -227,7 +397,7 @@ function SupportChat(ctx) {
227
397
  }
228
398
  }
229
399
  return /* @__PURE__ */ jsxs("div", { className: "chat-panel support-chat text-ink bg-card border border-line p-6 rounded-card compact:p-4", children: [
230
- /* @__PURE__ */ jsx("p", { className: "text-[0.75rem] text-ink mb-3", children: demo ? "Synthetische Support-Demo. Keine \xDCbertragung an Teams." : "Ihre Nachrichten und die Antworten werden an den Thinkport-Support in Microsoft Teams weitergegeben." }),
400
+ /* @__PURE__ */ jsx("p", { className: "text-[0.75rem] text-ink mb-3", children: demo ? "Synthetische Support-Demo. Keine \xDCbertragung an einen Support-Channel." : `Ihre Nachrichten und die Antworten werden an den Support in ${ctx.config.chatSupport?.mode === "slack" ? "Slack" : "Microsoft Teams"} weitergegeben.` }),
231
401
  /* @__PURE__ */ jsxs("p", { role: "status", className: "text-[0.8125rem] mb-3", children: [
232
402
  current?.status === "support" ? `${current.support?.name} \xB7 Support` : current?.status === "closed" ? "Gespr\xE4ch abgeschlossen" : "Digitaler Assistent",
233
403
  auth && ready && !demo ? ` \xB7 Angemeldet als ${auth.name}` : ""
@@ -273,7 +443,7 @@ function SupportChat(ctx) {
273
443
  onClick: () => void auth?.login().catch(
274
444
  () => setError("Microsoft-Anmeldung konnte nicht gestartet werden.")
275
445
  ),
276
- children: "Mit Microsoft anmelden"
446
+ children: ctx.config.chatSupport?.auth === "session" ? "Portal-Anmeldung erneuern" : "Mit Microsoft anmelden"
277
447
  }
278
448
  ),
279
449
  ready && /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "chat-form flex gap-2.5 items-end", children: [
@@ -362,7 +532,7 @@ function SupportChat(ctx) {
362
532
  onClick: () => void auth.login().catch(
363
533
  () => setError("Microsoft-Anmeldung konnte nicht gestartet werden.")
364
534
  ),
365
- children: "Microsoft-Anmeldung erneuern"
535
+ children: ctx.config.chatSupport?.auth === "session" ? "Portal-Anmeldung erneuern" : "Microsoft-Anmeldung erneuern"
366
536
  }
367
537
  ),
368
538
  error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-danger text-[0.75rem] mt-3", children: error })
@@ -614,6 +784,7 @@ function LegacyChat(ctx) {
614
784
  var browser_default = {
615
785
  id: "chat",
616
786
  sdkVersion: cspPlugin.sdkVersion,
787
+ tools: chatTools,
617
788
  sections: [
618
789
  {
619
790
  id: "chat",
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var cspPlugin = {
3
3
  id: "chat",
4
- sdkVersion: "^0.7.0",
4
+ sdkVersion: "^0.9.0",
5
5
  browser: "./browser",
6
6
  server: "./server"
7
7
  };
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cspPlugin
3
- } from "./chunk-ALGUQ3T5.js";
3
+ } from "./chunk-R6G6BYF4.js";
4
4
 
5
5
  // src/server.ts
6
6
  import { z as z2 } from "zod";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kieksme/csp-plugin-chat",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@azure/msal-browser": "^5.25.0",
26
- "@kieksme/csp-sdk": "^0.7.0",
26
+ "@kieksme/csp-sdk": "^0.9.0",
27
27
  "openai": "^6.3.0",
28
28
  "zod": "^3.25.76"
29
29
  },
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "cspPlugin": {
37
37
  "id": "chat",
38
- "sdkVersion": "^0.7.0",
38
+ "sdkVersion": "^0.9.0",
39
39
  "browser": "./browser",
40
40
  "server": "./server"
41
41
  },