refactor(agent)!: focus tools on SCADA
This commit is contained in:
@@ -3,36 +3,8 @@ import type { FrontendActionManifest } from "./types.js";
|
||||
|
||||
type Definition = { manifest: FrontendActionManifest; inputSchema: z.ZodTypeAny; outputSchema: z.ZodTypeAny };
|
||||
const zoomInput = z.object({ x: z.number().finite(), y: z.number().finite(), source_crs: z.enum(["EPSG:3857", "EPSG:4326"]).optional(), zoom: z.number().finite().optional(), duration_ms: z.number().finite().nonnegative().optional() }).strict();
|
||||
const id = z.string().trim().min(1).max(256);
|
||||
const isoTime = z.string().datetime({ offset: true });
|
||||
const timeRange = {
|
||||
start_time: isoTime.optional(),
|
||||
end_time: isoTime.optional(),
|
||||
};
|
||||
const requiredTimeRange = {
|
||||
start_time: isoTime,
|
||||
end_time: isoTime,
|
||||
};
|
||||
const locateInput = z.object({
|
||||
ids: z.array(id).min(1).max(100),
|
||||
feature_type: z.enum(["junction", "pipe", "valve", "reservoir", "pump", "tank", "conduit", "orifice", "outfall"]),
|
||||
}).strict();
|
||||
const historyInput = z.object({
|
||||
feature_infos: z.array(z.tuple([id, id])).min(1).max(100),
|
||||
data_type: z.enum(["realtime", "scheme", "none"]),
|
||||
...requiredTimeRange,
|
||||
}).strict().refine((value) => Date.parse(value.start_time) < Date.parse(value.end_time), "start_time must precede end_time");
|
||||
const scadaInput = z.object({
|
||||
device_id: id.optional(),
|
||||
device_ids: z.array(id).min(1).max(100).optional(),
|
||||
...timeRange,
|
||||
}).strict().refine((value) => Boolean(value.device_id) !== Boolean(value.device_ids), "provide exactly one of device_id or device_ids")
|
||||
.refine((value) => !value.start_time || !value.end_time || Date.parse(value.start_time) <= Date.parse(value.end_time), "start_time must not be after end_time");
|
||||
const definitions: Definition[] = [
|
||||
{ manifest: { id: "zoom_to_map", version: "frontend-action@1", risk: "view", timeoutMs: 10_000, supportedSurfaces: ["map_overlay"] }, inputSchema: zoomInput, outputSchema: z.object({}).passthrough() },
|
||||
{ manifest: { id: "locate_features", version: "frontend-action@1", risk: "view", timeoutMs: 15_000, supportedSurfaces: ["map_overlay"] }, inputSchema: locateInput, outputSchema: z.object({ locatedIds: z.array(id), missingIds: z.array(id), featureType: id, bounds: z.tuple([z.number(), z.number(), z.number(), z.number()]) }).strict() },
|
||||
{ manifest: { id: "view_history", version: "frontend-action@1", risk: "view", timeoutMs: 10_000, supportedSurfaces: ["side_panel", "canvas"] }, inputSchema: historyInput, outputSchema: z.object({ component: z.literal("HistoryPanel"), rendered: z.literal(true), itemCount: z.number().int().nonnegative() }).strict() },
|
||||
{ manifest: { id: "view_scada", version: "frontend-action@1", risk: "view", timeoutMs: 10_000, supportedSurfaces: ["side_panel", "canvas"] }, inputSchema: scadaInput, outputSchema: z.object({ component: z.literal("ScadaPanel"), rendered: z.literal(true), itemCount: z.number().int().nonnegative() }).strict() },
|
||||
];
|
||||
|
||||
export const frontendActionRegistry = definitions.map((item) => item.manifest);
|
||||
|
||||
Vendored
+75
-682
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
"/api/v1/meta/projects": {
|
||||
"/api/v1/projects": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -12,7 +12,7 @@ export interface paths {
|
||||
cookie?: never;
|
||||
};
|
||||
/** Projects */
|
||||
get: operations["projects_api_v1_meta_projects_get"];
|
||||
get: operations["projects_api_v1_projects_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
@@ -21,15 +21,15 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/meta/project": {
|
||||
"/api/v1/scada/assets": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Project */
|
||||
get: operations["project_api_v1_meta_project_get"];
|
||||
/** Assets */
|
||||
get: operations["assets_api_v1_scada_assets_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
@@ -38,58 +38,7 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/meta/db/health": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Health */
|
||||
get: operations["health_api_v1_meta_db_health_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/monitoring/devices": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Devices */
|
||||
get: operations["devices_api_v1_monitoring_devices_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/monitoring/points": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Points */
|
||||
get: operations["points_api_v1_monitoring_points_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/monitoring/readings/query": {
|
||||
"/api/v1/scada/readings/query": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -99,133 +48,14 @@ export interface paths {
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Readings */
|
||||
post: operations["readings_api_v1_monitoring_readings_query_post"];
|
||||
post: operations["readings_api_v1_scada_readings_query_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/analyses": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Create */
|
||||
post: operations["create_api_v1_analyses_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/analyses/{job_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get */
|
||||
get: operations["get_api_v1_analyses__job_id__get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/analyses/{job_id}/cancel": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Cancel */
|
||||
post: operations["cancel_api_v1_analyses__job_id__cancel_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/simulation/models": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Upload */
|
||||
post: operations["upload_api_v1_simulation_models_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/simulations": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Create */
|
||||
post: operations["create_api_v1_simulations_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/simulations/{job_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get */
|
||||
get: operations["get_api_v1_simulations__job_id__get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/jobs/{job_id}/events": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Events */
|
||||
get: operations["events_api_v1_jobs__job_id__events_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/tianditu/geocode": {
|
||||
"/api/v1/geocode": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -235,7 +65,7 @@ export interface paths {
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Geocode */
|
||||
post: operations["geocode_api_v1_tianditu_geocode_post"];
|
||||
post: operations["geocode_api_v1_geocode_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
@@ -280,80 +110,39 @@ export interface paths {
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
/** AnalysisCreate */
|
||||
AnalysisCreate: {
|
||||
/**
|
||||
* Algorithm
|
||||
* @constant
|
||||
*/
|
||||
algorithm: "data-quality";
|
||||
/** Device Ids */
|
||||
device_ids: string[];
|
||||
/**
|
||||
* Observed From
|
||||
* Format: date-time
|
||||
*/
|
||||
observed_from: string;
|
||||
/**
|
||||
* Observed To
|
||||
* Format: date-time
|
||||
*/
|
||||
observed_to: string;
|
||||
/** GeocodeLocation */
|
||||
GeocodeLocation: {
|
||||
/** Lon */
|
||||
lon: number;
|
||||
/** Lat */
|
||||
lat: number;
|
||||
/** Level */
|
||||
level?: string | null;
|
||||
/** Score */
|
||||
score?: number | null;
|
||||
/** Keyword */
|
||||
keyWord?: string | null;
|
||||
};
|
||||
/** Body_upload_api_v1_simulation_models_post */
|
||||
Body_upload_api_v1_simulation_models_post: {
|
||||
/** Name */
|
||||
name: string;
|
||||
/** File */
|
||||
file: string;
|
||||
/** GeocodeRequest */
|
||||
GeocodeRequest: {
|
||||
/** Keyword */
|
||||
keyword: string;
|
||||
};
|
||||
/** DatabaseHealthResponse */
|
||||
DatabaseHealthResponse: {
|
||||
/** Postgres */
|
||||
postgres: string;
|
||||
/** Timescale */
|
||||
timescale: string;
|
||||
};
|
||||
/** DeviceOut */
|
||||
DeviceOut: {
|
||||
/**
|
||||
* Id
|
||||
* Format: uuid
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Monitoring Point Id
|
||||
* Format: uuid
|
||||
*/
|
||||
monitoring_point_id: string;
|
||||
/** External Id */
|
||||
external_id: string;
|
||||
/** Active */
|
||||
active: boolean;
|
||||
/** GeocodeResponse */
|
||||
GeocodeResponse: {
|
||||
/** Status */
|
||||
status: string | number;
|
||||
/** Msg */
|
||||
msg?: string | null;
|
||||
location?: components["schemas"]["GeocodeLocation"] | null;
|
||||
/** Searchversion */
|
||||
searchVersion?: string | null;
|
||||
};
|
||||
/** HTTPValidationError */
|
||||
HTTPValidationError: {
|
||||
/** Detail */
|
||||
detail?: components["schemas"]["ValidationError"][];
|
||||
};
|
||||
/** JobOut */
|
||||
JobOut: {
|
||||
/**
|
||||
* Id
|
||||
* Format: uuid
|
||||
*/
|
||||
id: string;
|
||||
/** Status */
|
||||
status: string;
|
||||
/** Progress */
|
||||
progress?: number | null;
|
||||
/** Result */
|
||||
result?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Error */
|
||||
error?: string | null;
|
||||
};
|
||||
/** MapExtent */
|
||||
MapExtent: {
|
||||
/** Xmin */
|
||||
@@ -365,37 +154,8 @@ export interface components {
|
||||
/** Ymax */
|
||||
ymax: number;
|
||||
};
|
||||
/** ModelUploadOut */
|
||||
ModelUploadOut: {
|
||||
/**
|
||||
* Model Id
|
||||
* Format: uuid
|
||||
*/
|
||||
model_id: string;
|
||||
/**
|
||||
* Model Version Id
|
||||
* Format: uuid
|
||||
*/
|
||||
model_version_id: string;
|
||||
/** Version */
|
||||
version: number;
|
||||
/** Sha256 */
|
||||
sha256: string;
|
||||
};
|
||||
/** MonitoringPointOut */
|
||||
MonitoringPointOut: {
|
||||
/**
|
||||
* Id
|
||||
* Format: uuid
|
||||
*/
|
||||
id: string;
|
||||
/** External Id */
|
||||
external_id: string;
|
||||
/** Name */
|
||||
name: string;
|
||||
};
|
||||
/** ProjectMetaResponse */
|
||||
ProjectMetaResponse: {
|
||||
/** ProjectResponse */
|
||||
ProjectResponse: {
|
||||
/**
|
||||
* Project Id
|
||||
* Format: uuid
|
||||
@@ -418,31 +178,36 @@ export interface components {
|
||||
gs_workspace: string;
|
||||
map_extent: components["schemas"]["MapExtent"] | null;
|
||||
};
|
||||
/** ProjectSummaryResponse */
|
||||
ProjectSummaryResponse: {
|
||||
/** ScadaAssetOut */
|
||||
ScadaAssetOut: {
|
||||
/**
|
||||
* Project Id
|
||||
* Scada Id
|
||||
* Format: uuid
|
||||
*/
|
||||
project_id: string;
|
||||
/** Code */
|
||||
code: string;
|
||||
scada_id: string;
|
||||
/** External Id */
|
||||
external_id: string;
|
||||
/** Site External Id */
|
||||
site_external_id: string;
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Description */
|
||||
description: string | null;
|
||||
/**
|
||||
* Status
|
||||
* Kind
|
||||
* @enum {string}
|
||||
*/
|
||||
status: "active" | "inactive";
|
||||
/** Project Role */
|
||||
project_role: string;
|
||||
kind: "water_quality" | "radar_level" | "ultrasonic_flow";
|
||||
/** Active */
|
||||
active: boolean;
|
||||
};
|
||||
/** ReadingQuery */
|
||||
ReadingQuery: {
|
||||
/** Device Ids */
|
||||
device_ids: string[];
|
||||
/** ScadaAssetsResponse */
|
||||
ScadaAssetsResponse: {
|
||||
/** Assets */
|
||||
assets: components["schemas"]["ScadaAssetOut"][];
|
||||
};
|
||||
/** ScadaReadingQuery */
|
||||
ScadaReadingQuery: {
|
||||
/** Asset Ids */
|
||||
asset_ids: string[];
|
||||
/**
|
||||
* Observed From
|
||||
* Format: date-time
|
||||
@@ -460,24 +225,24 @@ export interface components {
|
||||
*/
|
||||
granularity: "5m" | "1h" | "1d";
|
||||
/** Metrics */
|
||||
metrics: string[];
|
||||
metrics: ("conductivity_mean" | "level_mean" | "flow_mean" | "temperature_mean")[];
|
||||
};
|
||||
/** ReadingQueryResponse */
|
||||
ReadingQueryResponse: {
|
||||
/** ScadaReadingQueryResponse */
|
||||
ScadaReadingQueryResponse: {
|
||||
/** Data */
|
||||
data: components["schemas"]["ReadingRow"][];
|
||||
data: components["schemas"]["ScadaReadingRow"][];
|
||||
/** Meta */
|
||||
meta: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
/** ReadingRow */
|
||||
ReadingRow: {
|
||||
/** ScadaReadingRow */
|
||||
ScadaReadingRow: {
|
||||
/**
|
||||
* Device Id
|
||||
* Asset Id
|
||||
* Format: uuid
|
||||
*/
|
||||
device_id: string;
|
||||
asset_id: string;
|
||||
/**
|
||||
* Observed At
|
||||
* Format: date-time
|
||||
@@ -488,46 +253,6 @@ export interface components {
|
||||
[key: string]: number | null;
|
||||
};
|
||||
};
|
||||
/** SimulationCreate */
|
||||
SimulationCreate: {
|
||||
/**
|
||||
* Model Version Id
|
||||
* Format: uuid
|
||||
*/
|
||||
model_version_id: string;
|
||||
/** Parameters */
|
||||
parameters?: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
/** TiandituGeocodeLocation */
|
||||
TiandituGeocodeLocation: {
|
||||
/** Lon */
|
||||
lon: number;
|
||||
/** Lat */
|
||||
lat: number;
|
||||
/** Level */
|
||||
level?: string | null;
|
||||
/** Score */
|
||||
score?: number | null;
|
||||
/** Keyword */
|
||||
keyWord?: string | null;
|
||||
};
|
||||
/** TiandituGeocodeRequest */
|
||||
TiandituGeocodeRequest: {
|
||||
/** Keyword */
|
||||
keyword: string;
|
||||
};
|
||||
/** TiandituGeocodeResponse */
|
||||
TiandituGeocodeResponse: {
|
||||
/** Status */
|
||||
status: string | number;
|
||||
/** Msg */
|
||||
msg?: string | null;
|
||||
location?: components["schemas"]["TiandituGeocodeLocation"] | null;
|
||||
/** Searchversion */
|
||||
searchVersion?: string | null;
|
||||
};
|
||||
/** ValidationError */
|
||||
ValidationError: {
|
||||
/** Location */
|
||||
@@ -550,7 +275,7 @@ export interface components {
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
projects_api_v1_meta_projects_get: {
|
||||
projects_api_v1_projects_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -565,12 +290,12 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ProjectSummaryResponse"][];
|
||||
"application/json": components["schemas"]["ProjectResponse"][];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
project_api_v1_meta_project_get: {
|
||||
assets_api_v1_scada_assets_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
@@ -587,7 +312,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ProjectMetaResponse"];
|
||||
"application/json": components["schemas"]["ScadaAssetsResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
@@ -601,100 +326,7 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
health_api_v1_meta_db_health_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["DatabaseHealthResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
devices_api_v1_monitoring_devices_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["DeviceOut"][];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
points_api_v1_monitoring_points_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["MonitoringPointOut"][];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
readings_api_v1_monitoring_readings_query_post: {
|
||||
readings_api_v1_scada_readings_query_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
@@ -705,7 +337,7 @@ export interface operations {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ReadingQuery"];
|
||||
"application/json": components["schemas"]["ScadaReadingQuery"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@@ -715,7 +347,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ReadingQueryResponse"];
|
||||
"application/json": components["schemas"]["ScadaReadingQueryResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
@@ -729,246 +361,7 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
create_api_v1_analyses_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"Idempotency-Key"?: string | null;
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["AnalysisCreate"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
202: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["JobOut"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
get_api_v1_analyses__job_id__get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path: {
|
||||
job_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["JobOut"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cancel_api_v1_analyses__job_id__cancel_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path: {
|
||||
job_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["JobOut"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
upload_api_v1_simulation_models_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"multipart/form-data": components["schemas"]["Body_upload_api_v1_simulation_models_post"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ModelUploadOut"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
create_api_v1_simulations_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"Idempotency-Key"?: string | null;
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["SimulationCreate"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
202: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["JobOut"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
get_api_v1_simulations__job_id__get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path: {
|
||||
job_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["JobOut"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
events_api_v1_jobs__job_id__events_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: {
|
||||
"X-Project-Id"?: string | null;
|
||||
};
|
||||
path: {
|
||||
job_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
geocode_api_v1_tianditu_geocode_post: {
|
||||
geocode_api_v1_geocode_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -977,7 +370,7 @@ export interface operations {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["TiandituGeocodeRequest"];
|
||||
"application/json": components["schemas"]["GeocodeRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@@ -987,7 +380,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["TiandituGeocodeResponse"];
|
||||
"application/json": components["schemas"]["GeocodeResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
|
||||
+1
-108
@@ -1,11 +1,8 @@
|
||||
import { readJsonFile } from "../utils/fileStore.js";
|
||||
import {
|
||||
type ResultReferenceKind,
|
||||
type ResultReferenceRecord,
|
||||
type ResultReferenceSource,
|
||||
type RetrievalContext,
|
||||
RESULT_REFERENCE_KIND,
|
||||
RESULT_REFERENCE_SOURCE,
|
||||
type ResultReferenceStore,
|
||||
} from "./store.js";
|
||||
|
||||
@@ -26,12 +23,6 @@ type RegisterResultReferenceInput = {
|
||||
traceId: string;
|
||||
};
|
||||
|
||||
export type RenderJunctionPayload = {
|
||||
node_area_map: Record<string, string>;
|
||||
area_ids?: string[];
|
||||
area_colors?: Record<string, string>;
|
||||
};
|
||||
|
||||
export class ResultReferenceResolver {
|
||||
constructor(private readonly store: ResultReferenceStore) {}
|
||||
|
||||
@@ -59,34 +50,6 @@ export class ResultReferenceResolver {
|
||||
});
|
||||
}
|
||||
|
||||
async registerRenderPayloadFile(
|
||||
filePath: string,
|
||||
input: Omit<RegisterResultReferenceInput, "data" | "kind" | "schemaVersion">,
|
||||
) {
|
||||
const raw = await readJsonFile<unknown>(filePath);
|
||||
if (raw === null) {
|
||||
throw new Error(`render payload file not found: ${filePath}`);
|
||||
}
|
||||
|
||||
const wrapper = normalizeRenderPayloadFile(raw, filePath);
|
||||
if (!wrapper) {
|
||||
throw new Error("render payload file must use the wrapped { metadata, location, data } format");
|
||||
}
|
||||
|
||||
const payload = extractRenderJunctionPayload(wrapper.data);
|
||||
if (!payload) {
|
||||
throw new Error("render payload file does not contain a valid junction render payload");
|
||||
}
|
||||
|
||||
return this.register({
|
||||
...input,
|
||||
data: payload,
|
||||
kind: RESULT_REFERENCE_KIND.renderJunctionsPayload,
|
||||
schemaVersion: 1,
|
||||
source: RESULT_REFERENCE_SOURCE.agentGenerated,
|
||||
});
|
||||
}
|
||||
|
||||
async getFullAuthorized(
|
||||
resultRef: string,
|
||||
context: RetrievalContext,
|
||||
@@ -136,83 +99,13 @@ export class ResultReferenceResolver {
|
||||
}
|
||||
}
|
||||
|
||||
export const extractRenderJunctionPayload = (
|
||||
value: unknown,
|
||||
): RenderJunctionPayload | null => {
|
||||
const candidate = unwrapReferencePayload(value);
|
||||
if (!candidate || !isRecord(candidate.node_area_map)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 节点渲染结果只保留前端真正需要的映射字段,剔除空值并统一转为字符串。
|
||||
const nodeAreaMap = normalizeStringRecord(candidate.node_area_map);
|
||||
if (Object.keys(nodeAreaMap).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const areaIds = Array.isArray(candidate.area_ids)
|
||||
? candidate.area_ids.map((entry) => String(entry).trim()).filter(Boolean)
|
||||
: undefined;
|
||||
|
||||
const areaColors = isRecord(candidate.area_colors)
|
||||
? normalizeStringRecord(candidate.area_colors)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
node_area_map: nodeAreaMap,
|
||||
...(areaIds && areaIds.length > 0 ? { area_ids: areaIds } : {}),
|
||||
...(areaColors && Object.keys(areaColors).length > 0
|
||||
? { area_colors: areaColors }
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeDataForKind = (
|
||||
kind: ResultReferenceKind,
|
||||
_kind: ResultReferenceKind,
|
||||
data: unknown,
|
||||
schemaVersion: number,
|
||||
): unknown | null => {
|
||||
if (!Number.isInteger(schemaVersion) || schemaVersion < 1) {
|
||||
return null;
|
||||
}
|
||||
if (kind === RESULT_REFERENCE_KIND.renderJunctionsPayload) {
|
||||
return extractRenderJunctionPayload(data);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const normalizeRenderPayloadFile = (
|
||||
value: unknown,
|
||||
filePath: string,
|
||||
): { data: unknown } | null => {
|
||||
if (!isRecord(value) || !("data" in value)) {
|
||||
return null;
|
||||
}
|
||||
if (!isRecord(value.metadata) || !isRecord(value.location)) {
|
||||
return null;
|
||||
}
|
||||
if (value.location.file_path !== filePath) {
|
||||
return null;
|
||||
}
|
||||
return { data: value.data };
|
||||
};
|
||||
|
||||
const unwrapReferencePayload = (value: unknown): Record<string, unknown> | null => {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
if ("data" in value && value.data !== undefined && value.data !== null) {
|
||||
return isRecord(value.data) ? value.data : null;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const normalizeStringRecord = (value: Record<string, unknown>) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(value)
|
||||
.map(([key, entry]) => [String(key), String(entry ?? "").trim()])
|
||||
.filter(([, entry]) => entry.length > 0),
|
||||
);
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
@@ -16,7 +16,6 @@ export const RESULT_REF_PATTERN = /^res-[a-f0-9-]{8,64}$/;
|
||||
const RESULT_REF_FILE_PATTERN = /^(res-[a-f0-9-]{8,64})(?:\.json)?$/;
|
||||
|
||||
export const RESULT_REFERENCE_KIND = {
|
||||
renderJunctionsPayload: "render-junctions-payload",
|
||||
serverApiPayload: "server_api_payload",
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -31,11 +31,6 @@ import { FrontendActionCoordinator } from "../frontendAction/coordinator.js";
|
||||
import { frontendActionRegistryResponse } from "../frontendAction/registry.js";
|
||||
import type { UIEnvelopePayload } from "../uiEnvelope/types.js";
|
||||
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
|
||||
import {
|
||||
createPressureTrendDemoEnvelopePayload,
|
||||
createPressureTrendDemoToolCall,
|
||||
isPressureTrendDemoPrompt,
|
||||
} from "./pressureTrendDemo.js";
|
||||
import {
|
||||
buildForkedSessionUiState,
|
||||
buildPromptWithLearningContext,
|
||||
@@ -1059,39 +1054,6 @@ export const buildChatRouter = (
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
if (isPressureTrendDemoPrompt(promptText)) {
|
||||
const toolCall = createPressureTrendDemoToolCall();
|
||||
publish("progress", {
|
||||
session_id: clientSessionId,
|
||||
id: "pressure-trend-demo",
|
||||
phase: "tool",
|
||||
status: "running",
|
||||
title: "生成压力趋势图",
|
||||
detail: "已命中受控演示用例,正在通过 show_chart 生成可信 UIEnvelope。",
|
||||
started_at: Date.now(),
|
||||
elapsed_ms: 0,
|
||||
});
|
||||
publish("tool_call", {
|
||||
session_id: clientSessionId,
|
||||
tool: toolCall.tool,
|
||||
params: toolCall.params,
|
||||
reason: toolCall.reason,
|
||||
});
|
||||
publish(
|
||||
"ui_envelope",
|
||||
createPressureTrendDemoEnvelopePayload(clientSessionId, toolCall),
|
||||
);
|
||||
publish("token", {
|
||||
session_id: clientSessionId,
|
||||
content: "已生成最近压力趋势图,并通过可信 UIEnvelope 发送到前端。",
|
||||
});
|
||||
publish("done", {
|
||||
session_id: clientSessionId,
|
||||
duration_ms: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const preparedMessage = await buildPromptWithLearningContext(
|
||||
memoryStore,
|
||||
requestContext.actorKey,
|
||||
|
||||
@@ -48,43 +48,6 @@ export const registerChatAuxiliaryRoutes = (
|
||||
frontendActionCoordinator,
|
||||
}: RegisterAuxiliaryRoutesOptions,
|
||||
) => {
|
||||
chatRouter.get("/render-ref/:render_ref", async (req, res) => {
|
||||
const renderRef = req.params.render_ref?.trim();
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const userId = authContext.userId;
|
||||
const projectId = authContext.projectId;
|
||||
const clientSessionId =
|
||||
typeof req.query.session_id === "string"
|
||||
? req.query.session_id.trim()
|
||||
: undefined;
|
||||
|
||||
if (!renderRef) {
|
||||
res.status(400).json({
|
||||
message: "render_ref is required",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await resultReferenceResolver.getFullAuthorized(
|
||||
renderRef,
|
||||
{
|
||||
actorKey: toActorKey(userId),
|
||||
clientSessionId,
|
||||
projectId,
|
||||
},
|
||||
{
|
||||
expectedKind: RESULT_REFERENCE_KIND.renderJunctionsPayload,
|
||||
},
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
res.status(404).json({ message: "render_ref not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
chatRouter.get("/result-ref/:result_ref", async (req, res) => {
|
||||
const resultRef = req.params.result_ref?.trim();
|
||||
const context = getLocalAgentContext(req);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { MemoryStore } from "../memory/store.js";
|
||||
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
|
||||
|
||||
import { collectTextContent } from "./chatStream.js";
|
||||
import { isPressureTrendDemoPrompt } from "./pressureTrendDemo.js";
|
||||
|
||||
const TITLE_PROMPT_TIMEOUT_MS = 5000;
|
||||
const TITLE_CONTEXT_MESSAGE_LIMIT = 40;
|
||||
@@ -241,9 +240,6 @@ export const resolveRequestedStreamSessionId = (input: {
|
||||
if (sessionId) {
|
||||
return sessionId;
|
||||
}
|
||||
if (isPressureTrendDemoPrompt(input.promptText)) {
|
||||
return input.createClientSessionId();
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -830,7 +830,7 @@ export const streamPromptResponse = async ({
|
||||
params: toolParams,
|
||||
reason,
|
||||
});
|
||||
if (envelope && !(suppressLegacyFrontendActions && ["zoom_to_map", "locate_features", "view_history", "view_scada"].includes(part.tool))) {
|
||||
if (envelope && !(suppressLegacyFrontendActions && part.tool === "zoom_to_map")) {
|
||||
write("ui_envelope", {
|
||||
session_id: clientSessionId,
|
||||
envelope_id: createEnvelopeId(part.tool),
|
||||
|
||||
@@ -65,12 +65,7 @@ const toolLabels: Record<string, string> = {
|
||||
session_search: "历史会话检索",
|
||||
skill_manager: "流程沉淀",
|
||||
web_search: "网页搜索",
|
||||
locate_features: "地图定位",
|
||||
zoom_to_map: "地图缩放",
|
||||
view_history: "历史数据面板",
|
||||
view_scada: "SCADA 面板",
|
||||
show_chart: "图表渲染",
|
||||
render_junctions: "节点渲染",
|
||||
};
|
||||
|
||||
export const logDevelopmentDebug = (
|
||||
|
||||
@@ -207,17 +207,9 @@ export const updateLastAssistantQuestion = (
|
||||
});
|
||||
|
||||
const getToolArtifactKind = (tool: string): ToolArtifactKind => {
|
||||
if (tool === "show_chart" || tool === "chart") return "chart";
|
||||
if (
|
||||
tool === "locate_features" ||
|
||||
tool === "zoom_to_map" ||
|
||||
tool === "render_junctions" ||
|
||||
tool === "apply_layer_style" ||
|
||||
tool.startsWith("locate_")
|
||||
) {
|
||||
if (tool === "zoom_to_map") {
|
||||
return "map";
|
||||
}
|
||||
if (tool === "view_history" || tool === "view_scada") return "panel";
|
||||
return "tool";
|
||||
};
|
||||
|
||||
@@ -225,13 +217,7 @@ const getToolArtifactTitle = (tool: string, params: Record<string, unknown>) =>
|
||||
if (typeof params.title === "string" && params.title.trim()) {
|
||||
return params.title.trim();
|
||||
}
|
||||
if (tool === "show_chart" || tool === "chart") return "生成图表";
|
||||
if (tool === "zoom_to_map") return "缩放到地图坐标";
|
||||
if (tool === "render_junctions") return "渲染节点分区";
|
||||
if (tool === "view_history") return "打开计算结果曲线";
|
||||
if (tool === "view_scada") return "打开 SCADA 数据面板";
|
||||
if (tool === "apply_layer_style") return "应用图层样式";
|
||||
if (tool === "locate_features" || tool.startsWith("locate_")) return "地图定位";
|
||||
return tool || "工具调用";
|
||||
};
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { createEnvelopeId } from "../uiEnvelope/ids.js";
|
||||
import { toUiEnvelopeFromToolCall } from "../uiEnvelope/fromToolCall.js";
|
||||
import type { UIEnvelopePayload } from "../uiEnvelope/types.js";
|
||||
|
||||
export type PressureTrendDemoToolCall = {
|
||||
tool: "show_chart";
|
||||
params: {
|
||||
reason: string;
|
||||
title: string;
|
||||
chart_type: "line";
|
||||
x_axis_name: string;
|
||||
y_axis_name: string;
|
||||
x_data: string[];
|
||||
series: Array<{
|
||||
name: string;
|
||||
type: "line";
|
||||
data: number[];
|
||||
}>;
|
||||
};
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export const isPressureTrendDemoPrompt = (message: string) =>
|
||||
message.replace(/\s+/g, "").includes("展示最近压力趋势");
|
||||
|
||||
export const createPressureTrendDemoToolCall = (): PressureTrendDemoToolCall => ({
|
||||
tool: "show_chart",
|
||||
reason: "展示最近 6 小时压力趋势。",
|
||||
params: {
|
||||
reason: "展示最近 6 小时压力趋势。",
|
||||
title: "最近压力趋势",
|
||||
chart_type: "line",
|
||||
x_axis_name: "时间",
|
||||
y_axis_name: "压力 MPa",
|
||||
x_data: ["10:00", "11:00", "12:00", "13:00", "14:00", "15:00"],
|
||||
series: [
|
||||
{
|
||||
name: "东部主干压力",
|
||||
type: "line",
|
||||
data: [0.42, 0.41, 0.39, 0.36, 0.35, 0.37],
|
||||
},
|
||||
{
|
||||
name: "正常基线",
|
||||
type: "line",
|
||||
data: [0.43, 0.43, 0.42, 0.42, 0.41, 0.41],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const createPressureTrendDemoEnvelopePayload = (
|
||||
sessionId: string,
|
||||
toolCall: PressureTrendDemoToolCall = createPressureTrendDemoToolCall(),
|
||||
): UIEnvelopePayload => {
|
||||
const envelope = toUiEnvelopeFromToolCall(toolCall);
|
||||
if (!envelope) {
|
||||
throw new Error("pressure trend demo tool call did not produce a UIEnvelope");
|
||||
}
|
||||
|
||||
return {
|
||||
session_id: sessionId,
|
||||
envelope_id: createEnvelopeId(toolCall.tool),
|
||||
created_at: Date.now(),
|
||||
envelope,
|
||||
};
|
||||
};
|
||||
+2
-56
@@ -11,10 +11,7 @@ import { logger } from "./logger.js";
|
||||
import { LearningOrchestrator } from "./learning/orchestrator.js";
|
||||
import { MemoryStore } from "./memory/store.js";
|
||||
import { ResultReferenceResolver } from "./results/resolver.js";
|
||||
import {
|
||||
RESULT_REFERENCE_SOURCE,
|
||||
ResultReferenceStore,
|
||||
} from "./results/store.js";
|
||||
import { ResultReferenceStore } from "./results/store.js";
|
||||
import { buildChatRouter } from "./routes/chat.js";
|
||||
import { FrontendActionCoordinator, FrontendActionError } from "./frontendAction/coordinator.js";
|
||||
import { opencodeRuntime } from "./runtime/opencode.js";
|
||||
@@ -44,7 +41,6 @@ const serverApiGateway = new ServerApiGateway(resultReferenceResolver);
|
||||
const internalToken = config.AGENT_INTERNAL_TOKEN ?? randomUUID();
|
||||
const frontendActionCoordinator = new FrontendActionCoordinator();
|
||||
|
||||
// 这个 token 只用于仍需服务端上下文的工具桥(store_render_ref)。
|
||||
process.env.TJWATER_AGENT_INTERNAL_TOKEN = internalToken;
|
||||
|
||||
app.use(cors());
|
||||
@@ -124,56 +120,6 @@ app.get("/health", async (_req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/internal/tools/store-render-ref", async (req, res) => {
|
||||
if (req.header("x-agent-internal-token") !== internalToken) {
|
||||
res.status(403).json({ message: "forbidden" });
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId =
|
||||
typeof req.body?.session_id === "string" ? req.body.session_id.trim() : "";
|
||||
const filePath = typeof req.body?.file_path === "string" ? req.body.file_path.trim() : "";
|
||||
const context = sessionId ? getRuntimeSessionContext(sessionId) : null;
|
||||
if (!context) {
|
||||
res.status(404).json({
|
||||
message: "session context not found",
|
||||
detail: sessionId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!filePath) {
|
||||
res.status(400).json({ message: "file_path is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const record = await resultReferenceResolver.registerRenderPayloadFile(filePath, {
|
||||
actorKey: context.actorKey,
|
||||
clientSessionId: context.clientSessionId,
|
||||
projectId: context.projectId,
|
||||
projectKey: context.projectKey,
|
||||
sessionId: context.clientSessionId,
|
||||
source: RESULT_REFERENCE_SOURCE.agentGenerated,
|
||||
traceId: context.traceId,
|
||||
});
|
||||
res.json({
|
||||
ok: true,
|
||||
render_ref: record.resultRef,
|
||||
stored_at: record.createdAt,
|
||||
preview: record.preview,
|
||||
kind: record.kind,
|
||||
schema_version: record.schemaVersion,
|
||||
source: record.source,
|
||||
});
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
res.status(400).json({
|
||||
message: "store render ref failed",
|
||||
detail,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/internal/tools/session-search", async (req, res) => {
|
||||
if (req.header("x-agent-internal-token") !== internalToken) {
|
||||
res.status(403).json({ message: "forbidden" });
|
||||
@@ -340,7 +286,7 @@ app.post("/internal/tools/geocode", async (req, res) => {
|
||||
|
||||
try {
|
||||
const response = await callBackendJson(
|
||||
"/api/v1/tianditu/geocode",
|
||||
"/api/v1/geocode",
|
||||
context,
|
||||
{ keyword },
|
||||
);
|
||||
|
||||
+19
-26
@@ -1,4 +1,3 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { z } from "zod";
|
||||
|
||||
import { config } from "../config.js";
|
||||
@@ -21,11 +20,8 @@ const responseSchemas: Record<DomainToolName, z.ZodTypeAny> = {
|
||||
gs_workspace: z.string(),
|
||||
map_extent: z.object({ xmin: z.number(), ymin: z.number(), xmax: z.number(), ymax: z.number() }).nullable(),
|
||||
}),
|
||||
list_monitoring_assets: z.object({ devices: z.array(z.unknown()).optional(), points: z.array(z.unknown()).optional() }),
|
||||
query_monitoring_readings: z.object({ data: z.array(z.unknown()), meta: z.record(z.unknown()) }),
|
||||
start_data_quality_analysis: z.object({ id: z.string().uuid(), status: z.string() }).passthrough(),
|
||||
start_simulation: z.object({ id: z.string().uuid(), status: z.string() }).passthrough(),
|
||||
get_job_status: z.object({ id: z.string().uuid(), status: z.string() }).passthrough(),
|
||||
list_scada_assets: z.object({ assets: z.array(z.unknown()) }),
|
||||
query_scada_readings: z.object({ data: z.array(z.unknown()), meta: z.record(z.unknown()) }),
|
||||
};
|
||||
|
||||
export class ServerApiGateway {
|
||||
@@ -62,33 +58,32 @@ export class ServerApiGateway {
|
||||
}
|
||||
|
||||
private async dispatch(name: DomainToolName, args: any, context: RuntimeSessionContext): Promise<unknown> {
|
||||
if (name === "list_monitoring_assets") {
|
||||
const result: Record<string, unknown> = {};
|
||||
if (args.include !== "points") result.devices = await this.request("GET", "/api/v1/monitoring/devices", undefined, context);
|
||||
if (args.include !== "devices") result.points = await this.request("GET", "/api/v1/monitoring/points", undefined, context);
|
||||
return result;
|
||||
if (name === "get_project_context") {
|
||||
const projects = await this.request("GET", "/api/v1/projects", undefined, context);
|
||||
if (!Array.isArray(projects)) throw new GatewayHttpError(502);
|
||||
const project = projects.find((item) => (
|
||||
typeof item === "object"
|
||||
&& item !== null
|
||||
&& "project_id" in item
|
||||
&& item.project_id === context.projectId
|
||||
));
|
||||
if (!project) throw new GatewayHttpError(404);
|
||||
return project;
|
||||
}
|
||||
const routes = {
|
||||
get_project_context: ["GET", "/api/v1/meta/project"],
|
||||
query_monitoring_readings: ["POST", "/api/v1/monitoring/readings/query"],
|
||||
start_data_quality_analysis: ["POST", "/api/v1/analyses"],
|
||||
start_simulation: ["POST", "/api/v1/simulations"],
|
||||
get_job_status: ["GET", args.job_type === "analysis" ? `/api/v1/analyses/${args.job_id}` : `/api/v1/simulations/${args.job_id}`],
|
||||
} as const;
|
||||
const [method, path] = routes[name];
|
||||
const body = name === "start_data_quality_analysis" ? { ...args, algorithm: "data-quality" } : method === "POST" ? args : undefined;
|
||||
const idempotencyKey = name.startsWith("start_") ? createIdempotencyKey(context, name, body) : undefined;
|
||||
return this.request(method, path, body, context, idempotencyKey);
|
||||
if (name === "list_scada_assets") {
|
||||
return this.request("GET", "/api/v1/scada/assets", undefined, context);
|
||||
}
|
||||
return this.request("POST", "/api/v1/scada/readings/query", args, context);
|
||||
}
|
||||
|
||||
private async request(method: string, path: string, body: unknown, context: RuntimeSessionContext, idempotencyKey?: string) {
|
||||
private async request(method: string, path: string, body: unknown, context: RuntimeSessionContext) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), config.TJWATER_API_TIMEOUT_MS);
|
||||
try {
|
||||
const response = await this.fetchImpl(new URL(path, config.TJWATER_API_BASE_URL), {
|
||||
method, signal: controller.signal, headers: {
|
||||
Accept: "application/json", "Content-Type": "application/json", "X-Project-Id": context.projectId!,
|
||||
"X-Request-Id": context.traceId, ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
|
||||
"X-Request-Id": context.traceId,
|
||||
}, ...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
});
|
||||
if (!response.ok) throw new GatewayHttpError(response.status);
|
||||
@@ -99,5 +94,3 @@ export class ServerApiGateway {
|
||||
|
||||
class GatewayHttpError extends Error { constructor(readonly status: number) { super("upstream request failed"); } }
|
||||
const failure = (code: GatewayErrorCode, message: string, detail?: unknown) => ({ ok: false, error: { code, message, ...(detail ? { detail } : {}) } });
|
||||
export const createIdempotencyKey = (context: RuntimeSessionContext, name: string, args: unknown) => createHash("sha256").update(JSON.stringify([context.clientSessionId, name, canonicalize(args)])).digest("hex");
|
||||
const canonicalize = (value: unknown): unknown => Array.isArray(value) ? value.map(canonicalize) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonicalize(entry)])) : value;
|
||||
|
||||
@@ -3,31 +3,21 @@ import { z } from "zod";
|
||||
const uuid = z.string().uuid();
|
||||
const dateTime = z.string().datetime({ offset: true });
|
||||
const metrics = z.enum([
|
||||
"flow_mean", "flow_total", "conductivity_mean", "velocity_mean",
|
||||
"temperature_mean", "level_mean",
|
||||
"conductivity_mean", "level_mean", "flow_mean", "temperature_mean",
|
||||
]);
|
||||
|
||||
export const domainToolSchemas = {
|
||||
get_project_context: z.object({}),
|
||||
list_monitoring_assets: z.object({ include: z.enum(["devices", "points", "all"]).default("all") }),
|
||||
query_monitoring_readings: z.object({
|
||||
device_ids: z.array(uuid).min(1).max(100),
|
||||
list_scada_assets: z.object({}),
|
||||
query_scada_readings: z.object({
|
||||
asset_ids: z.array(uuid).min(1).max(100),
|
||||
observed_from: dateTime,
|
||||
observed_to: dateTime,
|
||||
granularity: z.enum(["5m", "1h", "1d"]).default("5m"),
|
||||
metrics: z.array(metrics).min(1).max(6),
|
||||
metrics: z.array(metrics).min(1).max(4),
|
||||
}).refine((value) => Date.parse(value.observed_from) < Date.parse(value.observed_to), {
|
||||
message: "observed_from must precede observed_to",
|
||||
}),
|
||||
start_data_quality_analysis: z.object({
|
||||
device_ids: z.array(uuid).min(1).max(100),
|
||||
observed_from: dateTime,
|
||||
observed_to: dateTime,
|
||||
}).refine((value) => Date.parse(value.observed_from) < Date.parse(value.observed_to), {
|
||||
message: "observed_from must precede observed_to",
|
||||
}),
|
||||
start_simulation: z.object({ model_version_id: uuid, parameters: z.record(z.unknown()).default({}) }),
|
||||
get_job_status: z.object({ job_id: uuid, job_type: z.enum(["analysis", "simulation"]) }),
|
||||
} as const;
|
||||
|
||||
export type DomainToolName = keyof typeof domainToolSchemas;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { RESULT_REF_PATTERN } from "../results/store.js";
|
||||
import { chartGrammar } from "./registry.js";
|
||||
import type { UIEnvelope } from "./types.js";
|
||||
|
||||
type ToolCallInput = {
|
||||
@@ -8,67 +6,12 @@ type ToolCallInput = {
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
const isStringArray = (value: unknown): value is string[] =>
|
||||
Array.isArray(value) && value.every((item) => typeof item === "string");
|
||||
|
||||
const normalizeSeries = (value: unknown) =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
.filter(
|
||||
(item): item is { name: string; data: number[]; type?: "line" | "bar" } =>
|
||||
typeof item === "object" &&
|
||||
item !== null &&
|
||||
"name" in item &&
|
||||
typeof item.name === "string" &&
|
||||
"data" in item &&
|
||||
Array.isArray(item.data) &&
|
||||
item.data.every((entry: unknown) => typeof entry === "number"),
|
||||
)
|
||||
.map((item) => ({
|
||||
name: item.name,
|
||||
data: item.data,
|
||||
...(item.type === "line" || item.type === "bar" ? { type: item.type } : {}),
|
||||
}))
|
||||
: [];
|
||||
|
||||
export const toUiEnvelopeFromToolCall = ({
|
||||
tool,
|
||||
params,
|
||||
reason,
|
||||
}: ToolCallInput): UIEnvelope | null => {
|
||||
if (tool === "show_chart" || tool === "chart") {
|
||||
const xData = isStringArray(params.x_data) ? params.x_data : [];
|
||||
const series = normalizeSeries(params.series);
|
||||
if (xData.length === 0 || series.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const chartType = params.chart_type === "bar" ? "bar" : "line";
|
||||
return {
|
||||
kind: "chart",
|
||||
schemaVersion: "agent-ui@1",
|
||||
grammar: chartGrammar,
|
||||
surface: "chat_inline",
|
||||
spec: {
|
||||
title: typeof params.title === "string" ? params.title : undefined,
|
||||
chart_type: chartType,
|
||||
x_axis_name:
|
||||
typeof params.x_axis_name === "string" ? params.x_axis_name : undefined,
|
||||
y_axis_name:
|
||||
typeof params.y_axis_name === "string" ? params.y_axis_name : undefined,
|
||||
},
|
||||
data: {
|
||||
x_data: xData,
|
||||
series,
|
||||
},
|
||||
fallbackText: reason || "已生成图表。",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
tool === "locate_features" ||
|
||||
tool === "zoom_to_map" ||
|
||||
tool === "apply_layer_style"
|
||||
) {
|
||||
if (tool === "zoom_to_map") {
|
||||
return {
|
||||
kind: "map_action",
|
||||
schemaVersion: "agent-ui@1",
|
||||
@@ -79,43 +22,5 @@ export const toUiEnvelopeFromToolCall = ({
|
||||
};
|
||||
}
|
||||
|
||||
if (tool === "render_junctions") {
|
||||
const renderRef =
|
||||
typeof params.render_ref === "string" ? params.render_ref.trim() : "";
|
||||
if (!RESULT_REF_PATTERN.test(renderRef)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "map_action",
|
||||
schemaVersion: "agent-ui@1",
|
||||
action: tool,
|
||||
surface: "map_overlay",
|
||||
params: { render_ref: renderRef },
|
||||
fallbackText: reason || "已生成节点渲染操作。",
|
||||
};
|
||||
}
|
||||
|
||||
if (tool === "view_history") {
|
||||
return {
|
||||
kind: "registered_component",
|
||||
schemaVersion: "agent-ui@1",
|
||||
component: "HistoryPanel",
|
||||
surface: "side_panel",
|
||||
props: params,
|
||||
fallbackText: reason || "已打开历史数据面板。",
|
||||
};
|
||||
}
|
||||
|
||||
if (tool === "view_scada") {
|
||||
return {
|
||||
kind: "registered_component",
|
||||
schemaVersion: "agent-ui@1",
|
||||
component: "ScadaPanel",
|
||||
surface: "side_panel",
|
||||
props: params,
|
||||
fallbackText: reason || "已打开 SCADA 面板。",
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -2,51 +2,20 @@ import type { ActionManifest, ComponentManifest } from "./types.js";
|
||||
|
||||
export const chartGrammar = "echarts-safe-subset" as const;
|
||||
|
||||
export const componentRegistry: ComponentManifest[] = [
|
||||
{
|
||||
id: "HistoryPanel",
|
||||
version: "1.0.0",
|
||||
description: "Open a historical result panel for selected network features.",
|
||||
supportedSurfaces: ["side_panel", "canvas"],
|
||||
},
|
||||
{
|
||||
id: "ScadaPanel",
|
||||
version: "1.0.0",
|
||||
description: "Open a SCADA history panel for selected devices.",
|
||||
supportedSurfaces: ["side_panel", "canvas"],
|
||||
},
|
||||
];
|
||||
export const componentRegistry: ComponentManifest[] = [];
|
||||
|
||||
export const actionRegistry: ActionManifest[] = [
|
||||
{
|
||||
id: "locate_features",
|
||||
version: "1.0.0",
|
||||
description: "Locate and highlight network features on the map.",
|
||||
supportedSurfaces: ["map_overlay"],
|
||||
},
|
||||
{
|
||||
id: "zoom_to_map",
|
||||
version: "1.0.0",
|
||||
description: "Zoom the map to a coordinate.",
|
||||
supportedSurfaces: ["map_overlay"],
|
||||
},
|
||||
{
|
||||
id: "render_junctions",
|
||||
version: "1.0.0",
|
||||
description: "Render junction categories from an authorized render_ref.",
|
||||
supportedSurfaces: ["map_overlay"],
|
||||
},
|
||||
{
|
||||
id: "apply_layer_style",
|
||||
version: "1.0.0",
|
||||
description: "Apply or reset a map layer style.",
|
||||
supportedSurfaces: ["map_overlay"],
|
||||
},
|
||||
];
|
||||
|
||||
export const uiRegistryResponse = {
|
||||
schema_version: "agent-ui-registry@1",
|
||||
chart_grammars: [chartGrammar],
|
||||
chart_grammars: [],
|
||||
components: componentRegistry,
|
||||
actions: actionRegistry,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user