@agentforge-io/core 2.0.24 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/factory.js +56 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/services/agent-runner.service.js +57 -4
- package/dist/services/agent.service.d.ts +21 -1
- package/dist/services/agent.service.js +42 -8
- package/dist/services/orchestrator.service.d.ts +40 -1
- package/dist/services/orchestrator.service.js +220 -0
- package/dist/types/agent.types.d.ts +31 -6
- package/package.json +1 -1
- package/dist/adapters/billing/billing-adapter.interface.d.ts +0 -41
- package/dist/adapters/billing/billing-adapter.interface.js +0 -5
- package/dist/adapters/billing/stripe/stripe.adapter.d.ts +0 -30
- package/dist/adapters/billing/stripe/stripe.adapter.js +0 -122
- package/dist/adapters/email/email-adapter.interface.d.ts +0 -25
- package/dist/adapters/email/email-adapter.interface.js +0 -6
- package/dist/adapters/email/noop.adapter.d.ts +0 -10
- package/dist/adapters/email/noop.adapter.js +0 -15
- package/dist/adapters/email/resend.adapter.d.ts +0 -8
- package/dist/adapters/email/resend.adapter.js +0 -39
- package/dist/adapters/upload/noop.adapter.d.ts +0 -9
- package/dist/adapters/upload/noop.adapter.js +0 -14
- package/dist/adapters/upload/s3.adapter.d.ts +0 -38
- package/dist/adapters/upload/s3.adapter.js +0 -69
- package/dist/adapters/upload/upload-adapter.interface.d.ts +0 -37
- package/dist/adapters/upload/upload-adapter.interface.js +0 -15
- package/dist/billing/index.d.ts +0 -12
- package/dist/billing/index.js +0 -28
- package/dist/domain/agent.d.ts +0 -59
- package/dist/domain/agent.js +0 -2
- package/dist/domain/api-key.d.ts +0 -28
- package/dist/domain/api-key.js +0 -2
- package/dist/domain/auth-identity.d.ts +0 -10
- package/dist/domain/auth-identity.js +0 -2
- package/dist/domain/email-token.d.ts +0 -11
- package/dist/domain/email-token.js +0 -2
- package/dist/domain/external-user.d.ts +0 -23
- package/dist/domain/external-user.js +0 -2
- package/dist/domain/plan.d.ts +0 -20
- package/dist/domain/plan.js +0 -2
- package/dist/domain/platform-secret.d.ts +0 -24
- package/dist/domain/platform-secret.js +0 -8
- package/dist/domain/refresh-token.d.ts +0 -15
- package/dist/domain/refresh-token.js +0 -2
- package/dist/domain/subscription.d.ts +0 -21
- package/dist/domain/subscription.js +0 -2
- package/dist/domain/tenant.d.ts +0 -21
- package/dist/domain/tenant.js +0 -2
- package/dist/domain/usage-record.d.ts +0 -15
- package/dist/domain/usage-record.js +0 -2
- package/dist/domain/user.d.ts +0 -43
- package/dist/domain/user.js +0 -2
- package/dist/services/agent-config.service.d.ts +0 -45
- package/dist/services/agent-config.service.js +0 -114
- package/dist/services/api-key.service.d.ts +0 -41
- package/dist/services/api-key.service.js +0 -80
- package/dist/services/auth.service.d.ts +0 -133
- package/dist/services/auth.service.js +0 -411
- package/dist/services/billing.service.d.ts +0 -67
- package/dist/services/billing.service.js +0 -254
- package/dist/services/email-templates.d.ts +0 -18
- package/dist/services/email-templates.js +0 -39
- package/dist/services/email.service.d.ts +0 -26
- package/dist/services/email.service.js +0 -42
- package/dist/services/errors.d.ts +0 -7
- package/dist/services/errors.js +0 -27
- package/dist/services/oauth.service.d.ts +0 -73
- package/dist/services/oauth.service.js +0 -174
- package/dist/services/plan.service.d.ts +0 -54
- package/dist/services/plan.service.js +0 -120
- package/dist/services/refresh-token.service.d.ts +0 -38
- package/dist/services/refresh-token.service.js +0 -73
- package/dist/services/secrets/crypto.d.ts +0 -37
- package/dist/services/secrets/crypto.js +0 -110
- package/dist/services/secrets/known-keys.d.ts +0 -38
- package/dist/services/secrets/known-keys.js +0 -50
- package/dist/services/secrets.service.d.ts +0 -91
- package/dist/services/secrets.service.js +0 -193
- package/dist/services/tenant-billing.service.d.ts +0 -121
- package/dist/services/tenant-billing.service.js +0 -290
- package/dist/services/tenant.service.d.ts +0 -54
- package/dist/services/tenant.service.js +0 -96
- package/dist/services/upload.service.d.ts +0 -37
- package/dist/services/upload.service.js +0 -84
- package/dist/services/usage.service.d.ts +0 -34
- package/dist/services/usage.service.js +0 -108
- package/dist/types/billing.types.d.ts +0 -82
- package/dist/types/billing.types.js +0 -3
package/dist/factory.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.createAgentForge = createAgentForge;
|
|
4
37
|
const in_memory_1 = require("./adapters/rate-limiter/in-memory");
|
|
@@ -22,9 +55,29 @@ function createAgentForge(opts) {
|
|
|
22
55
|
// ─── Tool registry + runner + orchestrator ───────────────────────────────
|
|
23
56
|
const toolRegistry = new tool_registry_service_1.ToolRegistryService({ logger, initialTools: tools });
|
|
24
57
|
const runner = new agent_runner_service_1.AgentRunnerService(config.anthropic, toolRegistry, { logger });
|
|
58
|
+
// Bridge the host-supplied AgentResolver into the orchestrator's
|
|
59
|
+
// dynamic-lookup hook. The orchestrator only needs `AgentDefinition`,
|
|
60
|
+
// so we wrap the resolver's record-shaped result with the same
|
|
61
|
+
// `toAgentDefinition` adapter the AgentService uses internally.
|
|
62
|
+
// When no resolver is wired the orchestrator only sees the static
|
|
63
|
+
// `config.agents` map — preserving legacy behaviour.
|
|
64
|
+
const orchestratorResolver = adapters.agentResolver
|
|
65
|
+
? async (id) => {
|
|
66
|
+
const rec = await adapters.agentResolver.findById(id);
|
|
67
|
+
if (!rec || !rec.isActive)
|
|
68
|
+
return null;
|
|
69
|
+
// `toAgentDefinition` is the internal adapter — exposing it
|
|
70
|
+
// via the agent.service module keeps it private to the SDK.
|
|
71
|
+
// We dynamically require it here to dodge a circular import
|
|
72
|
+
// between factory.ts and agent.service.ts.
|
|
73
|
+
const { toAgentDefinition } = await Promise.resolve().then(() => __importStar(require('./services/agent.service')));
|
|
74
|
+
return toAgentDefinition(rec);
|
|
75
|
+
}
|
|
76
|
+
: undefined;
|
|
25
77
|
const orchestrator = new orchestrator_service_1.OrchestratorService(config.anthropic, runner, {
|
|
26
78
|
agents: config.agents ?? [],
|
|
27
79
|
logger,
|
|
80
|
+
resolveAgent: orchestratorResolver,
|
|
28
81
|
});
|
|
29
82
|
// ─── Prepared-stream store + service ─────────────────────────────────────
|
|
30
83
|
const preparedStreamStore = adapters.preparedStreamStore ?? new in_memory_prepared_stream_store_1.InMemoryPreparedStreamStore();
|
|
@@ -33,7 +86,9 @@ function createAgentForge(opts) {
|
|
|
33
86
|
const rateLimiter = adapters.rateLimiter ?? new in_memory_1.InMemoryRateLimiter();
|
|
34
87
|
// ─── Conversations + agents ──────────────────────────────────────────────
|
|
35
88
|
const conversations = new conversation_service_1.ConversationService(repositories.conversations, repositories.messages);
|
|
36
|
-
const agents = new agent_service_1.AgentService(config.agents ?? [], runner, conversations, adapters.agentResolver, hooks
|
|
89
|
+
const agents = new agent_service_1.AgentService(config.agents ?? [], runner, conversations, adapters.agentResolver, hooks, undefined, // connectorRegistry — wired by Nest binding when present
|
|
90
|
+
undefined, // copywriter — same
|
|
91
|
+
orchestrator);
|
|
37
92
|
// ─── Background-job worker + queue (in-memory default) ───────────────────
|
|
38
93
|
const agentJobWorker = new agent_job_worker_1.AgentJobWorker(orchestrator, conversations, {
|
|
39
94
|
logger,
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,5 @@ export { JOB_QUEUE, type JobQueue, type JobStatus, type JobState, type JobContex
|
|
|
11
11
|
export { InMemoryJobQueue, type InMemoryJobQueueOptions, } from './adapters/job-queue/in-memory';
|
|
12
12
|
export * from './services';
|
|
13
13
|
export type { AgentResolver, AgentRecord, AgentResolveParams, } from './services/agent.service';
|
|
14
|
+
export { toAgentDefinition } from './services/agent.service';
|
|
14
15
|
export { createAgentForge, type CreateAgentForgeOptions, type AgentForgeContainer, type AgentForgeRepositories, type AgentForgeAdapters, } from './factory';
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
27
27
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.createAgentForge = exports.InMemoryJobQueue = exports.JOB_QUEUE = exports.RedisRateLimiter = exports.InMemoryRateLimiter = exports.RATE_LIMITER = exports.PREPARED_STREAM_STORE = exports.CURRENT_USER = exports.AGENT_QUEUE_NAME = exports.AGENT_FORGE_CONFIG = void 0;
|
|
30
|
+
exports.createAgentForge = exports.toAgentDefinition = exports.InMemoryJobQueue = exports.JOB_QUEUE = exports.RedisRateLimiter = exports.InMemoryRateLimiter = exports.RATE_LIMITER = exports.PREPARED_STREAM_STORE = exports.CURRENT_USER = exports.AGENT_QUEUE_NAME = exports.AGENT_FORGE_CONFIG = void 0;
|
|
31
31
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
32
32
|
var constants_1 = require("./constants");
|
|
33
33
|
Object.defineProperty(exports, "AGENT_FORGE_CONFIG", { enumerable: true, get: function () { return constants_1.AGENT_FORGE_CONFIG; } });
|
|
@@ -54,6 +54,12 @@ var in_memory_2 = require("./adapters/job-queue/in-memory");
|
|
|
54
54
|
Object.defineProperty(exports, "InMemoryJobQueue", { enumerable: true, get: function () { return in_memory_2.InMemoryJobQueue; } });
|
|
55
55
|
// ─── Services (framework-free) ──────────────────────────────────────────────
|
|
56
56
|
__exportStar(require("./services"), exports);
|
|
57
|
+
// `toAgentDefinition` is the adapter from the host's `AgentRecord` shape
|
|
58
|
+
// to the SDK's runtime `AgentDefinition`. Exposed so the Nest binding
|
|
59
|
+
// can bridge an `AgentResolver` into the orchestrator's dynamic-lookup
|
|
60
|
+
// hook for Team flows.
|
|
61
|
+
var agent_service_1 = require("./services/agent.service");
|
|
62
|
+
Object.defineProperty(exports, "toAgentDefinition", { enumerable: true, get: function () { return agent_service_1.toAgentDefinition; } });
|
|
57
63
|
// ─── Container factory ──────────────────────────────────────────────────────
|
|
58
64
|
var factory_1 = require("./factory");
|
|
59
65
|
Object.defineProperty(exports, "createAgentForge", { enumerable: true, get: function () { return factory_1.createAgentForge; } });
|
|
@@ -11,6 +11,38 @@ const model_strategy_1 = require("../types/model-strategy");
|
|
|
11
11
|
const noopLogger = {
|
|
12
12
|
log: () => { }, warn: () => { }, debug: () => { }, error: () => { },
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Anthropic's newer model families deprecated the `temperature` parameter
|
|
16
|
+
* entirely — they auto-tune sampling internally and return 400
|
|
17
|
+
* `invalid_request_error: \`temperature\` is deprecated for this model` if
|
|
18
|
+
* the caller still sends one. Older families (3.x, the original 4.0
|
|
19
|
+
* releases) accept it fine.
|
|
20
|
+
*
|
|
21
|
+
* Detection by string match on the model id rather than a hard-coded
|
|
22
|
+
* allowlist: new model ids land between SDK releases, and we don't want
|
|
23
|
+
* to break temperature on legacy agents the day a new family ships.
|
|
24
|
+
* Pattern: anything that contains `-4-5`, `-4-6`, `-4-7`, …, `-5-*`,
|
|
25
|
+
* `-6-*`, etc. counts as "newer." Old 4-0 / 4-1 / 3-x ids are unaffected.
|
|
26
|
+
*
|
|
27
|
+
* Heuristic, not exhaustive — if a future family lands with a different
|
|
28
|
+
* naming convention we'll have to extend this. The cost of being wrong
|
|
29
|
+
* is a single 400 the operator can fix by clearing the temperature in
|
|
30
|
+
* the editor; the cost of NOT filtering is the same 400 today.
|
|
31
|
+
*/
|
|
32
|
+
function modelRejectsTemperature(model) {
|
|
33
|
+
if (!model)
|
|
34
|
+
return false;
|
|
35
|
+
// Normalize: ignore vendor prefixes like "anthropic/claude-..." and
|
|
36
|
+
// bracket suffixes like "claude-opus-4-7[1m]" (long-context variant).
|
|
37
|
+
const m = model.toLowerCase().replace(/\[[^\]]*\]/g, '');
|
|
38
|
+
// claude-*-4-5, 4-6, 4-7, 4-8 …
|
|
39
|
+
if (/claude-[a-z]+-4-([5-9])\b/.test(m))
|
|
40
|
+
return true;
|
|
41
|
+
// claude-*-5-x, claude-*-6-x, … (future major bumps)
|
|
42
|
+
if (/claude-[a-z]+-([5-9])-/.test(m))
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
14
46
|
/**
|
|
15
47
|
* Framework-free runner for Claude. Handles the agentic loop (tool calls) for
|
|
16
48
|
* sync runs and exposes streaming as an `AsyncGenerator<StreamChunk>` so any
|
|
@@ -33,7 +65,13 @@ class AgentRunnerService {
|
|
|
33
65
|
const runnerDefault = this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
34
66
|
const baseModel = overrides?.model ?? agent.model ?? runnerDefault;
|
|
35
67
|
const maxTokens = overrides?.maxTokens ?? agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
36
|
-
|
|
68
|
+
// Anthropic's newer models (Sonnet 4.6+, Haiku 4.5+) reject
|
|
69
|
+
// `temperature` when tools are present — they auto-tune sampling for
|
|
70
|
+
// tool use. Only forward it when the operator/caller declared one
|
|
71
|
+
// explicitly; never inject a default. Old models that required it
|
|
72
|
+
// accept its absence too (they fall back to their own internal
|
|
73
|
+
// default of 1.0).
|
|
74
|
+
const temperature = overrides?.temperature ?? agent.temperature;
|
|
37
75
|
const { tools, extras } = this.buildToolList(agent, overrides);
|
|
38
76
|
const systemPrompt = this.buildSystemPrompt(agent, tools, overrides);
|
|
39
77
|
const toolCalls = [];
|
|
@@ -68,10 +106,17 @@ class AgentRunnerService {
|
|
|
68
106
|
if (this.logger && selection.reason !== 'default' && selection.reason !== 'forced') {
|
|
69
107
|
this.logger.debug(`[modelRouter] agent=${agent.id} ${selection.reason}=${selection.trigger} → ${model}`);
|
|
70
108
|
}
|
|
109
|
+
// Per-turn temperature gating. The PER-MODEL filter runs INSIDE
|
|
110
|
+
// the loop because `model` can change between turns (model
|
|
111
|
+
// strategy can route a long-context turn to a different family
|
|
112
|
+
// than the short turns above it). Computing once outside would
|
|
113
|
+
// either over-strip (drop temperature for a legacy follow-up
|
|
114
|
+
// model) or under-strip (forward it to a new-family upgrade).
|
|
115
|
+
const includeTemperature = typeof temperature === 'number' && !modelRejectsTemperature(model);
|
|
71
116
|
const response = await this.client.messages.create({
|
|
72
117
|
model,
|
|
73
118
|
max_tokens: maxTokens,
|
|
74
|
-
temperature,
|
|
119
|
+
...(includeTemperature ? { temperature } : {}),
|
|
75
120
|
system: systemPrompt,
|
|
76
121
|
messages: currentMessages,
|
|
77
122
|
tools: tools,
|
|
@@ -160,7 +205,13 @@ class AgentRunnerService {
|
|
|
160
205
|
const runnerDefault = this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
161
206
|
const baseModel = overrides?.model ?? agent.model ?? runnerDefault;
|
|
162
207
|
const maxTokens = overrides?.maxTokens ?? agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
163
|
-
|
|
208
|
+
// Anthropic's newer models (Sonnet 4.6+, Haiku 4.5+) reject
|
|
209
|
+
// `temperature` when tools are present — they auto-tune sampling for
|
|
210
|
+
// tool use. Only forward it when the operator/caller declared one
|
|
211
|
+
// explicitly; never inject a default. Old models that required it
|
|
212
|
+
// accept its absence too (they fall back to their own internal
|
|
213
|
+
// default of 1.0).
|
|
214
|
+
const temperature = overrides?.temperature ?? agent.temperature;
|
|
164
215
|
const { tools, extras } = this.buildToolList(agent, overrides);
|
|
165
216
|
const systemPrompt = this.buildSystemPrompt(agent, tools, overrides);
|
|
166
217
|
let currentMessages = [...messages];
|
|
@@ -179,10 +230,12 @@ class AgentRunnerService {
|
|
|
179
230
|
if (this.logger && selection.reason !== 'default' && selection.reason !== 'forced') {
|
|
180
231
|
this.logger.debug(`[modelRouter] agent=${agent.id} ${selection.reason}=${selection.trigger} → ${model}`);
|
|
181
232
|
}
|
|
233
|
+
// Per-turn temperature gating — see `run()` above for rationale.
|
|
234
|
+
const includeTemperature = typeof temperature === 'number' && !modelRejectsTemperature(model);
|
|
182
235
|
const stream = this.client.messages.stream({
|
|
183
236
|
model,
|
|
184
237
|
max_tokens: maxTokens,
|
|
185
|
-
temperature,
|
|
238
|
+
...(includeTemperature ? { temperature } : {}),
|
|
186
239
|
system: systemPrompt,
|
|
187
240
|
messages: currentMessages,
|
|
188
241
|
tools: tools,
|
|
@@ -115,6 +115,11 @@ export declare class AgentService {
|
|
|
115
115
|
* meta-only render on the client when unwired or when generation
|
|
116
116
|
* fails. */
|
|
117
117
|
private readonly copywriter?;
|
|
118
|
+
/** When wired, agents flagged `canOrchestrate=true` route through
|
|
119
|
+
* the orchestrator's `stream()` instead of the bare runner so the
|
|
120
|
+
* `delegate_to_*` synthetic tools fire. Standalone agents always
|
|
121
|
+
* go straight to the runner. */
|
|
122
|
+
private readonly orchestrator?;
|
|
118
123
|
constructor(agents: AgentDefinition[], runner: AgentRunnerService, conversations: ConversationService,
|
|
119
124
|
/** When wired, agents created via the admin UI are looked up here first;
|
|
120
125
|
* the hardcoded `agents` array remains a fallback for legacy installs. */
|
|
@@ -130,7 +135,12 @@ export declare class AgentService {
|
|
|
130
135
|
* microcopy shown in the in-chat approval bubble. Falls back to a
|
|
131
136
|
* meta-only render on the client when unwired or when generation
|
|
132
137
|
* fails. */
|
|
133
|
-
copywriter?: ApprovalCopywriterService | undefined
|
|
138
|
+
copywriter?: ApprovalCopywriterService | undefined,
|
|
139
|
+
/** When wired, agents flagged `canOrchestrate=true` route through
|
|
140
|
+
* the orchestrator's `stream()` instead of the bare runner so the
|
|
141
|
+
* `delegate_to_*` synthetic tools fire. Standalone agents always
|
|
142
|
+
* go straight to the runner. */
|
|
143
|
+
orchestrator?: import("./orchestrator.service").OrchestratorService | undefined);
|
|
134
144
|
/**
|
|
135
145
|
* Look up the human-friendly connector name + tool description for a
|
|
136
146
|
* given tool slug. Powers the friendly copy in `awaiting_approval` /
|
|
@@ -227,3 +237,13 @@ export declare class AgentService {
|
|
|
227
237
|
chunk: StreamChunk;
|
|
228
238
|
}>;
|
|
229
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Map a persisted `AgentRecord` to the runtime `AgentDefinition` the runner
|
|
242
|
+
* expects. The `context` column (plain-text knowledge) is prepended to the
|
|
243
|
+
* system prompt — the cheapest path before RAG is implemented.
|
|
244
|
+
*
|
|
245
|
+
* Extra host fields (`appearance`, `slug`) are passed through as opaque
|
|
246
|
+
* properties so callers like `PublicChatController` can surface them to the
|
|
247
|
+
* widget. The runner ignores anything it doesn't recognize.
|
|
248
|
+
*/
|
|
249
|
+
export declare function toAgentDefinition(record: AgentRecord): AgentDefinition;
|
|
@@ -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
|
+
exports.toAgentDefinition = toAgentDefinition;
|
|
4
5
|
const tool_approval_gate_1 = require("./tool-approval-gate");
|
|
5
6
|
class AgentForbiddenError extends Error {
|
|
6
7
|
constructor(reason) {
|
|
@@ -26,7 +27,12 @@ class AgentService {
|
|
|
26
27
|
* microcopy shown in the in-chat approval bubble. Falls back to a
|
|
27
28
|
* meta-only render on the client when unwired or when generation
|
|
28
29
|
* fails. */
|
|
29
|
-
copywriter
|
|
30
|
+
copywriter,
|
|
31
|
+
/** When wired, agents flagged `canOrchestrate=true` route through
|
|
32
|
+
* the orchestrator's `stream()` instead of the bare runner so the
|
|
33
|
+
* `delegate_to_*` synthetic tools fire. Standalone agents always
|
|
34
|
+
* go straight to the runner. */
|
|
35
|
+
orchestrator) {
|
|
30
36
|
this.agents = agents;
|
|
31
37
|
this.runner = runner;
|
|
32
38
|
this.conversations = conversations;
|
|
@@ -34,6 +40,7 @@ class AgentService {
|
|
|
34
40
|
this.hooks = hooks;
|
|
35
41
|
this.connectorRegistry = connectorRegistry;
|
|
36
42
|
this.copywriter = copywriter;
|
|
43
|
+
this.orchestrator = orchestrator;
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
39
46
|
* Look up the human-friendly connector name + tool description for a
|
|
@@ -286,13 +293,30 @@ class AgentService {
|
|
|
286
293
|
const fromConnectors = filter && resolvedExtras ? filter(resolvedExtras) : resolvedExtras;
|
|
287
294
|
const extraTools = mergeExtraTools(params.overrides?.extraTools, fromConnectors);
|
|
288
295
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
// Team orchestrators route through OrchestratorService.stream()
|
|
297
|
+
// so the synthetic `delegate_to_*` tools the orchestrator was
|
|
298
|
+
// built with can fire. Standalone agents (or any orchestrator
|
|
299
|
+
// without an SDK that wired the service) go straight to the
|
|
300
|
+
// runner — the legacy path stays untouched for them.
|
|
301
|
+
const useOrchestrator = this.orchestrator &&
|
|
302
|
+
agent.canOrchestrate &&
|
|
303
|
+
(agent.subAgents?.length ?? 0) > 0;
|
|
304
|
+
const stream = useOrchestrator
|
|
305
|
+
? this.orchestrator.stream(agent.id, messages, {
|
|
306
|
+
userId: params.userId,
|
|
307
|
+
conversationId: params.conversationId,
|
|
308
|
+
agentId: conv.agentId,
|
|
309
|
+
messageId: 'streaming',
|
|
310
|
+
agent: { timezone: agent.timezone },
|
|
311
|
+
})
|
|
312
|
+
: this.runner.stream(agent, messages, {
|
|
313
|
+
userId: params.userId,
|
|
314
|
+
conversationId: params.conversationId,
|
|
315
|
+
agentId: conv.agentId,
|
|
316
|
+
messageId: 'streaming',
|
|
317
|
+
agent: { timezone: agent.timezone },
|
|
318
|
+
}, { ...(params.overrides ?? {}), extraTools });
|
|
319
|
+
for await (const chunk of stream) {
|
|
296
320
|
if (chunk.type === 'text_delta')
|
|
297
321
|
fullContent += chunk.delta;
|
|
298
322
|
if (chunk.type === 'usage')
|
|
@@ -511,6 +535,16 @@ function toAgentDefinition(record) {
|
|
|
511
535
|
// from the host's resolver to the SDK and every agent reads as
|
|
512
536
|
// `undefined` (i.e. public).
|
|
513
537
|
visibility: record.visibility,
|
|
538
|
+
// Team orchestrators need these to drive the delegation tool list
|
|
539
|
+
// the runner injects at run time. Standalone agents won't have
|
|
540
|
+
// them; the orchestrator service treats absence as "not an
|
|
541
|
+
// orchestrator" and falls through to the runner directly.
|
|
542
|
+
...(extra.canOrchestrate !== undefined
|
|
543
|
+
? { canOrchestrate: extra.canOrchestrate }
|
|
544
|
+
: {}),
|
|
545
|
+
...(Array.isArray(extra.subAgents)
|
|
546
|
+
? { subAgents: extra.subAgents }
|
|
547
|
+
: {}),
|
|
514
548
|
...(record.slug !== undefined ? { slug: record.slug } : {}),
|
|
515
549
|
...(extra.appearance !== undefined ? { appearance: extra.appearance } : {}),
|
|
516
550
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentResponse, AnthropicMessage, SubAgentDelegation, ToolExecutionContext } from '../types/agent.types';
|
|
1
|
+
import type { AgentResponse, AnthropicMessage, StreamChunk, SubAgentDelegation, ToolExecutionContext } from '../types/agent.types';
|
|
2
2
|
import type { AgentDefinition, AnthropicConfig } from '../types/config.types';
|
|
3
3
|
import type { AgentRunnerService } from './agent-runner.service';
|
|
4
4
|
import type { Logger } from './tool-registry.service';
|
|
@@ -13,6 +13,19 @@ export declare class OrchestratorError extends Error {
|
|
|
13
13
|
export interface OrchestratorServiceOptions {
|
|
14
14
|
agents: AgentDefinition[];
|
|
15
15
|
logger?: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* Optional dynamic resolver. When the orchestrator references a
|
|
18
|
+
* sub-agent that wasn't in the constructor's `agents[]` (typical for
|
|
19
|
+
* Team orchestrators whose members live in the database and change
|
|
20
|
+
* per-tenant), the service calls this to load it on demand. Returning
|
|
21
|
+
* `null` is treated as "member is gone" — the orchestrator emits an
|
|
22
|
+
* apology delegation_result and continues.
|
|
23
|
+
*
|
|
24
|
+
* The framework-free SDK doesn't know about the host's persistence,
|
|
25
|
+
* so this hook is the seam where the platform wires
|
|
26
|
+
* `AgentConfigService` + `toAgentDefinition` adapter in.
|
|
27
|
+
*/
|
|
28
|
+
resolveAgent?(agentId: string): Promise<AgentDefinition | null> | AgentDefinition | null;
|
|
16
29
|
}
|
|
17
30
|
/**
|
|
18
31
|
* Multi-agent workflows. Orchestrator agents can delegate tasks to specialized
|
|
@@ -29,7 +42,18 @@ export declare class OrchestratorService {
|
|
|
29
42
|
private readonly agentsMap;
|
|
30
43
|
private readonly client;
|
|
31
44
|
private readonly logger;
|
|
45
|
+
private readonly resolveAgentHook?;
|
|
32
46
|
constructor(anthropicConfig: AnthropicConfig, runner: AgentRunnerService, opts: OrchestratorServiceOptions);
|
|
47
|
+
/**
|
|
48
|
+
* Lookup with dynamic-resolver fallback. Hits the static map first
|
|
49
|
+
* (built from the constructor's `agents` list — covers the bootstrap
|
|
50
|
+
* use case), then falls back to the host-supplied `resolveAgent`
|
|
51
|
+
* hook (used by Team orchestrators whose members are loaded from
|
|
52
|
+
* the database per-tenant). Resolved agents are cached in the map
|
|
53
|
+
* for the lifetime of the service to avoid re-fetching across a
|
|
54
|
+
* multi-turn conversation.
|
|
55
|
+
*/
|
|
56
|
+
private resolveAgentDynamic;
|
|
33
57
|
/**
|
|
34
58
|
* Run an agent. Orchestrators automatically get delegation tools injected.
|
|
35
59
|
* Non-orchestrator agents fall straight through to the runner.
|
|
@@ -37,6 +61,21 @@ export declare class OrchestratorService {
|
|
|
37
61
|
run(agentId: string, messages: AnthropicMessage[], context: ToolExecutionContext): Promise<AgentResponse & {
|
|
38
62
|
delegations?: SubAgentDelegation[];
|
|
39
63
|
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Streaming variant. Orchestrators emit `delegation_start` /
|
|
66
|
+
* `delegation_result` chunks around each sub-agent invocation; the
|
|
67
|
+
* sub-agent's own chunks are forwarded byte-by-byte with their
|
|
68
|
+
* `actingAgentId` set so the client renders the member's avatar /
|
|
69
|
+
* name on the right bubble. Non-orchestrator agents short-circuit
|
|
70
|
+
* to the runner's stream.
|
|
71
|
+
*
|
|
72
|
+
* Implementation note: we drive the same Anthropic agentic loop as
|
|
73
|
+
* `runOrchestratorLoop` (no shortcut — the orchestrator's reasoning
|
|
74
|
+
* about WHO to delegate to is still a non-streamed messages.create
|
|
75
|
+
* call). The streaming part is the SUB-AGENT'S response, which is
|
|
76
|
+
* the part the visitor actually cares about seeing in real time.
|
|
77
|
+
*/
|
|
78
|
+
stream(agentId: string, messages: AnthropicMessage[], context: ToolExecutionContext): AsyncGenerator<StreamChunk>;
|
|
40
79
|
private runOrchestratorLoop;
|
|
41
80
|
private buildDelegationTools;
|
|
42
81
|
private getAgent;
|
|
@@ -39,10 +39,32 @@ class OrchestratorService {
|
|
|
39
39
|
baseURL: anthropicConfig.baseURL,
|
|
40
40
|
});
|
|
41
41
|
this.logger = opts.logger ?? noopLogger;
|
|
42
|
+
this.resolveAgentHook = opts.resolveAgent;
|
|
42
43
|
for (const agent of opts.agents) {
|
|
43
44
|
this.agentsMap.set(agent.id, agent);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Lookup with dynamic-resolver fallback. Hits the static map first
|
|
49
|
+
* (built from the constructor's `agents` list — covers the bootstrap
|
|
50
|
+
* use case), then falls back to the host-supplied `resolveAgent`
|
|
51
|
+
* hook (used by Team orchestrators whose members are loaded from
|
|
52
|
+
* the database per-tenant). Resolved agents are cached in the map
|
|
53
|
+
* for the lifetime of the service to avoid re-fetching across a
|
|
54
|
+
* multi-turn conversation.
|
|
55
|
+
*/
|
|
56
|
+
async resolveAgentDynamic(agentId) {
|
|
57
|
+
const cached = this.agentsMap.get(agentId);
|
|
58
|
+
if (cached)
|
|
59
|
+
return cached;
|
|
60
|
+
if (!this.resolveAgentHook)
|
|
61
|
+
return undefined;
|
|
62
|
+
const resolved = await this.resolveAgentHook(agentId);
|
|
63
|
+
if (!resolved)
|
|
64
|
+
return undefined;
|
|
65
|
+
this.agentsMap.set(resolved.id, resolved);
|
|
66
|
+
return resolved;
|
|
67
|
+
}
|
|
46
68
|
/**
|
|
47
69
|
* Run an agent. Orchestrators automatically get delegation tools injected.
|
|
48
70
|
* Non-orchestrator agents fall straight through to the runner.
|
|
@@ -55,6 +77,204 @@ class OrchestratorService {
|
|
|
55
77
|
this.logger.debug(`Running orchestrator "${agentId}" with subagents: ${agent.subAgents.join(', ')}`);
|
|
56
78
|
return this.runOrchestratorLoop(agent, messages, context);
|
|
57
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Streaming variant. Orchestrators emit `delegation_start` /
|
|
82
|
+
* `delegation_result` chunks around each sub-agent invocation; the
|
|
83
|
+
* sub-agent's own chunks are forwarded byte-by-byte with their
|
|
84
|
+
* `actingAgentId` set so the client renders the member's avatar /
|
|
85
|
+
* name on the right bubble. Non-orchestrator agents short-circuit
|
|
86
|
+
* to the runner's stream.
|
|
87
|
+
*
|
|
88
|
+
* Implementation note: we drive the same Anthropic agentic loop as
|
|
89
|
+
* `runOrchestratorLoop` (no shortcut — the orchestrator's reasoning
|
|
90
|
+
* about WHO to delegate to is still a non-streamed messages.create
|
|
91
|
+
* call). The streaming part is the SUB-AGENT'S response, which is
|
|
92
|
+
* the part the visitor actually cares about seeing in real time.
|
|
93
|
+
*/
|
|
94
|
+
async *stream(agentId, messages, context) {
|
|
95
|
+
const agent = (await this.resolveAgentDynamic(agentId)) ?? null;
|
|
96
|
+
if (!agent) {
|
|
97
|
+
throw new OrchestratorError('agent_not_found', `Agent "${agentId}" not found`);
|
|
98
|
+
}
|
|
99
|
+
if (!agent.canOrchestrate || !agent.subAgents?.length) {
|
|
100
|
+
// No-op orchestration — fall straight through. The runner's
|
|
101
|
+
// chunks won't carry `actingAgentId`, which is exactly what we
|
|
102
|
+
// want: this conversation is bound to one agent.
|
|
103
|
+
yield* this.runner.stream(agent, messages, context);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.logger.debug(`Streaming orchestrator "${agentId}" with subagents: ${agent.subAgents.join(', ')}`);
|
|
107
|
+
const delegationTools = this.buildDelegationTools(agent);
|
|
108
|
+
const model = agent.model ?? this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
109
|
+
const maxTokens = agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
110
|
+
const anthropicTools = delegationTools.map((t) => ({
|
|
111
|
+
name: t.name,
|
|
112
|
+
description: t.description,
|
|
113
|
+
input_schema: t.inputSchema,
|
|
114
|
+
}));
|
|
115
|
+
let currentMessages = [...messages];
|
|
116
|
+
let totalUsage = {
|
|
117
|
+
inputTokens: 0,
|
|
118
|
+
outputTokens: 0,
|
|
119
|
+
totalTokens: 0,
|
|
120
|
+
};
|
|
121
|
+
const messageId = (0, crypto_1.randomUUID)();
|
|
122
|
+
// Hard cap on orchestrator loops. Without one, a misbehaving model
|
|
123
|
+
// could ping-pong delegate → think → delegate forever. Three hops
|
|
124
|
+
// is enough for any well-formed team flow; the system prompt also
|
|
125
|
+
// discourages chaining so this is mostly defense in depth.
|
|
126
|
+
const MAX_LOOPS = 3;
|
|
127
|
+
let loopCount = 0;
|
|
128
|
+
while (true) {
|
|
129
|
+
if (loopCount++ > MAX_LOOPS) {
|
|
130
|
+
yield {
|
|
131
|
+
type: 'text_delta',
|
|
132
|
+
delta: '\n\n(Orchestrator stopped: too many delegations in one turn.)',
|
|
133
|
+
};
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
// Orchestrator's planning step — non-streamed. The model picks a
|
|
137
|
+
// member, we forward its prose as a single text_delta so the
|
|
138
|
+
// chat shows the "Routing to …" reasoning, then we drain the
|
|
139
|
+
// tool_use blocks one by one.
|
|
140
|
+
const response = await this.client.messages.create({
|
|
141
|
+
model,
|
|
142
|
+
max_tokens: maxTokens,
|
|
143
|
+
system: agent.systemPrompt,
|
|
144
|
+
messages: currentMessages,
|
|
145
|
+
tools: anthropicTools,
|
|
146
|
+
});
|
|
147
|
+
totalUsage = {
|
|
148
|
+
inputTokens: totalUsage.inputTokens + response.usage.input_tokens,
|
|
149
|
+
outputTokens: totalUsage.outputTokens + response.usage.output_tokens,
|
|
150
|
+
totalTokens: totalUsage.totalTokens +
|
|
151
|
+
response.usage.input_tokens +
|
|
152
|
+
response.usage.output_tokens,
|
|
153
|
+
};
|
|
154
|
+
// Emit orchestrator's own prose (the "Routing to Ana…" line).
|
|
155
|
+
// No actingAgentId — that means "this is the team speaking as
|
|
156
|
+
// itself", which the client renders with the team's branding.
|
|
157
|
+
for (const block of response.content) {
|
|
158
|
+
if (block.type === 'text' && block.text.trim()) {
|
|
159
|
+
yield { type: 'text_delta', delta: block.text };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (response.stop_reason !== 'tool_use') {
|
|
163
|
+
// No delegation this turn — we're done.
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
// Append the orchestrator's tool-use turn so the next loop sees
|
|
167
|
+
// it as part of the planning history.
|
|
168
|
+
const assistantMsg = {
|
|
169
|
+
role: 'assistant',
|
|
170
|
+
content: response.content,
|
|
171
|
+
};
|
|
172
|
+
currentMessages = [...currentMessages, assistantMsg];
|
|
173
|
+
const toolResults = [];
|
|
174
|
+
for (const block of response.content) {
|
|
175
|
+
if (block.type !== 'tool_use')
|
|
176
|
+
continue;
|
|
177
|
+
const delegateTool = delegationTools.find((t) => t.name === block.name);
|
|
178
|
+
if (!delegateTool)
|
|
179
|
+
continue;
|
|
180
|
+
const { task } = block.input;
|
|
181
|
+
const { subAgentId } = delegateTool;
|
|
182
|
+
const subAgent = await this.resolveAgentDynamic(subAgentId);
|
|
183
|
+
// delegation_start carries the member's identity so the client
|
|
184
|
+
// can render the routing hint AND swap the active bubble's
|
|
185
|
+
// avatar before the first text_delta arrives.
|
|
186
|
+
yield {
|
|
187
|
+
type: 'delegation_start',
|
|
188
|
+
subAgentId,
|
|
189
|
+
subAgentName: subAgent?.name,
|
|
190
|
+
subAgentAvatarUrl: undefined,
|
|
191
|
+
task,
|
|
192
|
+
};
|
|
193
|
+
if (!subAgent) {
|
|
194
|
+
// Member was deleted between team config + the orchestrator
|
|
195
|
+
// call. Synthesize a tool_result so the orchestrator can
|
|
196
|
+
// apologize on its next loop.
|
|
197
|
+
const errMsg = `Sub-agent "${subAgentId}" is no longer available.`;
|
|
198
|
+
yield {
|
|
199
|
+
type: 'delegation_result',
|
|
200
|
+
subAgentId,
|
|
201
|
+
result: errMsg,
|
|
202
|
+
};
|
|
203
|
+
toolResults.push({
|
|
204
|
+
type: 'tool_result',
|
|
205
|
+
tool_use_id: block.id,
|
|
206
|
+
content: errMsg,
|
|
207
|
+
});
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
// Stream the sub-agent's reply through the runner, tagging every
|
|
211
|
+
// chunk with the member's id so the client renders it with the
|
|
212
|
+
// member's identity. We accumulate the text body so the
|
|
213
|
+
// orchestrator's next loop can see what the member said.
|
|
214
|
+
const subMessages = [{ role: 'user', content: task }];
|
|
215
|
+
let assembled = '';
|
|
216
|
+
let subUsage = {
|
|
217
|
+
inputTokens: 0,
|
|
218
|
+
outputTokens: 0,
|
|
219
|
+
totalTokens: 0,
|
|
220
|
+
};
|
|
221
|
+
for await (const chunk of this.runner.stream(subAgent, subMessages, {
|
|
222
|
+
...context,
|
|
223
|
+
agentId: subAgentId,
|
|
224
|
+
})) {
|
|
225
|
+
// text_delta is the only chunk type whose body we accumulate
|
|
226
|
+
// for the orchestrator's tool_result. Other chunks (tool
|
|
227
|
+
// calls inside the member, usage updates) we forward to the
|
|
228
|
+
// visitor but don't feed back to the orchestrator.
|
|
229
|
+
if (chunk.type === 'text_delta') {
|
|
230
|
+
assembled += chunk.delta;
|
|
231
|
+
yield { ...chunk, actingAgentId: subAgentId };
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (chunk.type === 'usage') {
|
|
235
|
+
subUsage = chunk.usage;
|
|
236
|
+
yield { ...chunk, actingAgentId: subAgentId };
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (chunk.type === 'done') {
|
|
240
|
+
// Swallow the inner `done` — the OUTER loop emits its own
|
|
241
|
+
// when the whole orchestrator turn ends.
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// Pass-through with identity tag (tool_use_start, tool_result,
|
|
245
|
+
// awaiting_approval, tool_blocked, etc.).
|
|
246
|
+
yield { ...chunk, actingAgentId: subAgentId };
|
|
247
|
+
}
|
|
248
|
+
totalUsage.inputTokens += subUsage.inputTokens;
|
|
249
|
+
totalUsage.outputTokens += subUsage.outputTokens;
|
|
250
|
+
totalUsage.totalTokens += subUsage.totalTokens;
|
|
251
|
+
yield {
|
|
252
|
+
type: 'delegation_result',
|
|
253
|
+
subAgentId,
|
|
254
|
+
result: assembled,
|
|
255
|
+
};
|
|
256
|
+
toolResults.push({
|
|
257
|
+
type: 'tool_result',
|
|
258
|
+
tool_use_id: block.id,
|
|
259
|
+
// Anthropic rejects empty user-message content with a 400. A
|
|
260
|
+
// sub-agent can produce zero text_deltas legitimately (it ran
|
|
261
|
+
// only tools and never spoke) — when that happens we feed a
|
|
262
|
+
// sentinel string back into the orchestrator's next loop so
|
|
263
|
+
// the conversation stays well-formed. The sentinel doubles as
|
|
264
|
+
// a signal the orchestrator can interpret ("the member acted
|
|
265
|
+
// silently — decide whether to ask the user for confirmation").
|
|
266
|
+
content: assembled || '(member completed silently — no textual response)',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Feed the tool results back into the orchestrator's next loop.
|
|
270
|
+
currentMessages = [
|
|
271
|
+
...currentMessages,
|
|
272
|
+
{ role: 'user', content: toolResults },
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
yield { type: 'usage', usage: totalUsage };
|
|
276
|
+
yield { type: 'done', messageId };
|
|
277
|
+
}
|
|
58
278
|
async runOrchestratorLoop(orchestrator, messages, context) {
|
|
59
279
|
const delegations = [];
|
|
60
280
|
const delegationTools = this.buildDelegationTools(orchestrator);
|