@agentforge-io/sdk-server 4.0.18

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.
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ // chat resource — dos superficies distintas bajo un mismo nombre:
3
+ // - `createAnthropicClient()`: passthrough crudo al proxy Anthropic
4
+ // (`/api/v1/anthropic`), sin tool-calling ni persistencia de conversación.
5
+ // - `streamMessage()` / `getHistory()`: el chat AGENT-AWARE de
6
+ // chat-stream.controller.ts (`/api/v1/chat/*`) — resuelve connectors +
7
+ // tools 'remote' del agent, persiste la conversación, hace streaming SSE
8
+ // con tool-calling completo. Es lo que corre detrás de cualquier chat
9
+ // "de verdad" con un agent (SOP Architect, Finance Team, etc).
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.ChatResource = void 0;
12
+ class ChatResource {
13
+ constructor(client) {
14
+ this.client = client;
15
+ }
16
+ /**
17
+ * Devuelve una instancia de `Anthropic` (de @anthropic-ai/sdk) preconfigurada
18
+ * para hablar contra el proxy del platform.
19
+ *
20
+ * **Pre-req:** la app cliente tiene `@anthropic-ai/sdk` como dep regular y
21
+ * el `AgentForgeClient` se construyó con `apiKey` (ak_live_) además del
22
+ * chatToken.
23
+ *
24
+ * @example
25
+ * const anthropic = await af.chat.createAnthropicClient();
26
+ * const msg = await anthropic.messages.create({ ... });
27
+ */
28
+ async createAnthropicClient(opts = {}) {
29
+ const apiKey = this.client.getApiKey();
30
+ if (!apiKey) {
31
+ throw new Error('chat.createAnthropicClient requires apiKey (ak_live_) in AgentForgeClient. ' +
32
+ 'Pass it alongside chatToken when constructing the client.');
33
+ }
34
+ let Anthropic;
35
+ try {
36
+ // Lazy import — @anthropic-ai/sdk es optionalDependency.
37
+ Anthropic = require('@anthropic-ai/sdk').default ?? require('@anthropic-ai/sdk');
38
+ }
39
+ catch {
40
+ throw new Error('@anthropic-ai/sdk is required for chat.createAnthropicClient. ' +
41
+ 'Install it in the app: yarn add @anthropic-ai/sdk');
42
+ }
43
+ return new Anthropic({
44
+ apiKey,
45
+ baseURL: `${this.client.baseURL}/api/v1/anthropic`,
46
+ defaultHeaders: opts.anthropicVersion
47
+ ? { 'anthropic-version': opts.anthropicVersion }
48
+ : undefined,
49
+ });
50
+ }
51
+ /**
52
+ * Chat streaming AGENT-AWARE — `POST /api/v1/chat/messages/stream`. Auth:
53
+ * ChatToken (scoped al agent). Resuelve connectors del tenant + tools
54
+ * 'remote' registradas del agent automáticamente; el caller solo manda
55
+ * `content`.
56
+ *
57
+ * Yieldea `ChatStreamEvent` tipados en vez de frames SSE crudos — ver
58
+ * el tipo para el detalle de cada `kind`.
59
+ *
60
+ * @example
61
+ * let conversationId: string | undefined;
62
+ * for await (const ev of af.chat.streamMessage({ content: 'hola' })) {
63
+ * if (ev.kind === 'conversation') conversationId = ev.conversationId;
64
+ * else if (ev.kind === 'chunk' && ev.chunk.type === 'text_delta') {
65
+ * process.stdout.write(ev.chunk.delta);
66
+ * }
67
+ * }
68
+ */
69
+ async *streamMessage(input) {
70
+ const res = await this.client.requestStream('POST', '/api/v1/chat/messages/stream', {
71
+ content: input.content,
72
+ ...(input.conversationId ? { conversationId: input.conversationId } : {}),
73
+ });
74
+ if (!res.body) {
75
+ throw new Error('chat.streamMessage: response has no readable body');
76
+ }
77
+ const reader = res.body.getReader();
78
+ const decoder = new TextDecoder();
79
+ let buffer = '';
80
+ try {
81
+ for (;;) {
82
+ const { value, done } = await reader.read();
83
+ if (done)
84
+ break;
85
+ buffer += decoder.decode(value, { stream: true });
86
+ let idx;
87
+ while ((idx = buffer.indexOf('\n\n')) !== -1) {
88
+ const frame = buffer.slice(0, idx);
89
+ buffer = buffer.slice(idx + 2);
90
+ const raw = parseRawSseFrame(frame);
91
+ if (!raw)
92
+ continue;
93
+ const event = toChatStreamEvent(raw);
94
+ if (event)
95
+ yield event;
96
+ }
97
+ }
98
+ }
99
+ finally {
100
+ reader.releaseLock();
101
+ }
102
+ }
103
+ /**
104
+ * Historial completo de una conversación — `GET
105
+ * /api/v1/chat/conversations/:id/messages`. Útil para "resumir" un chat
106
+ * tras un refresh de página cuando el `conversationId` se guardó (ej. en
107
+ * la URL). Tira `AgentForgeNotFoundError` si la conversación no existe o
108
+ * no pertenece al ChatToken que llama.
109
+ */
110
+ async getHistory(conversationId) {
111
+ return this.client.request('GET', `/api/v1/chat/conversations/${encodeURIComponent(conversationId)}/messages`);
112
+ }
113
+ }
114
+ exports.ChatResource = ChatResource;
115
+ /** Frame SSE genérico: `event: <name>\ndata: <line>\ndata: <line>...`.
116
+ * `event:` es opcional en el estándar SSE (default 'message') — acá SIEMPRE
117
+ * viene, pero el fallback no hace daño. */
118
+ function parseRawSseFrame(frame) {
119
+ let event = 'message';
120
+ const dataLines = [];
121
+ for (const line of frame.split('\n')) {
122
+ if (line.startsWith('event:'))
123
+ event = line.slice(6).trim();
124
+ else if (line.startsWith('data:'))
125
+ dataLines.push(line.slice(5).replace(/^ /, ''));
126
+ }
127
+ if (dataLines.length === 0)
128
+ return null;
129
+ return { event, data: dataLines.join('\n') };
130
+ }
131
+ /**
132
+ * Mapea un frame SSE crudo al `ChatStreamEvent` tipado. Ver chat-stream.
133
+ * controller.ts: `event: conversation` y `event: error` son frames propios
134
+ * del controller (no vienen de StreamChunk); todo lo demás es
135
+ * `writeSse(chunk.type, chunk)` — el `event:` name coincide con `chunk.type`,
136
+ * y el `data:` payload YA trae ese mismo `type` adentro, así que basta con
137
+ * mirar `data.type` para reconstruir el chunk (no hace falta el nombre del
138
+ * evento). La excepción es el frame final `event: done` / `data: {ok:true}`
139
+ * que cierra la conexión HTTP — se distingue de `StreamChunk`'s propio
140
+ * `{type:'done', messageId}` por la AUSENCIA de `.type`.
141
+ */
142
+ function toChatStreamEvent(frame) {
143
+ if (frame.event === 'conversation') {
144
+ try {
145
+ const parsed = JSON.parse(frame.data);
146
+ if (parsed.conversationId) {
147
+ return { kind: 'conversation', conversationId: parsed.conversationId };
148
+ }
149
+ }
150
+ catch {
151
+ /* malformed frame — drop it, the stream continues */
152
+ }
153
+ return null;
154
+ }
155
+ if (frame.event === 'error') {
156
+ try {
157
+ const parsed = JSON.parse(frame.data);
158
+ return { kind: 'error', message: parsed.message ?? 'Unknown error' };
159
+ }
160
+ catch {
161
+ return { kind: 'error', message: frame.data };
162
+ }
163
+ }
164
+ let parsed;
165
+ try {
166
+ parsed = JSON.parse(frame.data);
167
+ }
168
+ catch {
169
+ return null;
170
+ }
171
+ if (parsed && typeof parsed === 'object') {
172
+ if ('type' in parsed) {
173
+ return { kind: 'chunk', chunk: parsed };
174
+ }
175
+ if ('ok' in parsed) {
176
+ return { kind: 'stream_end', ok: Boolean(parsed.ok) };
177
+ }
178
+ }
179
+ return null;
180
+ }
@@ -0,0 +1,200 @@
1
+ import type { AgentForgeClient } from '../client';
2
+ import type { ConnectorDefaultsResult, ConnectorStatus, CreateClickupTaskInput, CreateClickupTaskResult, ListConnectorResourcesResult, ToolPermission } from '../types';
3
+ export declare class ConnectorsResource {
4
+ private readonly client;
5
+ constructor(client: AgentForgeClient);
6
+ /**
7
+ * `GET /connector-resources` — TODOS los connectors que el platform
8
+ * soporta, cada uno con su estado real de conexión para este tenant.
9
+ * Incluye los NO conectados para poder ofrecer "Conectar" inline.
10
+ *
11
+ * @example
12
+ * const { items } = await af.connectors.listConnected();
13
+ * const unconnected = items.filter((c) => !c.connected);
14
+ */
15
+ listConnected(): Promise<{
16
+ items: ConnectorStatus[];
17
+ }>;
18
+ /**
19
+ * `GET /connector-resources/:connectorId` — recursos reales del connector
20
+ * (canales de Slack, repos de GitHub, listas de ClickUp, archivos de
21
+ * Drive…). Si el connector está conectado pero no tiene selector
22
+ * implementado todavía, devuelve `{ items: [], unsupported: true }` en vez
23
+ * de tirar — no es un error, es un estado a mostrar en la UI.
24
+ */
25
+ listResources(connectorId: string): Promise<ListConnectorResourcesResult>;
26
+ /**
27
+ * `POST /connectors/oauth/:id/authorize-for-tenant` — arranca el OAuth
28
+ * authorize del connector para el tenant del ak_live_ (sin sesión de
29
+ * dashboard). Devuelve la URL a abrir (ej. `window.open` en el frontend) —
30
+ * el flujo de callback lo maneja el platform de punta a punta.
31
+ *
32
+ * @example
33
+ * const { url } = await af.connectors.authorizeForTenant('slack', {
34
+ * postAuthRedirect: 'https://myapp.com/settings',
35
+ * });
36
+ */
37
+ authorizeForTenant(connectorId: string, opts?: {
38
+ postAuthRedirect?: string;
39
+ }): Promise<{
40
+ url: string;
41
+ }>;
42
+ /**
43
+ * `DELETE /connector-resources/:connectorId` — desconecta el connector
44
+ * para el tenant del ak_live_, borrando la autorización guardada.
45
+ * Idempotente: llamarlo sobre un connector ya desconectado no falla.
46
+ * Tras esto, `listConnected()` refleja `connected: false` y el usuario
47
+ * puede volver a llamar `authorizeForTenant()` para reconectar desde cero
48
+ * (útil cuando el token quedó revocado del lado del proveedor o el
49
+ * usuario quiere cambiar de cuenta).
50
+ *
51
+ * @example
52
+ * await af.connectors.disconnect('google-drive');
53
+ */
54
+ disconnect(connectorId: string): Promise<{
55
+ ok: true;
56
+ }>;
57
+ /**
58
+ * `POST /connector-resources/clickup/tasks` — crea una tarea REAL en
59
+ * ClickUp para el tenant del ak_live_. Sin gobernanza propia: el caller
60
+ * (ej. el confirm de una propuesta "propose_clickup_task" en ScrappyDoc)
61
+ * es responsable de solo invocar esto tras una confirmación humana
62
+ * explícita.
63
+ *
64
+ * @example
65
+ * const task = await af.connectors.createClickupTask({
66
+ * listId: '901234',
67
+ * name: 'Revisar contrato Q3',
68
+ * });
69
+ */
70
+ createClickupTask(input: CreateClickupTaskInput): Promise<CreateClickupTaskResult>;
71
+ /**
72
+ * `GET /connector-resources/:connectorId/defaults` — reglas de tenant
73
+ * (permisos por tool: allow/approval/blocked) para este connector, más la
74
+ * lista de tools que el connector expone. Mismo mecanismo de 3 niveles que
75
+ * usa el dashboard de agentforge-platform (`ConnectorDefaultsService.resolve`):
76
+ * override del tenant → template del autor del connector → fallback allow-all.
77
+ */
78
+ getDefaults(connectorId: string): Promise<ConnectorDefaultsResult>;
79
+ /**
80
+ * `PUT /connector-resources/:connectorId/defaults` — sobrescribe las
81
+ * reglas de tenant para este connector. El caller es responsable de
82
+ * gatear quién puede llamar esto (ej. solo admin/owner del workspace) —
83
+ * el server valida cada nombre de tool contra el registry pero no impone
84
+ * ningún rol propio más allá de la ak_live_ tenant-wide.
85
+ */
86
+ updateDefaults(connectorId: string, permissions: ToolPermission[]): Promise<{
87
+ ok: true;
88
+ }>;
89
+ postSlackMessage(input: {
90
+ channelId: string;
91
+ text: string;
92
+ }): Promise<{
93
+ ok: true;
94
+ ts?: string;
95
+ permalink?: string;
96
+ }>;
97
+ /**
98
+ * `GET /connectors/oauth/:id/oauth-app` — indica si el tenant del
99
+ * ak_live_ ya trajo su propia OAuth app para `connectorId` (Slack,
100
+ * HubSpot, GitHub, Notion, o cualquier otro connector OAuth del
101
+ * catálogo — el server valida genéricamente contra el registry, no
102
+ * hay lista fija) en vez de depender de la app de instancia del
103
+ * platform. Nunca devuelve el secret, solo un hint del clientId para
104
+ * mostrar en UI.
105
+ *
106
+ * @example
107
+ * const { configured } = await af.connectors.getOAuthApp('slack');
108
+ */
109
+ getOAuthApp(connectorId: string): Promise<{
110
+ configured: boolean;
111
+ clientIdHint?: string;
112
+ updatedAt?: string;
113
+ }>;
114
+ /**
115
+ * `POST /connectors/oauth/:id/oauth-app` — registra la OAuth app propia
116
+ * del tenant para `connectorId` (cualquier connector OAuth del catálogo:
117
+ * Slack, HubSpot, GitHub, Notion, etc.), sin pasar por el dashboard de
118
+ * agentforge-platform. Útil cuando ScrappyDoc (u otro producto externo)
119
+ * ya tiene su propia app y no quiere compartir la app de instancia del
120
+ * platform con otros tenants/productos.
121
+ *
122
+ * Efecto inmediato: el próximo `authorizeForTenant(connectorId)` usa
123
+ * estas credenciales — no hace falta reiniciar nada del lado del platform.
124
+ *
125
+ * `hostApp` (opcional): además de guardar bajo el tenant del ak_live_
126
+ * actual, espeja estas credenciales bajo un scope compartido por TODO ese
127
+ * host app (ej. 'scrappydoc') — así cualquier tenant per-(workspace,user)
128
+ * que ese host provisione después vía `POST /platform/host-tenants`
129
+ * hereda automáticamente esta OAuth app, sin volver a configurarla por
130
+ * tenant. Solo tiene sentido pasarlo desde el tenant GLOBAL del host (el
131
+ * dueño del ak_live_ fijo, no un tenant per-usuario) — el server rechaza
132
+ * el reclamo si otro tenant ya se adjudicó ese hostApp antes.
133
+ *
134
+ * @example
135
+ * await af.connectors.setOAuthApp('slack', {
136
+ * clientId: '123456.789',
137
+ * clientSecret: 'abcdef...',
138
+ * hostApp: 'scrappydoc',
139
+ * });
140
+ */
141
+ setOAuthApp(connectorId: string, input: {
142
+ clientId: string;
143
+ clientSecret: string;
144
+ hostApp?: string;
145
+ }): Promise<{
146
+ configured: true;
147
+ }>;
148
+ /**
149
+ * `GET /connectors/inbox/agents/:agentId/inbox/channels` — canales del
150
+ * inbox omnicanal (WhatsApp, Instagram, Slack bot, etc.) ya conectados
151
+ * para `agentId`, sin sesión de dashboard de agentforge-platform (el
152
+ * `ak_live_` tenant-wide alcanza — el server valida que `agentId`
153
+ * pertenezca a este tenant).
154
+ *
155
+ * @example
156
+ * const { items } = await af.connectors.listInboxChannels(agentId);
157
+ */
158
+ listInboxChannels(agentId: string): Promise<{
159
+ items: InboxChannelView[];
160
+ }>;
161
+ /**
162
+ * `POST /connectors/inbox/agents/:agentId/inbox/channels` — conecta un
163
+ * canal del inbox omnicanal para `agentId` (ej. `providerId: 'slack-bot'`
164
+ * con `credentials: { signingSecret, botUserId? }` — ver
165
+ * `SlackBotProvider.credentialsForm` en agentforge-platform para el shape
166
+ * exacto de cada provider).
167
+ *
168
+ * @example
169
+ * await af.connectors.createInboxChannel(agentId, {
170
+ * providerId: 'slack-bot',
171
+ * credentials: { signingSecret: '8f742...' },
172
+ * });
173
+ */
174
+ createInboxChannel(agentId: string, input: {
175
+ providerId: string;
176
+ label?: string;
177
+ credentials: Record<string, unknown>;
178
+ }): Promise<InboxChannelView>;
179
+ /**
180
+ * `DELETE /connectors/inbox/agents/:agentId/inbox/channels/:channelId` —
181
+ * desconecta un canal del inbox omnicanal.
182
+ */
183
+ destroyInboxChannel(agentId: string, channelId: string): Promise<void>;
184
+ }
185
+ export interface InboxChannelView {
186
+ id: string;
187
+ tenantId: string;
188
+ agentId?: string;
189
+ providerId: string;
190
+ label?: string;
191
+ externalIdentity?: string;
192
+ status: string;
193
+ lastError?: string;
194
+ lastSeenAt?: string;
195
+ displayName?: string;
196
+ username?: string;
197
+ avatarUrl?: string;
198
+ createdAt: string;
199
+ updatedAt: string;
200
+ }
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ // connectors resource — selector de recursos estilo Gemini para apps
3
+ // externas (ver ResourcePickerController / HostConnectorsController en el
4
+ // server): lista los connectors del tenant + su estado, navega los recursos
5
+ // reales de uno conectado (canales de Slack, repos de GitHub, etc), y arranca
6
+ // el OAuth authorize para uno que falte conectar.
7
+ //
8
+ // Auth: SIEMPRE ak_live_ tenant-wide — estos 3 endpoints usan `ApiKeyGuard`
9
+ // en el server y rechazan ChatToken de plano, aunque el `AgentForgeClient`
10
+ // también tenga uno configurado (ver `requestWithApiKey`).
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ConnectorsResource = void 0;
13
+ class ConnectorsResource {
14
+ constructor(client) {
15
+ this.client = client;
16
+ }
17
+ /**
18
+ * `GET /connector-resources` — TODOS los connectors que el platform
19
+ * soporta, cada uno con su estado real de conexión para este tenant.
20
+ * Incluye los NO conectados para poder ofrecer "Conectar" inline.
21
+ *
22
+ * @example
23
+ * const { items } = await af.connectors.listConnected();
24
+ * const unconnected = items.filter((c) => !c.connected);
25
+ */
26
+ async listConnected() {
27
+ return this.client.requestWithApiKey('GET', '/connector-resources');
28
+ }
29
+ /**
30
+ * `GET /connector-resources/:connectorId` — recursos reales del connector
31
+ * (canales de Slack, repos de GitHub, listas de ClickUp, archivos de
32
+ * Drive…). Si el connector está conectado pero no tiene selector
33
+ * implementado todavía, devuelve `{ items: [], unsupported: true }` en vez
34
+ * de tirar — no es un error, es un estado a mostrar en la UI.
35
+ */
36
+ async listResources(connectorId) {
37
+ return this.client.requestWithApiKey('GET', `/connector-resources/${encodeURIComponent(connectorId)}`);
38
+ }
39
+ /**
40
+ * `POST /connectors/oauth/:id/authorize-for-tenant` — arranca el OAuth
41
+ * authorize del connector para el tenant del ak_live_ (sin sesión de
42
+ * dashboard). Devuelve la URL a abrir (ej. `window.open` en el frontend) —
43
+ * el flujo de callback lo maneja el platform de punta a punta.
44
+ *
45
+ * @example
46
+ * const { url } = await af.connectors.authorizeForTenant('slack', {
47
+ * postAuthRedirect: 'https://myapp.com/settings',
48
+ * });
49
+ */
50
+ async authorizeForTenant(connectorId, opts) {
51
+ const qs = opts?.postAuthRedirect
52
+ ? `?postAuthRedirect=${encodeURIComponent(opts.postAuthRedirect)}`
53
+ : '';
54
+ return this.client.requestWithApiKey('POST', `/connectors/oauth/${encodeURIComponent(connectorId)}/authorize-for-tenant${qs}`);
55
+ }
56
+ /**
57
+ * `DELETE /connector-resources/:connectorId` — desconecta el connector
58
+ * para el tenant del ak_live_, borrando la autorización guardada.
59
+ * Idempotente: llamarlo sobre un connector ya desconectado no falla.
60
+ * Tras esto, `listConnected()` refleja `connected: false` y el usuario
61
+ * puede volver a llamar `authorizeForTenant()` para reconectar desde cero
62
+ * (útil cuando el token quedó revocado del lado del proveedor o el
63
+ * usuario quiere cambiar de cuenta).
64
+ *
65
+ * @example
66
+ * await af.connectors.disconnect('google-drive');
67
+ */
68
+ async disconnect(connectorId) {
69
+ return this.client.requestWithApiKey('DELETE', `/connector-resources/${encodeURIComponent(connectorId)}`);
70
+ }
71
+ /**
72
+ * `POST /connector-resources/clickup/tasks` — crea una tarea REAL en
73
+ * ClickUp para el tenant del ak_live_. Sin gobernanza propia: el caller
74
+ * (ej. el confirm de una propuesta "propose_clickup_task" en ScrappyDoc)
75
+ * es responsable de solo invocar esto tras una confirmación humana
76
+ * explícita.
77
+ *
78
+ * @example
79
+ * const task = await af.connectors.createClickupTask({
80
+ * listId: '901234',
81
+ * name: 'Revisar contrato Q3',
82
+ * });
83
+ */
84
+ async createClickupTask(input) {
85
+ return this.client.requestWithApiKey('POST', '/connector-resources/clickup/tasks', input);
86
+ }
87
+ /**
88
+ * `GET /connector-resources/:connectorId/defaults` — reglas de tenant
89
+ * (permisos por tool: allow/approval/blocked) para este connector, más la
90
+ * lista de tools que el connector expone. Mismo mecanismo de 3 niveles que
91
+ * usa el dashboard de agentforge-platform (`ConnectorDefaultsService.resolve`):
92
+ * override del tenant → template del autor del connector → fallback allow-all.
93
+ */
94
+ async getDefaults(connectorId) {
95
+ return this.client.requestWithApiKey('GET', `/connector-resources/${encodeURIComponent(connectorId)}/defaults`);
96
+ }
97
+ /**
98
+ * `PUT /connector-resources/:connectorId/defaults` — sobrescribe las
99
+ * reglas de tenant para este connector. El caller es responsable de
100
+ * gatear quién puede llamar esto (ej. solo admin/owner del workspace) —
101
+ * el server valida cada nombre de tool contra el registry pero no impone
102
+ * ningún rol propio más allá de la ak_live_ tenant-wide.
103
+ */
104
+ async updateDefaults(connectorId, permissions) {
105
+ return this.client.requestWithApiKey('PUT', `/connector-resources/${encodeURIComponent(connectorId)}/defaults`, { permissions });
106
+ }
107
+ async postSlackMessage(input) {
108
+ return this.client.requestWithApiKey('POST', '/connector-resources/slack/messages', input);
109
+ }
110
+ /**
111
+ * `GET /connectors/oauth/:id/oauth-app` — indica si el tenant del
112
+ * ak_live_ ya trajo su propia OAuth app para `connectorId` (Slack,
113
+ * HubSpot, GitHub, Notion, o cualquier otro connector OAuth del
114
+ * catálogo — el server valida genéricamente contra el registry, no
115
+ * hay lista fija) en vez de depender de la app de instancia del
116
+ * platform. Nunca devuelve el secret, solo un hint del clientId para
117
+ * mostrar en UI.
118
+ *
119
+ * @example
120
+ * const { configured } = await af.connectors.getOAuthApp('slack');
121
+ */
122
+ async getOAuthApp(connectorId) {
123
+ return this.client.requestWithApiKey('GET', `/connectors/oauth/${encodeURIComponent(connectorId)}/oauth-app`);
124
+ }
125
+ /**
126
+ * `POST /connectors/oauth/:id/oauth-app` — registra la OAuth app propia
127
+ * del tenant para `connectorId` (cualquier connector OAuth del catálogo:
128
+ * Slack, HubSpot, GitHub, Notion, etc.), sin pasar por el dashboard de
129
+ * agentforge-platform. Útil cuando ScrappyDoc (u otro producto externo)
130
+ * ya tiene su propia app y no quiere compartir la app de instancia del
131
+ * platform con otros tenants/productos.
132
+ *
133
+ * Efecto inmediato: el próximo `authorizeForTenant(connectorId)` usa
134
+ * estas credenciales — no hace falta reiniciar nada del lado del platform.
135
+ *
136
+ * `hostApp` (opcional): además de guardar bajo el tenant del ak_live_
137
+ * actual, espeja estas credenciales bajo un scope compartido por TODO ese
138
+ * host app (ej. 'scrappydoc') — así cualquier tenant per-(workspace,user)
139
+ * que ese host provisione después vía `POST /platform/host-tenants`
140
+ * hereda automáticamente esta OAuth app, sin volver a configurarla por
141
+ * tenant. Solo tiene sentido pasarlo desde el tenant GLOBAL del host (el
142
+ * dueño del ak_live_ fijo, no un tenant per-usuario) — el server rechaza
143
+ * el reclamo si otro tenant ya se adjudicó ese hostApp antes.
144
+ *
145
+ * @example
146
+ * await af.connectors.setOAuthApp('slack', {
147
+ * clientId: '123456.789',
148
+ * clientSecret: 'abcdef...',
149
+ * hostApp: 'scrappydoc',
150
+ * });
151
+ */
152
+ async setOAuthApp(connectorId, input) {
153
+ return this.client.requestWithApiKey('POST', `/connectors/oauth/${encodeURIComponent(connectorId)}/oauth-app`, input);
154
+ }
155
+ /**
156
+ * `GET /connectors/inbox/agents/:agentId/inbox/channels` — canales del
157
+ * inbox omnicanal (WhatsApp, Instagram, Slack bot, etc.) ya conectados
158
+ * para `agentId`, sin sesión de dashboard de agentforge-platform (el
159
+ * `ak_live_` tenant-wide alcanza — el server valida que `agentId`
160
+ * pertenezca a este tenant).
161
+ *
162
+ * @example
163
+ * const { items } = await af.connectors.listInboxChannels(agentId);
164
+ */
165
+ async listInboxChannels(agentId) {
166
+ return this.client.requestWithApiKey('GET', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/channels`);
167
+ }
168
+ /**
169
+ * `POST /connectors/inbox/agents/:agentId/inbox/channels` — conecta un
170
+ * canal del inbox omnicanal para `agentId` (ej. `providerId: 'slack-bot'`
171
+ * con `credentials: { signingSecret, botUserId? }` — ver
172
+ * `SlackBotProvider.credentialsForm` en agentforge-platform para el shape
173
+ * exacto de cada provider).
174
+ *
175
+ * @example
176
+ * await af.connectors.createInboxChannel(agentId, {
177
+ * providerId: 'slack-bot',
178
+ * credentials: { signingSecret: '8f742...' },
179
+ * });
180
+ */
181
+ async createInboxChannel(agentId, input) {
182
+ return this.client.requestWithApiKey('POST', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/channels`, input);
183
+ }
184
+ /**
185
+ * `DELETE /connectors/inbox/agents/:agentId/inbox/channels/:channelId` —
186
+ * desconecta un canal del inbox omnicanal.
187
+ */
188
+ async destroyInboxChannel(agentId, channelId) {
189
+ await this.client.requestWithApiKey('DELETE', `/connectors/inbox/agents/${encodeURIComponent(agentId)}/inbox/channels/${encodeURIComponent(channelId)}`);
190
+ }
191
+ }
192
+ exports.ConnectorsResource = ConnectorsResource;
@@ -0,0 +1,44 @@
1
+ import type { AgentForgeClient } from '../client';
2
+ import type { TenantSecretEntry } from '../types';
3
+ export declare class SecretsResource {
4
+ private readonly client;
5
+ constructor(client: AgentForgeClient);
6
+ /**
7
+ * `GET /tenant-secrets` — metadata de TODOS los secrets company-scoped que
8
+ * este tenant ha configurado. Nunca devuelve el valor en claro, solo
9
+ * `valueHint` (últimos 4 chars) y flags — suficiente para renderizar una
10
+ * lista "configurado / no configurado" en la UI de la app hija.
11
+ *
12
+ * @example
13
+ * const { items } = await af.secrets.list();
14
+ * const configured = items.filter((s) => s.configured);
15
+ */
16
+ list(): Promise<{
17
+ items: TenantSecretEntry[];
18
+ }>;
19
+ /**
20
+ * `PUT /tenant-secrets/:key` — crea o sobrescribe UN secret company-scoped
21
+ * para este tenant. El valor viaja cifrado al vault del platform; la
22
+ * respuesta trae solo metadata (nunca el valor en claro). La `key` debe
23
+ * matchear `/^[A-Z][A-Z0-9_]*$/` (mayúsculas, dígitos, guión bajo; empieza
24
+ * con letra) — el server responde 400 si no.
25
+ *
26
+ * El caller es responsable de gatear quién puede llamar esto (ej. solo
27
+ * admin/owner del workspace) — el server no impone rol propio más allá de
28
+ * la ak_live_ tenant-wide.
29
+ *
30
+ * @example
31
+ * await af.secrets.set('OPENAI_API_KEY', 'sk-...');
32
+ */
33
+ set(key: string, value: string): Promise<TenantSecretEntry>;
34
+ /**
35
+ * `DELETE /tenant-secrets/:key` — borra el override company-scoped de este
36
+ * tenant para `key`. No toca el valor 'instance' de la plataforma (si lo
37
+ * hubiera): tras esto, la resolución en runtime cae al fallback de
38
+ * plataforma. Devuelve 204 (sin body).
39
+ *
40
+ * @example
41
+ * await af.secrets.remove('OPENAI_API_KEY');
42
+ */
43
+ remove(key: string): Promise<void>;
44
+ }