@agentforge-io/core 3.0.0 → 3.0.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.
|
@@ -6,12 +6,15 @@ import type { AgentToolDefinition } from '../types/agent.types';
|
|
|
6
6
|
* date?" / "what time is it in Bogotá?" answers drift to either the
|
|
7
7
|
* training cutoff (months off) or the host server's UTC, neither of
|
|
8
8
|
* which match the user's expectations. This tool anchors every time
|
|
9
|
-
* question to the
|
|
9
|
+
* question to the right IANA timezone via a fallback chain:
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* 1. The explicit `timezone` argument if the model passed one.
|
|
12
|
+
* 2. `context.agent.timezone` — set by the runner from the agent
|
|
13
|
+
* record (per-agent override).
|
|
14
|
+
* 3. `context.tenant.timezone` — set by the host's TenantResolver,
|
|
15
|
+
* so the workspace default covers every agent that hasn't been
|
|
16
|
+
* individually configured.
|
|
17
|
+
* 4. `"UTC"` as a last-resort floor so the call always succeeds.
|
|
15
18
|
*
|
|
16
19
|
* Output is a compact JSON object — easier for the model to quote
|
|
17
20
|
* specific pieces (just the weekday, just the ISO) than a natural-
|
|
@@ -9,12 +9,15 @@ exports.currentTimeTool = currentTimeTool;
|
|
|
9
9
|
* date?" / "what time is it in Bogotá?" answers drift to either the
|
|
10
10
|
* training cutoff (months off) or the host server's UTC, neither of
|
|
11
11
|
* which match the user's expectations. This tool anchors every time
|
|
12
|
-
* question to the
|
|
12
|
+
* question to the right IANA timezone via a fallback chain:
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* 1. The explicit `timezone` argument if the model passed one.
|
|
15
|
+
* 2. `context.agent.timezone` — set by the runner from the agent
|
|
16
|
+
* record (per-agent override).
|
|
17
|
+
* 3. `context.tenant.timezone` — set by the host's TenantResolver,
|
|
18
|
+
* so the workspace default covers every agent that hasn't been
|
|
19
|
+
* individually configured.
|
|
20
|
+
* 4. `"UTC"` as a last-resort floor so the call always succeeds.
|
|
18
21
|
*
|
|
19
22
|
* Output is a compact JSON object — easier for the model to quote
|
|
20
23
|
* specific pieces (just the weekday, just the ISO) than a natural-
|
|
@@ -25,18 +28,27 @@ function currentTimeTool() {
|
|
|
25
28
|
return {
|
|
26
29
|
name: exports.CURRENT_TIME_TOOL_NAME,
|
|
27
30
|
alwaysOn: true,
|
|
28
|
-
description:
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
'
|
|
31
|
+
description: 'Look up the current wall-clock date and time silently, for use in ' +
|
|
32
|
+
'your own reasoning. Call this whenever an answer depends on knowing ' +
|
|
33
|
+
'the real-world moment — greetings keyed to time of day, deadlines ' +
|
|
34
|
+
'relative to "today", business-hours decisions, scheduling, or any ' +
|
|
35
|
+
'user phrasing like "now", "today", "this week". ' +
|
|
36
|
+
"The result is anchored to the agent's configured timezone (or the " +
|
|
37
|
+
'workspace default) unless the caller supplies an explicit ' +
|
|
38
|
+
'`timezone` argument. ' +
|
|
39
|
+
'IMPORTANT: do NOT recite the timestamp back to the user verbatim ' +
|
|
40
|
+
'unless they explicitly asked what time or date it is. Use the value ' +
|
|
41
|
+
'to inform your reply (e.g. choose "Good evening" vs "Good morning", ' +
|
|
42
|
+
'pick the right greeting, compute "in 3 days"); quoting the raw ' +
|
|
43
|
+
'clock reading in every message reads as robotic.',
|
|
33
44
|
inputSchema: {
|
|
34
45
|
type: 'object',
|
|
35
46
|
properties: {
|
|
36
47
|
timezone: {
|
|
37
48
|
type: 'string',
|
|
38
49
|
description: 'Optional IANA timezone (e.g. "America/Bogota", "Europe/Madrid", ' +
|
|
39
|
-
'"Asia/Tokyo"). Defaults to the agent\'s configured timezone
|
|
50
|
+
'"Asia/Tokyo"). Defaults to the agent\'s configured timezone, ' +
|
|
51
|
+
"then the tenant's default timezone, then UTC.",
|
|
40
52
|
},
|
|
41
53
|
},
|
|
42
54
|
additionalProperties: false,
|
|
@@ -45,7 +57,7 @@ function currentTimeTool() {
|
|
|
45
57
|
const requested = typeof input.timezone === 'string' && input.timezone.trim()
|
|
46
58
|
? input.timezone.trim()
|
|
47
59
|
: undefined;
|
|
48
|
-
const fallback = context.agent?.timezone || 'UTC';
|
|
60
|
+
const fallback = context.agent?.timezone || context.tenant?.timezone || 'UTC';
|
|
49
61
|
const tz = requested ?? fallback;
|
|
50
62
|
const now = new Date();
|
|
51
63
|
let iso;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ 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 './providers';
|
|
13
13
|
export * from './services';
|
|
14
|
-
export type { AgentResolver, AgentRecord, AgentResolveParams, } from './services/agent.service';
|
|
14
|
+
export type { AgentResolver, AgentRecord, AgentResolveParams, TenantResolver, TenantContext, } from './services/agent.service';
|
|
15
15
|
export { toAgentDefinition } from './services/agent.service';
|
|
16
16
|
export { createAgentForge, type CreateAgentForgeOptions, type AgentForgeContainer, type AgentForgeRepositories, type AgentForgeAdapters, } from './factory';
|
|
@@ -76,6 +76,31 @@ export interface AgentResolver {
|
|
|
76
76
|
findByIdForTenant(id: string, tenantId: string): Promise<AgentRecord | null>;
|
|
77
77
|
findById(id: string): Promise<AgentRecord | null>;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Tenant-level context the SDK passes to tools. The host owns tenant
|
|
81
|
+
* records; the SDK doesn't query them directly. Implement this
|
|
82
|
+
* interface in the host and register it via
|
|
83
|
+
* `AgentService.setTenantResolver(...)` so the runner can populate
|
|
84
|
+
* `ToolExecutionContext.tenant` for tools like `current_time` that
|
|
85
|
+
* need a workspace-wide default (e.g. timezone) when the agent itself
|
|
86
|
+
* has none configured.
|
|
87
|
+
*
|
|
88
|
+
* Returning `null` means "no tenant context available" — tools fall
|
|
89
|
+
* through to their own defaults (UTC for `current_time`). Returning a
|
|
90
|
+
* partial object is fine; the SDK only reads the fields tools ask for.
|
|
91
|
+
*/
|
|
92
|
+
export interface TenantResolver {
|
|
93
|
+
findById(tenantId: string): Promise<TenantContext | null>;
|
|
94
|
+
}
|
|
95
|
+
/** Minimal tenant-level context the SDK forwards to tools today. Add
|
|
96
|
+
* fields here as new tools start consuming tenant settings; keep them
|
|
97
|
+
* optional to preserve compatibility with hosts that don't populate
|
|
98
|
+
* every field. */
|
|
99
|
+
export interface TenantContext {
|
|
100
|
+
/** IANA timezone (e.g. `"America/Bogota"`). Used by `current_time`
|
|
101
|
+
* as the workspace default when the agent has none. */
|
|
102
|
+
timezone?: string;
|
|
103
|
+
}
|
|
79
104
|
export interface AgentNotFoundError extends Error {
|
|
80
105
|
status: 404;
|
|
81
106
|
}
|
|
@@ -141,6 +166,23 @@ export declare class AgentService {
|
|
|
141
166
|
* `delegate_to_*` synthetic tools fire. Standalone agents always
|
|
142
167
|
* go straight to the runner. */
|
|
143
168
|
orchestrator?: import("./orchestrator.service").OrchestratorService | undefined);
|
|
169
|
+
/** Optional host-supplied tenant resolver. When wired, every
|
|
170
|
+
* streamMessage / sendMessage attaches `tenant: { timezone }` (and
|
|
171
|
+
* whatever else TenantContext grows) to the ToolExecutionContext so
|
|
172
|
+
* tools like `current_time` can fall back to the workspace default.
|
|
173
|
+
* Left undefined for SDK consumers without a tenant model. */
|
|
174
|
+
private tenantResolver?;
|
|
175
|
+
/** Register the host's tenant resolver. Called once at module init
|
|
176
|
+
* (after construction so we don't bloat the positional constructor
|
|
177
|
+
* arg list for every SDK consumer). Idempotent — calling twice
|
|
178
|
+
* overwrites the previous resolver. */
|
|
179
|
+
setTenantResolver(resolver: TenantResolver): void;
|
|
180
|
+
/** Load the tenant context (currently just timezone) for an agent
|
|
181
|
+
* record. Returns `undefined` when the agent has no tenantId, no
|
|
182
|
+
* resolver is wired, or the lookup throws / returns null — in every
|
|
183
|
+
* one of those cases the tool falls back to its own defaults so a
|
|
184
|
+
* resolver outage never breaks the turn. */
|
|
185
|
+
private loadTenantContext;
|
|
144
186
|
/** Public re-export of `resolveExtraTools` keyed by agentId. The
|
|
145
187
|
* orchestrator uses this via `setExtraToolsResolver` to hydrate
|
|
146
188
|
* sub-agents' connector tools at delegation time. We synthesize a
|
|
@@ -64,6 +64,33 @@ class AgentService {
|
|
|
64
64
|
return this.resolveExtraToolsForAgent(agentId, userId);
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
+
/** Register the host's tenant resolver. Called once at module init
|
|
68
|
+
* (after construction so we don't bloat the positional constructor
|
|
69
|
+
* arg list for every SDK consumer). Idempotent — calling twice
|
|
70
|
+
* overwrites the previous resolver. */
|
|
71
|
+
setTenantResolver(resolver) {
|
|
72
|
+
this.tenantResolver = resolver;
|
|
73
|
+
}
|
|
74
|
+
/** Load the tenant context (currently just timezone) for an agent
|
|
75
|
+
* record. Returns `undefined` when the agent has no tenantId, no
|
|
76
|
+
* resolver is wired, or the lookup throws / returns null — in every
|
|
77
|
+
* one of those cases the tool falls back to its own defaults so a
|
|
78
|
+
* resolver outage never breaks the turn. */
|
|
79
|
+
async loadTenantContext(agent) {
|
|
80
|
+
if (!this.tenantResolver || !agent.tenantId)
|
|
81
|
+
return undefined;
|
|
82
|
+
try {
|
|
83
|
+
const ctx = await this.tenantResolver.findById(agent.tenantId);
|
|
84
|
+
if (!ctx)
|
|
85
|
+
return undefined;
|
|
86
|
+
return { timezone: ctx.timezone };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Resolver outage shouldn't break the turn — the tool's own
|
|
90
|
+
// fallback (agent.timezone → UTC) still produces a sane answer.
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
67
94
|
/** Public re-export of `resolveExtraTools` keyed by agentId. The
|
|
68
95
|
* orchestrator uses this via `setExtraToolsResolver` to hydrate
|
|
69
96
|
* sub-agents' connector tools at delegation time. We synthesize a
|
|
@@ -313,12 +340,14 @@ class AgentService {
|
|
|
313
340
|
// ChatStreamController). Caller wins on name collisions so an
|
|
314
341
|
// explicit override always trumps an inherited connector tool.
|
|
315
342
|
const extraTools = mergeExtraTools(params.overrides?.extraTools, fromConnectors);
|
|
343
|
+
const tenant = await this.loadTenantContext(agent);
|
|
316
344
|
const response = await this.runner.run(agent, messages, {
|
|
317
345
|
userId: params.userId,
|
|
318
346
|
conversationId: params.conversationId,
|
|
319
347
|
agentId: conv.agentId,
|
|
320
348
|
messageId: 'sync',
|
|
321
349
|
agent: { timezone: agent.timezone },
|
|
350
|
+
...(tenant ? { tenant } : {}),
|
|
322
351
|
}, { ...(params.overrides ?? {}), extraTools });
|
|
323
352
|
await this.conversations.addMessage({
|
|
324
353
|
conversationId: params.conversationId,
|
|
@@ -379,6 +408,7 @@ class AgentService {
|
|
|
379
408
|
const filter = params.overrides?.extraToolsFilter;
|
|
380
409
|
const fromConnectors = filter && resolvedExtras ? filter(resolvedExtras) : resolvedExtras;
|
|
381
410
|
const extraTools = mergeExtraTools(params.overrides?.extraTools, fromConnectors);
|
|
411
|
+
const tenant = await this.loadTenantContext(agent);
|
|
382
412
|
// Hoisted accumulators so the post-loop persistence (after the
|
|
383
413
|
// try) can see the final list. Defined here, populated inside
|
|
384
414
|
// the for-await loop below.
|
|
@@ -400,6 +430,7 @@ class AgentService {
|
|
|
400
430
|
agentId: conv.agentId,
|
|
401
431
|
messageId: 'streaming',
|
|
402
432
|
agent: { timezone: agent.timezone },
|
|
433
|
+
...(tenant ? { tenant } : {}),
|
|
403
434
|
})
|
|
404
435
|
: this.runner.stream(agent, messages, {
|
|
405
436
|
userId: params.userId,
|
|
@@ -407,6 +438,7 @@ class AgentService {
|
|
|
407
438
|
agentId: conv.agentId,
|
|
408
439
|
messageId: 'streaming',
|
|
409
440
|
agent: { timezone: agent.timezone },
|
|
441
|
+
...(tenant ? { tenant } : {}),
|
|
410
442
|
}, { ...(params.overrides ?? {}), extraTools });
|
|
411
443
|
// Accumulate tool_use / tool_result chunks during streaming so
|
|
412
444
|
// we can persist them on the assistant message row (line ~700).
|
|
@@ -147,6 +147,17 @@ export interface ToolExecutionContext {
|
|
|
147
147
|
agent?: {
|
|
148
148
|
timezone?: string;
|
|
149
149
|
};
|
|
150
|
+
/**
|
|
151
|
+
* Tenant-level configuration the tool may consult as a fallback when
|
|
152
|
+
* the agent itself does not specify a value. `current_time` walks the
|
|
153
|
+
* chain `agent.timezone → tenant.timezone → 'UTC'` so a workspace
|
|
154
|
+
* default covers every agent that hasn't been individually configured.
|
|
155
|
+
* Populated by the host's `TenantResolver` hook on AgentService;
|
|
156
|
+
* absent when no resolver is wired (legacy SDK consumers).
|
|
157
|
+
*/
|
|
158
|
+
tenant?: {
|
|
159
|
+
timezone?: string;
|
|
160
|
+
};
|
|
150
161
|
}
|
|
151
162
|
export type AnthropicMessage = Anthropic.MessageParam;
|
|
152
163
|
export type AnthropicTool = Anthropic.Tool;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/core",
|
|
3
|
-
"version": "3.0.
|
|
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
|
|
3
|
+
"version": "3.0.1",
|
|
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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
"tsx": "^4.19.0",
|
|
34
34
|
"typescript": "^5.0.0"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|