@promptbook/cli 0.103.0-54 → 0.103.0-55

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 (36) hide show
  1. package/apps/agents-server/config.ts +0 -2
  2. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +79 -6
  3. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +171 -69
  4. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +203 -0
  5. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +3 -1
  6. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -1
  7. package/apps/agents-server/src/app/agents/[agentName]/api/openai/chat/completions/route.ts +3 -169
  8. package/apps/agents-server/src/app/agents/[agentName]/api/openrouter/chat/completions/route.ts +10 -0
  9. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +218 -0
  10. package/apps/agents-server/src/app/api/auth/change-password/route.ts +75 -0
  11. package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +55 -0
  12. package/apps/agents-server/src/app/api/chat-history/export/route.ts +55 -0
  13. package/apps/agents-server/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx +41 -0
  14. package/apps/agents-server/src/components/ChangePasswordForm/ChangePasswordForm.tsx +159 -0
  15. package/apps/agents-server/src/components/Header/Header.tsx +94 -38
  16. package/apps/agents-server/src/middleware.ts +1 -1
  17. package/apps/agents-server/src/utils/convertToCsv.ts +31 -0
  18. package/apps/agents-server/src/utils/handleChatCompletion.ts +183 -0
  19. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +93 -0
  20. package/esm/index.es.js +770 -181
  21. package/esm/index.es.js.map +1 -1
  22. package/esm/typings/src/_packages/core.index.d.ts +8 -6
  23. package/esm/typings/src/_packages/types.index.d.ts +1 -1
  24. package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +4 -0
  25. package/esm/typings/src/commitments/CLOSED/CLOSED.d.ts +35 -0
  26. package/esm/typings/src/commitments/COMPONENT/COMPONENT.d.ts +28 -0
  27. package/esm/typings/src/commitments/FROM/FROM.d.ts +34 -0
  28. package/esm/typings/src/commitments/IMPORTANT/IMPORTANT.d.ts +26 -0
  29. package/esm/typings/src/commitments/LANGUAGE/LANGUAGE.d.ts +35 -0
  30. package/esm/typings/src/commitments/OPEN/OPEN.d.ts +35 -0
  31. package/esm/typings/src/commitments/index.d.ts +1 -82
  32. package/esm/typings/src/commitments/registry.d.ts +68 -0
  33. package/esm/typings/src/version.d.ts +1 -1
  34. package/package.json +2 -2
  35. package/umd/index.umd.js +770 -181
  36. package/umd/index.umd.js.map +1 -1
@@ -6,8 +6,6 @@ const config = ConfigChecker.from({
6
6
  // Note: To expose env variables to the browser, using this seemingly strange syntax:
7
7
  // @see https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#exposing-environment-variables-to-the-browser
8
8
  NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
9
-
10
- // Note: [🌇] Defa
11
9
  NEXT_PUBLIC_VERCEL_ENV: process.env.NEXT_PUBLIC_VERCEL_ENV,
12
10
  NEXT_PUBLIC_VERCEL_TARGET_ENV: process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV,
13
11
  NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL,
@@ -1,5 +1,7 @@
1
1
  'use client';
2
2
 
3
+ import { Chat } from '@promptbook-local/components';
4
+ import type { ChatMessage } from '@promptbook-local/types';
3
5
  import { useEffect, useMemo, useState } from 'react';
4
6
  import { Card } from '../../../components/Homepage/Card';
5
7
  import {
@@ -62,6 +64,7 @@ export function ChatFeedbackClient({ initialAgentName }: ChatFeedbackClientProps
62
64
  const [search, setSearch] = useState('');
63
65
  const [sortBy, setSortBy] = useState<ChatFeedbackSortField>('createdAt');
64
66
  const [sortOrder, setSortOrder] = useState<ChatFeedbackSortOrder>('desc');
67
+ const [selectedThread, setSelectedThread] = useState<ChatMessage[] | null>(null);
65
68
  const [loading, setLoading] = useState(true);
66
69
  const [error, setError] = useState<string | null>(null);
67
70
 
@@ -192,6 +195,25 @@ export function ChatFeedbackClient({ initialAgentName }: ChatFeedbackClientProps
192
195
  }
193
196
  };
194
197
 
198
+ const handleViewChat = (row: ChatFeedbackRow) => {
199
+ if (!row.chatThread) {
200
+ alert('No chat thread available for this feedback.');
201
+ return;
202
+ }
203
+ try {
204
+ // Ensure dates are Date objects
205
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
+ const thread = (row.chatThread as unknown as any[]).map((msg) => ({
207
+ ...msg,
208
+ date: msg.date ? new Date(msg.date) : (msg.createdAt ? new Date(msg.createdAt) : undefined),
209
+ })) as ChatMessage[];
210
+ setSelectedThread(thread);
211
+ } catch (e) {
212
+ console.error('Failed to parse chat thread', e);
213
+ alert('Failed to parse chat thread.');
214
+ }
215
+ };
216
+
195
217
  const handleDeleteRow = async (row: ChatFeedbackRow) => {
196
218
  if (!row.id) return;
197
219
 
@@ -250,6 +272,14 @@ export function ChatFeedbackClient({ initialAgentName }: ChatFeedbackClientProps
250
272
 
251
273
  const isSortedBy = (field: ChatFeedbackSortField) => sortBy === field;
252
274
 
275
+ const getExportUrl = () => {
276
+ const params = new URLSearchParams();
277
+ if (agentName) {
278
+ params.set('agentName', agentName);
279
+ }
280
+ return `/api/chat-feedback/export?${params.toString()}`;
281
+ };
282
+
253
283
  return (
254
284
  <div className="container mx-auto px-4 py-8 space-y-6">
255
285
  <div className="mt-20 mb-4 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
@@ -259,12 +289,24 @@ export function ChatFeedbackClient({ initialAgentName }: ChatFeedbackClientProps
259
289
  Review and triage user feedback collected from your agents.
260
290
  </p>
261
291
  </div>
262
- <div className="text-sm text-gray-500 md:text-right">
263
- <div className="text-xl font-semibold text-gray-900">
264
- {total.toLocaleString()}
292
+ <div className="flex items-end gap-4 text-sm text-gray-500 md:text-right">
293
+ <div>
294
+ <a
295
+ href={getExportUrl()}
296
+ target="_blank"
297
+ rel="noopener noreferrer"
298
+ className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
299
+ >
300
+ Download CSV
301
+ </a>
265
302
  </div>
266
- <div className="text-xs uppercase tracking-wide text-gray-400">
267
- Total feedback entries
303
+ <div>
304
+ <div className="text-xl font-semibold text-gray-900">
305
+ {total.toLocaleString()}
306
+ </div>
307
+ <div className="text-xs uppercase tracking-wide text-gray-400">
308
+ Total feedback entries
309
+ </div>
268
310
  </div>
269
311
  </div>
270
312
  </div>
@@ -468,7 +510,16 @@ export function ChatFeedbackClient({ initialAgentName }: ChatFeedbackClientProps
468
510
  {row.platform || '-'}
469
511
  </div>
470
512
  </td>
471
- <td className="whitespace-nowrap px-4 py-3 text-right text-xs font-medium">
513
+ <td className="whitespace-nowrap px-4 py-3 text-right text-xs font-medium space-x-2">
514
+ {row.chatThread && (
515
+ <button
516
+ type="button"
517
+ onClick={() => handleViewChat(row)}
518
+ className="text-blue-600 hover:text-blue-800"
519
+ >
520
+ View Chat
521
+ </button>
522
+ )}
472
523
  <button
473
524
  type="button"
474
525
  onClick={() => handleDeleteRow(row)}
@@ -536,6 +587,28 @@ export function ChatFeedbackClient({ initialAgentName }: ChatFeedbackClientProps
536
587
  </div>
537
588
  </div>
538
589
  </Card>
590
+
591
+ {selectedThread && (
592
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
593
+ <div className="bg-white rounded-lg shadow-xl w-full max-w-4xl h-[80vh] flex flex-col overflow-hidden">
594
+ <div className="flex justify-between items-center p-4 border-b border-gray-200">
595
+ <h3 className="text-lg font-medium text-gray-900">Chat Thread</h3>
596
+ <button
597
+ onClick={() => setSelectedThread(null)}
598
+ className="text-gray-400 hover:text-gray-500"
599
+ >
600
+ <span className="sr-only">Close</span>
601
+ <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
602
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
603
+ </svg>
604
+ </button>
605
+ </div>
606
+ <div className="flex-1 overflow-hidden relative">
607
+ <Chat messages={selectedThread} />
608
+ </div>
609
+ </div>
610
+ </div>
611
+ )}
539
612
  </div>
540
613
  );
541
614
  }
@@ -1,5 +1,7 @@
1
1
  'use client';
2
2
 
3
+ import { MockedChat } from '@promptbook-local/components';
4
+ import type { ChatMessage } from '@promptbook-local/types';
3
5
  import { useEffect, useMemo, useState } from 'react';
4
6
  import { Card } from '../../../components/Homepage/Card';
5
7
  import {
@@ -83,6 +85,7 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
83
85
  const [search, setSearch] = useState('');
84
86
  const [sortBy, setSortBy] = useState<ChatHistorySortField>('createdAt');
85
87
  const [sortOrder, setSortOrder] = useState<ChatHistorySortOrder>('desc');
88
+ const [viewMode, setViewMode] = useState<'table' | 'chat'>('table');
86
89
  const [loading, setLoading] = useState(true);
87
90
  const [error, setError] = useState<string | null>(null);
88
91
 
@@ -213,6 +216,21 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
213
216
  }
214
217
  };
215
218
 
219
+ const handleViewModeChange = (mode: 'table' | 'chat') => {
220
+ setViewMode(mode);
221
+ if (mode === 'chat') {
222
+ // When switching to chat view, sort by creation date ascending to make sense of conversation flow
223
+ setSortBy('createdAt');
224
+ setSortOrder('asc');
225
+ setPageSize(100); // Show more messages in chat view
226
+ } else {
227
+ // Default back to desc when switching to table
228
+ setSortBy('createdAt');
229
+ setSortOrder('desc');
230
+ setPageSize(20);
231
+ }
232
+ };
233
+
216
234
  const handleDeleteRow = async (row: ChatHistoryRow) => {
217
235
  if (!row.id) return;
218
236
 
@@ -271,6 +289,75 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
271
289
 
272
290
  const isSortedBy = (field: ChatHistorySortField) => sortBy === field;
273
291
 
292
+ const getExportUrl = () => {
293
+ const params = new URLSearchParams();
294
+ if (agentName) {
295
+ params.set('agentName', agentName);
296
+ }
297
+ return `/api/chat-history/export?${params.toString()}`;
298
+ };
299
+
300
+ const chatMessages = useMemo(() => {
301
+ if (viewMode !== 'chat') return [];
302
+ return items.map((row) => {
303
+ const message = row.message as { role?: string; content?: string };
304
+ const role = (message.role || 'USER').toUpperCase();
305
+ return {
306
+ id: String(row.id),
307
+ from: role === 'USER' ? 'USER' : 'ASSISTANT',
308
+ content: message.content || JSON.stringify(message),
309
+ isComplete: true,
310
+ date: new Date(row.createdAt),
311
+ } satisfies ChatMessage;
312
+ });
313
+ }, [items, viewMode]);
314
+
315
+ const pagination = (
316
+ <div className="mt-4 flex flex-col items-center justify-between gap-3 border-t border-gray-100 pt-4 text-xs text-gray-600 md:flex-row">
317
+ <div>
318
+ {total > 0 ? (
319
+ <>
320
+ Showing{' '}
321
+ <span className="font-semibold">
322
+ {Math.min((page - 1) * pageSize + 1, total)}
323
+ </span>{' '}
324
+ –{' '}
325
+ <span className="font-semibold">
326
+ {Math.min(page * pageSize, total)}
327
+ </span>{' '}
328
+ of{' '}
329
+ <span className="font-semibold">{total}</span>{' '}
330
+ messages
331
+ </>
332
+ ) : (
333
+ 'No messages'
334
+ )}
335
+ </div>
336
+ <div className="flex items-center gap-2">
337
+ <button
338
+ type="button"
339
+ onClick={() => setPage((prev) => Math.max(1, prev - 1))}
340
+ disabled={page <= 1}
341
+ className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
342
+ >
343
+ Previous
344
+ </button>
345
+ <span>
346
+ Page <span className="font-semibold">{page}</span> of{' '}
347
+ <span className="font-semibold">{totalPages}</span>
348
+ </span>
349
+ <button
350
+ type="button"
351
+ onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
352
+ disabled={page >= totalPages}
353
+ className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
354
+ >
355
+ Next
356
+ </button>
357
+ </div>
358
+ </div>
359
+ );
360
+
274
361
  return (
275
362
  <div className="container mx-auto px-4 py-8 space-y-6">
276
363
  <div className="mt-20 mb-4 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
@@ -280,12 +367,48 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
280
367
  Inspect and manage all recorded chat messages across your agents.
281
368
  </p>
282
369
  </div>
283
- <div className="text-sm text-gray-500 md:text-right">
284
- <div className="text-xl font-semibold text-gray-900">
285
- {total.toLocaleString()}
370
+ <div className="flex items-end gap-4 text-sm text-gray-500 md:text-right">
371
+ <div className="flex rounded-md shadow-sm" role="group">
372
+ <button
373
+ type="button"
374
+ onClick={() => handleViewModeChange('table')}
375
+ className={`px-4 py-2 text-sm font-medium border border-gray-300 rounded-l-lg ${
376
+ viewMode === 'table'
377
+ ? 'bg-blue-600 text-white border-blue-600'
378
+ : 'bg-white text-gray-700 hover:bg-gray-50'
379
+ }`}
380
+ >
381
+ Table
382
+ </button>
383
+ <button
384
+ type="button"
385
+ onClick={() => handleViewModeChange('chat')}
386
+ className={`px-4 py-2 text-sm font-medium border border-gray-300 rounded-r-lg border-l-0 ${
387
+ viewMode === 'chat'
388
+ ? 'bg-blue-600 text-white border-blue-600'
389
+ : 'bg-white text-gray-700 hover:bg-gray-50'
390
+ }`}
391
+ >
392
+ Chat
393
+ </button>
394
+ </div>
395
+ <div>
396
+ <a
397
+ href={getExportUrl()}
398
+ target="_blank"
399
+ rel="noopener noreferrer"
400
+ className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
401
+ >
402
+ Download CSV
403
+ </a>
286
404
  </div>
287
- <div className="text-xs uppercase tracking-wide text-gray-400">
288
- Total messages
405
+ <div>
406
+ <div className="text-xl font-semibold text-gray-900">
407
+ {total.toLocaleString()}
408
+ </div>
409
+ <div className="text-xs uppercase tracking-wide text-gray-400">
410
+ Total messages
411
+ </div>
289
412
  </div>
290
413
  </div>
291
414
  </div>
@@ -372,12 +495,27 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
372
495
  )}
373
496
  </Card>
374
497
 
375
- <Card>
376
- <div className="flex items-center justify-between mb-4">
377
- <h2 className="text-lg font-medium text-gray-900">
378
- Messages ({total})
379
- </h2>
498
+ {viewMode === 'chat' ? (
499
+ <div className="bg-white rounded-lg shadow border border-gray-200 overflow-hidden flex flex-col">
500
+ <div className="h-[800px] relative">
501
+ <MockedChat
502
+ messages={chatMessages}
503
+ isPausable={true}
504
+ isResettable={false}
505
+ isSaveButtonEnabled={true}
506
+ />
507
+ </div>
508
+ <div className="p-4 bg-gray-50 border-t border-gray-200">
509
+ {pagination}
510
+ </div>
380
511
  </div>
512
+ ) : (
513
+ <Card>
514
+ <div className="flex items-center justify-between mb-4">
515
+ <h2 className="text-lg font-medium text-gray-900">
516
+ Messages ({total})
517
+ </h2>
518
+ </div>
381
519
  {error && (
382
520
  <div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">
383
521
  {error}
@@ -417,13 +555,27 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
417
555
  )}
418
556
  </button>
419
557
  </th>
420
- <th className="px-4 py-3 text-left font-medium text-gray-500">Role</th>
421
- <th className="px-4 py-3 text-left font-medium text-gray-500">Message</th>
422
- <th className="px-4 py-3 text-left font-medium text-gray-500">URL</th>
423
- <th className="px-4 py-3 text-left font-medium text-gray-500">IP</th>
424
- <th className="px-4 py-3 text-left font-medium text-gray-500">Language</th>
425
- <th className="px-4 py-3 text-left font-medium text-gray-500">Platform</th>
426
- <th className="px-4 py-3 text-right font-medium text-gray-500">Actions</th>
558
+ <th className="px-4 py-3 text-left font-medium text-gray-500">
559
+ Role
560
+ </th>
561
+ <th className="px-4 py-3 text-left font-medium text-gray-500">
562
+ Message
563
+ </th>
564
+ <th className="px-4 py-3 text-left font-medium text-gray-500">
565
+ URL
566
+ </th>
567
+ <th className="px-4 py-3 text-left font-medium text-gray-500">
568
+ IP
569
+ </th>
570
+ <th className="px-4 py-3 text-left font-medium text-gray-500">
571
+ Language
572
+ </th>
573
+ <th className="px-4 py-3 text-left font-medium text-gray-500">
574
+ Platform
575
+ </th>
576
+ <th className="px-4 py-3 text-right font-medium text-gray-500">
577
+ Actions
578
+ </th>
427
579
  </tr>
428
580
  </thead>
429
581
  <tbody className="divide-y divide-gray-200 bg-white">
@@ -474,59 +626,9 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
474
626
  </table>
475
627
  </div>
476
628
  )}
477
-
478
- <div className="mt-4 flex flex-col items-center justify-between gap-3 border-t border-gray-100 pt-4 text-xs text-gray-600 md:flex-row">
479
- <div>
480
- {total > 0 ? (
481
- <>
482
- Showing{' '}
483
- <span className="font-semibold">
484
- {Math.min((page - 1) * pageSize + 1, total)}
485
- </span>{' '}
486
- –{' '}
487
- <span className="font-semibold">
488
- {Math.min(page * pageSize, total)}
489
- </span>{' '}
490
- of{' '}
491
- <span className="font-semibold">
492
- {total}
493
- </span>{' '}
494
- messages
495
- </>
496
- ) : (
497
- 'No messages'
498
- )}
499
- </div>
500
- <div className="flex items-center gap-2">
501
- <button
502
- type="button"
503
- onClick={() => setPage((prev) => Math.max(1, prev - 1))}
504
- disabled={page <= 1}
505
- className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
506
- >
507
- Previous
508
- </button>
509
- <span>
510
- Page{' '}
511
- <span className="font-semibold">
512
- {page}
513
- </span>{' '}
514
- of{' '}
515
- <span className="font-semibold">
516
- {totalPages}
517
- </span>
518
- </span>
519
- <button
520
- type="button"
521
- onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
522
- disabled={page >= totalPages}
523
- className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
524
- >
525
- Next
526
- </button>
527
- </div>
528
- </div>
629
+ {pagination}
529
630
  </Card>
631
+ )}
530
632
  </div>
531
633
  );
532
634
  }
@@ -0,0 +1,203 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
5
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
6
+ import { Agent } from '@promptbook-local/core';
7
+ import { ChatMessage, ChatPromptResult, Prompt, TODO_any } from '@promptbook-local/types';
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { z } from 'zod';
10
+
11
+ // Global map to store active transports
12
+ // Note: This works in stateful environments or single-instance deployments.
13
+ // In serverless with multiple instances, this will fail if POST lands on a different instance.
14
+ // However, for standard deployments or sticky sessions, it works.
15
+ const sessions = new Map<string, SSENextJsTransport>();
16
+
17
+ class SSENextJsTransport implements Transport {
18
+ public onmessage?: (message: JSONRPCMessage) => void;
19
+ public onclose?: () => void;
20
+ public onError?: (error: Error) => void;
21
+ private controller: ReadableStreamDefaultController<TODO_any>;
22
+ private encoder = new TextEncoder();
23
+ public sessionId: string;
24
+
25
+ constructor(controller: ReadableStreamDefaultController<TODO_any>, sessionId: string) {
26
+ this.controller = controller;
27
+ this.sessionId = sessionId;
28
+ }
29
+
30
+ async start(): Promise<void> {
31
+ // No-op for SSE
32
+ }
33
+
34
+ async close(): Promise<void> {
35
+ this.onclose?.();
36
+ sessions.delete(this.sessionId);
37
+ }
38
+
39
+ async send(message: JSONRPCMessage): Promise<void> {
40
+ const event = `event: message\ndata: ${JSON.stringify(message)}\n\n`;
41
+ this.controller.enqueue(this.encoder.encode(event));
42
+ }
43
+
44
+ async handlePostMessage(message: JSONRPCMessage): Promise<void> {
45
+ this.onmessage?.(message);
46
+ }
47
+
48
+ sendEndpointEvent(endpoint: string) {
49
+ const event = `event: endpoint\ndata: ${endpoint}\n\n`;
50
+ this.controller.enqueue(this.encoder.encode(event));
51
+ }
52
+ }
53
+
54
+ export async function GET(
55
+ request: NextRequest,
56
+ { params }: { params: Promise<{ agentName: string }> },
57
+ ) {
58
+ const { agentName } = await params;
59
+
60
+ // Check if agent exists
61
+ try {
62
+ const collection = await $provideAgentCollectionForServer();
63
+ await collection.getAgentSource(agentName);
64
+ } catch (error) {
65
+ return NextResponse.json({ error: 'Agent not found' }, { status: 404 });
66
+ }
67
+
68
+ const sessionId = crypto.randomUUID();
69
+
70
+ const stream = new ReadableStream({
71
+ start: async (controller) => {
72
+ const transport = new SSENextJsTransport(controller, sessionId);
73
+ sessions.set(sessionId, transport);
74
+
75
+ // Send endpoint event
76
+ // Construct the POST endpoint URL
77
+ // We assume the client can construct it or we send relative/absolute path
78
+ // The user wants: /agents/[agentName]/api/mcp
79
+ // We can send specific query param
80
+ const endpoint = `/agents/${agentName}/api/mcp?sessionId=${sessionId}`;
81
+ transport.sendEndpointEvent(endpoint);
82
+
83
+ // Initialize MCP Server
84
+ const server = new McpServer({
85
+ name: `Agent ${agentName}`,
86
+ version: '1.0.0',
87
+ });
88
+
89
+ // Register Chat Tool
90
+ server.tool(
91
+ 'chat',
92
+ {
93
+ messages: z.array(
94
+ z.object({
95
+ role: z.enum(['user', 'assistant', 'system']),
96
+ content: z.string(),
97
+ }),
98
+ ),
99
+ model: z.string().optional(),
100
+ },
101
+ async ({ messages, model }) => {
102
+ try {
103
+ const collection = await $provideAgentCollectionForServer();
104
+ const agentSource = await collection.getAgentSource(agentName);
105
+
106
+ const executionTools = await $provideExecutionToolsForServer();
107
+ const agent = new Agent({
108
+ agentSource,
109
+ executionTools,
110
+ isVerbose: true,
111
+ });
112
+
113
+ // Prepare thread and content
114
+ const lastMessage = messages[messages.length - 1];
115
+ const previousMessages = messages.slice(0, -1);
116
+
117
+ const thread: ChatMessage[] = previousMessages.map((msg: TODO_any, index: number) => ({
118
+ id: `msg-${index}`,
119
+ from: msg.role === 'assistant' ? 'agent' : 'user', // Mapping standard roles
120
+ content: msg.content,
121
+ isComplete: true,
122
+ date: new Date(),
123
+ }));
124
+
125
+ const prompt: Prompt = {
126
+ title: 'MCP Chat Completion',
127
+ content: lastMessage.content,
128
+ modelRequirements: {
129
+ modelVariant: 'CHAT',
130
+ },
131
+ parameters: {},
132
+ thread,
133
+ } as Prompt;
134
+
135
+ const result = await agent.callChatModel(prompt);
136
+
137
+ return {
138
+ content: [
139
+ {
140
+ type: 'text',
141
+ text: result.content,
142
+ },
143
+ ],
144
+ };
145
+ } catch (error) {
146
+ return {
147
+ content: [
148
+ {
149
+ type: 'text',
150
+ text: `Error: ${(error as Error).message}`,
151
+ },
152
+ ],
153
+ isError: true,
154
+ };
155
+ }
156
+ },
157
+ );
158
+
159
+ await server.connect(transport);
160
+
161
+ // Handle connection close
162
+ // In ReadableStream, verify if there is a way to detect close from client side in Next.js?
163
+ // Usually if client disconnects, the stream might be cancelled.
164
+ // But we don't have a direct hook here unless we return logic in 'cancel'.
165
+ },
166
+ cancel() {
167
+ sessions.delete(sessionId);
168
+ },
169
+ });
170
+
171
+ return new Response(stream, {
172
+ headers: {
173
+ 'Content-Type': 'text/event-stream',
174
+ 'Cache-Control': 'no-cache',
175
+ Connection: 'keep-alive',
176
+ },
177
+ });
178
+ }
179
+
180
+ export async function POST(
181
+ request: NextRequest,
182
+ { params }: { params: Promise<{ agentName: string }> },
183
+ ) {
184
+ const { searchParams } = new URL(request.url);
185
+ const sessionId = searchParams.get('sessionId');
186
+
187
+ if (!sessionId) {
188
+ return NextResponse.json({ error: 'Session ID required' }, { status: 400 });
189
+ }
190
+
191
+ const transport = sessions.get(sessionId);
192
+ if (!transport) {
193
+ return NextResponse.json({ error: 'Session not found' }, { status: 404 });
194
+ }
195
+
196
+ try {
197
+ const body = await request.json();
198
+ await transport.handlePostMessage(body);
199
+ return NextResponse.json({ success: true }); // Accepted
200
+ } catch (error) {
201
+ return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
202
+ }
203
+ }
@@ -1,4 +1,5 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { resolveInheritedAgentSource } from '@/src/utils/resolveInheritedAgentSource';
2
3
  import { createAgentModelRequirements } from '@promptbook-local/core';
3
4
  import { serializeError } from '@promptbook-local/utils';
4
5
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
@@ -12,7 +13,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
12
13
  try {
13
14
  const collection = await $provideAgentCollectionForServer();
14
15
  const agentSource = await collection.getAgentSource(agentName);
15
- const modelRequirements = await createAgentModelRequirements(agentSource);
16
+ const effectiveAgentSource = await resolveInheritedAgentSource(agentSource, collection);
17
+ const modelRequirements = await createAgentModelRequirements(effectiveAgentSource);
16
18
 
17
19
  return new Response(
18
20
  JSON.stringify(