@agentforge-io/sdk-server 4.6.0 → 4.6.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.
package/dist/index.d.ts CHANGED
@@ -2,19 +2,19 @@ export { AgentForgeClient } from './client';
2
2
  export type { AgentForgeClientOptions, RequestOptions } from './client';
3
3
  export { AgentForgeError, AgentForgeAuthError, AgentForgeNotFoundError, AgentForgeNetworkError, } from './errors';
4
4
  export { TOOL_CALLBACK_HEADERS, ToolCallbackSignatureError, signToolCallbackPayload, verifyToolCallbackSignature, } from './webhooks';
5
- export type { AgentMe, AgentSummary, AppUserConnectorStatus, ApprovalCopyBundle, ChannelContext, ChatStreamChunk, ChatStreamEvent, ChatTokenRecord, ConnectorDefaultsResult, ConnectorDefaultsSource, ConnectorResourceItem, ConnectorStatus, ConnectorToolInfo, ConversationHistory, ConversationStatus, CreateAgentInput, CreateClickupTaskInput, CreateClickupTaskResult, ExternalToolSummary, IssueChatTokenInput, ListAgentsResult, ListConnectorResourcesResult, McpServerConfig, MessageRecord, MessageRole, PublicAppBranding, AppSelf, AppSelfConnector, AppSelfConnectors, RegisterToolInput, RegisterToolsInput, RegisterToolsResult, ToolCallbackPayload, ToolCallRecord, ToolPermission, ToolPermissionMode, TokenUsage, TenantSecretEntry, UpdateAgentInput, } from './types';
5
+ export type { AgentMe, AgentSummary, AppUserConnectorStatus, ApprovalCopyBundle, ChannelContext, ChatStreamChunk, ChatStreamEvent, ChatTokenRecord, ConnectorDefaultsResult, ConnectorDefaultsSource, ConnectorResourceItem, ConnectorStatus, ConnectorToolInfo, ConversationHistory, ConversationStatus, CreateAgentInput, CreateClickupTaskInput, CreateClickupTaskResult, ExternalToolSummary, IssueChatTokenInput, ListAgentsResult, ListConnectorResourcesResult, McpServerConfig, MessageRecord, MessageRole, PublicAppBranding, AppSelf, AppSelfConnector, AppSelfConnectors, AppSelfConnectorTools, AppSelfConnectorToolsResponse, RegisterToolInput, RegisterToolsInput, RegisterToolsResult, ToolCallbackPayload, ToolCallRecord, ToolPermission, ToolPermissionMode, TokenUsage, TenantSecretEntry, UpdateAgentInput, InboxThreadMode, InboxMessageDirection, InboxMessageSource, InboxMessageType, InboxThreadCustomerProfileView, InboxThreadView, InboxMessageView, SupportKpiTotals, SupportKpiResponse, AgentAttentionLogRow, } from './types';
6
6
  export { AgentsResource } from './resources/agents';
7
7
  export { AnalyticsResource } from './resources/analytics';
8
- export type { AgentAttentionLogRow, SupportAnalyticsQuery, SupportKpiResponse, SupportKpiTotals, } from './resources/analytics';
8
+ export type { GetSupportKpisInput, GetAgentAttentionLogInput, } from './resources/analytics';
9
9
  export { AppsResource } from './resources/apps';
10
10
  export { ChatResource } from './resources/chat';
11
11
  export type { AnthropicClientOptions, StreamMessageInput } from './resources/chat';
12
12
  export { ConnectorsResource } from './resources/connectors';
13
13
  export type { InboxChannelView } from './resources/connectors';
14
14
  export { InboxResource } from './resources/inbox';
15
- export type { InboxMessageView, InboxThreadCustomerProfileView, InboxThreadMode, InboxThreadView, } from './resources/inbox';
15
+ export type { ListThreadsOptions, SendMessageInput, AssignThreadInput, UnassignThreadInput, SetThreadModeInput, CloseThreadInput, } from './resources/inbox';
16
16
  export { ProvisioningResource } from './resources/provisioning';
17
- export type { ProvisionedTenant, ProvisionedApp, } from './resources/provisioning';
17
+ export type { ProvisionedTenant, ProvisionedApp, ProvisionedTeam, } from './resources/provisioning';
18
18
  export { SecretsResource } from './resources/secrets';
19
19
  export { ToolsResource } from './resources/tools';
20
20
  export type { RespondToCallbackInput, VerifyCallbackInput } from './resources/tools';
@@ -1,72 +1,39 @@
1
1
  import type { AgentForgeClient } from '../client';
2
- export interface SupportKpiTotals {
3
- receivedCount: number;
4
- aiResponsesCount: number;
5
- humanMessagesCount: number;
6
- escalationsCount: number;
7
- aiResolvedCount: number;
8
- humanHandledCount: number;
9
- assignmentsCount: number;
10
- avgResolutionMs: number | null;
11
- avgFrtMs: number | null;
12
- avgTimeToAssignMs: number | null;
13
- }
14
- export interface SupportKpiResponse {
15
- tenantId: string;
16
- from: string;
17
- to: string;
18
- totals: SupportKpiTotals;
19
- byDimension: Array<SupportKpiTotals & {
20
- dimensionType: 'agent' | 'operator';
21
- dimensionKey: string;
22
- }>;
23
- }
24
- export interface AgentAttentionLogRow {
25
- dimensionType: 'agent' | 'operator';
26
- dimensionKey: string;
27
- metricDate: string;
28
- receivedCount: number;
29
- aiResponsesCount: number;
30
- humanMessagesCount: number;
31
- escalationsCount: number;
32
- aiResolvedCount: number;
33
- humanHandledCount: number;
34
- assignmentsCount: number;
35
- avgResolutionMs: number | null;
36
- avgFrtMs: number | null;
37
- avgTimeToAssignMs: number | null;
2
+ import type { SupportKpiResponse, AgentAttentionLogRow } from '../types';
3
+ export interface GetSupportKpisInput {
4
+ /** Window start (inclusive). ISO 8601 or Date. */
5
+ from: string | Date;
6
+ /** Window end (exclusive). ISO 8601 or Date. */
7
+ to: string | Date;
8
+ agentId?: string;
9
+ operatorUserId?: string;
38
10
  }
39
- export interface SupportAnalyticsQuery {
40
- /** ISO date string. Required. */
41
- from: string;
42
- /** ISO date string. Required. */
43
- to: string;
44
- /** Narrow to one agent's rows (`byDimension`/`items` filtered to
45
- * `dimensionType: 'agent', dimensionKey: agentId`). */
11
+ export interface GetAgentAttentionLogInput {
12
+ from: string | Date;
13
+ to: string | Date;
46
14
  agentId?: string;
47
- /** Narrow to one operator's rows. */
48
15
  operatorUserId?: string;
49
16
  }
50
17
  export declare class AnalyticsResource {
51
18
  private readonly client;
52
19
  constructor(client: AgentForgeClient);
53
20
  /**
54
- * `GET /apps/me/support-kpis` — totals + per-agent/operator breakdown for
55
- * the calling App's tenant, over `[from, to]`.
21
+ * `GET /apps/me/support-kpis` — totals + per-(agent|operator)
22
+ * breakdown for the App's tenant, in the given window.
56
23
  *
57
24
  * @example
58
- * const kpis = await af.analytics.getSupportKpis({
59
- * from: '2026-08-01T00:00:00.000Z',
60
- * to: '2026-08-13T00:00:00.000Z',
25
+ * const { totals, byDimension } = await af.analytics.getSupportKpis({
26
+ * from: '2026-08-01T00:00:00Z',
27
+ * to: '2026-08-08T00:00:00Z',
61
28
  * });
62
29
  */
63
- getSupportKpis(query: SupportAnalyticsQuery): Promise<SupportKpiResponse>;
30
+ getSupportKpis(input: GetSupportKpisInput): Promise<SupportKpiResponse>;
64
31
  /**
65
- * `GET /apps/me/support-attention-log` — daily per-agent/operator rows
66
- * over `[from, to]`. Same query params as `getSupportKpis`.
32
+ * `GET /apps/me/support-attention-log` — per-day rows for
33
+ * the "atención por agente" table/chart (SD-11).
67
34
  */
68
- getAgentAttentionLog(query: SupportAnalyticsQuery): Promise<{
35
+ getAgentAttentionLog(input: GetAgentAttentionLogInput): Promise<{
69
36
  items: AgentAttentionLogRow[];
70
37
  }>;
71
- private toQueryString;
38
+ private buildQuery;
72
39
  }
@@ -1,26 +1,14 @@
1
1
  "use strict";
2
- // analytics resource (AGF-SD-3b, AGF-SUPPORT-DESK epic) — wraps
3
- // `AppSupportAnalyticsController` (`GET /apps/me/support-kpis`,
4
- // `GET /apps/me/support-attention-log`) on the platform: pre-aggregated
5
- // support KPIs (received/AI-responses/escalations/resolution-time/etc.)
6
- // for the calling App's tenant, tenant-wide with optional agent/operator
7
- // narrowing.
2
+ // analytics resource (AGF-SD-5) — App-scoped read surface for the support
3
+ // KPIs materialized by SD-3 (`af_support_metrics`). Wraps SD-3b's
4
+ // `AppSupportAnalyticsController` (`GET /apps/me/support-*`), which follows
5
+ // the `me/*` precedent of `AppsController`: the `sk_live_` IS the identity,
6
+ // so there is no `:agentId` in the path and no `tenantId` in the query.
8
7
  //
9
- // Auth: `AppSupportAnalyticsController` accepts BOTH `sk_live_` (appSecret)
10
- // and `ak_live_` (apiKey) via `ApiKeyGuard`. This resource uses plain
11
- // `this.client.request()` (NOT `requestWithApiKey()` — that helper
12
- // hardcodes `ak_live_`-only via `forceApiKey: true` and throws early on an
13
- // appSecret-only client; see `connectors.ts`'s file header for the same
14
- // gotcha on the App-user scope methods there), so `authHeaders()` can pick
15
- // either credential for the `Authorization: Bearer` header. `tenantId` is
16
- // NEVER a param here (SDD §4.5) — the server resolves it exclusively from
17
- // the credential.
18
- //
19
- // `from`/`to` are REQUIRED on both methods — the platform rejects a
20
- // missing/malformed/inverted/too-wide range with 400 (no implicit default
21
- // window). This wrapper does no validation of its own; it forwards
22
- // whatever `Date` the caller passes as an ISO string and lets the server
23
- // be the single source of truth for what's a valid range.
8
+ // Both methods are single-tenant reads: `tenantId` is resolved from the
9
+ // caller's auth context on the server (never from input), so the SDK
10
+ // signature only takes the query knobs. `agentId`/`operatorUserId` are
11
+ // optional filters within that tenant.
24
12
  Object.defineProperty(exports, "__esModule", { value: true });
25
13
  exports.AnalyticsResource = void 0;
26
14
  class AnalyticsResource {
@@ -28,32 +16,36 @@ class AnalyticsResource {
28
16
  this.client = client;
29
17
  }
30
18
  /**
31
- * `GET /apps/me/support-kpis` — totals + per-agent/operator breakdown for
32
- * the calling App's tenant, over `[from, to]`.
19
+ * `GET /apps/me/support-kpis` — totals + per-(agent|operator)
20
+ * breakdown for the App's tenant, in the given window.
33
21
  *
34
22
  * @example
35
- * const kpis = await af.analytics.getSupportKpis({
36
- * from: '2026-08-01T00:00:00.000Z',
37
- * to: '2026-08-13T00:00:00.000Z',
23
+ * const { totals, byDimension } = await af.analytics.getSupportKpis({
24
+ * from: '2026-08-01T00:00:00Z',
25
+ * to: '2026-08-08T00:00:00Z',
38
26
  * });
39
27
  */
40
- async getSupportKpis(query) {
41
- return this.client.request('GET', `/apps/me/support-kpis${this.toQueryString(query)}`);
28
+ async getSupportKpis(input) {
29
+ const qs = this.buildQuery(input);
30
+ return this.client.request('GET', `/apps/me/support-kpis?${qs}`);
42
31
  }
43
32
  /**
44
- * `GET /apps/me/support-attention-log` — daily per-agent/operator rows
45
- * over `[from, to]`. Same query params as `getSupportKpis`.
33
+ * `GET /apps/me/support-attention-log` — per-day rows for
34
+ * the "atención por agente" table/chart (SD-11).
46
35
  */
47
- async getAgentAttentionLog(query) {
48
- return this.client.request('GET', `/apps/me/support-attention-log${this.toQueryString(query)}`);
36
+ async getAgentAttentionLog(input) {
37
+ const qs = this.buildQuery(input);
38
+ return this.client.request('GET', `/apps/me/support-attention-log?${qs}`);
49
39
  }
50
- toQueryString(query) {
51
- const params = new URLSearchParams({ from: query.from, to: query.to });
52
- if (query.agentId)
53
- params.set('agentId', query.agentId);
54
- if (query.operatorUserId)
55
- params.set('operatorUserId', query.operatorUserId);
56
- return `?${params.toString()}`;
40
+ buildQuery(input) {
41
+ const params = new URLSearchParams();
42
+ params.set('from', input.from instanceof Date ? input.from.toISOString() : input.from);
43
+ params.set('to', input.to instanceof Date ? input.to.toISOString() : input.to);
44
+ if (input.agentId)
45
+ params.set('agentId', input.agentId);
46
+ if (input.operatorUserId)
47
+ params.set('operatorUserId', input.operatorUserId);
48
+ return params.toString();
57
49
  }
58
50
  }
59
51
  exports.AnalyticsResource = AnalyticsResource;
@@ -1,5 +1,5 @@
1
1
  import type { AgentForgeClient } from '../client';
2
- import type { PublicAppBranding, AppSelf, AppSelfConnectors } from '../types';
2
+ import type { PublicAppBranding, AppSelf, AppSelfConnectors, AppSelfConnectorToolsResponse } from '../types';
3
3
  export declare class AppsResource {
4
4
  private readonly client;
5
5
  constructor(client: AgentForgeClient);
@@ -41,5 +41,21 @@ export declare class AppsResource {
41
41
  * by the App at runtime.
42
42
  */
43
43
  list: () => Promise<AppSelfConnectors>;
44
+ /**
45
+ * `GET /apps/me/connector-tools` (AGF-CTG-1) — flat tool-name catalog
46
+ * contributed by the connectors ENABLED for the App this client
47
+ * authenticated AS. App-level granularity ONLY: this is NOT narrowed
48
+ * per-end-user — that narrowing already happens downstream at runtime
49
+ * via `toolsForAppUser ∩ agent.tools[]`.
50
+ *
51
+ * Use it to build a whitelist of tool names an App may reference (e.g.
52
+ * the SOP Architect's bootstrap), so a prompt cannot name a connector
53
+ * tool the App has no connector enabled for.
54
+ *
55
+ * @example
56
+ * const { toolNames } = await af.apps.connectors.tools();
57
+ * const allowed = new Set(toolNames);
58
+ */
59
+ tools: () => Promise<AppSelfConnectorToolsResponse>;
44
60
  };
45
61
  }
@@ -35,6 +35,24 @@ class AppsResource {
35
35
  list: async () => {
36
36
  return this.client.request('GET', '/apps/me/connectors');
37
37
  },
38
+ /**
39
+ * `GET /apps/me/connector-tools` (AGF-CTG-1) — flat tool-name catalog
40
+ * contributed by the connectors ENABLED for the App this client
41
+ * authenticated AS. App-level granularity ONLY: this is NOT narrowed
42
+ * per-end-user — that narrowing already happens downstream at runtime
43
+ * via `toolsForAppUser ∩ agent.tools[]`.
44
+ *
45
+ * Use it to build a whitelist of tool names an App may reference (e.g.
46
+ * the SOP Architect's bootstrap), so a prompt cannot name a connector
47
+ * tool the App has no connector enabled for.
48
+ *
49
+ * @example
50
+ * const { toolNames } = await af.apps.connectors.tools();
51
+ * const allowed = new Set(toolNames);
52
+ */
53
+ tools: async () => {
54
+ return this.client.request('GET', '/apps/me/connector-tools');
55
+ },
38
56
  };
39
57
  }
40
58
  /**
@@ -1,113 +1,71 @@
1
1
  import type { AgentForgeClient } from '../client';
2
- export declare class InboxResource {
3
- private readonly client;
4
- constructor(client: AgentForgeClient);
5
- /**
6
- * `GET /connectors/inbox/agents/:agentId/inbox/threads` — threads for this
7
- * agent's support inbox. Optional `channelId` narrows to one channel.
8
- *
9
- * @example
10
- * const { items } = await af.inbox.listThreads(agentId);
11
- */
12
- listThreads(agentId: string, opts?: {
13
- channelId?: string;
14
- }): Promise<{
15
- items: InboxThreadView[];
16
- }>;
17
- /**
18
- * `GET /connectors/inbox/agents/:agentId/inbox/threads/:threadId` — a
19
- * single thread.
20
- */
21
- getThread(agentId: string, threadId: string): Promise<InboxThreadView>;
22
- /**
23
- * `GET /connectors/inbox/agents/:agentId/inbox/threads/:threadId/messages`
24
- * — full message history for a thread.
25
- */
26
- listMessages(agentId: string, threadId: string): Promise<{
27
- items: InboxMessageView[];
28
- }>;
29
- /**
30
- * `POST .../threads/:threadId/messages` — send a message as the operator.
31
- * `pauseAgent` defaults `true` server-side (pass `false` to reply without
32
- * taking the thread away from the agent). `operatorId` is the App's own
33
- * opaque identifier for the acting human rep — see file header.
34
- *
35
- * @example
36
- * await af.inbox.sendOperatorMessage(agentId, threadId, {
37
- * text: 'Un momento, reviso tu caso.',
38
- * operatorId: 'scrappydoc-rep-42',
39
- * });
40
- */
41
- sendOperatorMessage(agentId: string, threadId: string, input: {
42
- text: string;
43
- pauseAgent?: boolean;
44
- operatorId?: string;
45
- }): Promise<void>;
46
- /**
47
- * `POST .../threads/:threadId/assign` — claim a thread for an operator.
48
- * `operatorId` is required (the server rejects a missing one with 401).
49
- */
50
- assignThread(agentId: string, threadId: string, input: {
51
- operatorId: string;
52
- }): Promise<void>;
53
- /**
54
- * `DELETE .../threads/:threadId/assign` — release a thread claim
55
- * (handback). `operatorId`, when given, must match the current assignee
56
- * server-side.
57
- */
58
- handback(agentId: string, threadId: string, opts?: {
59
- operatorId?: string;
60
- }): Promise<void>;
61
- /**
62
- * `POST .../threads/:threadId/mode` — switch a thread between agent/human
63
- * mode.
64
- */
65
- setThreadMode(agentId: string, threadId: string, mode: InboxThreadMode): Promise<void>;
66
- /**
67
- * `POST .../threads/:threadId/close` — close a human-mode thread.
68
- */
69
- closeThread(agentId: string, threadId: string, opts?: {
70
- notes?: string;
71
- operatorId?: string;
72
- }): Promise<void>;
2
+ import type { InboxThreadView, InboxMessageView, InboxThreadMode } from '../types';
3
+ export interface ListThreadsOptions {
4
+ /** Optional filter to threads on a single channel of the agent. */
5
+ channelId?: string;
73
6
  }
74
- export type InboxThreadMode = 'agent' | 'human';
75
- export interface InboxThreadCustomerProfileView {
76
- id: string;
77
- displayName?: string;
78
- username?: string;
79
- avatarUrl?: string;
80
- providerId?: string;
81
- tags?: string[];
82
- notes?: string;
7
+ export interface SendMessageInput {
8
+ /** The operator's text. */
9
+ text: string;
10
+ /** Default true. Toggle off when the operator wants to chime in
11
+ * without taking the thread away from the agent. */
12
+ pauseAgent?: boolean;
13
+ /** App-minted operator identity (see file header). */
14
+ operatorId?: string;
15
+ }
16
+ export interface AssignThreadInput {
17
+ /** App-minted operator identity — required for assign. Same string
18
+ * re-sent proves ownership of a prior claim. */
19
+ operatorId: string;
20
+ }
21
+ export interface UnassignThreadInput {
22
+ /** App-minted operator identity — must match the current assignee. */
23
+ operatorId: string;
83
24
  }
84
- export interface InboxThreadView {
85
- id: string;
86
- channelId: string;
87
- externalId: string;
88
- displayName?: string;
25
+ export interface SetThreadModeInput {
89
26
  mode: InboxThreadMode;
90
- unreadCount: number;
91
- lastMessageAt?: string;
92
- lastMessagePreview?: string;
93
- assignedHumanUserId?: string;
94
- closedAt?: string;
95
- closeNotes?: string;
96
- closedByUserId?: string;
97
- customerProfileId?: string;
98
- customerProfile?: InboxThreadCustomerProfileView;
99
- createdAt: string;
100
- updatedAt: string;
101
27
  }
102
- export interface InboxMessageView {
103
- id: string;
104
- threadId: string;
105
- direction: 'inbound' | 'outbound';
106
- source: 'customer' | 'agent' | 'operator';
107
- type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'location' | 'unsupported';
108
- body: string;
109
- externalMessageId?: string;
110
- payload?: Record<string, unknown>;
111
- sentAt: string;
112
- recordedAt: string;
28
+ export interface CloseThreadInput {
29
+ /** Free-text notes the operator leaves on close. Persisted as
30
+ * `af_inbox_threads.close_notes` — later consumed by AGF-BRAIN as
31
+ * institutional knowledge. */
32
+ notes?: string;
33
+ /** App-minted operator identity — recorded on `closed_by_user_id`. */
34
+ operatorId?: string;
35
+ }
36
+ /**
37
+ * Agent-scoped inbox operator surface. Every method takes the `agentId`
38
+ * whose threads the App is operating; the App-ownership boundary is
39
+ * enforced server-side (404 on mismatch, mirroring
40
+ * `assertAgentOwnedByApp` in the identity-side controllers).
41
+ *
42
+ * @example
43
+ * const inbox = af.inbox.forAgent(agentId);
44
+ * const { items: threads } = await inbox.listThreads();
45
+ * const { items: messages } = await inbox.listMessages(threads[0].id);
46
+ * await inbox.sendMessage(threads[0].id, {
47
+ * text: 'Hi, jumping in to help',
48
+ * operatorId: 'rep_alice',
49
+ * });
50
+ */
51
+ export declare class InboxResource {
52
+ private readonly client;
53
+ constructor(client: AgentForgeClient);
54
+ /** Bind an agent id and return a helper with one method per
55
+ * `AppInboxController` route. Avoids threading `agentId` through
56
+ * every call. */
57
+ forAgent(agentId: string): {
58
+ listThreads: (opts?: ListThreadsOptions) => Promise<{
59
+ items: InboxThreadView[];
60
+ }>;
61
+ getThread: (threadId: string) => Promise<InboxThreadView>;
62
+ listMessages: (threadId: string) => Promise<{
63
+ items: InboxMessageView[];
64
+ }>;
65
+ sendMessage: (threadId: string, input: SendMessageInput) => Promise<void>;
66
+ assign: (threadId: string, input: AssignThreadInput) => Promise<void>;
67
+ unassign: (threadId: string, input: UnassignThreadInput) => Promise<void>;
68
+ setMode: (threadId: string, input: SetThreadModeInput) => Promise<void>;
69
+ close: (threadId: string, input?: CloseThreadInput) => Promise<void>;
70
+ };
113
71
  }
@@ -1,118 +1,80 @@
1
1
  "use strict";
2
- // inbox resource (AGF-SD-1, AGF-SUPPORT-DESK epic) — wraps
3
- // `AppInboxController` (`connectors/inbox/agents/:agentId/inbox/*`) on the
4
- // platform: list/read/reply/assign/handback/mode/close for an agent's
5
- // support inbox threads, authenticated as an App (`sk_live_`) or an
6
- // agent-scoped `ak_live_` — never a human dashboard session.
2
+ // inbox resource (AGF-SD-5) — App-scoped operator surface. Wraps the
3
+ // platform's `AppInboxController` (AGF-SD-1, `connectors/inbox/agents/:agentId`),
4
+ // exposing list/read/reply/assign/close for the threads of an agent the
5
+ // calling App owns.
7
6
  //
8
- // Auth: `AppInboxController` accepts BOTH `sk_live_` (appSecret) and
9
- // `ak_live_` (apiKey) via `ApiKeyGuard` — unlike the tenant-admin-only
10
- // surfaces `ConnectorsResource` wraps with `requestWithApiKey()` (which
11
- // hardcodes `ak_live_` and throws early on an appSecret-only client, see
12
- // that file's header), inbox operator routes are meant to be called by an
13
- // App itself. So this resource uses plain `this.client.request()`, whose
14
- // `authHeaders()` picks `apiKey` OR `appSecret` for the `Authorization:
15
- // Bearer` header (chatToken, if present instead, is rejected server-side
16
- // with 401 since the route requires `auth.isApiKey`). Every method takes
17
- // `agentId` explicitly (unlike `af.agents.me()`) because the platform
18
- // route itself is per-agent and validates ownership server-side
19
- // (`assertOwnedAgent` — 404, not 403, on a foreign agent).
7
+ // Auth: `ApiKeyGuard` on the server accepts BOTH `ak_live_` and `sk_live_`.
8
+ // The SDK's `authHeaders()` already sends whichever the client was
9
+ // constructed with on the `Authorization: Bearer` header — no per-call
10
+ // wiring needed here.
20
11
  //
21
- // Tenant isolation (SDD §4.5): `tenantId` is NEVER a param here — the
22
- // server resolves it exclusively from the `ak_live_`/`sk_live_` credential.
12
+ // `operatorId` semantics: SD-1's header comment flags this explicitly — an
13
+ // App has no `af_users.id`, so `operatorId` is whatever stable string the
14
+ // App mints for its own end of the conversation. The SDK exposes it as an
15
+ // optional param on `send()`, `assign()`, `unassign()`, `close()` and
16
+ // passes it through verbatim. The server's assignment guard still enforces
17
+ // "same operatorId re-sent = same claimant".
23
18
  //
24
- // Operator identity: the platform has no concept of an App's own
25
- // human-support-rep identity. `operatorId` (optional on most write methods)
26
- // is an opaque string the calling App mints for its own end of the
27
- // conversation — passed through verbatim as the platform's `actingUserId`/
28
- // `userId`. See `AppInboxController`'s file header on the platform for the
29
- // full rationale.
30
- //
31
- // NOT covered here (left for a future extension — see file bottom for
32
- // what's deliberately excluded and why):
33
- // - `af.inbox.channels.get`/`update` — no App-scoped single-channel-read
34
- // or update route exists on the platform today (`ApiKeyInboxController`
35
- // only has list/create/delete). `list`/`create`/`destroy` already live
36
- // on `af.connectors.*` (see `connectors.ts`) — not duplicated here.
37
- // - operator online/offline status (`PUT teams/:teamId/operator/status`)
38
- // — lives on `OperatorStatusController`, gated by the GLOBAL
39
- // `JwtAuthGuard` (human dashboard session), not `ApiKeyGuard`. No
40
- // `@Public()`, not reachable with `sk_live_`/`ak_live_` at all.
19
+ // NOTE — chip AGF-INBOX-OPERATORID-UUID: SD-1's free-string `operatorId`
20
+ // collides with UUID columns on assign/close (open follow-up on the
21
+ // platform side). Callers passing arbitrary strings should not be
22
+ // surprised by a 500 today; the shape convergence lands separately.
41
23
  Object.defineProperty(exports, "__esModule", { value: true });
42
24
  exports.InboxResource = void 0;
25
+ /**
26
+ * Agent-scoped inbox operator surface. Every method takes the `agentId`
27
+ * whose threads the App is operating; the App-ownership boundary is
28
+ * enforced server-side (404 on mismatch, mirroring
29
+ * `assertAgentOwnedByApp` in the identity-side controllers).
30
+ *
31
+ * @example
32
+ * const inbox = af.inbox.forAgent(agentId);
33
+ * const { items: threads } = await inbox.listThreads();
34
+ * const { items: messages } = await inbox.listMessages(threads[0].id);
35
+ * await inbox.sendMessage(threads[0].id, {
36
+ * text: 'Hi, jumping in to help',
37
+ * operatorId: 'rep_alice',
38
+ * });
39
+ */
43
40
  class InboxResource {
44
41
  constructor(client) {
45
42
  this.client = client;
46
43
  }
47
- /**
48
- * `GET /connectors/inbox/agents/:agentId/inbox/threads` — threads for this
49
- * agent's support inbox. Optional `channelId` narrows to one channel.
50
- *
51
- * @example
52
- * const { items } = await af.inbox.listThreads(agentId);
53
- */
54
- async listThreads(agentId, opts) {
55
- const qs = opts?.channelId
56
- ? `?channelId=${encodeURIComponent(opts.channelId)}`
57
- : '';
58
- return this.client.request('GET', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads${qs}`);
59
- }
60
- /**
61
- * `GET /connectors/inbox/agents/:agentId/inbox/threads/:threadId` — a
62
- * single thread.
63
- */
64
- async getThread(agentId, threadId) {
65
- return this.client.request('GET', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}`);
66
- }
67
- /**
68
- * `GET /connectors/inbox/agents/:agentId/inbox/threads/:threadId/messages`
69
- * — full message history for a thread.
70
- */
71
- async listMessages(agentId, threadId) {
72
- return this.client.request('GET', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}/messages`);
73
- }
74
- /**
75
- * `POST .../threads/:threadId/messages` — send a message as the operator.
76
- * `pauseAgent` defaults `true` server-side (pass `false` to reply without
77
- * taking the thread away from the agent). `operatorId` is the App's own
78
- * opaque identifier for the acting human rep — see file header.
79
- *
80
- * @example
81
- * await af.inbox.sendOperatorMessage(agentId, threadId, {
82
- * text: 'Un momento, reviso tu caso.',
83
- * operatorId: 'scrappydoc-rep-42',
84
- * });
85
- */
86
- async sendOperatorMessage(agentId, threadId, input) {
87
- await this.client.request('POST', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}/messages`, input);
88
- }
89
- /**
90
- * `POST .../threads/:threadId/assign` — claim a thread for an operator.
91
- * `operatorId` is required (the server rejects a missing one with 401).
92
- */
93
- async assignThread(agentId, threadId, input) {
94
- await this.client.request('POST', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}/assign`, input);
95
- }
96
- /**
97
- * `DELETE .../threads/:threadId/assign` — release a thread claim
98
- * (handback). `operatorId`, when given, must match the current assignee
99
- * server-side.
100
- */
101
- async handback(agentId, threadId, opts) {
102
- await this.client.request('DELETE', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}/assign`, opts);
103
- }
104
- /**
105
- * `POST .../threads/:threadId/mode` — switch a thread between agent/human
106
- * mode.
107
- */
108
- async setThreadMode(agentId, threadId, mode) {
109
- await this.client.request('POST', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}/mode`, { mode });
110
- }
111
- /**
112
- * `POST .../threads/:threadId/close` — close a human-mode thread.
113
- */
114
- async closeThread(agentId, threadId, opts) {
115
- await this.client.request('POST', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/threads/${encodeURIComponent(threadId)}/close`, opts);
44
+ /** Bind an agent id and return a helper with one method per
45
+ * `AppInboxController` route. Avoids threading `agentId` through
46
+ * every call. */
47
+ forAgent(agentId) {
48
+ const base = `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox`;
49
+ return {
50
+ listThreads: async (opts = {}) => {
51
+ const qs = opts.channelId
52
+ ? `?channelId=${encodeURIComponent(opts.channelId)}`
53
+ : '';
54
+ return this.client.request('GET', `${base}/threads${qs}`);
55
+ },
56
+ getThread: async (threadId) => {
57
+ return this.client.request('GET', `${base}/threads/${encodeURIComponent(threadId)}`);
58
+ },
59
+ listMessages: async (threadId) => {
60
+ return this.client.request('GET', `${base}/threads/${encodeURIComponent(threadId)}/messages`);
61
+ },
62
+ sendMessage: async (threadId, input) => {
63
+ await this.client.request('POST', `${base}/threads/${encodeURIComponent(threadId)}/messages`, input);
64
+ },
65
+ assign: async (threadId, input) => {
66
+ await this.client.request('POST', `${base}/threads/${encodeURIComponent(threadId)}/assign`, input);
67
+ },
68
+ unassign: async (threadId, input) => {
69
+ await this.client.request('DELETE', `${base}/threads/${encodeURIComponent(threadId)}/assign`, input);
70
+ },
71
+ setMode: async (threadId, input) => {
72
+ await this.client.request('POST', `${base}/threads/${encodeURIComponent(threadId)}/mode`, input);
73
+ },
74
+ close: async (threadId, input = {}) => {
75
+ await this.client.request('POST', `${base}/threads/${encodeURIComponent(threadId)}/close`, input);
76
+ },
77
+ };
116
78
  }
117
79
  }
118
80
  exports.InboxResource = InboxResource;
@@ -11,6 +11,14 @@ export interface ProvisionedApp {
11
11
  publicKey: string;
12
12
  appSecret: string;
13
13
  }
14
+ /** `POST /platform/host-teams` result — a team-orchestrator's identity
15
+ * (AGF-SD-6a). No secret to reveal here (unlike `tenant()`/`app()`) — a
16
+ * team has no credential of its own, it's addressed through the
17
+ * tenant's `ak_live_` / App's `sk_live_`. */
18
+ export interface ProvisionedTeam {
19
+ teamId: string;
20
+ orchestratorAgentId: string;
21
+ }
14
22
  export declare class ProvisioningResource {
15
23
  private readonly client;
16
24
  constructor(client: AgentForgeClient);
@@ -49,4 +57,27 @@ export declare class ProvisioningResource {
49
57
  hostApp: string;
50
58
  hostRef?: string;
51
59
  }): Promise<ProvisionedApp>;
60
+ /**
61
+ * `POST /platform/host-teams` (AGF-SD-6a) — find-or-create a
62
+ * team-orchestrator for an EXISTING tenant on behalf of a trusted host
63
+ * app. Idempotent by `(tenantId, hostRef)`: a re-provision returns the
64
+ * SAME `teamId`/`orchestratorAgentId`, never a duplicate orchestrator.
65
+ *
66
+ * @param input.tenantId the tenant the team belongs to (host provisions
67
+ * it first via `tenant()`)
68
+ * @param input.hostApp short host id (e.g. "scrappydoc") — recorded
69
+ * for parity with `tenant()`/`app()`, not part of
70
+ * the idempotency key here (a team already lives
71
+ * under one tenant, so `(tenantId, hostRef)`
72
+ * alone is unambiguous)
73
+ * @param input.hostRef host reference (e.g. the workspaceId) — the
74
+ * idempotency key together with `tenantId`
75
+ * @param input.name display name, only used on first provision
76
+ */
77
+ team(input: {
78
+ tenantId: string;
79
+ hostApp: string;
80
+ hostRef: string;
81
+ name?: string;
82
+ }): Promise<ProvisionedTeam>;
52
83
  }
@@ -51,5 +51,30 @@ class ProvisioningResource {
51
51
  async app(input) {
52
52
  return this.client.request('POST', '/platform/host-apps', { tenantId: input.tenantId, hostApp: input.hostApp, hostRef: input.hostRef }, { headers: this.headers() });
53
53
  }
54
+ /**
55
+ * `POST /platform/host-teams` (AGF-SD-6a) — find-or-create a
56
+ * team-orchestrator for an EXISTING tenant on behalf of a trusted host
57
+ * app. Idempotent by `(tenantId, hostRef)`: a re-provision returns the
58
+ * SAME `teamId`/`orchestratorAgentId`, never a duplicate orchestrator.
59
+ *
60
+ * @param input.tenantId the tenant the team belongs to (host provisions
61
+ * it first via `tenant()`)
62
+ * @param input.hostApp short host id (e.g. "scrappydoc") — recorded
63
+ * for parity with `tenant()`/`app()`, not part of
64
+ * the idempotency key here (a team already lives
65
+ * under one tenant, so `(tenantId, hostRef)`
66
+ * alone is unambiguous)
67
+ * @param input.hostRef host reference (e.g. the workspaceId) — the
68
+ * idempotency key together with `tenantId`
69
+ * @param input.name display name, only used on first provision
70
+ */
71
+ async team(input) {
72
+ return this.client.request('POST', '/platform/host-teams', {
73
+ tenantId: input.tenantId,
74
+ hostApp: input.hostApp,
75
+ hostRef: input.hostRef,
76
+ name: input.name,
77
+ }, { headers: this.headers() });
78
+ }
54
79
  }
55
80
  exports.ProvisioningResource = ProvisioningResource;
package/dist/types.d.ts CHANGED
@@ -84,6 +84,16 @@ export interface AppSelfConnectors {
84
84
  mode: 'whitelist' | 'legacy';
85
85
  connectors: AppSelfConnector[];
86
86
  }
87
+ export interface AppSelfConnectorTools {
88
+ connectorId: string;
89
+ toolNames: string[];
90
+ }
91
+ export interface AppSelfConnectorToolsResponse {
92
+ connectors: AppSelfConnectorTools[];
93
+ /** Flat, de-duplicated union of every `toolNames` entry above — this is
94
+ * what a whitelist consumer actually wants. */
95
+ toolNames: string[];
96
+ }
87
97
  export interface RegisterToolInput {
88
98
  name: string;
89
99
  description: string;
@@ -488,4 +498,93 @@ export interface TenantSecretEntry {
488
498
  * Ausente si nunca se configuró. */
489
499
  updatedAt?: string;
490
500
  }
501
+ export type InboxThreadMode = 'agent' | 'human';
502
+ export type InboxMessageDirection = 'inbound' | 'outbound';
503
+ export type InboxMessageSource = 'customer' | 'agent' | 'operator';
504
+ export type InboxMessageType = 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'location' | 'unsupported';
505
+ /** Read-through customer profile snapshot embedded on every thread — saves
506
+ * the UI a round-trip per thread. NULL on threads whose customerProfileId
507
+ * hasn't been linked yet (very brief window on first inbound). */
508
+ export interface InboxThreadCustomerProfileView {
509
+ id: string;
510
+ displayName?: string;
511
+ /** Provider handle without the leading `@`. */
512
+ username?: string;
513
+ avatarUrl?: string;
514
+ providerId?: string;
515
+ tags?: string[];
516
+ notes?: string;
517
+ }
518
+ export interface InboxThreadView {
519
+ id: string;
520
+ channelId: string;
521
+ externalId: string;
522
+ displayName?: string;
523
+ mode: InboxThreadMode;
524
+ unreadCount: number;
525
+ lastMessageAt?: string;
526
+ lastMessagePreview?: string;
527
+ /** Workspace user currently responsible for the thread. */
528
+ assignedHumanUserId?: string;
529
+ /** AGF-ASSIGN-P1-CLOSE. ISO timestamp when the human conversation was
530
+ * formally closed. Undefined = open / never escalated. */
531
+ closedAt?: string;
532
+ closeNotes?: string;
533
+ closedByUserId?: string;
534
+ customerProfileId?: string;
535
+ customerProfile?: InboxThreadCustomerProfileView;
536
+ createdAt: string;
537
+ updatedAt: string;
538
+ }
539
+ export interface InboxMessageView {
540
+ id: string;
541
+ threadId: string;
542
+ direction: InboxMessageDirection;
543
+ source: InboxMessageSource;
544
+ type: InboxMessageType;
545
+ body: string;
546
+ externalMessageId?: string;
547
+ payload?: Record<string, unknown>;
548
+ sentAt: string;
549
+ recordedAt: string;
550
+ }
551
+ export interface SupportKpiTotals {
552
+ receivedCount: number;
553
+ aiResponsesCount: number;
554
+ humanMessagesCount: number;
555
+ escalationsCount: number;
556
+ aiResolvedCount: number;
557
+ humanHandledCount: number;
558
+ assignmentsCount: number;
559
+ avgResolutionMs: number | null;
560
+ avgFrtMs: number | null;
561
+ avgTimeToAssignMs: number | null;
562
+ }
563
+ export interface SupportKpiResponse {
564
+ tenantId: string;
565
+ from: string;
566
+ to: string;
567
+ totals: SupportKpiTotals;
568
+ /** One entry per (dimensionType, dimensionKey) in range. */
569
+ byDimension: Array<SupportKpiTotals & {
570
+ dimensionType: 'agent' | 'operator';
571
+ dimensionKey: string;
572
+ }>;
573
+ }
574
+ export interface AgentAttentionLogRow {
575
+ dimensionType: 'agent' | 'operator';
576
+ dimensionKey: string;
577
+ /** YYYY-MM-DD */
578
+ metricDate: string;
579
+ receivedCount: number;
580
+ aiResponsesCount: number;
581
+ humanMessagesCount: number;
582
+ escalationsCount: number;
583
+ aiResolvedCount: number;
584
+ humanHandledCount: number;
585
+ assignmentsCount: number;
586
+ avgResolutionMs: number | null;
587
+ avgFrtMs: number | null;
588
+ avgTimeToAssignMs: number | null;
589
+ }
491
590
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/sdk-server",
3
- "version": "4.6.0",
3
+ "version": "4.6.1",
4
4
  "description": "Server-side SDK for backend apps integrating with AgentForge platform. Auth via ChatToken or ak_live_ API key.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",