From a0f3cf9974f3a0fb39abe0bacc094418be7a605c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Freitas?= Date: Mon, 20 Jan 2025 13:47:54 +0000 Subject: [PATCH] update --- package.json | 2 +- src/commands/dev-deprecated.ts | 30 +++++++ src/commands/dev.ts | 152 ++++++++++++++++++++++++++++----- src/commands/playground.ts | 101 ---------------------- src/index.ts | 6 +- src/services/proxy.ts | 9 +- src/services/vite.ts | 2 +- src/utils/deployed-url.ts | 4 +- src/utils/openapi.ts | 10 ++- 9 files changed, 177 insertions(+), 139 deletions(-) create mode 100644 src/commands/dev-deprecated.ts delete mode 100644 src/commands/playground.ts diff --git a/package.json b/package.json index 39c49db..751876d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "homepage": "https://github.com/mintbase/make-agent/repo#readme", "scripts": { "typecheck": "tsc --noEmit", - "dev": "bun run ./src/index.ts playground --port 3000", + "dev": "bun run ./src/index.ts dev --port 3000", "build": "bun run typecheck && bun build ./src/index.ts --outdir ./dist --target node", "prepublishOnly": "bun run build", "build-binary": "bun run typecheck && bun build ./src/index.ts --compile --outfile make-agent", diff --git a/src/commands/dev-deprecated.ts b/src/commands/dev-deprecated.ts new file mode 100644 index 0000000..6fdbf50 --- /dev/null +++ b/src/commands/dev-deprecated.ts @@ -0,0 +1,30 @@ +import { Command } from "commander"; + +import { TunnelService } from "../services/tunnel"; +import { detectPort } from "../utils/port-detector"; + +export const devDeprecatedCommand = new Command() + .name("dev-deprecated") + .description("Make your AI agent discoverable and register the plugin") + .option("-p, --port ", "Local port to expose", parseInt) + .option("-s, --serveo", "Use Serveo instead of Localtunnel", false) + .option("-t, --testnet", "Use Testnet instead of Mainnet", false) + .action(async (options) => { + let port = options.port; + if (!port) { + port = await detectPort(); + if (!port) { + console.error( + "Unable to detect the port automatically. Please specify a port using the -p or --port option.", + ); + process.exit(1); + } + console.log(`Detected port: ${port}`); + } + const tunnelService = new TunnelService({ + port, + useServeo: options.serveo, + useTestnet: options.testnet, + }); + await tunnelService.start(); + }); diff --git a/src/commands/dev.ts b/src/commands/dev.ts index 5794da8..0ee3532 100644 --- a/src/commands/dev.ts +++ b/src/commands/dev.ts @@ -1,30 +1,142 @@ import { Command } from "commander"; - -import { TunnelService } from "../services/tunnel"; +import dotenv from 'dotenv'; +import isPortReachable from 'is-port-reachable'; +import path from "path"; +import { startApiServer } from "../services/proxy"; +import { createViteServer } from "../services/vite"; +import { getDeployedUrl } from "../utils/deployed-url"; +import { validateEnv } from "../utils/env"; +import { validateAndParseOpenApiSpec } from "../utils/openapi"; import { detectPort } from "../utils/port-detector"; +import { getHostname, getSpecUrl } from "../utils/url-utils"; + +dotenv.config(); +validateEnv(); + +interface ApiConfig { + key: string; + url: string; + serverPort: number; +} + +interface ValidationResult { + pluginId: string; + accountId: string; + spec: any; +} + +const DEFAULT_PORTS = { + SERVER: 3010, + UI: 5000 +} as const; + +async function findAvailablePort(startPort: number): Promise { + let port = startPort; + while (await isPortReachable(port, {host: 'localhost'})) { + port++; + } + return port; +} + +const API_CONFIG: ApiConfig = { + key: process.env.BITTE_API_KEY!, + url: process.env.BITTE_API_URL!, + serverPort: DEFAULT_PORTS.SERVER +}; + +async function fetchAndValidateSpec(url: string): Promise { + const pluginId = getHostname(url); + const specUrl = getSpecUrl(url); + + const validation = await validateAndParseOpenApiSpec(specUrl); + const { isValid, accountId } = validation; + + const specContent = await fetch(specUrl).then(res => res.text()); + let spec = JSON.parse(specContent); + + if (!isValid) { + spec = { + ...spec, + servers: [{ url }], + "x-mb": { + ...spec["x-mb"], + "account-id": accountId || "anon", + } + }; + } + + return { + pluginId, + accountId: accountId || "anon", + spec + }; +} + +async function setupPorts(options: { port?: string }) { + let port = parseInt(options.port || '') || 0; + + if (port === 0) { + const detectedPort = await detectPort(); + if (detectedPort) { + port = detectedPort; + } else { + port = await findAvailablePort(3000); + } + } + + const uiPort = await findAvailablePort(DEFAULT_PORTS.UI); + const serverPort = await findAvailablePort(DEFAULT_PORTS.SERVER); + + return { port, uiPort, serverPort }; +} + +async function createViteConfiguration(uiPort: number, serverPort: number, localAgent: ValidationResult) { + return { + root: path.resolve(__dirname, "../playground"), + port: uiPort, + configFile: path.resolve(__dirname, "../playground/vite.config.ts"), + define: { + __APP_DATA__: JSON.stringify({ + serverStartTime: new Date().toISOString(), + environment: "make-agent", + localAgent, + apiUrl: `http://localhost:${serverPort}/api/v1/chat`, + bitteApiKey: API_CONFIG.key, + bitteApiUrl: `http://localhost:${serverPort}/api/v1/chat` + }) + } + }; +} export const devCommand = new Command() .name("dev") - .description("Make your AI agent discoverable and register the plugin") - .option("-p, --port ", "Local port to expose", parseInt) - .option("-s, --serveo", "Use Serveo instead of Localtunnel", false) + .description("Start a local playground for your AI agent") + .option("-p, --port ", "Port to run playground on", "3000") .option("-t, --testnet", "Use Testnet instead of Mainnet", false) .action(async (options) => { - let port = options.port; - if (!port) { - port = await detectPort(); - if (!port) { - console.error( - "Unable to detect the port automatically. Please specify a port using the -p or --port option.", - ); - process.exit(1); + try { + const { port, uiPort, serverPort } = await setupPorts(options); + + API_CONFIG.serverPort = serverPort; + const server = await startApiServer(API_CONFIG); + + const deployedUrl = getDeployedUrl(port); + if (!deployedUrl) { + throw new Error("Deployed URL could not be determined."); } - console.log(`Detected port: ${port}`); + + const localAgent = await fetchAndValidateSpec(deployedUrl); + const viteConfig = await createViteConfiguration(uiPort, serverPort, localAgent); + const viteServer = createViteServer(viteConfig); + await viteServer.start(); + + process.on("SIGINT", async () => { + await viteServer.close(); + server.close(); + process.exit(0); + }); + + } catch (error) { + process.exit(1); } - const tunnelService = new TunnelService({ - port, - useServeo: options.serveo, - useTestnet: options.testnet, - }); - await tunnelService.start(); }); diff --git a/src/commands/playground.ts b/src/commands/playground.ts deleted file mode 100644 index 2d1a461..0000000 --- a/src/commands/playground.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Command } from "commander"; -import dotenv from 'dotenv'; -import path from "path"; -import { startApiServer } from "../services/proxy"; -import { createViteServer } from "../services/vite"; -import { deployedUrl } from "../utils/deployed-url"; -import { validateEnv } from "../utils/env"; -import { validateAndParseOpenApiSpec } from "../utils/openapi"; -import { getHostname, getSpecUrl } from "../utils/url-utils"; - -// Environment setup -dotenv.config(); -validateEnv(); - -const API_CONFIG = { - key: process.env.BITTE_API_KEY!, - url: process.env.BITTE_API_URL!, - serverPort: 3010 -}; - -console.log('API Configuration:', { - url: API_CONFIG.url, - serverPort: API_CONFIG.serverPort -}); - -async function fetchAndValidateSpec(url: string) { - const pluginId = getHostname(url); - const specUrl = getSpecUrl(url); - - const { isValid, accountId } = await validateAndParseOpenApiSpec(specUrl); - - if (!isValid || !accountId) { - throw new Error("Invalid OpenAPI specification or missing account ID"); - } - - const specContent = await fetch(specUrl).then(res => res.text()); - - return { - pluginId, - accountId, - spec: JSON.parse(specContent) - }; -} - -export const playgroundCommand = new Command() - .name("playground") - .description("Start a local playground for your AI agent") - .option("-p, --port ", "Port to run playground on", "3000") - .action(async (options) => { - try { - console.log('Starting playground with configuration:'); - console.log('API_CONFIG:', { - url: API_CONFIG.url, - serverPort: API_CONFIG.serverPort, - // Omit key for security - }); - - // Start API server - const server = await startApiServer(API_CONFIG); - console.log('API server started successfully'); - - // Validate deployed URL - if (!deployedUrl) { - throw new Error("Deployed URL could not be determined."); - } - - // Fetch and validate OpenAPI spec - const localAgent = await fetchAndValidateSpec(deployedUrl); - - // Configure and start Vite server - const viteConfig = { - root: path.resolve(__dirname, "../playground"), - port: 10000, - configFile: path.resolve(__dirname, "../playground/vite.config.ts"), - define: { - __APP_DATA__: JSON.stringify({ - serverStartTime: new Date().toISOString(), - environment: "make-agent", - localAgent, - apiUrl: `http://localhost:${API_CONFIG.serverPort}/api/v1/chat`, - bitteApiKey: API_CONFIG.key, - bitteApiUrl: `http://localhost:${API_CONFIG.serverPort}/api/v1/chat` - }) - } - }; - - const viteServer = createViteServer(viteConfig); - await viteServer.start(); - - // Handle graceful shutdown - process.on("SIGINT", async () => { - await viteServer.close(); - server.close(); - process.exit(); - }); - - } catch (error) { - console.error("Failed to start playground:", error); - process.exit(1); - } - }); diff --git a/src/index.ts b/src/index.ts index 99b1ca2..472e778 100755 --- a/src/index.ts +++ b/src/index.ts @@ -6,11 +6,11 @@ import packageJson from "../package.json"; import { contractCommand } from "./commands/contract"; import { deleteCommand } from "./commands/delete"; import { deployCommand } from "./commands/deploy"; -import { devCommand } from "./commands/dev"; +import { devDeprecatedCommand } from "./commands/dev-deprecated"; import { registerCommand } from "./commands/register"; import { updateCommand } from "./commands/update"; import { verifyCommand } from "./commands/verify"; -import { playgroundCommand } from "./commands/playground"; +import { devCommand } from "./commands/dev"; dotenv.config(); @@ -27,6 +27,6 @@ program .addCommand(updateCommand) .addCommand(deleteCommand) .addCommand(verifyCommand) - .addCommand(playgroundCommand); + .addCommand(devDeprecatedCommand); program.parse(); diff --git a/src/services/proxy.ts b/src/services/proxy.ts index 7462a5e..3c1ab53 100644 --- a/src/services/proxy.ts +++ b/src/services/proxy.ts @@ -16,15 +16,12 @@ export async function startApiServer(config: ApiConfig) { // Add request logging middleware app.use((req, res, next) => { - console.log(`[Express] Incoming request: ${req.method} ${req.url}`); next(); }); // Handle chat requests app.post('/api/v1/chat', async (req, res) => { - try { - console.log('[Server] Forwarding chat request to Bitte API'); - + try { const response = await fetch(config.url, { method: 'POST', headers: { @@ -36,7 +33,6 @@ export async function startApiServer(config: ApiConfig) { }); const data = await response.text(); - console.log('[Server] Response received:', data); res.status(response.status); response.headers.forEach((value, key) => { @@ -48,7 +44,6 @@ export async function startApiServer(config: ApiConfig) { res.send(data); } catch (error) { - console.error('[Server] Error handling chat request:', error); res.status(500).json({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' @@ -59,11 +54,9 @@ export async function startApiServer(config: ApiConfig) { return new Promise>((resolve, reject) => { try { const server = app.listen(config.serverPort, () => { - console.log(`API server running on port ${config.serverPort}`); resolve(server); }); } catch (error) { - console.error('Failed to start API server:', error); reject(error); } }); diff --git a/src/services/vite.ts b/src/services/vite.ts index 6b0e6ec..a3780e8 100644 --- a/src/services/vite.ts +++ b/src/services/vite.ts @@ -39,7 +39,7 @@ export class ViteServer { }); await this.server.listen(); - console.log(`Vite dev server running at http://localhost:${this.config.port}`); + console.log(`Playground running at http://localhost:${this.config.port}`); } catch (error) { console.error('Failed to start Vite server:', error); diff --git a/src/utils/deployed-url.ts b/src/utils/deployed-url.ts index a99389a..ef54b0e 100644 --- a/src/utils/deployed-url.ts +++ b/src/utils/deployed-url.ts @@ -16,7 +16,7 @@ export const VERCEL_DEPLOYMENT_URL = (() => { } })(); -const getDeployedUrl = (): string => { +export const getDeployedUrl = (port?: number): string => { // Vercel if (VERCEL_ENV) { return VERCEL_DEPLOYMENT_URL; @@ -63,7 +63,7 @@ const getDeployedUrl = (): string => { } // Fallback to localhost if no deployment URL is found - return "http://localhost:3000"; // TODO: make port dynamic + return `http://localhost:${port || 3000}`; }; export const deployedUrl = getDeployedUrl(); diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 1d9eae4..503abac 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -18,10 +18,14 @@ export async function validateAndParseOpenApiSpec( const accountId = apiResponse["x-mb"]?.["account-id"]; return { isValid: true, accountId: accountId }; - } catch (error) { + } catch (error: any) { console.error( - "Error in OpenAPI specification fetch, validation, or parsing:", - error, + "OpenAPI Validation Errors:", + error?.details?.map((detail: any) => ({ + path: detail.instancePath, + error: detail.message, + params: detail.params + })) || error?.message || 'Unknown error' ); return { isValid: false }; }