@agentforge-io/core 2.0.4 → 2.0.6
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/builtins/current-time.tool.d.ts +21 -0
- package/dist/builtins/current-time.tool.js +135 -0
- package/dist/builtins/index.d.ts +1 -0
- package/dist/builtins/index.js +10 -0
- package/dist/services/agent-runner.service.d.ts +21 -0
- package/dist/services/agent-runner.service.js +44 -9
- package/dist/services/agent.service.d.ts +7 -0
- package/dist/services/agent.service.js +3 -0
- package/dist/services/tool-registry.service.js +17 -1
- package/dist/types/agent.types.d.ts +17 -0
- package/dist/types/config.types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentToolDefinition } from '../types/agent.types';
|
|
2
|
+
/**
|
|
3
|
+
* `current_time` — built-in tool every agent gets for free.
|
|
4
|
+
*
|
|
5
|
+
* Models can't read the system clock. Without this, "what's today's
|
|
6
|
+
* date?" / "what time is it in Bogotá?" answers drift to either the
|
|
7
|
+
* training cutoff (months off) or the host server's UTC, neither of
|
|
8
|
+
* which match the user's expectations. This tool anchors every time
|
|
9
|
+
* question to the agent's configured IANA timezone.
|
|
10
|
+
*
|
|
11
|
+
* Input is optional: when no `timezone` argument is supplied we fall
|
|
12
|
+
* back to `context.agent.timezone` (set by the runner from the
|
|
13
|
+
* AgentDefinition), and finally to `"UTC"` so the call still succeeds
|
|
14
|
+
* on agents with no tz configured.
|
|
15
|
+
*
|
|
16
|
+
* Output is a compact JSON object — easier for the model to quote
|
|
17
|
+
* specific pieces (just the weekday, just the ISO) than a natural-
|
|
18
|
+
* language string would be.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CURRENT_TIME_TOOL_NAME = "current_time";
|
|
21
|
+
export declare function currentTimeTool(): AgentToolDefinition;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CURRENT_TIME_TOOL_NAME = void 0;
|
|
4
|
+
exports.currentTimeTool = currentTimeTool;
|
|
5
|
+
/**
|
|
6
|
+
* `current_time` — built-in tool every agent gets for free.
|
|
7
|
+
*
|
|
8
|
+
* Models can't read the system clock. Without this, "what's today's
|
|
9
|
+
* date?" / "what time is it in Bogotá?" answers drift to either the
|
|
10
|
+
* training cutoff (months off) or the host server's UTC, neither of
|
|
11
|
+
* which match the user's expectations. This tool anchors every time
|
|
12
|
+
* question to the agent's configured IANA timezone.
|
|
13
|
+
*
|
|
14
|
+
* Input is optional: when no `timezone` argument is supplied we fall
|
|
15
|
+
* back to `context.agent.timezone` (set by the runner from the
|
|
16
|
+
* AgentDefinition), and finally to `"UTC"` so the call still succeeds
|
|
17
|
+
* on agents with no tz configured.
|
|
18
|
+
*
|
|
19
|
+
* Output is a compact JSON object — easier for the model to quote
|
|
20
|
+
* specific pieces (just the weekday, just the ISO) than a natural-
|
|
21
|
+
* language string would be.
|
|
22
|
+
*/
|
|
23
|
+
exports.CURRENT_TIME_TOOL_NAME = 'current_time';
|
|
24
|
+
function currentTimeTool() {
|
|
25
|
+
return {
|
|
26
|
+
name: exports.CURRENT_TIME_TOOL_NAME,
|
|
27
|
+
alwaysOn: true,
|
|
28
|
+
description: "Get the current date and time. Use this any time the user asks " +
|
|
29
|
+
'about "now", "today", "the current date", a deadline relative to ' +
|
|
30
|
+
"today, or anything else that depends on knowing the real-world " +
|
|
31
|
+
"wall-clock. The result is anchored to the agent's configured " +
|
|
32
|
+
'timezone unless the caller passes an explicit `timezone` argument.',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
timezone: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Optional IANA timezone (e.g. "America/Bogota", "Europe/Madrid", ' +
|
|
39
|
+
'"Asia/Tokyo"). Defaults to the agent\'s configured timezone.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
},
|
|
44
|
+
execute: async (input, context) => {
|
|
45
|
+
const requested = typeof input.timezone === 'string' && input.timezone.trim()
|
|
46
|
+
? input.timezone.trim()
|
|
47
|
+
: undefined;
|
|
48
|
+
const fallback = context.agent?.timezone || 'UTC';
|
|
49
|
+
const tz = requested ?? fallback;
|
|
50
|
+
const now = new Date();
|
|
51
|
+
let iso;
|
|
52
|
+
let weekday;
|
|
53
|
+
let resolvedTz = tz;
|
|
54
|
+
try {
|
|
55
|
+
// Use Intl to format the offset in the target tz, then assemble an
|
|
56
|
+
// ISO 8601 string that includes the offset. `toISOString()` is UTC
|
|
57
|
+
// only, which would defeat the purpose.
|
|
58
|
+
iso = formatIsoInTimezone(now, tz);
|
|
59
|
+
weekday = new Intl.DateTimeFormat('en-US', {
|
|
60
|
+
weekday: 'long',
|
|
61
|
+
timeZone: tz,
|
|
62
|
+
}).format(now);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Invalid IANA string — fall back to UTC rather than failing the
|
|
66
|
+
// tool call (which would surface as a confusing model error).
|
|
67
|
+
resolvedTz = 'UTC';
|
|
68
|
+
iso = now.toISOString();
|
|
69
|
+
weekday = new Intl.DateTimeFormat('en-US', { weekday: 'long' }).format(now);
|
|
70
|
+
}
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
iso,
|
|
73
|
+
timezone: resolvedTz,
|
|
74
|
+
weekday,
|
|
75
|
+
epochMs: now.getTime(),
|
|
76
|
+
...(requested && requested !== resolvedTz
|
|
77
|
+
? { requestedTimezone: requested, note: 'invalid_timezone_fell_back_to_utc' }
|
|
78
|
+
: {}),
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build an ISO 8601 string that reflects the local wall-clock in `tz`.
|
|
85
|
+
*
|
|
86
|
+
* `Date#toISOString()` always emits UTC ("…Z"), so we hand-compose the
|
|
87
|
+
* parts via Intl. The shape is `YYYY-MM-DDTHH:mm:ss±HH:MM`, the form the
|
|
88
|
+
* model is most likely to have seen in training data.
|
|
89
|
+
*/
|
|
90
|
+
function formatIsoInTimezone(date, tz) {
|
|
91
|
+
// Throws on invalid tz — surfaced as a try/catch in the caller.
|
|
92
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
93
|
+
timeZone: tz,
|
|
94
|
+
year: 'numeric',
|
|
95
|
+
month: '2-digit',
|
|
96
|
+
day: '2-digit',
|
|
97
|
+
hour: '2-digit',
|
|
98
|
+
minute: '2-digit',
|
|
99
|
+
second: '2-digit',
|
|
100
|
+
hour12: false,
|
|
101
|
+
});
|
|
102
|
+
const parts = Object.fromEntries(dtf.formatToParts(date).map((p) => [p.type, p.value]));
|
|
103
|
+
const offset = formatOffset(date, tz);
|
|
104
|
+
// `hour` reports "24" at midnight on some engines — normalize.
|
|
105
|
+
const hour = parts.hour === '24' ? '00' : parts.hour;
|
|
106
|
+
return `${parts.year}-${parts.month}-${parts.day}T${hour}:${parts.minute}:${parts.second}${offset}`;
|
|
107
|
+
}
|
|
108
|
+
function formatOffset(date, tz) {
|
|
109
|
+
// Compute the offset by formatting the same instant in UTC vs target tz
|
|
110
|
+
// and taking the delta. Cheaper than parsing Intl's `shortOffset` token,
|
|
111
|
+
// and avoids engine-specific formatting quirks.
|
|
112
|
+
const tzParts = partsToUtcMs(date, tz);
|
|
113
|
+
const utcParts = partsToUtcMs(date, 'UTC');
|
|
114
|
+
const diffMin = Math.round((tzParts - utcParts) / 60000);
|
|
115
|
+
const sign = diffMin >= 0 ? '+' : '-';
|
|
116
|
+
const abs = Math.abs(diffMin);
|
|
117
|
+
const hh = String(Math.floor(abs / 60)).padStart(2, '0');
|
|
118
|
+
const mm = String(abs % 60).padStart(2, '0');
|
|
119
|
+
return `${sign}${hh}:${mm}`;
|
|
120
|
+
}
|
|
121
|
+
function partsToUtcMs(date, tz) {
|
|
122
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
123
|
+
timeZone: tz,
|
|
124
|
+
year: 'numeric',
|
|
125
|
+
month: '2-digit',
|
|
126
|
+
day: '2-digit',
|
|
127
|
+
hour: '2-digit',
|
|
128
|
+
minute: '2-digit',
|
|
129
|
+
second: '2-digit',
|
|
130
|
+
hour12: false,
|
|
131
|
+
});
|
|
132
|
+
const p = Object.fromEntries(dtf.formatToParts(date).map((pp) => [pp.type, pp.value]));
|
|
133
|
+
const hour = p.hour === '24' ? '00' : p.hour;
|
|
134
|
+
return Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { currentTimeTool, CURRENT_TIME_TOOL_NAME } from './current-time.tool';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Built-in tools — always available to every agent regardless of the
|
|
3
|
+
// `agent.tools[]` whitelist. The runner registers these alongside the
|
|
4
|
+
// host-supplied catalog at boot time. UIs can still show them in the
|
|
5
|
+
// picker (read-only) so users understand the agent can call them.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.CURRENT_TIME_TOOL_NAME = exports.currentTimeTool = void 0;
|
|
8
|
+
var current_time_tool_1 = require("./current-time.tool");
|
|
9
|
+
Object.defineProperty(exports, "currentTimeTool", { enumerable: true, get: function () { return current_time_tool_1.currentTimeTool; } });
|
|
10
|
+
Object.defineProperty(exports, "CURRENT_TIME_TOOL_NAME", { enumerable: true, get: function () { return current_time_tool_1.CURRENT_TIME_TOOL_NAME; } });
|
|
@@ -17,6 +17,27 @@ export declare class AgentRunnerService {
|
|
|
17
17
|
});
|
|
18
18
|
run(agent: AgentDefinition, messages: AnthropicMessage[], context: ToolExecutionContext, overrides?: AgentOverrides): Promise<AgentResponse>;
|
|
19
19
|
stream(agent: AgentDefinition, messages: AnthropicMessage[], context: ToolExecutionContext, overrides?: AgentOverrides): AsyncGenerator<StreamChunk>;
|
|
20
|
+
/**
|
|
21
|
+
* Compose the system prompt for a single Anthropic call:
|
|
22
|
+
*
|
|
23
|
+
* [tool-awareness preamble] ← only when `tools` is non-empty
|
|
24
|
+
* ──────────────────────────
|
|
25
|
+
* agent.systemPrompt ← what the operator wrote
|
|
26
|
+
* ──────────────────────────
|
|
27
|
+
* overrides.systemPromptSuffix ← optional per-call addendum
|
|
28
|
+
*
|
|
29
|
+
* The preamble exists because operator prompts in the wild often pin
|
|
30
|
+
* the model to a narrow persona ("respond in three sentences", "stick
|
|
31
|
+
* to product Q&A") which suppresses tool use even when tools are
|
|
32
|
+
* attached and clearly relevant. The preamble doesn't override the
|
|
33
|
+
* operator — it's a short, generic reminder that listed tools are
|
|
34
|
+
* available and should be consulted when the user's question depends
|
|
35
|
+
* on real-world / current / authoritative information.
|
|
36
|
+
*
|
|
37
|
+
* It's only added when tools are present, so prompt-only agents stay
|
|
38
|
+
* exactly as the operator wrote them.
|
|
39
|
+
*/
|
|
40
|
+
private buildSystemPrompt;
|
|
20
41
|
/**
|
|
21
42
|
* Merge `agent.tools[]` (resolved via the global registry) with any
|
|
22
43
|
* per-call extras (e.g. the user's connector tools).
|
|
@@ -30,10 +30,8 @@ class AgentRunnerService {
|
|
|
30
30
|
const model = overrides?.model ?? agent.model ?? this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
31
31
|
const maxTokens = overrides?.maxTokens ?? agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
32
32
|
const temperature = overrides?.temperature ?? agent.temperature ?? 1;
|
|
33
|
-
const systemPrompt = overrides?.systemPromptSuffix
|
|
34
|
-
? `${agent.systemPrompt}\n\n${overrides.systemPromptSuffix}`
|
|
35
|
-
: agent.systemPrompt;
|
|
36
33
|
const { tools, extras } = this.buildToolList(agent, overrides);
|
|
34
|
+
const systemPrompt = this.buildSystemPrompt(agent, tools, overrides);
|
|
37
35
|
const toolCalls = [];
|
|
38
36
|
let currentMessages = [...messages];
|
|
39
37
|
let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
@@ -117,10 +115,8 @@ class AgentRunnerService {
|
|
|
117
115
|
const model = overrides?.model ?? agent.model ?? this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
118
116
|
const maxTokens = overrides?.maxTokens ?? agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
119
117
|
const temperature = overrides?.temperature ?? agent.temperature ?? 1;
|
|
120
|
-
const systemPrompt = overrides?.systemPromptSuffix
|
|
121
|
-
? `${agent.systemPrompt}\n\n${overrides.systemPromptSuffix}`
|
|
122
|
-
: agent.systemPrompt;
|
|
123
118
|
const { tools, extras } = this.buildToolList(agent, overrides);
|
|
119
|
+
const systemPrompt = this.buildSystemPrompt(agent, tools, overrides);
|
|
124
120
|
let currentMessages = [...messages];
|
|
125
121
|
let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
126
122
|
while (true) {
|
|
@@ -184,6 +180,44 @@ class AgentRunnerService {
|
|
|
184
180
|
yield { type: 'usage', usage: totalUsage };
|
|
185
181
|
yield { type: 'done', messageId };
|
|
186
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Compose the system prompt for a single Anthropic call:
|
|
185
|
+
*
|
|
186
|
+
* [tool-awareness preamble] ← only when `tools` is non-empty
|
|
187
|
+
* ──────────────────────────
|
|
188
|
+
* agent.systemPrompt ← what the operator wrote
|
|
189
|
+
* ──────────────────────────
|
|
190
|
+
* overrides.systemPromptSuffix ← optional per-call addendum
|
|
191
|
+
*
|
|
192
|
+
* The preamble exists because operator prompts in the wild often pin
|
|
193
|
+
* the model to a narrow persona ("respond in three sentences", "stick
|
|
194
|
+
* to product Q&A") which suppresses tool use even when tools are
|
|
195
|
+
* attached and clearly relevant. The preamble doesn't override the
|
|
196
|
+
* operator — it's a short, generic reminder that listed tools are
|
|
197
|
+
* available and should be consulted when the user's question depends
|
|
198
|
+
* on real-world / current / authoritative information.
|
|
199
|
+
*
|
|
200
|
+
* It's only added when tools are present, so prompt-only agents stay
|
|
201
|
+
* exactly as the operator wrote them.
|
|
202
|
+
*/
|
|
203
|
+
buildSystemPrompt(agent, tools, overrides) {
|
|
204
|
+
const parts = [];
|
|
205
|
+
if (tools && tools.length > 0) {
|
|
206
|
+
const names = tools.map((t) => `\`${t.name}\``).join(', ');
|
|
207
|
+
parts.push('You have the following tools available: ' +
|
|
208
|
+
names +
|
|
209
|
+
'. Use them whenever the user’s request depends on information ' +
|
|
210
|
+
'you can’t reliably produce from memory (current data, time, ' +
|
|
211
|
+
'account-specific facts, external APIs). Do not refuse to use a ' +
|
|
212
|
+
'tool because of style or persona instructions further below — ' +
|
|
213
|
+
'those control your voice, not your capabilities. When a tool is ' +
|
|
214
|
+
'clearly relevant, call it before composing the final answer.');
|
|
215
|
+
}
|
|
216
|
+
parts.push(agent.systemPrompt);
|
|
217
|
+
if (overrides?.systemPromptSuffix)
|
|
218
|
+
parts.push(overrides.systemPromptSuffix);
|
|
219
|
+
return parts.join('\n\n');
|
|
220
|
+
}
|
|
187
221
|
/**
|
|
188
222
|
* Merge `agent.tools[]` (resolved via the global registry) with any
|
|
189
223
|
* per-call extras (e.g. the user's connector tools).
|
|
@@ -206,9 +240,10 @@ class AgentRunnerService {
|
|
|
206
240
|
* for example. The shadowing only lasts for this call.
|
|
207
241
|
*/
|
|
208
242
|
buildToolList(agent, overrides) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
243
|
+
// Always invoke the registry — even when the agent has no whitelist —
|
|
244
|
+
// because alwaysOn tools (e.g. `current_time`) get folded in there and
|
|
245
|
+
// need to reach an agent whose `tools[]` is empty.
|
|
246
|
+
const fromRegistry = this.toolRegistry.getToolsForAgent(agent.id, agent.tools ?? []);
|
|
212
247
|
const allowed = new Set(agent.tools ?? []);
|
|
213
248
|
const extras = new Map();
|
|
214
249
|
const extrasSchema = [];
|
|
@@ -41,6 +41,13 @@ export interface AgentRecord {
|
|
|
41
41
|
* caller's userId, preserving the original behavior.
|
|
42
42
|
*/
|
|
43
43
|
connectorOwnerUserId?: string;
|
|
44
|
+
/**
|
|
45
|
+
* IANA timezone (e.g. `"America/Bogota"`, `"Europe/Madrid"`). The
|
|
46
|
+
* built-in `current_time` tool reads this to anchor "what time is it"
|
|
47
|
+
* answers to the agent owner's locale instead of the host server's UTC.
|
|
48
|
+
* Defaults to `"UTC"` when unset.
|
|
49
|
+
*/
|
|
50
|
+
timezone?: string;
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
53
|
* Host-supplied resolver for per-tenant agent configurations. The SDK never
|
|
@@ -167,6 +167,7 @@ class AgentService {
|
|
|
167
167
|
conversationId: params.conversationId,
|
|
168
168
|
agentId: conv.agentId,
|
|
169
169
|
messageId: 'sync',
|
|
170
|
+
agent: { timezone: agent.timezone },
|
|
170
171
|
}, { ...(params.overrides ?? {}), extraTools });
|
|
171
172
|
await this.conversations.addMessage({
|
|
172
173
|
conversationId: params.conversationId,
|
|
@@ -230,6 +231,7 @@ class AgentService {
|
|
|
230
231
|
conversationId: params.conversationId,
|
|
231
232
|
agentId: conv.agentId,
|
|
232
233
|
messageId: 'streaming',
|
|
234
|
+
agent: { timezone: agent.timezone },
|
|
233
235
|
}, { ...(params.overrides ?? {}), extraTools })) {
|
|
234
236
|
if (chunk.type === 'text_delta')
|
|
235
237
|
fullContent += chunk.delta;
|
|
@@ -338,6 +340,7 @@ function toAgentDefinition(record) {
|
|
|
338
340
|
mcpServers: record.mcpServers,
|
|
339
341
|
metadata: record.metadata,
|
|
340
342
|
connectorOwnerUserId: record.connectorOwnerUserId,
|
|
343
|
+
timezone: record.timezone,
|
|
341
344
|
...(record.slug !== undefined ? { slug: record.slug } : {}),
|
|
342
345
|
...(extra.appearance !== undefined ? { appearance: extra.appearance } : {}),
|
|
343
346
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ToolRegistryService = void 0;
|
|
4
|
+
const current_time_tool_1 = require("../builtins/current-time.tool");
|
|
4
5
|
const noopLogger = {
|
|
5
6
|
log: () => { },
|
|
6
7
|
warn: () => { },
|
|
@@ -16,6 +17,10 @@ class ToolRegistryService {
|
|
|
16
17
|
constructor(opts = {}) {
|
|
17
18
|
this.tools = new Map();
|
|
18
19
|
this.logger = opts.logger ?? noopLogger;
|
|
20
|
+
// Built-in essentials registered before host tools so a host can still
|
|
21
|
+
// override by registering the same name (rare — but the explicit Map
|
|
22
|
+
// overwrite warning makes the swap visible).
|
|
23
|
+
this.register((0, current_time_tool_1.currentTimeTool)());
|
|
19
24
|
if (opts.initialTools)
|
|
20
25
|
this.registerMany(opts.initialTools);
|
|
21
26
|
}
|
|
@@ -39,7 +44,18 @@ class ToolRegistryService {
|
|
|
39
44
|
return existed;
|
|
40
45
|
}
|
|
41
46
|
getToolsForAgent(agentId, toolNames) {
|
|
42
|
-
|
|
47
|
+
// Always-on tools (e.g. `current_time`) are offered to every agent
|
|
48
|
+
// regardless of the whitelist — they're runtime essentials the model
|
|
49
|
+
// needs to answer correctly. Per-agent allowlists still apply.
|
|
50
|
+
const requested = new Set(toolNames);
|
|
51
|
+
for (const t of this.tools.values()) {
|
|
52
|
+
if (!t.alwaysOn)
|
|
53
|
+
continue;
|
|
54
|
+
if (t.agents && t.agents.length > 0 && !t.agents.includes(agentId))
|
|
55
|
+
continue;
|
|
56
|
+
requested.add(t.name);
|
|
57
|
+
}
|
|
58
|
+
return Array.from(requested)
|
|
43
59
|
.filter((name) => {
|
|
44
60
|
const tool = this.tools.get(name);
|
|
45
61
|
if (!tool) {
|
|
@@ -97,6 +97,14 @@ export interface AgentToolDefinition {
|
|
|
97
97
|
/** MCP server this tool was discovered from. Set by McpClientService when
|
|
98
98
|
* wrapping a remote tool; left undefined for built-ins and connector tools. */
|
|
99
99
|
mcpServerName?: string;
|
|
100
|
+
/**
|
|
101
|
+
* When true, the tool is offered to every agent regardless of whether the
|
|
102
|
+
* agent's `tools[]` whitelist mentions it. Used for runtime essentials
|
|
103
|
+
* like `current_time` that should never silently be off — the model
|
|
104
|
+
* needs them to answer correctly. UIs can still surface them in the
|
|
105
|
+
* picker as informational rows.
|
|
106
|
+
*/
|
|
107
|
+
alwaysOn?: boolean;
|
|
100
108
|
/** Owning tenantId for tenant-scoped tools (MCP servers registered per
|
|
101
109
|
* tenant). Used by `/tools` to filter results to the caller's tenant so
|
|
102
110
|
* the catalog doesn't leak another workspace's rows. Built-ins leave
|
|
@@ -108,6 +116,15 @@ export interface ToolExecutionContext {
|
|
|
108
116
|
conversationId: string;
|
|
109
117
|
agentId: string;
|
|
110
118
|
messageId: string;
|
|
119
|
+
/**
|
|
120
|
+
* Agent-level configuration the tool may consult — e.g. `current_time`
|
|
121
|
+
* reads `timezone` to anchor results to the agent owner's locale. Set
|
|
122
|
+
* by the runner when it dispatches; tools should treat any field as
|
|
123
|
+
* optional and fall back to sensible defaults.
|
|
124
|
+
*/
|
|
125
|
+
agent?: {
|
|
126
|
+
timezone?: string;
|
|
127
|
+
};
|
|
111
128
|
}
|
|
112
129
|
export type AnthropicMessage = Anthropic.MessageParam;
|
|
113
130
|
export type AnthropicTool = Anthropic.Tool;
|
|
@@ -103,6 +103,12 @@ export interface AgentDefinition {
|
|
|
103
103
|
* userId, preserving the original behavior.
|
|
104
104
|
*/
|
|
105
105
|
connectorOwnerUserId?: string;
|
|
106
|
+
/**
|
|
107
|
+
* IANA timezone string (e.g. `"America/Bogota"`). The built-in
|
|
108
|
+
* `current_time` tool reads this so date/time questions resolve to the
|
|
109
|
+
* agent owner's locale instead of UTC. Defaults to `"UTC"` when unset.
|
|
110
|
+
*/
|
|
111
|
+
timezone?: string;
|
|
106
112
|
}
|
|
107
113
|
/**
|
|
108
114
|
* Configuration for an MCP server the runtime should connect to.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
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",
|