@junando/core 0.14.0 → 0.15.1

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.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$7 = createLogger();
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$7.warn({
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$6 = createLogger();
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$6.warn({
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/llm.adapter.ts
996
- const logger$5 = createLogger();
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$5.warn({
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$5.warn({
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$5.warn({ correlationId }, "llm:parse:unusable");
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$5.warn({ correlationId }, "llm:parse:empty");
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
- * Gemini LLM provider using Google Generative AI SDK.
1109
- * Wrapped with circuit breaker for resilience.
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
- var GeminiProvider = class {
1112
- apiKey;
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
- breaker;
1115
- constructor(apiKey, model = LLM_MODELS.Gemini) {
1116
- this.apiKey = apiKey;
1108
+ client = null;
1109
+ constructor(model = LLM_MODELS.Bedrock) {
1117
1110
  this.model = model;
1118
- this.breaker = new Breaker.default(this.analyzeRaw.bind(this), BREAKER_OPTIONS);
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
- return {
1123
- ...await this.analyzeWithBreaker(cluster, traces),
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
- return await this.breaker.fire(cluster, traces);
1132
- } catch {
1133
- return this.analyzeRaw(cluster, traces);
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
- * Mock LLM provider for testing and local development.
1185
- * Returns deterministic responses without external API calls.
1226
+ * Gemini LLM provider using Google Generative AI SDK.
1227
+ * Wrapped with circuit breaker for resilience.
1186
1228
  */
1187
- var MockLLMProvider = class {
1188
- callLog = [];
1189
- async analyze(cluster, _traces) {
1190
- this.callLog.push({ cluster });
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
- analysis: {
1193
- probable_cause: `Mock: ${cluster.alertType} on ${cluster.serviceName}`,
1194
- impacted_services: [cluster.serviceName],
1195
- recommended_steps: ["Check the logs", "Verify the deployment"],
1196
- urgency_level: "high",
1197
- requires_rollback: false
1198
- },
1199
- provider: MOCK_PROVIDER_NAME,
1200
- model: MOCK_PROVIDER_NAME,
1201
- latencyMs: 0,
1202
- promptTokens: 0,
1203
- completionTokens: 0
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();
@@ -2186,6 +2302,16 @@ function collectUnresolvedChannels(config, registry) {
2186
2302
  for (const section of Object.values(ruleConfig)) for (const rule of section.rules) for (const action of rule.actions) if ("channel" in action) referenced.add(action.channel);
2187
2303
  return [...referenced].filter((channel) => !registry?.has(channel));
2188
2304
  }
2305
+ /**
2306
+ * Creates the RuleEngine from a YAML rules config file.
2307
+ *
2308
+ * Returns undefined when `config.rulesConfigPath` is not set,
2309
+ * meaning rule evaluation is disabled (pass-through behavior).
2310
+ */
2311
+ function createRuleEngine(config) {
2312
+ if (!config.rulesConfigPath) return;
2313
+ return new RuleEngine(parseRuleConfig(readFileSync(config.rulesConfigPath, "utf-8")));
2314
+ }
2189
2315
  //#endregion
2190
2316
  //#region src/infrastructure/rollback/noop-rollback-action.handler.ts
2191
2317
  const logger$1 = createLogger();
@@ -35696,6 +35822,20 @@ function parseBooleanEnv(value) {
35696
35822
  const normalized = value.trim().toLowerCase();
35697
35823
  return normalized === "true" || normalized === "1";
35698
35824
  }
35825
+ function llmApiKeyRequired(llmProvider, llmApiKey) {
35826
+ return llmProvider !== "bedrock" && !llmApiKey;
35827
+ }
35828
+ /**
35829
+ * Resolves the effective LLM_MODEL. An explicit LLM_MODEL always wins, for any
35830
+ * provider. BEDROCK_DEFAULT_MODEL (set by the CDK stack from the deployment
35831
+ * region — see junando-stack.ts) is used ONLY as a fallback when the provider
35832
+ * is bedrock, so a Bedrock-specific value can never leak into another
35833
+ * provider's model override.
35834
+ */
35835
+ function resolveLlmModel(llmProvider, llmModel, bedrockDefaultModel) {
35836
+ if (llmModel) return llmModel;
35837
+ return llmProvider === "bedrock" ? bedrockDefaultModel : void 0;
35838
+ }
35699
35839
  function parseOptionalStringArray(value) {
35700
35840
  if (value === void 0 || value === "") return void 0;
35701
35841
  return value.split(",").map((s) => s.trim()).filter(Boolean);
@@ -35745,9 +35885,10 @@ const ConfigSchema = z.object({
35745
35885
  "gemini",
35746
35886
  "claude",
35747
35887
  "openrouter",
35748
- "qwen"
35888
+ "qwen",
35889
+ "bedrock"
35749
35890
  ]),
35750
- llmApiKey: z.string().min(1),
35891
+ llmApiKey: z.string().min(1).optional(),
35751
35892
  llmModel: z.string().optional().transform((v) => v === "" ? void 0 : v),
35752
35893
  notifierType: z.enum(["slack", "teams"]).default("slack"),
35753
35894
  slackBotToken: z.string().startsWith("xoxb-").optional(),
@@ -35830,13 +35971,18 @@ const ConfigSchema = z.object({
35830
35971
  path: ["redisUrl"],
35831
35972
  message: "[dedupStore: redis] REDIS_URL is required"
35832
35973
  });
35974
+ if (llmApiKeyRequired(data.llmProvider, data.llmApiKey)) ctx.addIssue({
35975
+ code: z.ZodIssueCode.custom,
35976
+ path: ["llmApiKey"],
35977
+ message: `[llmProvider: ${data.llmProvider}] LLM_API_KEY is required`
35978
+ });
35833
35979
  });
35834
35980
  async function loadConfig() {
35835
35981
  await loadSecretsFromSSM();
35836
35982
  const result = ConfigSchema.safeParse({
35837
35983
  llmProvider: process.env["LLM_PROVIDER"],
35838
35984
  llmApiKey: process.env["LLM_API_KEY"],
35839
- llmModel: process.env["LLM_MODEL"],
35985
+ llmModel: resolveLlmModel(process.env["LLM_PROVIDER"], process.env["LLM_MODEL"], process.env["BEDROCK_DEFAULT_MODEL"]),
35840
35986
  notifierType: process.env["NOTIFIER_TYPE"],
35841
35987
  slackBotToken: process.env["SLACK_BOT_TOKEN"],
35842
35988
  slackSigningSecret: process.env["SLACK_SIGNING_SECRET"],
@@ -35859,11 +36005,14 @@ async function loadConfig() {
35859
36005
  });
35860
36006
  if (!result.success) {
35861
36007
  const errorMessages = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
36008
+ const rawLlmProvider = process.env["LLM_PROVIDER"];
36009
+ const rawLlmApiKey = process.env["LLM_API_KEY"];
36010
+ if (!errorMessages.some((m) => m.startsWith("llmApiKey")) && llmApiKeyRequired(rawLlmProvider, rawLlmApiKey)) errorMessages.push(`llmApiKey: [llmProvider: ${rawLlmProvider ?? "(unset)"}] LLM_API_KEY is required`);
35862
36011
  throw new Error(`Invalid configuration:\n - ${errorMessages.join("\n - ")}`);
35863
36012
  }
35864
36013
  return result.data;
35865
36014
  }
35866
36015
  //#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 };
36016
+ 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, createRuleEngine, dispatchActions, flushLoki, loadConfig, metrics_exports as metrics, normalizePayload, parseRuleConfig, reinitLogger, startSqsLagPoller };
35868
36017
 
35869
36018
  //# sourceMappingURL=index.js.map