@andreprado/agentkit 0.1.0-alpha.10

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.
Files changed (122) hide show
  1. package/README.md +69 -0
  2. package/bin/agentkit.mjs +23 -0
  3. package/docs/guides/add-channel.md +114 -0
  4. package/docs/guides/add-knowledge.md +134 -0
  5. package/docs/guides/add-tool.md +342 -0
  6. package/docs/guides/agentkit-skills-architecture.md +471 -0
  7. package/docs/guides/channel-security.md +81 -0
  8. package/docs/guides/channels-implementation-map.md +243 -0
  9. package/docs/guides/channels-production-handoff.md +102 -0
  10. package/docs/guides/connect-telegram.md +110 -0
  11. package/docs/guides/connect-whatsapp-zapster.md +119 -0
  12. package/docs/guides/create-agent.md +220 -0
  13. package/docs/guides/prepare-deploy.md +209 -0
  14. package/docs/guides/run-evals.md +179 -0
  15. package/docs/guides/security-rules.md +156 -0
  16. package/docs/guides/use-provider.md +140 -0
  17. package/docs/llms-full.txt +876 -0
  18. package/docs/llms.txt +83 -0
  19. package/docs/portable-deploy-release-checklist.md +41 -0
  20. package/package.json +47 -0
  21. package/src/cli/args.ts +36 -0
  22. package/src/cli/cloud-client.ts +265 -0
  23. package/src/cli/commands/channels.ts +810 -0
  24. package/src/cli/commands/knowledge.ts +136 -0
  25. package/src/cli/constants.ts +4 -0
  26. package/src/cli/deploy-chat-ui.ts +392 -0
  27. package/src/cli/deploy-readiness.ts +348 -0
  28. package/src/cli/flags.ts +162 -0
  29. package/src/cli/help.ts +184 -0
  30. package/src/cli/index.ts +1276 -0
  31. package/src/cli/process.ts +31 -0
  32. package/src/cloud/artifact.ts +139 -0
  33. package/src/cloud/client.ts +79 -0
  34. package/src/cloud/contracts.ts +63 -0
  35. package/src/cloud/index.ts +3 -0
  36. package/src/create-project.ts +177 -0
  37. package/src/index.ts +408 -0
  38. package/src/providers/index.ts +25 -0
  39. package/src/providers/pi.ts +286 -0
  40. package/src/providers/test.ts +133 -0
  41. package/src/providers/types.ts +34 -0
  42. package/src/runtime/build.ts +43 -0
  43. package/src/runtime/channel-buffer.ts +30 -0
  44. package/src/runtime/channel-test-harness.ts +112 -0
  45. package/src/runtime/channels/telegram.ts +360 -0
  46. package/src/runtime/channels/website.ts +132 -0
  47. package/src/runtime/channels/whatsapp-meta.ts +71 -0
  48. package/src/runtime/channels/whatsapp-zapster.ts +278 -0
  49. package/src/runtime/channels.ts +138 -0
  50. package/src/runtime/chat.ts +218 -0
  51. package/src/runtime/config.ts +684 -0
  52. package/src/runtime/conversations.ts +38 -0
  53. package/src/runtime/core/deploy-state.ts +54 -0
  54. package/src/runtime/core/manifest.ts +213 -0
  55. package/src/runtime/core/targets.ts +133 -0
  56. package/src/runtime/database.ts +256 -0
  57. package/src/runtime/db-commands.ts +167 -0
  58. package/src/runtime/deploy-readiness.ts +105 -0
  59. package/src/runtime/deploy.ts +1 -0
  60. package/src/runtime/dev-server.ts +1247 -0
  61. package/src/runtime/docs.ts +36 -0
  62. package/src/runtime/env.ts +152 -0
  63. package/src/runtime/errors.ts +13 -0
  64. package/src/runtime/evals.ts +509 -0
  65. package/src/runtime/inspect.ts +203 -0
  66. package/src/runtime/knowledge/chunk.ts +333 -0
  67. package/src/runtime/knowledge/config.ts +135 -0
  68. package/src/runtime/knowledge/embeddings.ts +133 -0
  69. package/src/runtime/knowledge/ingest.ts +521 -0
  70. package/src/runtime/knowledge/prompt-policy.ts +30 -0
  71. package/src/runtime/knowledge/retrieve.ts +283 -0
  72. package/src/runtime/knowledge/schema.ts +56 -0
  73. package/src/runtime/knowledge/tool.ts +64 -0
  74. package/src/runtime/knowledge/vector.ts +258 -0
  75. package/src/runtime/runtime-contract.ts +93 -0
  76. package/src/runtime/spec.ts +152 -0
  77. package/src/runtime/sync.ts +144 -0
  78. package/src/runtime/targets/cloudflare/build.ts +2517 -0
  79. package/src/runtime/targets/container/build.ts +146 -0
  80. package/src/runtime/targets/container/server.ts +33 -0
  81. package/src/runtime/targets/vps/deploy.ts +206 -0
  82. package/src/runtime/tool-runner.ts +65 -0
  83. package/src/runtime/tools.ts +470 -0
  84. package/src/runtime/traces.ts +41 -0
  85. package/src/storage/sqlite.ts +1118 -0
  86. package/src/templates/blank.ts +394 -0
  87. package/src/templates/dentista.ts +1003 -0
  88. package/src/templates/index.ts +33 -0
  89. package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -0
  90. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
  91. package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
  92. package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
  93. package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
  94. package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
  95. package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
  96. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
  97. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +41 -0
  98. package/src/templates/skills/agentkit-channels/references/telegram.md +38 -0
  99. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +44 -0
  100. package/src/templates/skills/agentkit-database/SKILL.md +45 -0
  101. package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
  102. package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
  103. package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
  104. package/src/templates/skills/agentkit-evals/SKILL.md +60 -0
  105. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
  106. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
  107. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
  108. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
  109. package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
  110. package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
  111. package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
  112. package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
  113. package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
  114. package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
  115. package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
  116. package/src/templates/skills/agentkit-security/SKILL.md +55 -0
  117. package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
  118. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
  119. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
  120. package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
  121. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
  122. package/src/templates/support.ts +401 -0
@@ -0,0 +1,278 @@
1
+ import type {
2
+ ChannelAdapter,
3
+ ChannelSendInput,
4
+ RawWebhookEvent,
5
+ WebhookVerificationInput,
6
+ WebhookVerificationResult,
7
+ } from "../channels";
8
+ import { AgentKitError } from "../errors";
9
+
10
+ export const zapsterWhatsappChannelAdapter: ChannelAdapter = {
11
+ type: "whatsapp",
12
+ provider: "zapster",
13
+ requiredSecrets: ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"],
14
+ verifyWebhook(input) {
15
+ const expectedInstanceId = input.secrets.ZAPSTER_INSTANCE_ID;
16
+ const expectedWebhookId = input.secrets.ZAPSTER_WEBHOOK_ID;
17
+
18
+ if (!expectedInstanceId) {
19
+ return {
20
+ ok: false,
21
+ code: "channel_secret_missing",
22
+ message: "Secret ZAPSTER_INSTANCE_ID is not set for this channel.",
23
+ };
24
+ }
25
+
26
+ if (!expectedWebhookId) {
27
+ return {
28
+ ok: false,
29
+ code: "channel_secret_missing",
30
+ message: "Secret ZAPSTER_WEBHOOK_ID is not set for this channel.",
31
+ };
32
+ }
33
+
34
+ const optionalTokenResult = verifyOptionalWebhookToken(input);
35
+
36
+ if (!optionalTokenResult.ok) {
37
+ return optionalTokenResult;
38
+ }
39
+
40
+ const instanceId = input.headers.get("x-instance-id");
41
+ const webhookId = input.headers.get("x-webhook-id");
42
+ const messageId = input.headers.get("x-message-id");
43
+ const attemptCount = input.headers.get("x-attempt-count");
44
+ const userAgent = input.headers.get("user-agent");
45
+
46
+ if (instanceId !== expectedInstanceId) {
47
+ return {
48
+ ok: false,
49
+ code: "channel_signature_invalid",
50
+ message: "Zapster instance id did not match.",
51
+ };
52
+ }
53
+
54
+ if (webhookId !== expectedWebhookId) {
55
+ return {
56
+ ok: false,
57
+ code: "channel_signature_invalid",
58
+ message: "Zapster webhook id did not match.",
59
+ };
60
+ }
61
+
62
+ if (!messageId || !isPositiveIntegerHeader(attemptCount) || !userAgent?.toLowerCase().startsWith("zapsterapi/")) {
63
+ return {
64
+ ok: false,
65
+ code: "channel_payload_invalid",
66
+ message: "Missing Zapster webhook headers.",
67
+ };
68
+ }
69
+
70
+ return { ok: true };
71
+ },
72
+ normalizeInbound(input) {
73
+ const body = parseJsonObject(input);
74
+ const message = readRecord(body, "message");
75
+ const instanceId = input.headers.get("x-instance-id") ?? readString(body, "instance_id") ?? "default";
76
+ const providerEventId = readString(body, "event_id") ?? input.headers.get("x-message-id") ?? readString(message, "id");
77
+
78
+ if (!providerEventId) {
79
+ throw new AgentKitError("channel_payload_invalid", "Zapster payload is missing event_id, X-Message-ID, or message.id.");
80
+ }
81
+
82
+ const contactId = readString(message, "from") ?? "unknown";
83
+ const messageType = readString(message, "type") ?? "unknown";
84
+ const base = {
85
+ type: "whatsapp" as const,
86
+ provider: "zapster" as const,
87
+ providerEventId,
88
+ dedupeKey: `whatsapp:zapster:${instanceId}:${providerEventId}`,
89
+ externalIdentity: {
90
+ key: `whatsapp:zapster:contact:${contactId}`,
91
+ display: contactId,
92
+ redactedDisplay: `whatsapp:zapster:contact:${redactPhone(contactId)}`,
93
+ },
94
+ receivedAt: input.receivedAt,
95
+ providerMetadata: {
96
+ instanceId,
97
+ messageType,
98
+ },
99
+ };
100
+
101
+ if (messageType !== "text") {
102
+ return [
103
+ {
104
+ ...base,
105
+ kind: "unsupported",
106
+ unsupportedReason: `unsupported_message_type:${messageType}`,
107
+ },
108
+ ];
109
+ }
110
+
111
+ const text = readString(readRecord(message, "text"), "body");
112
+
113
+ if (!text) {
114
+ return [
115
+ {
116
+ ...base,
117
+ kind: "unsupported",
118
+ unsupportedReason: "unsupported_message_type:empty_text",
119
+ },
120
+ ];
121
+ }
122
+
123
+ return [
124
+ {
125
+ ...base,
126
+ kind: "message",
127
+ message: {
128
+ role: "user",
129
+ contentType: "text",
130
+ content: text,
131
+ providerMessageId: readString(message, "id"),
132
+ },
133
+ },
134
+ ];
135
+ },
136
+ async sendMessage(input) {
137
+ const request = buildZapsterSendMessageRequest(input);
138
+
139
+ return {
140
+ ok: true,
141
+ status: "sent",
142
+ providerRequestId: request.idempotencyKey,
143
+ providerMetadata: {
144
+ method: request.method,
145
+ url: request.url,
146
+ },
147
+ };
148
+ },
149
+ getStatus(input) {
150
+ const missingSecrets = ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"].filter(
151
+ (secret) => !input.secrets[secret],
152
+ );
153
+
154
+ if (missingSecrets.length > 0) {
155
+ return {
156
+ status: "needs_setup",
157
+ checkedAt: new Date().toISOString(),
158
+ message: `Zapster channel secrets are missing: ${missingSecrets.join(", ")}.`,
159
+ };
160
+ }
161
+
162
+ return {
163
+ status: "waiting_for_provider_event",
164
+ checkedAt: new Date().toISOString(),
165
+ message: "Paste the AgentKit webhook URL into Zapster webhook settings.",
166
+ };
167
+ },
168
+ };
169
+
170
+ function verifyOptionalWebhookToken(input: WebhookVerificationInput): WebhookVerificationResult {
171
+ const expectsToken = input.channel.secrets.includes("ZAPSTER_WEBHOOK_TOKEN");
172
+
173
+ if (!expectsToken) {
174
+ return { ok: true };
175
+ }
176
+
177
+ const expectedToken = input.secrets.ZAPSTER_WEBHOOK_TOKEN;
178
+
179
+ if (!expectedToken) {
180
+ return {
181
+ ok: false,
182
+ code: "channel_secret_missing",
183
+ message: "Secret ZAPSTER_WEBHOOK_TOKEN is not set for this channel.",
184
+ };
185
+ }
186
+
187
+ let actualToken: string | null = null;
188
+
189
+ try {
190
+ actualToken = new URL(input.url).searchParams.get("token");
191
+ } catch {
192
+ actualToken = null;
193
+ }
194
+
195
+ if (actualToken !== expectedToken) {
196
+ return {
197
+ ok: false,
198
+ code: "channel_signature_invalid",
199
+ message: "Zapster webhook token did not match.",
200
+ };
201
+ }
202
+
203
+ return { ok: true };
204
+ }
205
+
206
+ export type ZapsterSendMessageRequest = {
207
+ method: "POST";
208
+ url: string;
209
+ body: {
210
+ to: string;
211
+ type: "text";
212
+ text: {
213
+ body: string;
214
+ };
215
+ };
216
+ idempotencyKey: string;
217
+ };
218
+
219
+ export function buildZapsterSendMessageRequest(input: ChannelSendInput): ZapsterSendMessageRequest {
220
+ const to = input.externalIdentity.key.match(/contact:([^:]+)$/)?.[1];
221
+
222
+ if (!to) {
223
+ throw new AgentKitError("channel_payload_invalid", "Zapster external identity is missing contact ID.");
224
+ }
225
+
226
+ return {
227
+ method: "POST",
228
+ url: "https://api.zapster.example/messages",
229
+ body: {
230
+ to,
231
+ type: "text",
232
+ text: {
233
+ body: input.message.content,
234
+ },
235
+ },
236
+ idempotencyKey: `whatsapp:zapster:${input.channelId}:${input.conversationId}:${redactPhone(to)}`,
237
+ };
238
+ }
239
+
240
+ function parseJsonObject(input: RawWebhookEvent): Record<string, unknown> {
241
+ try {
242
+ const parsed = JSON.parse(input.rawBody) as unknown;
243
+
244
+ if (isRecord(parsed)) {
245
+ return parsed;
246
+ }
247
+ } catch (error) {
248
+ throw new AgentKitError("channel_payload_invalid", "Zapster payload must be valid JSON.", { cause: error });
249
+ }
250
+
251
+ throw new AgentKitError("channel_payload_invalid", "Zapster payload must be a JSON object.");
252
+ }
253
+
254
+ function readRecord(value: Record<string, unknown>, key: string): Record<string, unknown> {
255
+ const record = value[key];
256
+ return isRecord(record) ? record : {};
257
+ }
258
+
259
+ function readString(value: Record<string, unknown>, key: string): string | undefined {
260
+ const field = value[key];
261
+ return typeof field === "string" && field.length > 0 ? field : undefined;
262
+ }
263
+
264
+ function isPositiveIntegerHeader(value: string | null): value is string {
265
+ return typeof value === "string" && /^[1-9]\d*$/.test(value);
266
+ }
267
+
268
+ function redactPhone(value: string): string {
269
+ if (value.length <= 6) {
270
+ return "*".repeat(value.length);
271
+ }
272
+
273
+ return `${value.slice(0, 4)}******${value.slice(-4)}`;
274
+ }
275
+
276
+ function isRecord(value: unknown): value is Record<string, unknown> {
277
+ return typeof value === "object" && value !== null && !Array.isArray(value);
278
+ }
@@ -0,0 +1,138 @@
1
+ import type { AgentChannel, ChannelProvider, ChannelType } from "../index";
2
+
3
+ export type ChannelMessageRole = "user" | "assistant";
4
+ export type ChannelMessageContentType = "text";
5
+ export type ChannelEventKind = "message" | "delivery_status" | "unsupported";
6
+ export type ChannelDeliveryDirection = "inbound" | "outbound";
7
+ export type ChannelDeliveryState =
8
+ | "webhook_received"
9
+ | "received"
10
+ | "validated"
11
+ | "duplicate"
12
+ | "buffered"
13
+ | "queued"
14
+ | "running"
15
+ | "agent_completed"
16
+ | "outbound_sent"
17
+ | "provider_failed"
18
+ | "synthetic_expected_failure"
19
+ | "sent"
20
+ | "delivered"
21
+ | "failed"
22
+ | "dead_lettered"
23
+ | "skipped";
24
+
25
+ export type ChannelAdapterId = {
26
+ type: ChannelType;
27
+ provider: ChannelProvider;
28
+ };
29
+
30
+ export type RawWebhookEvent = ChannelAdapterId & {
31
+ channelId: string;
32
+ method: string;
33
+ url: string;
34
+ headers: Headers;
35
+ rawBody: string;
36
+ receivedAt: string;
37
+ };
38
+
39
+ export type ChannelExternalIdentity = {
40
+ key: string;
41
+ display?: string;
42
+ redactedDisplay?: string;
43
+ };
44
+
45
+ export type NormalizedChannelMessage = {
46
+ role: ChannelMessageRole;
47
+ contentType: ChannelMessageContentType;
48
+ content: string;
49
+ providerMessageId?: string;
50
+ };
51
+
52
+ export type NormalizedChannelEvent = ChannelAdapterId & {
53
+ kind: ChannelEventKind;
54
+ providerEventId: string;
55
+ dedupeKey: string;
56
+ externalIdentity: ChannelExternalIdentity;
57
+ message?: NormalizedChannelMessage;
58
+ deliveryStatus?: ChannelDeliveryState;
59
+ receivedAt: string;
60
+ unsupportedReason?: string;
61
+ providerMetadata?: Record<string, unknown>;
62
+ };
63
+
64
+ export type WebhookVerificationInput = RawWebhookEvent & {
65
+ channel: AgentChannel;
66
+ secrets: Record<string, string>;
67
+ };
68
+
69
+ export type WebhookVerificationResult =
70
+ | {
71
+ ok: true;
72
+ }
73
+ | {
74
+ ok: false;
75
+ code: "channel_secret_missing" | "channel_signature_invalid" | "channel_payload_invalid";
76
+ message: string;
77
+ };
78
+
79
+ export type ChannelSendInput = ChannelAdapterId & {
80
+ channelId: string;
81
+ conversationId: string;
82
+ externalIdentity: ChannelExternalIdentity;
83
+ message: NormalizedChannelMessage;
84
+ secrets: Record<string, string>;
85
+ };
86
+
87
+ export type ChannelSendResult =
88
+ | {
89
+ ok: true;
90
+ providerRequestId?: string;
91
+ providerMessageId?: string;
92
+ status: "sent" | "delivered";
93
+ providerMetadata?: Record<string, unknown>;
94
+ }
95
+ | {
96
+ ok: false;
97
+ retryable: boolean;
98
+ code: "channel_secret_missing" | "channel_provider_unavailable" | "channel_send_failed" | "synthetic_expected_failure";
99
+ message: string;
100
+ providerRequestId?: string;
101
+ providerMetadata?: Record<string, unknown>;
102
+ };
103
+
104
+ export type ChannelStatusInput = ChannelAdapterId & {
105
+ channelId: string;
106
+ channel: AgentChannel;
107
+ secrets: Record<string, string>;
108
+ };
109
+
110
+ export type ChannelStatusResult = {
111
+ status: "connected" | "needs_setup" | "waiting_for_provider_event" | "failed";
112
+ checkedAt: string;
113
+ message?: string;
114
+ providerMetadata?: Record<string, unknown>;
115
+ };
116
+
117
+ export type ChannelAdapter = ChannelAdapterId & {
118
+ requiredSecrets: string[];
119
+ normalizeInbound(input: RawWebhookEvent): Promise<NormalizedChannelEvent[]> | NormalizedChannelEvent[];
120
+ sendMessage(input: ChannelSendInput): Promise<ChannelSendResult>;
121
+ verifyWebhook?(input: WebhookVerificationInput): Promise<WebhookVerificationResult> | WebhookVerificationResult;
122
+ getStatus?(input: ChannelStatusInput): Promise<ChannelStatusResult> | ChannelStatusResult;
123
+ };
124
+
125
+ export type PortableChannelRouteContract = {
126
+ route: "/channels/:name/:type/:provider/webhook";
127
+ verification: WebhookVerificationResult;
128
+ dedupe: {
129
+ key: string;
130
+ duplicate: boolean;
131
+ };
132
+ identity: ChannelExternalIdentity;
133
+ delivery: {
134
+ direction: ChannelDeliveryDirection;
135
+ state: ChannelDeliveryState;
136
+ };
137
+ outbound?: ChannelSendResult;
138
+ };
@@ -0,0 +1,218 @@
1
+ import { getProviderAdapter } from "../providers";
2
+ import type { AgentMessage, ProviderRunResult } from "../providers";
3
+ import type { ToolRuntimeContext } from "../index";
4
+ import type { ConversationRecord, ConversationSummary, PersistedChatRun } from "../storage/sqlite";
5
+ import type { LoadedAgentCapsule } from "./config";
6
+ import { loadAgentCapsule } from "./config";
7
+ import { openPreparedCapsuleStore } from "./database";
8
+ import { loadCapsuleEnv } from "./env";
9
+ import { AgentKitError, isAgentKitError } from "./errors";
10
+ import { syncConfiguredKnowledgeSources } from "./knowledge/ingest";
11
+ import { appendKnowledgePromptPolicy } from "./knowledge/prompt-policy";
12
+ import { createKnowledgeSearchTool } from "./knowledge/tool";
13
+ import { createToolRuntime } from "./tools";
14
+
15
+ export type RunAgentMessageOptions = {
16
+ message: string;
17
+ conversationId?: string;
18
+ signal?: AbortSignal;
19
+ runtime?: Partial<ToolRuntimeContext>;
20
+ };
21
+
22
+ export type AgentRunResult = ProviderRunResult & PersistedChatRun;
23
+
24
+ export async function runAgentMessageFromCwd(
25
+ cwd: string,
26
+ options: RunAgentMessageOptions,
27
+ ): Promise<AgentRunResult> {
28
+ return runAgentMessage(await loadAgentCapsule(cwd), options);
29
+ }
30
+
31
+ export async function runAgentMessage(
32
+ capsule: LoadedAgentCapsule,
33
+ options: RunAgentMessageOptions,
34
+ ): Promise<AgentRunResult> {
35
+ const content = options.message.trim();
36
+
37
+ if (!content) {
38
+ throw new AgentKitError("validation_error", "chat --message requires a non-empty message.");
39
+ }
40
+
41
+ const adapter = getProviderAdapter(capsule.config.provider);
42
+ const env = await loadCapsuleEnv(capsule.root);
43
+ await syncConfiguredKnowledgeSources(capsule);
44
+ const { store } = await openPreparedCapsuleStore(capsule);
45
+ const conversationId = normalizeConversationId(options.conversationId);
46
+ const { conversation, previousMessages } = resolveConversation(store, {
47
+ agentName: capsule.config.name,
48
+ conversationId,
49
+ firstMessage: content,
50
+ });
51
+ const messages: AgentMessage[] = [...previousMessages, { role: "user", content }];
52
+ const userMessage = store.appendMessage({
53
+ conversationId: conversation.id,
54
+ role: "user",
55
+ content,
56
+ });
57
+ const run = store.createRun({
58
+ conversationId: conversation.id,
59
+ provider: capsule.config.provider.name,
60
+ model: capsule.config.provider.model,
61
+ });
62
+ const runtimeTools = withKnowledgeTools(capsule);
63
+ const toolRuntime = createToolRuntime({
64
+ tools: runtimeTools,
65
+ store,
66
+ runId: run.id,
67
+ conversationId: conversation.id,
68
+ env,
69
+ signal: options.signal,
70
+ runtime: {
71
+ environment: "local",
72
+ invocation: "chat",
73
+ target: "bun",
74
+ database: "sqlite",
75
+ ...options.runtime,
76
+ },
77
+ });
78
+
79
+ try {
80
+ const result = await adapter.run({
81
+ agent: {
82
+ name: capsule.config.name,
83
+ runtime: capsule.config.runtime,
84
+ provider: capsule.config.provider,
85
+ },
86
+ instructions: withToolVisibilityInstructions(
87
+ appendKnowledgePromptPolicy(capsule.instructions, capsule.config),
88
+ runtimeTools,
89
+ ),
90
+ messages,
91
+ tools: toolRuntime.definitions,
92
+ toolRuntime,
93
+ env,
94
+ signal: options.signal,
95
+ });
96
+
97
+ const assistantMessage = store.appendMessage({
98
+ conversationId: conversation.id,
99
+ role: "assistant",
100
+ content: result.message.content,
101
+ });
102
+
103
+ store.completeRun({
104
+ runId: run.id,
105
+ status: "completed",
106
+ usage: result.usage,
107
+ });
108
+
109
+ return {
110
+ ...result,
111
+ conversationId: conversation.id,
112
+ runId: run.id,
113
+ userMessageId: userMessage.id,
114
+ assistantMessageId: assistantMessage.id,
115
+ };
116
+ } catch (error) {
117
+ store.completeRun({
118
+ runId: run.id,
119
+ status: "failed",
120
+ errorCode: isAgentKitError(error) ? error.code : "runtime_error",
121
+ errorMessage: error instanceof Error ? error.message : String(error),
122
+ });
123
+
124
+ throw error;
125
+ } finally {
126
+ store.close();
127
+ }
128
+ }
129
+
130
+ function withKnowledgeTools(capsule: LoadedAgentCapsule): NonNullable<LoadedAgentCapsule["config"]["tools"]> {
131
+ const tools = [...(capsule.config.tools ?? [])];
132
+ const knowledgeTool = createKnowledgeSearchTool(capsule);
133
+
134
+ if (knowledgeTool) {
135
+ return [knowledgeTool, ...tools];
136
+ }
137
+
138
+ return tools;
139
+ }
140
+
141
+ function resolveConversation(
142
+ store: Awaited<ReturnType<typeof openPreparedCapsuleStore>>["store"],
143
+ input: {
144
+ agentName: string;
145
+ conversationId?: string;
146
+ firstMessage: string;
147
+ },
148
+ ): { conversation: ConversationSummary; previousMessages: AgentMessage[] } {
149
+ if (input.conversationId) {
150
+ const existing = store.getConversation(input.conversationId);
151
+
152
+ if (existing) {
153
+ return {
154
+ conversation: existing,
155
+ previousMessages: toProviderMessages(existing),
156
+ };
157
+ }
158
+ }
159
+
160
+ const conversation = store.createConversation({
161
+ id: input.conversationId,
162
+ agentName: input.agentName,
163
+ title: titleFromMessage(input.firstMessage),
164
+ });
165
+
166
+ return {
167
+ conversation,
168
+ previousMessages: [],
169
+ };
170
+ }
171
+
172
+ function toProviderMessages(conversation: ConversationRecord): AgentMessage[] {
173
+ return conversation.messages.map((message) => ({
174
+ role: message.role,
175
+ content: message.content,
176
+ }));
177
+ }
178
+
179
+ function normalizeConversationId(value: string | undefined): string | undefined {
180
+ if (value === undefined) {
181
+ return undefined;
182
+ }
183
+
184
+ const conversationId = value.trim();
185
+
186
+ if (!conversationId) {
187
+ throw new AgentKitError("validation_error", "conversationId must be a non-empty string when provided.");
188
+ }
189
+
190
+ return conversationId;
191
+ }
192
+
193
+ function withToolVisibilityInstructions(instructions: string, tools: LoadedAgentCapsule["config"]["tools"] = []): string {
194
+ const internalTools = tools.filter((tool) => tool.visibility === "internal").map((tool) => tool.name).sort();
195
+
196
+ if (internalTools.length === 0) {
197
+ return instructions;
198
+ }
199
+
200
+ return `${instructions.trimEnd()}
201
+
202
+ Tool visibility rules:
203
+ - Tool outputs marked internal are operational state for the agent, not user-facing content.
204
+ - Use internal outputs to decide what to do next, but do not reveal raw internal labels, scores, statuses, JSON fields, or tool output objects to the user.
205
+ - Internal tools: ${internalTools.join(", ")}.
206
+ `;
207
+ }
208
+
209
+ function titleFromMessage(content: string): string {
210
+ const [firstLine = content] = content.split(/\r?\n/, 1);
211
+ const compactTitle = firstLine.replace(/\s+/g, " ").trim();
212
+
213
+ if (compactTitle.length <= 80) {
214
+ return compactTitle;
215
+ }
216
+
217
+ return `${compactTitle.slice(0, 77)}...`;
218
+ }