@dypai-ai/mcp 1.0.10 → 1.2.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.
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Summarize a per-node workflow trace for the agent without blowing its
3
+ * context window.
4
+ *
5
+ * The engine returns trace shape:
6
+ * { execution_id, workflow: {...}, execution_order: [...],
7
+ * nodes: { <id>: { status, duration_ms, output_summary, error?,
8
+ * failure_snapshot?, branch_decision?, events } } }
9
+ *
10
+ * Modes:
11
+ * - "smart" (default) — success → minimal; failure → focused on failing node
12
+ * - "full" — raw trace (opt-in, for deep dives)
13
+ * - "minimal" — just success/failure + duration + execution_order
14
+ *
15
+ * The raw `events[]` array (node-scoped event log) is always dropped from
16
+ * "smart" and "minimal" — it's redundant with the parsed fields.
17
+ */
18
+
19
+ const MAX_FIELD_CHARS = 2000 // per-string truncation threshold
20
+ const MAX_SNAPSHOT_KEYS = 6 // summarize large snapshots into a key outline
21
+
22
+ function truncateString(s, max = MAX_FIELD_CHARS) {
23
+ if (typeof s !== "string") return s
24
+ if (s.length <= max) return s
25
+ return s.slice(0, max) + `... [truncated ${s.length - max} chars, call dypai_trace with full=true]`
26
+ }
27
+
28
+ function summarizeValue(v) {
29
+ if (v == null) return v
30
+ if (typeof v === "string") return truncateString(v)
31
+ if (Array.isArray(v)) {
32
+ if (v.length > MAX_SNAPSHOT_KEYS) {
33
+ return [...v.slice(0, MAX_SNAPSHOT_KEYS).map(summarizeValue), `... ${v.length - MAX_SNAPSHOT_KEYS} more`]
34
+ }
35
+ return v.map(summarizeValue)
36
+ }
37
+ if (typeof v === "object") {
38
+ const keys = Object.keys(v)
39
+ if (keys.length > MAX_SNAPSHOT_KEYS) {
40
+ const sample = {}
41
+ for (const k of keys.slice(0, MAX_SNAPSHOT_KEYS)) sample[k] = summarizeValue(v[k])
42
+ sample[`_truncated_keys`] = keys.length - MAX_SNAPSHOT_KEYS
43
+ return sample
44
+ }
45
+ const out = {}
46
+ for (const [k, val] of Object.entries(v)) out[k] = summarizeValue(val)
47
+ return out
48
+ }
49
+ return v
50
+ }
51
+
52
+ /** Build a hint string from the error message based on common patterns. */
53
+ function hintFromError(err) {
54
+ const m = String(err?.message || "")
55
+ if (/relation .* does not exist/i.test(m)) return "Table not found. Check dypai/schema.sql."
56
+ if (/column .* does not exist/i.test(m)) return "Column not found. Verify the SQL column against the table schema."
57
+ if (/placeholder|\$\{/i.test(m)) return "Placeholder resolution failed. Inspect node inputs."
58
+ if (/credential/i.test(m)) return "Credential lookup failed. Check the credential name exists remotely."
59
+ if (/timeout|timed out/i.test(m)) return "Node timed out. Consider a faster query or raise the node timeout."
60
+ return null
61
+ }
62
+
63
+ export function summarizeTrace(trace, mode = "smart") {
64
+ if (!trace || typeof trace !== "object") return trace
65
+ if (mode === "full") return trace
66
+
67
+ const wf = trace.workflow || {}
68
+ const execOrder = Array.isArray(trace.execution_order) ? trace.execution_order : []
69
+ const nodes = trace.nodes || {}
70
+
71
+ if (mode === "minimal") {
72
+ return {
73
+ execution_id: trace.execution_id,
74
+ status: wf.status,
75
+ duration_ms: wf.duration_ms,
76
+ execution_order: execOrder,
77
+ error: wf.error || undefined,
78
+ }
79
+ }
80
+
81
+ // "smart" mode
82
+ const failed = wf.status === "failed"
83
+ const failedNodeId = failed
84
+ ? Object.values(nodes).find(n => n && n.status === "failed")?.node_id
85
+ : null
86
+
87
+ const nodeSummaries = []
88
+ for (const id of execOrder) {
89
+ const n = nodes[id]
90
+ if (!n) continue
91
+
92
+ const base = {
93
+ id: n.node_id,
94
+ type: n.node_type,
95
+ status: n.status,
96
+ duration_ms: n.duration_ms,
97
+ }
98
+
99
+ // On success: keep it tiny — just id/type/status/duration
100
+ if (n.status === "completed") {
101
+ // include output_summary only if it's the LAST node (likely the final result)
102
+ if (id === execOrder[execOrder.length - 1] && n.output_summary) {
103
+ base.output_summary = summarizeValue(n.output_summary)
104
+ }
105
+ nodeSummaries.push(base)
106
+ continue
107
+ }
108
+
109
+ // On the failing node: include full detail (error + snapshot outline)
110
+ if (n.status === "failed") {
111
+ base.error = {
112
+ message: truncateString(n.error?.message),
113
+ type: n.error?.type,
114
+ stack: truncateString(n.error?.stack, 800),
115
+ }
116
+ // failure_snapshot: reduce to a key outline — not full values
117
+ if (n.failure_snapshot) {
118
+ const snap = n.failure_snapshot
119
+ base.failure_snapshot = {
120
+ inputs_keys: Object.keys(snap.nodes_inputs || {}),
121
+ results_keys: Object.keys(snap.nodes_results || {}),
122
+ hint: "Use dypai_trace(execution_id, full=true) to see full snapshot values.",
123
+ }
124
+ }
125
+ if (n.branch_decision) base.branch_decision = summarizeValue(n.branch_decision)
126
+ nodeSummaries.push(base)
127
+ continue
128
+ }
129
+
130
+ nodeSummaries.push(base)
131
+ }
132
+
133
+ const summary = {
134
+ execution_id: trace.execution_id,
135
+ success: wf.status === "completed",
136
+ status: wf.status,
137
+ duration_ms: wf.duration_ms,
138
+ failed_at: failedNodeId,
139
+ execution_order: execOrder,
140
+ nodes: nodeSummaries,
141
+ }
142
+
143
+ if (failed) {
144
+ const failedNode = nodes[failedNodeId]
145
+ const hint = hintFromError(failedNode?.error)
146
+ if (hint) summary.hint = hint
147
+ summary.next_step =
148
+ `Inspect the failing node's input above. For full snapshot use: dypai_trace(execution_id='${trace.execution_id}', full=true).`
149
+ }
150
+
151
+ return summary
152
+ }
153
+
154
+ /**
155
+ * Given a test_workflow response that includes a `trace` field (from the
156
+ * updated remote MCP), apply summarization. Safe no-op if no trace present.
157
+ */
158
+ export function summarizeTestWorkflowResponse(response, mode = "smart") {
159
+ if (!response || typeof response !== "object") return response
160
+ if (!response.trace) return response // older engine or trace fetch failed
161
+ return {
162
+ ...response,
163
+ trace: summarizeTrace(response.trace, mode),
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Summarize a dypai_trace response. The response may contain either:
169
+ * - { execution_id, trace: {...} } (single trace)
170
+ * - { executions: [...] } (listing, already compact)
171
+ */
172
+ export function summarizeDypaiTraceResponse(response, mode = "smart") {
173
+ if (!response || typeof response !== "object") return response
174
+ if (response.trace) {
175
+ return { ...response, trace: summarizeTrace(response.trace, mode) }
176
+ }
177
+ return response
178
+ }