@nexalink/carecompass-sdk 0.3.0-beta.15 → 0.5.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 +479 -128
- package/dist/cli.js +185 -32
- package/dist/client.d.ts +32 -2
- package/dist/client.js +42 -3
- package/dist/compliance.d.ts +69 -0
- package/dist/compliance.js +121 -0
- package/dist/component.d.ts +32 -2
- package/dist/component.js +117 -56
- package/dist/core.d.ts +95 -0
- package/dist/core.js +352 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +6 -2
- package/dist/markdown.d.ts +4 -0
- package/dist/markdown.js +183 -0
- package/dist/mount.d.ts +4 -1
- package/dist/mount.js +2 -2
- package/dist/rcm.d.ts +93 -0
- package/dist/rcm.js +284 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +1 -0
- package/dist/schema.d.ts +49 -0
- package/dist/schema.js +36 -0
- package/dist/telemetry.js +114 -82
- package/dist/ui.d.ts +3 -0
- package/dist/ui.js +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/dist/web-component.d.ts +13 -0
- package/dist/web-component.js +236 -0
- package/package.json +43 -12
- package/qa-fixtures/DEMO-ENC-1042-rcm-policy.md +27 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const CARECOMPASS_COMPLIANCE_PRESETS = {
|
|
2
|
+
// Standard is intentionally non-mutating for existing integrations. Detection
|
|
3
|
+
// can still drive a safety callback when the host opts into one.
|
|
4
|
+
standard: {
|
|
5
|
+
auditEnabled: false,
|
|
6
|
+
phiAction: "allow",
|
|
7
|
+
replacement: "[REDACTED]",
|
|
8
|
+
},
|
|
9
|
+
// Healthcare protects browser-visible requests and retains metadata-only audit
|
|
10
|
+
// records. It is the recommended starting point for PHI-oriented deployments.
|
|
11
|
+
healthcare: {
|
|
12
|
+
auditEnabled: true,
|
|
13
|
+
phiAction: "redact",
|
|
14
|
+
replacement: "[REDACTED]",
|
|
15
|
+
},
|
|
16
|
+
// Strict is appropriate when the host must stop detectable PHI before it leaves
|
|
17
|
+
// the browser. It may reject legitimate identifier-bearing workflow requests.
|
|
18
|
+
strict: {
|
|
19
|
+
auditEnabled: true,
|
|
20
|
+
phiAction: "block",
|
|
21
|
+
replacement: "[REDACTED]",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const PHI_PATTERNS = [
|
|
25
|
+
{ category: "email", pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi },
|
|
26
|
+
{ category: "ssn", pattern: /\b\d{3}-\d{2}-\d{4}\b/g },
|
|
27
|
+
{
|
|
28
|
+
category: "phone",
|
|
29
|
+
pattern: /(?:\+1[ .-]?)?(?:\(?\d{3}\)?[ .-]?)\d{3}[ .-]\d{4}\b/g,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
category: "date_of_birth",
|
|
33
|
+
pattern: /\b(?:dob|date of birth|birth date)\s*[:#-]?\s*(?:\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{4}[/-]\d{1,2}[/-]\d{1,2})\b/gi,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
category: "medical_record_number",
|
|
37
|
+
pattern: /\b(?:mrn|medical record(?: number)?)\s*[:#-]?\s*[A-Z0-9-]{3,}\b/gi,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
category: "member_id",
|
|
41
|
+
pattern: /\b(?:member|patient|policy|subscriber)(?:\s+(?:id|number))?\s*[:#-]?\s*[A-Z0-9-]{3,}\b/gi,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const PHI_CATEGORIES = new Set([
|
|
45
|
+
"date_of_birth",
|
|
46
|
+
"email",
|
|
47
|
+
"medical_record_number",
|
|
48
|
+
"member_id",
|
|
49
|
+
"phone",
|
|
50
|
+
"ssn",
|
|
51
|
+
]);
|
|
52
|
+
const validMatches = (value, matches) => matches
|
|
53
|
+
.filter((match) => PHI_CATEGORIES.has(match.category) &&
|
|
54
|
+
Number.isInteger(match.start) &&
|
|
55
|
+
Number.isInteger(match.end) &&
|
|
56
|
+
match.start >= 0 &&
|
|
57
|
+
match.end > match.start &&
|
|
58
|
+
match.end <= value.length)
|
|
59
|
+
.sort((a, b) => a.start - b.start || b.end - a.end)
|
|
60
|
+
.filter((match, index, sorted) => index === 0 || match.start >= sorted[index - 1].end);
|
|
61
|
+
export const detectCareCompassPhi = (value) => {
|
|
62
|
+
const matches = [];
|
|
63
|
+
for (const { category, pattern } of PHI_PATTERNS) {
|
|
64
|
+
pattern.lastIndex = 0;
|
|
65
|
+
for (const match of value.matchAll(pattern)) {
|
|
66
|
+
const start = match.index;
|
|
67
|
+
if (start === undefined)
|
|
68
|
+
continue;
|
|
69
|
+
matches.push({ category, start, end: start + match[0].length });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return validMatches(value, matches);
|
|
73
|
+
};
|
|
74
|
+
export const redactCareCompassPhi = (value, matches, replacement) => {
|
|
75
|
+
let redacted = value;
|
|
76
|
+
for (const match of [...matches].sort((a, b) => b.start - a.start)) {
|
|
77
|
+
redacted = `${redacted.slice(0, match.start)}${replacement}${redacted.slice(match.end)}`;
|
|
78
|
+
}
|
|
79
|
+
return redacted;
|
|
80
|
+
};
|
|
81
|
+
export const resolveCareCompassCompliancePolicy = (config) => {
|
|
82
|
+
const preset = config?.preset ?? "standard";
|
|
83
|
+
const defaults = CARECOMPASS_COMPLIANCE_PRESETS[preset];
|
|
84
|
+
return {
|
|
85
|
+
preset,
|
|
86
|
+
auditEnabled: config?.audit?.enabled ?? defaults.auditEnabled,
|
|
87
|
+
phiAction: config?.phi?.action ?? defaults.phiAction,
|
|
88
|
+
replacement: config?.phi?.replacement ?? defaults.replacement,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
export const phiSummary = (matches) => ({
|
|
92
|
+
categories: [...new Set(matches.map((match) => match.category))].sort(),
|
|
93
|
+
count: matches.length,
|
|
94
|
+
});
|
|
95
|
+
export const sanitizeCareCompassValue = (value, source, policy, hooks) => {
|
|
96
|
+
if (typeof value === "string") {
|
|
97
|
+
const matches = validMatches(value, (hooks?.detect ?? detectCareCompassPhi)(value, { source }));
|
|
98
|
+
if (policy.phiAction !== "redact" || matches.length === 0) {
|
|
99
|
+
return { matches, value };
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
matches,
|
|
103
|
+
value: (hooks?.redact ?? redactCareCompassPhi)(value, matches, policy.replacement),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
const entries = value.map((entry) => sanitizeCareCompassValue(entry, source, policy, hooks));
|
|
108
|
+
return {
|
|
109
|
+
matches: entries.flatMap((entry) => entry.matches),
|
|
110
|
+
value: entries.map((entry) => entry.value),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (value && typeof value === "object") {
|
|
114
|
+
const entries = Object.entries(value).map(([key, entry]) => [key, sanitizeCareCompassValue(entry, source, policy, hooks)]);
|
|
115
|
+
return {
|
|
116
|
+
matches: entries.flatMap(([, entry]) => entry.matches),
|
|
117
|
+
value: Object.fromEntries(entries.map(([key, entry]) => [key, entry.value])),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { matches: [], value };
|
|
121
|
+
};
|
package/dist/component.d.ts
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type CareCompassContext, type CareCompassCore, type CareCompassCoreEvent, type CareCompassCoreOptions, type CareCompassCoreSnapshot, type CareCompassMessage, type CareCompassSendMessageOptions } from "./core.js";
|
|
3
|
+
import type { CareCompassRuntimeTokenProvider } from "./client.js";
|
|
1
4
|
import type { CareCompassConfig } from "./schema.js";
|
|
2
|
-
|
|
5
|
+
import type { CareCompassAuditListener, CareCompassSafetyListener } from "./compliance.js";
|
|
6
|
+
export type CareCompassChatProps = {
|
|
3
7
|
config: CareCompassConfig;
|
|
8
|
+
initialContext?: CareCompassContext;
|
|
9
|
+
onAuditEvent?: CareCompassAuditListener;
|
|
10
|
+
onEvent?: (event: CareCompassCoreEvent) => void;
|
|
11
|
+
onSafetyEvent?: CareCompassSafetyListener;
|
|
4
12
|
pollIntervalMs?: number;
|
|
5
|
-
|
|
13
|
+
renderMessage?: (message: CareCompassMessage) => React.ReactNode;
|
|
14
|
+
retryLabel?: string;
|
|
15
|
+
runtimeTokenProvider?: CareCompassRuntimeTokenProvider;
|
|
16
|
+
};
|
|
17
|
+
/** Options for the headless React hook. Keep object-valued options stable with
|
|
18
|
+
* `useMemo` when the calling component rerenders. */
|
|
19
|
+
export type UseCareCompassOptions = CareCompassCoreOptions;
|
|
20
|
+
export type CareCompassReactController = {
|
|
21
|
+
core: CareCompassCore;
|
|
22
|
+
snapshot: CareCompassCoreSnapshot;
|
|
23
|
+
sendMessage: (prompt: string, options?: CareCompassSendMessageOptions) => Promise<CareCompassCoreSnapshot>;
|
|
24
|
+
streamMessage: CareCompassCore["streamMessage"];
|
|
25
|
+
retryLast: CareCompassCore["retryLast"];
|
|
26
|
+
reset: CareCompassCore["reset"];
|
|
27
|
+
setContext: CareCompassCore["setContext"];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* React adapter for the headless CareCompass core. It owns the core lifecycle
|
|
31
|
+
* and subscribes React to its snapshots, leaving hosts free to render any UI.
|
|
32
|
+
*/
|
|
33
|
+
export declare function useCareCompass(config: CareCompassConfig, options?: UseCareCompassOptions): CareCompassReactController;
|
|
34
|
+
export declare function CareCompassChat({ config, initialContext, onAuditEvent, onEvent, onSafetyEvent, pollIntervalMs, renderMessage, retryLabel, runtimeTokenProvider, }: CareCompassChatProps): React.JSX.Element;
|
|
35
|
+
export declare const CareCompassEmbed: typeof CareCompassChat;
|
package/dist/component.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { createCareCompassCore, } from "./core.js";
|
|
4
|
+
import { CareCompassMarkdown } from "./markdown.js";
|
|
4
5
|
import { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, createCareCompassTelemetry, } from "./telemetry.js";
|
|
5
6
|
// The embed should fail closed inside the host page: show the configured
|
|
6
7
|
// fallback text, capture sanitized telemetry, and avoid crashing the integrator.
|
|
@@ -25,71 +26,115 @@ class CareCompassErrorBoundary extends React.Component {
|
|
|
25
26
|
return this.props.children;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
/**
|
|
30
|
+
* React adapter for the headless CareCompass core. It owns the core lifecycle
|
|
31
|
+
* and subscribes React to its snapshots, leaving hosts free to render any UI.
|
|
32
|
+
*/
|
|
33
|
+
export function useCareCompass(config, options = {}) {
|
|
34
|
+
const { compliance, fetcher, initialContext, now, onAuditEvent, onSafetyEvent, pollIntervalMs, runtimeTokenProvider, timeoutMs, } = options;
|
|
35
|
+
const core = React.useMemo(() => createCareCompassCore(config, {
|
|
36
|
+
compliance,
|
|
37
|
+
fetcher,
|
|
38
|
+
initialContext,
|
|
39
|
+
now,
|
|
40
|
+
onAuditEvent,
|
|
41
|
+
onSafetyEvent,
|
|
42
|
+
pollIntervalMs,
|
|
43
|
+
runtimeTokenProvider,
|
|
44
|
+
timeoutMs,
|
|
45
|
+
}), [
|
|
46
|
+
compliance,
|
|
47
|
+
config,
|
|
48
|
+
fetcher,
|
|
49
|
+
initialContext,
|
|
50
|
+
now,
|
|
51
|
+
onAuditEvent,
|
|
52
|
+
onSafetyEvent,
|
|
53
|
+
pollIntervalMs,
|
|
54
|
+
runtimeTokenProvider,
|
|
55
|
+
timeoutMs,
|
|
36
56
|
]);
|
|
57
|
+
const [snapshot, setSnapshot] = React.useState(() => core.getSnapshot());
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
const unsubscribe = core.subscribe(setSnapshot);
|
|
60
|
+
setSnapshot(core.getSnapshot());
|
|
61
|
+
return () => {
|
|
62
|
+
unsubscribe();
|
|
63
|
+
core.dispose();
|
|
64
|
+
};
|
|
65
|
+
}, [core]);
|
|
66
|
+
return React.useMemo(() => ({
|
|
67
|
+
core,
|
|
68
|
+
snapshot,
|
|
69
|
+
sendMessage: core.sendMessage,
|
|
70
|
+
streamMessage: core.streamMessage,
|
|
71
|
+
retryLast: core.retryLast,
|
|
72
|
+
reset: core.reset,
|
|
73
|
+
setContext: core.setContext,
|
|
74
|
+
}), [core, snapshot]);
|
|
75
|
+
}
|
|
76
|
+
function CareCompassChatContent({ config, initialContext, onAuditEvent, onEvent, onSafetyEvent, telemetry, pollIntervalMs = 1500, renderMessage, retryLabel = "Retry", runtimeTokenProvider, }) {
|
|
77
|
+
const core = React.useMemo(() => createCareCompassCore(config, {
|
|
78
|
+
initialContext,
|
|
79
|
+
onAuditEvent,
|
|
80
|
+
onSafetyEvent,
|
|
81
|
+
pollIntervalMs,
|
|
82
|
+
runtimeTokenProvider,
|
|
83
|
+
}), [
|
|
84
|
+
config,
|
|
85
|
+
initialContext,
|
|
86
|
+
onAuditEvent,
|
|
87
|
+
onSafetyEvent,
|
|
88
|
+
pollIntervalMs,
|
|
89
|
+
runtimeTokenProvider,
|
|
90
|
+
]);
|
|
91
|
+
const [snapshot, setSnapshot] = React.useState(() => core.getSnapshot());
|
|
37
92
|
const [input, setInput] = React.useState("");
|
|
38
|
-
const [loading, setLoading] = React.useState(false);
|
|
39
|
-
const [error, setError] = React.useState(null);
|
|
40
93
|
React.useEffect(() => {
|
|
41
94
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_RENDERED);
|
|
42
95
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_OPENED);
|
|
96
|
+
const unsubscribe = core.subscribe(setSnapshot);
|
|
97
|
+
setSnapshot(core.getSnapshot());
|
|
43
98
|
return () => {
|
|
99
|
+
unsubscribe();
|
|
100
|
+
core.dispose();
|
|
44
101
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_CLOSED);
|
|
45
102
|
};
|
|
46
|
-
}, [telemetry]);
|
|
103
|
+
}, [core, telemetry]);
|
|
47
104
|
async function sendMessage(event) {
|
|
48
105
|
event.preventDefault();
|
|
49
106
|
const prompt = input.trim();
|
|
50
|
-
if (!prompt || loading)
|
|
107
|
+
if (!prompt || snapshot.loading)
|
|
51
108
|
return;
|
|
52
109
|
setInput("");
|
|
53
|
-
setError(null);
|
|
54
|
-
setLoading(true);
|
|
55
|
-
setMessages((current) => [
|
|
56
|
-
...current,
|
|
57
|
-
{ id: `user-${Date.now()}`, role: "user", text: prompt },
|
|
58
|
-
]);
|
|
59
110
|
try {
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
// sees a terminal state or reaches the same timeout tracked by telemetry.
|
|
64
|
-
while (Date.now() - startedAt < 120_000) {
|
|
65
|
-
const status = await client.status(jobId);
|
|
66
|
-
if (status.status === "complete" && status.result) {
|
|
67
|
-
setMessages((current) => [
|
|
68
|
-
...current,
|
|
69
|
-
{
|
|
70
|
-
id: `assistant-${Date.now()}`,
|
|
71
|
-
role: "assistant",
|
|
72
|
-
text: status.result ?? "",
|
|
73
|
-
},
|
|
74
|
-
]);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (status.status === "failed") {
|
|
78
|
-
throw new Error(status.result ?? config.labels.errorMessage);
|
|
79
|
-
}
|
|
80
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
111
|
+
core.recordWidgetAction();
|
|
112
|
+
for await (const coreEvent of core.streamMessage(prompt)) {
|
|
113
|
+
onEvent?.(coreEvent);
|
|
81
114
|
}
|
|
82
|
-
client.recordInferenceTimeout(jobId, Date.now() - startedAt);
|
|
83
|
-
throw new Error("CareCompass request timed out.");
|
|
84
115
|
}
|
|
85
|
-
catch
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
catch {
|
|
117
|
+
// The core snapshot already carries the user-visible error and retry
|
|
118
|
+
// state. Swallow here so host pages do not see unhandled form errors.
|
|
88
119
|
}
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
}
|
|
121
|
+
async function retryMessage() {
|
|
122
|
+
try {
|
|
123
|
+
core.recordWidgetAction();
|
|
124
|
+
await core.retryLast();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// The core owns the visible retry failure state.
|
|
91
128
|
}
|
|
92
129
|
}
|
|
130
|
+
const messageContent = (message) => {
|
|
131
|
+
if (renderMessage)
|
|
132
|
+
return renderMessage(message);
|
|
133
|
+
if (message.role === "assistant") {
|
|
134
|
+
return _jsx(CareCompassMarkdown, { text: message.text });
|
|
135
|
+
}
|
|
136
|
+
return message.text;
|
|
137
|
+
};
|
|
93
138
|
return (_jsxs("section", { "aria-label": config.labels.title, style: {
|
|
94
139
|
"--carecompass-primary": config.theme.primaryColor,
|
|
95
140
|
"--carecompass-bg": config.theme.backgroundColor,
|
|
@@ -118,7 +163,7 @@ function CareCompassEmbedContent({ config, telemetry, pollIntervalMs = 1500, })
|
|
|
118
163
|
gap: 12,
|
|
119
164
|
overflowY: "auto",
|
|
120
165
|
padding: 16,
|
|
121
|
-
}, children: [messages.map((message) => (_jsx("div", { style: {
|
|
166
|
+
}, children: [snapshot.messages.map((message) => (_jsx("div", { style: {
|
|
122
167
|
alignSelf: message.role === "user" ? "flex-end" : "flex-start",
|
|
123
168
|
background: message.role === "user"
|
|
124
169
|
? "var(--carecompass-primary)"
|
|
@@ -127,14 +172,14 @@ function CareCompassEmbedContent({ config, telemetry, pollIntervalMs = 1500, })
|
|
|
127
172
|
color: message.role === "user" ? "#ffffff" : "inherit",
|
|
128
173
|
maxInlineSize: "82%",
|
|
129
174
|
padding: "10px 12px",
|
|
130
|
-
whiteSpace: "pre-wrap",
|
|
131
|
-
}, children: message
|
|
175
|
+
whiteSpace: message.role === "user" ? "pre-wrap" : "normal",
|
|
176
|
+
}, children: messageContent(message) }, message.id))), snapshot.loading ? (_jsx("div", { style: { fontSize: 13, opacity: 0.72 }, children: "Thinking..." })) : null] }), _jsxs("form", { onSubmit: sendMessage, style: {
|
|
132
177
|
borderTop: "1px solid color-mix(in srgb, var(--carecompass-text) 12%, transparent)",
|
|
133
178
|
display: "flex",
|
|
134
179
|
flexDirection: "column",
|
|
135
180
|
gap: 8,
|
|
136
181
|
padding: 12,
|
|
137
|
-
}, children: [_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("input", { "aria-label": config.labels.placeholder, disabled: loading, onChange: (event) => setInput(event.currentTarget.value), placeholder: config.labels.placeholder, style: {
|
|
182
|
+
}, children: [_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("input", { "aria-label": config.labels.placeholder, disabled: snapshot.loading, onChange: (event) => setInput(event.currentTarget.value), placeholder: config.labels.placeholder, style: {
|
|
138
183
|
border: "1px solid color-mix(in srgb, var(--carecompass-text) 16%, transparent)",
|
|
139
184
|
borderRadius: 8,
|
|
140
185
|
color: "inherit",
|
|
@@ -142,20 +187,36 @@ function CareCompassEmbedContent({ config, telemetry, pollIntervalMs = 1500, })
|
|
|
142
187
|
font: "inherit",
|
|
143
188
|
minInlineSize: 0,
|
|
144
189
|
padding: "10px 12px",
|
|
145
|
-
}, value: input }), _jsx("button", { disabled: loading || !input.trim(), style: {
|
|
190
|
+
}, value: input }), _jsx("button", { disabled: snapshot.loading || !input.trim(), style: {
|
|
146
191
|
background: "var(--carecompass-primary)",
|
|
147
192
|
border: 0,
|
|
148
193
|
borderRadius: 8,
|
|
149
194
|
color: "#ffffff",
|
|
150
|
-
cursor: loading ? "not-allowed" : "pointer",
|
|
195
|
+
cursor: snapshot.loading ? "not-allowed" : "pointer",
|
|
151
196
|
font: "inherit",
|
|
152
197
|
fontWeight: 700,
|
|
153
198
|
padding: "0 14px",
|
|
154
|
-
}, type: "submit", children: config.labels.sendButton })] }), error ? (
|
|
199
|
+
}, type: "submit", children: config.labels.sendButton })] }), snapshot.error ? (_jsxs("div", { role: "alert", style: {
|
|
200
|
+
alignItems: "center",
|
|
201
|
+
color: "#b91c1c",
|
|
202
|
+
display: "flex",
|
|
203
|
+
flexWrap: "wrap",
|
|
204
|
+
fontSize: 12,
|
|
205
|
+
gap: 8,
|
|
206
|
+
}, children: [_jsx("span", { children: snapshot.error.message }), snapshot.canRetry ? (_jsx("button", { onClick: retryMessage, style: {
|
|
207
|
+
background: "transparent",
|
|
208
|
+
border: "1px solid color-mix(in srgb, #b91c1c 40%, transparent)",
|
|
209
|
+
borderRadius: 6,
|
|
210
|
+
color: "#b91c1c",
|
|
211
|
+
cursor: "pointer",
|
|
212
|
+
font: "inherit",
|
|
213
|
+
padding: "4px 8px",
|
|
214
|
+
}, type: "button", children: retryLabel })) : null] })) : null, _jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: config.labels.disclaimer })] })] }));
|
|
155
215
|
}
|
|
156
|
-
export function
|
|
216
|
+
export function CareCompassChat({ config, initialContext, onAuditEvent, onEvent, onSafetyEvent, pollIntervalMs = 1500, renderMessage, retryLabel, runtimeTokenProvider, }) {
|
|
157
217
|
// Telemetry is scoped to the validated public config; no per-message content
|
|
158
218
|
// is passed through this component-level telemetry object.
|
|
159
219
|
const telemetry = React.useMemo(() => createCareCompassTelemetry(config), [config]);
|
|
160
|
-
return (_jsx(CareCompassErrorBoundary, { config: config, telemetry: telemetry, children: _jsx(
|
|
220
|
+
return (_jsx(CareCompassErrorBoundary, { config: config, telemetry: telemetry, children: _jsx(CareCompassChatContent, { config: config, initialContext: initialContext, onAuditEvent: onAuditEvent, onEvent: onEvent, onSafetyEvent: onSafetyEvent, pollIntervalMs: pollIntervalMs, renderMessage: renderMessage, retryLabel: retryLabel, runtimeTokenProvider: runtimeTokenProvider, telemetry: telemetry }) }));
|
|
161
221
|
}
|
|
222
|
+
export const CareCompassEmbed = CareCompassChat;
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type CareCompassRuntimeTokenProvider, type CareCompassStatusResponse, type CareCompassWorkflowId } from "./client.js";
|
|
2
|
+
import { type CareCompassAuditEvent, type CareCompassAuditListener, type CareCompassComplianceRuntimeOptions, type CareCompassSafetyDecision, type CareCompassSafetyListener } from "./compliance.js";
|
|
3
|
+
import type { CareCompassConfig } from "./schema.js";
|
|
4
|
+
type Fetcher = typeof fetch;
|
|
5
|
+
export type CareCompassContext = Record<string, unknown>;
|
|
6
|
+
export type CareCompassMessage = {
|
|
7
|
+
id: string;
|
|
8
|
+
role: "user" | "assistant";
|
|
9
|
+
text: string;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
status: "complete" | "failed" | "pending";
|
|
12
|
+
retrieval?: CareCompassStatusResponse["retrieval"];
|
|
13
|
+
};
|
|
14
|
+
export type CareCompassLifecyclePhase = "idle" | "submitting" | "streaming" | "complete" | "error" | "disposed";
|
|
15
|
+
export type CareCompassCoreErrorCode = "request_failed" | "safety_blocked" | "timeout" | "disposed";
|
|
16
|
+
export type CareCompassCoreError = {
|
|
17
|
+
code: CareCompassCoreErrorCode;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
export type CareCompassRetryMetadata = {
|
|
21
|
+
prompt: string;
|
|
22
|
+
requestId?: string;
|
|
23
|
+
workflow?: CareCompassWorkflowId;
|
|
24
|
+
context: CareCompassContext;
|
|
25
|
+
attempts: number;
|
|
26
|
+
};
|
|
27
|
+
export type CareCompassCoreSnapshot = {
|
|
28
|
+
messages: CareCompassMessage[];
|
|
29
|
+
phase: CareCompassLifecyclePhase;
|
|
30
|
+
loading: boolean;
|
|
31
|
+
error: CareCompassCoreError | null;
|
|
32
|
+
context: CareCompassContext;
|
|
33
|
+
canRetry: boolean;
|
|
34
|
+
retry: CareCompassRetryMetadata | null;
|
|
35
|
+
activeJobId: string | null;
|
|
36
|
+
};
|
|
37
|
+
export type CareCompassCoreEvent = {
|
|
38
|
+
type: "message_submitted";
|
|
39
|
+
message: CareCompassMessage;
|
|
40
|
+
snapshot: CareCompassCoreSnapshot;
|
|
41
|
+
} | {
|
|
42
|
+
type: "status";
|
|
43
|
+
jobId: string;
|
|
44
|
+
status: CareCompassStatusResponse["status"];
|
|
45
|
+
snapshot: CareCompassCoreSnapshot;
|
|
46
|
+
} | {
|
|
47
|
+
type: "message_completed";
|
|
48
|
+
message: CareCompassMessage;
|
|
49
|
+
snapshot: CareCompassCoreSnapshot;
|
|
50
|
+
} | {
|
|
51
|
+
type: "error";
|
|
52
|
+
error: CareCompassCoreError;
|
|
53
|
+
snapshot: CareCompassCoreSnapshot;
|
|
54
|
+
} | {
|
|
55
|
+
type: "context_updated";
|
|
56
|
+
snapshot: CareCompassCoreSnapshot;
|
|
57
|
+
} | {
|
|
58
|
+
type: "reset";
|
|
59
|
+
snapshot: CareCompassCoreSnapshot;
|
|
60
|
+
};
|
|
61
|
+
export type CareCompassCoreOptions = {
|
|
62
|
+
compliance?: CareCompassComplianceRuntimeOptions;
|
|
63
|
+
fetcher?: Fetcher;
|
|
64
|
+
initialContext?: CareCompassContext;
|
|
65
|
+
now?: () => number;
|
|
66
|
+
onAuditEvent?: CareCompassAuditListener;
|
|
67
|
+
onSafetyEvent?: CareCompassSafetyListener;
|
|
68
|
+
pollIntervalMs?: number;
|
|
69
|
+
runtimeTokenProvider?: CareCompassRuntimeTokenProvider;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
};
|
|
72
|
+
export type CareCompassSendMessageOptions = {
|
|
73
|
+
/** Reuse this value only when retrying the same hosted submission. */
|
|
74
|
+
requestId?: string;
|
|
75
|
+
workflow?: CareCompassWorkflowId;
|
|
76
|
+
context?: CareCompassContext;
|
|
77
|
+
};
|
|
78
|
+
export type CareCompassCore = {
|
|
79
|
+
exportAuditTrail: () => CareCompassAuditEvent[];
|
|
80
|
+
getSnapshot: () => CareCompassCoreSnapshot;
|
|
81
|
+
recordSafetyDecision: (decision: Exclude<CareCompassSafetyDecision, "redacted" | "blocked">, source?: "tool_call" | "widget_action") => void;
|
|
82
|
+
recordToolCall: (outcome?: "completed" | "failed") => void;
|
|
83
|
+
recordWidgetAction: (outcome?: "recorded" | "completed") => void;
|
|
84
|
+
subscribe: (listener: (snapshot: CareCompassCoreSnapshot) => void) => () => void;
|
|
85
|
+
subscribeAudit: (listener: CareCompassAuditListener) => () => void;
|
|
86
|
+
subscribeSafety: (listener: CareCompassSafetyListener) => () => void;
|
|
87
|
+
setContext: (context: CareCompassContext | ((current: CareCompassContext) => CareCompassContext)) => void;
|
|
88
|
+
sendMessage: (prompt: string, options?: CareCompassSendMessageOptions) => Promise<CareCompassCoreSnapshot>;
|
|
89
|
+
streamMessage: (prompt: string, options?: CareCompassSendMessageOptions) => AsyncIterable<CareCompassCoreEvent>;
|
|
90
|
+
retryLast: () => Promise<CareCompassCoreSnapshot>;
|
|
91
|
+
reset: () => void;
|
|
92
|
+
dispose: () => void;
|
|
93
|
+
};
|
|
94
|
+
export declare function createCareCompassCore(config: CareCompassConfig, options?: CareCompassCoreOptions): CareCompassCore;
|
|
95
|
+
export {};
|