@agentxjs/portagent 0.0.9 → 0.1.1

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.
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Portagent - AgentX Portal</title>
8
- <script type="module" crossorigin src="/assets/index-Diy1g-AB.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-CwJV4aRD.css">
8
+ <script type="module" crossorigin src="/assets/index-DtLPjMmM.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DYPjKVjB.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -1,23 +1,29 @@
1
1
  // src/server/index.ts
2
2
  import { dirname, resolve } from "path";
3
3
  import { fileURLToPath } from "url";
4
- import { serve } from "@hono/node-server";
4
+ import { createServer } from "http";
5
5
  import { Hono as Hono2 } from "hono";
6
6
  import { cors } from "hono/cors";
7
7
  import { serveStatic } from "@hono/node-server/serve-static";
8
8
  import { existsSync, readFileSync } from "fs";
9
- import { createAgentX, defineAgent } from "agentxjs";
10
- import { createAgentXHandler } from "agentxjs/server";
11
- import { toHonoHandler } from "agentxjs/server/adapters/hono";
12
- import { nodeRuntime } from "@agentxjs/node-runtime";
9
+ import { createAgentX } from "agentxjs";
13
10
  import { homedir } from "os";
14
11
  import { join } from "path";
12
+ import { mkdirSync } from "fs";
15
13
 
16
14
  // src/server/auth.ts
17
15
  import { Hono } from "hono";
18
16
  import { createMiddleware } from "hono/factory";
19
17
  import * as jose from "jose";
20
18
  var TOKEN_EXPIRY = "7d";
19
+ function isValidInviteCode(code) {
20
+ const timestamp = parseInt(code, 10);
21
+ if (isNaN(timestamp)) return false;
22
+ const now = /* @__PURE__ */ new Date();
23
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1);
24
+ const expectedTimestamp = Math.floor(todayStart.getTime() / 1e3);
25
+ return timestamp === expectedTimestamp;
26
+ }
21
27
  async function createToken(secret, userId) {
22
28
  const secretKey = new TextEncoder().encode(secret);
23
29
  const token = await new jose.SignJWT({ sub: userId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(TOKEN_EXPIRY).sign(secretKey);
@@ -43,13 +49,20 @@ function toUserInfo(user) {
43
49
  createdAt: user.createdAt
44
50
  };
45
51
  }
46
- function authRoutes(userRepository, jwtSecret, agentx) {
52
+ function authRoutes(userRepository, jwtSecret, agentx, config = {}) {
47
53
  const app = new Hono();
54
+ const { inviteCodeRequired = true } = config;
55
+ app.get("/config", (c) => {
56
+ return c.json({ inviteCodeRequired });
57
+ });
48
58
  app.post("/register", async (c) => {
49
59
  try {
50
60
  const body = await c.req.json();
51
- if (!body.username || !body.email || !body.password) {
52
- return c.json({ error: "Username, email, and password are required" }, 400);
61
+ if (!body.username || !body.password) {
62
+ return c.json({ error: "Username and password are required" }, 400);
63
+ }
64
+ if (inviteCodeRequired && (!body.inviteCode || !isValidInviteCode(body.inviteCode))) {
65
+ return c.json({ error: "Invalid invite code" }, 400);
53
66
  }
54
67
  if (body.username.length < 3) {
55
68
  return c.json({ error: "Username must be at least 3 characters" }, 400);
@@ -57,15 +70,19 @@ function authRoutes(userRepository, jwtSecret, agentx) {
57
70
  if (body.password.length < 6) {
58
71
  return c.json({ error: "Password must be at least 6 characters" }, 400);
59
72
  }
60
- if (!body.email.includes("@")) {
73
+ if (body.email && !body.email.includes("@")) {
61
74
  return c.json({ error: "Invalid email format" }, 400);
62
75
  }
63
- const container = await agentx.containers.create();
76
+ const containerId = `user-${crypto.randomUUID()}`;
77
+ const containerRes = await agentx.request("container_create_request", { containerId });
78
+ if (containerRes.data.error) {
79
+ return c.json({ error: "Failed to create user container" }, 500);
80
+ }
64
81
  const user = await userRepository.createUser({
65
82
  username: body.username,
66
83
  email: body.email,
67
84
  password: body.password,
68
- containerId: container.containerId,
85
+ containerId,
69
86
  displayName: body.displayName,
70
87
  avatar: body.avatar
71
88
  });
@@ -191,16 +208,17 @@ var SQLiteUserRepository = class {
191
208
  if (await this.usernameExists(input.username)) {
192
209
  throw new Error(`Username '${input.username}' already exists`);
193
210
  }
194
- if (await this.emailExists(input.email)) {
211
+ if (input.email && await this.emailExists(input.email)) {
195
212
  throw new Error(`Email '${input.email}' already exists`);
196
213
  }
197
214
  const passwordHash = await hash(input.password, SALT_ROUNDS);
198
215
  const userId = randomUUID();
199
216
  const now = Date.now();
217
+ const email = input.email || `${userId}@noemail.portagent`;
200
218
  const user = {
201
219
  userId,
202
220
  username: input.username,
203
- email: input.email,
221
+ email,
204
222
  passwordHash,
205
223
  containerId: input.containerId,
206
224
  displayName: input.displayName,
@@ -395,143 +413,127 @@ var SQLiteUserRepository = class {
395
413
  }
396
414
  };
397
415
 
398
- // src/server/ownership.ts
399
- function getUserId(request) {
400
- const authHeader = request.headers.get("Authorization");
401
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
402
- const url = new URL(request.url);
403
- const queryToken = url.searchParams.get("token");
404
- if (!queryToken) {
405
- return null;
416
+ // src/server/logger.ts
417
+ import pino from "pino";
418
+ import fs from "fs";
419
+ import path from "path";
420
+ var LEVEL_MAP = {
421
+ debug: "debug",
422
+ info: "info",
423
+ warn: "warn",
424
+ error: "error",
425
+ silent: "silent"
426
+ };
427
+ var PinoLoggerAdapter = class {
428
+ constructor(name, level, pino2) {
429
+ this.name = name;
430
+ this.level = level;
431
+ this.pino = pino2;
432
+ }
433
+ debug(message, context) {
434
+ this.pino.debug(context || {}, message);
435
+ }
436
+ info(message, context) {
437
+ this.pino.info(context || {}, message);
438
+ }
439
+ warn(message, context) {
440
+ this.pino.warn(context || {}, message);
441
+ }
442
+ error(message, context) {
443
+ if (message instanceof Error) {
444
+ this.pino.error({ ...context, err: message }, message.message);
445
+ } else {
446
+ this.pino.error(context || {}, message);
406
447
  }
407
448
  }
408
- return request.headers.get("X-User-Id");
409
- }
410
- function createOwnershipHandler(agentx, baseHandler) {
411
- return async (request) => {
412
- const url = new URL(request.url);
413
- const method = request.method;
414
- const path = url.pathname;
415
- const userId = getUserId(request);
416
- if (!userId) {
417
- return baseHandler(request);
449
+ isDebugEnabled() {
450
+ return this.pino.isLevelEnabled("debug");
451
+ }
452
+ isInfoEnabled() {
453
+ return this.pino.isLevelEnabled("info");
454
+ }
455
+ isWarnEnabled() {
456
+ return this.pino.isLevelEnabled("warn");
457
+ }
458
+ isErrorEnabled() {
459
+ return this.pino.isLevelEnabled("error");
460
+ }
461
+ };
462
+ var PinoLoggerFactory = class {
463
+ rootLogger;
464
+ constructor(options) {
465
+ const { level, logDir, pretty = process.env.NODE_ENV !== "production" } = options;
466
+ if (!fs.existsSync(logDir)) {
467
+ fs.mkdirSync(logDir, { recursive: true });
418
468
  }
419
- if (method === "POST" && path.endsWith("/containers")) {
420
- try {
421
- const body = await request.json();
422
- const config = body.config || {};
423
- config.ownerId = userId;
424
- const modifiedBody = { ...body, config };
425
- const modifiedRequest = new Request(request.url, {
426
- method: request.method,
427
- headers: request.headers,
428
- body: JSON.stringify(modifiedBody)
429
- });
430
- return baseHandler(modifiedRequest);
431
- } catch {
432
- return baseHandler(request);
469
+ const getLogFilePath = () => {
470
+ const now = /* @__PURE__ */ new Date();
471
+ const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
472
+ return path.join(logDir, `portagent-${dateStr}.log`);
473
+ };
474
+ const targets = [];
475
+ targets.push({
476
+ target: "pino/file",
477
+ level: LEVEL_MAP[level],
478
+ options: {
479
+ destination: getLogFilePath(),
480
+ mkdir: true
433
481
  }
434
- }
435
- const putContainerMatch = path.match(/\/containers\/([^/]+)$/);
436
- if (method === "PUT" && putContainerMatch) {
437
- const containerId = putContainerMatch[1];
438
- const existingContainer = await agentx.containers.get(containerId);
439
- if (existingContainer) {
440
- const ownerId = existingContainer.config?.ownerId;
441
- if (ownerId && ownerId !== userId) {
442
- return new Response(JSON.stringify({ error: "Access denied" }), {
443
- status: 403,
444
- headers: { "Content-Type": "application/json" }
445
- });
446
- }
447
- return baseHandler(request);
448
- } else {
449
- try {
450
- const body = await request.json();
451
- const config = body.config || {};
452
- config.ownerId = userId;
453
- const modifiedBody = { ...body, config };
454
- const modifiedRequest = new Request(request.url, {
455
- method: request.method,
456
- headers: request.headers,
457
- body: JSON.stringify(modifiedBody)
458
- });
459
- return baseHandler(modifiedRequest);
460
- } catch {
461
- return baseHandler(request);
482
+ });
483
+ if (pretty) {
484
+ targets.push({
485
+ target: "pino-pretty",
486
+ level: LEVEL_MAP[level],
487
+ options: {
488
+ colorize: true,
489
+ translateTime: "SYS:standard",
490
+ ignore: "pid,hostname"
462
491
  }
463
- }
464
- }
465
- const getContainerMatch = path.match(/\/containers\/([^/]+)$/);
466
- if (method === "GET" && getContainerMatch) {
467
- const containerId = getContainerMatch[1];
468
- const container = await agentx.containers.get(containerId);
469
- if (!container) {
470
- return new Response(JSON.stringify({ error: "Container not found" }), {
471
- status: 404,
472
- headers: { "Content-Type": "application/json" }
473
- });
474
- }
475
- const ownerId = container.config?.ownerId;
476
- if (ownerId && ownerId !== userId) {
477
- return new Response(JSON.stringify({ error: "Access denied" }), {
478
- status: 403,
479
- headers: { "Content-Type": "application/json" }
480
- });
481
- }
482
- return baseHandler(request);
483
- }
484
- const deleteContainerMatch = path.match(/\/containers\/([^/]+)$/);
485
- if (method === "DELETE" && deleteContainerMatch) {
486
- const containerId = deleteContainerMatch[1];
487
- const container = await agentx.containers.get(containerId);
488
- if (!container) {
489
- return new Response(JSON.stringify({ error: "Container not found" }), {
490
- status: 404,
491
- headers: { "Content-Type": "application/json" }
492
- });
493
- }
494
- const ownerId = container.config?.ownerId;
495
- if (ownerId && ownerId !== userId) {
496
- return new Response(JSON.stringify({ error: "Access denied" }), {
497
- status: 403,
498
- headers: { "Content-Type": "application/json" }
499
- });
500
- }
501
- return baseHandler(request);
492
+ });
493
+ } else {
494
+ targets.push({
495
+ target: "pino/file",
496
+ level: LEVEL_MAP[level],
497
+ options: { destination: 1 }
498
+ // stdout
499
+ });
502
500
  }
503
- if (method === "GET" && path.endsWith("/containers")) {
504
- const response = await baseHandler(request);
505
- if (response.ok) {
506
- const body = await response.json();
507
- if (Array.isArray(body)) {
508
- const filtered = body.filter((container) => {
509
- const ownerId = container.config?.ownerId;
510
- return !ownerId || ownerId === userId;
511
- });
512
- return new Response(JSON.stringify(filtered), {
513
- status: 200,
514
- headers: { "Content-Type": "application/json" }
515
- });
516
- }
501
+ this.rootLogger = pino({
502
+ level: LEVEL_MAP[level],
503
+ transport: {
504
+ targets
517
505
  }
518
- return response;
519
- }
520
- return baseHandler(request);
521
- };
522
- }
506
+ });
507
+ }
508
+ getLogger(name) {
509
+ const childLogger = this.rootLogger.child({ name });
510
+ return new PinoLoggerAdapter(name, this.rootLogger.level, childLogger);
511
+ }
512
+ };
523
513
 
524
514
  // src/server/index.ts
525
515
  var __filename = fileURLToPath(import.meta.url);
526
516
  var __dirname = dirname(__filename);
517
+ function getDataPaths() {
518
+ const dataDir = process.env.PORTAGENT_DATA_DIR || join(homedir(), ".agentx");
519
+ const dataDirPath = join(dataDir, "data");
520
+ const logsDirPath = join(dataDir, "logs");
521
+ mkdirSync(dataDirPath, { recursive: true });
522
+ mkdirSync(logsDirPath, { recursive: true });
523
+ return {
524
+ dataDir,
525
+ dataDirPath,
526
+ logsDirPath,
527
+ userDbPath: join(dataDirPath, "portagent.db"),
528
+ agentxDbPath: join(dataDirPath, "agentx.db"),
529
+ logFilePath: join(logsDirPath, "portagent.log")
530
+ };
531
+ }
527
532
  var PORT = parseInt(process.env.PORT || "5200", 10);
528
533
  var JWT_SECRET = process.env.JWT_SECRET || crypto.randomUUID();
529
- var USER_DB_PATH = process.env.USER_DB_PATH || join(homedir(), ".agentx/data/portagent.db");
530
- var DefaultAgent = defineAgent({
531
- name: "Assistant",
532
- systemPrompt: "You are a helpful AI assistant."
533
- });
534
- function createApp() {
534
+ var INVITE_CODE_REQUIRED = process.env.INVITE_CODE_REQUIRED !== "false";
535
+ async function createApp() {
536
+ const paths = getDataPaths();
535
537
  const app = new Hono2();
536
538
  app.use(
537
539
  "*",
@@ -541,23 +543,53 @@ function createApp() {
541
543
  allowHeaders: ["Content-Type", "Authorization"]
542
544
  })
543
545
  );
544
- const runtime = nodeRuntime();
545
- const agentx = createAgentX(runtime);
546
- agentx.definitions.register(DefaultAgent);
547
- const agentxHandler = createAgentXHandler(agentx, {
548
- basePath: "/agentx",
549
- allowDynamicCreation: true,
550
- allowedDefinitions: ["Assistant"],
551
- repository: runtime.repository
546
+ const apiKey = process.env.LLM_PROVIDER_KEY;
547
+ if (!apiKey) {
548
+ console.error("Error: LLM_PROVIDER_KEY is required");
549
+ console.log("\nSet it via environment variable:");
550
+ console.log(" export LLM_PROVIDER_KEY=sk-ant-xxx");
551
+ process.exit(1);
552
+ }
553
+ const { getRequestListener } = await import("@hono/node-server");
554
+ const listener = getRequestListener(app.fetch);
555
+ const server = createServer(listener);
556
+ const logLevel = process.env.LOG_LEVEL || "info";
557
+ const loggerFactory = new PinoLoggerFactory({
558
+ level: logLevel,
559
+ logDir: paths.logsDirPath,
560
+ pretty: process.env.NODE_ENV !== "production"
552
561
  });
553
- agentxHandler.registerDefinition("Assistant", DefaultAgent);
554
- const userRepository = new SQLiteUserRepository(USER_DB_PATH);
562
+ const agentx = await createAgentX({
563
+ llm: {
564
+ apiKey,
565
+ baseUrl: process.env.LLM_PROVIDER_URL,
566
+ model: process.env.LLM_PROVIDER_MODEL
567
+ },
568
+ storage: {
569
+ driver: "sqlite",
570
+ path: paths.agentxDbPath
571
+ },
572
+ logger: {
573
+ level: logLevel,
574
+ factory: loggerFactory
575
+ },
576
+ server
577
+ // Attach to existing HTTP server
578
+ });
579
+ const userRepository = new SQLiteUserRepository(paths.userDbPath);
555
580
  const authMiddleware = createAuthMiddleware(JWT_SECRET);
556
581
  app.get("/health", (c) => c.json({ status: "ok", timestamp: Date.now() }));
557
- app.route("/api/auth", authRoutes(userRepository, JWT_SECRET, agentx));
558
- const ownershipHandler = createOwnershipHandler(agentx, agentxHandler);
582
+ app.route(
583
+ "/api/auth",
584
+ authRoutes(userRepository, JWT_SECRET, agentx, { inviteCodeRequired: INVITE_CODE_REQUIRED })
585
+ );
559
586
  app.use("/agentx/*", authMiddleware);
560
- app.all("/agentx/*", toHonoHandler(ownershipHandler));
587
+ app.get("/agentx/info", (c) => {
588
+ return c.json({
589
+ version: "0.1.0",
590
+ wsPath: "/ws"
591
+ });
592
+ });
561
593
  const publicDir = resolve(__dirname, "../public");
562
594
  const isDev = process.env.NODE_ENV !== "production";
563
595
  if (existsSync(publicDir)) {
@@ -578,16 +610,10 @@ function createApp() {
578
610
  );
579
611
  });
580
612
  }
581
- return { app, agentx, userRepository };
613
+ return { app, server, agentx, userRepository, paths };
582
614
  }
583
615
  async function startServer() {
584
- if (!process.env.LLM_PROVIDER_KEY) {
585
- console.error("Error: LLM_PROVIDER_KEY is required");
586
- console.log("\nSet it via environment variable:");
587
- console.log(" export LLM_PROVIDER_KEY=sk-ant-xxx");
588
- process.exit(1);
589
- }
590
- const { app, agentx, userRepository } = createApp();
616
+ const { server, agentx, userRepository, paths } = await createApp();
591
617
  console.log(`
592
618
  ____ _ _
593
619
  | _ \\ ___ _ __| |_ __ _ __ _ ___ _ __ | |_
@@ -600,35 +626,30 @@ async function startServer() {
600
626
  `);
601
627
  console.log("Configuration:");
602
628
  console.log(` Port: ${PORT}`);
629
+ console.log(` Data Dir: ${paths.dataDir}`);
603
630
  console.log(` API Key: ${process.env.LLM_PROVIDER_KEY.substring(0, 15)}...`);
604
- console.log(` User DB: ${USER_DB_PATH}`);
605
- console.log(` AgentX DB: ${join(homedir(), ".agentx/data/agentx.db")}`);
631
+ console.log(` User DB: ${paths.userDbPath}`);
632
+ console.log(` AgentX DB: ${paths.agentxDbPath}`);
633
+ console.log(` Logs: ${paths.logsDirPath}`);
634
+ console.log(` Invite Code: ${INVITE_CODE_REQUIRED ? "required" : "disabled"}`);
606
635
  console.log(`
607
636
  Endpoints:`);
608
637
  console.log(` GET /health - Health check`);
609
638
  console.log(` POST /api/auth/register - Register new user`);
610
639
  console.log(` POST /api/auth/login - Login`);
611
640
  console.log(` GET /api/auth/verify - Verify token`);
612
- console.log(` --- AgentX API (protected) ---`);
613
641
  console.log(` GET /agentx/info - Platform info`);
614
- console.log(` GET /agentx/containers - List containers`);
615
- console.log(` POST /agentx/containers - Create container`);
616
- console.log(` GET /agentx/containers/:id - Get container`);
617
- console.log(` DELETE /agentx/containers/:id - Delete container`);
618
- console.log(` POST /agentx/agents - Create agent`);
619
- console.log(` GET /agentx/agents/:id/sse - SSE stream`);
620
- console.log(` POST /agentx/agents/:id/messages- Send message`);
621
- serve({
622
- fetch: app.fetch,
623
- port: PORT,
624
- hostname: "0.0.0.0"
625
- });
626
- console.log(`
642
+ console.log(` WS /ws - WebSocket connection`);
643
+ server.listen(PORT, "0.0.0.0", () => {
644
+ console.log(`
627
645
  \u{1F680} Server running at http://localhost:${PORT}`);
646
+ console.log(`\u{1F50C} WebSocket available at ws://localhost:${PORT}/ws`);
647
+ });
628
648
  const shutdown = async () => {
629
649
  console.log("\nShutting down...");
630
- await agentx.agents.destroyAll();
650
+ await agentx.dispose();
631
651
  userRepository.close();
652
+ server.close();
632
653
  console.log("Server stopped");
633
654
  process.exit(0);
634
655
  };