@nexalink/carecompass-sdk 0.1.0-beta.9 → 0.2.0-beta.12
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/LICENSE +2 -1
- package/README.md +150 -39
- package/RUNTIME_SECURITY_AUDIT.md +180 -0
- package/SECURITY_AUDIT.md +78 -0
- package/TELEMETRY.md +129 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +377 -41
- package/dist/client.d.ts +4 -2
- package/dist/client.js +163 -24
- package/dist/component.js +41 -2
- package/dist/credentials.js +4 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -2
- package/dist/mount.js +10 -2
- package/dist/schema.d.ts +3 -1
- package/dist/schema.js +79 -5
- package/dist/snippet.js +22 -4
- package/dist/telemetry.d.ts +71 -0
- package/dist/telemetry.js +180 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +3 -0
- package/package.json +33 -6
- package/dist/cli.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/component.js.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mount.js.map +0 -1
- package/dist/schema.js.map +0 -1
- package/dist/snippet.js.map +0 -1
package/dist/client.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { validateCareCompassConfig } from "./schema.js";
|
|
2
|
+
import { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, createCareCompassTelemetry, } from "./telemetry.js";
|
|
3
|
+
// Internal error metadata keeps telemetry classification separate from messages,
|
|
4
|
+
// which may include user-visible API details that should not be emitted.
|
|
5
|
+
class CareCompassClientError extends Error {
|
|
6
|
+
constructor(message, telemetryErrorCode, telemetryFailureCategory) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.telemetryErrorCode = telemetryErrorCode;
|
|
9
|
+
this.telemetryFailureCategory = telemetryFailureCategory;
|
|
10
|
+
this.name = "CareCompassClientError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
1
13
|
const joinUrl = (baseUrl, path) => `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
2
14
|
const isLocalhostUrl = (value) => {
|
|
3
15
|
try {
|
|
@@ -16,59 +28,186 @@ const isBrowserHostedAwayFromLocalhost = (apiBaseUrl) => {
|
|
|
16
28
|
return (window.location.hostname !== "localhost" &&
|
|
17
29
|
window.location.hostname !== "127.0.0.1");
|
|
18
30
|
};
|
|
19
|
-
|
|
31
|
+
// Keep HTTP status mapping centralized so every client command reports failures
|
|
32
|
+
// with the same public telemetry taxonomy.
|
|
33
|
+
const responseError = (status, message) => {
|
|
34
|
+
if (status === 401 || status === 403) {
|
|
35
|
+
return new CareCompassClientError(message, CARECOMPASS_TELEMETRY_ERROR_CODES.AUTHENTICATION_FAILED, "authentication");
|
|
36
|
+
}
|
|
37
|
+
if (status === 429) {
|
|
38
|
+
return new CareCompassClientError(message, CARECOMPASS_TELEMETRY_ERROR_CODES.RATE_LIMITED, "rate_limit");
|
|
39
|
+
}
|
|
40
|
+
if (status >= 500) {
|
|
41
|
+
return new CareCompassClientError(message, CARECOMPASS_TELEMETRY_ERROR_CODES.BACKEND_UNAVAILABLE, "network");
|
|
42
|
+
}
|
|
43
|
+
return new CareCompassClientError(message, CARECOMPASS_TELEMETRY_ERROR_CODES.API_ERROR, "unknown");
|
|
44
|
+
};
|
|
45
|
+
const telemetryFailure = (error) => error instanceof CareCompassClientError
|
|
46
|
+
? {
|
|
47
|
+
errorCode: error.telemetryErrorCode,
|
|
48
|
+
failureCategory: error.telemetryFailureCategory,
|
|
49
|
+
}
|
|
50
|
+
: {
|
|
51
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.INFERENCE_FAILED,
|
|
52
|
+
failureCategory: "unknown",
|
|
53
|
+
};
|
|
54
|
+
const captureInferenceFailure = (telemetry, error, durationMs) => {
|
|
55
|
+
const failure = telemetryFailure(error);
|
|
56
|
+
telemetry.capture(failure.errorCode === CARECOMPASS_TELEMETRY_ERROR_CODES.RATE_LIMITED
|
|
57
|
+
? CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_RATE_LIMITED
|
|
58
|
+
: CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_FAILED, { ...failure, durationMs });
|
|
59
|
+
};
|
|
60
|
+
export function createCareCompassClient(inputConfig, options = {}) {
|
|
61
|
+
const telemetry = createCareCompassTelemetry(inputConfig);
|
|
62
|
+
const now = options.now ?? Date.now;
|
|
63
|
+
let config;
|
|
64
|
+
try {
|
|
65
|
+
const validation = validateCareCompassConfig(inputConfig);
|
|
66
|
+
if (!validation.ok) {
|
|
67
|
+
const promptInvalid = validation.errors.some((error) => error.startsWith("prompt."));
|
|
68
|
+
if (promptInvalid) {
|
|
69
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.PROMPT_CONFIGURATION_FAILED, {
|
|
70
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID,
|
|
71
|
+
failureCategory: "configuration",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_CONFIGURATION_FAILED, {
|
|
75
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID,
|
|
76
|
+
failureCategory: "configuration",
|
|
77
|
+
});
|
|
78
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_INITIALIZATION_FAILED, {
|
|
79
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID,
|
|
80
|
+
failureCategory: "configuration",
|
|
81
|
+
});
|
|
82
|
+
throw new CareCompassClientError(`Invalid CareCompass config:\n${validation.errors.join("\n")}`, CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID, "configuration");
|
|
83
|
+
}
|
|
84
|
+
config = validation.config;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof CareCompassClientError)
|
|
88
|
+
throw error;
|
|
89
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_CONFIGURATION_FAILED, {
|
|
90
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID,
|
|
91
|
+
failureCategory: "configuration",
|
|
92
|
+
});
|
|
93
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_INITIALIZATION_FAILED, {
|
|
94
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID,
|
|
95
|
+
failureCategory: "configuration",
|
|
96
|
+
});
|
|
97
|
+
throw new CareCompassClientError("Invalid CareCompass config.", CARECOMPASS_TELEMETRY_ERROR_CODES.CONFIGURATION_INVALID, "configuration");
|
|
98
|
+
}
|
|
20
99
|
const fetcher = options.fetcher ?? fetch;
|
|
100
|
+
// These headers are the public embed identity; secret API keys stay in the CLI.
|
|
21
101
|
const headers = {
|
|
22
102
|
"content-type": "application/json",
|
|
23
103
|
"x-carecompass-payer-id": config.payerId,
|
|
24
104
|
"x-carecompass-embed-key": config.embedKey,
|
|
25
105
|
"x-carecompass-environment": config.environment,
|
|
26
106
|
};
|
|
107
|
+
const jobStartedAt = new Map();
|
|
108
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_INITIALIZED);
|
|
109
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.THEME_LOADED);
|
|
110
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.PROMPT_CONFIGURATION_LOADED);
|
|
27
111
|
async function send(input, init) {
|
|
112
|
+
// Hosted demos and sandboxes cannot call a developer's local API server, so
|
|
113
|
+
// fail early with a setup hint instead of surfacing an opaque fetch error.
|
|
28
114
|
if (isBrowserHostedAwayFromLocalhost(config.apiBaseUrl)) {
|
|
29
|
-
throw new
|
|
115
|
+
throw new CareCompassClientError(`CareCompass apiBaseUrl is ${config.apiBaseUrl}, but this page is running at ${window.location.origin}. A hosted sandbox or remote site cannot reach your local Next.js server. Use a deployed Nexalink API base URL, or run both the integrating page and Nexalink web app locally on the same machine.`, CARECOMPASS_TELEMETRY_ERROR_CODES.NETWORK_ERROR, "network");
|
|
30
116
|
}
|
|
31
117
|
try {
|
|
32
118
|
return await fetcher(input, init);
|
|
33
119
|
}
|
|
34
120
|
catch (error) {
|
|
121
|
+
if (error instanceof CareCompassClientError)
|
|
122
|
+
throw error;
|
|
35
123
|
const message = error instanceof Error ? error.message : "Network request failed";
|
|
36
|
-
throw new
|
|
124
|
+
throw new CareCompassClientError(`CareCompass could not reach ${config.apiBaseUrl}. Confirm apiBaseUrl, CORS, allowedOrigins, and Nexalink hosted API configuration. ${message}`, CARECOMPASS_TELEMETRY_ERROR_CODES.NETWORK_ERROR, "network");
|
|
37
125
|
}
|
|
38
126
|
}
|
|
39
127
|
async function parseJson(response) {
|
|
40
128
|
const body = (await response.json().catch(() => ({})));
|
|
41
129
|
if (!response.ok) {
|
|
42
|
-
throw
|
|
130
|
+
throw responseError(response.status, body.error ?? "CareCompass request failed.");
|
|
43
131
|
}
|
|
44
132
|
return body;
|
|
45
133
|
}
|
|
46
134
|
return {
|
|
47
135
|
validateConfig: async () => {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
136
|
+
const startedAt = now();
|
|
137
|
+
try {
|
|
138
|
+
const response = await send(joinUrl(config.apiBaseUrl, "/api/carecompass/embed/config/validate"), {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers,
|
|
141
|
+
body: JSON.stringify({ config }),
|
|
142
|
+
});
|
|
143
|
+
const result = await parseJson(response);
|
|
144
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_CONFIGURATION_VALIDATED, { durationMs: now() - startedAt });
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_CONFIGURATION_FAILED, { ...telemetryFailure(error), durationMs: now() - startedAt });
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
54
151
|
},
|
|
55
152
|
ask: async (request) => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
153
|
+
const startedAt = now();
|
|
154
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.PROMPT_SUBMITTED);
|
|
155
|
+
try {
|
|
156
|
+
const response = await send(joinUrl(config.apiBaseUrl, "/api/carecompass/embed/ask"), {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers,
|
|
159
|
+
body: JSON.stringify({
|
|
160
|
+
prompt: request.prompt,
|
|
161
|
+
sessionId: request.sessionId,
|
|
162
|
+
userContext: request.userContext,
|
|
163
|
+
promptConfig: config.prompt,
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
const result = await parseJson(response);
|
|
167
|
+
// Status polling is asynchronous, so retain the submit timestamp until
|
|
168
|
+
// the final job state lets telemetry report end-to-end latency.
|
|
169
|
+
jobStartedAt.set(result.jobId, startedAt);
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
captureInferenceFailure(telemetry, error, now() - startedAt);
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
67
176
|
},
|
|
68
177
|
status: async (jobId) => {
|
|
69
|
-
const
|
|
70
|
-
|
|
178
|
+
const startedAt = jobStartedAt.get(jobId) ?? now();
|
|
179
|
+
try {
|
|
180
|
+
const response = await send(joinUrl(config.apiBaseUrl, `/api/carecompass/embed/status/${encodeURIComponent(jobId)}`), { headers });
|
|
181
|
+
const result = await parseJson(response);
|
|
182
|
+
if (result.status === "complete") {
|
|
183
|
+
jobStartedAt.delete(jobId);
|
|
184
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_SUCCEEDED, {
|
|
185
|
+
durationMs: now() - startedAt,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else if (result.status === "failed") {
|
|
189
|
+
jobStartedAt.delete(jobId);
|
|
190
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_FAILED, {
|
|
191
|
+
durationMs: now() - startedAt,
|
|
192
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.INFERENCE_FAILED,
|
|
193
|
+
failureCategory: "inference",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
jobStartedAt.delete(jobId);
|
|
200
|
+
captureInferenceFailure(telemetry, error, now() - startedAt);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
recordInferenceTimeout: (jobId, durationMs) => {
|
|
205
|
+
jobStartedAt.delete(jobId);
|
|
206
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_FAILED, {
|
|
207
|
+
durationMs,
|
|
208
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.INFERENCE_TIMEOUT,
|
|
209
|
+
failureCategory: "timeout",
|
|
210
|
+
});
|
|
71
211
|
},
|
|
72
212
|
};
|
|
73
213
|
}
|
|
74
|
-
//# sourceMappingURL=client.js.map
|
package/dist/component.js
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { createCareCompassClient } from "./client.js";
|
|
4
|
-
|
|
4
|
+
import { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, createCareCompassTelemetry, } from "./telemetry.js";
|
|
5
|
+
// The embed should fail closed inside the host page: show the configured
|
|
6
|
+
// fallback text, capture sanitized telemetry, and avoid crashing the integrator.
|
|
7
|
+
class CareCompassErrorBoundary extends React.Component {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.state = { failed: false };
|
|
11
|
+
}
|
|
12
|
+
static getDerivedStateFromError() {
|
|
13
|
+
return { failed: true };
|
|
14
|
+
}
|
|
15
|
+
componentDidCatch() {
|
|
16
|
+
this.props.telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_ERROR, {
|
|
17
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.RENDER_FAILED,
|
|
18
|
+
failureCategory: "rendering",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
render() {
|
|
22
|
+
if (this.state.failed) {
|
|
23
|
+
return _jsx("div", { role: "alert", children: this.props.config.labels.errorMessage });
|
|
24
|
+
}
|
|
25
|
+
return this.props.children;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function CareCompassEmbedContent({ config, telemetry, pollIntervalMs = 1500, }) {
|
|
5
29
|
const client = React.useMemo(() => createCareCompassClient(config), [config]);
|
|
6
30
|
const [messages, setMessages] = React.useState([
|
|
7
31
|
{
|
|
@@ -13,6 +37,13 @@ export function CareCompassEmbed({ config, pollIntervalMs = 1500, }) {
|
|
|
13
37
|
const [input, setInput] = React.useState("");
|
|
14
38
|
const [loading, setLoading] = React.useState(false);
|
|
15
39
|
const [error, setError] = React.useState(null);
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_RENDERED);
|
|
42
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_OPENED);
|
|
43
|
+
return () => {
|
|
44
|
+
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_CLOSED);
|
|
45
|
+
};
|
|
46
|
+
}, [telemetry]);
|
|
16
47
|
async function sendMessage(event) {
|
|
17
48
|
event.preventDefault();
|
|
18
49
|
const prompt = input.trim();
|
|
@@ -28,6 +59,8 @@ export function CareCompassEmbed({ config, pollIntervalMs = 1500, }) {
|
|
|
28
59
|
try {
|
|
29
60
|
const { jobId } = await client.ask({ prompt });
|
|
30
61
|
const startedAt = Date.now();
|
|
62
|
+
// The backend returns a job immediately, so the component polls until it
|
|
63
|
+
// sees a terminal state or reaches the same timeout tracked by telemetry.
|
|
31
64
|
while (Date.now() - startedAt < 120_000) {
|
|
32
65
|
const status = await client.status(jobId);
|
|
33
66
|
if (status.status === "complete" && status.result) {
|
|
@@ -46,6 +79,7 @@ export function CareCompassEmbed({ config, pollIntervalMs = 1500, }) {
|
|
|
46
79
|
}
|
|
47
80
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
48
81
|
}
|
|
82
|
+
client.recordInferenceTimeout(jobId, Date.now() - startedAt);
|
|
49
83
|
throw new Error("CareCompass request timed out.");
|
|
50
84
|
}
|
|
51
85
|
catch (err) {
|
|
@@ -119,4 +153,9 @@ export function CareCompassEmbed({ config, pollIntervalMs = 1500, }) {
|
|
|
119
153
|
padding: "0 14px",
|
|
120
154
|
}, type: "submit", children: config.labels.sendButton })] }), error ? (_jsx("div", { role: "alert", style: { color: "#b91c1c", fontSize: 12 }, children: error })) : null, _jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: config.labels.disclaimer })] })] }));
|
|
121
155
|
}
|
|
122
|
-
|
|
156
|
+
export function CareCompassEmbed({ config, pollIntervalMs = 1500, }) {
|
|
157
|
+
// Telemetry is scoped to the validated public config; no per-message content
|
|
158
|
+
// is passed through this component-level telemetry object.
|
|
159
|
+
const telemetry = React.useMemo(() => createCareCompassTelemetry(config), [config]);
|
|
160
|
+
return (_jsx(CareCompassErrorBoundary, { config: config, telemetry: telemetry, children: _jsx(CareCompassEmbedContent, { config: config, pollIntervalMs: pollIntervalMs, telemetry: telemetry }) }));
|
|
161
|
+
}
|
package/dist/credentials.js
CHANGED
|
@@ -20,6 +20,8 @@ export function readStoredApiKey(path = resolveCredentialsPath()) {
|
|
|
20
20
|
: null;
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
23
|
+
// Missing, unreadable, or malformed credential files are treated the same so
|
|
24
|
+
// callers can fall through to the next credential source without leaking paths.
|
|
23
25
|
return null;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -37,6 +39,8 @@ export function writeStoredApiKey(apiKey, path = resolveCredentialsPath()) {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
export function resolveApiKey(argumentApiKey, env = process.env, credentialsPath = resolveCredentialsPath(env)) {
|
|
42
|
+
// Precedence mirrors common CLI behavior: explicit flag, then environment,
|
|
43
|
+
// then the local credential store created by `carecompass login` or `init`.
|
|
40
44
|
const argKey = normalizeApiKey(argumentApiKey);
|
|
41
45
|
if (argKey)
|
|
42
46
|
return { apiKey: argKey, source: "argument" };
|
|
@@ -48,4 +52,3 @@ export function resolveApiKey(argumentApiKey, env = process.env, credentialsPath
|
|
|
48
52
|
return { apiKey: fileKey, source: "file" };
|
|
49
53
|
return { apiKey: null, source: "missing" };
|
|
50
54
|
}
|
|
51
|
-
//# sourceMappingURL=credentials.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,8 @@ export type { CareCompassAskRequest, CareCompassAskResponse, CareCompassStatusRe
|
|
|
3
3
|
export { CareCompassEmbed } from "./component.js";
|
|
4
4
|
export { mountCareCompass } from "./mount.js";
|
|
5
5
|
export { generateCareCompassIframeSnippet, generateCareCompassSnippet, } from "./snippet.js";
|
|
6
|
-
export { careCompassConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
|
|
7
|
-
export type { CareCompassConfig, CareCompassLabels, CareCompassPromptConfig, CareCompassTheme, CareCompassValidationResult, } from "./schema.js";
|
|
6
|
+
export { careCompassApiBaseUrlSchema, careCompassConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
|
|
7
|
+
export type { CareCompassConfig, CareCompassConfigInput, CareCompassLabels, CareCompassPromptConfig, CareCompassTheme, CareCompassValidationResult, } from "./schema.js";
|
|
8
|
+
export { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, careCompassTelemetryDurationBucket, createCareCompassCliTelemetry, createCareCompassTelemetry, } from "./telemetry.js";
|
|
9
|
+
export type { CareCompassTelemetry, CareCompassTelemetryCaptureOptions, CareCompassTelemetryDurationBucket, CareCompassTelemetryErrorCode, CareCompassTelemetryEvent, CareCompassTelemetryFailureCategory, } from "./telemetry.js";
|
|
10
|
+
export { CARECOMPASS_COMPONENT_VERSION, CARECOMPASS_SDK_VERSION, CARECOMPASS_TELEMETRY_CONTRACT_VERSION, } from "./version.js";
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export { createCareCompassClient } from "./client.js";
|
|
|
2
2
|
export { CareCompassEmbed } from "./component.js";
|
|
3
3
|
export { mountCareCompass } from "./mount.js";
|
|
4
4
|
export { generateCareCompassIframeSnippet, generateCareCompassSnippet, } from "./snippet.js";
|
|
5
|
-
export { careCompassConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
|
|
6
|
-
|
|
5
|
+
export { careCompassApiBaseUrlSchema, careCompassConfigSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
|
|
6
|
+
export { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, careCompassTelemetryDurationBucket, createCareCompassCliTelemetry, createCareCompassTelemetry, } from "./telemetry.js";
|
|
7
|
+
export { CARECOMPASS_COMPONENT_VERSION, CARECOMPASS_SDK_VERSION, CARECOMPASS_TELEMETRY_CONTRACT_VERSION, } from "./version.js";
|
package/dist/mount.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { CareCompassEmbed } from "./component.js";
|
|
4
|
+
import { CARECOMPASS_TELEMETRY_ERROR_CODES, CARECOMPASS_TELEMETRY_EVENTS, createCareCompassTelemetry, } from "./telemetry.js";
|
|
4
5
|
export function mountCareCompass(element, config) {
|
|
5
|
-
if (!element)
|
|
6
|
+
if (!element) {
|
|
7
|
+
// Missing mount points usually mean a copied snippet and page markup drifted
|
|
8
|
+
// apart, so capture the setup error before throwing to the caller.
|
|
9
|
+
createCareCompassTelemetry(config).capture(CARECOMPASS_TELEMETRY_EVENTS.COMPONENT_ERROR, {
|
|
10
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.MOUNT_ELEMENT_MISSING,
|
|
11
|
+
failureCategory: "rendering",
|
|
12
|
+
});
|
|
6
13
|
throw new Error("CareCompass mount element was not found.");
|
|
14
|
+
}
|
|
7
15
|
const root = createRoot(element);
|
|
8
16
|
root.render(_jsx(CareCompassEmbed, { config: config }));
|
|
9
17
|
return {
|
|
18
|
+
// Return the small React root facade so non-React hosts can clean up embeds.
|
|
10
19
|
unmount: () => root.unmount(),
|
|
11
20
|
};
|
|
12
21
|
}
|
|
13
|
-
//# sourceMappingURL=mount.js.map
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const careCompassOriginSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
3
|
+
export declare const careCompassApiBaseUrlSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
3
4
|
export declare const careCompassThemeSchema: z.ZodObject<{
|
|
4
5
|
primaryColor: z.ZodString;
|
|
5
6
|
backgroundColor: z.ZodDefault<z.ZodString>;
|
|
@@ -40,7 +41,7 @@ export declare const careCompassConfigSchema: z.ZodObject<{
|
|
|
40
41
|
staging: "staging";
|
|
41
42
|
production: "production";
|
|
42
43
|
}>;
|
|
43
|
-
apiBaseUrl: z.ZodString
|
|
44
|
+
apiBaseUrl: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
44
45
|
embedKey: z.ZodString;
|
|
45
46
|
allowedOrigins: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
46
47
|
branding: z.ZodObject<{
|
|
@@ -88,6 +89,7 @@ export type CareCompassTheme = z.infer<typeof careCompassThemeSchema>;
|
|
|
88
89
|
export type CareCompassLabels = z.infer<typeof careCompassLabelsSchema>;
|
|
89
90
|
export type CareCompassPromptConfig = z.infer<typeof careCompassPromptConfigSchema>;
|
|
90
91
|
export type CareCompassConfig = z.infer<typeof careCompassConfigSchema>;
|
|
92
|
+
export type CareCompassConfigInput = z.input<typeof careCompassConfigSchema>;
|
|
91
93
|
export type CareCompassValidationResult = {
|
|
92
94
|
ok: true;
|
|
93
95
|
config: CareCompassConfig;
|
package/dist/schema.js
CHANGED
|
@@ -2,13 +2,40 @@ import { z } from "zod";
|
|
|
2
2
|
const HEX_COLOR_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
3
3
|
const PAYER_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{2,63}$/;
|
|
4
4
|
const EMBED_KEY_RE = /^[A-Za-z0-9._-]{16,128}$/;
|
|
5
|
+
const NEXALINK_HOSTNAME = "nexalink.care";
|
|
6
|
+
// These patterns are a local preflight check, not the full policy layer. They
|
|
7
|
+
// catch obvious prompt configurations that conflict with Nexalink guardrails.
|
|
5
8
|
const unsafePromptPatterns = [
|
|
6
9
|
/ignore (all )?(previous|prior) (instructions|rules)/i,
|
|
7
10
|
/bypass (safety|guardrails|compliance)/i,
|
|
8
11
|
/disable (safety|guardrails|compliance)/i,
|
|
9
12
|
/provide (a )?(diagnosis|treatment plan)/i,
|
|
13
|
+
/reveal (the )?(system|developer) (prompt|instructions)/i,
|
|
14
|
+
/override (nexalink|carecompass) (safety|guardrails|policy)/i,
|
|
10
15
|
/return raw (phi|credentials|tokens|secrets)/i,
|
|
11
16
|
];
|
|
17
|
+
const isLocalhostHostname = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
18
|
+
const isNexalinkHostname = (hostname) => hostname === NEXALINK_HOSTNAME || hostname.endsWith(`.${NEXALINK_HOSTNAME}`);
|
|
19
|
+
const parseUrl = (value) => {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(value);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const isLocalhostUrl = (value) => {
|
|
28
|
+
const parsed = parseUrl(value);
|
|
29
|
+
return parsed ? isLocalhostHostname(parsed.hostname) : false;
|
|
30
|
+
};
|
|
31
|
+
const isNexalinkHostedApiUrl = (value) => {
|
|
32
|
+
const parsed = parseUrl(value);
|
|
33
|
+
if (!parsed)
|
|
34
|
+
return false;
|
|
35
|
+
if (isLocalhostHostname(parsed.hostname))
|
|
36
|
+
return true;
|
|
37
|
+
return parsed.protocol === "https:" && isNexalinkHostname(parsed.hostname);
|
|
38
|
+
};
|
|
12
39
|
export const careCompassOriginSchema = z
|
|
13
40
|
.string()
|
|
14
41
|
.url("Allowed origins must be valid URLs.")
|
|
@@ -17,6 +44,13 @@ export const careCompassOriginSchema = z
|
|
|
17
44
|
const parsed = new URL(value);
|
|
18
45
|
return parsed.protocol === "https:" || parsed.hostname === "localhost";
|
|
19
46
|
}, "Allowed origins must use HTTPS, except localhost for development.");
|
|
47
|
+
export const careCompassApiBaseUrlSchema = z
|
|
48
|
+
.string()
|
|
49
|
+
.url("API base URL must be a valid URL.")
|
|
50
|
+
.transform((value) => value.replace(/\/+$/, ""))
|
|
51
|
+
// Runtime traffic should stay on Nexalink-hosted HTTPS endpoints; localhost is
|
|
52
|
+
// kept only for development because integrators need to test local web apps.
|
|
53
|
+
.refine(isNexalinkHostedApiUrl, "API base URL must be a Nexalink-hosted HTTPS URL, except localhost for development.");
|
|
20
54
|
export const careCompassThemeSchema = z.object({
|
|
21
55
|
primaryColor: z.string().regex(HEX_COLOR_RE, "Use a hex color like #2563eb."),
|
|
22
56
|
backgroundColor: z
|
|
@@ -72,17 +106,32 @@ export const careCompassPromptConfigSchema = z
|
|
|
72
106
|
});
|
|
73
107
|
export const careCompassBrandingSchema = z.object({
|
|
74
108
|
displayName: z.string().min(1).max(80),
|
|
75
|
-
logoUrl: z
|
|
76
|
-
|
|
109
|
+
logoUrl: z
|
|
110
|
+
.string()
|
|
111
|
+
.url()
|
|
112
|
+
.refine((value) => {
|
|
113
|
+
const parsed = new URL(value);
|
|
114
|
+
return (parsed.protocol === "https:" || isLocalhostHostname(parsed.hostname));
|
|
115
|
+
}, "Logo URL must use HTTPS, except localhost for development.")
|
|
116
|
+
.optional(),
|
|
117
|
+
supportUrl: z
|
|
118
|
+
.string()
|
|
119
|
+
.url()
|
|
120
|
+
.refine((value) => {
|
|
121
|
+
const parsed = new URL(value);
|
|
122
|
+
return (parsed.protocol === "https:" || isLocalhostHostname(parsed.hostname));
|
|
123
|
+
}, "Support URL must use HTTPS, except localhost for development.")
|
|
124
|
+
.optional(),
|
|
77
125
|
});
|
|
78
126
|
export const careCompassPlacementSchema = z.object({
|
|
79
127
|
mode: z.enum(["inline", "floating", "sidebar"]).default("inline"),
|
|
80
128
|
selector: z.string().min(1).max(120).optional(),
|
|
81
129
|
});
|
|
82
|
-
export const careCompassConfigSchema = z
|
|
130
|
+
export const careCompassConfigSchema = z
|
|
131
|
+
.object({
|
|
83
132
|
payerId: z.string().regex(PAYER_ID_RE, "Invalid payer ID."),
|
|
84
133
|
environment: z.enum(["development", "staging", "production"]),
|
|
85
|
-
apiBaseUrl:
|
|
134
|
+
apiBaseUrl: careCompassApiBaseUrlSchema,
|
|
86
135
|
embedKey: z.string().regex(EMBED_KEY_RE, "Invalid public embed key."),
|
|
87
136
|
allowedOrigins: z
|
|
88
137
|
.array(careCompassOriginSchema)
|
|
@@ -104,6 +153,28 @@ export const careCompassConfigSchema = z.object({
|
|
|
104
153
|
deploymentTarget: z
|
|
105
154
|
.enum(["react", "nextjs", "static", "web", "other"])
|
|
106
155
|
.default("web"),
|
|
156
|
+
})
|
|
157
|
+
.superRefine((value, ctx) => {
|
|
158
|
+
// Development can point both sides at localhost. Staging and production must
|
|
159
|
+
// use deployable origins so embeds cannot ship with machine-local endpoints.
|
|
160
|
+
if (value.environment === "development")
|
|
161
|
+
return;
|
|
162
|
+
if (isLocalhostUrl(value.apiBaseUrl)) {
|
|
163
|
+
ctx.addIssue({
|
|
164
|
+
code: "custom",
|
|
165
|
+
path: ["apiBaseUrl"],
|
|
166
|
+
message: "Staging and production API base URLs must use a Nexalink-hosted HTTPS URL.",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
for (const [index, origin] of value.allowedOrigins.entries()) {
|
|
170
|
+
if (isLocalhostUrl(origin)) {
|
|
171
|
+
ctx.addIssue({
|
|
172
|
+
code: "custom",
|
|
173
|
+
path: ["allowedOrigins", index],
|
|
174
|
+
message: "Staging and production allowed origins must not use localhost.",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
107
178
|
});
|
|
108
179
|
export function validateCareCompassConfig(value) {
|
|
109
180
|
const result = careCompassConfigSchema.safeParse(value);
|
|
@@ -111,6 +182,8 @@ export function validateCareCompassConfig(value) {
|
|
|
111
182
|
return { ok: true, config: result.data };
|
|
112
183
|
return {
|
|
113
184
|
ok: false,
|
|
185
|
+
// Flatten Zod issue paths into CLI-friendly strings while preserving enough
|
|
186
|
+
// context for users to find the bad field quickly.
|
|
114
187
|
errors: result.error.issues.map((issue) => {
|
|
115
188
|
const path = issue.path.length ? `${issue.path.join(".")}: ` : "";
|
|
116
189
|
return `${path}${issue.message}`;
|
|
@@ -118,6 +191,8 @@ export function validateCareCompassConfig(value) {
|
|
|
118
191
|
};
|
|
119
192
|
}
|
|
120
193
|
export function createDefaultCareCompassConfig(overrides = {}) {
|
|
194
|
+
// Defaults are intentionally valid staging examples so tests and generated
|
|
195
|
+
// setup kits can start from a complete config and override only local details.
|
|
121
196
|
return careCompassConfigSchema.parse({
|
|
122
197
|
payerId: "payer-demo",
|
|
123
198
|
environment: "staging",
|
|
@@ -142,4 +217,3 @@ export function createDefaultCareCompassConfig(overrides = {}) {
|
|
|
142
217
|
...overrides,
|
|
143
218
|
});
|
|
144
219
|
}
|
|
145
|
-
//# sourceMappingURL=schema.js.map
|
package/dist/snippet.js
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
const SCRIPT_JSON_ESCAPES = {
|
|
2
|
+
"<": "\\u003c",
|
|
3
|
+
">": "\\u003e",
|
|
4
|
+
"&": "\\u0026",
|
|
5
|
+
"\u2028": "\\u2028",
|
|
6
|
+
"\u2029": "\\u2029",
|
|
7
|
+
};
|
|
8
|
+
const escapeHtmlAttribute = (value) => value
|
|
9
|
+
.replaceAll("&", "&")
|
|
10
|
+
.replaceAll('"', """)
|
|
11
|
+
.replaceAll("<", "<")
|
|
12
|
+
.replaceAll(">", ">");
|
|
13
|
+
// Script JSON needs stronger escaping than attributes because `</script>` and
|
|
14
|
+
// Unicode line separators can terminate or break the generated module.
|
|
15
|
+
const serializeScriptJson = (value) => JSON.stringify(value, null, 2).replace(/[<>&\u2028\u2029]/g, (character) => SCRIPT_JSON_ESCAPES[character]);
|
|
1
16
|
export function generateCareCompassIframeSnippet(config, options = {}) {
|
|
2
17
|
const src = options.src ?? "/carecompass/carecompass-embed.html";
|
|
3
18
|
const title = `${config.branding.displayName} CareCompass`;
|
|
19
|
+
// The iframe form is intentionally static so it can be pasted into CMS pages
|
|
20
|
+
// or server-rendered apps without needing a bundler.
|
|
4
21
|
return `<iframe
|
|
5
|
-
title="${title
|
|
6
|
-
src="${src
|
|
22
|
+
title="${escapeHtmlAttribute(title)}"
|
|
23
|
+
src="${escapeHtmlAttribute(src)}"
|
|
7
24
|
style="width: 100%; min-height: 640px; border: 0; border-radius: ${config.theme.borderRadius};"
|
|
8
25
|
loading="lazy"
|
|
9
26
|
></iframe>`;
|
|
@@ -13,12 +30,13 @@ export function generateCareCompassSnippet(config, options = {}) {
|
|
|
13
30
|
const moduleUrl = options.mode === "cdn"
|
|
14
31
|
? "https://esm.sh/@nexalink/carecompass-sdk"
|
|
15
32
|
: "@nexalink/carecompass-sdk";
|
|
33
|
+
// The config is embedded beside the mount call so the generated page is
|
|
34
|
+
// deployable as a single static asset.
|
|
16
35
|
return `<div id="${targetId}"></div>
|
|
17
36
|
<script type="module">
|
|
18
37
|
import { mountCareCompass } from "${moduleUrl}";
|
|
19
38
|
|
|
20
|
-
const config = ${
|
|
39
|
+
const config = ${serializeScriptJson(config)};
|
|
21
40
|
mountCareCompass(document.getElementById("${targetId}"), config);
|
|
22
41
|
</script>`;
|
|
23
42
|
}
|
|
24
|
-
//# sourceMappingURL=snippet.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { CareCompassConfig } from "./schema.js";
|
|
2
|
+
export declare const CARECOMPASS_TELEMETRY_EVENTS: {
|
|
3
|
+
readonly SDK_INITIALIZED: "carecompass_sdk_initialized";
|
|
4
|
+
readonly SDK_INITIALIZATION_FAILED: "carecompass_sdk_initialization_failed";
|
|
5
|
+
readonly SDK_CONFIGURATION_VALIDATED: "carecompass_sdk_configuration_validated";
|
|
6
|
+
readonly SDK_CONFIGURATION_FAILED: "carecompass_sdk_configuration_failed";
|
|
7
|
+
readonly COMPONENT_RENDERED: "carecompass_component_rendered";
|
|
8
|
+
readonly COMPONENT_OPENED: "carecompass_component_opened";
|
|
9
|
+
readonly COMPONENT_CLOSED: "carecompass_component_closed";
|
|
10
|
+
readonly COMPONENT_ERROR: "carecompass_component_error";
|
|
11
|
+
readonly PROMPT_SUBMITTED: "carecompass_prompt_submitted";
|
|
12
|
+
readonly INFERENCE_SUCCEEDED: "carecompass_inference_succeeded";
|
|
13
|
+
readonly INFERENCE_FAILED: "carecompass_inference_failed";
|
|
14
|
+
readonly INFERENCE_RATE_LIMITED: "carecompass_inference_rate_limited";
|
|
15
|
+
readonly THEME_LOADED: "carecompass_theme_loaded";
|
|
16
|
+
readonly PROMPT_CONFIGURATION_LOADED: "carecompass_prompt_configuration_loaded";
|
|
17
|
+
readonly PROMPT_CONFIGURATION_FAILED: "carecompass_prompt_configuration_failed";
|
|
18
|
+
readonly CLI_STARTED: "carecompass_cli_started";
|
|
19
|
+
readonly CLI_LOGIN_STARTED: "carecompass_cli_login_started";
|
|
20
|
+
readonly CLI_LOGIN_SUCCEEDED: "carecompass_cli_login_succeeded";
|
|
21
|
+
readonly CLI_LOGIN_FAILED: "carecompass_cli_login_failed";
|
|
22
|
+
readonly CLI_INIT_STARTED: "carecompass_cli_init_started";
|
|
23
|
+
readonly CLI_INIT_FAILED: "carecompass_cli_init_failed";
|
|
24
|
+
readonly CLI_SETUP_KIT_GENERATED: "carecompass_cli_setup_kit_generated";
|
|
25
|
+
readonly CLI_VALIDATION_SUCCEEDED: "carecompass_cli_validation_succeeded";
|
|
26
|
+
readonly CLI_VALIDATION_FAILED: "carecompass_cli_validation_failed";
|
|
27
|
+
readonly CLI_SNIPPET_GENERATED: "carecompass_cli_snippet_generated";
|
|
28
|
+
readonly CLI_DOCTOR_COMPLETED: "carecompass_cli_doctor_completed";
|
|
29
|
+
readonly CLI_COMMAND_FAILED: "carecompass_cli_command_failed";
|
|
30
|
+
};
|
|
31
|
+
export type CareCompassTelemetryEvent = (typeof CARECOMPASS_TELEMETRY_EVENTS)[keyof typeof CARECOMPASS_TELEMETRY_EVENTS];
|
|
32
|
+
export declare const CARECOMPASS_TELEMETRY_ERROR_CODES: {
|
|
33
|
+
readonly API_ERROR: "api_error";
|
|
34
|
+
readonly AUTHENTICATION_FAILED: "authentication_failed";
|
|
35
|
+
readonly BACKEND_UNAVAILABLE: "backend_unavailable";
|
|
36
|
+
readonly CONFIGURATION_INVALID: "configuration_invalid";
|
|
37
|
+
readonly INFERENCE_FAILED: "inference_failed";
|
|
38
|
+
readonly INFERENCE_TIMEOUT: "inference_timeout";
|
|
39
|
+
readonly MOUNT_ELEMENT_MISSING: "mount_element_missing";
|
|
40
|
+
readonly NETWORK_ERROR: "network_error";
|
|
41
|
+
readonly RATE_LIMITED: "rate_limited";
|
|
42
|
+
readonly RENDER_FAILED: "render_failed";
|
|
43
|
+
readonly CLI_COMMAND_FAILED: "cli_command_failed";
|
|
44
|
+
};
|
|
45
|
+
export type CareCompassTelemetryErrorCode = (typeof CARECOMPASS_TELEMETRY_ERROR_CODES)[keyof typeof CARECOMPASS_TELEMETRY_ERROR_CODES];
|
|
46
|
+
export type CareCompassTelemetryFailureCategory = "authentication" | "configuration" | "inference" | "network" | "rate_limit" | "rendering" | "timeout" | "unknown";
|
|
47
|
+
export type CareCompassTelemetryDurationBucket = "under_250_ms" | "250_to_999_ms" | "1_to_4_999_ms" | "5_to_29_999_ms" | "30_s_or_more";
|
|
48
|
+
export type CareCompassTelemetryCaptureOptions = {
|
|
49
|
+
durationMs?: number;
|
|
50
|
+
errorCode?: CareCompassTelemetryErrorCode;
|
|
51
|
+
failureCategory?: CareCompassTelemetryFailureCategory;
|
|
52
|
+
};
|
|
53
|
+
type Fetcher = typeof fetch;
|
|
54
|
+
export type CareCompassTelemetry = {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
capture: (event: CareCompassTelemetryEvent, options?: CareCompassTelemetryCaptureOptions) => void;
|
|
57
|
+
captureAsync: (event: CareCompassTelemetryEvent, options?: CareCompassTelemetryCaptureOptions) => Promise<void>;
|
|
58
|
+
};
|
|
59
|
+
export declare const careCompassTelemetryDurationBucket: (durationMs: number) => CareCompassTelemetryDurationBucket;
|
|
60
|
+
export declare function createCareCompassTelemetry(config: Pick<CareCompassConfig, "environment" | "payerId">, options?: {
|
|
61
|
+
fetcher?: Fetcher;
|
|
62
|
+
now?: () => Date;
|
|
63
|
+
}): CareCompassTelemetry;
|
|
64
|
+
export declare function createCareCompassCliTelemetry(sessionId: string, context?: {
|
|
65
|
+
environment?: CareCompassConfig["environment"];
|
|
66
|
+
payerId?: string;
|
|
67
|
+
}, options?: {
|
|
68
|
+
fetcher?: Fetcher;
|
|
69
|
+
now?: () => Date;
|
|
70
|
+
}): CareCompassTelemetry;
|
|
71
|
+
export {};
|