fodda-mcp 1.3.2 → 1.30.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.
Files changed (109) hide show
  1. package/LICENSE +31 -0
  2. package/README.md +192 -20
  3. package/dist/a2aHandler.d.ts +20 -0
  4. package/dist/a2aHandler.d.ts.map +1 -0
  5. package/dist/a2aHandler.js +247 -0
  6. package/dist/a2aHandler.js.map +1 -0
  7. package/dist/agents/fodda-researcher/index.d.ts +35 -0
  8. package/dist/agents/fodda-researcher/index.d.ts.map +1 -0
  9. package/dist/agents/fodda-researcher/index.js +62 -0
  10. package/dist/agents/fodda-researcher/index.js.map +1 -0
  11. package/dist/agents/fodda-researcher/skills.d.ts +16 -0
  12. package/dist/agents/fodda-researcher/skills.d.ts.map +1 -0
  13. package/dist/agents/fodda-researcher/skills.js +163 -0
  14. package/dist/agents/fodda-researcher/skills.js.map +1 -0
  15. package/dist/brandTemplate.d.ts +14 -0
  16. package/dist/brandTemplate.d.ts.map +1 -0
  17. package/dist/brandTemplate.js +742 -0
  18. package/dist/brandTemplate.js.map +1 -0
  19. package/dist/catalogCache.d.ts +177 -0
  20. package/dist/catalogCache.d.ts.map +1 -0
  21. package/dist/catalogCache.js +708 -0
  22. package/dist/catalogCache.js.map +1 -0
  23. package/dist/editorialFill.d.ts +44 -0
  24. package/dist/editorialFill.d.ts.map +1 -0
  25. package/dist/editorialFill.js +377 -0
  26. package/dist/editorialFill.js.map +1 -0
  27. package/dist/enrichment.d.ts +74 -0
  28. package/dist/enrichment.d.ts.map +1 -0
  29. package/dist/enrichment.js +211 -0
  30. package/dist/enrichment.js.map +1 -0
  31. package/dist/errorHandling.d.ts +52 -0
  32. package/dist/errorHandling.d.ts.map +1 -0
  33. package/dist/errorHandling.js +327 -0
  34. package/dist/errorHandling.js.map +1 -0
  35. package/dist/index.d.ts +0 -1
  36. package/dist/index.js +595 -410
  37. package/dist/index.js.map +1 -1
  38. package/dist/pricingCache.d.ts +99 -0
  39. package/dist/pricingCache.d.ts.map +1 -0
  40. package/dist/pricingCache.js +414 -0
  41. package/dist/pricingCache.js.map +1 -0
  42. package/dist/queryCache.d.ts +25 -0
  43. package/dist/queryCache.d.ts.map +1 -0
  44. package/dist/queryCache.js +194 -0
  45. package/dist/queryCache.js.map +1 -0
  46. package/dist/searchTemplate.d.ts +16 -0
  47. package/dist/searchTemplate.d.ts.map +1 -0
  48. package/dist/searchTemplate.js +318 -0
  49. package/dist/searchTemplate.js.map +1 -0
  50. package/dist/sessionTracker.d.ts +39 -0
  51. package/dist/sessionTracker.d.ts.map +1 -0
  52. package/dist/sessionTracker.js +187 -0
  53. package/dist/sessionTracker.js.map +1 -0
  54. package/dist/skillClient.d.ts +142 -0
  55. package/dist/skillClient.d.ts.map +1 -0
  56. package/dist/skillClient.js +256 -0
  57. package/dist/skillClient.js.map +1 -0
  58. package/dist/svgVisuals.d.ts +77 -0
  59. package/dist/svgVisuals.d.ts.map +1 -0
  60. package/dist/svgVisuals.js +323 -0
  61. package/dist/svgVisuals.js.map +1 -0
  62. package/dist/systemPrompt.d.ts +26 -0
  63. package/dist/systemPrompt.d.ts.map +1 -0
  64. package/dist/systemPrompt.js +546 -0
  65. package/dist/systemPrompt.js.map +1 -0
  66. package/dist/test-waverunner-response.d.ts +2 -0
  67. package/dist/test-waverunner-response.d.ts.map +1 -0
  68. package/dist/test-waverunner-response.js +20 -0
  69. package/dist/test-waverunner-response.js.map +1 -0
  70. package/dist/test_analyst.d.ts +2 -0
  71. package/dist/test_analyst.d.ts.map +1 -0
  72. package/dist/test_analyst.js +39 -0
  73. package/dist/test_analyst.js.map +1 -0
  74. package/dist/test_analyst_local.d.ts +2 -0
  75. package/dist/test_analyst_local.d.ts.map +1 -0
  76. package/dist/test_analyst_local.js +51 -0
  77. package/dist/test_analyst_local.js.map +1 -0
  78. package/dist/test_live_mcp.d.ts +2 -0
  79. package/dist/test_live_mcp.d.ts.map +1 -0
  80. package/dist/test_live_mcp.js +49 -0
  81. package/dist/test_live_mcp.js.map +1 -0
  82. package/dist/test_skills.d.ts +9 -0
  83. package/dist/test_skills.d.ts.map +1 -0
  84. package/dist/test_skills.js +267 -0
  85. package/dist/test_skills.js.map +1 -0
  86. package/dist/toolHandlers.d.ts +11 -0
  87. package/dist/toolHandlers.d.ts.map +1 -0
  88. package/dist/toolHandlers.js +2829 -0
  89. package/dist/toolHandlers.js.map +1 -0
  90. package/dist/tools.d.ts +80 -3
  91. package/dist/tools.d.ts.map +1 -1
  92. package/dist/tools.js +89 -186
  93. package/dist/tools.js.map +1 -1
  94. package/dist/trialTracker.d.ts +58 -0
  95. package/dist/trialTracker.d.ts.map +1 -0
  96. package/dist/trialTracker.js +179 -0
  97. package/dist/trialTracker.js.map +1 -0
  98. package/dist/types.d.ts +3 -0
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/verify_hmac.js +1 -1
  101. package/dist/verify_hmac.js.map +1 -1
  102. package/dist/verify_tools_endpoint.js +5 -5
  103. package/dist/verify_tools_endpoint.js.map +1 -1
  104. package/dist/widgetShell.d.ts +15 -0
  105. package/dist/widgetShell.d.ts.map +1 -0
  106. package/dist/widgetShell.js +151 -0
  107. package/dist/widgetShell.js.map +1 -0
  108. package/package.json +10 -6
  109. package/server.json +12 -17
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Session Tracker — in-memory search history and frustration detection.
3
+ *
4
+ * Tracks tool call patterns within a single MCP session to detect
5
+ * structural frustration signals (repeated queries, NO_MATCH streaks,
6
+ * graph bouncing). Hints are injected into response text for Claude
7
+ * to act on — the user never sees them directly.
8
+ *
9
+ * When aggregate frustration is high (3+ patterns triggered in one session),
10
+ * an alert is posted to #fodda-sales on Slack for the sales bot to enrich.
11
+ *
12
+ * Resets per MCP connection (stateless across sessions).
13
+ */
14
+ // ---------------------------------------------------------------------------
15
+ // Slack posting — fire-and-forget frustration alerts
16
+ // ---------------------------------------------------------------------------
17
+ const SLACK_BOT_USER_ID = 'U0AU49JG7AS';
18
+ /**
19
+ * Post a message to #fodda-sales via the Slack Bot Token.
20
+ * Fire-and-forget — errors are logged but never thrown.
21
+ */
22
+ export async function postToSlack(text) {
23
+ const token = process.env.SLACK_BOT_TOKEN;
24
+ if (!token) {
25
+ console.error('[slack] SLACK_BOT_TOKEN not set — skipping Slack post');
26
+ return;
27
+ }
28
+ try {
29
+ const resp = await fetch('https://slack.com/api/chat.postMessage', {
30
+ method: 'POST',
31
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({ channel: 'fodda-sales', text, unfurl_links: false }),
33
+ });
34
+ const body = await resp.json();
35
+ if (!body.ok) {
36
+ console.error(`[slack] Slack API error: ${body.error}`);
37
+ }
38
+ }
39
+ catch (err) {
40
+ console.error(`[slack] Failed to post to Slack: ${err.message}`);
41
+ }
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // Session state — one instance per createServer() call
45
+ // ---------------------------------------------------------------------------
46
+ export function createSessionTracker() {
47
+ const sessionSearches = [];
48
+ let frustrationSlackSent = false; // Only post once per session
49
+ /**
50
+ * Record a search call after it completes.
51
+ */
52
+ function trackSearch(query, graphId, resultCount) {
53
+ sessionSearches.push({
54
+ query: query.toLowerCase().trim(),
55
+ graphId,
56
+ resultCount,
57
+ timestamp: Date.now(),
58
+ });
59
+ }
60
+ /**
61
+ * Return the last N searches for context (used by Slack alerts).
62
+ */
63
+ function getRecentSearches(n = 5) {
64
+ return sessionSearches.slice(-n);
65
+ }
66
+ /**
67
+ * Compute a frustration score and details from the recent search history.
68
+ * Returns null if no frustration patterns are detected.
69
+ */
70
+ function getFrustrationDetails() {
71
+ if (sessionSearches.length < 2)
72
+ return null;
73
+ const recent = sessionSearches.slice(-5);
74
+ const last = recent[recent.length - 1];
75
+ if (!last)
76
+ return null;
77
+ let score = 0;
78
+ let dominantPattern = 'NO_MATCH';
79
+ // Pattern 1: Repeated similar queries (same graph, overlapping words)
80
+ const similarCount = recent.filter(s => s.graphId === last.graphId &&
81
+ overlapRatio(s.query, last.query) > 0.5).length;
82
+ if (similarCount >= 3) {
83
+ score++;
84
+ dominantPattern = 'LOW_YIELD';
85
+ }
86
+ // Pattern 2: NO_MATCH streak
87
+ const noMatchStreak = recent.filter(s => s.resultCount === 0).length;
88
+ if (noMatchStreak >= 2) {
89
+ score++;
90
+ dominantPattern = 'NO_MATCH';
91
+ }
92
+ // Pattern 3: Graph bouncing (same query, multiple graphs)
93
+ const graphsForSameQuery = [...new Set(recent.filter(s => overlapRatio(s.query, last.query) > 0.6).map(s => s.graphId))];
94
+ if (graphsForSameQuery.length >= 3) {
95
+ score++;
96
+ dominantPattern = 'GRAPH_BOUNCING';
97
+ }
98
+ if (score === 0)
99
+ return null;
100
+ return {
101
+ pattern: dominantPattern,
102
+ graphsTried: [...new Set(recent.map(s => s.graphId))],
103
+ recentQueries: [...new Set(recent.map(s => s.query))],
104
+ score,
105
+ };
106
+ }
107
+ /**
108
+ * Detect frustration patterns from the recent search history.
109
+ * Returns a hint string for Claude, or null if no frustration detected.
110
+ */
111
+ function detectFrustration() {
112
+ if (sessionSearches.length < 2)
113
+ return null;
114
+ const recent = sessionSearches.slice(-5); // last 5 searches
115
+ const last = recent[recent.length - 1];
116
+ if (!last)
117
+ return null;
118
+ // Pattern 1: Repeated similar queries (same graph, overlapping words)
119
+ const similarCount = recent.filter(s => s.graphId === last.graphId &&
120
+ overlapRatio(s.query, last.query) > 0.5).length;
121
+ if (similarCount >= 3) {
122
+ return `⚠️ LOW YIELD: ${similarCount} similar searches in "${last.graphId}" ` +
123
+ `with declining results. Consider:\n` +
124
+ `1. Try a different graph — the topic may be covered elsewhere\n` +
125
+ `2. Broaden the search terms\n` +
126
+ `3. Use supplemental sources (Google Trends, Amazon) for external validation\n` +
127
+ `4. Ask the user to clarify what specific angle they need`;
128
+ }
129
+ // Pattern 2: NO_MATCH streak
130
+ const noMatchStreak = recent.filter(s => s.resultCount === 0).length;
131
+ if (noMatchStreak >= 2) {
132
+ const graphs = [...new Set(recent.map(s => s.graphId))];
133
+ return `⚠️ NO RESULTS for last ${noMatchStreak} searches. ` +
134
+ `Graphs tried: ${graphs.join(', ')}.\n` +
135
+ `This topic may not be in the curated knowledge graphs. Consider:\n` +
136
+ `1. Supplemental data sources for raw market/economic data\n` +
137
+ `2. Broader related terms that the graph may cover\n` +
138
+ `3. Checking if the user's actual need can be met differently`;
139
+ }
140
+ // Pattern 3: Graph bouncing (same query, multiple graphs)
141
+ const lastQuery = last.query;
142
+ const graphsForSameQuery = [...new Set(recent.filter(s => overlapRatio(s.query, lastQuery) > 0.6).map(s => s.graphId))];
143
+ if (graphsForSameQuery.length >= 3) {
144
+ return `⚠️ GRAPH BOUNCING: User searched "${lastQuery}" across ` +
145
+ `${graphsForSameQuery.length} graphs (${graphsForSameQuery.join(', ')}). ` +
146
+ `They may be unsure which graph to use. Help them narrow down.`;
147
+ }
148
+ return null;
149
+ }
150
+ /**
151
+ * Post aggregate frustration to #fodda-sales on Slack.
152
+ * Only fires once per session, and only when frustration score >= 2
153
+ * (i.e., multiple patterns triggered — not every single NO_MATCH).
154
+ */
155
+ function postFrustrationToSlack(userIdentifier) {
156
+ if (frustrationSlackSent)
157
+ return;
158
+ const details = getFrustrationDetails();
159
+ if (!details || details.score < 2)
160
+ return; // Only aggregate frustration
161
+ frustrationSlackSent = true;
162
+ const text = [
163
+ `<@${SLACK_BOT_USER_ID}> ⚠️ *Session Frustration Detected*`,
164
+ `👤 ${userIdentifier}`,
165
+ `🔍 Pattern: ${details.pattern}`,
166
+ `📊 Graphs tried: ${details.graphsTried.join(', ')}`,
167
+ `🔎 Queries: ${details.recentQueries.join(', ')}`,
168
+ `📈 Frustration score: ${details.score}/3`,
169
+ `→ User may be struggling. Check if content gaps or UX issues are involved.`,
170
+ ].join('\n');
171
+ // Fire-and-forget — never await in the hot path
172
+ postToSlack(text).catch(() => { });
173
+ }
174
+ return { trackSearch, detectFrustration, getRecentSearches, getFrustrationDetails, postFrustrationToSlack };
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Utility — simple word-overlap ratio
178
+ // ---------------------------------------------------------------------------
179
+ function overlapRatio(a, b) {
180
+ const wordsA = new Set(a.split(/\s+/).filter(w => w.length > 2));
181
+ const wordsB = new Set(b.split(/\s+/).filter(w => w.length > 2));
182
+ if (wordsA.size === 0 || wordsB.size === 0)
183
+ return 0;
184
+ const intersection = [...wordsA].filter(w => wordsB.has(w)).length;
185
+ return intersection / Math.min(wordsA.size, wordsB.size);
186
+ }
187
+ //# sourceMappingURL=sessionTracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionTracker.js","sourceRoot":"","sources":["../src/sessionTracker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Skill Client — Routes skill calls through the Fodda Core API.
3
+ *
4
+ * Architecture change (2026-05-03):
5
+ * BEFORE: MCP connected directly to external skill MCP servers.
6
+ * NOW: All skill calls route through Core API endpoints:
7
+ * - GET /v1/skills/{skill_id}/tools → discover available tools
8
+ * - POST /v1/skills/{skill_id}/execute → execute a specific tool
9
+ *
10
+ * The Core API handles auth, access control, billing (2 tokens/call),
11
+ * and upstream MCP connections. The MCP Server never talks to skill
12
+ * servers directly.
13
+ *
14
+ * Design decisions:
15
+ * - Fail-open — if discovery or execution fails, Fodda proceeds without skills
16
+ * - 15s timeout for execution (skill + API overhead)
17
+ * - 10s timeout for discovery
18
+ * - Structured input contract preserved for output-phase skills
19
+ */
20
+ /** A single tool exposed by a skill, as returned by the Core API. */
21
+ export interface SkillToolDefinition {
22
+ /** Tool name as registered on the skill's MCP server */
23
+ name: string;
24
+ /** Human-readable description */
25
+ description: string;
26
+ /** JSON Schema for tool arguments */
27
+ inputSchema: Record<string, any>;
28
+ }
29
+ /** Discovery response from GET /v1/skills/{skill_id}/tools */
30
+ export interface DiscoveredSkill {
31
+ /** Skill identifier (matches graph_id) */
32
+ skill_id: string;
33
+ /** Human-readable skill name */
34
+ skill_name: string;
35
+ /** Available tools on this skill */
36
+ tools: SkillToolDefinition[];
37
+ /** Token cost per execution (default: 2) */
38
+ cost_per_call: number;
39
+ }
40
+ /** Minimal skill config — no longer needs mcp_url or auth tokens */
41
+ export interface SkillConfig {
42
+ /** Skill identifier (matches graph_id in catalog) */
43
+ id: string;
44
+ /** Human-readable name */
45
+ name: string;
46
+ /** When in the pipeline to call: 'output' (post-research) or 'research' (pre-search, future) */
47
+ phase: 'output' | 'research';
48
+ }
49
+ export interface SkillInput {
50
+ /** The user's original query */
51
+ query: string;
52
+ /** Primary graph ID that was searched */
53
+ graphId: string;
54
+ /** Graph metadata */
55
+ context: {
56
+ graphName: string;
57
+ curatorName: string;
58
+ domain: string;
59
+ };
60
+ /** Trend results from graph search */
61
+ trends: Array<{
62
+ name: string;
63
+ summary?: string;
64
+ signal_score?: number;
65
+ trendLifecycle?: string;
66
+ momentum?: string;
67
+ evidence_count?: number;
68
+ graphName?: string;
69
+ }>;
70
+ /** Evidence articles from graph search */
71
+ evidence: Array<{
72
+ title: string;
73
+ sourceUrl?: string;
74
+ brandNames?: string[];
75
+ place?: string;
76
+ snippet?: string;
77
+ }>;
78
+ /** Supplemental data (Google Trends, Census, etc.) */
79
+ supplemental?: Record<string, any> | undefined;
80
+ }
81
+ export interface SkillResult {
82
+ /** Skill identifier */
83
+ skillId: string;
84
+ /** Human-readable skill name */
85
+ skillName: string;
86
+ /** Whether the skill executed successfully */
87
+ success: boolean;
88
+ /** Transformed output from the skill (markdown or JSON string) */
89
+ output?: string;
90
+ /** Error message if the skill failed */
91
+ error?: string;
92
+ /** How long the skill took to execute */
93
+ durationMs: number;
94
+ }
95
+ /**
96
+ * Discover available tools for a skill by calling the Core API.
97
+ *
98
+ * GET /v1/skills/{skill_id}/tools
99
+ *
100
+ * Returns null on failure (fail-open — skill is silently skipped).
101
+ */
102
+ export declare function discoverSkillTools(skillId: string, apiKey: string): Promise<DiscoveredSkill | null>;
103
+ /**
104
+ * Execute a specific tool on a skill via the Core API.
105
+ *
106
+ * POST /v1/skills/{skill_id}/execute
107
+ * Body: { tool: "tool_name", arguments: {...} }
108
+ *
109
+ * Returns the result text, or throws on error.
110
+ */
111
+ export declare function executeSkillTool(skillId: string, toolName: string, args: Record<string, any>, apiKey: string, userId: string): Promise<{
112
+ output: string;
113
+ durationMs: number;
114
+ }>;
115
+ /**
116
+ * Map a skill execution error to a user-friendly message.
117
+ * Returns null if the error should be handled by the standard error path
118
+ * (e.g. CREDITS_EXHAUSTED).
119
+ */
120
+ export declare function mapSkillError(err: any): string | null;
121
+ /**
122
+ * Call a skill's output-phase processing via the Core API.
123
+ *
124
+ * This wraps executeSkillTool() with the Fodda output data contract
125
+ * and returns a SkillResult for backward compatibility with the
126
+ * search_graph pipeline.
127
+ *
128
+ * Fail-open: returns a result with success=false on any error.
129
+ */
130
+ export declare function callSkill(skill: SkillConfig, input: SkillInput, apiKey: string, userId: string): Promise<SkillResult>;
131
+ /**
132
+ * Call all enabled output-phase skills in parallel via the Core API.
133
+ * Returns results for all skills (success or failure).
134
+ * Never throws — individual failures are captured in the result objects.
135
+ */
136
+ export declare function callOutputSkills(skills: SkillConfig[], input: SkillInput, apiKey: string, userId: string): Promise<SkillResult[]>;
137
+ /**
138
+ * Package Fodda search results into the standard skill input format.
139
+ * Call this after search_graph results are enriched but before widget rendering.
140
+ */
141
+ export declare function buildSkillInput(query: string, data: any, effectiveGraphId: string, primaryGraphName: string, supplemental?: Record<string, any>): SkillInput;
142
+ //# sourceMappingURL=skillClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillClient.d.ts","sourceRoot":"","sources":["../src/skillClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Skill Client — Routes skill calls through the Fodda Core API.
3
+ *
4
+ * Architecture change (2026-05-03):
5
+ * BEFORE: MCP connected directly to external skill MCP servers.
6
+ * NOW: All skill calls route through Core API endpoints:
7
+ * - GET /v1/skills/{skill_id}/tools → discover available tools
8
+ * - POST /v1/skills/{skill_id}/execute → execute a specific tool
9
+ *
10
+ * The Core API handles auth, access control, billing (2 tokens/call),
11
+ * and upstream MCP connections. The MCP Server never talks to skill
12
+ * servers directly.
13
+ *
14
+ * Design decisions:
15
+ * - Fail-open — if discovery or execution fails, Fodda proceeds without skills
16
+ * - 15s timeout for execution (skill + API overhead)
17
+ * - 10s timeout for discovery
18
+ * - Structured input contract preserved for output-phase skills
19
+ */
20
+ import axios from 'axios';
21
+ // ---------------------------------------------------------------------------
22
+ // Constants
23
+ // ---------------------------------------------------------------------------
24
+ const API_BASE_URL = process.env.FODDA_API_URL || 'https://api.fodda.ai';
25
+ const DISCOVERY_TIMEOUT_MS = 10_000;
26
+ const EXECUTION_TIMEOUT_MS = 15_000;
27
+ // ---------------------------------------------------------------------------
28
+ // Discovery: Get available tools for a skill via Core API
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Discover available tools for a skill by calling the Core API.
32
+ *
33
+ * GET /v1/skills/{skill_id}/tools
34
+ *
35
+ * Returns null on failure (fail-open — skill is silently skipped).
36
+ */
37
+ export async function discoverSkillTools(skillId, apiKey) {
38
+ try {
39
+ const url = `${API_BASE_URL}/v1/skills/${encodeURIComponent(skillId)}/tools`;
40
+ const response = await axios.get(url, {
41
+ headers: {
42
+ 'X-API-Key': apiKey,
43
+ },
44
+ timeout: DISCOVERY_TIMEOUT_MS,
45
+ });
46
+ const data = response.data;
47
+ if (!data || !Array.isArray(data.tools)) {
48
+ console.error(`[skillClient] Discovery for ${skillId}: invalid response shape`);
49
+ return null;
50
+ }
51
+ console.error(`[skillClient] Discovered ${data.tools.length} tool(s) for ${data.skill_name || skillId}`);
52
+ return {
53
+ skill_id: data.skill_id || skillId,
54
+ skill_name: data.skill_name || skillId,
55
+ tools: data.tools,
56
+ cost_per_call: data.cost_per_call ?? 2,
57
+ };
58
+ }
59
+ catch (err) {
60
+ const status = err.response?.status;
61
+ const code = err.response?.data?.error?.code;
62
+ if (status === 404) {
63
+ console.error(`[skillClient] Skill "${skillId}" not found or not available (${code || '404'})`);
64
+ }
65
+ else if (status === 403) {
66
+ console.error(`[skillClient] Skill "${skillId}" access denied (${code || '403'}) — may be disabled or draft`);
67
+ }
68
+ else {
69
+ console.error(`[skillClient] Discovery failed for ${skillId}: ${err.message}`);
70
+ }
71
+ return null;
72
+ }
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Execution: Call a skill tool via Core API
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * Execute a specific tool on a skill via the Core API.
79
+ *
80
+ * POST /v1/skills/{skill_id}/execute
81
+ * Body: { tool: "tool_name", arguments: {...} }
82
+ *
83
+ * Returns the result text, or throws on error.
84
+ */
85
+ export async function executeSkillTool(skillId, toolName, args, apiKey, userId) {
86
+ const startTime = Date.now();
87
+ const url = `${API_BASE_URL}/v1/skills/${encodeURIComponent(skillId)}/execute`;
88
+ const response = await axios.post(url, {
89
+ tool: toolName,
90
+ arguments: args,
91
+ }, {
92
+ headers: {
93
+ 'X-API-Key': apiKey,
94
+ 'X-User-Id': userId,
95
+ 'X-Fodda-Billing': 'mcp-orchestrated',
96
+ 'Content-Type': 'application/json',
97
+ },
98
+ timeout: EXECUTION_TIMEOUT_MS,
99
+ });
100
+ const durationMs = Date.now() - startTime;
101
+ const data = response.data;
102
+ // Extract text from response — the API may return { result: "..." } or { result: { content: [...] } }
103
+ let output = '';
104
+ if (typeof data.result === 'string') {
105
+ output = data.result;
106
+ }
107
+ else if (data.result?.content) {
108
+ const contentArr = Array.isArray(data.result.content) ? data.result.content : [];
109
+ output = contentArr
110
+ .map((c) => (c.type === 'text' ? c.text : JSON.stringify(c)))
111
+ .join('\n');
112
+ }
113
+ else if (data.result) {
114
+ output = JSON.stringify(data.result);
115
+ }
116
+ console.error(`[skillClient] ${skillId}/${toolName} executed in ${durationMs}ms (${output.length} chars)`);
117
+ return { output, durationMs };
118
+ }
119
+ // ---------------------------------------------------------------------------
120
+ // Error mapping — Convert API error codes to user-friendly messages
121
+ // ---------------------------------------------------------------------------
122
+ /**
123
+ * Map a skill execution error to a user-friendly message.
124
+ * Returns null if the error should be handled by the standard error path
125
+ * (e.g. CREDITS_EXHAUSTED).
126
+ */
127
+ export function mapSkillError(err) {
128
+ const status = err.response?.status;
129
+ const code = err.response?.data?.error?.code;
130
+ const message = err.response?.data?.error?.message;
131
+ switch (code) {
132
+ case 'GRAPH_DISABLED':
133
+ return '⚠️ This skill is currently disabled in your dashboard. Re-enable it at app.fodda.ai/graphs.';
134
+ case 'SKILL_NOT_FOUND':
135
+ return '⚠️ This skill is not currently available on the Fodda platform.';
136
+ case 'SKILL_NOT_AVAILABLE':
137
+ return '⚠️ This skill is not currently available — it may be in development.';
138
+ case 'SKILL_UPSTREAM_ERROR':
139
+ return '⚠️ The skill server encountered an error. Please try again in a moment.';
140
+ case 'CREDITS_EXHAUSTED':
141
+ return null; // Let the standard credit exhaustion handler deal with this
142
+ default:
143
+ if (status === 403) {
144
+ return `⚠️ Access denied: ${message || 'You may not have access to this skill.'}`;
145
+ }
146
+ if (status === 404) {
147
+ return '⚠️ This skill is not currently available.';
148
+ }
149
+ return null; // Unknown error — let caller handle
150
+ }
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // Core: Call a single output-phase skill via Core API
154
+ // ---------------------------------------------------------------------------
155
+ /**
156
+ * Call a skill's output-phase processing via the Core API.
157
+ *
158
+ * This wraps executeSkillTool() with the Fodda output data contract
159
+ * and returns a SkillResult for backward compatibility with the
160
+ * search_graph pipeline.
161
+ *
162
+ * Fail-open: returns a result with success=false on any error.
163
+ */
164
+ export async function callSkill(skill, input, apiKey, userId) {
165
+ const startTime = Date.now();
166
+ try {
167
+ const { output, durationMs } = await executeSkillTool(skill.id, 'process', // output-phase skills use a standard 'process' tool
168
+ {
169
+ fodda_output: input,
170
+ query: input.query,
171
+ trends: input.trends,
172
+ evidence: input.evidence,
173
+ }, apiKey, userId);
174
+ return {
175
+ skillId: skill.id,
176
+ skillName: skill.name,
177
+ success: true,
178
+ output,
179
+ durationMs,
180
+ };
181
+ }
182
+ catch (err) {
183
+ const durationMs = Date.now() - startTime;
184
+ const friendlyMsg = mapSkillError(err);
185
+ const errorMsg = friendlyMsg || err.response?.data?.error?.message || err.message;
186
+ console.error(`[skillClient] ${skill.name} failed after ${durationMs}ms: ${errorMsg}`);
187
+ return {
188
+ skillId: skill.id,
189
+ skillName: skill.name,
190
+ success: false,
191
+ error: errorMsg,
192
+ durationMs,
193
+ };
194
+ }
195
+ }
196
+ // ---------------------------------------------------------------------------
197
+ // Batch: Call multiple output-phase skills in parallel
198
+ // ---------------------------------------------------------------------------
199
+ /**
200
+ * Call all enabled output-phase skills in parallel via the Core API.
201
+ * Returns results for all skills (success or failure).
202
+ * Never throws — individual failures are captured in the result objects.
203
+ */
204
+ export async function callOutputSkills(skills, input, apiKey, userId) {
205
+ const outputSkills = skills.filter(s => s.phase === 'output');
206
+ if (outputSkills.length === 0)
207
+ return [];
208
+ console.error(`[skillClient] Calling ${outputSkills.length} output skill(s): ${outputSkills.map(s => s.name).join(', ')}`);
209
+ const results = await Promise.allSettled(outputSkills.map(skill => callSkill(skill, input, apiKey, userId)));
210
+ return results.map(r => r.status === 'fulfilled'
211
+ ? r.value
212
+ : {
213
+ skillId: 'unknown',
214
+ skillName: 'unknown',
215
+ success: false,
216
+ error: r.reason?.message || 'Unknown error',
217
+ durationMs: 0,
218
+ });
219
+ }
220
+ // ---------------------------------------------------------------------------
221
+ // Helpers: Build skill input from search results
222
+ // ---------------------------------------------------------------------------
223
+ /**
224
+ * Package Fodda search results into the standard skill input format.
225
+ * Call this after search_graph results are enriched but before widget rendering.
226
+ */
227
+ export function buildSkillInput(query, data, effectiveGraphId, primaryGraphName, supplemental) {
228
+ const rows = data?.rows || [];
229
+ return {
230
+ query,
231
+ graphId: effectiveGraphId,
232
+ context: {
233
+ graphName: primaryGraphName,
234
+ curatorName: rows[0]?.curator || '',
235
+ domain: rows[0]?.domain || '',
236
+ },
237
+ trends: rows.map((r) => ({
238
+ name: r.trendName || r.display || r.name || r.title || '',
239
+ summary: r.summary || r.description || r.trendDescription || '',
240
+ signal_score: r.signal_score || r.score || 0,
241
+ trendLifecycle: r.trendLifecycle || 'unknown',
242
+ momentum: r.momentum || 'unknown',
243
+ evidence_count: r.evidence_count || r.evidenceCount || 0,
244
+ graphName: r.graphName || '',
245
+ })),
246
+ evidence: rows.flatMap((r) => (r.evidence || []).map((e) => ({
247
+ title: e.title || e.articleTitle || '',
248
+ sourceUrl: e.sourceUrl || e.url || '',
249
+ brandNames: e.brandNames || [],
250
+ place: e.place || e.geographical_region || '',
251
+ snippet: e.snippet || e.summary || '',
252
+ }))).slice(0, 50), // Cap evidence to avoid oversized payloads
253
+ supplemental: supplemental || undefined,
254
+ };
255
+ }
256
+ //# sourceMappingURL=skillClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillClient.js","sourceRoot":"","sources":["../src/skillClient.ts"],"names":[],"mappings":"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"}