@nexalink/carecompass-sdk 0.4.0-beta.15 → 0.6.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.
@@ -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
+ };
@@ -1,13 +1,35 @@
1
1
  import React from "react";
2
- import { type CareCompassContext, type CareCompassCoreEvent, type CareCompassMessage } from "./core.js";
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";
3
4
  import type { CareCompassConfig } from "./schema.js";
5
+ import type { CareCompassAuditListener, CareCompassSafetyListener } from "./compliance.js";
4
6
  export type CareCompassChatProps = {
5
7
  config: CareCompassConfig;
6
8
  initialContext?: CareCompassContext;
9
+ onAuditEvent?: CareCompassAuditListener;
7
10
  onEvent?: (event: CareCompassCoreEvent) => void;
11
+ onSafetyEvent?: CareCompassSafetyListener;
8
12
  pollIntervalMs?: number;
9
13
  renderMessage?: (message: CareCompassMessage) => React.ReactNode;
10
14
  retryLabel?: string;
15
+ runtimeTokenProvider?: CareCompassRuntimeTokenProvider;
11
16
  };
12
- export declare function CareCompassChat({ config, initialContext, onEvent, pollIntervalMs, renderMessage, retryLabel, }: CareCompassChatProps): import("react/jsx-runtime").JSX.Element;
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;
13
35
  export declare const CareCompassEmbed: typeof CareCompassChat;
package/dist/component.js CHANGED
@@ -26,11 +26,68 @@ class CareCompassErrorBoundary extends React.Component {
26
26
  return this.props.children;
27
27
  }
28
28
  }
29
- function CareCompassChatContent({ config, initialContext, onEvent, telemetry, pollIntervalMs = 1500, renderMessage, retryLabel = "Retry", }) {
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;
30
35
  const core = React.useMemo(() => createCareCompassCore(config, {
36
+ compliance,
37
+ fetcher,
31
38
  initialContext,
39
+ now,
40
+ onAuditEvent,
41
+ onSafetyEvent,
32
42
  pollIntervalMs,
33
- }), [config, initialContext, 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,
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
+ ]);
34
91
  const [snapshot, setSnapshot] = React.useState(() => core.getSnapshot());
35
92
  const [input, setInput] = React.useState("");
36
93
  React.useEffect(() => {
@@ -51,6 +108,7 @@ function CareCompassChatContent({ config, initialContext, onEvent, telemetry, po
51
108
  return;
52
109
  setInput("");
53
110
  try {
111
+ core.recordWidgetAction();
54
112
  for await (const coreEvent of core.streamMessage(prompt)) {
55
113
  onEvent?.(coreEvent);
56
114
  }
@@ -62,6 +120,7 @@ function CareCompassChatContent({ config, initialContext, onEvent, telemetry, po
62
120
  }
63
121
  async function retryMessage() {
64
122
  try {
123
+ core.recordWidgetAction();
65
124
  await core.retryLast();
66
125
  }
67
126
  catch {
@@ -154,10 +213,10 @@ function CareCompassChatContent({ config, initialContext, onEvent, telemetry, po
154
213
  padding: "4px 8px",
155
214
  }, type: "button", children: retryLabel })) : null] })) : null, _jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: config.labels.disclaimer })] })] }));
156
215
  }
157
- export function CareCompassChat({ config, initialContext, onEvent, pollIntervalMs = 1500, renderMessage, retryLabel, }) {
216
+ export function CareCompassChat({ config, initialContext, onAuditEvent, onEvent, onSafetyEvent, pollIntervalMs = 1500, renderMessage, retryLabel, runtimeTokenProvider, }) {
158
217
  // Telemetry is scoped to the validated public config; no per-message content
159
218
  // is passed through this component-level telemetry object.
160
219
  const telemetry = React.useMemo(() => createCareCompassTelemetry(config), [config]);
161
- return (_jsx(CareCompassErrorBoundary, { config: config, telemetry: telemetry, children: _jsx(CareCompassChatContent, { config: config, initialContext: initialContext, onEvent: onEvent, pollIntervalMs: pollIntervalMs, renderMessage: renderMessage, retryLabel: retryLabel, telemetry: telemetry }) }));
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 }) }));
162
221
  }
163
222
  export const CareCompassEmbed = CareCompassChat;
package/dist/core.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type CareCompassStatusResponse } from "./client.js";
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";
2
3
  import type { CareCompassConfig } from "./schema.js";
3
4
  type Fetcher = typeof fetch;
4
5
  export type CareCompassContext = Record<string, unknown>;
@@ -8,15 +9,18 @@ export type CareCompassMessage = {
8
9
  text: string;
9
10
  createdAt: number;
10
11
  status: "complete" | "failed" | "pending";
12
+ retrieval?: CareCompassStatusResponse["retrieval"];
11
13
  };
12
14
  export type CareCompassLifecyclePhase = "idle" | "submitting" | "streaming" | "complete" | "error" | "disposed";
13
- export type CareCompassCoreErrorCode = "request_failed" | "timeout" | "disposed";
15
+ export type CareCompassCoreErrorCode = "request_failed" | "safety_blocked" | "timeout" | "disposed";
14
16
  export type CareCompassCoreError = {
15
17
  code: CareCompassCoreErrorCode;
16
18
  message: string;
17
19
  };
18
20
  export type CareCompassRetryMetadata = {
19
21
  prompt: string;
22
+ requestId?: string;
23
+ workflow?: CareCompassWorkflowId;
20
24
  context: CareCompassContext;
21
25
  attempts: number;
22
26
  };
@@ -55,18 +59,31 @@ export type CareCompassCoreEvent = {
55
59
  snapshot: CareCompassCoreSnapshot;
56
60
  };
57
61
  export type CareCompassCoreOptions = {
62
+ compliance?: CareCompassComplianceRuntimeOptions;
58
63
  fetcher?: Fetcher;
59
64
  initialContext?: CareCompassContext;
60
65
  now?: () => number;
66
+ onAuditEvent?: CareCompassAuditListener;
67
+ onSafetyEvent?: CareCompassSafetyListener;
61
68
  pollIntervalMs?: number;
69
+ runtimeTokenProvider?: CareCompassRuntimeTokenProvider;
62
70
  timeoutMs?: number;
63
71
  };
64
72
  export type CareCompassSendMessageOptions = {
73
+ /** Reuse this value only when retrying the same hosted submission. */
74
+ requestId?: string;
75
+ workflow?: CareCompassWorkflowId;
65
76
  context?: CareCompassContext;
66
77
  };
67
78
  export type CareCompassCore = {
79
+ exportAuditTrail: () => CareCompassAuditEvent[];
68
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;
69
84
  subscribe: (listener: (snapshot: CareCompassCoreSnapshot) => void) => () => void;
85
+ subscribeAudit: (listener: CareCompassAuditListener) => () => void;
86
+ subscribeSafety: (listener: CareCompassSafetyListener) => () => void;
70
87
  setContext: (context: CareCompassContext | ((current: CareCompassContext) => CareCompassContext)) => void;
71
88
  sendMessage: (prompt: string, options?: CareCompassSendMessageOptions) => Promise<CareCompassCoreSnapshot>;
72
89
  streamMessage: (prompt: string, options?: CareCompassSendMessageOptions) => AsyncIterable<CareCompassCoreEvent>;
package/dist/core.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createCareCompassClient, } from "./client.js";
2
+ import { phiSummary, resolveCareCompassCompliancePolicy, sanitizeCareCompassValue, } from "./compliance.js";
2
3
  const sleep = (durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs));
3
4
  const errorFrom = (error, code = "request_failed") => ({
4
5
  code,
@@ -11,7 +12,16 @@ export function createCareCompassCore(config, options = {}) {
11
12
  const client = createCareCompassClient(config, {
12
13
  fetcher: options.fetcher,
13
14
  now,
15
+ runtimeTokenProvider: options.runtimeTokenProvider,
14
16
  });
17
+ const compliancePolicy = resolveCareCompassCompliancePolicy(config.compliance);
18
+ const auditEvents = [];
19
+ const auditListeners = new Set();
20
+ const safetyListeners = new Set();
21
+ if (options.onAuditEvent)
22
+ auditListeners.add(options.onAuditEvent);
23
+ if (options.onSafetyEvent)
24
+ safetyListeners.add(options.onSafetyEvent);
15
25
  const listeners = new Set();
16
26
  let sequence = 0;
17
27
  let disposed = false;
@@ -30,6 +40,44 @@ export function createCareCompassCore(config, options = {}) {
30
40
  },
31
41
  ];
32
42
  const nextId = (prefix) => `${prefix}-${now()}-${++sequence}`;
43
+ const emitAudit = (type, outcome, source, matches = []) => {
44
+ if (!compliancePolicy.auditEnabled)
45
+ return;
46
+ const auditEvent = {
47
+ id: nextId("audit"),
48
+ occurredAt: now(),
49
+ outcome,
50
+ phi: phiSummary(matches),
51
+ ...(source ? { source } : {}),
52
+ type,
53
+ };
54
+ auditEvents.push(auditEvent);
55
+ for (const listener of auditListeners)
56
+ listener(auditEvent);
57
+ };
58
+ const emitSafety = (decision, source, matches = []) => {
59
+ const safetyEvent = {
60
+ decision,
61
+ occurredAt: now(),
62
+ phi: phiSummary(matches),
63
+ source,
64
+ };
65
+ for (const listener of safetyListeners)
66
+ listener(safetyEvent);
67
+ emitAudit("safety_decision", decision === "policy_sensitive" ? "recorded" : decision, source, matches);
68
+ };
69
+ const applyPhiPolicy = (value, source) => {
70
+ const sanitized = sanitizeCareCompassValue(value, source, compliancePolicy, options.compliance?.phi);
71
+ if (sanitized.matches.length === 0)
72
+ return sanitized;
73
+ const decision = compliancePolicy.phiAction === "block"
74
+ ? "blocked"
75
+ : compliancePolicy.phiAction === "redact"
76
+ ? "redacted"
77
+ : "policy_sensitive";
78
+ emitSafety(decision, source, sanitized.matches);
79
+ return sanitized;
80
+ };
33
81
  const getSnapshot = () => ({
34
82
  messages: messages.map((message) => ({ ...message })),
35
83
  phase,
@@ -40,6 +88,8 @@ export function createCareCompassCore(config, options = {}) {
40
88
  retry: retry
41
89
  ? {
42
90
  prompt: retry.prompt,
91
+ requestId: retry.requestId,
92
+ workflow: retry.workflow,
43
93
  context: { ...retry.context },
44
94
  attempts: retry.attempts,
45
95
  }
@@ -56,6 +106,7 @@ export function createCareCompassCore(config, options = {}) {
56
106
  error = coreError;
57
107
  phase = disposed ? "disposed" : "error";
58
108
  activeJobId = null;
109
+ emitAudit("error", "failed");
59
110
  notify();
60
111
  return event({ type: "error", error: coreError });
61
112
  };
@@ -72,14 +123,33 @@ export function createCareCompassCore(config, options = {}) {
72
123
  const prompt = promptInput.trim();
73
124
  if (!prompt)
74
125
  return;
75
- const requestContext = {
126
+ const protectedPrompt = applyPhiPolicy(prompt, "message");
127
+ const protectedContext = applyPhiPolicy({
76
128
  ...context,
77
129
  ...(sendOptions.context ?? {}),
78
- };
130
+ }, "context");
131
+ if (compliancePolicy.phiAction === "block" &&
132
+ (protectedPrompt.matches.length > 0 ||
133
+ protectedContext.matches.length > 0)) {
134
+ emitAudit("message", "blocked", "message", protectedPrompt.matches);
135
+ const blocked = {
136
+ code: "safety_blocked",
137
+ message: "CareCompass blocked this request because detectable PHI is not allowed in strict compliance mode.",
138
+ };
139
+ retry = null;
140
+ yield fail(blocked);
141
+ throw new Error(blocked.message);
142
+ }
143
+ const requestContext = protectedContext.value;
144
+ const protectedPromptValue = protectedPrompt.value;
145
+ const requestId = sendOptions.requestId ??
146
+ (typeof globalThis.crypto?.randomUUID === "function"
147
+ ? globalThis.crypto.randomUUID()
148
+ : nextId("request"));
79
149
  const userMessage = {
80
150
  id: nextId("user"),
81
151
  role: "user",
82
- text: prompt,
152
+ text: protectedPromptValue,
83
153
  createdAt: now(),
84
154
  status: "complete",
85
155
  };
@@ -89,12 +159,18 @@ export function createCareCompassCore(config, options = {}) {
89
159
  activeJobId = null;
90
160
  retry = null;
91
161
  notify();
162
+ emitAudit("message", protectedPrompt.matches.length > 0 &&
163
+ compliancePolicy.phiAction === "redact"
164
+ ? "redacted"
165
+ : "submitted", "message", protectedPrompt.matches);
92
166
  yield event({ type: "message_submitted", message: userMessage });
93
167
  const startedAt = now();
94
168
  let failureAlreadyCaptured = false;
95
169
  try {
96
170
  const { jobId } = await client.ask({
97
- prompt,
171
+ prompt: protectedPromptValue,
172
+ requestId,
173
+ workflow: sendOptions.workflow,
98
174
  userContext: requestContext,
99
175
  });
100
176
  activeJobId = jobId;
@@ -104,12 +180,25 @@ export function createCareCompassCore(config, options = {}) {
104
180
  const status = await client.status(jobId);
105
181
  yield event({ type: "status", jobId, status: status.status });
106
182
  if (status.status === "complete" && status.result) {
183
+ const protectedResponse = applyPhiPolicy(status.result, "response");
184
+ if (compliancePolicy.phiAction === "block" &&
185
+ protectedResponse.matches.length > 0) {
186
+ const blocked = {
187
+ code: "safety_blocked",
188
+ message: "CareCompass blocked a response because detectable PHI is not allowed in strict compliance mode.",
189
+ };
190
+ retry = null;
191
+ yield fail(blocked);
192
+ failureAlreadyCaptured = true;
193
+ throw new Error(blocked.message);
194
+ }
107
195
  const assistantMessage = {
108
196
  id: nextId("assistant"),
109
197
  role: "assistant",
110
- text: status.result,
198
+ text: protectedResponse.value,
111
199
  createdAt: now(),
112
200
  status: "complete",
201
+ retrieval: status.retrieval,
113
202
  };
114
203
  messages = [...messages, assistantMessage];
115
204
  phase = "complete";
@@ -117,6 +206,10 @@ export function createCareCompassCore(config, options = {}) {
117
206
  activeJobId = null;
118
207
  retry = null;
119
208
  notify();
209
+ emitAudit("message", protectedResponse.matches.length > 0 &&
210
+ compliancePolicy.phiAction === "redact"
211
+ ? "redacted"
212
+ : "completed", "response", protectedResponse.matches);
120
213
  yield event({
121
214
  type: "message_completed",
122
215
  message: assistantMessage,
@@ -125,7 +218,12 @@ export function createCareCompassCore(config, options = {}) {
125
218
  }
126
219
  if (status.status === "failed") {
127
220
  const failed = errorFrom(new Error(status.result ?? config.labels.errorMessage));
128
- retry = { prompt, context: requestContext, attempts: 0 };
221
+ retry = {
222
+ prompt: protectedPromptValue,
223
+ workflow: sendOptions.workflow,
224
+ context: requestContext,
225
+ attempts: 0,
226
+ };
129
227
  yield fail(failed);
130
228
  failureAlreadyCaptured = true;
131
229
  throw new Error(failed.message);
@@ -135,7 +233,13 @@ export function createCareCompassCore(config, options = {}) {
135
233
  }
136
234
  client.recordInferenceTimeout(jobId, now() - startedAt);
137
235
  const timedOut = errorFrom(new Error("CareCompass request timed out."), "timeout");
138
- retry = { prompt, context: requestContext, attempts: 0 };
236
+ retry = {
237
+ prompt: protectedPromptValue,
238
+ requestId,
239
+ workflow: sendOptions.workflow,
240
+ context: requestContext,
241
+ attempts: 0,
242
+ };
139
243
  yield fail(timedOut);
140
244
  failureAlreadyCaptured = true;
141
245
  throw new Error(timedOut.message);
@@ -143,20 +247,44 @@ export function createCareCompassCore(config, options = {}) {
143
247
  catch (caught) {
144
248
  if (failureAlreadyCaptured)
145
249
  throw caught;
146
- retry = { prompt, context: requestContext, attempts: 0 };
250
+ retry = {
251
+ prompt: protectedPromptValue,
252
+ requestId,
253
+ workflow: sendOptions.workflow,
254
+ context: requestContext,
255
+ attempts: 0,
256
+ };
147
257
  const requestError = errorFrom(caught);
148
258
  yield fail(requestError);
149
259
  throw caught instanceof Error ? caught : new Error(requestError.message);
150
260
  }
151
261
  }
152
262
  return {
263
+ exportAuditTrail: () => auditEvents.map((auditEvent) => ({
264
+ ...auditEvent,
265
+ phi: {
266
+ categories: [...auditEvent.phi.categories],
267
+ count: auditEvent.phi.count,
268
+ },
269
+ })),
153
270
  getSnapshot,
271
+ recordSafetyDecision: (decision, source = "tool_call") => emitSafety(decision, source),
272
+ recordToolCall: (outcome = "completed") => emitAudit("tool_call", outcome, "tool_call"),
273
+ recordWidgetAction: (outcome = "recorded") => emitAudit("widget_action", outcome, "widget_action"),
154
274
  subscribe: (listener) => {
155
275
  listeners.add(listener);
156
276
  return () => {
157
277
  listeners.delete(listener);
158
278
  };
159
279
  },
280
+ subscribeAudit: (listener) => {
281
+ auditListeners.add(listener);
282
+ return () => auditListeners.delete(listener);
283
+ },
284
+ subscribeSafety: (listener) => {
285
+ safetyListeners.add(listener);
286
+ return () => safetyListeners.delete(listener);
287
+ },
160
288
  setContext: (nextContext) => {
161
289
  ensureAvailable();
162
290
  context =
@@ -206,10 +334,14 @@ export function createCareCompassCore(config, options = {}) {
206
334
  phase = "disposed";
207
335
  activeJobId = null;
208
336
  listeners.clear();
337
+ auditListeners.clear();
338
+ safetyListeners.clear();
209
339
  },
210
340
  };
211
341
  async function streamRetry(retryRequest) {
212
342
  const iterator = streamMessage(retryRequest.prompt, {
343
+ requestId: retryRequest.requestId,
344
+ workflow: retryRequest.workflow,
213
345
  context: retryRequest.context,
214
346
  })[Symbol.asyncIterator]();
215
347
  while (!(await iterator.next()).done) {
package/dist/index.d.ts CHANGED
@@ -1,13 +1,19 @@
1
1
  export { createCareCompassClient } from "./client.js";
2
- export type { CareCompassAskRequest, CareCompassAskResponse, CareCompassStatusResponse, } from "./client.js";
3
- export { CareCompassChat, CareCompassEmbed } from "./component.js";
4
- export type { CareCompassChatProps } from "./component.js";
2
+ export type { CareCompassAskRequest, CareCompassAskResponse, CareCompassClientOptions, CareCompassRetrievalMetadata, CareCompassRetrievalReference, CareCompassRuntimeTokenProvider, CareCompassRuntimeTokenRequest, CareCompassStatusResponse, CareCompassWorkflowId, } from "./client.js";
3
+ export { CareCompassChat, CareCompassEmbed, useCareCompass, } from "./component.js";
4
+ export type { CareCompassChatProps, CareCompassReactController, UseCareCompassOptions, } from "./component.js";
5
5
  export { createCareCompassCore } from "./core.js";
6
6
  export type { CareCompassContext, CareCompassCore, CareCompassCoreError, CareCompassCoreErrorCode, CareCompassCoreEvent, CareCompassCoreOptions, CareCompassCoreSnapshot, CareCompassLifecyclePhase, CareCompassMessage, CareCompassRetryMetadata, CareCompassSendMessageOptions, } from "./core.js";
7
+ export { CARECOMPASS_RCM_SYNTHETIC_DEMO, createRcmCodingWorkflow, RcmCodingAssistant, } from "./rcm.js";
8
+ export type { RcmCodeRecommendation, RcmCodeSystem, RcmCodingAssistanceInput, RcmCodingAssistanceResult, RcmCodingAssistantProps, RcmCodingWorkflow, RcmCodingWorkflowOptions, RcmConfidence, RcmCustomerRules, RcmPolicyReference, RcmPolicyRetriever, RcmReviewDisposition, } from "./rcm.js";
7
9
  export { mountCareCompass } from "./mount.js";
10
+ export { CARECOMPASS_ELEMENT_TAG, defineCareCompassElement, } from "./web-component.js";
11
+ export type { CareCompassWebComponentElement } from "./web-component.js";
8
12
  export { generateCareCompassIframeSnippet, generateCareCompassSnippet, } from "./snippet.js";
9
- export { careCompassApiBaseUrlSchema, careCompassConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
10
- export type { CareCompassConfig, CareCompassConfigInput, CareCompassLabels, CareCompassPromptConfig, CareCompassTheme, CareCompassValidationResult, } from "./schema.js";
13
+ export { careCompassApiBaseUrlSchema, careCompassComplianceConfigSchema, careCompassConfigSchema, careCompassFeatureFlagsSchema, careCompassModelConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
14
+ export type { CareCompassComplianceConfig, CareCompassConfig, CareCompassConfigInput, CareCompassFeatureFlags, CareCompassLabels, CareCompassModelConfig, CareCompassPromptConfig, CareCompassTheme, CareCompassValidationResult, } from "./schema.js";
15
+ export { CARECOMPASS_COMPLIANCE_PRESETS, detectCareCompassPhi, redactCareCompassPhi, resolveCareCompassCompliancePolicy, } from "./compliance.js";
16
+ export type { CareCompassAuditEvent, CareCompassAuditEventType, CareCompassAuditListener, CareCompassAuditOutcome, CareCompassAuditSource, CareCompassCompliancePolicy, CareCompassCompliancePreset, CareCompassComplianceRuntimeOptions, CareCompassPhiAction, CareCompassPhiCategory, CareCompassPhiDetectionContext, CareCompassPhiDetector, CareCompassPhiHooks, CareCompassPhiMatch, CareCompassPhiRedactor, CareCompassPhiSource, CareCompassSafetyDecision, CareCompassSafetyEvent, CareCompassSafetyListener, } from "./compliance.js";
11
17
  export { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, careCompassTelemetryDurationBucket, createCareCompassCliTelemetry, createCareCompassTelemetry, } from "./telemetry.js";
12
18
  export type { CareCompassTelemetry, CareCompassTelemetryCaptureOptions, CareCompassTelemetryDurationBucket, CareCompassTelemetryErrorCode, CareCompassTelemetryEvent, CareCompassTelemetryFailureCategory, } from "./telemetry.js";
13
19
  export { CARECOMPASS_COMPONENT_VERSION, CARECOMPASS_SDK_VERSION, CARECOMPASS_TELEMETRY_CONTRACT_VERSION, } from "./version.js";
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  export { createCareCompassClient } from "./client.js";
2
- export { CareCompassChat, CareCompassEmbed } from "./component.js";
2
+ export { CareCompassChat, CareCompassEmbed, useCareCompass, } from "./component.js";
3
3
  export { createCareCompassCore } from "./core.js";
4
+ export { CARECOMPASS_RCM_SYNTHETIC_DEMO, createRcmCodingWorkflow, RcmCodingAssistant, } from "./rcm.js";
4
5
  export { mountCareCompass } from "./mount.js";
6
+ export { CARECOMPASS_ELEMENT_TAG, defineCareCompassElement, } from "./web-component.js";
5
7
  export { generateCareCompassIframeSnippet, generateCareCompassSnippet, } from "./snippet.js";
6
- export { careCompassApiBaseUrlSchema, careCompassConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
8
+ export { careCompassApiBaseUrlSchema, careCompassComplianceConfigSchema, careCompassConfigSchema, careCompassFeatureFlagsSchema, careCompassModelConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
9
+ export { CARECOMPASS_COMPLIANCE_PRESETS, detectCareCompassPhi, redactCareCompassPhi, resolveCareCompassCompliancePolicy, } from "./compliance.js";
7
10
  export { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, careCompassTelemetryDurationBucket, createCareCompassCliTelemetry, createCareCompassTelemetry, } from "./telemetry.js";
8
11
  export { CARECOMPASS_COMPONENT_VERSION, CARECOMPASS_SDK_VERSION, CARECOMPASS_TELEMETRY_CONTRACT_VERSION, } from "./version.js";
@@ -1,3 +1,4 @@
1
+ import React from "react";
1
2
  export declare function CareCompassMarkdown({ text }: {
2
3
  text: string;
3
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): React.JSX.Element;
package/dist/mount.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import type { CareCompassRuntimeTokenProvider } from "./client.js";
1
2
  import type { CareCompassConfig } from "./schema.js";
2
- export declare function mountCareCompass(element: Element | null, config: CareCompassConfig): {
3
+ export declare function mountCareCompass(element: Element | null, config: CareCompassConfig, options?: {
4
+ runtimeTokenProvider?: CareCompassRuntimeTokenProvider;
5
+ }): {
3
6
  unmount: () => void;
4
7
  };
package/dist/mount.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createRoot } from "react-dom/client";
3
3
  import { CareCompassEmbed } from "./component.js";
4
4
  import { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, createCareCompassTelemetry, } from "./telemetry.js";
5
- export function mountCareCompass(element, config) {
5
+ export function mountCareCompass(element, config, options = {}) {
6
6
  if (!element) {
7
7
  // Missing mount points usually mean a copied snippet and page markup drifted
8
8
  // apart, so capture the setup error before throwing to the caller.
@@ -13,7 +13,7 @@ export function mountCareCompass(element, config) {
13
13
  throw new Error("CareCompass mount element was not found.");
14
14
  }
15
15
  const root = createRoot(element);
16
- root.render(_jsx(CareCompassEmbed, { config: config }));
16
+ root.render(_jsx(CareCompassEmbed, { config: config, runtimeTokenProvider: options.runtimeTokenProvider }));
17
17
  return {
18
18
  // Return the small React root facade so non-React hosts can clean up embeds.
19
19
  unmount: () => root.unmount(),