@promptbook/cli 0.103.0-49 → 0.103.0-51

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 (35) hide show
  1. package/apps/agents-server/package.json +1 -0
  2. package/apps/agents-server/src/app/AddAgentButton.tsx +7 -6
  3. package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +6 -1
  4. package/apps/agents-server/src/app/{metadata → admin/metadata}/MetadataClient.tsx +5 -13
  5. package/apps/agents-server/src/app/{metadata → admin/metadata}/page.tsx +2 -2
  6. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +21 -1
  7. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +23 -3
  8. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +10 -2
  9. package/apps/agents-server/src/app/api/agents/route.ts +34 -0
  10. package/apps/agents-server/src/app/api/embed.js/route.ts +93 -0
  11. package/apps/agents-server/src/app/embed/page.tsx +24 -0
  12. package/apps/agents-server/src/app/page.tsx +59 -79
  13. package/apps/agents-server/src/components/Header/Header.tsx +28 -8
  14. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +28 -0
  15. package/apps/agents-server/src/components/Homepage/Card.tsx +18 -0
  16. package/apps/agents-server/src/components/Homepage/ModelCard.tsx +29 -0
  17. package/apps/agents-server/src/components/Homepage/Section.tsx +17 -0
  18. package/apps/agents-server/src/components/Homepage/TechInfoCard.tsx +20 -0
  19. package/apps/agents-server/src/components/UsersList/UsersList.tsx +6 -6
  20. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +3 -8
  21. package/apps/agents-server/src/database/metadataDefaults.ts +5 -0
  22. package/apps/agents-server/src/database/migrate.ts +131 -0
  23. package/apps/agents-server/src/database/{schema.sql → migrations/2025-11-0001-initial-schema.sql} +1 -17
  24. package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +16 -0
  25. package/apps/agents-server/src/middleware.ts +47 -9
  26. package/apps/agents-server/src/utils/getFederatedAgents.ts +66 -0
  27. package/esm/index.es.js +1 -1
  28. package/esm/typings/src/_packages/components.index.d.ts +2 -0
  29. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +23 -0
  30. package/esm/typings/src/llm-providers/agent/Agent.d.ts +8 -0
  31. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +7 -0
  32. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +5 -0
  33. package/esm/typings/src/version.d.ts +1 -1
  34. package/package.json +1 -1
  35. package/umd/index.umd.js +1 -1
@@ -0,0 +1,28 @@
1
+ import Link from 'next/link';
2
+ import React from 'react';
3
+ import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
4
+ import { AvatarProfile } from '../../../../../src/book-components/AvatarProfile/AvatarProfile/AvatarProfile';
5
+ import { Card } from './Card';
6
+
7
+ type AgentCardProps = {
8
+ agent: AgentBasicInformation;
9
+ href: string;
10
+ };
11
+
12
+ export function AgentCard({ agent, href }: AgentCardProps) {
13
+ return (
14
+ <Link href={href}>
15
+ <Card
16
+ style={
17
+ !agent.meta.color
18
+ ? {}
19
+ : {
20
+ backgroundColor: `${agent.meta.color}22`,
21
+ }
22
+ }
23
+ >
24
+ <AvatarProfile agent={agent} />
25
+ </Card>
26
+ </Link>
27
+ );
28
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ type CardProps = {
4
+ children: React.ReactNode;
5
+ className?: string;
6
+ style?: React.CSSProperties;
7
+ };
8
+
9
+ export function Card({ children, className = '', style }: CardProps) {
10
+ return (
11
+ <div
12
+ className={`block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400 ${className}`}
13
+ style={style}
14
+ >
15
+ {children}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,29 @@
1
+ import Link from 'next/link';
2
+ import React from 'react';
3
+ import { Card } from './Card';
4
+
5
+ type ModelCardProps = {
6
+ modelName: string;
7
+ modelTitle: string;
8
+ modelDescription?: string;
9
+ };
10
+
11
+ export function ModelCard({ modelName, modelTitle, modelDescription }: ModelCardProps) {
12
+ return (
13
+ <Link href={`#[🐱‍🚀]`}>
14
+ <Card className="h-full flex flex-col">
15
+ <div className="flex justify-between items-start mb-2">
16
+ <h2 className="text-xl font-bold text-gray-900">{modelTitle}</h2>
17
+ <span className="inline-block bg-gray-100 text-gray-600 text-xs px-2 py-1 rounded font-mono">
18
+ {modelName}
19
+ </span>
20
+ </div>
21
+ {modelDescription && (
22
+ <p className="text-gray-600 text-sm mt-2 flex-grow leading-relaxed">
23
+ {modelDescription}
24
+ </p>
25
+ )}
26
+ </Card>
27
+ </Link>
28
+ );
29
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+
3
+ type SectionProps = {
4
+ title: string;
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ export function Section({ title, children }: SectionProps) {
9
+ return (
10
+ <section className="mt-16 first:mt-4 mb-4">
11
+ <h2 className="text-3xl text-gray-900 mb-6 font-light">{title}</h2>
12
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
13
+ {children}
14
+ </div>
15
+ </section>
16
+ );
17
+ }
@@ -0,0 +1,20 @@
1
+ import Link from 'next/link';
2
+ import React from 'react';
3
+ import { Card } from './Card';
4
+
5
+ type TechInfoCardProps = {
6
+ title: string;
7
+ children: React.ReactNode;
8
+ href?: string;
9
+ };
10
+
11
+ export function TechInfoCard({ title, children, href = '#' }: TechInfoCardProps) {
12
+ return (
13
+ <Link href={href}>
14
+ <Card>
15
+ <h2 className="text-2xl font-semibold text-gray-900 mb-2">{title}</h2>
16
+ {children}
17
+ </Card>
18
+ </Link>
19
+ );
20
+ }
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
+ import { Card } from '../Homepage/Card';
5
+ import { Section } from '../Homepage/Section';
4
6
 
5
7
  type User = {
6
8
  id: number;
@@ -111,13 +113,11 @@ export function UsersList() {
111
113
 
112
114
  return (
113
115
  <div className="space-y-6">
114
- <h2 className="text-3xl text-gray-900 mt-16 mb-4">Users ({users.length})</h2>
115
-
116
116
  {error && <div className="bg-red-100 text-red-700 p-3 rounded">{error}</div>}
117
117
 
118
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
118
+ <Section title={`Users (${users.length})`}>
119
119
  {users.map((user) => (
120
- <div key={user.id} className="block p-6 bg-white rounded-lg shadow-md border border-gray-200">
120
+ <Card key={user.id}>
121
121
  <div className="flex justify-between items-start">
122
122
  <div>
123
123
  <h3 className="text-xl font-semibold text-gray-900">{user.username}</h3>
@@ -139,7 +139,7 @@ export function UsersList() {
139
139
  </button>
140
140
  </div>
141
141
  </div>
142
- </div>
142
+ </Card>
143
143
  ))}
144
144
 
145
145
  {/* Create User Form */}
@@ -184,7 +184,7 @@ export function UsersList() {
184
184
  </button>
185
185
  </form>
186
186
  </div>
187
- </div>
187
+ </Section>
188
188
  </div>
189
189
  );
190
190
  }
@@ -16,19 +16,14 @@ import {
16
16
  NEXT_PUBLIC_VERCEL_TARGET_ENV,
17
17
  NEXT_PUBLIC_VERCEL_URL,
18
18
  } from '@/config';
19
- import Link from 'next/link';
19
+ import { TechInfoCard } from '../Homepage/TechInfoCard';
20
20
 
21
21
  /**
22
22
  * [♐️] Expose Vercel environment variables to indentify the deployment
23
23
  */
24
24
  export default function VercelDeploymentCard() {
25
25
  return (
26
- <Link
27
- href="#"
28
- className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
29
- >
30
- <h2 className="text-2xl font-semibold text-gray-900 mb-4">Vercel Deployment</h2>
31
-
26
+ <TechInfoCard title="Vercel Deployment">
32
27
  <p className="text-gray-600">NEXT_PUBLIC_VERCEL_ENV: {NEXT_PUBLIC_VERCEL_ENV}</p>
33
28
  <p className="text-gray-600">NEXT_PUBLIC_VERCEL_TARGET_ENV: {NEXT_PUBLIC_VERCEL_TARGET_ENV}</p>
34
29
  <p className="text-gray-600">NEXT_PUBLIC_VERCEL_URL: {NEXT_PUBLIC_VERCEL_URL}</p>
@@ -55,6 +50,6 @@ export default function VercelDeploymentCard() {
55
50
  <p className="text-gray-600">
56
51
  NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID: {NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID}
57
52
  </p>
58
- </Link>
53
+ </TechInfoCard>
59
54
  );
60
55
  }
@@ -29,4 +29,9 @@ export const metadataDefaults = [
29
29
  value: '',
30
30
  note: 'Comma separated list of allowed IPs or CIDR ranges. If set, only clients from these IPs are allowed to access the server.',
31
31
  },
32
+ {
33
+ key: 'FEDERATED_SERVERS',
34
+ value: '',
35
+ note: 'Comma separated list of federated servers URLs. The server will look to all federated servers and list their agents.',
36
+ },
32
37
  ] as const;
@@ -0,0 +1,131 @@
1
+ import * as dotenv from 'dotenv';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { Client } from 'pg';
5
+
6
+ dotenv.config();
7
+
8
+ async function migrate() {
9
+ console.info('🚀 Starting database migration');
10
+
11
+ // 1. Get configuration
12
+ const prefixesEnv = process.env.SUPABASE_MIGRATION_PREFIXES;
13
+ if (!prefixesEnv) {
14
+ console.warn('⚠️ SUPABASE_MIGRATION_PREFIXES is not defined. Skipping migration.');
15
+ return;
16
+ }
17
+ const prefixes = prefixesEnv
18
+ .split(',')
19
+ .map((p) => p.trim())
20
+ .filter((p) => p !== '');
21
+
22
+ if (prefixes.length === 0) {
23
+ console.warn('⚠️ No prefixes found in SUPABASE_MIGRATION_PREFIXES. Skipping migration.');
24
+ return;
25
+ }
26
+
27
+ const connectionString = process.env.POSTGRES_URL || process.env.DATABASE_URL;
28
+ if (!connectionString) {
29
+ console.error('❌ POSTGRES_URL or DATABASE_URL is not defined.');
30
+ process.exit(1);
31
+ }
32
+
33
+ console.info(`📋 Found ${prefixes.length} prefixes to migrate: ${prefixes.join(', ')}`);
34
+
35
+ // 2. Connect to database
36
+ const client = new Client({
37
+ connectionString,
38
+ ssl: { rejectUnauthorized: false }, // Required for some Supabase/Heroku connections
39
+ });
40
+
41
+ try {
42
+ await client.connect();
43
+ console.info('🔌 Connected to database');
44
+
45
+ // 3. Read migration files
46
+ const migrationsDir = path.join(__dirname, 'migrations');
47
+ if (!fs.existsSync(migrationsDir)) {
48
+ console.error(`❌ Migrations directory not found at ${migrationsDir}`);
49
+ process.exit(1);
50
+ }
51
+
52
+ const migrationFiles = fs
53
+ .readdirSync(migrationsDir)
54
+ .filter((file) => file.endsWith('.sql'))
55
+ .sort(); // Ensure files are processed in order
56
+
57
+ console.info(`📂 Found ${migrationFiles.length} migration files`);
58
+
59
+ // 4. Iterate over prefixes and apply migrations
60
+ for (const prefix of prefixes) {
61
+ console.info(`\n🏗️ Migrating prefix: "${prefix}"`);
62
+ const migrationsTableName = `${prefix}Migrations`;
63
+
64
+ // 4.1 Create migrations table if not exists
65
+ const createMigrationsTableSql = `
66
+ CREATE TABLE IF NOT EXISTS "${migrationsTableName}" (
67
+ "filename" TEXT PRIMARY KEY,
68
+ "appliedAt" TIMESTAMP WITH TIME ZONE DEFAULT now()
69
+ );
70
+ `;
71
+ await client.query(createMigrationsTableSql);
72
+
73
+ // Enable RLS for migrations table
74
+ const enableRlsSql = `ALTER TABLE "${migrationsTableName}" ENABLE ROW LEVEL SECURITY;`;
75
+ await client.query(enableRlsSql);
76
+
77
+ // 4.2 Get applied migrations
78
+ const { rows: appliedMigrationsRows } = await client.query(
79
+ `SELECT "filename" FROM "${migrationsTableName}"`,
80
+ );
81
+ const appliedMigrations = new Set(appliedMigrationsRows.map((row) => row.filename));
82
+
83
+ // 4.3 Apply new migrations in one big transaction
84
+ let migrationError = null;
85
+ await client.query('BEGIN');
86
+ try {
87
+ for (const file of migrationFiles) {
88
+ if (appliedMigrations.has(file)) {
89
+ // console.info(` ⏭️ Skipping ${file} (already applied)`);
90
+ continue;
91
+ }
92
+
93
+ console.info(` 🚀 Applying ${file}...`);
94
+ const filePath = path.join(migrationsDir, file);
95
+ let sql = fs.readFileSync(filePath, 'utf-8');
96
+
97
+ // Replace prefix placeholder
98
+ sql = sql.replace(/prefix_/g, prefix);
99
+
100
+ try {
101
+ await client.query(sql);
102
+ await client.query(`INSERT INTO "${migrationsTableName}" ("filename") VALUES ($1)`, [file]);
103
+ console.info(` ✅ Applied ${file}`);
104
+ } catch (error) {
105
+ console.error(` ❌ Failed to apply ${file}:`, error);
106
+ migrationError = error;
107
+ break;
108
+ }
109
+ }
110
+ if (migrationError) {
111
+ await client.query('ROLLBACK');
112
+ throw migrationError;
113
+ } else {
114
+ await client.query('COMMIT');
115
+ }
116
+ } catch (error) {
117
+ await client.query('ROLLBACK');
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ console.info('\n🎉 All migrations completed successfully');
123
+ } catch (error) {
124
+ console.error('\n❌ Migration failed:', error);
125
+ process.exit(1);
126
+ } finally {
127
+ await client.end();
128
+ }
129
+ }
130
+
131
+ migrate();
@@ -1,22 +1,8 @@
1
-
2
1
  -- Note: This is primary source of truth for the database schema
3
2
  -- In future we want to be compatible with more then Supabase so we keep SQL as main schema definition
4
3
  -- To update, search for [💽]
5
4
 
6
5
 
7
- CREATE TABLE IF NOT EXISTS "prefix_Metadata" (
8
- "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
9
- "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
10
- "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
11
- "key" TEXT NOT NULL,
12
- "value" TEXT NOT NULL,
13
- "note" TEXT NULL,
14
- CONSTRAINT prefix_Metadata_key_key UNIQUE ("key")
15
- );
16
-
17
- ALTER TABLE "prefix_Metadata" ENABLE ROW LEVEL SECURITY;
18
-
19
-
20
6
  CREATE TABLE IF NOT EXISTS "prefix_Agent" (
21
7
  "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
22
8
  "agentName" TEXT NOT NULL,
@@ -36,7 +22,7 @@ CREATE TABLE IF NOT EXISTS "prefix_Agent" (
36
22
  "preparedExternals" JSONB NULL
37
23
  );
38
24
  -- Ensure uniqueness of agentName even on repeated schema application without duplicate constraint creation
39
- CREATE UNIQUE INDEX IF NOT EXISTS prefix_agent_agentname_key ON "prefix_Agent" ("agentName");
25
+ CREATE UNIQUE INDEX IF NOT EXISTS "prefix_agent_agentname_key" ON "prefix_Agent" ("agentName");
40
26
  COMMENT ON COLUMN "prefix_Agent"."agentName" IS 'The unique name of the agent derived from the first line of the `agentSource`, automatically updated on `agentSource` change';
41
27
  COMMENT ON COLUMN "prefix_Agent"."agentHash" IS 'The hash of the `agentSource`, automatically updated on `agentSource` change';
42
28
  COMMENT ON COLUMN "prefix_Agent"."agentSource" IS 'The source code of the agent';
@@ -175,5 +161,3 @@ ALTER TABLE "prefix_ChatFeedback"
175
161
  FOREIGN KEY ("agentName")
176
162
  REFERENCES "prefix_Agent"("agentName")
177
163
  ON DELETE CASCADE;
178
-
179
- -- TODO: [🐱‍🚀] Create propper database migrations
@@ -0,0 +1,16 @@
1
+ -- Note: This is primary source of truth for the database schema
2
+ -- In future we want to be compatible with more then Supabase so we keep SQL as main schema definition
3
+ -- To update, search for [💽]
4
+
5
+
6
+ CREATE TABLE IF NOT EXISTS "prefix_Metadata" (
7
+ "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
8
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
9
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
10
+ "key" TEXT NOT NULL,
11
+ "value" TEXT NOT NULL,
12
+ "note" TEXT NULL,
13
+ CONSTRAINT "prefix_Metadata_key_key" UNIQUE ("key")
14
+ );
15
+
16
+ ALTER TABLE "prefix_Metadata" ENABLE ROW LEVEL SECURITY;
@@ -72,10 +72,48 @@ export async function middleware(req: NextRequest) {
72
72
  }
73
73
  }
74
74
 
75
- const allowedIps = allowedIpsMetadata !== null && allowedIpsMetadata !== undefined ? allowedIpsMetadata : allowedIpsEnv;
75
+ const allowedIps =
76
+ allowedIpsMetadata !== null && allowedIpsMetadata !== undefined ? allowedIpsMetadata : allowedIpsEnv;
76
77
 
77
78
  if (isIpAllowed(ip, allowedIps)) {
78
- // 3. Custom Domain Routing
79
+ // Handle OPTIONS (preflight) requests before any redirects
80
+ // to avoid CORS issues ("Redirect is not allowed for a preflight request")
81
+ if (req.method === 'OPTIONS') {
82
+ return new NextResponse(null, {
83
+ status: 200,
84
+ headers: {
85
+ 'Access-Control-Allow-Origin': '*',
86
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
87
+ 'Access-Control-Allow-Headers': 'Content-Type',
88
+ },
89
+ });
90
+ }
91
+
92
+ // 3. Redirect /:agentName/* to /agents/:agentName/*
93
+ // This enables accessing agents from the root path
94
+ const pathParts = req.nextUrl.pathname.split('/');
95
+ const potentialAgentName = pathParts[1];
96
+
97
+ if (
98
+ potentialAgentName &&
99
+ !['agents', 'api', 'admin', 'embed', '_next', 'favicon.ico'].includes(potentialAgentName) &&
100
+ !potentialAgentName.startsWith('.') &&
101
+ // Note: Other static files are excluded by the matcher configuration below
102
+ true
103
+ ) {
104
+ const url = req.nextUrl.clone();
105
+ url.pathname = `/agents${req.nextUrl.pathname}`;
106
+ const response = NextResponse.redirect(url);
107
+
108
+ // Enable CORS for the redirect
109
+ response.headers.set('Access-Control-Allow-Origin', '*');
110
+ response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
111
+ response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
112
+
113
+ return response;
114
+ }
115
+
116
+ // 4. Custom Domain Routing
79
117
  // If the host is not one of the configured SERVERS, try to find an agent with a matching META LINK
80
118
 
81
119
  if (host && SERVERS && !SERVERS.some((server) => server === host)) {
@@ -94,7 +132,7 @@ export async function middleware(req: NextRequest) {
94
132
  // We check all configured servers because the custom domain could point to any of them
95
133
  // (or if they share the database, we need to check the relevant tables)
96
134
  const serversToCheck = SERVERS;
97
-
135
+
98
136
  // TODO: [🧠] If there are many servers, this loop might be slow. Optimize if needed.
99
137
  for (const serverHost of serversToCheck) {
100
138
  let serverName = serverHost;
@@ -105,11 +143,11 @@ export async function middleware(req: NextRequest) {
105
143
  // Search for agent with matching META LINK
106
144
  // agentProfile->links is an array of strings
107
145
  // We check if it contains the host, or https://host, or http://host
108
-
146
+
109
147
  const searchLinks = [host, `https://${host}`, `http://${host}`];
110
-
148
+
111
149
  // Construct OR filter: agentProfile.cs.{"links":["link1"]},agentProfile.cs.{"links":["link2"]},...
112
- const orFilter = searchLinks.map(link => `agentProfile.cs.{"links":["${link}"]}`).join(',');
150
+ const orFilter = searchLinks.map((link) => `agentProfile.cs.{"links":["${link}"]}`).join(',');
113
151
 
114
152
  try {
115
153
  const { data } = await supabase
@@ -123,7 +161,7 @@ export async function middleware(req: NextRequest) {
123
161
  // Found the agent!
124
162
  const url = req.nextUrl.clone();
125
163
  url.pathname = `/${data.agentName}`;
126
-
164
+
127
165
  // Pass the server context to the app via header
128
166
  const requestHeaders = new Headers(req.headers);
129
167
  requestHeaders.set('x-promptbook-server', serverHost);
@@ -135,8 +173,8 @@ export async function middleware(req: NextRequest) {
135
173
  });
136
174
  }
137
175
  } catch (error) {
138
- // Ignore error (e.g. table not found, or agent not found) and continue to next server
139
- // console.error(`Error checking server ${serverHost} for custom domain ${host}:`, error);
176
+ // Ignore error (e.g. table not found, or agent not found) and continue to next server
177
+ // console.error(`Error checking server ${serverHost} for custom domain ${host}:`, error);
140
178
  }
141
179
  }
142
180
  }
@@ -0,0 +1,66 @@
1
+ import { AgentCollection } from '@promptbook-local/types';
2
+
3
+ type Agent = Awaited<ReturnType<AgentCollection['listAgents']>>[number];
4
+
5
+ type AgentWithUrl = Agent & {
6
+ url: string;
7
+ };
8
+
9
+ type AgentsApiResponse = {
10
+ agents: AgentWithUrl[];
11
+ federatedServers: string[];
12
+ };
13
+
14
+ /**
15
+ * Fetches agents from federated servers recursively
16
+ */
17
+ export async function getFederatedAgents(initialServers: string[]): Promise<AgentWithUrl[]> {
18
+ const visited = new Set<string>();
19
+ const queue = [...initialServers];
20
+ const externalAgentsMap = new Map<string, AgentWithUrl>();
21
+ const MAX_SERVERS = 20;
22
+
23
+ while (queue.length > 0 && visited.size < MAX_SERVERS) {
24
+ const serverUrl = queue.shift();
25
+ if (!serverUrl) continue;
26
+
27
+ const normalizedUrl = serverUrl.trim().replace(/\/$/, '');
28
+ if (visited.has(normalizedUrl)) continue;
29
+ visited.add(normalizedUrl);
30
+
31
+ try {
32
+ // TODO: [🧠] Should we use some shorter timeout?
33
+ const response = await fetch(`${normalizedUrl}/api/agents`, {
34
+ next: { revalidate: 600 } // Cache for 10 minutes
35
+ });
36
+
37
+ if (!response.ok) {
38
+ console.warn(`Failed to fetch agents from ${normalizedUrl}: ${response.status} ${response.statusText}`);
39
+ continue;
40
+ }
41
+
42
+ const data: AgentsApiResponse = await response.json();
43
+
44
+ if (data.agents && Array.isArray(data.agents)) {
45
+ for (const agent of data.agents) {
46
+ if (agent.url && !externalAgentsMap.has(agent.url)) {
47
+ externalAgentsMap.set(agent.url, agent);
48
+ }
49
+ }
50
+ }
51
+
52
+ if (data.federatedServers && Array.isArray(data.federatedServers)) {
53
+ for (const server of data.federatedServers) {
54
+ const normalizedServer = server.trim().replace(/\/$/, '');
55
+ if (!visited.has(normalizedServer) && normalizedServer !== '') {
56
+ queue.push(normalizedServer);
57
+ }
58
+ }
59
+ }
60
+ } catch (error) {
61
+ console.error(`Error fetching agents from ${normalizedUrl}:`, error);
62
+ }
63
+ }
64
+
65
+ return Array.from(externalAgentsMap.values());
66
+ }
package/esm/index.es.js CHANGED
@@ -47,7 +47,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
47
47
  * @generated
48
48
  * @see https://github.com/webgptorg/promptbook
49
49
  */
50
- const PROMPTBOOK_ENGINE_VERSION = '0.103.0-49';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-51';
51
51
  /**
52
52
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
53
53
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -51,6 +51,7 @@ import { ResetIcon } from '../book-components/icons/ResetIcon';
51
51
  import { SaveIcon } from '../book-components/icons/SaveIcon';
52
52
  import { SendIcon } from '../book-components/icons/SendIcon';
53
53
  import { TemplateIcon } from '../book-components/icons/TemplateIcon';
54
+ import { PromptbookAgent } from '../book-components/PromptbookAgent/PromptbookAgent';
54
55
  import { BrandedQrCode } from '../book-components/Qr/BrandedQrCode';
55
56
  import { GenericQrCode } from '../book-components/Qr/GenericQrCode';
56
57
  import { PromptbookQrCode } from '../book-components/Qr/PromptbookQrCode';
@@ -109,6 +110,7 @@ export { ResetIcon };
109
110
  export { SaveIcon };
110
111
  export { SendIcon };
111
112
  export { TemplateIcon };
113
+ export { PromptbookAgent };
112
114
  export { BrandedQrCode };
113
115
  export { GenericQrCode };
114
116
  export { PromptbookQrCode };
@@ -0,0 +1,23 @@
1
+ type PromptbookAgentProps = {
2
+ /**
3
+ * URL of the agent to connect to
4
+ *
5
+ * @example "http://s6.ptbk.io/benjamin-white"
6
+ */
7
+ agentUrl: string;
8
+ /**
9
+ * Callback when the window is opened or closed
10
+ */
11
+ onOpenChange?: (isOpen: boolean) => void;
12
+ };
13
+ /**
14
+ * Renders a floating agent button that opens a chat window with the remote agent.
15
+ *
16
+ * @public exported from `@promptbook/components`
17
+ */
18
+ export declare function PromptbookAgent(props: PromptbookAgentProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
20
+ /**
21
+ * TODO: !!! Load the full branding
22
+ * TODO: !!! <promptbook-agent> element
23
+ */
@@ -2,6 +2,8 @@ import { BehaviorSubject } from 'rxjs';
2
2
  import type { AgentBasicInformation, BookParameter } from '../../book-2.0/agent-source/AgentBasicInformation';
3
3
  import type { string_book } from '../../book-2.0/agent-source/string_book';
4
4
  import type { LlmExecutionTools } from '../../execution/LlmExecutionTools';
5
+ import type { ChatPromptResult } from '../../execution/PromptResult';
6
+ import type { Prompt } from '../../types/Prompt';
5
7
  import type { string_agent_hash, string_agent_name, string_agent_url, string_url_image } from '../../types/typeAliases';
6
8
  import { AgentLlmExecutionTools } from './AgentLlmExecutionTools';
7
9
  import type { AgentOptions } from './AgentOptions';
@@ -55,6 +57,12 @@ export declare class Agent extends AgentLlmExecutionTools implements LlmExecutio
55
57
  get parameters(): BookParameter[];
56
58
  readonly agentSource: BehaviorSubject<string_book>;
57
59
  constructor(options: AgentOptions);
60
+ /**
61
+ * Calls the chat model with agent-specific system prompt and requirements with streaming
62
+ *
63
+ * Note: This method also implements the learning mechanism
64
+ */
65
+ callChatModelStream(prompt: Prompt, onProgress: (chunk: ChatPromptResult) => void): Promise<ChatPromptResult>;
58
66
  }
59
67
  /**
60
68
  * TODO: [🧠][😰]Agent is not working with the parameters, should it be?
@@ -1,4 +1,5 @@
1
1
  import type { Promisable } from 'type-fest';
2
+ import type { string_book } from '../../book-2.0/agent-source/string_book';
2
3
  import type { ChatParticipant } from '../../book-components/Chat/types/ChatParticipant';
3
4
  import type { AvailableModel } from '../../execution/AvailableModel';
4
5
  import type { LlmExecutionTools } from '../../execution/LlmExecutionTools';
@@ -40,6 +41,12 @@ export declare class AgentLlmExecutionTools implements LlmExecutionTools {
40
41
  * @param agentSource The agent source string that defines the agent's behavior
41
42
  */
42
43
  constructor(options: CreateAgentLlmExecutionToolsOptions);
44
+ /**
45
+ * Updates the agent source and clears the cache
46
+ *
47
+ * @param agentSource The new agent source string
48
+ */
49
+ protected updateAgentSource(agentSource: string_book): void;
43
50
  /**
44
51
  * Get cached or parse agent information
45
52
  */
@@ -1,5 +1,6 @@
1
1
  import type { ChatPromptResult } from '../../execution/PromptResult';
2
2
  import type { Prompt } from '../../types/Prompt';
3
+ import type { string_agent_hash, string_agent_name } from '../../types/typeAliases';
3
4
  import { Agent } from './Agent';
4
5
  import type { RemoteAgentOptions } from './RemoteAgentOptions';
5
6
  /**
@@ -20,7 +21,11 @@ export declare class RemoteAgent extends Agent {
20
21
  * The source of the agent
21
22
  */
22
23
  private agentUrl;
24
+ private _remoteAgentName;
25
+ private _remoteAgentHash;
23
26
  private constructor();
27
+ get agentName(): string_agent_name;
28
+ get agentHash(): string_agent_hash;
24
29
  /**
25
30
  * Calls the agent on agents remote server
26
31
  */