@junando/core 0.14.0 → 0.15.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/dist/index.d.ts +65 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +233 -94
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
6
6
|
import pino from "pino";
|
|
7
7
|
import { Readable, Writable } from "node:stream";
|
|
8
8
|
import { DeleteItemCommand, DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
|
|
9
|
+
import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
|
|
10
|
+
import { GoogleGenerativeAI, GoogleGenerativeAIAbortError } from "@google/generative-ai";
|
|
9
11
|
import * as Breaker from "opossum";
|
|
10
12
|
import { readFileSync } from "node:fs";
|
|
11
13
|
import { YAMLParseError, parse } from "yaml";
|
|
@@ -40,6 +42,7 @@ let LLMProviderType = /* @__PURE__ */ function(LLMProviderType) {
|
|
|
40
42
|
LLMProviderType["Claude"] = "claude";
|
|
41
43
|
LLMProviderType["OpenRouter"] = "openrouter";
|
|
42
44
|
LLMProviderType["Qwen"] = "qwen";
|
|
45
|
+
LLMProviderType["Bedrock"] = "bedrock";
|
|
43
46
|
return LLMProviderType;
|
|
44
47
|
}({});
|
|
45
48
|
const HTTP_TIMEOUT_MS = Object.freeze({
|
|
@@ -72,7 +75,8 @@ const LLM_FALLBACK_DEFAULTS = Object.freeze({
|
|
|
72
75
|
const LLM_MODELS = Object.freeze({
|
|
73
76
|
Gemini: "gemini-2.0-flash",
|
|
74
77
|
Claude: "claude-haiku-4-5",
|
|
75
|
-
OpenRouter: "qwen/qwen-2.5-72b-instruct"
|
|
78
|
+
OpenRouter: "qwen/qwen-2.5-72b-instruct",
|
|
79
|
+
Bedrock: "us.amazon.nova-lite-v1:0"
|
|
76
80
|
});
|
|
77
81
|
const SLACK_API_URL = "https://slack.com/api/chat.postMessage";
|
|
78
82
|
const ROLLBACK_ACTION_ID = "trigger_rollback";
|
|
@@ -850,7 +854,7 @@ var ProcessIncidentUseCase = class {
|
|
|
850
854
|
};
|
|
851
855
|
//#endregion
|
|
852
856
|
//#region src/infrastructure/dedup/redis-dedup.adapter.ts
|
|
853
|
-
const logger$
|
|
857
|
+
const logger$8 = createLogger();
|
|
854
858
|
var RedisDeduplicationStore = class {
|
|
855
859
|
redis;
|
|
856
860
|
keyPrefix = "junando:dedup:";
|
|
@@ -865,7 +869,7 @@ var RedisDeduplicationStore = class {
|
|
|
865
869
|
};
|
|
866
870
|
} catch (err) {
|
|
867
871
|
const message = err instanceof Error ? err.message : String(err);
|
|
868
|
-
logger$
|
|
872
|
+
logger$8.warn({
|
|
869
873
|
err,
|
|
870
874
|
fingerprint
|
|
871
875
|
}, "Redis dedup check failed, failing open");
|
|
@@ -905,7 +909,7 @@ var InMemoryDeduplicationStore = class {
|
|
|
905
909
|
};
|
|
906
910
|
//#endregion
|
|
907
911
|
//#region src/infrastructure/dedup/dynamodb-dedup.adapter.ts
|
|
908
|
-
const logger$
|
|
912
|
+
const logger$7 = createLogger();
|
|
909
913
|
const CONDITION_EXPRESSION = "attribute_not_exists(fingerprint) OR expiresAt < :now";
|
|
910
914
|
var DynamoDBDeduplicationStore = class {
|
|
911
915
|
tableName;
|
|
@@ -941,7 +945,7 @@ var DynamoDBDeduplicationStore = class {
|
|
|
941
945
|
ttlSeconds
|
|
942
946
|
};
|
|
943
947
|
const message = err instanceof Error ? err.message : String(err);
|
|
944
|
-
logger$
|
|
948
|
+
logger$7.warn({
|
|
945
949
|
err,
|
|
946
950
|
fingerprint
|
|
947
951
|
}, "DynamoDB dedup check failed, failing open");
|
|
@@ -992,30 +996,8 @@ var InMemoryIndexer = class {
|
|
|
992
996
|
}
|
|
993
997
|
};
|
|
994
998
|
//#endregion
|
|
995
|
-
//#region src/infrastructure/llm/
|
|
996
|
-
const logger$
|
|
997
|
-
/** Provider name reported by MockLLMProvider results. */
|
|
998
|
-
const MOCK_PROVIDER_NAME = "mock";
|
|
999
|
-
/**
|
|
1000
|
-
* Schema for OpenRouter API response validation.
|
|
1001
|
-
* Ensures type safety at the external boundary.
|
|
1002
|
-
*/
|
|
1003
|
-
const OpenRouterResponseSchema = z.object({
|
|
1004
|
-
id: z.string().optional(),
|
|
1005
|
-
choices: z.array(z.object({
|
|
1006
|
-
index: z.number(),
|
|
1007
|
-
message: z.object({
|
|
1008
|
-
role: z.string(),
|
|
1009
|
-
content: z.string().optional()
|
|
1010
|
-
}),
|
|
1011
|
-
finish_reason: z.string().optional()
|
|
1012
|
-
})),
|
|
1013
|
-
usage: z.object({
|
|
1014
|
-
prompt_tokens: z.number().optional(),
|
|
1015
|
-
completion_tokens: z.number().optional(),
|
|
1016
|
-
total_tokens: z.number().optional()
|
|
1017
|
-
}).optional()
|
|
1018
|
-
});
|
|
999
|
+
//#region src/infrastructure/llm/shared.ts
|
|
1000
|
+
const logger$6 = createLogger();
|
|
1019
1001
|
const SYSTEM_PROMPT = `You are a senior Site Reliability Engineer.
|
|
1020
1002
|
Respond ONLY with raw JSON, no markdown, no text before or after:
|
|
1021
1003
|
{"probable_cause":"string","impacted_services":["string"],"recommended_steps":["string"],"urgency_level":"low|medium|high|critical","requires_rollback":true|false}`;
|
|
@@ -1029,11 +1011,6 @@ const RE_URGENCY_LEVEL = /"urgency_level"\s*:\s*"([^"]+)"/;
|
|
|
1029
1011
|
const RE_REQUIRES_ROLLBACK = /"requires_rollback"\s*:\s*(true|false)/;
|
|
1030
1012
|
const RE_RECOMMENDED_STEPS = /"recommended_steps"\s*:\s*\[([^\]]+)\]/;
|
|
1031
1013
|
const RE_IMPACTED_SERVICES = /"impacted_services"\s*:\s*\[([^\]]+)\]/;
|
|
1032
|
-
const BREAKER_OPTIONS = {
|
|
1033
|
-
timeout: CIRCUIT_BREAKER.Timeout,
|
|
1034
|
-
errorThresholdPercentage: CIRCUIT_BREAKER.ErrorThresholdPercentage,
|
|
1035
|
-
resetTimeout: CIRCUIT_BREAKER.ResetTimeoutMs
|
|
1036
|
-
};
|
|
1037
1014
|
/**
|
|
1038
1015
|
* Builds the user-facing prompt sent to the LLM for analysis.
|
|
1039
1016
|
* Includes cluster summary and trace count for context.
|
|
@@ -1055,7 +1032,7 @@ function parseAnalysis(raw, correlationId) {
|
|
|
1055
1032
|
stage1Succeeded = true;
|
|
1056
1033
|
return analysis;
|
|
1057
1034
|
} catch {}
|
|
1058
|
-
if (!stage1Succeeded) logger$
|
|
1035
|
+
if (!stage1Succeeded) logger$6.warn({
|
|
1059
1036
|
rawResponse: raw.slice(0, 500),
|
|
1060
1037
|
correlationId
|
|
1061
1038
|
}, "llm:parse:failed");
|
|
@@ -1076,13 +1053,13 @@ function parseAnalysis(raw, correlationId) {
|
|
|
1076
1053
|
requires_rollback: rollbackMatch?.[1] === "true"
|
|
1077
1054
|
};
|
|
1078
1055
|
const parsed = LLMAnalysisSchema.parse(analysis);
|
|
1079
|
-
logger$
|
|
1056
|
+
logger$6.warn({
|
|
1080
1057
|
matchedFields: ["probable_cause", "urgency_level"],
|
|
1081
1058
|
correlationId
|
|
1082
1059
|
}, "llm:parse:partial");
|
|
1083
1060
|
return parsed;
|
|
1084
1061
|
}
|
|
1085
|
-
logger$
|
|
1062
|
+
logger$6.warn({ correlationId }, "llm:parse:unusable");
|
|
1086
1063
|
return null;
|
|
1087
1064
|
}
|
|
1088
1065
|
/**
|
|
@@ -1092,7 +1069,7 @@ function parseAnalysis(raw, correlationId) {
|
|
|
1092
1069
|
*/
|
|
1093
1070
|
function parseLlmText(raw, correlationId) {
|
|
1094
1071
|
if (raw.trim() === "") {
|
|
1095
|
-
logger$
|
|
1072
|
+
logger$6.warn({ correlationId }, "llm:parse:empty");
|
|
1096
1073
|
return {
|
|
1097
1074
|
analysis: null,
|
|
1098
1075
|
degradedReason: "empty_response"
|
|
@@ -1104,49 +1081,79 @@ function parseLlmText(raw, correlationId) {
|
|
|
1104
1081
|
degradedReason: "unparseable_response"
|
|
1105
1082
|
};
|
|
1106
1083
|
}
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/infrastructure/llm/bedrock.provider.ts
|
|
1107
1086
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1087
|
+
* Recognized transient Bedrock error names, mapped to their degradedReason.
|
|
1088
|
+
* Any other error name (or non-Error rejection) is rethrown by the caller.
|
|
1110
1089
|
*/
|
|
1111
|
-
|
|
1112
|
-
|
|
1090
|
+
const BEDROCK_AVAILABILITY_ERRORS = /* @__PURE__ */ new Map([
|
|
1091
|
+
["ThrottlingException", "provider_unavailable"],
|
|
1092
|
+
["ServiceUnavailableException", "provider_unavailable"],
|
|
1093
|
+
["InternalServerException", "provider_unavailable"],
|
|
1094
|
+
["ModelTimeoutException", "timeout"],
|
|
1095
|
+
["AbortError", "timeout"]
|
|
1096
|
+
]);
|
|
1097
|
+
function classifyBedrockError(error) {
|
|
1098
|
+
if (!(error instanceof Error)) return void 0;
|
|
1099
|
+
return BEDROCK_AVAILABILITY_ERRORS.get(error.name);
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Bedrock LLM provider using AWS Bedrock Runtime's Converse API.
|
|
1103
|
+
* No circuit breaker — Bedrock's own throttling/timeout errors are
|
|
1104
|
+
* classified directly into a degradedReason.
|
|
1105
|
+
*/
|
|
1106
|
+
var BedrockProvider = class {
|
|
1113
1107
|
model;
|
|
1114
|
-
|
|
1115
|
-
constructor(
|
|
1116
|
-
this.apiKey = apiKey;
|
|
1108
|
+
client = null;
|
|
1109
|
+
constructor(model = LLM_MODELS.Bedrock) {
|
|
1117
1110
|
this.model = model;
|
|
1118
|
-
|
|
1111
|
+
}
|
|
1112
|
+
getClient() {
|
|
1113
|
+
if (!this.client) this.client = new BedrockRuntimeClient({});
|
|
1114
|
+
return this.client;
|
|
1119
1115
|
}
|
|
1120
1116
|
async analyze(cluster, traces) {
|
|
1121
1117
|
const startMs = Date.now();
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
provider: "gemini",
|
|
1125
|
-
model: this.model,
|
|
1126
|
-
latencyMs: Date.now() - startMs
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
async analyzeWithBreaker(cluster, traces) {
|
|
1118
|
+
const abortController = new AbortController();
|
|
1119
|
+
const timeoutHandle = setTimeout(() => abortController.abort(), HTTP_TIMEOUT_MS.LLM);
|
|
1130
1120
|
try {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1121
|
+
const response = await this.getClient().send(new ConverseCommand({
|
|
1122
|
+
modelId: this.model,
|
|
1123
|
+
system: [{ text: SYSTEM_PROMPT }],
|
|
1124
|
+
messages: [{
|
|
1125
|
+
role: "user",
|
|
1126
|
+
content: [{ text: buildUserPrompt(cluster, traces) }]
|
|
1127
|
+
}],
|
|
1128
|
+
inferenceConfig: { maxTokens: LLM_MAX_TOKENS }
|
|
1129
|
+
}), { abortSignal: abortController.signal });
|
|
1130
|
+
return {
|
|
1131
|
+
...parseLlmText(response.output?.message?.content?.find((b) => b.text !== void 0)?.text ?? ""),
|
|
1132
|
+
provider: "bedrock",
|
|
1133
|
+
model: this.model,
|
|
1134
|
+
latencyMs: Date.now() - startMs,
|
|
1135
|
+
promptTokens: response.usage?.inputTokens ?? 0,
|
|
1136
|
+
completionTokens: response.usage?.outputTokens ?? 0
|
|
1137
|
+
};
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
const degradedReason = classifyBedrockError(error);
|
|
1140
|
+
if (degradedReason === void 0) throw error;
|
|
1141
|
+
return {
|
|
1142
|
+
analysis: null,
|
|
1143
|
+
degradedReason,
|
|
1144
|
+
provider: "bedrock",
|
|
1145
|
+
model: this.model,
|
|
1146
|
+
latencyMs: Date.now() - startMs,
|
|
1147
|
+
promptTokens: 0,
|
|
1148
|
+
completionTokens: 0
|
|
1149
|
+
};
|
|
1150
|
+
} finally {
|
|
1151
|
+
clearTimeout(timeoutHandle);
|
|
1134
1152
|
}
|
|
1135
1153
|
}
|
|
1136
|
-
async analyzeRaw(cluster, traces) {
|
|
1137
|
-
const { GoogleGenerativeAI } = await import("@google/generative-ai");
|
|
1138
|
-
const result = await new GoogleGenerativeAI(this.apiKey).getGenerativeModel({
|
|
1139
|
-
model: this.model,
|
|
1140
|
-
systemInstruction: SYSTEM_PROMPT
|
|
1141
|
-
}).generateContent(buildUserPrompt(cluster, traces));
|
|
1142
|
-
const usage = result.response.usageMetadata;
|
|
1143
|
-
return {
|
|
1144
|
-
...parseLlmText(result.response.text()),
|
|
1145
|
-
promptTokens: usage?.promptTokenCount ?? 0,
|
|
1146
|
-
completionTokens: usage?.candidatesTokenCount ?? 0
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
1154
|
};
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/infrastructure/llm/claude.provider.ts
|
|
1150
1157
|
/**
|
|
1151
1158
|
* Claude LLM provider using Anthropic SDK.
|
|
1152
1159
|
* Supports Claude Haiku and other models.
|
|
@@ -1180,30 +1187,108 @@ var ClaudeProvider = class {
|
|
|
1180
1187
|
};
|
|
1181
1188
|
}
|
|
1182
1189
|
};
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region src/infrastructure/llm/gemini.provider.ts
|
|
1192
|
+
const OPOSSUM_TIMEOUT_CODE = "ETIMEDOUT";
|
|
1193
|
+
const OPOSSUM_OPEN_BREAKER_CODE = "EOPENBREAKER";
|
|
1194
|
+
const UNDICI_CONNECT_TIMEOUT_CODE = "UND_ERR_CONNECT_TIMEOUT";
|
|
1195
|
+
function isErrorLike(value) {
|
|
1196
|
+
return typeof value === "object" && value !== null;
|
|
1197
|
+
}
|
|
1198
|
+
function hasTimeoutCode(error) {
|
|
1199
|
+
return error.code === OPOSSUM_TIMEOUT_CODE || error.code === UNDICI_CONNECT_TIMEOUT_CODE;
|
|
1200
|
+
}
|
|
1201
|
+
function isStandardFetchTimeout(error) {
|
|
1202
|
+
return error instanceof DOMException && error.name === "TimeoutError";
|
|
1203
|
+
}
|
|
1204
|
+
function classifyGeminiAvailabilityError(error) {
|
|
1205
|
+
if (!isErrorLike(error)) return void 0;
|
|
1206
|
+
if (error.code === OPOSSUM_OPEN_BREAKER_CODE) return "circuit_breaker_open";
|
|
1207
|
+
if (hasTimeoutCode(error)) return "timeout";
|
|
1208
|
+
if (error instanceof GoogleGenerativeAIAbortError) return "timeout";
|
|
1209
|
+
if (isStandardFetchTimeout(error)) return "timeout";
|
|
1210
|
+
if (isErrorLike(error.cause) && (hasTimeoutCode(error.cause) || isStandardFetchTimeout(error.cause))) return "timeout";
|
|
1211
|
+
}
|
|
1212
|
+
function degradedGeminiResult(degradedReason) {
|
|
1213
|
+
return {
|
|
1214
|
+
analysis: null,
|
|
1215
|
+
degradedReason,
|
|
1216
|
+
promptTokens: 0,
|
|
1217
|
+
completionTokens: 0
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
const BREAKER_OPTIONS = {
|
|
1221
|
+
timeout: CIRCUIT_BREAKER.Timeout,
|
|
1222
|
+
errorThresholdPercentage: CIRCUIT_BREAKER.ErrorThresholdPercentage,
|
|
1223
|
+
resetTimeout: CIRCUIT_BREAKER.ResetTimeoutMs
|
|
1224
|
+
};
|
|
1183
1225
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1226
|
+
* Gemini LLM provider using Google Generative AI SDK.
|
|
1227
|
+
* Wrapped with circuit breaker for resilience.
|
|
1186
1228
|
*/
|
|
1187
|
-
var
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1229
|
+
var GeminiProvider = class {
|
|
1230
|
+
apiKey;
|
|
1231
|
+
model;
|
|
1232
|
+
breaker;
|
|
1233
|
+
constructor(apiKey, model = LLM_MODELS.Gemini) {
|
|
1234
|
+
this.apiKey = apiKey;
|
|
1235
|
+
this.model = model;
|
|
1236
|
+
this.breaker = new Breaker.default(this.analyzeRaw.bind(this), BREAKER_OPTIONS);
|
|
1237
|
+
}
|
|
1238
|
+
async analyze(cluster, traces) {
|
|
1239
|
+
const startMs = Date.now();
|
|
1191
1240
|
return {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1241
|
+
...await this.analyzeWithBreaker(cluster, traces),
|
|
1242
|
+
provider: "gemini",
|
|
1243
|
+
model: this.model,
|
|
1244
|
+
latencyMs: Date.now() - startMs
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
async analyzeWithBreaker(cluster, traces) {
|
|
1248
|
+
try {
|
|
1249
|
+
return await this.breaker.fire(cluster, traces);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
const degradedReason = classifyGeminiAvailabilityError(error);
|
|
1252
|
+
if (degradedReason !== void 0) return degradedGeminiResult(degradedReason);
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
async analyzeRaw(cluster, traces) {
|
|
1257
|
+
const result = await new GoogleGenerativeAI(this.apiKey).getGenerativeModel({
|
|
1258
|
+
model: this.model,
|
|
1259
|
+
systemInstruction: SYSTEM_PROMPT
|
|
1260
|
+
}).generateContent(buildUserPrompt(cluster, traces));
|
|
1261
|
+
const usage = result.response.usageMetadata;
|
|
1262
|
+
return {
|
|
1263
|
+
...parseLlmText(result.response.text()),
|
|
1264
|
+
promptTokens: usage?.promptTokenCount ?? 0,
|
|
1265
|
+
completionTokens: usage?.candidatesTokenCount ?? 0
|
|
1204
1266
|
};
|
|
1205
1267
|
}
|
|
1206
1268
|
};
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region src/infrastructure/llm/openrouter.provider.ts
|
|
1271
|
+
const logger$5 = createLogger();
|
|
1272
|
+
/**
|
|
1273
|
+
* Schema for OpenRouter API response validation.
|
|
1274
|
+
* Ensures type safety at the external boundary.
|
|
1275
|
+
*/
|
|
1276
|
+
const OpenRouterResponseSchema = z.object({
|
|
1277
|
+
id: z.string().optional(),
|
|
1278
|
+
choices: z.array(z.object({
|
|
1279
|
+
index: z.number(),
|
|
1280
|
+
message: z.object({
|
|
1281
|
+
role: z.string(),
|
|
1282
|
+
content: z.string().optional()
|
|
1283
|
+
}),
|
|
1284
|
+
finish_reason: z.string().optional()
|
|
1285
|
+
})),
|
|
1286
|
+
usage: z.object({
|
|
1287
|
+
prompt_tokens: z.number().optional(),
|
|
1288
|
+
completion_tokens: z.number().optional(),
|
|
1289
|
+
total_tokens: z.number().optional()
|
|
1290
|
+
}).optional()
|
|
1291
|
+
});
|
|
1207
1292
|
/**
|
|
1208
1293
|
* OpenRouter LLM provider using OpenAI-compatible API.
|
|
1209
1294
|
* Supports various open models (Qwen, etc.) via OpenRouter gateway.
|
|
@@ -1367,6 +1452,8 @@ var OpenRouterProvider = class {
|
|
|
1367
1452
|
throw new Error("OpenRouter API exhausted all models");
|
|
1368
1453
|
}
|
|
1369
1454
|
};
|
|
1455
|
+
//#endregion
|
|
1456
|
+
//#region src/infrastructure/llm/factory.ts
|
|
1370
1457
|
/**
|
|
1371
1458
|
* Registry mapping provider names to their factory functions.
|
|
1372
1459
|
* Used by createLLMProvider to instantiate the appropriate LLM client.
|
|
@@ -1375,7 +1462,8 @@ const LLM_PROVIDER_REGISTRY = /* @__PURE__ */ new Map([
|
|
|
1375
1462
|
["gemini", (apiKey, model) => new GeminiProvider(apiKey, model)],
|
|
1376
1463
|
["claude", (apiKey, model) => new ClaudeProvider(apiKey, model)],
|
|
1377
1464
|
["openrouter", (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs, "openrouter")],
|
|
1378
|
-
["qwen", (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs, "qwen")]
|
|
1465
|
+
["qwen", (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs, "qwen")],
|
|
1466
|
+
["bedrock", (_apiKey, model) => new BedrockProvider(model)]
|
|
1379
1467
|
]);
|
|
1380
1468
|
function createLLMProvider(provider, apiKey, model, options) {
|
|
1381
1469
|
const factory = LLM_PROVIDER_REGISTRY.get(provider);
|
|
@@ -1383,9 +1471,37 @@ function createLLMProvider(provider, apiKey, model, options) {
|
|
|
1383
1471
|
const supported = Array.from(LLM_PROVIDER_REGISTRY.keys()).join(", ");
|
|
1384
1472
|
throw new Error(`Unknown LLM_PROVIDER: "${provider}". Supported: ${supported}`);
|
|
1385
1473
|
}
|
|
1386
|
-
return factory(apiKey, model, options);
|
|
1474
|
+
return factory(apiKey ?? "", model, options);
|
|
1387
1475
|
}
|
|
1388
1476
|
//#endregion
|
|
1477
|
+
//#region src/infrastructure/llm/mock.provider.ts
|
|
1478
|
+
/** Provider name reported by MockLLMProvider results. */
|
|
1479
|
+
const MOCK_PROVIDER_NAME = "mock";
|
|
1480
|
+
/**
|
|
1481
|
+
* Mock LLM provider for testing and local development.
|
|
1482
|
+
* Returns deterministic responses without external API calls.
|
|
1483
|
+
*/
|
|
1484
|
+
var MockLLMProvider = class {
|
|
1485
|
+
callLog = [];
|
|
1486
|
+
async analyze(cluster, _traces) {
|
|
1487
|
+
this.callLog.push({ cluster });
|
|
1488
|
+
return {
|
|
1489
|
+
analysis: {
|
|
1490
|
+
probable_cause: `Mock: ${cluster.alertType} on ${cluster.serviceName}`,
|
|
1491
|
+
impacted_services: [cluster.serviceName],
|
|
1492
|
+
recommended_steps: ["Check the logs", "Verify the deployment"],
|
|
1493
|
+
urgency_level: "high",
|
|
1494
|
+
requires_rollback: false
|
|
1495
|
+
},
|
|
1496
|
+
provider: MOCK_PROVIDER_NAME,
|
|
1497
|
+
model: MOCK_PROVIDER_NAME,
|
|
1498
|
+
latencyMs: 0,
|
|
1499
|
+
promptTokens: 0,
|
|
1500
|
+
completionTokens: 0
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
//#endregion
|
|
1389
1505
|
//#region src/shared/factory-registry.ts
|
|
1390
1506
|
var FactoryRegistry = class {
|
|
1391
1507
|
_factories = /* @__PURE__ */ new Map();
|
|
@@ -35696,6 +35812,20 @@ function parseBooleanEnv(value) {
|
|
|
35696
35812
|
const normalized = value.trim().toLowerCase();
|
|
35697
35813
|
return normalized === "true" || normalized === "1";
|
|
35698
35814
|
}
|
|
35815
|
+
function llmApiKeyRequired(llmProvider, llmApiKey) {
|
|
35816
|
+
return llmProvider !== "bedrock" && !llmApiKey;
|
|
35817
|
+
}
|
|
35818
|
+
/**
|
|
35819
|
+
* Resolves the effective LLM_MODEL. An explicit LLM_MODEL always wins, for any
|
|
35820
|
+
* provider. BEDROCK_DEFAULT_MODEL (set by the CDK stack from the deployment
|
|
35821
|
+
* region — see junando-stack.ts) is used ONLY as a fallback when the provider
|
|
35822
|
+
* is bedrock, so a Bedrock-specific value can never leak into another
|
|
35823
|
+
* provider's model override.
|
|
35824
|
+
*/
|
|
35825
|
+
function resolveLlmModel(llmProvider, llmModel, bedrockDefaultModel) {
|
|
35826
|
+
if (llmModel) return llmModel;
|
|
35827
|
+
return llmProvider === "bedrock" ? bedrockDefaultModel : void 0;
|
|
35828
|
+
}
|
|
35699
35829
|
function parseOptionalStringArray(value) {
|
|
35700
35830
|
if (value === void 0 || value === "") return void 0;
|
|
35701
35831
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -35745,9 +35875,10 @@ const ConfigSchema = z.object({
|
|
|
35745
35875
|
"gemini",
|
|
35746
35876
|
"claude",
|
|
35747
35877
|
"openrouter",
|
|
35748
|
-
"qwen"
|
|
35878
|
+
"qwen",
|
|
35879
|
+
"bedrock"
|
|
35749
35880
|
]),
|
|
35750
|
-
llmApiKey: z.string().min(1),
|
|
35881
|
+
llmApiKey: z.string().min(1).optional(),
|
|
35751
35882
|
llmModel: z.string().optional().transform((v) => v === "" ? void 0 : v),
|
|
35752
35883
|
notifierType: z.enum(["slack", "teams"]).default("slack"),
|
|
35753
35884
|
slackBotToken: z.string().startsWith("xoxb-").optional(),
|
|
@@ -35830,13 +35961,18 @@ const ConfigSchema = z.object({
|
|
|
35830
35961
|
path: ["redisUrl"],
|
|
35831
35962
|
message: "[dedupStore: redis] REDIS_URL is required"
|
|
35832
35963
|
});
|
|
35964
|
+
if (llmApiKeyRequired(data.llmProvider, data.llmApiKey)) ctx.addIssue({
|
|
35965
|
+
code: z.ZodIssueCode.custom,
|
|
35966
|
+
path: ["llmApiKey"],
|
|
35967
|
+
message: `[llmProvider: ${data.llmProvider}] LLM_API_KEY is required`
|
|
35968
|
+
});
|
|
35833
35969
|
});
|
|
35834
35970
|
async function loadConfig() {
|
|
35835
35971
|
await loadSecretsFromSSM();
|
|
35836
35972
|
const result = ConfigSchema.safeParse({
|
|
35837
35973
|
llmProvider: process.env["LLM_PROVIDER"],
|
|
35838
35974
|
llmApiKey: process.env["LLM_API_KEY"],
|
|
35839
|
-
llmModel: process.env["LLM_MODEL"],
|
|
35975
|
+
llmModel: resolveLlmModel(process.env["LLM_PROVIDER"], process.env["LLM_MODEL"], process.env["BEDROCK_DEFAULT_MODEL"]),
|
|
35840
35976
|
notifierType: process.env["NOTIFIER_TYPE"],
|
|
35841
35977
|
slackBotToken: process.env["SLACK_BOT_TOKEN"],
|
|
35842
35978
|
slackSigningSecret: process.env["SLACK_SIGNING_SECRET"],
|
|
@@ -35859,11 +35995,14 @@ async function loadConfig() {
|
|
|
35859
35995
|
});
|
|
35860
35996
|
if (!result.success) {
|
|
35861
35997
|
const errorMessages = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
35998
|
+
const rawLlmProvider = process.env["LLM_PROVIDER"];
|
|
35999
|
+
const rawLlmApiKey = process.env["LLM_API_KEY"];
|
|
36000
|
+
if (!errorMessages.some((m) => m.startsWith("llmApiKey")) && llmApiKeyRequired(rawLlmProvider, rawLlmApiKey)) errorMessages.push(`llmApiKey: [llmProvider: ${rawLlmProvider ?? "(unset)"}] LLM_API_KEY is required`);
|
|
35862
36001
|
throw new Error(`Invalid configuration:\n - ${errorMessages.join("\n - ")}`);
|
|
35863
36002
|
}
|
|
35864
36003
|
return result.data;
|
|
35865
36004
|
}
|
|
35866
36005
|
//#endregion
|
|
35867
|
-
export { ALERT_TYPE_LABELS, AlertClusterSchema, AlertStatusSchema, AlertType, AlertmanagerPayloadSchema, CIRCUIT_BREAKER, ChannelRegistry, ClaudeProvider, ClusteringService, Component, ConsoleNotifier, DEDUP_TTL_MS_MULTIPLIER, DEV_SERVER_PORT, DynamoDBDeduplicationStore, FactoryRegistry, Fingerprint, GeminiProvider, HOUR_MS, HTTP_TIMEOUT_MS, InMemoryAlertQueue, InMemoryDeduplicationStore, InMemoryIndexer, IncidentSchema, LLMAnalysisSchema, LLMProviderType, LLM_FALLBACK_DEFAULTS, LLM_MAX_TOKENS, LLM_MODELS, LokiTraceRepository, MockLLMProvider, MockTraceRepository, NoopRollbackActionHandler, NormalizedAlertSchema, NotifyOutcome, OpenSearchIndexer, Outcome, PAYLOAD_DEFAULTS, ProcessIncidentUseCase, RATE_LIMITER, REDIS_KEY_PREFIX, ROLLBACK_ACTION_ID, RedisDeduplicationStore, RoutingNotifier, RuleActionSchema, RuleActionType, RuleConditionSchema, RuleConfigurationSchema, RuleEngine, RuleEvaluationPhase, RuleSchema, RuleSectionSchema, SLACK_API_URL, SQSAlertQueue, SamplingDecision, SeverityLevel, SlackNotifier, Stage, TEAMS_WEBHOOK_TIMEOUT_MS, TeamsNotifier, TeamsNotifierError, URGENCY_EMOJI, UrgencyLevelSchema, WEBHOOK_DEFAULTS, WideEventBuilder, compileCondition, createLLMProvider, createLogger, createNotifier, createRollbackActionHandler, dispatchActions, flushLoki, loadConfig, metrics_exports as metrics, normalizePayload, parseRuleConfig, reinitLogger, startSqsLagPoller };
|
|
36006
|
+
export { ALERT_TYPE_LABELS, AlertClusterSchema, AlertStatusSchema, AlertType, AlertmanagerPayloadSchema, BedrockProvider, CIRCUIT_BREAKER, ChannelRegistry, ClaudeProvider, ClusteringService, Component, ConsoleNotifier, DEDUP_TTL_MS_MULTIPLIER, DEV_SERVER_PORT, DynamoDBDeduplicationStore, FactoryRegistry, Fingerprint, GeminiProvider, HOUR_MS, HTTP_TIMEOUT_MS, InMemoryAlertQueue, InMemoryDeduplicationStore, InMemoryIndexer, IncidentSchema, LLMAnalysisSchema, LLMProviderType, LLM_FALLBACK_DEFAULTS, LLM_MAX_TOKENS, LLM_MODELS, LokiTraceRepository, MockLLMProvider, MockTraceRepository, NoopRollbackActionHandler, NormalizedAlertSchema, NotifyOutcome, OpenRouterProvider, OpenSearchIndexer, Outcome, PAYLOAD_DEFAULTS, ProcessIncidentUseCase, RATE_LIMITER, REDIS_KEY_PREFIX, ROLLBACK_ACTION_ID, RedisDeduplicationStore, RoutingNotifier, RuleActionSchema, RuleActionType, RuleConditionSchema, RuleConfigurationSchema, RuleEngine, RuleEvaluationPhase, RuleSchema, RuleSectionSchema, SLACK_API_URL, SQSAlertQueue, SamplingDecision, SeverityLevel, SlackNotifier, Stage, TEAMS_WEBHOOK_TIMEOUT_MS, TeamsNotifier, TeamsNotifierError, URGENCY_EMOJI, UrgencyLevelSchema, WEBHOOK_DEFAULTS, WideEventBuilder, compileCondition, createLLMProvider, createLogger, createNotifier, createRollbackActionHandler, dispatchActions, flushLoki, loadConfig, metrics_exports as metrics, normalizePayload, parseRuleConfig, reinitLogger, startSqsLagPoller };
|
|
35868
36007
|
|
|
35869
36008
|
//# sourceMappingURL=index.js.map
|