@easyoref/agent 1.21.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.
Files changed (158) hide show
  1. package/__tests__/clarify.test.ts +827 -0
  2. package/__tests__/config.test.ts +304 -0
  3. package/__tests__/enrichment.integration.test.ts +871 -0
  4. package/__tests__/graph.test.ts +661 -0
  5. package/dist/auth.d.ts +11 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +54 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/dry-run.d.ts +12 -0
  10. package/dist/dry-run.d.ts.map +1 -0
  11. package/dist/dry-run.js +236 -0
  12. package/dist/dry-run.js.map +1 -0
  13. package/dist/extract.d.ts +180 -0
  14. package/dist/extract.d.ts.map +1 -0
  15. package/dist/extract.js +210 -0
  16. package/dist/extract.js.map +1 -0
  17. package/dist/graph.d.ts +4083 -0
  18. package/dist/graph.d.ts.map +1 -0
  19. package/dist/graph.js +162 -0
  20. package/dist/graph.js.map +1 -0
  21. package/dist/index.d.ts +23 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +23 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/models.d.ts +7 -0
  26. package/dist/models.d.ts.map +1 -0
  27. package/dist/models.js +18 -0
  28. package/dist/models.js.map +1 -0
  29. package/dist/nodes/clarify-node.d.ts +132 -0
  30. package/dist/nodes/clarify-node.d.ts.map +1 -0
  31. package/dist/nodes/clarify-node.js +118 -0
  32. package/dist/nodes/clarify-node.js.map +1 -0
  33. package/dist/nodes/clarify.d.ts +6 -0
  34. package/dist/nodes/clarify.d.ts.map +1 -0
  35. package/dist/nodes/clarify.js +124 -0
  36. package/dist/nodes/clarify.js.map +1 -0
  37. package/dist/nodes/edit-node.d.ts +71 -0
  38. package/dist/nodes/edit-node.d.ts.map +1 -0
  39. package/dist/nodes/edit-node.js +496 -0
  40. package/dist/nodes/edit-node.js.map +1 -0
  41. package/dist/nodes/edit.d.ts +6 -0
  42. package/dist/nodes/edit.d.ts.map +1 -0
  43. package/dist/nodes/edit.js +22 -0
  44. package/dist/nodes/edit.js.map +1 -0
  45. package/dist/nodes/extract-node.d.ts +174 -0
  46. package/dist/nodes/extract-node.d.ts.map +1 -0
  47. package/dist/nodes/extract-node.js +233 -0
  48. package/dist/nodes/extract-node.js.map +1 -0
  49. package/dist/nodes/extract.d.ts +6 -0
  50. package/dist/nodes/extract.d.ts.map +1 -0
  51. package/dist/nodes/extract.js +49 -0
  52. package/dist/nodes/extract.js.map +1 -0
  53. package/dist/nodes/filter-agent.d.ts +11 -0
  54. package/dist/nodes/filter-agent.d.ts.map +1 -0
  55. package/dist/nodes/filter-agent.js +60 -0
  56. package/dist/nodes/filter-agent.js.map +1 -0
  57. package/dist/nodes/filter-node.d.ts +9 -0
  58. package/dist/nodes/filter-node.d.ts.map +1 -0
  59. package/dist/nodes/filter-node.js +111 -0
  60. package/dist/nodes/filter-node.js.map +1 -0
  61. package/dist/nodes/filters.d.ts +13 -0
  62. package/dist/nodes/filters.d.ts.map +1 -0
  63. package/dist/nodes/filters.js +111 -0
  64. package/dist/nodes/filters.js.map +1 -0
  65. package/dist/nodes/message-node.d.ts +71 -0
  66. package/dist/nodes/message-node.d.ts.map +1 -0
  67. package/dist/nodes/message-node.js +491 -0
  68. package/dist/nodes/message-node.js.map +1 -0
  69. package/dist/nodes/message.d.ts +71 -0
  70. package/dist/nodes/message.d.ts.map +1 -0
  71. package/dist/nodes/message.js +496 -0
  72. package/dist/nodes/message.js.map +1 -0
  73. package/dist/nodes/vote-node.d.ts +13 -0
  74. package/dist/nodes/vote-node.d.ts.map +1 -0
  75. package/dist/nodes/vote-node.js +232 -0
  76. package/dist/nodes/vote-node.js.map +1 -0
  77. package/dist/nodes/vote.d.ts +13 -0
  78. package/dist/nodes/vote.d.ts.map +1 -0
  79. package/dist/nodes/vote.js +232 -0
  80. package/dist/nodes/vote.js.map +1 -0
  81. package/dist/queue.d.ts +15 -0
  82. package/dist/queue.d.ts.map +1 -0
  83. package/dist/queue.js +41 -0
  84. package/dist/queue.js.map +1 -0
  85. package/dist/redis.d.ts +8 -0
  86. package/dist/redis.d.ts.map +1 -0
  87. package/dist/redis.js +33 -0
  88. package/dist/redis.js.map +1 -0
  89. package/dist/runtime/auth.d.ts +11 -0
  90. package/dist/runtime/auth.d.ts.map +1 -0
  91. package/dist/runtime/auth.js +54 -0
  92. package/dist/runtime/auth.js.map +1 -0
  93. package/dist/runtime/dry-run.d.ts +12 -0
  94. package/dist/runtime/dry-run.d.ts.map +1 -0
  95. package/dist/runtime/dry-run.js +236 -0
  96. package/dist/runtime/dry-run.js.map +1 -0
  97. package/dist/runtime/queue.d.ts +15 -0
  98. package/dist/runtime/queue.d.ts.map +1 -0
  99. package/dist/runtime/queue.js +41 -0
  100. package/dist/runtime/queue.js.map +1 -0
  101. package/dist/runtime/redis.d.ts +8 -0
  102. package/dist/runtime/redis.d.ts.map +1 -0
  103. package/dist/runtime/redis.js +33 -0
  104. package/dist/runtime/redis.js.map +1 -0
  105. package/dist/runtime/worker.d.ts +14 -0
  106. package/dist/runtime/worker.d.ts.map +1 -0
  107. package/dist/runtime/worker.js +135 -0
  108. package/dist/runtime/worker.js.map +1 -0
  109. package/dist/tools/alert-history.d.ts +18 -0
  110. package/dist/tools/alert-history.d.ts.map +1 -0
  111. package/dist/tools/alert-history.js +98 -0
  112. package/dist/tools/alert-history.js.map +1 -0
  113. package/dist/tools/betterstack-log.d.ts +15 -0
  114. package/dist/tools/betterstack-log.d.ts.map +1 -0
  115. package/dist/tools/betterstack-log.js +80 -0
  116. package/dist/tools/betterstack-log.js.map +1 -0
  117. package/dist/tools/index.d.ts +44 -0
  118. package/dist/tools/index.d.ts.map +1 -0
  119. package/dist/tools/index.js +20 -0
  120. package/dist/tools/index.js.map +1 -0
  121. package/dist/tools/read-sources.d.ts +15 -0
  122. package/dist/tools/read-sources.d.ts.map +1 -0
  123. package/dist/tools/read-sources.js +67 -0
  124. package/dist/tools/read-sources.js.map +1 -0
  125. package/dist/tools/resolve-area.d.ts +19 -0
  126. package/dist/tools/resolve-area.d.ts.map +1 -0
  127. package/dist/tools/resolve-area.js +147 -0
  128. package/dist/tools/resolve-area.js.map +1 -0
  129. package/dist/tools.d.ts +115 -0
  130. package/dist/tools.d.ts.map +1 -0
  131. package/dist/tools.js +439 -0
  132. package/dist/tools.js.map +1 -0
  133. package/dist/worker.d.ts +14 -0
  134. package/dist/worker.d.ts.map +1 -0
  135. package/dist/worker.js +135 -0
  136. package/dist/worker.js.map +1 -0
  137. package/package.json +26 -0
  138. package/src/graph.ts +200 -0
  139. package/src/index.ts +27 -0
  140. package/src/models.ts +20 -0
  141. package/src/nodes/clarify-node.ts +172 -0
  142. package/src/nodes/edit-node.ts +695 -0
  143. package/src/nodes/extract-node.ts +299 -0
  144. package/src/nodes/filter-node.ts +139 -0
  145. package/src/nodes/message.ts +695 -0
  146. package/src/nodes/vote-node.ts +354 -0
  147. package/src/nodes/vote.ts +354 -0
  148. package/src/runtime/auth.ts +63 -0
  149. package/src/runtime/dry-run.ts +303 -0
  150. package/src/runtime/queue.ts +53 -0
  151. package/src/runtime/redis.ts +38 -0
  152. package/src/runtime/worker.ts +167 -0
  153. package/src/tools/alert-history.ts +120 -0
  154. package/src/tools/betterstack-log.ts +102 -0
  155. package/src/tools/index.ts +23 -0
  156. package/src/tools/read-sources.ts +86 -0
  157. package/src/tools/resolve-area.ts +202 -0
  158. package/tsconfig.json +14 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Extract Node — LLM extraction from relevant channels.
3
+ */
4
+ import { type ValidatedExtraction } from "@easyoref/shared";
5
+ import type { AgentStateType } from "../graph.js";
6
+ export declare const extractAgent: import("langchain").ReactAgent<import("langchain").AgentTypeConfig<{
7
+ channel: string;
8
+ regionRelevance: number;
9
+ sourceTrust: number;
10
+ tone: "calm" | "neutral" | "alarmist";
11
+ confidence: number;
12
+ timeRelevance: number;
13
+ countryOrigin?: string | undefined;
14
+ rocketCount?: number | undefined;
15
+ isCassette?: boolean | undefined;
16
+ intercepted?: number | undefined;
17
+ interceptedQual?: {
18
+ type: "all";
19
+ } | {
20
+ type: "most";
21
+ } | {
22
+ type: "many";
23
+ } | {
24
+ type: "few";
25
+ } | {
26
+ type: "exists";
27
+ } | {
28
+ type: "none";
29
+ } | {
30
+ type: "more_than";
31
+ value: number;
32
+ } | {
33
+ type: "less_than";
34
+ value: number;
35
+ } | undefined;
36
+ seaImpact?: number | undefined;
37
+ seaImpactQual?: {
38
+ type: "all";
39
+ } | {
40
+ type: "most";
41
+ } | {
42
+ type: "many";
43
+ } | {
44
+ type: "few";
45
+ } | {
46
+ type: "exists";
47
+ } | {
48
+ type: "none";
49
+ } | {
50
+ type: "more_than";
51
+ value: number;
52
+ } | {
53
+ type: "less_than";
54
+ value: number;
55
+ } | undefined;
56
+ openAreaImpact?: number | undefined;
57
+ openAreaImpactQual?: {
58
+ type: "all";
59
+ } | {
60
+ type: "most";
61
+ } | {
62
+ type: "many";
63
+ } | {
64
+ type: "few";
65
+ } | {
66
+ type: "exists";
67
+ } | {
68
+ type: "none";
69
+ } | {
70
+ type: "more_than";
71
+ value: number;
72
+ } | {
73
+ type: "less_than";
74
+ value: number;
75
+ } | undefined;
76
+ hitsConfirmed?: number | undefined;
77
+ hitLocation?: string | undefined;
78
+ hitType?: "direct" | "shrapnel" | undefined;
79
+ hitDetail?: string | undefined;
80
+ casualties?: number | undefined;
81
+ injuries?: number | undefined;
82
+ injuriesCause?: "rocket" | "rushing_to_shelter" | undefined;
83
+ etaRefinedMinutes?: number | undefined;
84
+ rocketDetail?: string | undefined;
85
+ }, undefined, import("langchain").AnyAnnotationRoot, readonly import("langchain").AgentMiddleware<any, any, any, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>[], readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>>;
86
+ export declare const postFilter: (extractions: ValidatedExtraction[], alertId: string) => ValidatedExtraction[];
87
+ export declare const extractNode: (state: AgentStateType) => Promise<Partial<AgentStateType>>;
88
+ export declare const _test: {
89
+ readonly extractAgent: import("langchain").ReactAgent<import("langchain").AgentTypeConfig<{
90
+ channel: string;
91
+ regionRelevance: number;
92
+ sourceTrust: number;
93
+ tone: "calm" | "neutral" | "alarmist";
94
+ confidence: number;
95
+ timeRelevance: number;
96
+ countryOrigin?: string | undefined;
97
+ rocketCount?: number | undefined;
98
+ isCassette?: boolean | undefined;
99
+ intercepted?: number | undefined;
100
+ interceptedQual?: {
101
+ type: "all";
102
+ } | {
103
+ type: "most";
104
+ } | {
105
+ type: "many";
106
+ } | {
107
+ type: "few";
108
+ } | {
109
+ type: "exists";
110
+ } | {
111
+ type: "none";
112
+ } | {
113
+ type: "more_than";
114
+ value: number;
115
+ } | {
116
+ type: "less_than";
117
+ value: number;
118
+ } | undefined;
119
+ seaImpact?: number | undefined;
120
+ seaImpactQual?: {
121
+ type: "all";
122
+ } | {
123
+ type: "most";
124
+ } | {
125
+ type: "many";
126
+ } | {
127
+ type: "few";
128
+ } | {
129
+ type: "exists";
130
+ } | {
131
+ type: "none";
132
+ } | {
133
+ type: "more_than";
134
+ value: number;
135
+ } | {
136
+ type: "less_than";
137
+ value: number;
138
+ } | undefined;
139
+ openAreaImpact?: number | undefined;
140
+ openAreaImpactQual?: {
141
+ type: "all";
142
+ } | {
143
+ type: "most";
144
+ } | {
145
+ type: "many";
146
+ } | {
147
+ type: "few";
148
+ } | {
149
+ type: "exists";
150
+ } | {
151
+ type: "none";
152
+ } | {
153
+ type: "more_than";
154
+ value: number;
155
+ } | {
156
+ type: "less_than";
157
+ value: number;
158
+ } | undefined;
159
+ hitsConfirmed?: number | undefined;
160
+ hitLocation?: string | undefined;
161
+ hitType?: "direct" | "shrapnel" | undefined;
162
+ hitDetail?: string | undefined;
163
+ casualties?: number | undefined;
164
+ injuries?: number | undefined;
165
+ injuriesCause?: "rocket" | "rushing_to_shelter" | undefined;
166
+ etaRefinedMinutes?: number | undefined;
167
+ rocketDetail?: string | undefined;
168
+ }, undefined, import("langchain").AnyAnnotationRoot, readonly import("langchain").AgentMiddleware<any, any, any, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>[], readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>>;
169
+ readonly filterAgent: import("langchain").ReactAgent<import("langchain").AgentTypeConfig<{
170
+ relevantChannels: string[];
171
+ }, undefined, import("langchain").AnyAnnotationRoot, readonly import("langchain").AgentMiddleware<any, any, any, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>[], readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>>;
172
+ readonly postFilter: (extractions: ValidatedExtraction[], alertId: string) => ValidatedExtraction[];
173
+ };
174
+ //# sourceMappingURL=extract-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-node.d.ts","sourceRoot":"","sources":["../../src/nodes/extract-node.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAUL,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gUAwBvB,CAAC;AAaH,eAAO,MAAM,UAAU,GACrB,aAAa,mBAAmB,EAAE,EAClC,SAAS,MAAM,KACd,mBAAmB,EA4DrB,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,cAAc,KACpB,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAsKjC,CAAC;AAEF,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCA1OH,mBAAmB,EAAE,WACzB,MAAM,KACd,mBAAmB,EAAE;CA4Od,CAAC"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Extract Node — LLM extraction from relevant channels.
3
+ */
4
+ import * as logger from "@easyoref/monitoring";
5
+ import { config, ExtractionResultSchema, getCachedExtractions, saveCachedExtractions, setLastUpdateTs, textHash, toIsraelTime, } from "@easyoref/shared";
6
+ import { createAgent, providerStrategy } from "langchain";
7
+ import { extractModel } from "../models.js";
8
+ import { filterAgent } from "./filter-node.js";
9
+ export const extractAgent = createAgent({
10
+ model: extractModel,
11
+ responseFormat: providerStrategy(ExtractionResultSchema),
12
+ systemPrompt: `You analyze Telegram channel messages about a missile/rocket attack on Israel.
13
+ Extract structured data from the message.
14
+
15
+ CRITICAL — TIME VALIDATION:
16
+ - If post discusses events BEFORE alert time → time_relevance=0
17
+ - If post is generic military news not specific to THIS attack → time_relevance=0.2
18
+ - If post discusses current attack → time_relevance=1.0
19
+
20
+ MANDATORY METADATA: time_relevance, region_relevance, confidence, source_trust, tone.
21
+
22
+ PHASE-SPECIFIC:
23
+ - early_warning: Focus on country_origin, eta_refined_minutes, rocket_count, is_cassette. NOT: intercepted, hits, casualties.
24
+ - red_alert: Focus on country_origin, rocket_count, intercepted, sea_impact, open_area_impact. NOT: hits, casualties.
25
+ - resolved: All fields valid. Prioritize confirmed official reports.
26
+
27
+ RULES:
28
+ - Only extract concrete numbers explicitly stated. Never guess.
29
+ - If source says "all intercepted" without count, use intercepted=null, intercepted_qual="all".
30
+ - If message uses excessive caps/exclamations → tone="alarmist".
31
+ - For IDF posts about ongoing operations (not this attack) → time_relevance=0.
32
+ - CASUALTIES: Only set > 0 if text explicitly uses "killed", "dead", "fatality" (Hebrew: נהרג/מת, Russian: погиб/убит, English: killed/dead).`,
33
+ });
34
+ function getPhaseInstructions(alertType) {
35
+ switch (alertType) {
36
+ case "early_warning":
37
+ return `PHASE: EARLY WARNING. Focus on country_origin, eta_refined_minutes, rocket_count, is_cassette.`;
38
+ case "red_alert":
39
+ return `PHASE: RED ALERT. Focus on country_origin, rocket_count, intercepted, sea_impact, open_area_impact.`;
40
+ case "resolved":
41
+ return `PHASE: RESOLVED. All fields valid. Prioritize confirmed official reports.`;
42
+ }
43
+ }
44
+ export const postFilter = (extractions, alertId) => {
45
+ const validated = extractions.map((ext) => {
46
+ if (ext.timeRelevance < 0.5) {
47
+ return { ...ext, valid: false, rejectReason: "stale_post" };
48
+ }
49
+ const regionThreshold = ext.rocketCount != undefined &&
50
+ ext.intercepted == undefined &&
51
+ ext.interceptedQual == undefined &&
52
+ ext.hitsConfirmed == undefined &&
53
+ ext.casualties == undefined &&
54
+ ext.injuries == undefined
55
+ ? 0.3
56
+ : 0.5;
57
+ if (ext.regionRelevance < regionThreshold) {
58
+ return { ...ext, valid: false, rejectReason: "region_irrelevant" };
59
+ }
60
+ if (ext.sourceTrust < 0.4) {
61
+ return { ...ext, valid: false, rejectReason: "untrusted_source" };
62
+ }
63
+ if (ext.tone === "alarmist") {
64
+ return { ...ext, valid: false, rejectReason: "alarmist_tone" };
65
+ }
66
+ const hasData = ext.countryOrigin != undefined ||
67
+ ext.rocketCount != undefined ||
68
+ ext.isCassette != undefined ||
69
+ ext.intercepted != undefined ||
70
+ ext.interceptedQual != undefined ||
71
+ ext.hitsConfirmed != undefined ||
72
+ ext.casualties != undefined ||
73
+ ext.injuries != undefined ||
74
+ ext.etaRefinedMinutes != undefined;
75
+ if (!hasData) {
76
+ return { ...ext, valid: false, rejectReason: "no_data" };
77
+ }
78
+ const confidenceFloor = ext.rocketCount != undefined ? 0.2 : 0.3;
79
+ if (ext.confidence < confidenceFloor) {
80
+ return { ...ext, valid: false, rejectReason: "low_confidence" };
81
+ }
82
+ return { ...ext, valid: true };
83
+ });
84
+ const passed = validated.filter((ext) => ext.valid);
85
+ const rejected = validated.filter((ext) => !ext.valid);
86
+ logger.info("Agent: post-filter", {
87
+ alertId,
88
+ passed: passed.length,
89
+ rejected: rejected.length,
90
+ reasons: rejected.map((ext) => `${ext.channel}:${ext.rejectReason}`),
91
+ });
92
+ return validated;
93
+ };
94
+ export const extractNode = async (state) => {
95
+ if (!state.tracking || state.tracking.channelsWithUpdates.length === 0) {
96
+ logger.info("Agent: no channels with updates", { alertId: state.alertId });
97
+ return { extractions: [] };
98
+ }
99
+ const channels = state.tracking.channelsWithUpdates;
100
+ const channelSummaries = channels
101
+ .map((channel) => {
102
+ const messages = channel.unprocessedMessages
103
+ .map((message) => {
104
+ return ` [${toIsraelTime(message.timestamp)}] ${message.text.slice(0, 200)}`;
105
+ })
106
+ .join("\n");
107
+ return `${channel.channel} (${channel.unprocessedMessages.length} new):\n${messages}`;
108
+ })
109
+ .join("\n\n");
110
+ const regionHint = state.alertAreas.length > 0 ? state.alertAreas.join(", ") : "Israel";
111
+ const alertTime = toIsraelTime(state.alertTs);
112
+ const userPrompt = `Alert: ${regionHint} at ${alertTime}, phase: ${state.alertType}\n\nChannels:\n${channelSummaries}`;
113
+ let relevantChannels = [];
114
+ try {
115
+ const result = await filterAgent.invoke({ messages: [userPrompt] });
116
+ relevantChannels = result.structuredResponse?.relevantChannels ?? [];
117
+ }
118
+ catch {
119
+ relevantChannels = channels.map((c) => c.channel);
120
+ }
121
+ if (relevantChannels.length === 0) {
122
+ return { extractions: [] };
123
+ }
124
+ const postsToExtract = [];
125
+ for (const channel of channels) {
126
+ const match = relevantChannels.some((rc) => rc === channel.channel ||
127
+ rc === `@${channel.channel}` ||
128
+ `@${rc}` === channel.channel);
129
+ if (match) {
130
+ postsToExtract.push(...channel.unprocessedMessages);
131
+ }
132
+ }
133
+ if (postsToExtract.length === 0) {
134
+ return { extractions: [] };
135
+ }
136
+ const postHashMap = new Map();
137
+ for (const post of postsToExtract) {
138
+ const hash = textHash(post.channelId + "|" + post.text.slice(0, 800));
139
+ postHashMap.set(hash, post);
140
+ }
141
+ const allHashes = [...postHashMap.keys()];
142
+ const cached = await getCachedExtractions(allHashes);
143
+ const cachedResults = [];
144
+ const newPosts = [];
145
+ for (const [hash, post] of postHashMap) {
146
+ const cachedJson = cached.get(hash);
147
+ if (cachedJson) {
148
+ cachedResults.push(JSON.parse(cachedJson));
149
+ }
150
+ else {
151
+ newPosts.push(post);
152
+ }
153
+ }
154
+ if (newPosts.length === 0) {
155
+ const filtered = postFilter(cachedResults, state.alertId);
156
+ return { extractions: filtered };
157
+ }
158
+ const alertTimeIL = toIsraelTime(state.alertTs);
159
+ const nowIL = toIsraelTime(Date.now());
160
+ const phaseInstructions = getPhaseInstructions(state.alertType);
161
+ const enrichCtxParts = [];
162
+ if (state.previousEnrichment?.origin) {
163
+ enrichCtxParts.push(`Origin: ${state.previousEnrichment.origin}`);
164
+ }
165
+ if (state.previousEnrichment?.rocketCount) {
166
+ enrichCtxParts.push(`Rockets: ${state.previousEnrichment.rocketCount}`);
167
+ }
168
+ if (state.previousEnrichment?.intercepted) {
169
+ enrichCtxParts.push(`Intercepted: ${state.previousEnrichment.intercepted}`);
170
+ }
171
+ const enrichCtxLine = enrichCtxParts.length > 0
172
+ ? `EXISTING ENRICHMENT: ${enrichCtxParts.join(", ")}\n`
173
+ : "";
174
+ const newResults = await Promise.all(newPosts.map(async (post) => {
175
+ const postTimeIL = toIsraelTime(post.timestamp);
176
+ const postAgeMin = Math.round((state.alertTs - post.timestamp) / 60_000);
177
+ const postAgeSuffix = postAgeMin > 0
178
+ ? `(${postAgeMin} min BEFORE alert)`
179
+ : postAgeMin < 0
180
+ ? `(${Math.abs(postAgeMin)} min AFTER alert)`
181
+ : "(same time as alert)";
182
+ const contextHeader = `${phaseInstructions}\n\n` +
183
+ `Alert time: ${alertTimeIL} (Israel)\n` +
184
+ `Post time: ${postTimeIL} (Israel) ${postAgeSuffix}\n` +
185
+ `Current time: ${nowIL} (Israel)\n` +
186
+ `Alert region: ${regionHint}\n` +
187
+ `UI language: ${config.language}\n` +
188
+ enrichCtxLine;
189
+ try {
190
+ const result = await extractAgent.invoke({
191
+ messages: [
192
+ `${contextHeader}Channel: ${post.channelId}\n\nMessage:\n${post.text.slice(0, 800)}`,
193
+ ],
194
+ });
195
+ const extracted = result.structuredResponse;
196
+ return {
197
+ ...extracted,
198
+ channel: post.channelId,
199
+ messageUrl: post.sourceUrl,
200
+ timeRelevance: extracted?.timeRelevance ?? 0.5,
201
+ valid: true,
202
+ };
203
+ }
204
+ catch {
205
+ return {
206
+ channel: post.channelId,
207
+ regionRelevance: 0,
208
+ sourceTrust: 0,
209
+ tone: "neutral",
210
+ timeRelevance: 0,
211
+ confidence: 0,
212
+ valid: false,
213
+ rejectReason: "extraction_error",
214
+ };
215
+ }
216
+ }));
217
+ const cacheEntries = {};
218
+ newPosts.forEach((post, i) => {
219
+ const hash = textHash(post.channelId + "|" + post.text.slice(0, 800));
220
+ cacheEntries[hash] = JSON.stringify(newResults[i]);
221
+ });
222
+ await saveCachedExtractions(cacheEntries);
223
+ const results = [...cachedResults, ...newResults];
224
+ const filtered = postFilter(results, state.alertId);
225
+ await setLastUpdateTs(Date.now());
226
+ return { extractions: filtered };
227
+ };
228
+ export const _test = {
229
+ extractAgent,
230
+ filterAgent,
231
+ postFilter,
232
+ };
233
+ //# sourceMappingURL=extract-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-node.js","sourceRoot":"","sources":["../../src/nodes/extract-node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Extract Node — cheap LLM filter + expensive extraction.
3
+ */
4
+ import type { AgentStateType } from "../graph.js";
5
+ export declare const extractNode: (state: AgentStateType) => Promise<Partial<AgentStateType>>;
6
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/nodes/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,eAAO,MAAM,WAAW,GACtB,OAAO,cAAc,KACpB,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAwDjC,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Extract Node — cheap LLM filter + expensive extraction.
3
+ */
4
+ import * as logger from "@easyoref/monitoring";
5
+ import { config, setLastUpdateTs, } from "@easyoref/shared";
6
+ import { extractPosts, filterChannelsCheap, postFilter, } from "../extract.js";
7
+ export const extractNode = async (state) => {
8
+ if (!state.tracking || state.tracking.channels_with_updates.length === 0) {
9
+ logger.info("Agent: no channels with updates", {
10
+ alertId: state.alertId,
11
+ });
12
+ return { extractions: [] };
13
+ }
14
+ const relevantChannels = await filterChannelsCheap(state.tracking, state.alertAreas, state.alertTs, state.alertType);
15
+ if (relevantChannels.length === 0) {
16
+ logger.info("Agent: no relevant channels after cheap filter", {
17
+ alertId: state.alertId,
18
+ });
19
+ return { extractions: [] };
20
+ }
21
+ const postsToExtract = [];
22
+ for (const channel of state.tracking.channels_with_updates) {
23
+ const match = relevantChannels.some((rc) => rc === channel.channel ||
24
+ rc === `@${channel.channel}` ||
25
+ `@${rc}` === channel.channel);
26
+ if (match) {
27
+ postsToExtract.push(...channel.last_tracked_messages);
28
+ }
29
+ }
30
+ if (postsToExtract.length === 0) {
31
+ logger.info("Agent: no posts from relevant channels", {
32
+ alertId: state.alertId,
33
+ });
34
+ return { extractions: [] };
35
+ }
36
+ const context = {
37
+ alertTs: state.alertTs,
38
+ alertType: state.alertType,
39
+ alertAreas: state.alertAreas,
40
+ alertId: state.alertId,
41
+ language: config.language,
42
+ existingEnrichment: state.previousEnrichment,
43
+ };
44
+ const raw = await extractPosts(postsToExtract, context);
45
+ const filtered = postFilter(raw, state.alertId);
46
+ await setLastUpdateTs(Date.now());
47
+ return { extractions: filtered };
48
+ };
49
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/nodes/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EACL,MAAM,EACN,eAAe,GAEhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,UAAU,GAEX,MAAM,eAAe,CAAC;AAGvB,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC9B,KAAqB,EACa,EAAE;IACpC,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,qBAAqB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE;YAC7C,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAC;QACH,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,mBAAmB,CAChD,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,CAChB,CAAC;IAEF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,gDAAgD,EAAE;YAC5D,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAC;QACH,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,cAAc,GAAqB,EAAE,CAAC;IAC5C,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,qBAAqB,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CACjC,CAAC,EAAE,EAAE,EAAE,CACL,EAAE,KAAK,OAAO,CAAC,OAAO;YACtB,EAAE,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE;YAC5B,IAAI,EAAE,EAAE,KAAK,OAAO,CAAC,OAAO,CAC/B,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACV,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE;YACpD,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAC;QACH,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,OAAO,GAAmB;QAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;KAC7C,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAEhD,MAAM,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAElC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Filter Agent — cheap LLM pre-filter for channel relevance.
3
+ */
4
+ import { type AlertType, type ChannelTracking } from "@easyoref/shared";
5
+ import { ChatOpenRouter } from "@langchain/openrouter";
6
+ export declare const filterModel: ChatOpenRouter;
7
+ export declare const filterAgent: import("langchain").ReactAgent<import("langchain").AgentTypeConfig<{
8
+ relevant_channels: string[];
9
+ }, undefined, import("langchain").AnyAnnotationRoot, readonly import("langchain").AgentMiddleware<any, any, any, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>[], readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>>;
10
+ export declare const filterChannels: (tracking: ChannelTracking, alertAreas: string[], alertTs: number, alertType: AlertType) => Promise<string[]>;
11
+ //# sourceMappingURL=filter-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-agent.d.ts","sourceRoot":"","sources":["../../src/nodes/filter-agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,eAAO,MAAM,WAAW,gBAKtB,CAAC;AAEH,eAAO,MAAM,WAAW;;gUAkBtB,CAAC;AAEH,eAAO,MAAM,cAAc,GACzB,UAAU,eAAe,EACzB,YAAY,MAAM,EAAE,EACpB,SAAS,MAAM,EACf,WAAW,SAAS,KACnB,OAAO,CAAC,MAAM,EAAE,CA6BlB,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Filter Agent — cheap LLM pre-filter for channel relevance.
3
+ */
4
+ import { FilterOutputSchema, } from "@easyoref/shared";
5
+ import { config } from "@easyoref/shared";
6
+ import { createAgent, providerStrategy } from "langchain";
7
+ import { ChatOpenRouter } from "@langchain/openrouter";
8
+ export const filterModel = new ChatOpenRouter({
9
+ apiKey: config.agent.apiKey,
10
+ model: config.agent.filterModel,
11
+ temperature: 0,
12
+ maxTokens: 200,
13
+ });
14
+ export const filterAgent = createAgent({
15
+ model: filterModel,
16
+ responseFormat: providerStrategy(FilterOutputSchema),
17
+ systemPrompt: `You pre-filter Telegram channels for an Israeli missile alert system.
18
+ Given channels with their latest messages, identify which contain IMPORTANT military intel:
19
+ - Country of origin (where rockets/missiles launched from)
20
+ - Impact location (where they hit)
21
+ - Warhead type / cassette munitions
22
+ - Damage / destruction reports
23
+ - Interception reports (Iron Dome, David's Sling)
24
+ - Casualty / injury reports
25
+
26
+ IGNORE channels that only contain:
27
+ - Panic, speculation, or unverified rumors
28
+ - Rehashes of official alerts without new data
29
+ - General commentary without actionable facts
30
+
31
+ Return relevant channel names.`,
32
+ });
33
+ export const filterChannels = async (tracking, alertAreas, alertTs, alertType) => {
34
+ const channels = tracking.channels_with_updates;
35
+ if (channels.length === 0)
36
+ return [];
37
+ const channelSummaries = channels
38
+ .map((channel) => {
39
+ const messages = channel.last_tracked_messages
40
+ .map((message) => {
41
+ return ` [${new Date(message.timestamp).toLocaleTimeString("he-IL", { timeZone: "Asia/Jerusalem" })}] ${message.text.slice(0, 200)}`;
42
+ })
43
+ .join("\n");
44
+ return `${channel.channel} (${channel.last_tracked_messages.length} new):\n${messages}`;
45
+ })
46
+ .join("\n\n");
47
+ const regionHint = alertAreas.length > 0 ? alertAreas.join(", ") : "Israel";
48
+ const alertTime = new Date(alertTs).toLocaleTimeString("he-IL", {
49
+ timeZone: "Asia/Jerusalem",
50
+ });
51
+ const userPrompt = `Alert: ${regionHint} at ${alertTime}, phase: ${alertType}\n\nChannels:\n${channelSummaries}`;
52
+ try {
53
+ const result = await filterAgent.invoke({ messages: [userPrompt] });
54
+ return result.structuredResponse?.relevant_channels ?? [];
55
+ }
56
+ catch {
57
+ return channels.map((channel) => channel.channel);
58
+ }
59
+ };
60
+ //# sourceMappingURL=filter-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-agent.js","sourceRoot":"","sources":["../../src/nodes/filter-agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,kBAAkB,GAGnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,cAAc,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;IAC3B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW;IAC/B,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,GAAG;CACf,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC;IACrC,KAAK,EAAE,WAAW;IAClB,cAAc,EAAE,gBAAgB,CAAC,kBAAkB,CAAC;IACpD,YAAY,EAAE;;;;;;;;;;;;;;+BAce;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,QAAyB,EACzB,UAAoB,EACpB,OAAe,EACf,SAAoB,EACD,EAAE;IACrB,MAAM,QAAQ,GAAG,QAAQ,CAAC,qBAAqB,CAAC;IAChD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,gBAAgB,GAAG,QAAQ;SAC9B,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QACf,MAAM,QAAQ,GAAG,OAAO,CAAC,qBAAqB;aAC3C,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YACf,OAAO,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACxI,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,GAAG,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,qBAAqB,CAAC,MAAM,WAAW,QAAQ,EAAE,CAAC;IAC1F,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC5E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QAC9D,QAAQ,EAAE,gBAAgB;KAC3B,CAAC,CAAC;IAEH,MAAM,UAAU,GACd,UAAU,UAAU,OAAO,SAAS,YAAY,SAAS,kBAAkB,gBAAgB,EAAE,CAAC;IAEhG,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC,kBAAkB,EAAE,iBAAiB,IAAI,EAAE,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Filter Node — deterministic pre-filters + LLM channel relevance.
3
+ */
4
+ import type { AgentStateType } from "../graph.js";
5
+ export declare const filterAgent: import("langchain").ReactAgent<import("langchain").AgentTypeConfig<{
6
+ relevantChannels: string[];
7
+ }, undefined, import("langchain").AnyAnnotationRoot, readonly import("langchain").AgentMiddleware<any, any, any, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>[], readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>>;
8
+ export declare const filterNode: (state: AgentStateType) => Promise<Partial<AgentStateType>>;
9
+ //# sourceMappingURL=filter-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-node.d.ts","sourceRoot":"","sources":["../../src/nodes/filter-node.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD,eAAO,MAAM,WAAW;;gUAkBtB,CAAC;AAsEH,eAAO,MAAM,UAAU,GACrB,OAAO,cAAc,KACpB,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAyBjC,CAAC"}