@agentforge-io/core 3.4.0 → 4.0.2

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.
@@ -1,6 +1,14 @@
1
1
  import type { ConversationStatus, MessageRole, ToolCallRecord, TokenUsage } from '../types/agent.types';
2
2
  export interface Conversation {
3
3
  id: string;
4
+ /** Short, URL-friendly public handle (nanoid). Preferred over `id`
5
+ * for links, deep-link params (`?c=`/`?conv=`) and SDK resume — it's
6
+ * shorter and doesn't leak the internal UUID. Generated on create.
7
+ * Optional in the type so persistence adapters that predate the
8
+ * column (older @agentforge-io/typeorm builds) still satisfy the
9
+ * contract during the rollout — once every adapter ships the column
10
+ * this can be tightened to required. */
11
+ code?: string;
4
12
  userId: string;
5
13
  agentId: string;
6
14
  title?: string;
@@ -50,6 +58,9 @@ export type NewMessage = Omit<Message, 'id' | 'createdAt'>;
50
58
  */
51
59
  export interface ConversationListItem {
52
60
  id: string;
61
+ /** Short public handle — see `Conversation.code`. Optional during the
62
+ * rollout (older typeorm builds don't select it yet). */
63
+ code?: string;
53
64
  userId: string;
54
65
  agentId: string;
55
66
  status: ConversationStatus;
@@ -5,6 +5,7 @@ export declare class InMemoryConversationRepository implements ConversationRepos
5
5
  private byId;
6
6
  create(c: NewConversation): Promise<Conversation>;
7
7
  findById(id: string): Promise<Conversation | null>;
8
+ findByCode(code: string): Promise<Conversation | null>;
8
9
  findByIdForUser(id: string, userId: string): Promise<Conversation | null>;
9
10
  listForUser(userId: string, opts?: {
10
11
  status?: ConversationStatus;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.InMemoryMessageRepository = exports.InMemoryConversationRepository = void 0;
7
7
  exports.createInMemoryRepositories = createInMemoryRepositories;
8
8
  const crypto_1 = require("crypto");
9
+ const conversation_service_1 = require("../services/conversation.service");
9
10
  class InMemoryConversationRepository {
10
11
  constructor() {
11
12
  this.byId = new Map();
@@ -14,6 +15,7 @@ class InMemoryConversationRepository {
14
15
  const now = new Date();
15
16
  const full = {
16
17
  id: (0, crypto_1.randomUUID)(),
18
+ code: (0, conversation_service_1.generateConversationCode)(),
17
19
  status: 'active',
18
20
  totalInputTokens: 0,
19
21
  totalOutputTokens: 0,
@@ -28,6 +30,13 @@ class InMemoryConversationRepository {
28
30
  async findById(id) {
29
31
  return this.byId.get(id) ?? null;
30
32
  }
33
+ async findByCode(code) {
34
+ for (const c of this.byId.values()) {
35
+ if (c.code === code)
36
+ return c;
37
+ }
38
+ return null;
39
+ }
31
40
  async findByIdForUser(id, userId) {
32
41
  const c = this.byId.get(id);
33
42
  return c && c.userId === userId ? c : null;
@@ -54,6 +63,7 @@ class InMemoryConversationRepository {
54
63
  return {
55
64
  items: all.slice(start, end).map((c) => ({
56
65
  id: c.id,
66
+ code: c.code,
57
67
  userId: c.userId,
58
68
  agentId: c.agentId,
59
69
  status: c.status,
@@ -11,6 +11,11 @@ export declare const CONNECTOR_AUTH_REPOSITORY = "AGENTFORGE_CONNECTOR_AUTH_REPO
11
11
  export interface ConversationRepository {
12
12
  create(conv: NewConversation): Promise<Conversation>;
13
13
  findById(id: string): Promise<Conversation | null>;
14
+ /** Look a conversation up by its short public `code` (nanoid).
15
+ * Optional during rollout — adapters that predate the column don't
16
+ * implement it yet; callers must feature-detect (see
17
+ * `ConversationService.getByCode`). */
18
+ findByCode?(code: string): Promise<Conversation | null>;
14
19
  /** Same as findById but also enforces ownership — used in user-scoped routes. */
15
20
  findByIdForUser(id: string, userId: string): Promise<Conversation | null>;
16
21
  /**
@@ -6,6 +6,13 @@ export declare class ConversationNotFoundError extends Error {
6
6
  code: string;
7
7
  constructor(id: string);
8
8
  }
9
+ /**
10
+ * Generate a short, URL-friendly conversation `code` (nanoid-style).
11
+ * 31^12 ≈ 7.6e17 combinations — collisions are negligible; the unique
12
+ * index on the column is the backstop. Uses `crypto.randomBytes` so we
13
+ * don't add a nanoid dependency to the shared packages.
14
+ */
15
+ export declare function generateConversationCode(length?: number): string;
9
16
  export declare class ConversationService {
10
17
  private readonly convRepo;
11
18
  private readonly msgRepo;
@@ -16,6 +23,14 @@ export declare class ConversationService {
16
23
  title?: string;
17
24
  metadata?: Record<string, unknown>;
18
25
  }): Promise<Conversation>;
26
+ /** Resolve a conversation by its short public `code`. Returns `null`
27
+ * when no conversation carries that code, or when the wired repo
28
+ * adapter predates the column (no `findByCode`). */
29
+ findByCode(code: string): Promise<Conversation | null>;
30
+ /** Resolve a conversation by `code`, enforcing ownership. Throws
31
+ * `ConversationNotFoundError` (ambiguous on purpose) when the code is
32
+ * unknown OR belongs to another user — same contract as `loadOwned`. */
33
+ getByCode(code: string, userId: string): Promise<Conversation>;
19
34
  addMessage(params: {
20
35
  conversationId: string;
21
36
  userId: string;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConversationService = exports.ConversationNotFoundError = void 0;
4
+ exports.generateConversationCode = generateConversationCode;
5
+ const crypto_1 = require("crypto");
4
6
  class ConversationNotFoundError extends Error {
5
7
  constructor(id) {
6
8
  super(`Conversation "${id}" not found`);
@@ -9,6 +11,24 @@ class ConversationNotFoundError extends Error {
9
11
  }
10
12
  }
11
13
  exports.ConversationNotFoundError = ConversationNotFoundError;
14
+ /** Alphabet for conversation codes. Lowercase + digits, minus the
15
+ * ambiguous glyphs (0/o/1/l/i) so a code stays readable when shared. */
16
+ const CODE_ALPHABET = 'abcdefghjkmnpqrstuvwxyz23456789';
17
+ const CODE_LENGTH = 12;
18
+ /**
19
+ * Generate a short, URL-friendly conversation `code` (nanoid-style).
20
+ * 31^12 ≈ 7.6e17 combinations — collisions are negligible; the unique
21
+ * index on the column is the backstop. Uses `crypto.randomBytes` so we
22
+ * don't add a nanoid dependency to the shared packages.
23
+ */
24
+ function generateConversationCode(length = CODE_LENGTH) {
25
+ const bytes = (0, crypto_1.randomBytes)(length);
26
+ let out = '';
27
+ for (let i = 0; i < length; i++) {
28
+ out += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];
29
+ }
30
+ return out;
31
+ }
12
32
  class ConversationService {
13
33
  constructor(convRepo, msgRepo) {
14
34
  this.convRepo = convRepo;
@@ -20,12 +40,33 @@ class ConversationService {
20
40
  agentId: params.agentId,
21
41
  title: params.title,
22
42
  metadata: params.metadata,
43
+ code: generateConversationCode(),
23
44
  status: 'active',
24
45
  totalInputTokens: 0,
25
46
  totalOutputTokens: 0,
26
47
  messageCount: 0,
27
48
  });
28
49
  }
50
+ /** Resolve a conversation by its short public `code`. Returns `null`
51
+ * when no conversation carries that code, or when the wired repo
52
+ * adapter predates the column (no `findByCode`). */
53
+ async findByCode(code) {
54
+ if (!this.convRepo.findByCode)
55
+ return null;
56
+ return this.convRepo.findByCode(code);
57
+ }
58
+ /** Resolve a conversation by `code`, enforcing ownership. Throws
59
+ * `ConversationNotFoundError` (ambiguous on purpose) when the code is
60
+ * unknown OR belongs to another user — same contract as `loadOwned`. */
61
+ async getByCode(code, userId) {
62
+ const conv = this.convRepo.findByCode
63
+ ? await this.convRepo.findByCode(code)
64
+ : null;
65
+ if (!conv || conv.userId !== userId) {
66
+ throw new ConversationNotFoundError(code);
67
+ }
68
+ return conv;
69
+ }
29
70
  async addMessage(params) {
30
71
  // Coerce empty/whitespace-only content to a sentinel so we don't
31
72
  // poison the history with rows that getAnthropicMessages would
@@ -3,7 +3,7 @@ export { AgentRunnerService } from './agent-runner.service';
3
3
  export { ToolApprovalRequired, ToolBlockedError, isToolApprovalRequired, isToolBlockedError, type ToolApprovalGate, type ToolApprovalCheckArgs, type ToolApprovalDecision, } from './tool-approval-gate';
4
4
  export { PreparedStreamService, PreparedStreamError, } from './prepared-stream.service';
5
5
  export { InMemoryPreparedStreamStore } from './in-memory-prepared-stream.store';
6
- export { ConversationService, ConversationNotFoundError, } from './conversation.service';
6
+ export { ConversationService, ConversationNotFoundError, generateConversationCode, } from './conversation.service';
7
7
  export { AgentService, AgentForbiddenError, type AgentNotFoundError, } from './agent.service';
8
8
  export { ApprovalCopywriterService, type ApprovalCopyBundle as ApprovalCopywriterBundle, type ApprovalCopywriterInput, } from './approval-copywriter.service';
9
9
  export { OrchestratorService, OrchestratorError, type OrchestratorServiceOptions, } from './orchestrator.service';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InMemoryAuthorizeStateStore = exports.ConnectorError = exports.ConnectorRegistryService = exports.OAuth2Service = exports.McpServerError = exports.McpServerService = exports.McpClientError = exports.McpClientService = exports.ChatTokenError = exports.ChatTokenService = exports.AgentJobWorker = exports.OrchestratorError = exports.OrchestratorService = exports.ApprovalCopywriterService = exports.AgentForbiddenError = exports.AgentService = exports.ConversationNotFoundError = exports.ConversationService = exports.InMemoryPreparedStreamStore = exports.PreparedStreamError = exports.PreparedStreamService = exports.isToolBlockedError = exports.isToolApprovalRequired = exports.ToolBlockedError = exports.ToolApprovalRequired = exports.AgentRunnerService = exports.ToolRegistryService = void 0;
3
+ exports.InMemoryAuthorizeStateStore = exports.ConnectorError = exports.ConnectorRegistryService = exports.OAuth2Service = exports.McpServerError = exports.McpServerService = exports.McpClientError = exports.McpClientService = exports.ChatTokenError = exports.ChatTokenService = exports.AgentJobWorker = exports.OrchestratorError = exports.OrchestratorService = exports.ApprovalCopywriterService = exports.AgentForbiddenError = exports.AgentService = exports.generateConversationCode = exports.ConversationNotFoundError = exports.ConversationService = exports.InMemoryPreparedStreamStore = exports.PreparedStreamError = exports.PreparedStreamService = exports.isToolBlockedError = exports.isToolApprovalRequired = exports.ToolBlockedError = exports.ToolApprovalRequired = exports.AgentRunnerService = exports.ToolRegistryService = void 0;
4
4
  // Public service surface of @agentforge-io/core. AI runtime only — auth,
5
5
  // identity, billing, infra, etc. have all moved to the host server.
6
6
  var tool_registry_service_1 = require("./tool-registry.service");
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "InMemoryPreparedStreamStore", { enumerable: true
24
24
  var conversation_service_1 = require("./conversation.service");
25
25
  Object.defineProperty(exports, "ConversationService", { enumerable: true, get: function () { return conversation_service_1.ConversationService; } });
26
26
  Object.defineProperty(exports, "ConversationNotFoundError", { enumerable: true, get: function () { return conversation_service_1.ConversationNotFoundError; } });
27
+ Object.defineProperty(exports, "generateConversationCode", { enumerable: true, get: function () { return conversation_service_1.generateConversationCode; } });
27
28
  var agent_service_1 = require("./agent.service");
28
29
  Object.defineProperty(exports, "AgentService", { enumerable: true, get: function () { return agent_service_1.AgentService; } });
29
30
  Object.defineProperty(exports, "AgentForbiddenError", { enumerable: true, get: function () { return agent_service_1.AgentForbiddenError; } });
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@agentforge-io/core",
3
- "version": "3.4.0",
3
+ "version": "4.0.2",
4
4
  "description": "Framework-free AI runtime SDK. Owns: agent loop (Anthropic), conversations, tools, streaming, agent-job queue, SdkHooks. Identity, billing, infra (email/uploads/secrets) live in the host's modules — not here.",
5
5
  "license": "MIT",
6
+ "type": "commonjs",
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
8
9
  "exports": {