@agentforge-io/core 2.0.23 → 2.1.0
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/ai/index.d.ts +2 -0
- package/dist/ai/index.js +5 -1
- 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 +117 -7
- package/dist/services/agent.service.d.ts +21 -1
- package/dist/services/agent.service.js +77 -10
- 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/dist/types/config.types.d.ts +8 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/model-strategy.d.ts +97 -0
- package/dist/types/model-strategy.js +83 -0
- 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/ai/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { AGENT_FORGE_CONFIG, AGENT_QUEUE_NAME, CURRENT_USER, } from '../constant
|
|
|
2
2
|
export type { AgentDefinition, AnthropicConfig, McpServerConfig, AgentForgeConfig, DatabaseConfig, RedisConfig, QueueConfig, } from '../types/config.types';
|
|
3
3
|
export type { AgentResponse, AgentOverrides, StreamChunk, TokenUsage, ToolCallRecord, AgentToolDefinition, AgentJobPayload, AgentJobResult, AnthropicMessage, } from '../types/agent.types';
|
|
4
4
|
export type { SdkHooks, UsageEvent, TurnCompleteEvent, ToolCallEvent, } from '../types/hooks';
|
|
5
|
+
export type { ModelStrategy, ModelTier, EscalateRule, FallbackRule, TurnSignals, ModelSelection, } from '../types/model-strategy';
|
|
6
|
+
export { selectModel, DEFAULT_LONG_CONTEXT_TOKENS, DEFAULT_SHORT_INPUT_TOKENS, } from '../types/model-strategy';
|
|
5
7
|
export { ToolRegistryService, type Logger } from '../services/tool-registry.service';
|
|
6
8
|
export { AgentRunnerService } from '../services/agent-runner.service';
|
|
7
9
|
export { OrchestratorService } from '../services/orchestrator.service';
|
package/dist/ai/index.js
CHANGED
|
@@ -8,12 +8,16 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Files still co-located physically; this is a logical seam.
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.InMemoryJobQueue = exports.JOB_QUEUE = exports.InMemoryPreparedStreamStore = exports.PREPARED_STREAM_STORE = exports.AgentJobWorker = exports.AgentForbiddenError = exports.AgentService = exports.ConversationNotFoundError = exports.ConversationService = exports.PreparedStreamError = exports.PreparedStreamService = exports.OrchestratorService = exports.AgentRunnerService = exports.ToolRegistryService = exports.CURRENT_USER = exports.AGENT_QUEUE_NAME = exports.AGENT_FORGE_CONFIG = void 0;
|
|
11
|
+
exports.InMemoryJobQueue = exports.JOB_QUEUE = exports.InMemoryPreparedStreamStore = exports.PREPARED_STREAM_STORE = exports.AgentJobWorker = exports.AgentForbiddenError = exports.AgentService = exports.ConversationNotFoundError = exports.ConversationService = exports.PreparedStreamError = exports.PreparedStreamService = exports.OrchestratorService = exports.AgentRunnerService = exports.ToolRegistryService = exports.DEFAULT_SHORT_INPUT_TOKENS = exports.DEFAULT_LONG_CONTEXT_TOKENS = exports.selectModel = exports.CURRENT_USER = exports.AGENT_QUEUE_NAME = exports.AGENT_FORGE_CONFIG = void 0;
|
|
12
12
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
13
13
|
var constants_1 = require("../constants");
|
|
14
14
|
Object.defineProperty(exports, "AGENT_FORGE_CONFIG", { enumerable: true, get: function () { return constants_1.AGENT_FORGE_CONFIG; } });
|
|
15
15
|
Object.defineProperty(exports, "AGENT_QUEUE_NAME", { enumerable: true, get: function () { return constants_1.AGENT_QUEUE_NAME; } });
|
|
16
16
|
Object.defineProperty(exports, "CURRENT_USER", { enumerable: true, get: function () { return constants_1.CURRENT_USER; } });
|
|
17
|
+
var model_strategy_1 = require("../types/model-strategy");
|
|
18
|
+
Object.defineProperty(exports, "selectModel", { enumerable: true, get: function () { return model_strategy_1.selectModel; } });
|
|
19
|
+
Object.defineProperty(exports, "DEFAULT_LONG_CONTEXT_TOKENS", { enumerable: true, get: function () { return model_strategy_1.DEFAULT_LONG_CONTEXT_TOKENS; } });
|
|
20
|
+
Object.defineProperty(exports, "DEFAULT_SHORT_INPUT_TOKENS", { enumerable: true, get: function () { return model_strategy_1.DEFAULT_SHORT_INPUT_TOKENS; } });
|
|
17
21
|
// ─── Services ──────────────────────────────────────────────────────────────
|
|
18
22
|
var tool_registry_service_1 = require("../services/tool-registry.service");
|
|
19
23
|
Object.defineProperty(exports, "ToolRegistryService", { enumerable: true, get: function () { return tool_registry_service_1.ToolRegistryService; } });
|
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; } });
|
|
@@ -7,6 +7,7 @@ exports.AgentRunnerService = void 0;
|
|
|
7
7
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
9
|
const tool_approval_gate_1 = require("./tool-approval-gate");
|
|
10
|
+
const model_strategy_1 = require("../types/model-strategy");
|
|
10
11
|
const noopLogger = {
|
|
11
12
|
log: () => { }, warn: () => { }, debug: () => { }, error: () => { },
|
|
12
13
|
};
|
|
@@ -29,25 +30,64 @@ class AgentRunnerService {
|
|
|
29
30
|
// ─── Run (non-streaming) ──────────────────────────────────────────────────
|
|
30
31
|
async run(agent, messages, context, overrides) {
|
|
31
32
|
const messageId = (0, crypto_1.randomUUID)();
|
|
32
|
-
const
|
|
33
|
+
const runnerDefault = this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
34
|
+
const baseModel = overrides?.model ?? agent.model ?? runnerDefault;
|
|
33
35
|
const maxTokens = overrides?.maxTokens ?? agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
34
|
-
|
|
36
|
+
// Anthropic's newer models (Sonnet 4.6+, Haiku 4.5+) reject
|
|
37
|
+
// `temperature` when tools are present — they auto-tune sampling for
|
|
38
|
+
// tool use. Only forward it when the operator/caller declared one
|
|
39
|
+
// explicitly; never inject a default. Old models that required it
|
|
40
|
+
// accept its absence too (they fall back to their own internal
|
|
41
|
+
// default of 1.0).
|
|
42
|
+
const temperature = overrides?.temperature ?? agent.temperature;
|
|
35
43
|
const { tools, extras } = this.buildToolList(agent, overrides);
|
|
36
44
|
const systemPrompt = this.buildSystemPrompt(agent, tools, overrides);
|
|
37
45
|
const toolCalls = [];
|
|
38
46
|
let currentMessages = [...messages];
|
|
47
|
+
// Pre-compute the signals the model router reads. `hasTools` and
|
|
48
|
+
// `hasApprovalTool` are constant across the agentic loop (we
|
|
49
|
+
// don't add tools mid-conversation); `estimatedInputTokens`
|
|
50
|
+
// starts from a word-count heuristic and gets replaced by the
|
|
51
|
+
// real `usage.input_tokens` once we have a response.
|
|
52
|
+
const turnSignals = {
|
|
53
|
+
hasTools: !!tools && tools.length > 0,
|
|
54
|
+
hasApprovalTool: hasApprovalGatedTool(agent),
|
|
55
|
+
estimatedInputTokens: estimateInputTokens(systemPrompt, currentMessages),
|
|
56
|
+
};
|
|
39
57
|
let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
40
58
|
let finalContent = '';
|
|
41
59
|
let stopReason = 'end_turn';
|
|
60
|
+
// Last model id the router chose. Surfaced on the response so
|
|
61
|
+
// callers (and the conversation/usage logs) record what
|
|
62
|
+
// actually ran, not what the agent's `model` field says.
|
|
63
|
+
let lastModel = baseModel;
|
|
42
64
|
while (true) {
|
|
65
|
+
// Per-turn model selection. When overrides force a model we
|
|
66
|
+
// honour it (manual `agent.runMessage({ overrides: { model }})`
|
|
67
|
+
// beats the strategy). Otherwise the strategy decides; absent
|
|
68
|
+
// strategy → behave exactly like before this feature landed.
|
|
69
|
+
const selection = overrides?.model
|
|
70
|
+
? { model: overrides.model, reason: 'forced' }
|
|
71
|
+
: (0, model_strategy_1.selectModel)(agent.modelStrategy, turnSignals, baseModel);
|
|
72
|
+
const model = selection.model;
|
|
73
|
+
lastModel = model;
|
|
74
|
+
if (this.logger && selection.reason !== 'default' && selection.reason !== 'forced') {
|
|
75
|
+
this.logger.debug(`[modelRouter] agent=${agent.id} ${selection.reason}=${selection.trigger} → ${model}`);
|
|
76
|
+
}
|
|
43
77
|
const response = await this.client.messages.create({
|
|
44
78
|
model,
|
|
45
79
|
max_tokens: maxTokens,
|
|
46
|
-
temperature
|
|
80
|
+
// Only include temperature when explicitly declared — newer
|
|
81
|
+
// models 400 on `temperature` when tools are present.
|
|
82
|
+
...(typeof temperature === 'number' ? { temperature } : {}),
|
|
47
83
|
system: systemPrompt,
|
|
48
84
|
messages: currentMessages,
|
|
49
85
|
tools: tools,
|
|
50
86
|
});
|
|
87
|
+
// Update the signal for the NEXT iteration of the loop — the
|
|
88
|
+
// tool-result feedback we're about to add can balloon the
|
|
89
|
+
// context past the long-context threshold.
|
|
90
|
+
turnSignals.estimatedInputTokens = response.usage.input_tokens;
|
|
51
91
|
totalUsage = {
|
|
52
92
|
inputTokens: totalUsage.inputTokens + response.usage.input_tokens,
|
|
53
93
|
outputTokens: totalUsage.outputTokens + response.usage.output_tokens,
|
|
@@ -117,7 +157,7 @@ class AgentRunnerService {
|
|
|
117
157
|
role: 'assistant',
|
|
118
158
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
119
159
|
usage: totalUsage,
|
|
120
|
-
model,
|
|
160
|
+
model: lastModel,
|
|
121
161
|
stopReason,
|
|
122
162
|
createdAt: new Date(),
|
|
123
163
|
};
|
|
@@ -125,18 +165,38 @@ class AgentRunnerService {
|
|
|
125
165
|
// ─── Run (streaming) ──────────────────────────────────────────────────────
|
|
126
166
|
async *stream(agent, messages, context, overrides) {
|
|
127
167
|
const messageId = (0, crypto_1.randomUUID)();
|
|
128
|
-
const
|
|
168
|
+
const runnerDefault = this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
169
|
+
const baseModel = overrides?.model ?? agent.model ?? runnerDefault;
|
|
129
170
|
const maxTokens = overrides?.maxTokens ?? agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
130
|
-
|
|
171
|
+
// Anthropic's newer models (Sonnet 4.6+, Haiku 4.5+) reject
|
|
172
|
+
// `temperature` when tools are present — they auto-tune sampling for
|
|
173
|
+
// tool use. Only forward it when the operator/caller declared one
|
|
174
|
+
// explicitly; never inject a default. Old models that required it
|
|
175
|
+
// accept its absence too (they fall back to their own internal
|
|
176
|
+
// default of 1.0).
|
|
177
|
+
const temperature = overrides?.temperature ?? agent.temperature;
|
|
131
178
|
const { tools, extras } = this.buildToolList(agent, overrides);
|
|
132
179
|
const systemPrompt = this.buildSystemPrompt(agent, tools, overrides);
|
|
133
180
|
let currentMessages = [...messages];
|
|
134
181
|
let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
182
|
+
// See `run()` above for the rationale on these signals.
|
|
183
|
+
const turnSignals = {
|
|
184
|
+
hasTools: !!tools && tools.length > 0,
|
|
185
|
+
hasApprovalTool: hasApprovalGatedTool(agent),
|
|
186
|
+
estimatedInputTokens: estimateInputTokens(systemPrompt, currentMessages),
|
|
187
|
+
};
|
|
135
188
|
while (true) {
|
|
189
|
+
const selection = overrides?.model
|
|
190
|
+
? { model: overrides.model, reason: 'forced' }
|
|
191
|
+
: (0, model_strategy_1.selectModel)(agent.modelStrategy, turnSignals, baseModel);
|
|
192
|
+
const model = selection.model;
|
|
193
|
+
if (this.logger && selection.reason !== 'default' && selection.reason !== 'forced') {
|
|
194
|
+
this.logger.debug(`[modelRouter] agent=${agent.id} ${selection.reason}=${selection.trigger} → ${model}`);
|
|
195
|
+
}
|
|
136
196
|
const stream = this.client.messages.stream({
|
|
137
197
|
model,
|
|
138
198
|
max_tokens: maxTokens,
|
|
139
|
-
temperature,
|
|
199
|
+
...(typeof temperature === 'number' ? { temperature } : {}),
|
|
140
200
|
system: systemPrompt,
|
|
141
201
|
messages: currentMessages,
|
|
142
202
|
tools: tools,
|
|
@@ -168,6 +228,12 @@ class AgentRunnerService {
|
|
|
168
228
|
}
|
|
169
229
|
}
|
|
170
230
|
const finalMessage = await stream.finalMessage();
|
|
231
|
+
// Refresh the input-token signal so the next iteration of the
|
|
232
|
+
// tool loop has the post-tool-result context length, not the
|
|
233
|
+
// initial estimate.
|
|
234
|
+
if (typeof finalMessage.usage?.input_tokens === 'number') {
|
|
235
|
+
turnSignals.estimatedInputTokens = finalMessage.usage.input_tokens;
|
|
236
|
+
}
|
|
171
237
|
if (finalMessage.stop_reason === 'tool_use') {
|
|
172
238
|
currentMessages = [...currentMessages, { role: 'assistant', content: finalMessage.content }];
|
|
173
239
|
const toolResults = [];
|
|
@@ -311,3 +377,47 @@ class AgentRunnerService {
|
|
|
311
377
|
}
|
|
312
378
|
}
|
|
313
379
|
exports.AgentRunnerService = AgentRunnerService;
|
|
380
|
+
/** Cheap word-based token estimate for the first turn — Anthropic
|
|
381
|
+
* reports real `usage.input_tokens` from the response onwards, so
|
|
382
|
+
* this only needs to be accurate enough to fire `longContext` /
|
|
383
|
+
* `shortInput` rules on the FIRST request before any usage is back.
|
|
384
|
+
* Rough rule of thumb (verified against Anthropic's tokenizer on
|
|
385
|
+
* English prose): ~0.75 tokens per whitespace-split word. */
|
|
386
|
+
function estimateInputTokens(systemPrompt, messages) {
|
|
387
|
+
let words = systemPrompt.trim().split(/\s+/).filter(Boolean).length;
|
|
388
|
+
for (const m of messages) {
|
|
389
|
+
if (typeof m.content === 'string') {
|
|
390
|
+
words += m.content.trim().split(/\s+/).filter(Boolean).length;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (Array.isArray(m.content)) {
|
|
394
|
+
for (const block of m.content) {
|
|
395
|
+
if (block &&
|
|
396
|
+
typeof block === 'object' &&
|
|
397
|
+
'type' in block &&
|
|
398
|
+
block.type === 'text' &&
|
|
399
|
+
typeof block.text === 'string') {
|
|
400
|
+
words += block.text
|
|
401
|
+
.trim()
|
|
402
|
+
.split(/\s+/)
|
|
403
|
+
.filter(Boolean).length;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return Math.ceil(words / 0.75);
|
|
409
|
+
}
|
|
410
|
+
/** True when at least one declared tool requires human approval at
|
|
411
|
+
* runtime. Read off `agent.tools` only — `extraTools` (per-call
|
|
412
|
+
* connector tools attached at request time) follow the host's own
|
|
413
|
+
* per-tenant policy and aren't visible to this scope. */
|
|
414
|
+
function hasApprovalGatedTool(agent) {
|
|
415
|
+
const tools = agent.tools;
|
|
416
|
+
if (!tools)
|
|
417
|
+
return false;
|
|
418
|
+
for (const t of tools) {
|
|
419
|
+
if (typeof t === 'object' && t && t.mode === 'approval')
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
@@ -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
|
|
@@ -212,7 +219,12 @@ class AgentService {
|
|
|
212
219
|
// caller's userId, which is the historical personal-agent path.
|
|
213
220
|
const resolvedExtras = await this.resolveExtraTools(agent.connectorOwnerUserId ?? params.userId);
|
|
214
221
|
const filter = params.overrides?.extraToolsFilter;
|
|
215
|
-
const
|
|
222
|
+
const fromConnectors = filter && resolvedExtras ? filter(resolvedExtras) : resolvedExtras;
|
|
223
|
+
// Merge connector tools with whatever the caller passed in
|
|
224
|
+
// `overrides.extraTools` (e.g. the remote-tool wrappers from
|
|
225
|
+
// ChatStreamController). Caller wins on name collisions so an
|
|
226
|
+
// explicit override always trumps an inherited connector tool.
|
|
227
|
+
const extraTools = mergeExtraTools(params.overrides?.extraTools, fromConnectors);
|
|
216
228
|
const response = await this.runner.run(agent, messages, {
|
|
217
229
|
userId: params.userId,
|
|
218
230
|
conversationId: params.conversationId,
|
|
@@ -278,15 +290,33 @@ class AgentService {
|
|
|
278
290
|
// toolbelt regardless of which visitor session is streaming.
|
|
279
291
|
const resolvedExtras = await this.resolveExtraTools(agent.connectorOwnerUserId ?? params.userId);
|
|
280
292
|
const filter = params.overrides?.extraToolsFilter;
|
|
281
|
-
const
|
|
293
|
+
const fromConnectors = filter && resolvedExtras ? filter(resolvedExtras) : resolvedExtras;
|
|
294
|
+
const extraTools = mergeExtraTools(params.overrides?.extraTools, fromConnectors);
|
|
282
295
|
try {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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) {
|
|
290
320
|
if (chunk.type === 'text_delta')
|
|
291
321
|
fullContent += chunk.delta;
|
|
292
322
|
if (chunk.type === 'usage')
|
|
@@ -505,7 +535,44 @@ function toAgentDefinition(record) {
|
|
|
505
535
|
// from the host's resolver to the SDK and every agent reads as
|
|
506
536
|
// `undefined` (i.e. public).
|
|
507
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
|
+
: {}),
|
|
508
548
|
...(record.slug !== undefined ? { slug: record.slug } : {}),
|
|
509
549
|
...(extra.appearance !== undefined ? { appearance: extra.appearance } : {}),
|
|
510
550
|
};
|
|
511
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* Merge two `extraTools` arrays so an explicit caller-provided list
|
|
554
|
+
* (e.g. remote-tool wrappers from a chat-stream controller) doesn't get
|
|
555
|
+
* shadowed by an `undefined` result from the connector resolver.
|
|
556
|
+
*
|
|
557
|
+
* Caller wins on name collisions — the caller passed the tool
|
|
558
|
+
* deliberately and knows the host context; an inherited connector tool
|
|
559
|
+
* with the same name is almost certainly stale or coincidental.
|
|
560
|
+
*
|
|
561
|
+
* Returns `undefined` (not `[]`) when both inputs are empty so the
|
|
562
|
+
* runner's "if (!extras?.length) skip overrides" path keeps working.
|
|
563
|
+
*/
|
|
564
|
+
function mergeExtraTools(caller, connectors) {
|
|
565
|
+
if (!caller?.length && !connectors?.length)
|
|
566
|
+
return undefined;
|
|
567
|
+
if (!caller?.length)
|
|
568
|
+
return connectors;
|
|
569
|
+
if (!connectors?.length)
|
|
570
|
+
return caller;
|
|
571
|
+
const callerNames = new Set(caller.map((t) => t.name));
|
|
572
|
+
const merged = [...caller];
|
|
573
|
+
for (const t of connectors) {
|
|
574
|
+
if (!callerNames.has(t.name))
|
|
575
|
+
merged.push(t);
|
|
576
|
+
}
|
|
577
|
+
return merged;
|
|
578
|
+
}
|
|
@@ -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;
|