@agentforge-io/core 2.0.20 → 2.0.21
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/services/agent.service.d.ts +12 -1
- package/dist/services/agent.service.js +52 -10
- package/dist/services/approval-copywriter.service.d.ts +57 -0
- package/dist/services/approval-copywriter.service.js +176 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +3 -1
- package/dist/types/agent.types.d.ts +21 -11
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import type { SdkHooks } from '../types/hooks';
|
|
|
4
4
|
import type { AgentRunnerService } from './agent-runner.service';
|
|
5
5
|
import type { ConversationService } from './conversation.service';
|
|
6
6
|
import type { ConnectorRegistryService } from './connector-registry.service';
|
|
7
|
+
import type { ApprovalCopywriterService } from './approval-copywriter.service';
|
|
7
8
|
/**
|
|
8
9
|
* Minimal record the host's agent-config layer must supply to resolve
|
|
9
10
|
* agents by `(tenantId, slug)` or by id. The SDK doesn't care where this
|
|
@@ -94,6 +95,11 @@ export declare class AgentService {
|
|
|
94
95
|
* authenticated user's connector tools (Gmail, Drive, …) on the fly
|
|
95
96
|
* via `overrides.extraTools`. Optional — connectors are opt-in. */
|
|
96
97
|
private readonly connectorRegistry?;
|
|
98
|
+
/** When wired, the AgentService asks this service to generate the
|
|
99
|
+
* microcopy shown in the in-chat approval bubble. Falls back to a
|
|
100
|
+
* meta-only render on the client when unwired or when generation
|
|
101
|
+
* fails. */
|
|
102
|
+
private readonly copywriter?;
|
|
97
103
|
constructor(agents: AgentDefinition[], runner: AgentRunnerService, conversations: ConversationService,
|
|
98
104
|
/** When wired, agents created via the admin UI are looked up here first;
|
|
99
105
|
* the hardcoded `agents` array remains a fallback for legacy installs. */
|
|
@@ -104,7 +110,12 @@ export declare class AgentService {
|
|
|
104
110
|
/** When wired, every streamMessage / sendMessage call attaches the
|
|
105
111
|
* authenticated user's connector tools (Gmail, Drive, …) on the fly
|
|
106
112
|
* via `overrides.extraTools`. Optional — connectors are opt-in. */
|
|
107
|
-
connectorRegistry?: ConnectorRegistryService | undefined
|
|
113
|
+
connectorRegistry?: ConnectorRegistryService | undefined,
|
|
114
|
+
/** When wired, the AgentService asks this service to generate the
|
|
115
|
+
* microcopy shown in the in-chat approval bubble. Falls back to a
|
|
116
|
+
* meta-only render on the client when unwired or when generation
|
|
117
|
+
* fails. */
|
|
118
|
+
copywriter?: ApprovalCopywriterService | undefined);
|
|
108
119
|
/**
|
|
109
120
|
* Look up the human-friendly connector name + tool description for a
|
|
110
121
|
* given tool slug. Powers the friendly copy in `awaiting_approval` /
|
|
@@ -21,13 +21,19 @@ class AgentService {
|
|
|
21
21
|
/** When wired, every streamMessage / sendMessage call attaches the
|
|
22
22
|
* authenticated user's connector tools (Gmail, Drive, …) on the fly
|
|
23
23
|
* via `overrides.extraTools`. Optional — connectors are opt-in. */
|
|
24
|
-
connectorRegistry
|
|
24
|
+
connectorRegistry,
|
|
25
|
+
/** When wired, the AgentService asks this service to generate the
|
|
26
|
+
* microcopy shown in the in-chat approval bubble. Falls back to a
|
|
27
|
+
* meta-only render on the client when unwired or when generation
|
|
28
|
+
* fails. */
|
|
29
|
+
copywriter) {
|
|
25
30
|
this.agents = agents;
|
|
26
31
|
this.runner = runner;
|
|
27
32
|
this.conversations = conversations;
|
|
28
33
|
this.resolver = resolver;
|
|
29
34
|
this.hooks = hooks;
|
|
30
35
|
this.connectorRegistry = connectorRegistry;
|
|
36
|
+
this.copywriter = copywriter;
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Look up the human-friendly connector name + tool description for a
|
|
@@ -298,6 +304,14 @@ class AgentService {
|
|
|
298
304
|
// context.
|
|
299
305
|
if ((0, tool_approval_gate_1.isToolApprovalRequired)(err)) {
|
|
300
306
|
const ctx = this.describeTool(err.toolName);
|
|
307
|
+
const copy = await this.copywriter?.generate({
|
|
308
|
+
kind: 'approval',
|
|
309
|
+
toolName: err.toolName,
|
|
310
|
+
connectorName: ctx?.connectorName,
|
|
311
|
+
toolDescription: ctx?.toolDescription,
|
|
312
|
+
recentMessages: extractRecentForCopywriter(messages),
|
|
313
|
+
agentPersona: agent.systemPrompt,
|
|
314
|
+
});
|
|
301
315
|
yield {
|
|
302
316
|
type: 'awaiting_approval',
|
|
303
317
|
approvalId: err.approvalId,
|
|
@@ -305,17 +319,19 @@ class AgentService {
|
|
|
305
319
|
expiresAt: err.expiresAt,
|
|
306
320
|
connectorName: ctx?.connectorName,
|
|
307
321
|
toolDescription: ctx?.toolDescription,
|
|
322
|
+
copy,
|
|
308
323
|
};
|
|
309
324
|
await this.conversations.addMessage({
|
|
310
325
|
conversationId: params.conversationId,
|
|
311
326
|
userId: params.userId,
|
|
312
327
|
role: 'assistant',
|
|
313
|
-
// Plain-text fallback
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
content:
|
|
317
|
-
|
|
318
|
-
|
|
328
|
+
// Plain-text fallback for legacy clients that don't render
|
|
329
|
+
// `metadata.kind`. Capable widgets read the structured card
|
|
330
|
+
// from `metadata.copy`.
|
|
331
|
+
content: copy?.body ??
|
|
332
|
+
(ctx?.connectorName
|
|
333
|
+
? `Necesito tu permiso para usar ${ctx.connectorName}.`
|
|
334
|
+
: `Necesito tu permiso para continuar.`),
|
|
319
335
|
metadata: {
|
|
320
336
|
kind: 'awaiting_approval',
|
|
321
337
|
approvalId: err.approvalId,
|
|
@@ -323,32 +339,44 @@ class AgentService {
|
|
|
323
339
|
expiresAt: err.expiresAt,
|
|
324
340
|
connectorName: ctx?.connectorName,
|
|
325
341
|
toolDescription: ctx?.toolDescription,
|
|
342
|
+
copy,
|
|
326
343
|
},
|
|
327
344
|
});
|
|
328
345
|
return;
|
|
329
346
|
}
|
|
330
347
|
if ((0, tool_approval_gate_1.isToolBlockedError)(err)) {
|
|
331
348
|
const ctx = this.describeTool(err.toolName);
|
|
349
|
+
const copy = await this.copywriter?.generate({
|
|
350
|
+
kind: 'blocked',
|
|
351
|
+
toolName: err.toolName,
|
|
352
|
+
connectorName: ctx?.connectorName,
|
|
353
|
+
toolDescription: ctx?.toolDescription,
|
|
354
|
+
recentMessages: extractRecentForCopywriter(messages),
|
|
355
|
+
agentPersona: agent.systemPrompt,
|
|
356
|
+
});
|
|
332
357
|
yield {
|
|
333
358
|
type: 'tool_blocked',
|
|
334
359
|
toolName: err.toolName,
|
|
335
360
|
reason: err.reason,
|
|
336
361
|
connectorName: ctx?.connectorName,
|
|
337
362
|
toolDescription: ctx?.toolDescription,
|
|
363
|
+
copy,
|
|
338
364
|
};
|
|
339
365
|
await this.conversations.addMessage({
|
|
340
366
|
conversationId: params.conversationId,
|
|
341
367
|
userId: params.userId,
|
|
342
368
|
role: 'assistant',
|
|
343
|
-
content:
|
|
344
|
-
|
|
345
|
-
|
|
369
|
+
content: copy?.blockedBody ??
|
|
370
|
+
(ctx?.connectorName
|
|
371
|
+
? `No puedo usar ${ctx.connectorName} en esta cuenta.`
|
|
372
|
+
: `No puedo usar esa herramienta en esta cuenta.`),
|
|
346
373
|
metadata: {
|
|
347
374
|
kind: 'tool_blocked',
|
|
348
375
|
toolName: err.toolName,
|
|
349
376
|
reason: err.reason,
|
|
350
377
|
connectorName: ctx?.connectorName,
|
|
351
378
|
toolDescription: ctx?.toolDescription,
|
|
379
|
+
copy,
|
|
352
380
|
},
|
|
353
381
|
});
|
|
354
382
|
return;
|
|
@@ -429,6 +457,20 @@ class AgentService {
|
|
|
429
457
|
}
|
|
430
458
|
}
|
|
431
459
|
exports.AgentService = AgentService;
|
|
460
|
+
/**
|
|
461
|
+
* Pull the last few text-only turns out of the Anthropic message array
|
|
462
|
+
* for the approval copywriter. We strip tool blocks because they are
|
|
463
|
+
* not useful for language/tone detection and they can be long. Returns
|
|
464
|
+
* the most recent 6 turns, each capped to ~600 chars.
|
|
465
|
+
*/
|
|
466
|
+
function extractRecentForCopywriter(messages) {
|
|
467
|
+
const trimmed = messages
|
|
468
|
+
.map((m) => ({ role: m.role, content: m.content?.trim?.() ?? '' }))
|
|
469
|
+
.filter((m) => m.content.length > 0)
|
|
470
|
+
.slice(-6)
|
|
471
|
+
.map((m) => ({ role: m.role, content: m.content.slice(0, 600) }));
|
|
472
|
+
return trimmed;
|
|
473
|
+
}
|
|
432
474
|
/**
|
|
433
475
|
* Map a persisted `AgentRecord` to the runtime `AgentDefinition` the runner
|
|
434
476
|
* expects. The `context` column (plain-text knowledge) is prepended to the
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface ApprovalCopyBundle {
|
|
2
|
+
title: string;
|
|
3
|
+
body: string;
|
|
4
|
+
approveLabel: string;
|
|
5
|
+
approveBusyLabel: string;
|
|
6
|
+
denyLabel: string;
|
|
7
|
+
denyBusyLabel: string;
|
|
8
|
+
approvedPill: string;
|
|
9
|
+
deniedPill: string;
|
|
10
|
+
readOnlyHint: string;
|
|
11
|
+
blockedTitle: string;
|
|
12
|
+
blockedBody: string;
|
|
13
|
+
/** Formatter hint for "Expires in Xm Ys". The SDK fills the time. */
|
|
14
|
+
expiresPrefix: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ApprovalCopywriterInput {
|
|
17
|
+
toolName: string;
|
|
18
|
+
connectorName?: string;
|
|
19
|
+
toolDescription?: string;
|
|
20
|
+
kind: 'approval' | 'blocked';
|
|
21
|
+
/** Last few turns of the conversation. The model uses these to pick
|
|
22
|
+
* language, tone and any references the user already established. */
|
|
23
|
+
recentMessages?: {
|
|
24
|
+
role: 'user' | 'assistant';
|
|
25
|
+
content: string;
|
|
26
|
+
}[];
|
|
27
|
+
/** Optional agent persona/instructions excerpt so the copy keeps the
|
|
28
|
+
* agent's voice. Trimmed to a few hundred chars by the caller. */
|
|
29
|
+
agentPersona?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generates a natural-language approval copy bundle for the bubble the
|
|
33
|
+
* chat widget renders when the tool-approval gate pauses a run.
|
|
34
|
+
*
|
|
35
|
+
* Why a dedicated service: the SDK used to ship a hardcoded English (then
|
|
36
|
+
* Spanish) string set. That doesn't scale — different tenants speak
|
|
37
|
+
* different languages, and the bubble should sound like the rest of the
|
|
38
|
+
* agent's voice, not a generic system prompt. Delegating copy to the
|
|
39
|
+
* model that's already shaping the conversation keeps tone, locale and
|
|
40
|
+
* register coherent without dictionaries to maintain.
|
|
41
|
+
*
|
|
42
|
+
* The actual model call is Haiku (cheap + ~200ms) with a tight token cap.
|
|
43
|
+
* If anything goes wrong — timeout, parse error, missing API key — the
|
|
44
|
+
* caller gets `undefined` and falls back to a meta-only render. The gate
|
|
45
|
+
* MUST keep working when the copywriter doesn't.
|
|
46
|
+
*/
|
|
47
|
+
export declare class ApprovalCopywriterService {
|
|
48
|
+
private readonly client;
|
|
49
|
+
private readonly cache;
|
|
50
|
+
constructor(opts: {
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
baseURL?: string;
|
|
53
|
+
});
|
|
54
|
+
generate(input: ApprovalCopywriterInput): Promise<ApprovalCopyBundle | undefined>;
|
|
55
|
+
private cacheKey;
|
|
56
|
+
private callModel;
|
|
57
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ApprovalCopywriterService = void 0;
|
|
7
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
10
|
+
const HAIKU_MODEL = 'claude-haiku-4-5-20251001';
|
|
11
|
+
const HAIKU_MAX_TOKENS = 400;
|
|
12
|
+
const HAIKU_TIMEOUT_MS = 2_500;
|
|
13
|
+
/**
|
|
14
|
+
* Generates a natural-language approval copy bundle for the bubble the
|
|
15
|
+
* chat widget renders when the tool-approval gate pauses a run.
|
|
16
|
+
*
|
|
17
|
+
* Why a dedicated service: the SDK used to ship a hardcoded English (then
|
|
18
|
+
* Spanish) string set. That doesn't scale — different tenants speak
|
|
19
|
+
* different languages, and the bubble should sound like the rest of the
|
|
20
|
+
* agent's voice, not a generic system prompt. Delegating copy to the
|
|
21
|
+
* model that's already shaping the conversation keeps tone, locale and
|
|
22
|
+
* register coherent without dictionaries to maintain.
|
|
23
|
+
*
|
|
24
|
+
* The actual model call is Haiku (cheap + ~200ms) with a tight token cap.
|
|
25
|
+
* If anything goes wrong — timeout, parse error, missing API key — the
|
|
26
|
+
* caller gets `undefined` and falls back to a meta-only render. The gate
|
|
27
|
+
* MUST keep working when the copywriter doesn't.
|
|
28
|
+
*/
|
|
29
|
+
class ApprovalCopywriterService {
|
|
30
|
+
constructor(opts) {
|
|
31
|
+
this.cache = new Map();
|
|
32
|
+
if (!opts.apiKey) {
|
|
33
|
+
this.client = undefined;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.client = new sdk_1.default({
|
|
37
|
+
apiKey: opts.apiKey,
|
|
38
|
+
baseURL: opts.baseURL,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async generate(input) {
|
|
42
|
+
if (!this.client)
|
|
43
|
+
return undefined;
|
|
44
|
+
const key = this.cacheKey(input);
|
|
45
|
+
const hit = this.cache.get(key);
|
|
46
|
+
if (hit && hit.expiresAt > Date.now())
|
|
47
|
+
return hit.value;
|
|
48
|
+
try {
|
|
49
|
+
const value = await this.callModel(input);
|
|
50
|
+
if (!value)
|
|
51
|
+
return undefined;
|
|
52
|
+
this.cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
cacheKey(input) {
|
|
60
|
+
const recent = (input.recentMessages ?? [])
|
|
61
|
+
.slice(-3)
|
|
62
|
+
.map((m) => `${m.role[0]}:${m.content.slice(0, 120)}`)
|
|
63
|
+
.join('|');
|
|
64
|
+
const raw = [
|
|
65
|
+
input.kind,
|
|
66
|
+
input.toolName,
|
|
67
|
+
input.connectorName ?? '',
|
|
68
|
+
input.toolDescription ?? '',
|
|
69
|
+
input.agentPersona?.slice(0, 200) ?? '',
|
|
70
|
+
recent,
|
|
71
|
+
].join('');
|
|
72
|
+
return (0, crypto_1.createHash)('sha1').update(raw).digest('hex');
|
|
73
|
+
}
|
|
74
|
+
async callModel(input) {
|
|
75
|
+
const recent = (input.recentMessages ?? [])
|
|
76
|
+
.slice(-6)
|
|
77
|
+
.map((m) => `${m.role.toUpperCase()}: ${m.content.slice(0, 600)}`)
|
|
78
|
+
.join('\n');
|
|
79
|
+
const targetLabel = input.connectorName ?? input.toolName;
|
|
80
|
+
const whatItDoes = input.toolDescription
|
|
81
|
+
? `It does: ${input.toolDescription}`
|
|
82
|
+
: '';
|
|
83
|
+
const personaLine = input.agentPersona
|
|
84
|
+
? `Agent persona: ${input.agentPersona.slice(0, 400)}`
|
|
85
|
+
: '';
|
|
86
|
+
const guidance = input.kind === 'approval'
|
|
87
|
+
? `The agent paused because it wants to use the "${targetLabel}" capability and needs the user's permission. Write a short, warm bubble that matches the language, tone and register of the conversation so far. Mention "${targetLabel}" naturally. Keep it conversational — not corporate or robotic.`
|
|
88
|
+
: `The agent tried to use the "${targetLabel}" capability but it is blocked on this account. Write a short bubble that tells the user clearly, in the language and tone of the conversation, that this action isn't available. Do not blame them. Do not invite retrying.`;
|
|
89
|
+
const system = [
|
|
90
|
+
'You write microcopy for an in-chat permission card. Output STRICT JSON, no prose, no code fences.',
|
|
91
|
+
'Match the language of the most recent USER message. If the conversation is empty, default to English.',
|
|
92
|
+
'Keep all strings under ~80 chars unless noted. Buttons under ~20 chars.',
|
|
93
|
+
'No emojis. No markdown.',
|
|
94
|
+
].join(' ');
|
|
95
|
+
const userPrompt = [
|
|
96
|
+
guidance,
|
|
97
|
+
whatItDoes,
|
|
98
|
+
personaLine,
|
|
99
|
+
'',
|
|
100
|
+
'Recent conversation (most recent last):',
|
|
101
|
+
recent || '(none)',
|
|
102
|
+
'',
|
|
103
|
+
'Return JSON with exactly these keys (all strings):',
|
|
104
|
+
'{',
|
|
105
|
+
' "title": "headline of the bubble",',
|
|
106
|
+
' "body": "one sentence asking permission or explaining the block",',
|
|
107
|
+
' "approveLabel": "primary button when asking permission",',
|
|
108
|
+
' "approveBusyLabel": "primary button while confirming",',
|
|
109
|
+
' "denyLabel": "secondary button when asking permission",',
|
|
110
|
+
' "denyBusyLabel": "secondary button while confirming",',
|
|
111
|
+
' "approvedPill": "tiny status pill after the user approved",',
|
|
112
|
+
' "deniedPill": "tiny status pill after the user denied",',
|
|
113
|
+
' "readOnlyHint": "tiny line shown when the bubble is read-only (e.g. on history)",',
|
|
114
|
+
' "blockedTitle": "headline when the action is blocked",',
|
|
115
|
+
' "blockedBody": "one sentence explaining the block",',
|
|
116
|
+
' "expiresPrefix": "short prefix shown before the countdown, e.g. \\"Expires in\\""',
|
|
117
|
+
'}',
|
|
118
|
+
].join('\n');
|
|
119
|
+
const ac = new AbortController();
|
|
120
|
+
const timer = setTimeout(() => ac.abort(), HAIKU_TIMEOUT_MS);
|
|
121
|
+
try {
|
|
122
|
+
const res = await this.client.messages.create({
|
|
123
|
+
model: HAIKU_MODEL,
|
|
124
|
+
max_tokens: HAIKU_MAX_TOKENS,
|
|
125
|
+
temperature: 0.4,
|
|
126
|
+
system,
|
|
127
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
128
|
+
}, { signal: ac.signal });
|
|
129
|
+
const text = res.content
|
|
130
|
+
.filter((b) => b.type === 'text')
|
|
131
|
+
.map((b) => b.text)
|
|
132
|
+
.join('')
|
|
133
|
+
.trim();
|
|
134
|
+
return parseBundle(text);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.ApprovalCopywriterService = ApprovalCopywriterService;
|
|
142
|
+
function parseBundle(text) {
|
|
143
|
+
const jsonStart = text.indexOf('{');
|
|
144
|
+
const jsonEnd = text.lastIndexOf('}');
|
|
145
|
+
if (jsonStart < 0 || jsonEnd <= jsonStart)
|
|
146
|
+
return undefined;
|
|
147
|
+
let obj;
|
|
148
|
+
try {
|
|
149
|
+
obj = JSON.parse(text.slice(jsonStart, jsonEnd + 1));
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const keys = [
|
|
155
|
+
'title',
|
|
156
|
+
'body',
|
|
157
|
+
'approveLabel',
|
|
158
|
+
'approveBusyLabel',
|
|
159
|
+
'denyLabel',
|
|
160
|
+
'denyBusyLabel',
|
|
161
|
+
'approvedPill',
|
|
162
|
+
'deniedPill',
|
|
163
|
+
'readOnlyHint',
|
|
164
|
+
'blockedTitle',
|
|
165
|
+
'blockedBody',
|
|
166
|
+
'expiresPrefix',
|
|
167
|
+
];
|
|
168
|
+
const out = {};
|
|
169
|
+
for (const k of keys) {
|
|
170
|
+
const v = obj[k];
|
|
171
|
+
if (typeof v !== 'string' || !v.trim())
|
|
172
|
+
return undefined;
|
|
173
|
+
out[k] = v.trim();
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { PreparedStreamService, PreparedStreamError, } from './prepared-stream.s
|
|
|
5
5
|
export { InMemoryPreparedStreamStore } from './in-memory-prepared-stream.store';
|
|
6
6
|
export { ConversationService, ConversationNotFoundError, } from './conversation.service';
|
|
7
7
|
export { AgentService, AgentForbiddenError, type AgentNotFoundError, } from './agent.service';
|
|
8
|
+
export { ApprovalCopywriterService, type ApprovalCopyBundle as ApprovalCopywriterBundle, type ApprovalCopywriterInput, } from './approval-copywriter.service';
|
|
8
9
|
export { OrchestratorService, OrchestratorError, type OrchestratorServiceOptions, } from './orchestrator.service';
|
|
9
10
|
export { AgentJobWorker } from './agent-job.worker';
|
|
10
11
|
export { ChatTokenService, ChatTokenError, type CreateChatTokenInput, } from './chat-token.service';
|
package/dist/services/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemoryAuthorizeStateStore = exports.ConnectorError = exports.ConnectorRegistryService = exports.OAuth2Service = exports.McpServerError = exports.McpServerService = exports.McpClientError = exports.McpClientService = exports.ChatTokenError = exports.ChatTokenService = exports.AgentJobWorker = exports.OrchestratorError = exports.OrchestratorService = exports.AgentForbiddenError = exports.AgentService = exports.ConversationNotFoundError = exports.ConversationService = exports.InMemoryPreparedStreamStore = exports.PreparedStreamError = exports.PreparedStreamService = exports.isToolBlockedError = exports.isToolApprovalRequired = exports.ToolBlockedError = exports.ToolApprovalRequired = exports.AgentRunnerService = exports.ToolRegistryService = void 0;
|
|
3
|
+
exports.InMemoryAuthorizeStateStore = exports.ConnectorError = exports.ConnectorRegistryService = exports.OAuth2Service = exports.McpServerError = exports.McpServerService = exports.McpClientError = exports.McpClientService = exports.ChatTokenError = exports.ChatTokenService = exports.AgentJobWorker = exports.OrchestratorError = exports.OrchestratorService = exports.ApprovalCopywriterService = exports.AgentForbiddenError = exports.AgentService = exports.ConversationNotFoundError = exports.ConversationService = exports.InMemoryPreparedStreamStore = exports.PreparedStreamError = exports.PreparedStreamService = exports.isToolBlockedError = exports.isToolApprovalRequired = exports.ToolBlockedError = exports.ToolApprovalRequired = exports.AgentRunnerService = exports.ToolRegistryService = void 0;
|
|
4
4
|
// Public service surface of @agentforge-io/core. AI runtime only — auth,
|
|
5
5
|
// identity, billing, infra, etc. have all moved to the host server.
|
|
6
6
|
var tool_registry_service_1 = require("./tool-registry.service");
|
|
@@ -27,6 +27,8 @@ Object.defineProperty(exports, "ConversationNotFoundError", { enumerable: true,
|
|
|
27
27
|
var agent_service_1 = require("./agent.service");
|
|
28
28
|
Object.defineProperty(exports, "AgentService", { enumerable: true, get: function () { return agent_service_1.AgentService; } });
|
|
29
29
|
Object.defineProperty(exports, "AgentForbiddenError", { enumerable: true, get: function () { return agent_service_1.AgentForbiddenError; } });
|
|
30
|
+
var approval_copywriter_service_1 = require("./approval-copywriter.service");
|
|
31
|
+
Object.defineProperty(exports, "ApprovalCopywriterService", { enumerable: true, get: function () { return approval_copywriter_service_1.ApprovalCopywriterService; } });
|
|
30
32
|
var orchestrator_service_1 = require("./orchestrator.service");
|
|
31
33
|
Object.defineProperty(exports, "OrchestratorService", { enumerable: true, get: function () { return orchestrator_service_1.OrchestratorService; } });
|
|
32
34
|
Object.defineProperty(exports, "OrchestratorError", { enumerable: true, get: function () { return orchestrator_service_1.OrchestratorError; } });
|
|
@@ -151,6 +151,20 @@ export interface ToolExecutionContext {
|
|
|
151
151
|
export type AnthropicMessage = Anthropic.MessageParam;
|
|
152
152
|
export type AnthropicTool = Anthropic.Tool;
|
|
153
153
|
export type AnthropicContentBlock = Anthropic.ContentBlock;
|
|
154
|
+
export interface ApprovalCopyBundle {
|
|
155
|
+
title: string;
|
|
156
|
+
body: string;
|
|
157
|
+
approveLabel: string;
|
|
158
|
+
approveBusyLabel: string;
|
|
159
|
+
denyLabel: string;
|
|
160
|
+
denyBusyLabel: string;
|
|
161
|
+
approvedPill: string;
|
|
162
|
+
deniedPill: string;
|
|
163
|
+
readOnlyHint: string;
|
|
164
|
+
blockedTitle: string;
|
|
165
|
+
blockedBody: string;
|
|
166
|
+
expiresPrefix: string;
|
|
167
|
+
}
|
|
154
168
|
export type StreamChunk = {
|
|
155
169
|
type: 'text_delta';
|
|
156
170
|
delta: string;
|
|
@@ -187,25 +201,21 @@ export type StreamChunk = {
|
|
|
187
201
|
approvalId: string;
|
|
188
202
|
toolName: string;
|
|
189
203
|
expiresAt: string;
|
|
190
|
-
/** Human-friendly connector name (`'Granola'`, `'Notion'`). The
|
|
191
|
-
* runner enriches it from the registry so the chat widget can
|
|
192
|
-
* render a sentence like "I need permission to use Granola"
|
|
193
|
-
* instead of the raw tool slug. Optional — clients fall back
|
|
194
|
-
* to `toolName` when the runtime doesn't supply this. */
|
|
195
204
|
connectorName?: string;
|
|
196
|
-
/** Human-friendly first sentence of the tool's description.
|
|
197
|
-
* Same story: enables the widget to say what the tool DOES in
|
|
198
|
-
* natural language. Optional. */
|
|
199
205
|
toolDescription?: string;
|
|
206
|
+
/** AI-generated microcopy for the bubble. Built server-side by
|
|
207
|
+
* the approval copywriter (a small Haiku call) so the language,
|
|
208
|
+
* tone and register match the conversation. Optional — when
|
|
209
|
+
* absent the client falls back to a minimal render derived from
|
|
210
|
+
* `connectorName` / `toolName`. */
|
|
211
|
+
copy?: ApprovalCopyBundle;
|
|
200
212
|
} | {
|
|
201
|
-
/** Emitted when a tool dispatch hit `kind: 'blocked'`. Mirrors the
|
|
202
|
-
* shape above so the client renders a terminal-error card with
|
|
203
|
-
* the same component. */
|
|
204
213
|
type: 'tool_blocked';
|
|
205
214
|
toolName: string;
|
|
206
215
|
reason?: string;
|
|
207
216
|
connectorName?: string;
|
|
208
217
|
toolDescription?: string;
|
|
218
|
+
copy?: ApprovalCopyBundle;
|
|
209
219
|
} | {
|
|
210
220
|
type: 'done';
|
|
211
221
|
messageId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
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",
|