@andreprado/agentkit 0.1.0-alpha.14 → 0.1.0-alpha.16
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/README.md +2 -0
- package/docs/guides/add-channel.md +63 -0
- package/docs/guides/channel-security.md +32 -0
- package/docs/guides/connect-telegram.md +58 -0
- package/docs/guides/connect-whatsapp-zapster.md +65 -0
- package/docs/guides/run-evals.md +73 -25
- package/docs/llms-full.txt +24 -9
- package/docs/llms.txt +2 -0
- package/package.json +1 -1
- package/src/cli/cloud-client.ts +30 -10
- package/src/cli/commands/channels.ts +2 -0
- package/src/cli/deploy-readiness.ts +32 -11
- package/src/cli/index.ts +20 -6
- package/src/cloud/client.ts +4 -3
- package/src/cloud/contracts.ts +1 -1
- package/src/create-project.ts +1 -1
- package/src/index.ts +110 -1
- package/src/providers/pi.ts +14 -1
- package/src/providers/test.ts +36 -0
- package/src/runtime/channel-test-harness.ts +2 -0
- package/src/runtime/channels/telegram.ts +326 -10
- package/src/runtime/channels/whatsapp-zapster.ts +319 -0
- package/src/runtime/channels.ts +47 -1
- package/src/runtime/chat.ts +59 -42
- package/src/runtime/config.ts +96 -4
- package/src/runtime/core/manifest.ts +35 -3
- package/src/runtime/deploy-readiness.ts +3 -3
- package/src/runtime/dev-server.ts +243 -17
- package/src/runtime/env.ts +8 -3
- package/src/runtime/evals.ts +404 -69
- package/src/runtime/inspect.ts +46 -0
- package/src/runtime/prompt-context.ts +141 -0
- package/src/runtime/runtime-contract.ts +17 -7
- package/src/runtime/targets/cloudflare/build.ts +25 -3
- package/src/runtime/targets/container/server.ts +1 -1
- package/src/runtime/targets/vps/deploy.ts +25 -8
- package/src/runtime/tool-runner.ts +7 -0
- package/src/runtime/tools.ts +8 -2
- package/src/runtime/transcription.ts +483 -0
- package/src/templates/blank.ts +8 -3
- package/src/templates/dentista.ts +18 -10
- package/src/templates/skills/agentkit-build-agent/SKILL.md +6 -5
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
- package/src/templates/skills/agentkit-capsule/SKILL.md +1 -1
- package/src/templates/skills/agentkit-channels/SKILL.md +34 -1
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +13 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +32 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +29 -0
- package/src/templates/skills/agentkit-evals/SKILL.md +53 -13
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
- package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
- package/src/templates/skills/agentkit-tools/SKILL.md +2 -1
- package/src/templates/support.ts +8 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type RuntimePromptContextOptions = {
|
|
2
|
+
timeZone?: string | null;
|
|
3
|
+
env?: Record<string, string | undefined>;
|
|
4
|
+
now?: Date;
|
|
5
|
+
clock?: RuntimeClockContext;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type RuntimePromptContext = {
|
|
9
|
+
isoTimestamp: string;
|
|
10
|
+
timeZone: string;
|
|
11
|
+
localDate: string;
|
|
12
|
+
localWeekday: string;
|
|
13
|
+
localDateTime: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type RuntimeClockContext = RuntimePromptContext & {
|
|
17
|
+
now: Date;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const FALLBACK_TIME_ZONE = "UTC";
|
|
21
|
+
const LOCALE = "en-US";
|
|
22
|
+
|
|
23
|
+
export function appendRuntimePromptContext(
|
|
24
|
+
instructions: string,
|
|
25
|
+
options: RuntimePromptContextOptions = {},
|
|
26
|
+
): string {
|
|
27
|
+
const context = options.clock ?? buildRuntimePromptContext(options);
|
|
28
|
+
|
|
29
|
+
return `${instructions.trimEnd()}
|
|
30
|
+
|
|
31
|
+
Runtime context:
|
|
32
|
+
- Current timestamp: ${context.isoTimestamp}
|
|
33
|
+
- Time zone: ${context.timeZone}
|
|
34
|
+
- Local date: ${context.localDate}
|
|
35
|
+
- Local weekday: ${context.localWeekday}
|
|
36
|
+
- Local date and time: ${context.localDateTime}
|
|
37
|
+
|
|
38
|
+
Use this runtime context to interpret relative dates and times such as today, tomorrow, next Friday, and morning/afternoon. If a scheduling request depends on a different user or business time zone, ask for confirmation before booking.`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildRuntimePromptContext(options: RuntimePromptContextOptions = {}): RuntimePromptContext {
|
|
42
|
+
const clock = buildRuntimeClockContext(options);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
isoTimestamp: clock.isoTimestamp,
|
|
46
|
+
timeZone: clock.timeZone,
|
|
47
|
+
localDate: clock.localDate,
|
|
48
|
+
localWeekday: clock.localWeekday,
|
|
49
|
+
localDateTime: clock.localDateTime,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildRuntimeClockContext(options: RuntimePromptContextOptions = {}): RuntimeClockContext {
|
|
54
|
+
if (options.clock) {
|
|
55
|
+
return options.clock;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const now = new Date((options.now ?? new Date()).getTime());
|
|
59
|
+
|
|
60
|
+
if (Number.isNaN(now.getTime())) {
|
|
61
|
+
throw new Error("runtime prompt context requires a valid Date.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const timeZone = resolveRuntimeTimeZone(options);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
now,
|
|
68
|
+
isoTimestamp: now.toISOString(),
|
|
69
|
+
timeZone,
|
|
70
|
+
localDate: formatLocalDate(now, timeZone),
|
|
71
|
+
localWeekday: formatDateTime(now, timeZone, {
|
|
72
|
+
weekday: "long",
|
|
73
|
+
}),
|
|
74
|
+
localDateTime: formatDateTime(now, timeZone, {
|
|
75
|
+
weekday: "long",
|
|
76
|
+
year: "numeric",
|
|
77
|
+
month: "long",
|
|
78
|
+
day: "numeric",
|
|
79
|
+
hour: "numeric",
|
|
80
|
+
minute: "2-digit",
|
|
81
|
+
timeZoneName: "short",
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveRuntimeTimeZone(options: RuntimePromptContextOptions = {}): string {
|
|
87
|
+
if (options.timeZone?.trim()) {
|
|
88
|
+
return normalizeTimeZone(options.timeZone, "timeZone");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const explicitEnvTimeZone = options.env?.AGENTKIT_TIME_ZONE?.trim();
|
|
92
|
+
|
|
93
|
+
if (explicitEnvTimeZone) {
|
|
94
|
+
return normalizeTimeZone(explicitEnvTimeZone, "AGENTKIT_TIME_ZONE");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const processTimeZone = options.env?.TZ?.trim();
|
|
98
|
+
|
|
99
|
+
if (processTimeZone && isValidTimeZone(processTimeZone)) {
|
|
100
|
+
return processTimeZone;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return normalizeTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone || FALLBACK_TIME_ZONE, "runtime time zone");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function isValidTimeZone(value: string): boolean {
|
|
107
|
+
try {
|
|
108
|
+
new Intl.DateTimeFormat(LOCALE, { timeZone: value }).format(new Date(0));
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeTimeZone(value: string, source: string): string {
|
|
116
|
+
const timeZone = value.trim();
|
|
117
|
+
|
|
118
|
+
if (!isValidTimeZone(timeZone)) {
|
|
119
|
+
throw new Error(`${source} must be a valid IANA time zone such as "UTC" or "America/New_York".`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return timeZone;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatLocalDate(date: Date, timeZone: string): string {
|
|
126
|
+
const parts = new Intl.DateTimeFormat(LOCALE, {
|
|
127
|
+
timeZone,
|
|
128
|
+
year: "numeric",
|
|
129
|
+
month: "2-digit",
|
|
130
|
+
day: "2-digit",
|
|
131
|
+
}).formatToParts(date);
|
|
132
|
+
const byType = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
133
|
+
return `${byType.year}-${byType.month}-${byType.day}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function formatDateTime(date: Date, timeZone: string, options: Intl.DateTimeFormatOptions): string {
|
|
137
|
+
return new Intl.DateTimeFormat(LOCALE, {
|
|
138
|
+
...options,
|
|
139
|
+
timeZone,
|
|
140
|
+
}).format(date);
|
|
141
|
+
}
|
|
@@ -7,6 +7,10 @@ export type RuntimeContractTarget = {
|
|
|
7
7
|
|
|
8
8
|
export type RuntimeContractOptions = {
|
|
9
9
|
expectedAgentName: string;
|
|
10
|
+
auth?: {
|
|
11
|
+
chat?: HeadersInit;
|
|
12
|
+
admin?: HeadersInit;
|
|
13
|
+
};
|
|
10
14
|
tool: {
|
|
11
15
|
name: string;
|
|
12
16
|
input: unknown;
|
|
@@ -20,20 +24,20 @@ export async function assertRuntimeContract(
|
|
|
20
24
|
): Promise<void> {
|
|
21
25
|
const baseUrl = target.baseUrl.replace(/\/$/, "");
|
|
22
26
|
|
|
23
|
-
const root = await fetch(`${baseUrl}
|
|
27
|
+
const root = await fetch(`${baseUrl}/`, { headers: options.auth?.chat });
|
|
24
28
|
expect(root.status).toBe(200);
|
|
25
29
|
|
|
26
30
|
const health = await fetchJson(`${baseUrl}/health`);
|
|
27
31
|
expect(health.status).toBe(200);
|
|
28
32
|
expect(health.body).toMatchObject({ status: "ok" });
|
|
29
33
|
|
|
30
|
-
const inspect = await fetchJson(`${baseUrl}/_agentkit
|
|
34
|
+
const inspect = await fetchJson(`${baseUrl}/_agentkit`, { headers: options.auth?.admin });
|
|
31
35
|
expect(inspect.status).toBe(200);
|
|
32
36
|
expect(inspect.body).toMatchObject({ agent: options.expectedAgentName });
|
|
33
37
|
|
|
34
38
|
const unauthorized = await fetchJson(`${baseUrl}/chat`, {
|
|
35
39
|
method: "POST",
|
|
36
|
-
headers: { "content-type": "application/json" },
|
|
40
|
+
headers: withHeaders({ "content-type": "application/json" }, options.auth?.chat),
|
|
37
41
|
body: JSON.stringify({ message: { role: "assistant", content: "not allowed" } }),
|
|
38
42
|
});
|
|
39
43
|
expect(unauthorized.status).toBe(400);
|
|
@@ -41,7 +45,7 @@ export async function assertRuntimeContract(
|
|
|
41
45
|
|
|
42
46
|
const chat = await fetchJson(`${baseUrl}/chat`, {
|
|
43
47
|
method: "POST",
|
|
44
|
-
headers: { "content-type": "application/json" },
|
|
48
|
+
headers: withHeaders({ "content-type": "application/json" }, options.auth?.chat),
|
|
45
49
|
body: JSON.stringify({ message: { role: "user", content: "my name is Ada" } }),
|
|
46
50
|
});
|
|
47
51
|
expect(chat.status).toBe(200);
|
|
@@ -55,7 +59,7 @@ export async function assertRuntimeContract(
|
|
|
55
59
|
|
|
56
60
|
const continuedChat = await fetchJson(`${baseUrl}/chat`, {
|
|
57
61
|
method: "POST",
|
|
58
|
-
headers: { "content-type": "application/json" },
|
|
62
|
+
headers: withHeaders({ "content-type": "application/json" }, options.auth?.chat),
|
|
59
63
|
body: JSON.stringify({ message: "what is my name?", conversationId: chat.body.conversationId }),
|
|
60
64
|
});
|
|
61
65
|
expect(continuedChat.status).toBe(200);
|
|
@@ -69,7 +73,7 @@ export async function assertRuntimeContract(
|
|
|
69
73
|
|
|
70
74
|
const tool = await fetchJson(`${baseUrl}/tools/${encodeURIComponent(options.tool.name)}`, {
|
|
71
75
|
method: "POST",
|
|
72
|
-
headers: { "content-type": "application/json" },
|
|
76
|
+
headers: withHeaders({ "content-type": "application/json" }, options.auth?.admin),
|
|
73
77
|
body: JSON.stringify({ input: options.tool.input }),
|
|
74
78
|
});
|
|
75
79
|
expect(tool.status).toBe(200);
|
|
@@ -78,7 +82,7 @@ export async function assertRuntimeContract(
|
|
|
78
82
|
output: options.tool.expectedOutput,
|
|
79
83
|
});
|
|
80
84
|
|
|
81
|
-
const conversations = await fetchJson(`${baseUrl}/v1/conversations
|
|
85
|
+
const conversations = await fetchJson(`${baseUrl}/v1/conversations`, { headers: options.auth?.admin });
|
|
82
86
|
expect(conversations.status).toBe(200);
|
|
83
87
|
expect(conversations.body.conversations.length).toBeGreaterThanOrEqual(1);
|
|
84
88
|
expect(conversations.body.conversations.some((conversation: { id: string }) => conversation.id === chat.body.conversationId)).toBe(true);
|
|
@@ -91,3 +95,9 @@ async function fetchJson(url: string, init?: RequestInit): Promise<{ status: num
|
|
|
91
95
|
body: await response.json(),
|
|
92
96
|
};
|
|
93
97
|
}
|
|
98
|
+
|
|
99
|
+
function withHeaders(required: HeadersInit, extra: HeadersInit | undefined): Headers {
|
|
100
|
+
const headers = new Headers(extra);
|
|
101
|
+
new Headers(required).forEach((value, key) => headers.set(key, value));
|
|
102
|
+
return headers;
|
|
103
|
+
}
|
|
@@ -32,10 +32,12 @@ export type AgentManifest = {
|
|
|
32
32
|
runtime: "edge";
|
|
33
33
|
target: "cloudflare";
|
|
34
34
|
provider: SharedAgentManifest["provider"];
|
|
35
|
+
timeZone: SharedAgentManifest["timeZone"];
|
|
35
36
|
prompt: SharedAgentManifest["prompt"];
|
|
36
37
|
secrets: SharedAgentManifest["secrets"];
|
|
37
38
|
tools: SharedAgentManifest["tools"];
|
|
38
39
|
channels: SharedAgentManifest["channels"];
|
|
40
|
+
transcription: SharedAgentManifest["transcription"];
|
|
39
41
|
knowledge: SharedAgentManifest["knowledge"];
|
|
40
42
|
access: SharedAgentManifest["access"];
|
|
41
43
|
storage: {
|
|
@@ -192,10 +194,12 @@ function buildManifest(
|
|
|
192
194
|
runtime: "edge",
|
|
193
195
|
target: "cloudflare",
|
|
194
196
|
provider: shared.provider,
|
|
197
|
+
timeZone: shared.timeZone,
|
|
195
198
|
prompt: shared.prompt,
|
|
196
199
|
secrets: shared.secrets,
|
|
197
200
|
tools: shared.tools,
|
|
198
201
|
channels: shared.channels,
|
|
202
|
+
transcription: shared.transcription,
|
|
199
203
|
knowledge: shared.knowledge,
|
|
200
204
|
access: shared.access,
|
|
201
205
|
storage: {
|
|
@@ -538,10 +542,12 @@ function renderCloudflareWorker(input: { manifest: AgentManifest; instructions:
|
|
|
538
542
|
const instructionsJson = JSON.stringify(input.instructions);
|
|
539
543
|
const configImportPath = JSON.stringify(input.configPath);
|
|
540
544
|
const providerImportPath = JSON.stringify(resolve(moduleDir, "../../../providers/index.ts"));
|
|
545
|
+
const promptContextImportPath = JSON.stringify(resolve(moduleDir, "../../prompt-context.ts"));
|
|
541
546
|
|
|
542
547
|
return `import { DurableObject } from "cloudflare:workers";
|
|
543
548
|
import agentConfig from ${configImportPath};
|
|
544
549
|
import { getProviderAdapter } from ${providerImportPath};
|
|
550
|
+
import { appendRuntimePromptContext, buildRuntimeClockContext } from ${promptContextImportPath};
|
|
545
551
|
|
|
546
552
|
const manifest = ${manifestJson};
|
|
547
553
|
const instructions = ${instructionsJson};
|
|
@@ -1123,16 +1129,25 @@ function isRecord(value) {
|
|
|
1123
1129
|
|
|
1124
1130
|
async function callHostedProvider(env, messages, signal) {
|
|
1125
1131
|
const adapter = getProviderAdapter(manifest.provider);
|
|
1132
|
+
const clock = buildRuntimeClockContext({
|
|
1133
|
+
timeZone: manifest.timeZone,
|
|
1134
|
+
env,
|
|
1135
|
+
});
|
|
1126
1136
|
const result = await adapter.run({
|
|
1127
1137
|
agent: {
|
|
1128
1138
|
name: manifest.name,
|
|
1129
1139
|
runtime: manifest.runtime,
|
|
1130
1140
|
provider: manifest.provider,
|
|
1131
1141
|
},
|
|
1132
|
-
instructions:
|
|
1142
|
+
instructions: appendRuntimePromptContext(
|
|
1143
|
+
withToolVisibilityInstructions(appendHostedKnowledgePromptPolicy(instructions), hostedTools),
|
|
1144
|
+
{
|
|
1145
|
+
clock,
|
|
1146
|
+
},
|
|
1147
|
+
),
|
|
1133
1148
|
messages,
|
|
1134
1149
|
tools: hostedTools,
|
|
1135
|
-
toolRuntime: createHostedProviderToolRuntime(env, signal),
|
|
1150
|
+
toolRuntime: createHostedProviderToolRuntime(env, signal, clock),
|
|
1136
1151
|
env,
|
|
1137
1152
|
signal,
|
|
1138
1153
|
});
|
|
@@ -1618,7 +1633,7 @@ function hostedManagedSecrets() {
|
|
|
1618
1633
|
return ["TURSO_AUTH_TOKEN", "TURSO_DATABASE_URL"];
|
|
1619
1634
|
}
|
|
1620
1635
|
|
|
1621
|
-
function createHostedProviderToolRuntime(env, signal) {
|
|
1636
|
+
function createHostedProviderToolRuntime(env, signal, clock) {
|
|
1622
1637
|
return {
|
|
1623
1638
|
definitions: hostedTools,
|
|
1624
1639
|
execute(request) {
|
|
@@ -1628,6 +1643,7 @@ function createHostedProviderToolRuntime(env, signal) {
|
|
|
1628
1643
|
env,
|
|
1629
1644
|
signal,
|
|
1630
1645
|
invocation: "chat",
|
|
1646
|
+
clock,
|
|
1631
1647
|
});
|
|
1632
1648
|
},
|
|
1633
1649
|
};
|
|
@@ -1646,6 +1662,10 @@ async function executeHostedTool(request) {
|
|
|
1646
1662
|
|
|
1647
1663
|
const secrets = resolveToolSecrets(tool, request.env);
|
|
1648
1664
|
const database = createHostedDatabaseRunner(request.env);
|
|
1665
|
+
const clock = request.clock ?? buildRuntimeClockContext({
|
|
1666
|
+
timeZone: manifest.timeZone,
|
|
1667
|
+
env: request.env,
|
|
1668
|
+
});
|
|
1649
1669
|
const output = await runToolWithTimeout(
|
|
1650
1670
|
tool,
|
|
1651
1671
|
input,
|
|
@@ -1653,6 +1673,7 @@ async function executeHostedTool(request) {
|
|
|
1653
1673
|
secrets,
|
|
1654
1674
|
database,
|
|
1655
1675
|
signal: request.signal,
|
|
1676
|
+
clock,
|
|
1656
1677
|
runtime: {
|
|
1657
1678
|
environment: "hosted",
|
|
1658
1679
|
invocation: request.invocation ?? "tool",
|
|
@@ -1717,6 +1738,7 @@ async function runToolWithTimeout(tool, input, context, timeoutMs) {
|
|
|
1717
1738
|
secrets: context.secrets,
|
|
1718
1739
|
signal: controller.signal,
|
|
1719
1740
|
runtime: context.runtime,
|
|
1741
|
+
clock: context.clock,
|
|
1720
1742
|
database: context.database,
|
|
1721
1743
|
db: context.database,
|
|
1722
1744
|
storage: {
|
|
@@ -12,7 +12,7 @@ export async function startContainerRuntime(options: ContainerRuntimeOptions = {
|
|
|
12
12
|
const capsuleRoot = resolve(options.capsuleRoot ?? process.env.AGENTKIT_CAPSULE_ROOT ?? process.cwd());
|
|
13
13
|
const hostname = options.hostname ?? process.env.HOST ?? "0.0.0.0";
|
|
14
14
|
const port = options.port ?? parsePort(process.env.PORT) ?? 4123;
|
|
15
|
-
return startAgentDevServer(capsuleRoot, { hostname, port });
|
|
15
|
+
return startAgentDevServer(capsuleRoot, { hostname, port, access: "enforce" });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function parsePort(value: string | undefined): number | undefined {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { LocalDeployState } from "../../core/deploy-state";
|
|
@@ -38,15 +38,19 @@ export async function prepareVpsDeploy(
|
|
|
38
38
|
const manifest = JSON.parse(await readFile(container.manifestPath, "utf8")) as {
|
|
39
39
|
name: string;
|
|
40
40
|
secrets: string[];
|
|
41
|
+
access: {
|
|
42
|
+
mode: "private" | "token" | "public";
|
|
43
|
+
};
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
await mkdir(buildDir, { recursive: true });
|
|
44
47
|
const envExample = renderEnvExample(manifest.secrets);
|
|
45
48
|
await writeFile(composePath, renderCompose(manifest.name));
|
|
46
49
|
await writeFile(envExamplePath, envExample);
|
|
47
|
-
await writeFile(envPath, envExample);
|
|
50
|
+
await writeFile(envPath, envExample, { mode: 0o600 });
|
|
51
|
+
await chmod(envPath, 0o600);
|
|
48
52
|
await writeFile(secretsPath, renderSecretsChecklist(manifest.secrets));
|
|
49
|
-
await writeFile(handoffPath, renderProductionHandoff(options.host, manifest.name));
|
|
53
|
+
await writeFile(handoffPath, renderProductionHandoff(options.host, manifest.name, manifest.access.mode));
|
|
50
54
|
|
|
51
55
|
const state: LocalDeployState = {
|
|
52
56
|
deploy_id: `vps_${slugify(manifest.name)}`,
|
|
@@ -71,7 +75,8 @@ export async function prepareVpsDeploy(
|
|
|
71
75
|
},
|
|
72
76
|
created_at: new Date().toISOString(),
|
|
73
77
|
};
|
|
74
|
-
await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n
|
|
78
|
+
await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
79
|
+
await chmod(statePath, 0o600);
|
|
75
80
|
|
|
76
81
|
return {
|
|
77
82
|
target: "vps",
|
|
@@ -88,7 +93,7 @@ export async function prepareVpsDeploy(
|
|
|
88
93
|
`docker compose -f ${composePath} --env-file ${envPath} config`,
|
|
89
94
|
`docker compose -f ${composePath} --env-file ${envPath} up -d --build`,
|
|
90
95
|
`curl -fsS https://${options.host}/health`,
|
|
91
|
-
`curl -fsS https://${options.host}/_agentkit`,
|
|
96
|
+
`curl -fsS -H "Authorization: Bearer $AGENTKIT_PRIVATE_TOKEN" https://${options.host}/_agentkit`,
|
|
92
97
|
],
|
|
93
98
|
};
|
|
94
99
|
}
|
|
@@ -145,7 +150,7 @@ ${lines}
|
|
|
145
150
|
`;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
function renderProductionHandoff(host: string, agentName: string): string {
|
|
153
|
+
function renderProductionHandoff(host: string, agentName: string, accessMode: "private" | "token" | "public"): string {
|
|
149
154
|
return `# VPS Production Handoff
|
|
150
155
|
|
|
151
156
|
## Deploy
|
|
@@ -169,12 +174,14 @@ docker compose -f .agentkit/build/vps/compose.yaml --env-file .agentkit/build/vp
|
|
|
169
174
|
|
|
170
175
|
\`\`\`sh
|
|
171
176
|
curl -fsS https://${host}/health
|
|
172
|
-
curl -fsS https://${host}/_agentkit
|
|
177
|
+
curl -fsS -H "Authorization: Bearer $AGENTKIT_PRIVATE_TOKEN" https://${host}/_agentkit
|
|
173
178
|
curl -fsS -X POST https://${host}/chat \\
|
|
174
|
-
-H 'content-type: application/json' \\
|
|
179
|
+
-H 'content-type: application/json'${renderChatAuthHeader(accessMode)} \\
|
|
175
180
|
-d '{"message":{"role":"user","content":"hello"}}'
|
|
176
181
|
\`\`\`
|
|
177
182
|
|
|
183
|
+
Set AGENTKIT_PRIVATE_TOKEN for privileged AgentKit endpoints. Chat access follows access.mode: ${accessMode}.
|
|
184
|
+
|
|
178
185
|
## Database
|
|
179
186
|
|
|
180
187
|
- Default database: SQLite at /data/agentkit.db in the agentkit_data volume.
|
|
@@ -195,6 +202,16 @@ docker compose -f .agentkit/build/vps/compose.yaml --env-file .agentkit/build/vp
|
|
|
195
202
|
`;
|
|
196
203
|
}
|
|
197
204
|
|
|
205
|
+
function renderChatAuthHeader(accessMode: "private" | "token" | "public"): string {
|
|
206
|
+
if (accessMode === "public") {
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const secretName = accessMode === "private" ? "AGENTKIT_PRIVATE_TOKEN" : "AGENTKIT_ACCESS_TOKEN";
|
|
211
|
+
return ` \\
|
|
212
|
+
-H "Authorization: Bearer $${secretName}"`;
|
|
213
|
+
}
|
|
214
|
+
|
|
198
215
|
function slugify(value: string): string {
|
|
199
216
|
const slug = value
|
|
200
217
|
.toLowerCase()
|
|
@@ -2,12 +2,14 @@ import { loadAgentCapsule } from "./config";
|
|
|
2
2
|
import { openPreparedCapsuleStore } from "./database";
|
|
3
3
|
import { loadCapsuleEnv } from "./env";
|
|
4
4
|
import { isAgentKitError } from "./errors";
|
|
5
|
+
import { buildRuntimeClockContext } from "./prompt-context";
|
|
5
6
|
import { createToolRuntime, type ToolCallResult } from "./tools";
|
|
6
7
|
|
|
7
8
|
export type RunToolOptions = {
|
|
8
9
|
name: string;
|
|
9
10
|
input?: unknown;
|
|
10
11
|
signal?: AbortSignal;
|
|
12
|
+
now?: Date;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export async function runToolFromCwd(cwd: string, options: RunToolOptions): Promise<ToolCallResult> {
|
|
@@ -30,6 +32,11 @@ export async function runToolFromCwd(cwd: string, options: RunToolOptions): Prom
|
|
|
30
32
|
conversationId: conversation.id,
|
|
31
33
|
env,
|
|
32
34
|
signal: options.signal,
|
|
35
|
+
clock: buildRuntimeClockContext({
|
|
36
|
+
timeZone: capsule.config.timeZone,
|
|
37
|
+
env,
|
|
38
|
+
now: options.now,
|
|
39
|
+
}),
|
|
33
40
|
runtime: {
|
|
34
41
|
environment: "local",
|
|
35
42
|
invocation: "tool",
|
package/src/runtime/tools.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AgentTool, JsonSchema, ToolContext, ToolRenderResult, ToolRuntimeC
|
|
|
2
2
|
import type { SqliteAgentKitStore } from "../storage/sqlite";
|
|
3
3
|
import { createLocalDatabaseRunner } from "./database";
|
|
4
4
|
import { AgentKitError } from "./errors";
|
|
5
|
+
import type { RuntimeClockContext } from "./prompt-context";
|
|
5
6
|
|
|
6
7
|
export type ToolCallRequest = {
|
|
7
8
|
name: string;
|
|
@@ -31,6 +32,7 @@ export type ToolRuntimeOptions = {
|
|
|
31
32
|
signal?: AbortSignal;
|
|
32
33
|
defaultTimeoutMs?: number;
|
|
33
34
|
runtime: ToolRuntimeContext;
|
|
35
|
+
clock: RuntimeClockContext;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const DEFAULT_TOOL_TIMEOUT_MS = 30_000;
|
|
@@ -89,6 +91,7 @@ async function executeToolCall(
|
|
|
89
91
|
secrets,
|
|
90
92
|
database,
|
|
91
93
|
runtime: options.runtime,
|
|
94
|
+
clock: options.clock,
|
|
92
95
|
signal: options.signal,
|
|
93
96
|
},
|
|
94
97
|
tool.timeoutMs ?? options.defaultTimeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS,
|
|
@@ -98,7 +101,7 @@ async function executeToolCall(
|
|
|
98
101
|
validateSchema(output, tool.outputSchema, `tool "${tool.name}" output`);
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
const rendered = renderToolOutput(tool, input, output, options.runtime);
|
|
104
|
+
const rendered = renderToolOutput(tool, input, output, options.runtime, options.clock);
|
|
102
105
|
|
|
103
106
|
options.store.completeToolCall({
|
|
104
107
|
toolCallId: toolCall.id,
|
|
@@ -135,6 +138,7 @@ function renderToolOutput(
|
|
|
135
138
|
input: unknown,
|
|
136
139
|
output: unknown,
|
|
137
140
|
runtime: ToolRuntimeContext,
|
|
141
|
+
clock: RuntimeClockContext,
|
|
138
142
|
): ToolRenderResult | undefined {
|
|
139
143
|
if (!tool.render) {
|
|
140
144
|
return undefined;
|
|
@@ -143,6 +147,7 @@ function renderToolOutput(
|
|
|
143
147
|
const rendered = tool.render(output, {
|
|
144
148
|
input,
|
|
145
149
|
runtime,
|
|
150
|
+
clock,
|
|
146
151
|
});
|
|
147
152
|
|
|
148
153
|
if (typeof rendered === "string") {
|
|
@@ -278,7 +283,7 @@ function collectConfiguredSecretValues(tool: AgentTool | undefined, env: Record<
|
|
|
278
283
|
async function runToolWithTimeout(
|
|
279
284
|
tool: AgentTool,
|
|
280
285
|
input: unknown,
|
|
281
|
-
context: Pick<ToolContext, "secrets" | "database" | "runtime"> & { signal?: AbortSignal },
|
|
286
|
+
context: Pick<ToolContext, "secrets" | "database" | "runtime" | "clock"> & { signal?: AbortSignal },
|
|
282
287
|
timeoutMs: number,
|
|
283
288
|
): Promise<unknown> {
|
|
284
289
|
const controller = new AbortController();
|
|
@@ -306,6 +311,7 @@ async function runToolWithTimeout(
|
|
|
306
311
|
secrets: context.secrets,
|
|
307
312
|
signal: controller.signal,
|
|
308
313
|
runtime: context.runtime,
|
|
314
|
+
clock: context.clock,
|
|
309
315
|
database: context.database,
|
|
310
316
|
db: context.database,
|
|
311
317
|
storage: {
|