offnadir-delta-mcp 1.3.26 → 1.4.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/catalog.js CHANGED
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Static introspection catalog for the Off-Nadir Delta stdio MCP proxy.
3
3
  *
4
+ * AUTO-GENERATED from the Off-Nadir Delta contract registry
5
+ * (src/lib/api/toolRegistry.ts + src/lib/api/mcpResources.ts) by
6
+ * scripts/gen-contract-artifacts.mjs — DO NOT EDIT BY HAND.
7
+ *
4
8
  * These tool / resource / prompt definitions mirror the live remote MCP server
5
9
  * (https://offnadir-delta.com/api/v1/mcp) so that `tools/list`, `resources/list`,
6
10
  * and `prompts/list` answer instantly and WITHOUT credentials — this is what
@@ -8,52 +12,160 @@
8
12
  *
9
13
  * Actual `tools/call` / `resources/read` / `prompts/get` requests are forwarded
10
14
  * to the remote server with the caller's OFFNADIR_DELTA_API_KEY (see index.ts).
11
- *
12
- * Keep in sync with the source of truth: src/app/api/v1/mcp/route.ts in the
13
- * Off-Nadir Delta app. Token costs and enums are inlined from that surface.
14
15
  */
15
- const DELTA_CATEGORIES = [
16
- 'kinetic',
17
- 'armed_conflict',
18
- 'maritime',
19
- 'natural_disaster',
20
- 'infrastructure',
21
- 'aviation',
22
- 'humanitarian',
23
- 'protest',
24
- 'diplomacy',
25
- 'other',
26
- ];
27
- const MARKET_IMPACTS = [
28
- 'oil',
29
- 'natural_gas',
30
- 'grain',
31
- 'shipping',
32
- 'defense',
33
- 'metals',
34
- 'semiconductors',
35
- 'fx',
36
- 'equities',
37
- ];
38
- const IMAGERY_COLLECTIONS = [
39
- 'sentinel-1-grd',
40
- 'sentinel-1-rtc',
41
- 'sentinel-2-l2a',
42
- 'OPERA_L2_RTC-S1_V1',
43
- ];
44
- const SIGNALS_MAX_DAYS = 30;
45
- const SIGNALS_MAX_LIMIT = 500;
46
- const BBOX = {
47
- type: 'array',
48
- items: { type: 'number' },
49
- minItems: 4,
50
- maxItems: 4,
51
- };
16
+ // Generated for Off-Nadir Delta MCP 1.4.0.
52
17
  export const TOOLS = [
53
18
  {
54
- name: 'query_signals',
55
- annotations: { "readOnlyHint": false, "openWorldHint": false, "destructiveHint": true },
56
- outputSchema: {
19
+ "name": "query_signals",
20
+ "description": "Query geolocated world event signals (Delta Signals: geopolitical, security, disaster, and infrastructure events distilled from global news media, AI-enriched with severity/GEOINT scores and satellite-collection recommendations). Filter by bounding box, date window, and category. Costs 3 token(s) per call, charged to the API key owner's balance.",
21
+ "inputSchema": {
22
+ "type": "object",
23
+ "properties": {
24
+ "bbox": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "number"
28
+ },
29
+ "minItems": 4,
30
+ "maxItems": 4,
31
+ "description": "Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Omit for worldwide."
32
+ },
33
+ "date": {
34
+ "type": "string",
35
+ "description": "Window end date, YYYY-MM-DD (UTC). Defaults to today."
36
+ },
37
+ "days": {
38
+ "type": "integer",
39
+ "minimum": 1,
40
+ "maximum": 30,
41
+ "description": "Window length in days ending on `date`. Defaults to 1."
42
+ },
43
+ "categories": {
44
+ "type": "array",
45
+ "items": {
46
+ "type": "string",
47
+ "enum": [
48
+ "kinetic",
49
+ "armed_conflict",
50
+ "maritime",
51
+ "natural_disaster",
52
+ "infrastructure",
53
+ "aviation",
54
+ "humanitarian",
55
+ "protest",
56
+ "diplomacy",
57
+ "other"
58
+ ]
59
+ },
60
+ "description": "Restrict to these categories. Omit for all."
61
+ },
62
+ "markets": {
63
+ "type": "array",
64
+ "items": {
65
+ "type": "string",
66
+ "enum": [
67
+ "oil",
68
+ "natural_gas",
69
+ "grain",
70
+ "shipping",
71
+ "defense",
72
+ "metals",
73
+ "semiconductors",
74
+ "fx",
75
+ "equities"
76
+ ]
77
+ },
78
+ "description": "Restrict to signals AI-tagged as exposing these financial markets via a direct physical/supply channel (informational only, not investment advice). Omit for all."
79
+ },
80
+ "limit": {
81
+ "type": "integer",
82
+ "minimum": 1,
83
+ "maximum": 500,
84
+ "description": "Maximum rows per page. Defaults to 100."
85
+ },
86
+ "cursor": {
87
+ "type": "string",
88
+ "description": "Opaque pagination cursor from a previous response's meta.next_cursor."
89
+ },
90
+ "minSeverity": {
91
+ "type": "number",
92
+ "minimum": 0,
93
+ "maximum": 10,
94
+ "description": "Keep only signals with severity_score >= this (0-10)."
95
+ },
96
+ "escalating": {
97
+ "type": "boolean",
98
+ "description": "Keep only signals whose escalation_trend is \"escalating\"."
99
+ },
100
+ "sort": {
101
+ "type": "string",
102
+ "enum": [
103
+ "severity",
104
+ "recent",
105
+ "sources",
106
+ "geoint"
107
+ ],
108
+ "description": "Result ordering. Omit for the feed's default ranking. \"geoint\" ranks by the continuous GEOINT collection priority (intelligence.collection_priority) — an imageability gate times tasking value (severity, urgency, information gain, corroboration, escalation, market) — so imageable, decision-relevant events (e.g. a high-severity escalating strike) rise and non-observable news noise sinks. This is NOT the saturated geoint_score."
109
+ },
110
+ "updatedSince": {
111
+ "type": "string",
112
+ "description": "Differential fetch: only signals (re)enriched at/after this ISO 8601 timestamp. Ignores the date window. Response signals carry last_updated_at."
113
+ },
114
+ "createdSince": {
115
+ "type": "string",
116
+ "description": "Differential fetch: only signals first enriched at/after this ISO 8601 timestamp."
117
+ },
118
+ "observability": {
119
+ "type": "string",
120
+ "enum": [
121
+ "observable",
122
+ "not-observable"
123
+ ],
124
+ "description": "Keep only signals with this satellite observability — whether a physical mark is imageable at all (intelligence.satellite_observability)."
125
+ },
126
+ "observabilityStatus": {
127
+ "type": "string",
128
+ "enum": [
129
+ "observable",
130
+ "not_observable",
131
+ "insufficient_detail"
132
+ ],
133
+ "description": "3-state observability filter. Unlike `observability` (binary), this exposes `insufficient_detail` — signals where imageability is unknown (no RS enrichment yet, unresolved location, or an Impossible verdict rescued because the event reads kinetic). `not_observable` = a considered \"nothing to image\"; neither bucket leaks insufficient_detail."
134
+ },
135
+ "openData": {
136
+ "type": "string",
137
+ "enum": [
138
+ "sufficient",
139
+ "commercial-recommended",
140
+ "not-applicable"
141
+ ],
142
+ "description": "Keep only signals with this open-data sufficiency — free imagery is enough vs commercial tasking recommended (intelligence.open_data_sufficiency)."
143
+ },
144
+ "minInformationGain": {
145
+ "type": "number",
146
+ "minimum": 0,
147
+ "maximum": 1,
148
+ "description": "Keep only signals whose intelligence.expected_information_gain >= this (0-1)."
149
+ },
150
+ "taskableOnly": {
151
+ "type": "boolean",
152
+ "description": "Keep only signals whose coordinate is search_ready — GEO-READY ONLY: drops country centroids, ADM1 mismatches, reporting-dateline fallbacks and unresolved fixes (intelligence.geo_validation.search_ready). It does NOT imply observable or quality-passed, so not-observable / insufficient-detail / quality-failed signals can still appear. For automated imagery tasking use collectionReadyOnly (or combine with observability:\"observable\")."
153
+ },
154
+ "collectionReadyOnly": {
155
+ "type": "boolean",
156
+ "description": "STRICT tasking-candidate filter: search_ready AND observability=observable AND quality.status!=failed AND a concrete collection plan (rs_target + rs_reason present) AND an event coordinate. The safe input set for automated imagery tasking — a superset of every gate taskableOnly alone does not check."
157
+ },
158
+ "responseFormat": {
159
+ "type": "string",
160
+ "enum": [
161
+ "concise",
162
+ "detailed"
163
+ ],
164
+ "description": "Per-signal field detail. \"concise\" (default) returns the key decision + GEOINT fields (id, date, category, title, location, lat/lng, severity/geoint scores, collection_priority, escalation, market, rs_level/rs_sensor, observability, observability_status, verification_status, geo_status, search_ready, article_count, independent_source_count, information_gain) — cheaper to scan. \"detailed\" returns the full Signal object (shape per the signals://schema resource)."
165
+ }
166
+ }
167
+ },
168
+ "outputSchema": {
57
169
  "type": "object",
58
170
  "properties": {
59
171
  "meta": {
@@ -76,44 +188,59 @@ export const TOOLS = [
76
188
  "signals"
77
189
  ]
78
190
  },
79
- description: 'Query geolocated world event signals (Delta Signals: geopolitical, security, disaster, and ' +
80
- 'infrastructure events distilled from global news media, AI-enriched with severity/GEOINT scores ' +
81
- 'and satellite-collection recommendations). Filter by bounding box, date window, and category. ' +
82
- "Costs 3 token(s) per call, charged to the API key owner's balance.",
83
- inputSchema: {
84
- type: 'object',
85
- properties: {
86
- bbox: { ...BBOX, description: 'Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Omit for worldwide.' },
87
- date: { type: 'string', description: 'Window end date, YYYY-MM-DD (UTC). Defaults to today.' },
88
- days: { type: 'integer', minimum: 1, maximum: SIGNALS_MAX_DAYS, description: 'Window length in days ending on `date`. Defaults to 1.' },
89
- categories: { type: 'array', items: { type: 'string', enum: [...DELTA_CATEGORIES] }, description: 'Restrict to these categories. Omit for all.' },
90
- markets: {
91
- type: 'array',
92
- items: { type: 'string', enum: [...MARKET_IMPACTS] },
93
- description: 'Restrict to signals AI-tagged as exposing these financial markets via a direct ' +
94
- 'physical/supply channel (informational only, not investment advice). Omit for all.',
95
- },
96
- limit: { type: 'integer', minimum: 1, maximum: SIGNALS_MAX_LIMIT, description: 'Maximum rows per page. Defaults to 100.' },
97
- cursor: { type: 'string', description: "Opaque pagination cursor from a previous response's meta.next_cursor." },
98
- minSeverity: { type: 'number', minimum: 0, maximum: 10, description: 'Keep only signals with severity_score >= this (0-10).' },
99
- escalating: { type: 'boolean', description: 'Keep only signals whose escalation_trend is "escalating".' },
100
- sort: { type: 'string', enum: ['severity', 'recent', 'sources', 'geoint'], description: "Result ordering. Omit for the feed's default ranking. \"geoint\" ranks by the continuous GEOINT collection priority (intelligence.collection_priority) — an imageability gate times tasking value (severity, urgency, information gain, corroboration, escalation, market) — so imageable, decision-relevant events (e.g. a high-severity escalating strike) rise and non-observable news noise sinks. This is NOT the saturated geoint_score." },
101
- updatedSince: { type: 'string', description: 'Differential fetch: only signals (re)enriched at/after this ISO 8601 timestamp. Ignores the date window. Response signals carry last_updated_at.' },
102
- createdSince: { type: 'string', description: 'Differential fetch: only signals first enriched at/after this ISO 8601 timestamp.' },
103
- observability: { type: 'string', enum: ['observable', 'not-observable'], description: 'Keep only signals with this satellite observability — whether a physical mark is imageable at all (intelligence.satellite_observability).' },
104
- observabilityStatus: { type: 'string', enum: ['observable', 'not_observable', 'insufficient_detail'], description: '3-state observability filter. Unlike `observability` (binary), this exposes `insufficient_detail` — signals where imageability is unknown (no RS enrichment yet, unresolved location, or an Impossible verdict rescued because the event reads kinetic). `not_observable` = a considered "nothing to image"; neither bucket leaks insufficient_detail.' },
105
- openData: { type: 'string', enum: ['sufficient', 'commercial-recommended', 'not-applicable'], description: 'Keep only signals with this open-data sufficiency — free imagery is enough vs commercial tasking recommended (intelligence.open_data_sufficiency).' },
106
- minInformationGain: { type: 'number', minimum: 0, maximum: 1, description: 'Keep only signals whose intelligence.expected_information_gain >= this (0-1).' },
107
- taskableOnly: { type: 'boolean', description: 'Keep only signals whose coordinate is search_ready — GEO-READY ONLY: drops country centroids, ADM1 mismatches, reporting-dateline fallbacks and unresolved fixes (intelligence.geo_validation.search_ready). It does NOT imply observable or quality-passed, so not-observable / insufficient-detail / quality-failed signals can still appear. For automated imagery tasking use collectionReadyOnly (or combine with observability:"observable").' },
108
- collectionReadyOnly: { type: 'boolean', description: 'STRICT tasking-candidate filter: search_ready AND observability=observable AND quality.status!=failed AND a concrete collection plan (rs_target + rs_reason present) AND an event coordinate. The safe input set for automated imagery tasking — a superset of every gate taskableOnly alone does not check.' },
109
- responseFormat: { type: 'string', enum: ['concise', 'detailed'], description: 'Per-signal field detail. "concise" (default) returns key decision + GEOINT fields (incl. collection_priority, the corrected 0-100 ranking value); "detailed" returns the full Signal object.' },
110
- },
111
- },
191
+ "annotations": {
192
+ "readOnlyHint": false,
193
+ "openWorldHint": false,
194
+ "destructiveHint": true
195
+ }
112
196
  },
113
197
  {
114
- name: 'query_stats',
115
- annotations: { "readOnlyHint": false, "openWorldHint": false, "destructiveHint": true },
116
- outputSchema: {
198
+ "name": "query_stats",
199
+ "description": "Aggregate statistics over the signal corpus — total event count plus per-category and per-day breakdown (trend) for a bounding box and date window. Cheaper than query_signals (returns roll-ups, not rows). NOTE the unit: `total` counts article-deduped events (meta.population = article_deduped_events), which is NOT cluster-collapsed, so it is >= the query_signals count for the same window. Costs 1 token(s) per call.",
200
+ "inputSchema": {
201
+ "type": "object",
202
+ "properties": {
203
+ "bbox": {
204
+ "type": "array",
205
+ "items": {
206
+ "type": "number"
207
+ },
208
+ "minItems": 4,
209
+ "maxItems": 4,
210
+ "description": "Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Omit for worldwide."
211
+ },
212
+ "date": {
213
+ "type": "string",
214
+ "description": "Window end date, YYYY-MM-DD (UTC). Defaults to today."
215
+ },
216
+ "days": {
217
+ "type": "integer",
218
+ "minimum": 1,
219
+ "maximum": 30,
220
+ "description": "Window length in days. Defaults to 1."
221
+ },
222
+ "categories": {
223
+ "type": "array",
224
+ "items": {
225
+ "type": "string",
226
+ "enum": [
227
+ "kinetic",
228
+ "armed_conflict",
229
+ "maritime",
230
+ "natural_disaster",
231
+ "infrastructure",
232
+ "aviation",
233
+ "humanitarian",
234
+ "protest",
235
+ "diplomacy",
236
+ "other"
237
+ ]
238
+ },
239
+ "description": "Restrict to these categories. Omit for all."
240
+ }
241
+ }
242
+ },
243
+ "outputSchema": {
117
244
  "type": "object",
118
245
  "properties": {
119
246
  "meta": {
@@ -133,25 +260,77 @@ export const TOOLS = [
133
260
  "stats"
134
261
  ]
135
262
  },
136
- description: 'Aggregate statistics over the signal corpus — total event count plus per-category and per-day ' +
137
- 'breakdown (trend) for a bounding box and date window. Cheaper than query_signals (returns ' +
138
- 'roll-ups, not rows). NOTE the unit: `total` counts article-deduped events (meta.population = ' +
139
- 'article_deduped_events), which is NOT cluster-collapsed, so it is >= the query_signals count ' +
140
- 'for the same window. Costs 1 token(s) per call.',
141
- inputSchema: {
142
- type: 'object',
143
- properties: {
144
- bbox: { ...BBOX, description: 'Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Omit for worldwide.' },
145
- date: { type: 'string', description: 'Window end date, YYYY-MM-DD (UTC). Defaults to today.' },
146
- days: { type: 'integer', minimum: 1, maximum: SIGNALS_MAX_DAYS, description: 'Window length in days. Defaults to 1.' },
147
- categories: { type: 'array', items: { type: 'string', enum: [...DELTA_CATEGORIES] }, description: 'Restrict to these categories. Omit for all.' },
148
- },
149
- },
263
+ "annotations": {
264
+ "readOnlyHint": false,
265
+ "openWorldHint": false,
266
+ "destructiveHint": true
267
+ }
150
268
  },
151
269
  {
152
- name: 'query_hotspots',
153
- annotations: { "readOnlyHint": false, "openWorldHint": false, "destructiveHint": true },
154
- outputSchema: {
270
+ "name": "query_hotspots",
271
+ "description": "Geographic hotspots — signal density grid-binned into cells, ranked by event count, each with peak severity, the categories present, and up to 5 representative event_ids (trace a cell back to its signals). Use to find WHERE activity is concentrating. NOTE the unit: cells count satellite-observable points (meta.population = rs_observable_points); meta reports source_point_count and dropped_by_geo_count / dropped_by_severity_count so point_count is fully accountable. Costs 1 token(s) per call.",
272
+ "inputSchema": {
273
+ "type": "object",
274
+ "properties": {
275
+ "bbox": {
276
+ "type": "array",
277
+ "items": {
278
+ "type": "number"
279
+ },
280
+ "minItems": 4,
281
+ "maxItems": 4,
282
+ "description": "Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Omit for worldwide."
283
+ },
284
+ "date": {
285
+ "type": "string",
286
+ "description": "Window end date, YYYY-MM-DD (UTC). Defaults to today."
287
+ },
288
+ "days": {
289
+ "type": "integer",
290
+ "minimum": 1,
291
+ "maximum": 30,
292
+ "description": "Window length in days. Defaults to 1."
293
+ },
294
+ "categories": {
295
+ "type": "array",
296
+ "items": {
297
+ "type": "string",
298
+ "enum": [
299
+ "kinetic",
300
+ "armed_conflict",
301
+ "maritime",
302
+ "natural_disaster",
303
+ "infrastructure",
304
+ "aviation",
305
+ "humanitarian",
306
+ "protest",
307
+ "diplomacy",
308
+ "other"
309
+ ]
310
+ },
311
+ "description": "Restrict to these categories. Omit for all."
312
+ },
313
+ "precision": {
314
+ "type": "number",
315
+ "minimum": 0.1,
316
+ "maximum": 5,
317
+ "description": "Grid cell size in decimal degrees. Defaults to 1."
318
+ },
319
+ "minSeverity": {
320
+ "type": "number",
321
+ "minimum": 0,
322
+ "maximum": 10,
323
+ "description": "Keep only points with severity_score >= this."
324
+ },
325
+ "limit": {
326
+ "type": "integer",
327
+ "minimum": 1,
328
+ "maximum": 500,
329
+ "description": "Max source points sampled before grid-binning — NOT the number of cells returned. Defaults to 500 (the max). Lower values sample fewer events and fragment clusters (each cell trends toward count 1), so leave at the default for a representative density map."
330
+ }
331
+ }
332
+ },
333
+ "outputSchema": {
155
334
  "type": "object",
156
335
  "properties": {
157
336
  "meta": {
@@ -174,29 +353,25 @@ export const TOOLS = [
174
353
  "hotspots"
175
354
  ]
176
355
  },
177
- description: 'Geographic hotspots — signal density grid-binned into cells, ranked by event count, each with ' +
178
- 'peak severity, the categories present, and up to 5 representative event_ids (trace a cell back ' +
179
- 'to its signals). Use to find WHERE activity is concentrating. NOTE the unit: cells count ' +
180
- 'satellite-observable points (meta.population = rs_observable_points); meta reports ' +
181
- 'source_point_count and dropped_by_geo_count / dropped_by_severity_count so point_count is fully ' +
182
- 'accountable. Costs 1 token(s) per call.',
183
- inputSchema: {
184
- type: 'object',
185
- properties: {
186
- bbox: { ...BBOX, description: 'Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Omit for worldwide.' },
187
- date: { type: 'string', description: 'Window end date, YYYY-MM-DD (UTC). Defaults to today.' },
188
- days: { type: 'integer', minimum: 1, maximum: SIGNALS_MAX_DAYS, description: 'Window length in days. Defaults to 1.' },
189
- categories: { type: 'array', items: { type: 'string', enum: [...DELTA_CATEGORIES] }, description: 'Restrict to these categories. Omit for all.' },
190
- precision: { type: 'number', minimum: 0.1, maximum: 5, description: 'Grid cell size in decimal degrees. Defaults to 1.' },
191
- minSeverity: { type: 'number', minimum: 0, maximum: 10, description: 'Keep only points with severity_score >= this.' },
192
- limit: { type: 'integer', minimum: 1, maximum: SIGNALS_MAX_LIMIT, description: 'Max source points sampled before grid-binning — NOT the number of cells returned. Defaults to 500 (the max). Lower values sample fewer events and fragment clusters (each cell trends toward count 1), so leave at the default for a representative density map.' },
193
- },
194
- },
356
+ "annotations": {
357
+ "readOnlyHint": false,
358
+ "openWorldHint": false,
359
+ "destructiveHint": true
360
+ }
195
361
  },
196
362
  {
197
- name: 'get_world_brief',
198
- annotations: { "readOnlyHint": true, "openWorldHint": false, "destructiveHint": false },
199
- outputSchema: {
363
+ "name": "get_world_brief",
364
+ "description": "Fetch the Daily World Brief — an AI-synthesized OSINT/GEOINT digest of the previous UTC day's worldwide event signals (headline, executive summary, top developments with why-it-matters and what-to-watch, per-theme roll-up, ranked signals). Free of token charges. The result includes a freshness object (brief_date, age_hours, is_stale) — if is_stale is true this is the latest published brief and a newer day is not yet available, so relay it as possibly out of date.",
365
+ "inputSchema": {
366
+ "type": "object",
367
+ "properties": {
368
+ "date": {
369
+ "type": "string",
370
+ "description": "Brief date, YYYY-MM-DD (UTC). Defaults to the latest available."
371
+ }
372
+ }
373
+ },
374
+ "outputSchema": {
200
375
  "type": "object",
201
376
  "properties": {
202
377
  "brief": {
@@ -211,20 +386,20 @@ export const TOOLS = [
211
386
  "brief"
212
387
  ]
213
388
  },
214
- description: "Fetch the Daily World Brief — an AI-synthesized OSINT/GEOINT digest of the previous UTC day's " +
215
- 'worldwide event signals (headline, executive summary, top developments with why-it-matters and ' +
216
- 'what-to-watch, per-theme roll-up, ranked signals). Free of token charges.',
217
- inputSchema: {
218
- type: 'object',
219
- properties: {
220
- date: { type: 'string', description: 'Brief date, YYYY-MM-DD (UTC). Defaults to the latest available.' },
221
- },
222
- },
389
+ "annotations": {
390
+ "readOnlyHint": true,
391
+ "openWorldHint": false,
392
+ "destructiveHint": false
393
+ }
223
394
  },
224
395
  {
225
- name: 'get_usage',
226
- annotations: { "readOnlyHint": true, "openWorldHint": false, "destructiveHint": false },
227
- outputSchema: {
396
+ "name": "get_usage",
397
+ "description": "Check the calling key's remaining token balance and plan capabilities — monthly allocation, tokens used this period, tokens remaining, and whether the plan includes AI tools over the API (assess_signal / ask_analyst). Use this to pre-flight a metered call: decide whether enough balance is left before spending. Free of token charges.",
398
+ "inputSchema": {
399
+ "type": "object",
400
+ "properties": {}
401
+ },
402
+ "outputSchema": {
228
403
  "type": "object",
229
404
  "properties": {
230
405
  "tokens": {
@@ -267,16 +442,99 @@ export const TOOLS = [
267
442
  "plan"
268
443
  ]
269
444
  },
270
- description: "Check the calling key's remaining token balance and plan capabilities — monthly allocation, " +
271
- 'tokens used this period, tokens remaining, and whether the plan includes AI tools over the API ' +
272
- '(assess_signal / ask_analyst). Use this to pre-flight a metered call: decide whether enough ' +
273
- 'balance is left before spending. Free of token charges.',
274
- inputSchema: { type: 'object', properties: {} },
445
+ "annotations": {
446
+ "readOnlyHint": true,
447
+ "openWorldHint": false,
448
+ "destructiveHint": false
449
+ }
275
450
  },
276
451
  {
277
- name: 'search_imagery',
278
- annotations: { "readOnlyHint": false, "openWorldHint": false, "destructiveHint": true },
279
- outputSchema: {
452
+ "name": "search_imagery",
453
+ "description": "Search the satellite imagery catalog (Sentinel-1/2, OPERA RTC-S1) for scenes over an area and date window — the natural follow-up to a signal (find imagery over the event location). Returns scene metadata (id, datetime, footprint, cloud cover, platform, orbit geometry, coverage, catalog link) — no imagery bytes. Pass eventDate to classify each scene timing=pre/post/same_day_unknown (a same-day scene is same_day_unknown, never post, without a real event time) and get pre/post bracketing + window_status + SAR sar_pair_status in meta. Pass eventPoint [lon,lat] and/or eventAoi [minLon,minLat,maxLon,maxLat] to get each scene's target_relation (covers_event_geometry = pure geometry gate; usable_for_analysis additionally requires acceptable cloud for optical, so a 99%-cloud scene is geometry-covering but not analysis-usable) — so a scene that only clips the wide bbox is not mistaken for covering the event. Costs 2 token(s) per call.",
454
+ "inputSchema": {
455
+ "type": "object",
456
+ "properties": {
457
+ "bbox": {
458
+ "type": "array",
459
+ "items": {
460
+ "type": "number"
461
+ },
462
+ "minItems": 4,
463
+ "maxItems": 4,
464
+ "description": "Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Required."
465
+ },
466
+ "collection": {
467
+ "type": "string",
468
+ "enum": [
469
+ "sentinel-1-grd",
470
+ "sentinel-1-rtc",
471
+ "sentinel-2-l2a",
472
+ "OPERA_L2_RTC-S1_V1"
473
+ ],
474
+ "description": "Catalog collection. Defaults to sentinel-2-l2a."
475
+ },
476
+ "date": {
477
+ "type": "string",
478
+ "description": "Window end date, YYYY-MM-DD (UTC). Defaults to today."
479
+ },
480
+ "days": {
481
+ "type": "integer",
482
+ "minimum": 1,
483
+ "maximum": 30,
484
+ "description": "Window length in days. Defaults to 7."
485
+ },
486
+ "eventDate": {
487
+ "type": "string",
488
+ "description": "Event date, YYYY-MM-DD (UTC). When set, each scene is tagged timing=pre/post/same_day_unknown and the search window is widened to the canonical pre/post span, so meta reports has_pre_baseline / has_post / bracketing_available / window_status; for sentinel-1-grd it also reports sar_pair_status (ready | not_ready | indeterminate_event_time) + orbit_note."
489
+ },
490
+ "eventPoint": {
491
+ "type": "array",
492
+ "items": {
493
+ "type": "number"
494
+ },
495
+ "minItems": 2,
496
+ "maxItems": 2,
497
+ "description": "Event point [lon, lat] (WGS84). When set, each scene reports target_relation.covers_event_point / usable_for_event so a scene that only clips the wide bbox is not presented as covering the event."
498
+ },
499
+ "eventAoi": {
500
+ "type": "array",
501
+ "items": {
502
+ "type": "number"
503
+ },
504
+ "minItems": 4,
505
+ "maxItems": 4,
506
+ "description": "Event AOI bbox [minLon, minLat, maxLon, maxLat] (WGS84). Drives target_relation.intersects_event_aoi / event_aoi_coverage_ratio."
507
+ },
508
+ "eventTimestamp": {
509
+ "type": "string",
510
+ "description": "Full event timestamp (ISO 8601) when known — promotes same-day scenes from same_day_unknown to pre/post by time."
511
+ },
512
+ "cloudCoverMax": {
513
+ "type": "number",
514
+ "minimum": 0,
515
+ "maximum": 100,
516
+ "description": "Sentinel-2 only: max cloud cover %."
517
+ },
518
+ "limit": {
519
+ "type": "integer",
520
+ "minimum": 1,
521
+ "maximum": 100,
522
+ "description": "Max scenes to return. Defaults to 25."
523
+ },
524
+ "responseFormat": {
525
+ "type": "string",
526
+ "enum": [
527
+ "concise",
528
+ "detailed"
529
+ ],
530
+ "description": "Per-scene field detail. \"concise\" (default) returns id, collection, datetime, timing, cloud_cover, platform, orbit_state, relative_orbit, instrument_mode, product_type, coverage_ratio, covers_event_point, usable_for_event, stac_item_url, preview. \"detailed\" adds the full footprint bbox/geometry, the complete target_relation, constellation, polarizations, absolute_orbit, incidence_angle, and non-signed asset hrefs."
531
+ }
532
+ },
533
+ "required": [
534
+ "bbox"
535
+ ]
536
+ },
537
+ "outputSchema": {
280
538
  "type": "object",
281
539
  "properties": {
282
540
  "meta": {
@@ -299,38 +557,329 @@ export const TOOLS = [
299
557
  "scenes"
300
558
  ]
301
559
  },
302
- description: 'Search the satellite imagery catalog (Sentinel-1/2, OPERA RTC-S1) for scenes over an area and ' +
303
- 'date window — the natural follow-up to a signal (find imagery over the event location). Returns ' +
304
- 'scene metadata (id, datetime, footprint, cloud cover, platform, orbit geometry, coverage, catalog ' +
305
- 'link) — no imagery bytes. Pass eventDate to classify each scene timing=pre/post/same_day_unknown ' +
306
- '(a same-day scene is same_day_unknown, never post, without a real event time) and get pre/post ' +
307
- 'bracketing + window_status + SAR sar_pair_status in meta. Pass eventPoint [lon,lat] and/or eventAoi ' +
308
- '[minLon,minLat,maxLon,maxLat] to get each scene\'s target_relation (covers_event_geometry = pure ' +
309
- 'geometry gate; usable_for_analysis additionally requires acceptable cloud for optical, so a ' +
310
- '99%-cloud scene is geometry-covering but not analysis-usable) — so a scene that only clips the wide ' +
311
- 'bbox is not mistaken for covering the event. Costs 2 token(s) per call.',
312
- inputSchema: {
313
- type: 'object',
314
- properties: {
315
- bbox: { ...BBOX, description: 'Bounding box [minLon, minLat, maxLon, maxLat] (WGS84). Required.' },
316
- collection: { type: 'string', enum: [...IMAGERY_COLLECTIONS], description: 'Catalog collection. Defaults to sentinel-2-l2a.' },
317
- date: { type: 'string', description: 'Window end date, YYYY-MM-DD (UTC). Defaults to today.' },
318
- days: { type: 'integer', minimum: 1, maximum: SIGNALS_MAX_DAYS, description: 'Window length in days. Defaults to 7.' },
319
- eventDate: { type: 'string', description: 'Event date, YYYY-MM-DD (UTC). When set, each scene is tagged timing=pre/post/same_day_unknown and the search window is widened to the canonical pre/post span, so meta reports has_pre_baseline / has_post / bracketing_available / window_status; for sentinel-1-grd it also reports sar_pair_status (ready | not_ready | indeterminate_event_time) + orbit_note.' },
320
- eventPoint: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: 'Event point [lon, lat] (WGS84). When set, each scene reports target_relation.covers_event_point / usable_for_event so a scene that only clips the wide bbox is not presented as covering the event.' },
321
- eventAoi: { type: 'array', items: { type: 'number' }, minItems: 4, maxItems: 4, description: 'Event AOI bbox [minLon, minLat, maxLon, maxLat] (WGS84). Drives target_relation.intersects_event_aoi / event_aoi_coverage_ratio.' },
322
- eventTimestamp: { type: 'string', description: 'Full event timestamp (ISO 8601) when known — promotes same-day scenes from same_day_unknown to pre/post by time.' },
323
- cloudCoverMax: { type: 'number', minimum: 0, maximum: 100, description: 'Sentinel-2 only: max cloud cover %.' },
324
- limit: { type: 'integer', minimum: 1, maximum: 100, description: 'Max scenes to return. Defaults to 25.' },
325
- responseFormat: { type: 'string', enum: ['concise', 'detailed'], description: 'Per-scene field detail. "concise" (default) returns id, collection, datetime, timing, cloud_cover, platform, orbit_state, relative_orbit, instrument_mode, product_type, coverage_ratio, covers_event_point, usable_for_event, stac_item_url, preview. "detailed" adds the full footprint bbox/geometry, the complete target_relation, constellation, polarizations, absolute_orbit, incidence_angle, and non-signed asset hrefs.' },
560
+ "annotations": {
561
+ "readOnlyHint": false,
562
+ "openWorldHint": false,
563
+ "destructiveHint": true
564
+ }
565
+ },
566
+ {
567
+ "name": "plan_event_imagery",
568
+ "description": "Plan the imagery evidence for ONE event in a single call, instead of guessing collections one at a time. Give the event_id and what you are trying to establish (damage_assessment | flood_mapping | wildfire_assessment) and the SERVER runs the deterministic plan for that goal: it always checks BOTH in-app sensors — sentinel-1-grd (SAR: all-weather, the only look that survives cloud and night) and sentinel-2-l2a (optical, human-legible) — exactly once each, against the event’s own footprint and a pre/post window around its date. The result carries, per search, why it was made, how many scenes came back, how many actually COVER the event and are usable (cloud-obscured optical does not count), the SAR pair status, and whether a pre/post bracket exists. It also states screening / detection / identification capability: Sentinel-1/2 screen and detect at facility scale, and only object-level identification needs commercial VHR — a VHR recommendation NEVER invalidates what the free catalog already showed. Prefer this over several search_imagery calls for the same event: it cannot miss the SAR look and cannot repeat a search. An event with no resolvable footprint is refused rather than planned against a guess. Costs 4 token(s) per call (it deliberately issues two catalog searches).",
569
+ "inputSchema": {
570
+ "type": "object",
571
+ "properties": {
572
+ "event_id": {
573
+ "type": "integer",
574
+ "description": "The `id` from query_signals. The server resolves its authoritative point + AOI."
575
+ },
576
+ "analysis_goal": {
577
+ "type": "string",
578
+ "enum": [
579
+ "damage_assessment",
580
+ "flood_mapping",
581
+ "wildfire_assessment"
582
+ ],
583
+ "description": "What the imagery must establish — decides which collection leads and how cloud is gated."
584
+ },
585
+ "event_date": {
586
+ "type": "string",
587
+ "description": "Event date YYYY-MM-DD. Optional — the event row supplies it when known."
588
+ }
326
589
  },
327
- required: ['bbox'],
590
+ "required": [
591
+ "event_id",
592
+ "analysis_goal"
593
+ ]
328
594
  },
595
+ "outputSchema": {
596
+ "type": "object",
597
+ "properties": {
598
+ "meta": {
599
+ "type": "object",
600
+ "description": "Query echo, token charge/balance (meta.tokens), and pagination where applicable."
601
+ },
602
+ "summary": {
603
+ "type": "string",
604
+ "description": "One-line natural-language summary of the result, ready to relay to a user."
605
+ },
606
+ "plan": {
607
+ "type": "object"
608
+ }
609
+ },
610
+ "required": [
611
+ "meta",
612
+ "plan"
613
+ ]
614
+ },
615
+ "annotations": {
616
+ "readOnlyHint": false,
617
+ "openWorldHint": false,
618
+ "destructiveHint": true
619
+ }
329
620
  },
330
621
  {
331
- name: 'assess_signal',
332
- annotations: { "readOnlyHint": false, "openWorldHint": false, "destructiveHint": true, "idempotentHint": true },
333
- outputSchema: {
622
+ "name": "rank_imaging_priority",
623
+ "description": "WHERE — and with what class (and therefore cost) of satellite — is observation most worthwhile right now? Crosses each event’s composite IMPORTANCE (severity × source breadth × market relevance) with the SPEC CLASS its required resolution demands: coarse (≤100 m), hr (≤10 m, Sentinel-class free data) or vhr (sub-metre, commercial tasking). Returns how many imageable events fall in each class with their mean importance, plus the top targets with importance, required class and AOI. Use it to triage a theatre before spending on imagery: a high-importance event that only needs hr is answerable with free Sentinel data, while a vhr one is what a paid order is for. Deterministic — no LLM, no per-event cost. Costs 1 token(s) per call.",
624
+ "inputSchema": {
625
+ "type": "object",
626
+ "properties": {
627
+ "bbox": {
628
+ "type": "array",
629
+ "items": {
630
+ "type": "number"
631
+ },
632
+ "minItems": 4,
633
+ "maxItems": 4,
634
+ "description": "Area [west, south, east, north] in WGS84. Omit for a global survey."
635
+ },
636
+ "start_date": {
637
+ "type": "string",
638
+ "description": "Inclusive start date YYYY-MM-DD. Defaults to today; clamped to your plan history floor."
639
+ },
640
+ "end_date": {
641
+ "type": "string",
642
+ "description": "Inclusive end date YYYY-MM-DD. Defaults to today. The window is capped at 30 days."
643
+ },
644
+ "categories": {
645
+ "type": "array",
646
+ "items": {
647
+ "type": "string"
648
+ },
649
+ "description": "Restrict to these Delta categories (kinetic, armed_conflict, maritime, natural_disaster, infrastructure, aviation, humanitarian, protest, diplomacy)."
650
+ },
651
+ "min_geoint_score": {
652
+ "type": "number",
653
+ "description": "Drop events below this GEOINT score before ranking."
654
+ },
655
+ "top_n": {
656
+ "type": "number",
657
+ "minimum": 1,
658
+ "maximum": 50,
659
+ "description": "How many top targets to return (default 12)."
660
+ }
661
+ },
662
+ "required": []
663
+ },
664
+ "outputSchema": {
665
+ "type": "object",
666
+ "properties": {
667
+ "meta": {
668
+ "type": "object",
669
+ "description": "Query echo, token charge/balance (meta.tokens), and pagination where applicable."
670
+ },
671
+ "summary": {
672
+ "type": "string",
673
+ "description": "One-line natural-language summary of the result, ready to relay to a user."
674
+ },
675
+ "priority": {
676
+ "type": "object"
677
+ }
678
+ },
679
+ "required": [
680
+ "meta",
681
+ "priority"
682
+ ]
683
+ },
684
+ "annotations": {
685
+ "readOnlyHint": false,
686
+ "openWorldHint": false,
687
+ "destructiveHint": true
688
+ }
689
+ },
690
+ {
691
+ "name": "survey_observable_events",
692
+ "description": "Which events in a window can a given in-app sensor actually RESOLVE? Evaluates the FULL set (not just the top few) against each event’s precomputed required resolution, and returns how many are observable vs not, the breakdown by required resolution, and the top observable events with the ready-made imaging rationale. Observability here is resolvability — whether the physical mark is large enough for the sensor: Sentinel-2 is ~10 m optical (needs daylight and clear sky), Sentinel-1 is SAR (all-weather, day or night). Prefer this over asking about events one at a time: it is exhaustive AND cheap, and it is the honest way to answer \"what can we actually see\" before committing collection effort. Deterministic — no LLM. Costs 1 token(s) per call.",
693
+ "inputSchema": {
694
+ "type": "object",
695
+ "properties": {
696
+ "sensor": {
697
+ "type": "string",
698
+ "enum": [
699
+ "sentinel-2",
700
+ "sentinel-1"
701
+ ],
702
+ "description": "Which in-app sensor to evaluate against. sentinel-2 = ~10 m optical (daylight, clear sky); sentinel-1 = SAR (all-weather, day/night). Default sentinel-2."
703
+ },
704
+ "bbox": {
705
+ "type": "array",
706
+ "items": {
707
+ "type": "number"
708
+ },
709
+ "minItems": 4,
710
+ "maxItems": 4,
711
+ "description": "Area [west, south, east, north] in WGS84. Omit for a global survey."
712
+ },
713
+ "start_date": {
714
+ "type": "string",
715
+ "description": "Inclusive start date YYYY-MM-DD. Defaults to today; clamped to your plan history floor."
716
+ },
717
+ "end_date": {
718
+ "type": "string",
719
+ "description": "Inclusive end date YYYY-MM-DD. Defaults to today. The window is capped at 30 days."
720
+ },
721
+ "categories": {
722
+ "type": "array",
723
+ "items": {
724
+ "type": "string"
725
+ },
726
+ "description": "Restrict to these Delta categories."
727
+ },
728
+ "min_geoint_score": {
729
+ "type": "number",
730
+ "description": "Drop events below this GEOINT score before surveying."
731
+ },
732
+ "top_n": {
733
+ "type": "number",
734
+ "minimum": 1,
735
+ "maximum": 50,
736
+ "description": "How many observable events to return (default 20)."
737
+ }
738
+ },
739
+ "required": []
740
+ },
741
+ "outputSchema": {
742
+ "type": "object",
743
+ "properties": {
744
+ "meta": {
745
+ "type": "object",
746
+ "description": "Query echo, token charge/balance (meta.tokens), and pagination where applicable."
747
+ },
748
+ "summary": {
749
+ "type": "string",
750
+ "description": "One-line natural-language summary of the result, ready to relay to a user."
751
+ },
752
+ "survey": {
753
+ "type": "object"
754
+ }
755
+ },
756
+ "required": [
757
+ "meta",
758
+ "survey"
759
+ ]
760
+ },
761
+ "annotations": {
762
+ "readOnlyHint": false,
763
+ "openWorldHint": false,
764
+ "destructiveHint": true
765
+ }
766
+ },
767
+ {
768
+ "name": "predict_satellite_passes",
769
+ "description": "WHEN can this place next be imaged, and by WHAT — the timing half of collection planning. Propagates current orbital elements (SGP4 over day-cached CelesTrak two-line elements) for seven families and returns the access windows over a target: Sentinel-1 (SAR), Sentinel-2 and Landsat (free, SYSTEMATIC — scheduled acquisitions you will get), plus WorldView, ICEYE, Capella and SkySat (commercial, AGILE — taskable ACCESS opportunities that require a paid order and are NOT guaranteed collects). Each pass carries acquisition/loss times, the closest-approach instant, peak elevation, OFF-NADIR angle, ground distance, ascending/descending, solar elevation and whether the target is sunlit (optical needs light; SAR does not), and the age of the element set it was computed from. Use it to answer \"when is the next chance to see this\", to choose between waiting for a free systematic pass and paying to task an agile one, and to time a pre/post change-detection pair. Give lat/lon, or a bbox whose centre is used. Horizon is capped at 7 days. If elements cannot be retrieved the result says so (retrieval_ok:false) — that means timing is UNAVAILABLE, never \"no passes\". Costs 2 token(s) per call.",
770
+ "inputSchema": {
771
+ "type": "object",
772
+ "properties": {
773
+ "lat": {
774
+ "type": "number",
775
+ "minimum": -90,
776
+ "maximum": 90,
777
+ "description": "Target latitude (-90..90; positive = North). Required unless bbox is given."
778
+ },
779
+ "lon": {
780
+ "type": "number",
781
+ "minimum": -180,
782
+ "maximum": 180,
783
+ "description": "Target longitude (-180..180; positive = East). Required unless bbox is given."
784
+ },
785
+ "bbox": {
786
+ "type": "array",
787
+ "items": {
788
+ "type": "number"
789
+ },
790
+ "minItems": 4,
791
+ "maxItems": 4,
792
+ "description": "Area [west, south, east, north] in WGS84. The CENTRE is used as the target when lat/lon are omitted."
793
+ },
794
+ "start_date": {
795
+ "type": "string",
796
+ "description": "Inclusive start date YYYY-MM-DD (UTC). Defaults to today."
797
+ },
798
+ "end_date": {
799
+ "type": "string",
800
+ "description": "Inclusive end date YYYY-MM-DD (UTC). Defaults to start+2 days; the window is capped to a 7-day horizon."
801
+ },
802
+ "satellites": {
803
+ "type": "array",
804
+ "items": {
805
+ "type": "string",
806
+ "enum": [
807
+ "sentinel-1",
808
+ "sentinel-2",
809
+ "landsat",
810
+ "worldview",
811
+ "iceye",
812
+ "capella",
813
+ "skysat"
814
+ ]
815
+ },
816
+ "description": "Families to consider. Omit for all seven. Use this to compare \"free systematic only\" against \"what could I task\"."
817
+ },
818
+ "max_passes": {
819
+ "type": "number",
820
+ "minimum": 1,
821
+ "maximum": 100,
822
+ "description": "Maximum passes to return, soonest first (default 40)."
823
+ }
824
+ },
825
+ "required": []
826
+ },
827
+ "outputSchema": {
828
+ "type": "object",
829
+ "properties": {
830
+ "meta": {
831
+ "type": "object",
832
+ "description": "Query echo, token charge/balance (meta.tokens), and pagination where applicable."
833
+ },
834
+ "summary": {
835
+ "type": "string",
836
+ "description": "One-line natural-language summary of the result, ready to relay to a user."
837
+ },
838
+ "passes": {
839
+ "type": "array",
840
+ "items": {
841
+ "type": "object"
842
+ }
843
+ },
844
+ "freshness": {
845
+ "type": "object"
846
+ }
847
+ },
848
+ "required": [
849
+ "meta",
850
+ "passes"
851
+ ]
852
+ },
853
+ "annotations": {
854
+ "readOnlyHint": false,
855
+ "openWorldHint": false,
856
+ "destructiveHint": true
857
+ }
858
+ },
859
+ {
860
+ "name": "assess_signal",
861
+ "description": "Run an AI RS (remote-sensing) deep-dive assessment for a specific signal: what to observe, recommended sensors, and a collection window. `eventId` is the `id` from query_signals. The result also carries a deterministic `context` block (event id/date, normalized target, AOI bbox, observability + quality verdict, and an `imagery_handoff` giving the exact bbox + event_date to pass to search_imagery for REAL pre/post scene candidates) — turning the assessment into an actionable collection plan. Costs 5 (quick) or 15 (deep) tokens, charged to the key owner's balance. A prior assessment for the same signal is cached (no re-charge). The exact charge and remaining balance are in the result meta.tokens. Signals that are not satellite-observable (observability:\"not-observable\" — e.g. political statements or broad-area events with no imageable physical mark) are rejected BEFORE any charge, so pre-filter with query_signals observability:\"observable\" to spend only where imagery helps.",
862
+ "inputSchema": {
863
+ "type": "object",
864
+ "properties": {
865
+ "eventId": {
866
+ "type": "integer",
867
+ "description": "Signal id (global_event_id) from query_signals."
868
+ },
869
+ "kind": {
870
+ "type": "string",
871
+ "enum": [
872
+ "quick",
873
+ "deep"
874
+ ],
875
+ "description": "Assessment depth. Defaults to quick."
876
+ }
877
+ },
878
+ "required": [
879
+ "eventId"
880
+ ]
881
+ },
882
+ "outputSchema": {
334
883
  "type": "object",
335
884
  "properties": {
336
885
  "kind": {
@@ -356,30 +905,50 @@ export const TOOLS = [
356
905
  "meta"
357
906
  ]
358
907
  },
359
- description: 'Run an AI RS (remote-sensing) deep-dive assessment for a specific signal: what to observe, ' +
360
- 'recommended sensors, and a collection window. `eventId` is the `id` from query_signals. ' +
361
- 'The result also carries a deterministic `context` block (event id/date, normalized target, AOI ' +
362
- 'bbox, observability + quality verdict, and an `imagery_handoff` giving the exact bbox + event_date ' +
363
- 'to pass to search_imagery for REAL pre/post scene candidates) — turning the assessment into an ' +
364
- 'actionable collection plan. ' +
365
- "Costs 5 (quick) or 15 (deep) tokens, charged to the key owner's balance. A prior assessment for " +
366
- 'the same signal is cached (no re-charge). The exact charge and remaining balance are in meta.tokens. ' +
367
- 'Signals that are not satellite-observable (observability:"not-observable" — e.g. political statements ' +
368
- 'or broad-area events with no imageable physical mark) are rejected BEFORE any charge, so pre-filter ' +
369
- 'with query_signals observability:"observable" to spend only where imagery helps.',
370
- inputSchema: {
371
- type: 'object',
372
- properties: {
373
- eventId: { type: 'integer', description: 'Signal id (global_event_id) from query_signals.' },
374
- kind: { type: 'string', enum: ['quick', 'deep'], description: 'Assessment depth. Defaults to quick.' },
375
- },
376
- required: ['eventId'],
377
- },
908
+ "annotations": {
909
+ "readOnlyHint": false,
910
+ "openWorldHint": false,
911
+ "destructiveHint": true,
912
+ "idempotentHint": true
913
+ }
378
914
  },
379
915
  {
380
- name: 'ask_analyst',
381
- annotations: { "readOnlyHint": false, "openWorldHint": false, "destructiveHint": true },
382
- outputSchema: {
916
+ "name": "ask_analyst",
917
+ "description": "Ask the Delta Analyst an OSINT/GEOINT question. Runs an agentic multi-step analysis over the signal corpus and returns a structured brief (summary, findings with collection recommendations, assessment, citations). Costs 5–123 tokens (usage-based, metered by the compute the question actually uses; charged ONCE, when the run completes; the exact charge and remaining balance are in the result meta.tokens). Durable async: the run is ENQUEUED and returns {status:\"processing\", job_id} immediately, then completes in a background worker — so it is never lost to a client timeout. Timing: most questions finish in ~30–90s; a complex brief (satellite-imagery lookups or many sources) can take 2–3 minutes. Fetch the finished brief by calling get_analyst_job with the job_id (poll every ~10–20s), or ask_analyst again with the SAME idempotencyKey (no second charge).",
918
+ "inputSchema": {
919
+ "type": "object",
920
+ "properties": {
921
+ "question": {
922
+ "type": "string",
923
+ "description": "The analytic question (≤ 500 chars)."
924
+ },
925
+ "bbox": {
926
+ "type": "array",
927
+ "items": {
928
+ "type": "number"
929
+ },
930
+ "minItems": 4,
931
+ "maxItems": 4,
932
+ "description": "Optional focus bounding box [minLon, minLat, maxLon, maxLat] (WGS84)."
933
+ },
934
+ "mode": {
935
+ "type": "string",
936
+ "enum": [
937
+ "fast",
938
+ "deep"
939
+ ],
940
+ "description": "fast (default) or deep. Deep enables extended reasoning and wider evidence-gathering budgets — for forecasting, collection trade-offs and market-implication questions where step-by-step reasoning materially helps. It is slower and the ceiling rises from 123 to 415 tokens; charging stays metered by what the run actually consumes, so a light deep question does not cost the ceiling."
941
+ },
942
+ "idempotencyKey": {
943
+ "type": "string",
944
+ "description": "Optional at-most-once key. Re-sending the SAME key resolves to the SAME run: if it finished you get the brief with NO second charge; if it is still running you get its processing status. Strongly recommended — it makes a timeout recoverable. Use a fresh key to ask again."
945
+ }
946
+ },
947
+ "required": [
948
+ "question"
949
+ ]
950
+ },
951
+ "outputSchema": {
383
952
  "type": "object",
384
953
  "properties": {
385
954
  "brief": {
@@ -397,34 +966,95 @@ export const TOOLS = [
397
966
  "type": "string",
398
967
  "description": "Id of the analyst run — pass to get_analyst_job (also at GET /api/v1/analyst/{job_id})."
399
968
  },
969
+ "progress": {
970
+ "type": "object",
971
+ "description": "Pipeline progress while the job is processing. completed_steps reaches total_steps ONLY when status is \"done\".",
972
+ "properties": {
973
+ "stage": {
974
+ "type": "string",
975
+ "enum": [
976
+ "queued",
977
+ "retrieval",
978
+ "imagery_reconciliation",
979
+ "synthesis",
980
+ "final_validation",
981
+ "persistence",
982
+ "complete"
983
+ ],
984
+ "description": "Current pipeline stage."
985
+ },
986
+ "completed_steps": {
987
+ "type": "integer",
988
+ "description": "Completed pipeline steps (0-6)."
989
+ },
990
+ "total_steps": {
991
+ "type": "integer",
992
+ "description": "Always 6."
993
+ },
994
+ "agent_progress": {
995
+ "type": "object",
996
+ "description": "The model's internal step counter (an upper bound on the steps a run may take), or null.",
997
+ "properties": {
998
+ "completed_steps": {
999
+ "type": "integer"
1000
+ },
1001
+ "total_steps": {
1002
+ "type": "integer"
1003
+ }
1004
+ }
1005
+ }
1006
+ },
1007
+ "required": [
1008
+ "stage",
1009
+ "completed_steps",
1010
+ "total_steps"
1011
+ ]
1012
+ },
1013
+ "estimated_charge": {
1014
+ "type": "object",
1015
+ "description": "The charge ceiling quoted for THIS run, fixed at enqueue (統合改善指示書 P1-1). The completed run reports the actual charge in meta.tokens.charged and echoes this ceiling as meta.tokens.maximum_promised; actual never exceeds it.",
1016
+ "properties": {
1017
+ "minimum": {
1018
+ "type": "integer",
1019
+ "description": "Floor charge for any completed run."
1020
+ },
1021
+ "maximum": {
1022
+ "type": "integer",
1023
+ "description": "Ceiling this run may be charged."
1024
+ }
1025
+ },
1026
+ "required": [
1027
+ "minimum",
1028
+ "maximum"
1029
+ ]
1030
+ },
400
1031
  "message": {
401
1032
  "type": "string"
402
1033
  }
403
1034
  }
404
1035
  },
405
- description: 'Ask the Delta Analyst an OSINT/GEOINT question. Runs an agentic multi-step analysis over the ' +
406
- 'signal corpus and returns a structured brief (summary, findings with collection recommendations, ' +
407
- 'assessment, citations). Costs 5–123 tokens (usage-based, metered by the compute the question actually ' +
408
- 'uses; charged ONCE, when the run completes; the exact charge and remaining balance are in meta.tokens). ' +
409
- 'Durable async: the run is ENQUEUED and returns {status:"processing", job_id} immediately, then completes ' +
410
- 'in a background worker — so it is never lost to a client timeout. Timing: most questions finish in ~30–90s; ' +
411
- 'a complex brief (satellite-imagery lookups or many sources) can take 2–3 minutes. Fetch the finished brief ' +
412
- 'by calling get_analyst_job with the job_id (poll every ~10–20s), or ask_analyst again with the SAME ' +
413
- 'idempotencyKey (no second charge).',
414
- inputSchema: {
415
- type: 'object',
416
- properties: {
417
- question: { type: 'string', description: 'The analytic question (≤ 500 chars).' },
418
- bbox: { ...BBOX, description: 'Optional focus bounding box [minLon, minLat, maxLon, maxLat] (WGS84).' },
419
- idempotencyKey: { type: 'string', description: 'Optional at-most-once key. Re-sending the SAME key resolves to the SAME run: if it finished you get the brief with NO second charge; if it is still running you get its processing status. Strongly recommended — it makes a timeout recoverable. Use a fresh key to ask again.' },
420
- },
421
- required: ['question'],
422
- },
1036
+ "annotations": {
1037
+ "readOnlyHint": false,
1038
+ "openWorldHint": false,
1039
+ "destructiveHint": true
1040
+ }
423
1041
  },
424
1042
  {
425
- name: 'get_analyst_job',
426
- annotations: { "readOnlyHint": true, "openWorldHint": false, "destructiveHint": false },
427
- outputSchema: {
1043
+ "name": "get_analyst_job",
1044
+ "description": "Fetch the status and result of an ask_analyst run by job_id. Job status is ONLY \"processing\" (still running — poll again in ~10-20s), \"done\" (with the finished brief and meta.tokens), or \"error\". When done, result_quality.status (\"complete\" | \"partial\", with any issues[]) reports whether the structured output is fully populated — this is SEPARATE from the job status (a done job can carry a partial result). Free of token charges — the run itself is charged once on completion. A job is visible only to the API key owner that created it.",
1045
+ "inputSchema": {
1046
+ "type": "object",
1047
+ "properties": {
1048
+ "job_id": {
1049
+ "type": "string",
1050
+ "description": "The job_id returned by ask_analyst."
1051
+ }
1052
+ },
1053
+ "required": [
1054
+ "job_id"
1055
+ ]
1056
+ },
1057
+ "outputSchema": {
428
1058
  "type": "object",
429
1059
  "properties": {
430
1060
  "job_id": {
@@ -434,6 +1064,50 @@ export const TOOLS = [
434
1064
  "type": "string",
435
1065
  "description": "\"processing\" | \"done\" | \"error\"."
436
1066
  },
1067
+ "progress": {
1068
+ "type": "object",
1069
+ "description": "Pipeline progress while the job is processing. completed_steps reaches total_steps ONLY when status is \"done\".",
1070
+ "properties": {
1071
+ "stage": {
1072
+ "type": "string",
1073
+ "enum": [
1074
+ "queued",
1075
+ "retrieval",
1076
+ "imagery_reconciliation",
1077
+ "synthesis",
1078
+ "final_validation",
1079
+ "persistence",
1080
+ "complete"
1081
+ ],
1082
+ "description": "Current pipeline stage."
1083
+ },
1084
+ "completed_steps": {
1085
+ "type": "integer",
1086
+ "description": "Completed pipeline steps (0-6)."
1087
+ },
1088
+ "total_steps": {
1089
+ "type": "integer",
1090
+ "description": "Always 6."
1091
+ },
1092
+ "agent_progress": {
1093
+ "type": "object",
1094
+ "description": "The model's internal step counter (an upper bound on the steps a run may take), or null.",
1095
+ "properties": {
1096
+ "completed_steps": {
1097
+ "type": "integer"
1098
+ },
1099
+ "total_steps": {
1100
+ "type": "integer"
1101
+ }
1102
+ }
1103
+ }
1104
+ },
1105
+ "required": [
1106
+ "stage",
1107
+ "completed_steps",
1108
+ "total_steps"
1109
+ ]
1110
+ },
437
1111
  "brief": {
438
1112
  "type": "object"
439
1113
  },
@@ -456,54 +1130,106 @@ export const TOOLS = [
456
1130
  }
457
1131
  }
458
1132
  },
459
- description: 'Fetch the status and result of an ask_analyst run by job_id. Returns status "processing" (still ' +
460
- 'running — poll again in ~10-20s), "done" (with the finished brief and meta.tokens), or "error". ' +
461
- 'Free of token charges — the run itself is charged once on completion. A job is visible only to the ' +
462
- 'API key owner that created it. Use this to retrieve the brief after ask_analyst returned a job_id.',
463
- inputSchema: {
464
- type: 'object',
465
- properties: {
466
- job_id: { type: 'string', description: 'The job_id returned by ask_analyst.' },
467
- },
468
- required: ['job_id'],
469
- },
470
- },
1133
+ "annotations": {
1134
+ "readOnlyHint": true,
1135
+ "openWorldHint": false,
1136
+ "destructiveHint": false
1137
+ }
1138
+ }
471
1139
  ];
472
1140
  export const RESOURCES = [
473
- { uri: 'brief://latest', name: 'Daily World Brief (latest)', description: 'The most recent AI-synthesized Daily World Brief (JSON). Free.', mimeType: 'application/json' },
474
- { uri: 'signals://schema', name: 'Signal object schema', description: 'JSON Schema of the public Signal shape returned by query_signals / /api/v1/signals.', mimeType: 'application/json' },
475
- { uri: 'usage://current', name: 'API usage & quota', description: 'Remaining token balance and plan capabilities for the calling key. Free.', mimeType: 'application/json' },
476
- { uri: 'imagery://collections', name: 'Imagery collections', description: 'The satellite catalog collections searchable via search_imagery. Free.', mimeType: 'application/json' },
477
- { uri: 'status://current', name: 'Data freshness & pipeline status', description: 'How current the data is (ingestion/enrichment frontier), the Daily World Brief status, and an Operational/Delayed/Degraded roll-up. Free.', mimeType: 'application/json' },
1141
+ {
1142
+ "uri": "brief://latest",
1143
+ "name": "Daily World Brief (latest)",
1144
+ "description": "The most recent AI-synthesized Daily World Brief (JSON). Free.",
1145
+ "mimeType": "application/json"
1146
+ },
1147
+ {
1148
+ "uri": "signals://schema",
1149
+ "name": "Signal object schema",
1150
+ "description": "JSON Schema of the public Signal shape returned by query_signals / /api/v1/signals.",
1151
+ "mimeType": "application/json"
1152
+ },
1153
+ {
1154
+ "uri": "usage://current",
1155
+ "name": "API usage & quota",
1156
+ "description": "Remaining token balance and plan capabilities for the calling key. Free.",
1157
+ "mimeType": "application/json"
1158
+ },
1159
+ {
1160
+ "uri": "imagery://collections",
1161
+ "name": "Imagery collections",
1162
+ "description": "The satellite catalog collections searchable via search_imagery. Free.",
1163
+ "mimeType": "application/json"
1164
+ },
1165
+ {
1166
+ "uri": "status://current",
1167
+ "name": "Data freshness & pipeline status",
1168
+ "description": "How current the data is (ingestion/enrichment frontier), the Daily World Brief status, and an Operational/Delayed/Degraded roll-up. Free.",
1169
+ "mimeType": "application/json"
1170
+ }
478
1171
  ];
479
1172
  export const RESOURCE_TEMPLATES = [
480
- { uriTemplate: 'brief://{date}', name: 'Daily World Brief by date', description: 'The Daily World Brief for a specific UTC date (YYYY-MM-DD). Free.', mimeType: 'application/json' },
1173
+ {
1174
+ "uriTemplate": "brief://{date}",
1175
+ "name": "Daily World Brief by date",
1176
+ "description": "The Daily World Brief for a specific UTC date (YYYY-MM-DD). Free.",
1177
+ "mimeType": "application/json"
1178
+ }
481
1179
  ];
482
1180
  export const PROMPTS = [
483
1181
  {
484
- name: 'daily-situation-briefing',
485
- description: 'Summarize the current world situation from the Daily World Brief.',
486
- arguments: [{ name: 'date', description: 'UTC date YYYY-MM-DD (optional; defaults to latest).', required: false }],
1182
+ "name": "daily-situation-briefing",
1183
+ "description": "Summarize the current world situation from the Daily World Brief.",
1184
+ "arguments": [
1185
+ {
1186
+ "name": "date",
1187
+ "description": "UTC date YYYY-MM-DD (optional; defaults to latest).",
1188
+ "required": false
1189
+ }
1190
+ ]
487
1191
  },
488
1192
  {
489
- name: 'assess-top-signal',
490
- description: 'Find the highest-severity recent signal in an area/category and run an RS assessment.',
491
- arguments: [
492
- { name: 'bbox', description: 'Bounding box "minLon,minLat,maxLon,maxLat" (optional).', required: false },
493
- { name: 'category', description: 'Category filter (optional).', required: false },
494
- ],
1193
+ "name": "assess-top-signal",
1194
+ "description": "Find the highest-severity recent signal in an area/category and run an RS assessment.",
1195
+ "arguments": [
1196
+ {
1197
+ "name": "bbox",
1198
+ "description": "Bounding box \"minLon,minLat,maxLon,maxLat\" (optional).",
1199
+ "required": false
1200
+ },
1201
+ {
1202
+ "name": "category",
1203
+ "description": "Category filter (optional).",
1204
+ "required": false
1205
+ }
1206
+ ]
495
1207
  },
496
1208
  {
497
- name: 'aoi-watch',
498
- description: 'Scan an area of interest for recent escalations and recommend collection.',
499
- arguments: [{ name: 'bbox', description: 'Bounding box "minLon,minLat,maxLon,maxLat".', required: true }],
1209
+ "name": "aoi-watch",
1210
+ "description": "Scan an area of interest for recent escalations and recommend collection.",
1211
+ "arguments": [
1212
+ {
1213
+ "name": "bbox",
1214
+ "description": "Bounding box \"minLon,minLat,maxLon,maxLat\".",
1215
+ "required": true
1216
+ }
1217
+ ]
500
1218
  },
501
1219
  {
502
- name: 'market-exposure-check',
503
- description: 'Find recent events that could plausibly move a given market (oil, grain, shipping, ...) and explain each transmission channel. Informational only — not investment advice.',
504
- arguments: [
505
- { name: 'market', description: `Market to check: ${MARKET_IMPACTS.join(', ')}.`, required: true },
506
- { name: 'days', description: 'Lookback window in days (optional; default 3).', required: false },
507
- ],
508
- },
1220
+ "name": "market-exposure-check",
1221
+ "description": "Find recent events that could plausibly move a given market (oil, grain, shipping, ...) and explain each transmission channel. Informational only — not investment advice.",
1222
+ "arguments": [
1223
+ {
1224
+ "name": "market",
1225
+ "description": "Market to check: oil, natural_gas, grain, shipping, defense, metals, semiconductors, fx, equities.",
1226
+ "required": true
1227
+ },
1228
+ {
1229
+ "name": "days",
1230
+ "description": "Lookback window in days (optional; default 3).",
1231
+ "required": false
1232
+ }
1233
+ ]
1234
+ }
509
1235
  ];