@junando/core 0.13.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 +89 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +325 -95
- package/dist/index.js.map +1 -1
- package/dist/shared/metrics/index.d.ts +3 -3
- package/dist/shared/metrics/index.d.ts.map +1 -1
- package/dist/shared/metrics/index.js +6 -6
- package/dist/shared/metrics/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./rolldown-runtime-BMI-E3GI.js";
|
|
2
|
-
import { dedupDuplicate,
|
|
2
|
+
import { dedupDuplicate, dedupFailoverTotal, dedupNew, llmInferenceDuration, llmInferenceTotal, notificationsTotal, sqsQueueLag, suppressedClusters, t as metrics_exports } from "./shared/metrics/index.js";
|
|
3
3
|
import { $ as booleanSelector, $t as ServiceException, A as BinaryDecisionDiagram, An as isValidHostLabel, B as NODE_REGION_CONFIG_FILE_OPTIONS, C as isIpAddress, Ct as dateToUtcString, Dt as parseRfc7231DateTime, E as EndpointError, En as normalizeProvider$1, Et as parseRfc3339DateTimeWithOffset, F as init_config$1, Fn as hasOwn, G as NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS, H as REGION_ENV_NAME, Ht as NoOpLogger, It as fromBase64, Jt as resolveDefaultRuntimeConfig, Kt as getDefaultExtensionConfiguration, L as resolveDefaultsModeConfig, Ln as getSmithyContext, N as resolveParams, Nn as HttpRequest, Nt as toBase64, O as EndpointCache, On as isValidHostname, Ot as expectUnion, P as config_exports, Pt as fromUtf8, Qt as loadConfigsForDefaultMode, Rt as client_exports$1, Sn as parseUrl, Tt as parseEpochTimestamp, U as REGION_INI_NAME, V as NODE_REGION_CONFIG_OPTIONS, Vt as makeBuilder, Wt as getValueFromTextNode, X as loadConfig$1, Yt as emitWarningIfUnsupportedVersion$1, Z as SelectorType, _ as resolveEndpointConfig, _t as _parseRfc7231DateTime, b as decideEndpoint, bn as init_transport, bt as quoteHeader, c as generateIdempotencyToken, cn as TypeRegistry, ct as NumericValue, d as v4, en as decorateServiceException, ft as splitHeader, g as init_endpoints, gn as deref, gt as _parseRfc3339DateTimeWithOffset, h as getEndpointPlugin, ht as _parseEpochTimestamp, in as Command, jn as HttpResponse, jt as toUtf8, l as init_serde, m as endpoints_exports, mn as getSchemaSerdePlugin, mt as splitEvery, n as init_checksum, nn as createAggregatedClient, on as init_schema, p as sdkStreamMixin, pn as translateTraits, q as NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS, r as Sha256Node, rt as calculateBodyLength, s as Uint8ArrayBlobAdapter, sn as schema_exports, t as checksum_exports, tt as ProviderError, u as serde_exports, un as NormalizedSchema, ut as nv, vn as Client, w as customEndpointFunctions, wn as parseQueryString, xt as LazyJsonString, y as resolveEndpoint, z as resolveRegionConfig, zn as require_dist_cjs$17, zt as init_client$1 } from "./checksum-Bbvsykod.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import pino from "pino";
|
|
7
7
|
import { Readable, Writable } from "node:stream";
|
|
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";
|
|
8
11
|
import * as Breaker from "opossum";
|
|
9
12
|
import { readFileSync } from "node:fs";
|
|
10
13
|
import { YAMLParseError, parse } from "yaml";
|
|
@@ -39,6 +42,7 @@ let LLMProviderType = /* @__PURE__ */ function(LLMProviderType) {
|
|
|
39
42
|
LLMProviderType["Claude"] = "claude";
|
|
40
43
|
LLMProviderType["OpenRouter"] = "openrouter";
|
|
41
44
|
LLMProviderType["Qwen"] = "qwen";
|
|
45
|
+
LLMProviderType["Bedrock"] = "bedrock";
|
|
42
46
|
return LLMProviderType;
|
|
43
47
|
}({});
|
|
44
48
|
const HTTP_TIMEOUT_MS = Object.freeze({
|
|
@@ -71,7 +75,8 @@ const LLM_FALLBACK_DEFAULTS = Object.freeze({
|
|
|
71
75
|
const LLM_MODELS = Object.freeze({
|
|
72
76
|
Gemini: "gemini-2.0-flash",
|
|
73
77
|
Claude: "claude-haiku-4-5",
|
|
74
|
-
OpenRouter: "qwen/qwen-2.5-72b-instruct"
|
|
78
|
+
OpenRouter: "qwen/qwen-2.5-72b-instruct",
|
|
79
|
+
Bedrock: "us.amazon.nova-lite-v1:0"
|
|
75
80
|
});
|
|
76
81
|
const SLACK_API_URL = "https://slack.com/api/chat.postMessage";
|
|
77
82
|
const ROLLBACK_ACTION_ID = "trigger_rollback";
|
|
@@ -808,6 +813,7 @@ var ProcessIncidentUseCase = class {
|
|
|
808
813
|
latencyMs: Date.now() - notifyStartMs
|
|
809
814
|
});
|
|
810
815
|
builder.set("error", toErrorSection(err));
|
|
816
|
+
await this.releaseDedupClaim(cluster.fingerprint);
|
|
811
817
|
this.emit(builder, Outcome.Error, clusterStartMs);
|
|
812
818
|
throw err;
|
|
813
819
|
}
|
|
@@ -820,6 +826,22 @@ var ProcessIncidentUseCase = class {
|
|
|
820
826
|
}
|
|
821
827
|
}
|
|
822
828
|
/**
|
|
829
|
+
* Releases the dedup claim so an SQS retry is not discarded as a duplicate.
|
|
830
|
+
* Concurrency: safe without a lock — sqs.adapter.ts sets messageGroupId to
|
|
831
|
+
* the fingerprint, so SQS FIFO serialises delivery per fingerprint and two
|
|
832
|
+
* runs for the same fingerprint are never in flight simultaneously.
|
|
833
|
+
*/
|
|
834
|
+
async releaseDedupClaim(fingerprint) {
|
|
835
|
+
try {
|
|
836
|
+
await this.deps.dedup.reset(fingerprint);
|
|
837
|
+
} catch (err) {
|
|
838
|
+
this.deps.logger.warn({
|
|
839
|
+
err,
|
|
840
|
+
fingerprint
|
|
841
|
+
}, "Failed to release dedup claim; retry may be suppressed");
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
823
845
|
* Flushes the builder into a final event, applies tail sampling, redacts
|
|
824
846
|
* PII, and emits the single canonical log line for the cluster.
|
|
825
847
|
*/
|
|
@@ -832,7 +854,7 @@ var ProcessIncidentUseCase = class {
|
|
|
832
854
|
};
|
|
833
855
|
//#endregion
|
|
834
856
|
//#region src/infrastructure/dedup/redis-dedup.adapter.ts
|
|
835
|
-
const logger$
|
|
857
|
+
const logger$8 = createLogger();
|
|
836
858
|
var RedisDeduplicationStore = class {
|
|
837
859
|
redis;
|
|
838
860
|
keyPrefix = "junando:dedup:";
|
|
@@ -847,11 +869,11 @@ var RedisDeduplicationStore = class {
|
|
|
847
869
|
};
|
|
848
870
|
} catch (err) {
|
|
849
871
|
const message = err instanceof Error ? err.message : String(err);
|
|
850
|
-
logger$
|
|
872
|
+
logger$8.warn({
|
|
851
873
|
err,
|
|
852
874
|
fingerprint
|
|
853
875
|
}, "Redis dedup check failed, failing open");
|
|
854
|
-
|
|
876
|
+
dedupFailoverTotal.inc();
|
|
855
877
|
return {
|
|
856
878
|
isNew: true,
|
|
857
879
|
ttlSeconds,
|
|
@@ -886,6 +908,63 @@ var InMemoryDeduplicationStore = class {
|
|
|
886
908
|
}
|
|
887
909
|
};
|
|
888
910
|
//#endregion
|
|
911
|
+
//#region src/infrastructure/dedup/dynamodb-dedup.adapter.ts
|
|
912
|
+
const logger$7 = createLogger();
|
|
913
|
+
const CONDITION_EXPRESSION = "attribute_not_exists(fingerprint) OR expiresAt < :now";
|
|
914
|
+
var DynamoDBDeduplicationStore = class {
|
|
915
|
+
tableName;
|
|
916
|
+
region;
|
|
917
|
+
client = null;
|
|
918
|
+
constructor(tableName, region) {
|
|
919
|
+
this.tableName = tableName;
|
|
920
|
+
this.region = region;
|
|
921
|
+
}
|
|
922
|
+
getClient() {
|
|
923
|
+
if (!this.client) this.client = new DynamoDBClient(this.region ? { region: this.region } : {});
|
|
924
|
+
return this.client;
|
|
925
|
+
}
|
|
926
|
+
async isNew(fingerprint, ttlSeconds) {
|
|
927
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
928
|
+
try {
|
|
929
|
+
await this.getClient().send(new PutItemCommand({
|
|
930
|
+
TableName: this.tableName,
|
|
931
|
+
Item: {
|
|
932
|
+
fingerprint: { S: fingerprint },
|
|
933
|
+
expiresAt: { N: String(nowSec + ttlSeconds) }
|
|
934
|
+
},
|
|
935
|
+
ConditionExpression: CONDITION_EXPRESSION,
|
|
936
|
+
ExpressionAttributeValues: { ":now": { N: String(nowSec) } }
|
|
937
|
+
}));
|
|
938
|
+
return {
|
|
939
|
+
isNew: true,
|
|
940
|
+
ttlSeconds
|
|
941
|
+
};
|
|
942
|
+
} catch (err) {
|
|
943
|
+
if (err instanceof Error && err.name === "ConditionalCheckFailedException") return {
|
|
944
|
+
isNew: false,
|
|
945
|
+
ttlSeconds
|
|
946
|
+
};
|
|
947
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
948
|
+
logger$7.warn({
|
|
949
|
+
err,
|
|
950
|
+
fingerprint
|
|
951
|
+
}, "DynamoDB dedup check failed, failing open");
|
|
952
|
+
dedupFailoverTotal.inc();
|
|
953
|
+
return {
|
|
954
|
+
isNew: true,
|
|
955
|
+
ttlSeconds,
|
|
956
|
+
error: message
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async reset(fingerprint) {
|
|
961
|
+
await this.getClient().send(new DeleteItemCommand({
|
|
962
|
+
TableName: this.tableName,
|
|
963
|
+
Key: { fingerprint: { S: fingerprint } }
|
|
964
|
+
}));
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
//#endregion
|
|
889
968
|
//#region src/infrastructure/indexer/opensearch.adapter.ts
|
|
890
969
|
var OpenSearchIndexer = class {
|
|
891
970
|
endpoint;
|
|
@@ -917,30 +996,8 @@ var InMemoryIndexer = class {
|
|
|
917
996
|
}
|
|
918
997
|
};
|
|
919
998
|
//#endregion
|
|
920
|
-
//#region src/infrastructure/llm/
|
|
921
|
-
const logger$
|
|
922
|
-
/** Provider name reported by MockLLMProvider results. */
|
|
923
|
-
const MOCK_PROVIDER_NAME = "mock";
|
|
924
|
-
/**
|
|
925
|
-
* Schema for OpenRouter API response validation.
|
|
926
|
-
* Ensures type safety at the external boundary.
|
|
927
|
-
*/
|
|
928
|
-
const OpenRouterResponseSchema = z.object({
|
|
929
|
-
id: z.string().optional(),
|
|
930
|
-
choices: z.array(z.object({
|
|
931
|
-
index: z.number(),
|
|
932
|
-
message: z.object({
|
|
933
|
-
role: z.string(),
|
|
934
|
-
content: z.string().optional()
|
|
935
|
-
}),
|
|
936
|
-
finish_reason: z.string().optional()
|
|
937
|
-
})),
|
|
938
|
-
usage: z.object({
|
|
939
|
-
prompt_tokens: z.number().optional(),
|
|
940
|
-
completion_tokens: z.number().optional(),
|
|
941
|
-
total_tokens: z.number().optional()
|
|
942
|
-
}).optional()
|
|
943
|
-
});
|
|
999
|
+
//#region src/infrastructure/llm/shared.ts
|
|
1000
|
+
const logger$6 = createLogger();
|
|
944
1001
|
const SYSTEM_PROMPT = `You are a senior Site Reliability Engineer.
|
|
945
1002
|
Respond ONLY with raw JSON, no markdown, no text before or after:
|
|
946
1003
|
{"probable_cause":"string","impacted_services":["string"],"recommended_steps":["string"],"urgency_level":"low|medium|high|critical","requires_rollback":true|false}`;
|
|
@@ -954,11 +1011,6 @@ const RE_URGENCY_LEVEL = /"urgency_level"\s*:\s*"([^"]+)"/;
|
|
|
954
1011
|
const RE_REQUIRES_ROLLBACK = /"requires_rollback"\s*:\s*(true|false)/;
|
|
955
1012
|
const RE_RECOMMENDED_STEPS = /"recommended_steps"\s*:\s*\[([^\]]+)\]/;
|
|
956
1013
|
const RE_IMPACTED_SERVICES = /"impacted_services"\s*:\s*\[([^\]]+)\]/;
|
|
957
|
-
const BREAKER_OPTIONS = {
|
|
958
|
-
timeout: CIRCUIT_BREAKER.Timeout,
|
|
959
|
-
errorThresholdPercentage: CIRCUIT_BREAKER.ErrorThresholdPercentage,
|
|
960
|
-
resetTimeout: CIRCUIT_BREAKER.ResetTimeoutMs
|
|
961
|
-
};
|
|
962
1014
|
/**
|
|
963
1015
|
* Builds the user-facing prompt sent to the LLM for analysis.
|
|
964
1016
|
* Includes cluster summary and trace count for context.
|
|
@@ -980,7 +1032,7 @@ function parseAnalysis(raw, correlationId) {
|
|
|
980
1032
|
stage1Succeeded = true;
|
|
981
1033
|
return analysis;
|
|
982
1034
|
} catch {}
|
|
983
|
-
if (!stage1Succeeded) logger$
|
|
1035
|
+
if (!stage1Succeeded) logger$6.warn({
|
|
984
1036
|
rawResponse: raw.slice(0, 500),
|
|
985
1037
|
correlationId
|
|
986
1038
|
}, "llm:parse:failed");
|
|
@@ -1001,13 +1053,13 @@ function parseAnalysis(raw, correlationId) {
|
|
|
1001
1053
|
requires_rollback: rollbackMatch?.[1] === "true"
|
|
1002
1054
|
};
|
|
1003
1055
|
const parsed = LLMAnalysisSchema.parse(analysis);
|
|
1004
|
-
logger$
|
|
1056
|
+
logger$6.warn({
|
|
1005
1057
|
matchedFields: ["probable_cause", "urgency_level"],
|
|
1006
1058
|
correlationId
|
|
1007
1059
|
}, "llm:parse:partial");
|
|
1008
1060
|
return parsed;
|
|
1009
1061
|
}
|
|
1010
|
-
logger$
|
|
1062
|
+
logger$6.warn({ correlationId }, "llm:parse:unusable");
|
|
1011
1063
|
return null;
|
|
1012
1064
|
}
|
|
1013
1065
|
/**
|
|
@@ -1017,7 +1069,7 @@ function parseAnalysis(raw, correlationId) {
|
|
|
1017
1069
|
*/
|
|
1018
1070
|
function parseLlmText(raw, correlationId) {
|
|
1019
1071
|
if (raw.trim() === "") {
|
|
1020
|
-
logger$
|
|
1072
|
+
logger$6.warn({ correlationId }, "llm:parse:empty");
|
|
1021
1073
|
return {
|
|
1022
1074
|
analysis: null,
|
|
1023
1075
|
degradedReason: "empty_response"
|
|
@@ -1029,49 +1081,79 @@ function parseLlmText(raw, correlationId) {
|
|
|
1029
1081
|
degradedReason: "unparseable_response"
|
|
1030
1082
|
};
|
|
1031
1083
|
}
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/infrastructure/llm/bedrock.provider.ts
|
|
1032
1086
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1087
|
+
* Recognized transient Bedrock error names, mapped to their degradedReason.
|
|
1088
|
+
* Any other error name (or non-Error rejection) is rethrown by the caller.
|
|
1035
1089
|
*/
|
|
1036
|
-
|
|
1037
|
-
|
|
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 {
|
|
1038
1107
|
model;
|
|
1039
|
-
|
|
1040
|
-
constructor(
|
|
1041
|
-
this.apiKey = apiKey;
|
|
1108
|
+
client = null;
|
|
1109
|
+
constructor(model = LLM_MODELS.Bedrock) {
|
|
1042
1110
|
this.model = model;
|
|
1043
|
-
|
|
1111
|
+
}
|
|
1112
|
+
getClient() {
|
|
1113
|
+
if (!this.client) this.client = new BedrockRuntimeClient({});
|
|
1114
|
+
return this.client;
|
|
1044
1115
|
}
|
|
1045
1116
|
async analyze(cluster, traces) {
|
|
1046
1117
|
const startMs = Date.now();
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
provider: "gemini",
|
|
1050
|
-
model: this.model,
|
|
1051
|
-
latencyMs: Date.now() - startMs
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
async analyzeWithBreaker(cluster, traces) {
|
|
1118
|
+
const abortController = new AbortController();
|
|
1119
|
+
const timeoutHandle = setTimeout(() => abortController.abort(), HTTP_TIMEOUT_MS.LLM);
|
|
1055
1120
|
try {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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);
|
|
1059
1152
|
}
|
|
1060
1153
|
}
|
|
1061
|
-
async analyzeRaw(cluster, traces) {
|
|
1062
|
-
const { GoogleGenerativeAI } = await import("@google/generative-ai");
|
|
1063
|
-
const result = await new GoogleGenerativeAI(this.apiKey).getGenerativeModel({
|
|
1064
|
-
model: this.model,
|
|
1065
|
-
systemInstruction: SYSTEM_PROMPT
|
|
1066
|
-
}).generateContent(buildUserPrompt(cluster, traces));
|
|
1067
|
-
const usage = result.response.usageMetadata;
|
|
1068
|
-
return {
|
|
1069
|
-
...parseLlmText(result.response.text()),
|
|
1070
|
-
promptTokens: usage?.promptTokenCount ?? 0,
|
|
1071
|
-
completionTokens: usage?.candidatesTokenCount ?? 0
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
1154
|
};
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/infrastructure/llm/claude.provider.ts
|
|
1075
1157
|
/**
|
|
1076
1158
|
* Claude LLM provider using Anthropic SDK.
|
|
1077
1159
|
* Supports Claude Haiku and other models.
|
|
@@ -1105,30 +1187,108 @@ var ClaudeProvider = class {
|
|
|
1105
1187
|
};
|
|
1106
1188
|
}
|
|
1107
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
|
+
};
|
|
1108
1225
|
/**
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1226
|
+
* Gemini LLM provider using Google Generative AI SDK.
|
|
1227
|
+
* Wrapped with circuit breaker for resilience.
|
|
1111
1228
|
*/
|
|
1112
|
-
var
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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();
|
|
1116
1240
|
return {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
|
1129
1266
|
};
|
|
1130
1267
|
}
|
|
1131
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
|
+
});
|
|
1132
1292
|
/**
|
|
1133
1293
|
* OpenRouter LLM provider using OpenAI-compatible API.
|
|
1134
1294
|
* Supports various open models (Qwen, etc.) via OpenRouter gateway.
|
|
@@ -1292,6 +1452,8 @@ var OpenRouterProvider = class {
|
|
|
1292
1452
|
throw new Error("OpenRouter API exhausted all models");
|
|
1293
1453
|
}
|
|
1294
1454
|
};
|
|
1455
|
+
//#endregion
|
|
1456
|
+
//#region src/infrastructure/llm/factory.ts
|
|
1295
1457
|
/**
|
|
1296
1458
|
* Registry mapping provider names to their factory functions.
|
|
1297
1459
|
* Used by createLLMProvider to instantiate the appropriate LLM client.
|
|
@@ -1300,7 +1462,8 @@ const LLM_PROVIDER_REGISTRY = /* @__PURE__ */ new Map([
|
|
|
1300
1462
|
["gemini", (apiKey, model) => new GeminiProvider(apiKey, model)],
|
|
1301
1463
|
["claude", (apiKey, model) => new ClaudeProvider(apiKey, model)],
|
|
1302
1464
|
["openrouter", (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs, "openrouter")],
|
|
1303
|
-
["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)]
|
|
1304
1467
|
]);
|
|
1305
1468
|
function createLLMProvider(provider, apiKey, model, options) {
|
|
1306
1469
|
const factory = LLM_PROVIDER_REGISTRY.get(provider);
|
|
@@ -1308,9 +1471,37 @@ function createLLMProvider(provider, apiKey, model, options) {
|
|
|
1308
1471
|
const supported = Array.from(LLM_PROVIDER_REGISTRY.keys()).join(", ");
|
|
1309
1472
|
throw new Error(`Unknown LLM_PROVIDER: "${provider}". Supported: ${supported}`);
|
|
1310
1473
|
}
|
|
1311
|
-
return factory(apiKey, model, options);
|
|
1474
|
+
return factory(apiKey ?? "", model, options);
|
|
1312
1475
|
}
|
|
1313
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
|
|
1314
1505
|
//#region src/shared/factory-registry.ts
|
|
1315
1506
|
var FactoryRegistry = class {
|
|
1316
1507
|
_factories = /* @__PURE__ */ new Map();
|
|
@@ -35621,6 +35812,20 @@ function parseBooleanEnv(value) {
|
|
|
35621
35812
|
const normalized = value.trim().toLowerCase();
|
|
35622
35813
|
return normalized === "true" || normalized === "1";
|
|
35623
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
|
+
}
|
|
35624
35829
|
function parseOptionalStringArray(value) {
|
|
35625
35830
|
if (value === void 0 || value === "") return void 0;
|
|
35626
35831
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -35659,6 +35864,8 @@ async function loadSecretsFromSSM() {
|
|
|
35659
35864
|
const key = param.Name.replace(`${prefix}/`, "").replaceAll("-", "_").toUpperCase();
|
|
35660
35865
|
process.env[key] = param.Value;
|
|
35661
35866
|
}
|
|
35867
|
+
const missing = result.InvalidParameters ?? [];
|
|
35868
|
+
if (missing.length > 0) createLogger().warn({ missingParameters: missing }, "SSM parameters not found");
|
|
35662
35869
|
} catch (err) {
|
|
35663
35870
|
createLogger().error({ err }, "Failed to load SSM parameters");
|
|
35664
35871
|
}
|
|
@@ -35668,9 +35875,10 @@ const ConfigSchema = z.object({
|
|
|
35668
35875
|
"gemini",
|
|
35669
35876
|
"claude",
|
|
35670
35877
|
"openrouter",
|
|
35671
|
-
"qwen"
|
|
35878
|
+
"qwen",
|
|
35879
|
+
"bedrock"
|
|
35672
35880
|
]),
|
|
35673
|
-
llmApiKey: z.string().min(1),
|
|
35881
|
+
llmApiKey: z.string().min(1).optional(),
|
|
35674
35882
|
llmModel: z.string().optional().transform((v) => v === "" ? void 0 : v),
|
|
35675
35883
|
notifierType: z.enum(["slack", "teams"]).default("slack"),
|
|
35676
35884
|
slackBotToken: z.string().startsWith("xoxb-").optional(),
|
|
@@ -35680,7 +35888,9 @@ const ConfigSchema = z.object({
|
|
|
35680
35888
|
rollbackActionAllowedSlackUserIds: z.array(z.string()).optional(),
|
|
35681
35889
|
teamsWebhookUrl: z.string().url().optional(),
|
|
35682
35890
|
lokiUrl: z.string().optional().transform((v) => v === "" ? void 0 : v),
|
|
35683
|
-
|
|
35891
|
+
dedupStore: z.enum(["dynamodb", "redis"]).default("dynamodb"),
|
|
35892
|
+
dedupTableName: z.string().min(1).optional(),
|
|
35893
|
+
redisUrl: z.string().url().optional(),
|
|
35684
35894
|
sqsQueueUrl: z.string().url().optional().or(z.literal("")),
|
|
35685
35895
|
dedupTtlSeconds: z.coerce.number().int().positive().default(300),
|
|
35686
35896
|
clusterWindowMs: z.coerce.number().int().positive().default(12e4),
|
|
@@ -35741,13 +35951,28 @@ const ConfigSchema = z.object({
|
|
|
35741
35951
|
});
|
|
35742
35952
|
}
|
|
35743
35953
|
}
|
|
35954
|
+
if (data.dedupStore === "dynamodb" && !data.dedupTableName) ctx.addIssue({
|
|
35955
|
+
code: z.ZodIssueCode.custom,
|
|
35956
|
+
path: ["dedupTableName"],
|
|
35957
|
+
message: "[dedupStore: dynamodb] DEDUP_TABLE_NAME is required"
|
|
35958
|
+
});
|
|
35959
|
+
if (data.dedupStore === "redis" && !data.redisUrl) ctx.addIssue({
|
|
35960
|
+
code: z.ZodIssueCode.custom,
|
|
35961
|
+
path: ["redisUrl"],
|
|
35962
|
+
message: "[dedupStore: redis] REDIS_URL is required"
|
|
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
|
+
});
|
|
35744
35969
|
});
|
|
35745
35970
|
async function loadConfig() {
|
|
35746
35971
|
await loadSecretsFromSSM();
|
|
35747
35972
|
const result = ConfigSchema.safeParse({
|
|
35748
35973
|
llmProvider: process.env["LLM_PROVIDER"],
|
|
35749
35974
|
llmApiKey: process.env["LLM_API_KEY"],
|
|
35750
|
-
llmModel: process.env["LLM_MODEL"],
|
|
35975
|
+
llmModel: resolveLlmModel(process.env["LLM_PROVIDER"], process.env["LLM_MODEL"], process.env["BEDROCK_DEFAULT_MODEL"]),
|
|
35751
35976
|
notifierType: process.env["NOTIFIER_TYPE"],
|
|
35752
35977
|
slackBotToken: process.env["SLACK_BOT_TOKEN"],
|
|
35753
35978
|
slackSigningSecret: process.env["SLACK_SIGNING_SECRET"],
|
|
@@ -35755,6 +35980,8 @@ async function loadConfig() {
|
|
|
35755
35980
|
teamsWebhookUrl: process.env["TEAMS_WEBHOOK_URL"],
|
|
35756
35981
|
lokiUrl: process.env["LOKI_URL"],
|
|
35757
35982
|
redisUrl: process.env["REDIS_URL"],
|
|
35983
|
+
dedupStore: process.env["DEDUP_STORE"],
|
|
35984
|
+
dedupTableName: process.env["DEDUP_TABLE_NAME"],
|
|
35758
35985
|
sqsQueueUrl: process.env["SQS_QUEUE_URL"],
|
|
35759
35986
|
dedupTtlSeconds: process.env["DEDUP_TTL_SECONDS"],
|
|
35760
35987
|
clusterWindowMs: process.env["CLUSTER_WINDOW_MS"],
|
|
@@ -35768,11 +35995,14 @@ async function loadConfig() {
|
|
|
35768
35995
|
});
|
|
35769
35996
|
if (!result.success) {
|
|
35770
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`);
|
|
35771
36001
|
throw new Error(`Invalid configuration:\n - ${errorMessages.join("\n - ")}`);
|
|
35772
36002
|
}
|
|
35773
36003
|
return result.data;
|
|
35774
36004
|
}
|
|
35775
36005
|
//#endregion
|
|
35776
|
-
export { ALERT_TYPE_LABELS, AlertClusterSchema, AlertStatusSchema, AlertType, AlertmanagerPayloadSchema, CIRCUIT_BREAKER, ChannelRegistry, ClaudeProvider, ClusteringService, Component, ConsoleNotifier, DEDUP_TTL_MS_MULTIPLIER, DEV_SERVER_PORT, 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 };
|
|
35777
36007
|
|
|
35778
36008
|
//# sourceMappingURL=index.js.map
|