@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.
- package/dist/client.d.ts +72 -0
- package/dist/client.js +153 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +36 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +53 -0
- package/dist/resources/agents.d.ts +88 -0
- package/dist/resources/agents.js +119 -0
- package/dist/resources/chat.d.ts +55 -0
- package/dist/resources/chat.js +180 -0
- package/dist/resources/connectors.d.ts +200 -0
- package/dist/resources/connectors.js +192 -0
- package/dist/resources/secrets.d.ts +44 -0
- package/dist/resources/secrets.js +71 -0
- package/dist/resources/tools.d.ts +58 -0
- package/dist/resources/tools.js +72 -0
- package/dist/types.d.ts +355 -0
- package/dist/types.js +8 -0
- package/dist/webhooks.d.ts +22 -0
- package/dist/webhooks.js +90 -0
- package/package.json +39 -0
|
@@ -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
|
+
}
|