mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
feat(core): Add get_environment tool for runtime date and timezone (no-changelog) (#29930)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9255311491
commit
4b67c31896
|
|
@ -636,6 +636,20 @@ export class AgentsService {
|
|||
const { agent, agentId, projectId, credentialProvider, nodeToolsEnabled, integrationType } =
|
||||
params;
|
||||
|
||||
// Inject get_environment unconditionally. It surfaces info the model
|
||||
// can't know on its own (current date, instance timezone, day of week)
|
||||
// via a tool call rather than the system prompt — so values that change
|
||||
// per request don't bust system-prompt prompt caching.
|
||||
try {
|
||||
const { createGetEnvironmentTool } = await import('./tools/environment-tool');
|
||||
agent.tool(createGetEnvironmentTool());
|
||||
} catch (toolError) {
|
||||
this.logger.warn('Failed to inject get_environment tool', {
|
||||
agentId,
|
||||
error: toolError instanceof Error ? toolError.message : String(toolError),
|
||||
});
|
||||
}
|
||||
|
||||
// Inject the rich_interaction tool only for platforms that can actually
|
||||
// render its suspend/resume HITL cards. Two gates:
|
||||
// - A registered integration in ChatIntegrationRegistry. The in-app
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
import { DateTime, Settings } from 'luxon';
|
||||
|
||||
import { createGetEnvironmentTool } from '../environment-tool';
|
||||
|
||||
describe('createGetEnvironmentTool', () => {
|
||||
const FIXED_NOW_MS = DateTime.fromISO('2026-05-05T14:32:11.000Z', { zone: 'utc' }).toMillis();
|
||||
|
||||
beforeEach(() => {
|
||||
Settings.now = () => FIXED_NOW_MS;
|
||||
Container.set(GlobalConfig, { generic: { timezone: 'America/New_York' } } as GlobalConfig);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Settings.now = () => Date.now();
|
||||
Container.reset();
|
||||
});
|
||||
|
||||
function makeCtx() {
|
||||
return {
|
||||
parentTelemetry: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
it('builds a tool with the correct name', () => {
|
||||
const tool = createGetEnvironmentTool().build();
|
||||
expect(tool.name).toBe('get_environment');
|
||||
});
|
||||
|
||||
it('returns now in the instance timezone with offset', async () => {
|
||||
const tool = createGetEnvironmentTool().build();
|
||||
|
||||
const result = (await tool.handler!({}, makeCtx())) as {
|
||||
now: string;
|
||||
timezone: string;
|
||||
dayOfWeek: string;
|
||||
};
|
||||
|
||||
// 14:32:11Z in America/New_York (DST) = 10:32:11-04:00
|
||||
expect(result.now).toBe('2026-05-05T10:32:11.000-04:00');
|
||||
expect(result.timezone).toBe('America/New_York');
|
||||
expect(result.dayOfWeek).toBe('Tuesday');
|
||||
});
|
||||
|
||||
it('reflects a different configured timezone', async () => {
|
||||
Container.set(GlobalConfig, { generic: { timezone: 'Asia/Tokyo' } } as GlobalConfig);
|
||||
|
||||
const tool = createGetEnvironmentTool().build();
|
||||
const result = (await tool.handler!({}, makeCtx())) as {
|
||||
now: string;
|
||||
timezone: string;
|
||||
dayOfWeek: string;
|
||||
};
|
||||
|
||||
// 14:32:11Z in Asia/Tokyo = 23:32:11+09:00
|
||||
expect(result.now).toBe('2026-05-05T23:32:11.000+09:00');
|
||||
expect(result.timezone).toBe('Asia/Tokyo');
|
||||
expect(result.dayOfWeek).toBe('Tuesday');
|
||||
});
|
||||
|
||||
it('reads timezone at handler time, not creation time', async () => {
|
||||
const tool = createGetEnvironmentTool().build();
|
||||
|
||||
// Mutate config after the tool is built; the handler must pick up the change.
|
||||
Container.set(GlobalConfig, { generic: { timezone: 'Europe/London' } } as GlobalConfig);
|
||||
|
||||
const result = (await tool.handler!({}, makeCtx())) as { timezone: string };
|
||||
expect(result.timezone).toBe('Europe/London');
|
||||
});
|
||||
});
|
||||
28
packages/cli/src/modules/agents/tools/environment-tool.ts
Normal file
28
packages/cli/src/modules/agents/tools/environment-tool.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { Tool } from '@n8n/agents';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
import { DateTime } from 'luxon';
|
||||
import { z } from 'zod';
|
||||
|
||||
const DESCRIPTION =
|
||||
'Returns runtime info that the LLM cannot know on its own: ' +
|
||||
'current ISO date/time, instance timezone (IANA), and day of week. ' +
|
||||
'Call when reasoning about "today", deadlines, or scheduling.';
|
||||
|
||||
export function createGetEnvironmentTool() {
|
||||
return (
|
||||
new Tool('get_environment')
|
||||
.description(DESCRIPTION)
|
||||
.input(z.object({}))
|
||||
// eslint-disable-next-line @typescript-eslint/require-await -- Tool.handler() expects an async callback
|
||||
.handler(async () => {
|
||||
const timezone = Container.get(GlobalConfig).generic.timezone;
|
||||
const now = DateTime.now().setZone(timezone);
|
||||
return {
|
||||
now: now.toISO(),
|
||||
timezone,
|
||||
dayOfWeek: now.weekdayLong,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user