@agentforge-io/core 2.0.17 → 2.0.19
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/domain/connector.d.ts +31 -0
- package/dist/domain/conversation.d.ts +14 -0
- package/dist/services/agent.service.js +68 -12
- package/dist/services/conversation.service.d.ts +3 -0
- package/dist/services/conversation.service.js +2 -0
- package/dist/types/agent.types.d.ts +25 -0
- package/package.json +1 -1
|
@@ -28,6 +28,24 @@ export interface ConnectorToolFactory {
|
|
|
28
28
|
definition: Omit<AgentToolDefinition, 'execute'>;
|
|
29
29
|
build: (ctx: ConnectorToolContext) => AgentToolDefinition['execute'];
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Recommended initial state for a single connector tool when the tenant
|
|
33
|
+
* has no override yet.
|
|
34
|
+
*
|
|
35
|
+
* Same shape as the platform's `ToolPermission` (which is the canonical
|
|
36
|
+
* type stored on `agent.tools` / `automation.tools` / `skill.tools`
|
|
37
|
+
* JSONB). We redeclare it here so `@agentforge-io/core` stays free of any
|
|
38
|
+
* dependency on the host's `platform/` module — connectors are core's
|
|
39
|
+
* concern, ToolPermission is the host's. The shape must stay in sync;
|
|
40
|
+
* platform-side `hydrateToolList` accepts either.
|
|
41
|
+
*/
|
|
42
|
+
export type ConnectorToolMode = 'allow' | 'approval' | 'blocked';
|
|
43
|
+
export interface ConnectorToolDefault {
|
|
44
|
+
/** Tool name as it appears in the connector's `tools[]` factory. */
|
|
45
|
+
name: string;
|
|
46
|
+
/** What the runtime should do by default when the LLM invokes this. */
|
|
47
|
+
mode: ConnectorToolMode;
|
|
48
|
+
}
|
|
31
49
|
/**
|
|
32
50
|
* UI hints for connectors that authenticate via a user-pasted API key
|
|
33
51
|
* instead of the OAuth2 dance (Granola — and any future provider whose
|
|
@@ -86,4 +104,17 @@ export interface ConnectorDefinition {
|
|
|
86
104
|
/** Tools this connector contributes to the agent's toolbelt once a user
|
|
87
105
|
* has authorized. */
|
|
88
106
|
tools: ConnectorToolFactory[];
|
|
107
|
+
/**
|
|
108
|
+
* Connector-author's recommended initial mode for each tool. The
|
|
109
|
+
* platform uses this as the fallback when the tenant has not saved a
|
|
110
|
+
* per-tool override yet (no row in `af_connector_tool_defaults`). It
|
|
111
|
+
* also seeds the `/connectors/:id` admin page on first visit.
|
|
112
|
+
*
|
|
113
|
+
* Tools not listed here are assumed `allow`. Tools listed that don't
|
|
114
|
+
* match any `tools[i].definition.name` are ignored by the resolver.
|
|
115
|
+
*
|
|
116
|
+
* Optional. When omitted, every tool defaults to `allow` (matches the
|
|
117
|
+
* historical whitelist semantics).
|
|
118
|
+
*/
|
|
119
|
+
defaultToolPermissions?: ConnectorToolDefault[];
|
|
89
120
|
}
|
|
@@ -20,6 +20,20 @@ export interface Message {
|
|
|
20
20
|
content: string;
|
|
21
21
|
toolCalls?: ToolCallRecord[];
|
|
22
22
|
usage?: TokenUsage;
|
|
23
|
+
/**
|
|
24
|
+
* Free-form metadata the host can attach to a message. The runtime is
|
|
25
|
+
* agnostic about its contents — it persists and surfaces it back. Used
|
|
26
|
+
* today by:
|
|
27
|
+
*
|
|
28
|
+
* - `tool_approvals`: synthetic assistant message carries
|
|
29
|
+
* `{ kind: 'awaiting_approval', approvalId, toolName, expiresAt }`
|
|
30
|
+
* so the chat client can render an Approve/Deny card inline
|
|
31
|
+
* instead of plain text.
|
|
32
|
+
*
|
|
33
|
+
* Add new kinds without a schema migration — the field is JSONB in
|
|
34
|
+
* the persistence adapter.
|
|
35
|
+
*/
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
23
37
|
createdAt: Date;
|
|
24
38
|
}
|
|
25
39
|
export type NewConversation = Pick<Conversation, 'userId' | 'agentId'> & Partial<Omit<Conversation, 'id' | 'createdAt' | 'updatedAt'>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentService = exports.AgentForbiddenError = void 0;
|
|
4
|
+
const tool_approval_gate_1 = require("./tool-approval-gate");
|
|
4
5
|
class AgentForbiddenError extends Error {
|
|
5
6
|
constructor(reason) {
|
|
6
7
|
super(`Usage limit exceeded: ${reason}`);
|
|
@@ -240,18 +241,73 @@ class AgentService {
|
|
|
240
241
|
const resolvedExtras = await this.resolveExtraTools(agent.connectorOwnerUserId ?? params.userId);
|
|
241
242
|
const filter = params.overrides?.extraToolsFilter;
|
|
242
243
|
const extraTools = filter && resolvedExtras ? filter(resolvedExtras) : resolvedExtras;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
244
|
+
try {
|
|
245
|
+
for await (const chunk of this.runner.stream(agent, messages, {
|
|
246
|
+
userId: params.userId,
|
|
247
|
+
conversationId: params.conversationId,
|
|
248
|
+
agentId: conv.agentId,
|
|
249
|
+
messageId: 'streaming',
|
|
250
|
+
agent: { timezone: agent.timezone },
|
|
251
|
+
}, { ...(params.overrides ?? {}), extraTools })) {
|
|
252
|
+
if (chunk.type === 'text_delta')
|
|
253
|
+
fullContent += chunk.delta;
|
|
254
|
+
if (chunk.type === 'usage')
|
|
255
|
+
finalUsage = chunk.usage;
|
|
256
|
+
yield chunk;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
// Tool-approval gate decisions surface as typed exceptions. We
|
|
261
|
+
// intercept them HERE so the SSE consumer sees a structured
|
|
262
|
+
// chunk instead of a torn-down generator. Each branch also
|
|
263
|
+
// persists a synthetic assistant message carrying the metadata
|
|
264
|
+
// the chat client needs to re-render the card on reload — the
|
|
265
|
+
// stream may close, but the conversation history doesn't lose
|
|
266
|
+
// context.
|
|
267
|
+
if ((0, tool_approval_gate_1.isToolApprovalRequired)(err)) {
|
|
268
|
+
yield {
|
|
269
|
+
type: 'awaiting_approval',
|
|
270
|
+
approvalId: err.approvalId,
|
|
271
|
+
toolName: err.toolName,
|
|
272
|
+
expiresAt: err.expiresAt,
|
|
273
|
+
};
|
|
274
|
+
await this.conversations.addMessage({
|
|
275
|
+
conversationId: params.conversationId,
|
|
276
|
+
userId: params.userId,
|
|
277
|
+
role: 'assistant',
|
|
278
|
+
// Plain-text body so legacy clients (or a server reload of
|
|
279
|
+
// history into a non-aware client) still get a readable
|
|
280
|
+
// line. The card lives in `metadata`.
|
|
281
|
+
content: `(waiting for approval to run \`${err.toolName}\`)`,
|
|
282
|
+
metadata: {
|
|
283
|
+
kind: 'awaiting_approval',
|
|
284
|
+
approvalId: err.approvalId,
|
|
285
|
+
toolName: err.toolName,
|
|
286
|
+
expiresAt: err.expiresAt,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if ((0, tool_approval_gate_1.isToolBlockedError)(err)) {
|
|
292
|
+
yield {
|
|
293
|
+
type: 'tool_blocked',
|
|
294
|
+
toolName: err.toolName,
|
|
295
|
+
reason: err.reason,
|
|
296
|
+
};
|
|
297
|
+
await this.conversations.addMessage({
|
|
298
|
+
conversationId: params.conversationId,
|
|
299
|
+
userId: params.userId,
|
|
300
|
+
role: 'assistant',
|
|
301
|
+
content: err.reason ?? `Tool "${err.toolName}" is blocked.`,
|
|
302
|
+
metadata: {
|
|
303
|
+
kind: 'tool_blocked',
|
|
304
|
+
toolName: err.toolName,
|
|
305
|
+
reason: err.reason,
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
throw err;
|
|
255
311
|
}
|
|
256
312
|
if (fullContent) {
|
|
257
313
|
await this.conversations.addMessage({
|
|
@@ -23,6 +23,9 @@ export declare class ConversationService {
|
|
|
23
23
|
content: string;
|
|
24
24
|
toolCalls?: ToolCallRecord[];
|
|
25
25
|
usage?: TokenUsage;
|
|
26
|
+
/** Optional structured payload. See `Message.metadata` in
|
|
27
|
+
* `domain/conversation.ts` for usage. */
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
26
29
|
}): Promise<Message>;
|
|
27
30
|
/** Throws on not-found OR cross-user access. */
|
|
28
31
|
private loadOwned;
|
|
@@ -34,6 +34,7 @@ class ConversationService {
|
|
|
34
34
|
content: params.content,
|
|
35
35
|
toolCalls: params.toolCalls,
|
|
36
36
|
usage: params.usage,
|
|
37
|
+
metadata: params.metadata,
|
|
37
38
|
});
|
|
38
39
|
await this.convRepo.updateStats(params.conversationId, {
|
|
39
40
|
addInputTokens: params.usage?.inputTokens,
|
|
@@ -94,6 +95,7 @@ class ConversationService {
|
|
|
94
95
|
content: msg.content,
|
|
95
96
|
toolCalls: msg.toolCalls,
|
|
96
97
|
usage: msg.usage,
|
|
98
|
+
metadata: msg.metadata,
|
|
97
99
|
createdAt: msg.createdAt,
|
|
98
100
|
};
|
|
99
101
|
}
|
|
@@ -173,6 +173,27 @@ export type StreamChunk = {
|
|
|
173
173
|
} | {
|
|
174
174
|
type: 'usage';
|
|
175
175
|
usage: TokenUsage;
|
|
176
|
+
} | {
|
|
177
|
+
/**
|
|
178
|
+
* Emitted by the runtime when a tool dispatch hit
|
|
179
|
+
* `ToolApprovalGate.check() → { kind: 'approval' }`. The stream
|
|
180
|
+
* ends right after this chunk — the runner is paused until the
|
|
181
|
+
* approval is decided. The chat client renders an Approve/Deny
|
|
182
|
+
* card based on the carried fields; after Approve the client
|
|
183
|
+
* triggers a fresh `sendMessage("continue")` and the gate's
|
|
184
|
+
* fast-path consumes the pending row.
|
|
185
|
+
*/
|
|
186
|
+
type: 'awaiting_approval';
|
|
187
|
+
approvalId: string;
|
|
188
|
+
toolName: string;
|
|
189
|
+
expiresAt: string;
|
|
190
|
+
} | {
|
|
191
|
+
/** Emitted when a tool dispatch hit `kind: 'blocked'`. Mirrors the
|
|
192
|
+
* shape above so the client renders a terminal-error card with
|
|
193
|
+
* the same component. */
|
|
194
|
+
type: 'tool_blocked';
|
|
195
|
+
toolName: string;
|
|
196
|
+
reason?: string;
|
|
176
197
|
} | {
|
|
177
198
|
type: 'done';
|
|
178
199
|
messageId: string;
|
|
@@ -207,5 +228,9 @@ export interface MessageRecord {
|
|
|
207
228
|
content: string;
|
|
208
229
|
toolCalls?: ToolCallRecord[];
|
|
209
230
|
usage?: TokenUsage;
|
|
231
|
+
/** Structured payload mirror of `Message.metadata`. Same kinds, same
|
|
232
|
+
* back-compat semantics. Lets the chat history endpoint pass the
|
|
233
|
+
* approval-card / blocked-tool markers through to the client. */
|
|
234
|
+
metadata?: Record<string, unknown>;
|
|
210
235
|
createdAt: Date;
|
|
211
236
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.19",
|
|
4
4
|
"description": "Framework-free AI runtime SDK. Owns: agent loop (Anthropic), conversations, tools, streaming, agent-job queue, SdkHooks. Identity, billing, infra (email/uploads/secrets) live in the host's modules — not here.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|