@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
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { careCompassApiBaseUrlSchema, createDefaultCareCompassConfig, validateCareCompassConfig, } from "./schema.js";
|
|
@@ -32,13 +32,26 @@ planning, emergency triage, legal advice, and coverage guarantees. When a member
|
|
|
32
32
|
needs clinical, emergency, plan-specific, or account-specific help that requires
|
|
33
33
|
a human, route them to the customer's approved support resources.`;
|
|
34
34
|
const CLI_COMMAND_ALLOWLIST = new Set([
|
|
35
|
+
"auth",
|
|
36
|
+
"completion",
|
|
37
|
+
"config",
|
|
38
|
+
"configure",
|
|
35
39
|
"doctor",
|
|
40
|
+
"embed",
|
|
41
|
+
"health",
|
|
36
42
|
"help",
|
|
37
43
|
"init",
|
|
38
44
|
"login",
|
|
45
|
+
"logout",
|
|
46
|
+
"query",
|
|
47
|
+
"setup",
|
|
39
48
|
"snippet",
|
|
40
49
|
"validate",
|
|
50
|
+
"version",
|
|
41
51
|
]);
|
|
52
|
+
const UNAUTHENTICATED_AUTH_ATTEMPT_LIMIT = 2;
|
|
53
|
+
const UNAUTHENTICATED_AUTH_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000;
|
|
54
|
+
const UNAUTHENTICATED_AUTH_RATE_LIMIT_FILE = "auth-rate-limit.json";
|
|
42
55
|
class CliTelemetryError extends Error {
|
|
43
56
|
constructor(message, telemetry) {
|
|
44
57
|
super(message);
|
|
@@ -47,6 +60,8 @@ class CliTelemetryError extends Error {
|
|
|
47
60
|
}
|
|
48
61
|
const sanitizeCommandName = (command) => {
|
|
49
62
|
const normalized = command === "--help" || command === "-h" ? "help" : command;
|
|
63
|
+
if (command === "--version" || command === "-v")
|
|
64
|
+
return "version";
|
|
50
65
|
return CLI_COMMAND_ALLOWLIST.has(normalized) ? normalized : "unknown_command";
|
|
51
66
|
};
|
|
52
67
|
const authTelemetryForSource = (source) => {
|
|
@@ -58,6 +73,96 @@ const authTelemetryForSource = (source) => {
|
|
|
58
73
|
}
|
|
59
74
|
return { authMethod: "api_key", keySource: "cli_flag" };
|
|
60
75
|
};
|
|
76
|
+
const unauthenticatedRateLimitPath = (io) => io.env.CARECOMPASS_AUTH_RATE_LIMIT_PATH ??
|
|
77
|
+
join(dirname(resolveCredentialsPath(io.env)), UNAUTHENTICATED_AUTH_RATE_LIMIT_FILE);
|
|
78
|
+
const unauthenticatedRateLimitKey = (io) => {
|
|
79
|
+
const keyMaterial = io.env.CARECOMPASS_AUTH_RATE_LIMIT_KEY
|
|
80
|
+
? `explicit:${io.env.CARECOMPASS_AUTH_RATE_LIMIT_KEY}`
|
|
81
|
+
: io.env.CARECOMPASS_CLIENT_IP
|
|
82
|
+
? `ip:${io.env.CARECOMPASS_CLIENT_IP}`
|
|
83
|
+
: `credentials:${resolveCredentialsPath(io.env)}`;
|
|
84
|
+
return createHash("sha256")
|
|
85
|
+
.update("carecompass unauthenticated auth rate limit")
|
|
86
|
+
.update(keyMaterial)
|
|
87
|
+
.digest("hex");
|
|
88
|
+
};
|
|
89
|
+
const emptyUnauthenticatedAuthLedger = () => ({
|
|
90
|
+
version: 1,
|
|
91
|
+
entries: {},
|
|
92
|
+
});
|
|
93
|
+
const readUnauthenticatedAuthLedger = (path) => {
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
96
|
+
if (parsed.version !== 1 || typeof parsed.entries !== "object") {
|
|
97
|
+
return emptyUnauthenticatedAuthLedger();
|
|
98
|
+
}
|
|
99
|
+
const entries = {};
|
|
100
|
+
for (const [key, value] of Object.entries(parsed.entries)) {
|
|
101
|
+
if (typeof value === "object" &&
|
|
102
|
+
value !== null &&
|
|
103
|
+
typeof value.failures ===
|
|
104
|
+
"number" &&
|
|
105
|
+
typeof value.firstAttemptAt ===
|
|
106
|
+
"number" &&
|
|
107
|
+
typeof value.lastAttemptAt ===
|
|
108
|
+
"number") {
|
|
109
|
+
entries[key] = value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { version: 1, entries };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return emptyUnauthenticatedAuthLedger();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const writeUnauthenticatedAuthLedger = (path, ledger) => {
|
|
119
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
120
|
+
writeFileSync(path, `${JSON.stringify(ledger, null, 2)}\n`, { mode: 0o600 });
|
|
121
|
+
try {
|
|
122
|
+
chmodSync(path, 0o600);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Some filesystems do not support chmod; the ledger contains only hashed
|
|
126
|
+
// limiter keys and counters.
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const pruneUnauthenticatedAuthLedger = (ledger, now) => {
|
|
130
|
+
for (const [key, entry] of Object.entries(ledger.entries)) {
|
|
131
|
+
if (now - entry.firstAttemptAt >
|
|
132
|
+
UNAUTHENTICATED_AUTH_RATE_LIMIT_WINDOW_MS) {
|
|
133
|
+
delete ledger.entries[key];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const recordUnauthenticatedAuthFailure = (io) => {
|
|
138
|
+
const path = unauthenticatedRateLimitPath(io);
|
|
139
|
+
const key = unauthenticatedRateLimitKey(io);
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const ledger = readUnauthenticatedAuthLedger(path);
|
|
142
|
+
pruneUnauthenticatedAuthLedger(ledger, now);
|
|
143
|
+
const existing = ledger.entries[key];
|
|
144
|
+
const entry = existing &&
|
|
145
|
+
now - existing.firstAttemptAt <= UNAUTHENTICATED_AUTH_RATE_LIMIT_WINDOW_MS
|
|
146
|
+
? existing
|
|
147
|
+
: { failures: 0, firstAttemptAt: now, lastAttemptAt: now };
|
|
148
|
+
entry.failures += 1;
|
|
149
|
+
entry.lastAttemptAt = now;
|
|
150
|
+
ledger.entries[key] = entry;
|
|
151
|
+
writeUnauthenticatedAuthLedger(path, ledger);
|
|
152
|
+
const failures = entry.failures;
|
|
153
|
+
if (failures < UNAUTHENTICATED_AUTH_ATTEMPT_LIMIT)
|
|
154
|
+
return null;
|
|
155
|
+
return new CliTelemetryError("Too many failed authentication attempts. Wait before trying again or verify the API key in the Nexalink console.", {
|
|
156
|
+
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.RATE_LIMIT_EXCEEDED,
|
|
157
|
+
failureCategory: "rate_limit_error",
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
const resetUnauthenticatedAuthFailures = (io) => {
|
|
161
|
+
const path = unauthenticatedRateLimitPath(io);
|
|
162
|
+
const ledger = readUnauthenticatedAuthLedger(path);
|
|
163
|
+
delete ledger.entries[unauthenticatedRateLimitKey(io)];
|
|
164
|
+
writeUnauthenticatedAuthLedger(path, ledger);
|
|
165
|
+
};
|
|
61
166
|
const normalizeBaaStatus = (value) => {
|
|
62
167
|
if (value === "signed" || value === "pending" || value === "not_required") {
|
|
63
168
|
return value;
|
|
@@ -526,14 +631,11 @@ async function resolveInitApiKey(flags, io, apiBaseUrl, onAuthChecked = async ()
|
|
|
526
631
|
failureCategory: validation.failureCategory,
|
|
527
632
|
});
|
|
528
633
|
io.stdout.write(`${validation.message}\n`);
|
|
634
|
+
const rateLimitError = recordUnauthenticatedAuthFailure(io);
|
|
635
|
+
if (rateLimitError)
|
|
636
|
+
throw rateLimitError;
|
|
529
637
|
continue;
|
|
530
638
|
}
|
|
531
|
-
await onAuthChecked({
|
|
532
|
-
authResult: "success",
|
|
533
|
-
baaStatus: validation.baaStatus,
|
|
534
|
-
keySource: authTelemetry.keySource,
|
|
535
|
-
failureCategory: null,
|
|
536
|
-
});
|
|
537
639
|
if (validation.accountType !== accountType) {
|
|
538
640
|
if (validation.accountType === "enterprise") {
|
|
539
641
|
io.stdout.write(`This API key belongs to an enterprise account. ${enterpriseUnavailableMessage(urls.developer)}\n`);
|
|
@@ -543,12 +645,14 @@ async function resolveInitApiKey(flags, io, apiBaseUrl, onAuthChecked = async ()
|
|
|
543
645
|
continue;
|
|
544
646
|
}
|
|
545
647
|
writeStoredApiKey(normalizedApiKey, credentialsPath);
|
|
648
|
+
resetUnauthenticatedAuthFailures(io);
|
|
546
649
|
io.stdout.write(`Stored CareCompass credentials at ${credentialsPath}\n`);
|
|
547
650
|
io.stdout.write("Sign-in successful.\n");
|
|
548
651
|
return {
|
|
549
652
|
apiKey: normalizedApiKey,
|
|
550
653
|
authMethod: authTelemetry.authMethod,
|
|
551
654
|
baaStatus: validation.baaStatus,
|
|
655
|
+
keySource: authTelemetry.keySource,
|
|
552
656
|
setup: validation.setup,
|
|
553
657
|
};
|
|
554
658
|
}
|
|
@@ -790,12 +894,14 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
790
894
|
const sessionStartedAt = Date.now();
|
|
791
895
|
const rawCommand = argv[0] ?? "help";
|
|
792
896
|
const telemetryCommandName = sanitizeCommandName(rawCommand);
|
|
793
|
-
|
|
897
|
+
let commandStartedAt = sessionStartedAt;
|
|
898
|
+
let commandStartedCaptured = false;
|
|
794
899
|
let commandsAttempted = 0;
|
|
795
900
|
let commandsSucceeded = 0;
|
|
796
901
|
let commandsFailed = 0;
|
|
797
902
|
let activeCommand = rawCommand;
|
|
798
903
|
let authCheckedCaptured = false;
|
|
904
|
+
let initStartedCaptured = false;
|
|
799
905
|
let sessionEndedCaptured = false;
|
|
800
906
|
const captureAuthChecked = async (telemetry) => {
|
|
801
907
|
if (authCheckedCaptured)
|
|
@@ -811,7 +917,19 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
811
917
|
keySource: telemetry.keySource,
|
|
812
918
|
});
|
|
813
919
|
};
|
|
920
|
+
const captureCommandStarted = async () => {
|
|
921
|
+
if (commandStartedCaptured)
|
|
922
|
+
return;
|
|
923
|
+
commandStartedCaptured = true;
|
|
924
|
+
commandStartedAt = Date.now();
|
|
925
|
+
commandsAttempted += 1;
|
|
926
|
+
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_COMMAND_STARTED, {
|
|
927
|
+
commandName: telemetryCommandName,
|
|
928
|
+
commandArgsCount: Math.max(argv.length - 1, 0),
|
|
929
|
+
});
|
|
930
|
+
};
|
|
814
931
|
const captureCommandCompleted = async () => {
|
|
932
|
+
await captureCommandStarted();
|
|
815
933
|
commandsSucceeded += 1;
|
|
816
934
|
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_COMMAND_COMPLETED, {
|
|
817
935
|
commandName: telemetryCommandName,
|
|
@@ -820,6 +938,34 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
820
938
|
modelId: null,
|
|
821
939
|
});
|
|
822
940
|
};
|
|
941
|
+
const authenticateConfigCommand = async (flags) => {
|
|
942
|
+
let credentials;
|
|
943
|
+
try {
|
|
944
|
+
credentials = requireApiKey(flags, io);
|
|
945
|
+
}
|
|
946
|
+
catch (error) {
|
|
947
|
+
const failure = classifyCliFailure(error);
|
|
948
|
+
await captureAuthChecked({
|
|
949
|
+
authResult: "failure",
|
|
950
|
+
baaStatus: "signed",
|
|
951
|
+
keySource: "cli_flag",
|
|
952
|
+
errorCode: failure.errorCode,
|
|
953
|
+
failureCategory: failure.failureCategory,
|
|
954
|
+
});
|
|
955
|
+
const rateLimitError = recordUnauthenticatedAuthFailure(io);
|
|
956
|
+
throw rateLimitError ?? error;
|
|
957
|
+
}
|
|
958
|
+
const config = readConfig(flagString(flags, "config") ?? DEFAULT_CONFIG_PATH);
|
|
959
|
+
resetUnauthenticatedAuthFailures(io);
|
|
960
|
+
cliTelemetry = createCareCompassCliTelemetry(cliSessionId, config);
|
|
961
|
+
await captureAuthChecked({
|
|
962
|
+
authResult: "success",
|
|
963
|
+
baaStatus: "not_required",
|
|
964
|
+
keySource: authTelemetryForSource(credentials.source).keySource,
|
|
965
|
+
failureCategory: null,
|
|
966
|
+
});
|
|
967
|
+
return { config, credentials };
|
|
968
|
+
};
|
|
823
969
|
const captureSessionEnded = async (exitReason) => {
|
|
824
970
|
if (sessionEndedCaptured)
|
|
825
971
|
return;
|
|
@@ -833,11 +979,6 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
833
979
|
});
|
|
834
980
|
};
|
|
835
981
|
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_STARTED);
|
|
836
|
-
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_COMMAND_STARTED, {
|
|
837
|
-
commandName: telemetryCommandName,
|
|
838
|
-
commandArgsCount: Math.max(argv.length - 1, 0),
|
|
839
|
-
});
|
|
840
|
-
commandsAttempted += 1;
|
|
841
982
|
try {
|
|
842
983
|
const { command, flags, positionals } = parseArgs(argv);
|
|
843
984
|
activeCommand = command;
|
|
@@ -847,6 +988,7 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
847
988
|
return 0;
|
|
848
989
|
}
|
|
849
990
|
if (command === "login") {
|
|
991
|
+
await captureCommandStarted();
|
|
850
992
|
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_LOGIN_STARTED);
|
|
851
993
|
const apiKey = flagString(flags, "api-key") ?? positionals[0];
|
|
852
994
|
if (!apiKey) {
|
|
@@ -867,12 +1009,22 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
867
1009
|
return 0;
|
|
868
1010
|
}
|
|
869
1011
|
if (command === "init") {
|
|
870
|
-
const initStartedAt = Date.now();
|
|
871
|
-
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_INIT_STARTED);
|
|
872
1012
|
rejectLegacyBaaInitFlags(flags, io);
|
|
873
1013
|
const outputDir = flagString(flags, "output-dir") ?? DEFAULT_OUTPUT_DIR;
|
|
874
1014
|
const apiBaseUrl = resolveTrustedApiBaseUrl(flagString(flags, "api-base-url") ?? DEFAULT_API_BASE_URL);
|
|
875
1015
|
const initAuth = await resolveInitApiKey(flags, io, apiBaseUrl, captureAuthChecked);
|
|
1016
|
+
const config = buildInitConfig(flags, initAuth.setup);
|
|
1017
|
+
cliTelemetry = createCareCompassCliTelemetry(cliSessionId, config);
|
|
1018
|
+
await captureAuthChecked({
|
|
1019
|
+
authResult: "success",
|
|
1020
|
+
baaStatus: initAuth.baaStatus,
|
|
1021
|
+
keySource: initAuth.keySource,
|
|
1022
|
+
failureCategory: null,
|
|
1023
|
+
});
|
|
1024
|
+
await captureCommandStarted();
|
|
1025
|
+
const initStartedAt = Date.now();
|
|
1026
|
+
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_INIT_STARTED);
|
|
1027
|
+
initStartedCaptured = true;
|
|
876
1028
|
const output = flagString(flags, "output") ??
|
|
877
1029
|
join(outputDir, "carecompass.config.json");
|
|
878
1030
|
const snippetOutput = flagString(flags, "snippet-output") ??
|
|
@@ -895,8 +1047,6 @@ export async function runCareCompassCli(argv = process.argv.slice(2), io = {
|
|
|
895
1047
|
}
|
|
896
1048
|
}
|
|
897
1049
|
mkdirSync(outputDir, { recursive: true });
|
|
898
|
-
const config = buildInitConfig(flags, initAuth.setup);
|
|
899
|
-
cliTelemetry = createCareCompassCliTelemetry(cliSessionId, config);
|
|
900
1050
|
writeFileSync(output, `${JSON.stringify(config, null, 2)}\n`);
|
|
901
1051
|
writeFileSync(promptOutput, `${DEFAULT_SYSTEM_PROMPT}\n`);
|
|
902
1052
|
const resolvedConfig = readConfig(output);
|
|
@@ -946,9 +1096,8 @@ Next command:
|
|
|
946
1096
|
}
|
|
947
1097
|
if (command === "validate") {
|
|
948
1098
|
try {
|
|
949
|
-
const credentials =
|
|
950
|
-
|
|
951
|
-
cliTelemetry = createCareCompassCliTelemetry(cliSessionId, config);
|
|
1099
|
+
const { config, credentials } = await authenticateConfigCommand(flags);
|
|
1100
|
+
await captureCommandStarted();
|
|
952
1101
|
if (flags["local-only"] !== true) {
|
|
953
1102
|
await assertHostedConfig(config, credentials.apiKey);
|
|
954
1103
|
}
|
|
@@ -959,17 +1108,18 @@ Next command:
|
|
|
959
1108
|
}
|
|
960
1109
|
catch (error) {
|
|
961
1110
|
const failure = classifyCliFailure(error);
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1111
|
+
if (commandStartedCaptured) {
|
|
1112
|
+
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_VALIDATION_FAILED, {
|
|
1113
|
+
errorCode: failure.errorCode,
|
|
1114
|
+
failureCategory: failure.failureCategory,
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
966
1117
|
throw error;
|
|
967
1118
|
}
|
|
968
1119
|
}
|
|
969
1120
|
if (command === "snippet") {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
cliTelemetry = createCareCompassCliTelemetry(cliSessionId, config);
|
|
1121
|
+
const { config } = await authenticateConfigCommand(flags);
|
|
1122
|
+
await captureCommandStarted();
|
|
973
1123
|
const snippet = generateCareCompassSnippet(runtimeConfig(config), {
|
|
974
1124
|
mode: snippetMode(flags),
|
|
975
1125
|
});
|
|
@@ -987,9 +1137,8 @@ Next command:
|
|
|
987
1137
|
return 0;
|
|
988
1138
|
}
|
|
989
1139
|
if (command === "doctor") {
|
|
990
|
-
const credentials =
|
|
991
|
-
|
|
992
|
-
cliTelemetry = createCareCompassCliTelemetry(cliSessionId, config);
|
|
1140
|
+
const { config, credentials } = await authenticateConfigCommand(flags);
|
|
1141
|
+
await captureCommandStarted();
|
|
993
1142
|
let hostedStatus = "not checked";
|
|
994
1143
|
try {
|
|
995
1144
|
await assertHostedConfig(config, credentials.apiKey);
|
|
@@ -1013,12 +1162,16 @@ Status: ready for staging verification
|
|
|
1013
1162
|
await captureCommandCompleted();
|
|
1014
1163
|
return 0;
|
|
1015
1164
|
}
|
|
1165
|
+
await captureCommandStarted();
|
|
1016
1166
|
throw new Error(`Unknown command: ${command}`);
|
|
1017
1167
|
}
|
|
1018
1168
|
catch (error) {
|
|
1019
1169
|
commandsFailed += 1;
|
|
1170
|
+
if (!commandStartedCaptured && commandsAttempted === 0) {
|
|
1171
|
+
commandsAttempted = 1;
|
|
1172
|
+
}
|
|
1020
1173
|
const failure = classifyCliFailure(error);
|
|
1021
|
-
if (activeCommand === "init") {
|
|
1174
|
+
if (activeCommand === "init" && initStartedCaptured) {
|
|
1022
1175
|
await cliTelemetry.captureAsync(CARECOMPASS_TELEMETRY_EVENTS.CLI_INIT_FAILED, {
|
|
1023
1176
|
errorCode: failure.errorCode,
|
|
1024
1177
|
failureCategory: failure.failureCategory,
|
package/dist/client.d.ts
CHANGED
|
@@ -1,21 +1,51 @@
|
|
|
1
1
|
import { type CareCompassConfig } from "./schema.js";
|
|
2
2
|
export type CareCompassAskRequest = {
|
|
3
3
|
prompt: string;
|
|
4
|
+
/** Stable client-generated id used to make a submit/retry idempotent. */
|
|
5
|
+
requestId?: string;
|
|
6
|
+
workflow?: CareCompassWorkflowId;
|
|
4
7
|
sessionId?: string;
|
|
5
8
|
userContext?: Record<string, unknown>;
|
|
6
9
|
};
|
|
10
|
+
export type CareCompassWorkflowId = "rcm-coding-assistance";
|
|
7
11
|
export type CareCompassAskResponse = {
|
|
8
12
|
jobId: string;
|
|
9
13
|
};
|
|
14
|
+
export type CareCompassRetrievalReference = {
|
|
15
|
+
referenceId: string;
|
|
16
|
+
documentId: string;
|
|
17
|
+
title: string;
|
|
18
|
+
version: string;
|
|
19
|
+
excerpt: string;
|
|
20
|
+
};
|
|
21
|
+
export type CareCompassRetrievalMetadata = {
|
|
22
|
+
corpusId: string;
|
|
23
|
+
corpusVersion: string;
|
|
24
|
+
outcome: string;
|
|
25
|
+
latencyMs: number;
|
|
26
|
+
references: CareCompassRetrievalReference[];
|
|
27
|
+
};
|
|
10
28
|
export type CareCompassStatusResponse = {
|
|
11
29
|
status: "queued" | "running" | "complete" | "failed";
|
|
12
30
|
result?: string;
|
|
31
|
+
retrieval?: CareCompassRetrievalMetadata;
|
|
13
32
|
};
|
|
14
33
|
type Fetcher = typeof fetch;
|
|
15
|
-
export
|
|
34
|
+
export type CareCompassRuntimeTokenRequest = {
|
|
35
|
+
payerId: string;
|
|
36
|
+
environment: CareCompassConfig["environment"];
|
|
37
|
+
embedKey: string;
|
|
38
|
+
workflow?: CareCompassWorkflowId;
|
|
39
|
+
jobId?: string;
|
|
40
|
+
};
|
|
41
|
+
/** Obtains a short-lived production token from the integrator's BFF. */
|
|
42
|
+
export type CareCompassRuntimeTokenProvider = (request: CareCompassRuntimeTokenRequest) => Promise<string> | string;
|
|
43
|
+
export type CareCompassClientOptions = {
|
|
16
44
|
fetcher?: Fetcher;
|
|
17
45
|
now?: () => number;
|
|
18
|
-
|
|
46
|
+
runtimeTokenProvider?: CareCompassRuntimeTokenProvider;
|
|
47
|
+
};
|
|
48
|
+
export declare function createCareCompassClient(inputConfig: CareCompassConfig, options?: CareCompassClientOptions): {
|
|
19
49
|
validateConfig: () => Promise<{
|
|
20
50
|
ok: true;
|
|
21
51
|
}>;
|
package/dist/client.js
CHANGED
|
@@ -105,6 +105,30 @@ export function createCareCompassClient(inputConfig, options = {}) {
|
|
|
105
105
|
"x-carecompass-environment": config.environment,
|
|
106
106
|
};
|
|
107
107
|
const jobStartedAt = new Map();
|
|
108
|
+
const jobWorkflows = new Map();
|
|
109
|
+
async function requestHeaders(request = {}) {
|
|
110
|
+
if (config.environment !== "production")
|
|
111
|
+
return headers;
|
|
112
|
+
if (!options.runtimeTokenProvider) {
|
|
113
|
+
throw new CareCompassClientError("Production CareCompass embeds require a runtimeTokenProvider backed by your server.", CARECOMPASS_TELEMETRY_ERROR_CODES.AUTHENTICATION_FAILED, "authentication");
|
|
114
|
+
}
|
|
115
|
+
let token;
|
|
116
|
+
try {
|
|
117
|
+
token = await options.runtimeTokenProvider({
|
|
118
|
+
payerId: config.payerId,
|
|
119
|
+
environment: config.environment,
|
|
120
|
+
embedKey: config.embedKey,
|
|
121
|
+
...request,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
throw new CareCompassClientError("CareCompass runtime authorization could not be obtained.", CARECOMPASS_TELEMETRY_ERROR_CODES.AUTHENTICATION_FAILED, "authentication");
|
|
126
|
+
}
|
|
127
|
+
if (typeof token !== "string" || !token.trim()) {
|
|
128
|
+
throw new CareCompassClientError("CareCompass runtime authorization could not be obtained.", CARECOMPASS_TELEMETRY_ERROR_CODES.AUTHENTICATION_FAILED, "authentication");
|
|
129
|
+
}
|
|
130
|
+
return { ...headers, "x-carecompass-runtime-token": token.trim() };
|
|
131
|
+
}
|
|
108
132
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.SDK_INITIALIZED);
|
|
109
133
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.THEME_LOADED);
|
|
110
134
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.PROMPT_CONFIGURATION_LOADED);
|
|
@@ -137,7 +161,7 @@ export function createCareCompassClient(inputConfig, options = {}) {
|
|
|
137
161
|
try {
|
|
138
162
|
const response = await send(joinUrl(config.apiBaseUrl, "/api/carecompass/embed/config/validate"), {
|
|
139
163
|
method: "POST",
|
|
140
|
-
headers,
|
|
164
|
+
headers: await requestHeaders(),
|
|
141
165
|
body: JSON.stringify({ config }),
|
|
142
166
|
});
|
|
143
167
|
const result = await parseJson(response);
|
|
@@ -155,18 +179,25 @@ export function createCareCompassClient(inputConfig, options = {}) {
|
|
|
155
179
|
try {
|
|
156
180
|
const response = await send(joinUrl(config.apiBaseUrl, "/api/carecompass/embed/ask"), {
|
|
157
181
|
method: "POST",
|
|
158
|
-
headers,
|
|
182
|
+
headers: await requestHeaders({ workflow: request.workflow }),
|
|
159
183
|
body: JSON.stringify({
|
|
160
184
|
prompt: request.prompt,
|
|
185
|
+
requestId: request.requestId,
|
|
186
|
+
workflow: request.workflow,
|
|
161
187
|
sessionId: request.sessionId,
|
|
162
188
|
userContext: request.userContext,
|
|
163
189
|
promptConfig: config.prompt,
|
|
190
|
+
// Public routing preferences; the hosted runtime validates model
|
|
191
|
+
// availability and may ignore disabled feature flags.
|
|
192
|
+
model: config.model,
|
|
193
|
+
featureFlags: config.featureFlags,
|
|
164
194
|
}),
|
|
165
195
|
});
|
|
166
196
|
const result = await parseJson(response);
|
|
167
197
|
// Status polling is asynchronous, so retain the submit timestamp until
|
|
168
198
|
// the final job state lets telemetry report end-to-end latency.
|
|
169
199
|
jobStartedAt.set(result.jobId, startedAt);
|
|
200
|
+
jobWorkflows.set(result.jobId, request.workflow);
|
|
170
201
|
return result;
|
|
171
202
|
}
|
|
172
203
|
catch (error) {
|
|
@@ -177,16 +208,23 @@ export function createCareCompassClient(inputConfig, options = {}) {
|
|
|
177
208
|
status: async (jobId) => {
|
|
178
209
|
const startedAt = jobStartedAt.get(jobId) ?? now();
|
|
179
210
|
try {
|
|
180
|
-
const response = await send(joinUrl(config.apiBaseUrl, `/api/carecompass/embed/status/${encodeURIComponent(jobId)}`), {
|
|
211
|
+
const response = await send(joinUrl(config.apiBaseUrl, `/api/carecompass/embed/status/${encodeURIComponent(jobId)}`), {
|
|
212
|
+
headers: await requestHeaders({
|
|
213
|
+
jobId,
|
|
214
|
+
workflow: jobWorkflows.get(jobId),
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
181
217
|
const result = await parseJson(response);
|
|
182
218
|
if (result.status === "complete") {
|
|
183
219
|
jobStartedAt.delete(jobId);
|
|
220
|
+
jobWorkflows.delete(jobId);
|
|
184
221
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_SUCCEEDED, {
|
|
185
222
|
durationMs: now() - startedAt,
|
|
186
223
|
});
|
|
187
224
|
}
|
|
188
225
|
else if (result.status === "failed") {
|
|
189
226
|
jobStartedAt.delete(jobId);
|
|
227
|
+
jobWorkflows.delete(jobId);
|
|
190
228
|
telemetry.capture(CARECOMPASS_TELEMETRY_EVENTS.INFERENCE_FAILED, {
|
|
191
229
|
durationMs: now() - startedAt,
|
|
192
230
|
errorCode: CARECOMPASS_TELEMETRY_ERROR_CODES.INFERENCE_FAILED,
|
|
@@ -197,6 +235,7 @@ export function createCareCompassClient(inputConfig, options = {}) {
|
|
|
197
235
|
}
|
|
198
236
|
catch (error) {
|
|
199
237
|
jobStartedAt.delete(jobId);
|
|
238
|
+
jobWorkflows.delete(jobId);
|
|
200
239
|
captureInferenceFailure(telemetry, error, now() - startedAt);
|
|
201
240
|
throw error;
|
|
202
241
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CareCompassComplianceConfig } from "./schema.js";
|
|
2
|
+
/** The identifier categories detected by the SDK's local PHI preflight. */
|
|
3
|
+
export type CareCompassPhiCategory = "date_of_birth" | "email" | "medical_record_number" | "member_id" | "phone" | "ssn";
|
|
4
|
+
export type CareCompassPhiMatch = {
|
|
5
|
+
category: CareCompassPhiCategory;
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
};
|
|
9
|
+
export type CareCompassPhiSource = "message" | "response" | "context";
|
|
10
|
+
export type CareCompassPhiDetectionContext = {
|
|
11
|
+
source: CareCompassPhiSource;
|
|
12
|
+
};
|
|
13
|
+
export type CareCompassPhiDetector = (value: string, context: CareCompassPhiDetectionContext) => CareCompassPhiMatch[];
|
|
14
|
+
export type CareCompassPhiRedactor = (value: string, matches: CareCompassPhiMatch[], replacement: string) => string;
|
|
15
|
+
export type CareCompassPhiHooks = {
|
|
16
|
+
/** Replaces the built-in detector. The hook receives raw content; never emit it. */
|
|
17
|
+
detect?: CareCompassPhiDetector;
|
|
18
|
+
/** Replaces the built-in redactor. The hook receives raw content; never emit it. */
|
|
19
|
+
redact?: CareCompassPhiRedactor;
|
|
20
|
+
};
|
|
21
|
+
export type CareCompassComplianceRuntimeOptions = {
|
|
22
|
+
phi?: CareCompassPhiHooks;
|
|
23
|
+
};
|
|
24
|
+
export type CareCompassCompliancePreset = "standard" | "healthcare" | "strict";
|
|
25
|
+
export type CareCompassPhiAction = "allow" | "redact" | "block";
|
|
26
|
+
export type CareCompassCompliancePolicy = {
|
|
27
|
+
auditEnabled: boolean;
|
|
28
|
+
phiAction: CareCompassPhiAction;
|
|
29
|
+
preset: CareCompassCompliancePreset;
|
|
30
|
+
replacement: string;
|
|
31
|
+
};
|
|
32
|
+
export declare const CARECOMPASS_COMPLIANCE_PRESETS: Readonly<Record<CareCompassCompliancePreset, Omit<CareCompassCompliancePolicy, "preset">>>;
|
|
33
|
+
export type CareCompassAuditEventType = "message" | "tool_call" | "widget_action" | "error" | "safety_decision";
|
|
34
|
+
export type CareCompassAuditOutcome = "submitted" | "completed" | "failed" | "blocked" | "redacted" | "escalated" | "recorded";
|
|
35
|
+
export type CareCompassAuditSource = CareCompassPhiSource | "tool_call" | "widget_action";
|
|
36
|
+
export type CareCompassAuditEvent = {
|
|
37
|
+
id: string;
|
|
38
|
+
occurredAt: number;
|
|
39
|
+
outcome: CareCompassAuditOutcome;
|
|
40
|
+
phi: {
|
|
41
|
+
categories: CareCompassPhiCategory[];
|
|
42
|
+
count: number;
|
|
43
|
+
};
|
|
44
|
+
source?: CareCompassAuditSource;
|
|
45
|
+
type: CareCompassAuditEventType;
|
|
46
|
+
};
|
|
47
|
+
export type CareCompassSafetyDecision = "blocked" | "escalated" | "redacted" | "policy_sensitive";
|
|
48
|
+
export type CareCompassSafetyEvent = {
|
|
49
|
+
decision: CareCompassSafetyDecision;
|
|
50
|
+
occurredAt: number;
|
|
51
|
+
phi: {
|
|
52
|
+
categories: CareCompassPhiCategory[];
|
|
53
|
+
count: number;
|
|
54
|
+
};
|
|
55
|
+
source: CareCompassPhiSource | "tool_call" | "widget_action";
|
|
56
|
+
};
|
|
57
|
+
export type CareCompassAuditListener = (event: CareCompassAuditEvent) => void;
|
|
58
|
+
export type CareCompassSafetyListener = (event: CareCompassSafetyEvent) => void;
|
|
59
|
+
export declare const detectCareCompassPhi: CareCompassPhiDetector;
|
|
60
|
+
export declare const redactCareCompassPhi: CareCompassPhiRedactor;
|
|
61
|
+
export declare const resolveCareCompassCompliancePolicy: (config: CareCompassComplianceConfig | undefined) => CareCompassCompliancePolicy;
|
|
62
|
+
export declare const phiSummary: (matches: CareCompassPhiMatch[]) => {
|
|
63
|
+
categories: CareCompassPhiCategory[];
|
|
64
|
+
count: number;
|
|
65
|
+
};
|
|
66
|
+
export declare const sanitizeCareCompassValue: (value: unknown, source: CareCompassPhiSource, policy: CareCompassCompliancePolicy, hooks: CareCompassPhiHooks | undefined) => {
|
|
67
|
+
matches: CareCompassPhiMatch[];
|
|
68
|
+
value: unknown;
|
|
69
|
+
};
|