@@ -8,6 +8,7 @@ import { AGENTS, discoverAgents, getAgentById } from './agents/registry.js';
88import { runResearch , runSummary , runAnalysis , runCode , setApiKey , MODEL_LABELS } from './agents/services.js' ;
99import { orchestrate } from './agents/orchestrator.js' ;
1010import { getBalance , getTransactions , sendPayment } from './stellar/wallet.js' ;
11+ import { requestId , errorHandler } from './middleware/errorHandler.js' ;
1112
1213// x402 imports
1314import { paymentMiddlewareFromConfig } from '@x402/express' ;
@@ -20,6 +21,7 @@ const app = express();
2021app . use ( cors ( ) ) ;
2122app . use ( express . json ( ) ) ;
2223app . use ( express . static ( path . join ( __dirname , '..' , 'public' ) ) ) ;
24+ app . use ( requestId ) ;
2325
2426// ─── SSE Event Stream ────────────────────────────────────────
2527const sseClients = [ ] ;
@@ -103,117 +105,122 @@ if (config.serverAddress) {
103105}
104106
105107// ─── Premium x402-Protected Endpoints ────────────────────────
106- app . get ( '/api/premium/research' , async ( req , res ) => {
108+ app . get ( '/api/premium/research' , async ( req , res , next ) => {
107109 try {
108110 const topic = req . query . topic || 'AI and blockchain payments' ;
109111 broadcast ( { type : 'agent_call' , agent : '🔬 Research Agent' , agentId : 'research-bot' , input : topic , cost : '0.01' , timestamp : new Date ( ) . toISOString ( ) } ) ;
110112 const result = await runResearch ( topic ) ;
111113 broadcast ( { type : 'agent_response' , agent : '🔬 Research Agent' , agentId : 'research-bot' , resultPreview : result . substring ( 0 , 150 ) , cost : '0.01' , timestamp : new Date ( ) . toISOString ( ) } ) ;
112114 res . json ( { agent : 'research-bot' , topic, result, model : MODEL_LABELS . research , cost : '0.01 USDC' , paidVia : 'x402' } ) ;
113115 } catch ( err ) {
114- res . status ( 500 ) . json ( { error : 'Research agent temporarily unavailable' , details : err . message } ) ;
116+ next ( err ) ;
115117 }
116118} ) ;
117119
118- app . get ( '/api/premium/summarize' , async ( req , res ) => {
120+ app . get ( '/api/premium/summarize' , async ( req , res , next ) => {
119121 try {
120122 const text = req . query . text || 'Please provide text to summarize via ?text= parameter' ;
121123 broadcast ( { type : 'agent_call' , agent : '📝 Summary Agent' , agentId : 'summary-bot' , input : text . substring ( 0 , 100 ) , cost : '0.01' , timestamp : new Date ( ) . toISOString ( ) } ) ;
122124 const result = await runSummary ( text ) ;
123125 broadcast ( { type : 'agent_response' , agent : '📝 Summary Agent' , agentId : 'summary-bot' , resultPreview : result . substring ( 0 , 150 ) , cost : '0.01' , timestamp : new Date ( ) . toISOString ( ) } ) ;
124126 res . json ( { agent : 'summary-bot' , result, model : MODEL_LABELS . summary , cost : '0.01 USDC' , paidVia : 'x402' } ) ;
125127 } catch ( err ) {
126- res . status ( 500 ) . json ( { error : 'Summary agent temporarily unavailable' , details : err . message } ) ;
128+ next ( err ) ;
127129 }
128130} ) ;
129131
130- app . get ( '/api/premium/analyze' , async ( req , res ) => {
132+ app . get ( '/api/premium/analyze' , async ( req , res , next ) => {
131133 try {
132134 const topic = req . query . topic || 'AI agent economies' ;
133135 broadcast ( { type : 'agent_call' , agent : '📊 Analysis Agent' , agentId : 'analyst-bot' , input : topic , cost : '0.05' , timestamp : new Date ( ) . toISOString ( ) } ) ;
134136 const result = await runAnalysis ( topic ) ;
135137 broadcast ( { type : 'agent_response' , agent : '📊 Analysis Agent' , agentId : 'analyst-bot' , resultPreview : result . substring ( 0 , 150 ) , cost : '0.05' , timestamp : new Date ( ) . toISOString ( ) } ) ;
136138 res . json ( { agent : 'analyst-bot' , topic, result, model : MODEL_LABELS . analysis , cost : '0.05 USDC' , paidVia : 'x402' } ) ;
137139 } catch ( err ) {
138- res . status ( 500 ) . json ( { error : 'Analysis agent temporarily unavailable' , details : err . message } ) ;
140+ next ( err ) ;
139141 }
140142} ) ;
141143
142- app . get ( '/api/premium/code' , async ( req , res ) => {
144+ app . get ( '/api/premium/code' , async ( req , res , next ) => {
143145 try {
144146 const prompt = req . query . prompt || 'Write a hello world function' ;
145147 broadcast ( { type : 'agent_call' , agent : '💻 Code Agent' , agentId : 'code-bot' , input : prompt . substring ( 0 , 100 ) , cost : '0.03' , timestamp : new Date ( ) . toISOString ( ) } ) ;
146148 const result = await runCode ( prompt ) ;
147149 broadcast ( { type : 'agent_response' , agent : '💻 Code Agent' , agentId : 'code-bot' , resultPreview : result . substring ( 0 , 150 ) , cost : '0.03' , timestamp : new Date ( ) . toISOString ( ) } ) ;
148150 res . json ( { agent : 'code-bot' , prompt, result, model : MODEL_LABELS . code , cost : '0.03 USDC' , paidVia : 'x402' } ) ;
149151 } catch ( err ) {
150- res . status ( 500 ) . json ( { error : 'Code agent temporarily unavailable' , details : err . message } ) ;
152+ next ( err ) ;
151153 }
152154} ) ;
153155
154156// ─── Free Agent Endpoints (for internal orchestrator use) ────
155- app . get ( '/api/research' , async ( req , res ) => {
157+ app . get ( '/api/research' , async ( req , res , next ) => {
156158 try {
157159 const topic = req . query . topic || 'AI payments' ;
158160 const result = await runResearch ( topic ) ;
159161 res . json ( { agent : 'research-bot' , topic, result, model : MODEL_LABELS . research , cost : '0.01 USDC' } ) ;
160162 } catch ( err ) {
161- res . status ( 500 ) . json ( { error : 'Agent temporarily unavailable' , fallback : 'Try again' } ) ;
163+ next ( err ) ;
162164 }
163165} ) ;
164166
165- app . get ( '/api/summarize' , async ( req , res ) => {
167+ app . get ( '/api/summarize' , async ( req , res , next ) => {
166168 try {
167169 const text = req . query . text || '' ;
168170 const result = await runSummary ( text ) ;
169171 res . json ( { agent : 'summary-bot' , result, model : MODEL_LABELS . summary , cost : '0.01 USDC' } ) ;
170172 } catch ( err ) {
171- res . status ( 500 ) . json ( { error : 'Agent temporarily unavailable' , fallback : 'Try again' } ) ;
173+ next ( err ) ;
172174 }
173175} ) ;
174176
175- app . get ( '/api/analyze' , async ( req , res ) => {
177+ app . get ( '/api/analyze' , async ( req , res , next ) => {
176178 try {
177179 const topic = req . query . topic || '' ;
178180 const result = await runAnalysis ( topic ) ;
179181 res . json ( { agent : 'analyst-bot' , topic, result, model : MODEL_LABELS . analysis , cost : '0.05 USDC' } ) ;
180182 } catch ( err ) {
181- res . status ( 500 ) . json ( { error : 'Agent temporarily unavailable' , fallback : 'Try again' } ) ;
183+ next ( err ) ;
182184 }
183185} ) ;
184186
185- app . get ( '/api/code' , async ( req , res ) => {
187+ app . get ( '/api/code' , async ( req , res , next ) => {
186188 try {
187189 const prompt = req . query . prompt || '' ;
188190 const result = await runCode ( prompt ) ;
189191 res . json ( { agent : 'code-bot' , result, model : MODEL_LABELS . code , cost : '0.03 USDC' } ) ;
190192 } catch ( err ) {
191- res . status ( 500 ) . json ( { error : 'Agent temporarily unavailable' , fallback : 'Try again' } ) ;
193+ next ( err ) ;
192194 }
193195} ) ;
194196
195197// ─── Orchestrator Endpoint ───────────────────────────────────
196- app . post ( '/api/orchestrate' , async ( req , res ) => {
198+ app . post ( '/api/orchestrate' , async ( req , res , next ) => {
197199 try {
198200 const { task, budget } = req . body ;
199- if ( ! task ) return res . status ( 400 ) . json ( { error : 'Missing "task" in request body' } ) ;
201+ if ( ! task ) {
202+ const err = new Error ( 'Missing "task" in request body' ) ;
203+ err . status = 400 ;
204+ err . code = 'MISSING_FIELD' ;
205+ return next ( err ) ;
206+ }
200207 const budgetNum = parseFloat ( budget ) || 0.15 ;
201208 const result = await orchestrate ( task , budgetNum , broadcast ) ;
202209 res . json ( result ) ;
203210 } catch ( err ) {
204- res . status ( 500 ) . json ( { error : 'Orchestrator failed' , details : err . message } ) ;
211+ next ( err ) ;
205212 }
206213} ) ;
207214
208215// Also support GET for easy testing
209- app . get ( '/api/orchestrate' , async ( req , res ) => {
216+ app . get ( '/api/orchestrate' , async ( req , res , next ) => {
210217 try {
211218 const task = req . query . task || 'Research AI payments' ;
212219 const budget = parseFloat ( req . query . budget ) || 0.15 ;
213220 const result = await orchestrate ( task , budget , broadcast ) ;
214221 res . json ( result ) ;
215222 } catch ( err ) {
216- res . status ( 500 ) . json ( { error : 'Orchestrator failed' , details : err . message } ) ;
223+ next ( err ) ;
217224 }
218225} ) ;
219226
@@ -227,14 +234,19 @@ app.get('/api/agents/discover/:capability', (req, res) => {
227234 res . json ( results ) ;
228235} ) ;
229236
230- app . get ( '/api/agents/:id' , ( req , res ) => {
237+ app . get ( '/api/agents/:id' , ( req , res , next ) => {
231238 const agent = getAgentById ( req . params . id ) ;
232- if ( ! agent ) return res . status ( 404 ) . json ( { error : 'Agent not found' } ) ;
239+ if ( ! agent ) {
240+ const err = new Error ( 'Agent not found' ) ;
241+ err . status = 404 ;
242+ err . code = 'NOT_FOUND' ;
243+ return next ( err ) ;
244+ }
233245 res . json ( agent ) ;
234246} ) ;
235247
236248// ─── Wallet Endpoints ────────────────────────────────────────
237- app . get ( '/api/wallet/balances' , async ( req , res ) => {
249+ app . get ( '/api/wallet/balances' , async ( req , res , next ) => {
238250 try {
239251 const wallets = { } ;
240252 if ( config . serverAddress ) {
@@ -248,18 +260,18 @@ app.get('/api/wallet/balances', async (req, res) => {
248260 }
249261 res . json ( wallets ) ;
250262 } catch ( err ) {
251- res . status ( 500 ) . json ( { error : 'Failed to fetch balances' , details : err . message } ) ;
263+ next ( err ) ;
252264 }
253265} ) ;
254266
255- app . get ( '/api/wallet/transactions' , async ( req , res ) => {
267+ app . get ( '/api/wallet/transactions' , async ( req , res , next ) => {
256268 try {
257269 const address = req . query . address || config . orchestratorAddress || config . serverAddress ;
258270 if ( ! address ) return res . json ( [ ] ) ;
259271 const txs = await getTransactions ( address , 20 ) ;
260272 res . json ( txs ) ;
261273 } catch ( err ) {
262- res . status ( 500 ) . json ( { error : 'Failed to fetch transactions' , details : err . message } ) ;
274+ next ( err ) ;
263275 }
264276} ) ;
265277
@@ -303,10 +315,13 @@ app.get('/api/config/apikey', (req, res) => {
303315 } ) ;
304316} ) ;
305317
306- app . post ( '/api/config/apikey' , ( req , res ) => {
318+ app . post ( '/api/config/apikey' , ( req , res , next ) => {
307319 const { apiKey } = req . body ;
308320 if ( ! apiKey || ! apiKey . startsWith ( 'sk-ant-' ) ) {
309- return res . status ( 400 ) . json ( { error : 'Invalid API key. Must start with sk-ant-' } ) ;
321+ const err = new Error ( 'Invalid API key. Must start with sk-ant-' ) ;
322+ err . status = 400 ;
323+ err . code = 'INVALID_API_KEY' ;
324+ return next ( err ) ;
310325 }
311326 setApiKey ( apiKey ) ;
312327 res . json ( { success : true , masked : `sk-ant-...${ apiKey . slice ( - 6 ) } ` } ) ;
@@ -317,6 +332,9 @@ app.get('/', (req, res) => {
317332 res . sendFile ( path . join ( __dirname , '..' , 'public' , 'index.html' ) ) ;
318333} ) ;
319334
335+ // ─── Centralized Error Handler ───────────────────────────────
336+ app . use ( errorHandler ) ;
337+
320338// ─── Start Server ────────────────────────────────────────────
321339const PORT = config . port ;
322340app . listen ( PORT , ( ) => {
0 commit comments