@andreprado/agentkit 0.1.0 → 0.2.0
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 +8 -77
- package/docs/guides/add-channel.md +14 -92
- package/docs/guides/add-knowledge.md +0 -21
- package/docs/guides/add-tool.md +5 -11
- package/docs/guides/channel-security.md +3 -207
- package/docs/guides/connect-discord.md +7 -172
- package/docs/guides/connect-slack.md +6 -121
- package/docs/guides/connect-telegram.md +6 -165
- package/docs/guides/connect-whatsapp-evolution.md +6 -116
- package/docs/guides/connect-whatsapp-uazapi.md +6 -134
- package/docs/guides/connect-whatsapp-zapster.md +6 -202
- package/docs/guides/create-agent.md +5 -14
- package/docs/guides/debug-channel.md +4 -156
- package/docs/guides/improve-local.md +13 -0
- package/docs/guides/local-only-migration.md +35 -0
- package/docs/guides/replay-local-traces.md +11 -0
- package/docs/guides/run-evals.md +2 -4
- package/docs/guides/security-rules.md +5 -154
- package/docs/guides/use-jev.md +3 -6
- package/docs/guides/use-provider.md +0 -3
- package/docs/guides/write-feedback.md +10 -0
- package/docs/llms-full.txt +27 -448
- package/docs/llms.txt +8 -44
- package/package.json +2 -4
- package/src/cli/commands/channels.ts +8 -1613
- package/src/cli/commands/feedback.ts +8 -86
- package/src/cli/constants.ts +0 -3
- package/src/cli/flags.ts +0 -28
- package/src/cli/help.ts +16 -92
- package/src/cli/index.ts +15 -1091
- package/src/index.ts +6 -158
- package/src/providers/pi.ts +2 -0
- package/src/runtime/channels/discord.ts +16 -8
- package/src/runtime/channels/generic-webhook.ts +21 -313
- package/src/runtime/channels/net-guard.ts +480 -0
- package/src/runtime/channels/provider-fetch.ts +54 -0
- package/src/runtime/channels/slack.ts +9 -3
- package/src/runtime/channels/telegram.ts +43 -10
- package/src/runtime/channels/whatsapp-evolution.ts +33 -60
- package/src/runtime/channels/whatsapp-uazapi.ts +33 -164
- package/src/runtime/channels/whatsapp-zapster.ts +35 -7
- package/src/runtime/chat.ts +5 -3
- package/src/runtime/config.ts +36 -147
- package/src/runtime/database.ts +2 -2
- package/src/runtime/dev-server.ts +8 -8
- package/src/runtime/env.ts +11 -0
- package/src/runtime/improve.ts +2 -262
- package/src/runtime/inspect.ts +13 -73
- package/src/runtime/knowledge/embeddings.ts +45 -7
- package/src/runtime/knowledge/ingest.ts +70 -7
- package/src/runtime/knowledge/tool.ts +16 -2
- package/src/runtime/knowledge/vector.ts +31 -31
- package/src/runtime/tool-runner.ts +5 -3
- package/src/runtime/tools.ts +10 -14
- package/src/storage/sqlite.ts +11 -32
- package/src/templates/blank.ts +15 -102
- package/src/templates/common.ts +60 -0
- package/src/templates/dentista.ts +7 -74
- package/src/templates/skills/agentkit-capsule/SKILL.md +5 -7
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -3
- package/src/templates/skills/agentkit-channels/SKILL.md +6 -119
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +0 -9
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +1 -64
- package/src/templates/skills/agentkit-channels/references/discord.md +2 -92
- package/src/templates/skills/agentkit-channels/references/slack.md +2 -55
- package/src/templates/skills/agentkit-channels/references/telegram.md +2 -71
- package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +2 -56
- package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +2 -53
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +2 -70
- package/src/templates/skills/agentkit-database/SKILL.md +2 -4
- package/src/templates/skills/agentkit-evals/SKILL.md +1 -1
- package/src/templates/skills/agentkit-improve/SKILL.md +6 -85
- package/src/templates/skills/agentkit-improve/references/trace-packets.md +1 -1
- package/src/templates/skills/agentkit-provider/SKILL.md +0 -1
- package/src/templates/skills/agentkit-security/SKILL.md +1 -3
- package/src/templates/skills/agentkit-tools/SKILL.md +1 -1
- package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +1 -2
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +5 -11
- package/src/templates/support.ts +8 -92
- package/docs/guides/add-managed-composio.md +0 -165
- package/docs/guides/improve-from-production.md +0 -151
- package/docs/guides/prepare-deploy.md +0 -227
- package/docs/guides/replay-production-traces.md +0 -72
- package/docs/guides/send-feedback.md +0 -135
- package/src/cli/cloud-client.ts +0 -377
- package/src/cli/deploy-chat-ui.ts +0 -606
- package/src/cli/deploy-readiness.ts +0 -561
- package/src/cloud/artifact.ts +0 -139
- package/src/cloud/client.ts +0 -80
- package/src/cloud/contracts.ts +0 -63
- package/src/cloud/index.ts +0 -3
- package/src/runtime/build.ts +0 -43
- package/src/runtime/core/deploy-state.ts +0 -54
- package/src/runtime/core/manifest.ts +0 -283
- package/src/runtime/core/targets.ts +0 -133
- package/src/runtime/deploy-readiness.ts +0 -135
- package/src/runtime/deploy.ts +0 -1
- package/src/runtime/integrations/composio.ts +0 -425
- package/src/runtime/targets/cloudflare/build.ts +0 -3319
- package/src/runtime/targets/container/build.ts +0 -146
- package/src/runtime/targets/container/server.ts +0 -33
- package/src/runtime/targets/vps/deploy.ts +0 -223
- package/src/templates/skills/agentkit-deploy/SKILL.md +0 -52
- package/src/templates/skills/agentkit-integrations/SKILL.md +0 -98
|
@@ -12,9 +12,19 @@ import type {
|
|
|
12
12
|
WebhookVerificationResult,
|
|
13
13
|
} from "../channels";
|
|
14
14
|
import { AgentKitError } from "../errors";
|
|
15
|
+
import {
|
|
16
|
+
CHANNEL_DOWNLOAD_TIMEOUT_MS,
|
|
17
|
+
CHANNEL_SEND_TIMEOUT_MS,
|
|
18
|
+
CHANNEL_STATUS_TIMEOUT_MS,
|
|
19
|
+
classifySendRequestError,
|
|
20
|
+
isRetryableReadRequestError,
|
|
21
|
+
} from "./provider-fetch";
|
|
22
|
+
import { createPublicHttpsFetch, isBlockedOutboundHost } from "./net-guard";
|
|
15
23
|
|
|
16
24
|
type EvolutionFetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|
17
25
|
|
|
26
|
+
const evolutionFetch: EvolutionFetch = createPublicHttpsFetch("EVOLUTION_API_BASE_URL");
|
|
27
|
+
|
|
18
28
|
type EvolutionSendTextRequest = {
|
|
19
29
|
method: "POST";
|
|
20
30
|
url: string;
|
|
@@ -218,7 +228,7 @@ export function buildEvolutionSendTextRequest(input: ChannelSendInput): Evolutio
|
|
|
218
228
|
|
|
219
229
|
export async function sendEvolutionMessage(
|
|
220
230
|
input: ChannelSendInput,
|
|
221
|
-
fetcher: EvolutionFetch =
|
|
231
|
+
fetcher: EvolutionFetch = evolutionFetch,
|
|
222
232
|
): Promise<ChannelSendResult> {
|
|
223
233
|
if (input.secrets.AGENTKIT_CHANNEL_SEND_DRY_RUN === "1") {
|
|
224
234
|
const request = buildEvolutionSendTextRequest(input);
|
|
@@ -267,6 +277,7 @@ export async function sendEvolutionMessage(
|
|
|
267
277
|
|
|
268
278
|
try {
|
|
269
279
|
response = await fetcher(actualRequest.url, {
|
|
280
|
+
signal: AbortSignal.timeout(CHANNEL_SEND_TIMEOUT_MS),
|
|
270
281
|
method: actualRequest.method,
|
|
271
282
|
headers: {
|
|
272
283
|
apikey: apiKey,
|
|
@@ -276,11 +287,12 @@ export async function sendEvolutionMessage(
|
|
|
276
287
|
body: JSON.stringify(actualRequest.body),
|
|
277
288
|
});
|
|
278
289
|
} catch (error) {
|
|
290
|
+
const failure = classifySendRequestError(error);
|
|
279
291
|
return {
|
|
280
292
|
ok: false,
|
|
281
|
-
retryable:
|
|
282
|
-
code:
|
|
283
|
-
message: `Evolution sendText request
|
|
293
|
+
retryable: failure.retryable,
|
|
294
|
+
code: failure.code,
|
|
295
|
+
message: `Evolution sendText request ${failure.reason}: ${redactString(
|
|
284
296
|
error instanceof Error ? error.message : String(error),
|
|
285
297
|
[apiKey, baseUrl, instanceName],
|
|
286
298
|
)}`,
|
|
@@ -293,7 +305,7 @@ export async function sendEvolutionMessage(
|
|
|
293
305
|
};
|
|
294
306
|
}
|
|
295
307
|
|
|
296
|
-
const payload = await readEvolutionResponseBody(response);
|
|
308
|
+
const payload = await readEvolutionResponseBody(response).catch(() => null);
|
|
297
309
|
const redactedPayload = redactValue(payload, [apiKey, baseUrl, instanceName]);
|
|
298
310
|
const messageId = readEvolutionProviderMessageId(payload);
|
|
299
311
|
|
|
@@ -345,7 +357,7 @@ export async function sendEvolutionMessage(
|
|
|
345
357
|
|
|
346
358
|
export async function downloadEvolutionMedia(
|
|
347
359
|
input: ChannelMediaDownloadInput,
|
|
348
|
-
fetcher: EvolutionFetch =
|
|
360
|
+
fetcher: EvolutionFetch = evolutionFetch,
|
|
349
361
|
): Promise<ChannelMediaDownloadResult> {
|
|
350
362
|
const baseUrl = input.secrets.EVOLUTION_API_BASE_URL;
|
|
351
363
|
const apiKey = input.secrets.EVOLUTION_API_KEY;
|
|
@@ -388,6 +400,7 @@ export async function downloadEvolutionMedia(
|
|
|
388
400
|
|
|
389
401
|
try {
|
|
390
402
|
response = await fetcher(url, {
|
|
403
|
+
signal: AbortSignal.timeout(CHANNEL_DOWNLOAD_TIMEOUT_MS),
|
|
391
404
|
method: "POST",
|
|
392
405
|
headers: {
|
|
393
406
|
apikey: apiKey,
|
|
@@ -406,7 +419,7 @@ export async function downloadEvolutionMedia(
|
|
|
406
419
|
} catch (error) {
|
|
407
420
|
return {
|
|
408
421
|
ok: false,
|
|
409
|
-
retryable:
|
|
422
|
+
retryable: isRetryableReadRequestError(error),
|
|
410
423
|
code: "channel_provider_unavailable",
|
|
411
424
|
message: `Evolution media download failed before a response: ${redactString(
|
|
412
425
|
error instanceof Error ? error.message : String(error),
|
|
@@ -435,7 +448,15 @@ export async function downloadEvolutionMedia(
|
|
|
435
448
|
};
|
|
436
449
|
}
|
|
437
450
|
|
|
438
|
-
|
|
451
|
+
return {
|
|
452
|
+
ok: false,
|
|
453
|
+
retryable: true,
|
|
454
|
+
code: "channel_provider_unavailable",
|
|
455
|
+
message: `Evolution media download response could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
456
|
+
providerMetadata: {
|
|
457
|
+
status: response.status,
|
|
458
|
+
},
|
|
459
|
+
};
|
|
439
460
|
}
|
|
440
461
|
|
|
441
462
|
if (!response.ok) {
|
|
@@ -540,7 +561,7 @@ export async function downloadEvolutionMedia(
|
|
|
540
561
|
|
|
541
562
|
export async function getEvolutionStatus(
|
|
542
563
|
input: ChannelStatusInput,
|
|
543
|
-
fetcher: EvolutionFetch =
|
|
564
|
+
fetcher: EvolutionFetch = evolutionFetch,
|
|
544
565
|
): Promise<ChannelStatusResult> {
|
|
545
566
|
const missingSecrets = ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME"].filter(
|
|
546
567
|
(secret) => !input.secrets[secret],
|
|
@@ -573,6 +594,7 @@ export async function getEvolutionStatus(
|
|
|
573
594
|
|
|
574
595
|
try {
|
|
575
596
|
response = await fetcher(url, {
|
|
597
|
+
signal: AbortSignal.timeout(CHANNEL_STATUS_TIMEOUT_MS),
|
|
576
598
|
method: "GET",
|
|
577
599
|
headers: {
|
|
578
600
|
apikey: input.secrets.EVOLUTION_API_KEY,
|
|
@@ -590,7 +612,7 @@ export async function getEvolutionStatus(
|
|
|
590
612
|
};
|
|
591
613
|
}
|
|
592
614
|
|
|
593
|
-
const payload = await readEvolutionResponseBody(response);
|
|
615
|
+
const payload = await readEvolutionResponseBody(response).catch(() => null);
|
|
594
616
|
|
|
595
617
|
if (!response.ok) {
|
|
596
618
|
return {
|
|
@@ -1160,64 +1182,15 @@ function validateEvolutionBaseUrl(baseUrl: string): string {
|
|
|
1160
1182
|
|
|
1161
1183
|
const host = parsed.hostname.toLowerCase();
|
|
1162
1184
|
|
|
1163
|
-
if (
|
|
1185
|
+
if (isBlockedOutboundHost(host)) {
|
|
1164
1186
|
throw new Error("EVOLUTION_API_BASE_URL must be a public HTTPS origin.");
|
|
1165
1187
|
}
|
|
1166
1188
|
|
|
1167
1189
|
return parsed.origin;
|
|
1168
1190
|
}
|
|
1169
1191
|
|
|
1170
|
-
function isBlockedHost(host: string): boolean {
|
|
1171
|
-
return (
|
|
1172
|
-
host === "localhost" ||
|
|
1173
|
-
host === "metadata.google.internal" ||
|
|
1174
|
-
host.endsWith(".localhost") ||
|
|
1175
|
-
host.endsWith(".local") ||
|
|
1176
|
-
host.endsWith(".internal") ||
|
|
1177
|
-
isBlockedIpv4(host) ||
|
|
1178
|
-
isBlockedIpv6(host)
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
1192
|
|
|
1182
|
-
function isBlockedIpv4(host: string): boolean {
|
|
1183
|
-
const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
1184
1193
|
|
|
1185
|
-
if (!match) {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
const parts = match.slice(1).map((part) => Number(part));
|
|
1190
|
-
|
|
1191
|
-
if (parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
1192
|
-
return true;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
const [a, b] = parts;
|
|
1196
|
-
|
|
1197
|
-
return (
|
|
1198
|
-
a === 0 ||
|
|
1199
|
-
a === 10 ||
|
|
1200
|
-
a === 127 ||
|
|
1201
|
-
(a === 100 && b >= 64 && b <= 127) ||
|
|
1202
|
-
(a === 169 && b === 254) ||
|
|
1203
|
-
(a === 172 && b >= 16 && b <= 31) ||
|
|
1204
|
-
(a === 192 && b === 168) ||
|
|
1205
|
-
(a === 198 && (b === 18 || b === 19)) ||
|
|
1206
|
-
a >= 224
|
|
1207
|
-
);
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
function isBlockedIpv6(host: string): boolean {
|
|
1211
|
-
const normalized = host.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
1212
|
-
|
|
1213
|
-
return (
|
|
1214
|
-
normalized === "::1" ||
|
|
1215
|
-
normalized === "::" ||
|
|
1216
|
-
normalized.startsWith("fc") ||
|
|
1217
|
-
normalized.startsWith("fd") ||
|
|
1218
|
-
normalized.startsWith("fe80:")
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
1221
1194
|
|
|
1222
1195
|
function readRecord(record: Record<string, unknown>, key: string): Record<string, unknown> {
|
|
1223
1196
|
const value = record[key];
|
|
@@ -12,9 +12,19 @@ import type {
|
|
|
12
12
|
WebhookVerificationResult,
|
|
13
13
|
} from "../channels";
|
|
14
14
|
import { AgentKitError } from "../errors";
|
|
15
|
+
import {
|
|
16
|
+
CHANNEL_DOWNLOAD_TIMEOUT_MS,
|
|
17
|
+
CHANNEL_SEND_TIMEOUT_MS,
|
|
18
|
+
CHANNEL_STATUS_TIMEOUT_MS,
|
|
19
|
+
classifySendRequestError,
|
|
20
|
+
isRetryableReadRequestError,
|
|
21
|
+
} from "./provider-fetch";
|
|
22
|
+
import { createPublicHttpsFetch, isBlockedOutboundHost } from "./net-guard";
|
|
15
23
|
|
|
16
24
|
type UazapiFetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|
17
25
|
|
|
26
|
+
const uazapiFetch: UazapiFetch = createPublicHttpsFetch("UAZAPI_BASE_URL");
|
|
27
|
+
|
|
18
28
|
type UazapiSendTextRequest = {
|
|
19
29
|
method: "POST";
|
|
20
30
|
url: string;
|
|
@@ -195,7 +205,7 @@ export function buildUazapiSendTextRequest(input: ChannelSendInput): UazapiSendT
|
|
|
195
205
|
|
|
196
206
|
export async function sendUazapiMessage(
|
|
197
207
|
input: ChannelSendInput,
|
|
198
|
-
fetcher: UazapiFetch =
|
|
208
|
+
fetcher: UazapiFetch = uazapiFetch,
|
|
199
209
|
): Promise<ChannelSendResult> {
|
|
200
210
|
if (input.secrets.AGENTKIT_CHANNEL_SEND_DRY_RUN === "1") {
|
|
201
211
|
const request = buildUazapiSendTextRequest(input);
|
|
@@ -243,6 +253,7 @@ export async function sendUazapiMessage(
|
|
|
243
253
|
|
|
244
254
|
try {
|
|
245
255
|
response = await fetcher(actualRequest.url, {
|
|
256
|
+
signal: AbortSignal.timeout(CHANNEL_SEND_TIMEOUT_MS),
|
|
246
257
|
method: actualRequest.method,
|
|
247
258
|
headers: {
|
|
248
259
|
token,
|
|
@@ -252,13 +263,14 @@ export async function sendUazapiMessage(
|
|
|
252
263
|
body: JSON.stringify(actualRequest.body),
|
|
253
264
|
});
|
|
254
265
|
} catch (error) {
|
|
266
|
+
const failure = classifySendRequestError(error);
|
|
255
267
|
const message = redactString(error instanceof Error ? error.message : String(error), [token, baseUrl]);
|
|
256
268
|
|
|
257
269
|
return {
|
|
258
270
|
ok: false,
|
|
259
|
-
retryable:
|
|
260
|
-
code:
|
|
261
|
-
message: `UAZAPI sendText request
|
|
271
|
+
retryable: failure.retryable,
|
|
272
|
+
code: failure.code,
|
|
273
|
+
message: `UAZAPI sendText request ${failure.reason}: ${message}`,
|
|
262
274
|
providerRequestId: request.idempotencyKey,
|
|
263
275
|
providerMetadata: {
|
|
264
276
|
method: request.method,
|
|
@@ -268,7 +280,7 @@ export async function sendUazapiMessage(
|
|
|
268
280
|
};
|
|
269
281
|
}
|
|
270
282
|
|
|
271
|
-
const payload = await readUazapiResponseBody(response);
|
|
283
|
+
const payload = await readUazapiResponseBody(response).catch(() => null);
|
|
272
284
|
const redactedPayload = redactValue(payload, [token, baseUrl]);
|
|
273
285
|
const messageId = readUazapiProviderMessageId(payload);
|
|
274
286
|
|
|
@@ -320,7 +332,7 @@ export async function sendUazapiMessage(
|
|
|
320
332
|
|
|
321
333
|
export async function downloadUazapiMedia(
|
|
322
334
|
input: ChannelMediaDownloadInput,
|
|
323
|
-
fetcher: UazapiFetch =
|
|
335
|
+
fetcher: UazapiFetch = uazapiFetch,
|
|
324
336
|
): Promise<ChannelMediaDownloadResult> {
|
|
325
337
|
const token = input.secrets.UAZAPI_TOKEN;
|
|
326
338
|
const baseUrl = input.secrets.UAZAPI_BASE_URL;
|
|
@@ -362,6 +374,7 @@ export async function downloadUazapiMedia(
|
|
|
362
374
|
|
|
363
375
|
try {
|
|
364
376
|
response = await fetcher(url, {
|
|
377
|
+
signal: AbortSignal.timeout(CHANNEL_DOWNLOAD_TIMEOUT_MS),
|
|
365
378
|
method: "POST",
|
|
366
379
|
headers: {
|
|
367
380
|
token,
|
|
@@ -379,7 +392,7 @@ export async function downloadUazapiMedia(
|
|
|
379
392
|
} catch (error) {
|
|
380
393
|
return {
|
|
381
394
|
ok: false,
|
|
382
|
-
retryable:
|
|
395
|
+
retryable: isRetryableReadRequestError(error),
|
|
383
396
|
code: "channel_provider_unavailable",
|
|
384
397
|
message: `UAZAPI media download failed before a response: ${redactString(
|
|
385
398
|
error instanceof Error ? error.message : String(error),
|
|
@@ -408,7 +421,15 @@ export async function downloadUazapiMedia(
|
|
|
408
421
|
};
|
|
409
422
|
}
|
|
410
423
|
|
|
411
|
-
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
retryable: true,
|
|
427
|
+
code: "channel_provider_unavailable",
|
|
428
|
+
message: `UAZAPI media download response could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
429
|
+
providerMetadata: {
|
|
430
|
+
status: response.status,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
412
433
|
}
|
|
413
434
|
|
|
414
435
|
if (!response.ok) {
|
|
@@ -511,7 +532,7 @@ export async function downloadUazapiMedia(
|
|
|
511
532
|
|
|
512
533
|
export async function getUazapiStatus(
|
|
513
534
|
input: ChannelStatusInput,
|
|
514
|
-
fetcher: UazapiFetch =
|
|
535
|
+
fetcher: UazapiFetch = uazapiFetch,
|
|
515
536
|
): Promise<ChannelStatusResult> {
|
|
516
537
|
const missingSecrets = ["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"].filter(
|
|
517
538
|
(secret) => !input.secrets[secret],
|
|
@@ -541,6 +562,7 @@ export async function getUazapiStatus(
|
|
|
541
562
|
|
|
542
563
|
try {
|
|
543
564
|
response = await fetcher(url, {
|
|
565
|
+
signal: AbortSignal.timeout(CHANNEL_STATUS_TIMEOUT_MS),
|
|
544
566
|
method: "GET",
|
|
545
567
|
headers: {
|
|
546
568
|
token: input.secrets.UAZAPI_TOKEN,
|
|
@@ -558,7 +580,7 @@ export async function getUazapiStatus(
|
|
|
558
580
|
};
|
|
559
581
|
}
|
|
560
582
|
|
|
561
|
-
const payload = await readUazapiResponseBody(response);
|
|
583
|
+
const payload = await readUazapiResponseBody(response).catch(() => null);
|
|
562
584
|
|
|
563
585
|
if (!response.ok) {
|
|
564
586
|
return {
|
|
@@ -1034,7 +1056,7 @@ function validateUazapiBaseUrl(rawBaseUrl: string): string {
|
|
|
1034
1056
|
throw new Error("UAZAPI_BASE_URL must not include credentials.");
|
|
1035
1057
|
}
|
|
1036
1058
|
|
|
1037
|
-
if (
|
|
1059
|
+
if (isBlockedOutboundHost(url.hostname)) {
|
|
1038
1060
|
throw new Error("UAZAPI_BASE_URL host is not allowed.");
|
|
1039
1061
|
}
|
|
1040
1062
|
|
|
@@ -1045,165 +1067,12 @@ function validateUazapiBaseUrl(rawBaseUrl: string): string {
|
|
|
1045
1067
|
return url.href.replace(/\/$/, "");
|
|
1046
1068
|
}
|
|
1047
1069
|
|
|
1048
|
-
function isBlockedHostname(hostname: string): boolean {
|
|
1049
|
-
const host = normalizeUrlHostname(hostname);
|
|
1050
|
-
|
|
1051
|
-
if (
|
|
1052
|
-
host === "localhost" ||
|
|
1053
|
-
host.endsWith(".localhost") ||
|
|
1054
|
-
host === "metadata.google.internal" ||
|
|
1055
|
-
host === "169.254.169.254"
|
|
1056
|
-
) {
|
|
1057
|
-
return true;
|
|
1058
|
-
}
|
|
1059
1070
|
|
|
1060
|
-
const ipv4 = parseIpv4Octets(host);
|
|
1061
1071
|
|
|
1062
|
-
if (ipv4) {
|
|
1063
|
-
return isBlockedIpv4Octets(ipv4);
|
|
1064
|
-
}
|
|
1065
1072
|
|
|
1066
|
-
if (host.includes(":")) {
|
|
1067
|
-
return isBlockedIpv6Hostname(host);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
return false;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
function normalizeUrlHostname(hostname: string): string {
|
|
1074
|
-
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
1075
|
-
return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
1076
|
-
}
|
|
1077
1073
|
|
|
1078
|
-
function isBlockedIpv4Octets(parts: number[]): boolean {
|
|
1079
|
-
const [a, b] = parts;
|
|
1080
1074
|
|
|
1081
|
-
return (
|
|
1082
|
-
a === 10 ||
|
|
1083
|
-
a === 127 ||
|
|
1084
|
-
(a === 169 && b === 254) ||
|
|
1085
|
-
(a === 172 && b !== undefined && b >= 16 && b <= 31) ||
|
|
1086
|
-
(a === 192 && b === 168) ||
|
|
1087
|
-
a === 0
|
|
1088
|
-
);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
function isBlockedIpv6Hostname(host: string): boolean {
|
|
1092
|
-
if (host.includes("%")) {
|
|
1093
|
-
return true;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
const segments = parseIpv6Segments(host);
|
|
1097
|
-
|
|
1098
|
-
if (!segments) {
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
const first = segments[0] ?? 0;
|
|
1103
|
-
const isUnspecified = segments.every((segment) => segment === 0);
|
|
1104
|
-
const isLoopback = segments.slice(0, 7).every((segment) => segment === 0) && segments[7] === 1;
|
|
1105
|
-
const isUniqueLocal = (first & 0xfe00) === 0xfc00;
|
|
1106
|
-
const isLinkLocal = (first & 0xffc0) === 0xfe80;
|
|
1107
|
-
const isIpv4Mapped = segments.slice(0, 5).every((segment) => segment === 0) && segments[5] === 0xffff;
|
|
1108
|
-
const isIpv4Compatible = segments.slice(0, 6).every((segment) => segment === 0) && (segments[6] !== 0 || segments[7] !== 0);
|
|
1109
|
-
|
|
1110
|
-
if (isUnspecified || isLoopback || isUniqueLocal || isLinkLocal) {
|
|
1111
|
-
return true;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
if (isIpv4Mapped || isIpv4Compatible) {
|
|
1115
|
-
return isBlockedIpv4Octets([
|
|
1116
|
-
(segments[6] >> 8) & 255,
|
|
1117
|
-
segments[6] & 255,
|
|
1118
|
-
(segments[7] >> 8) & 255,
|
|
1119
|
-
segments[7] & 255,
|
|
1120
|
-
]);
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
return false;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
function parseIpv6Segments(host: string): number[] | undefined {
|
|
1127
|
-
if (!host.includes(":")) {
|
|
1128
|
-
return undefined;
|
|
1129
|
-
}
|
|
1130
1075
|
|
|
1131
|
-
if ((host.match(/::/g) ?? []).length > 1) {
|
|
1132
|
-
return undefined;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
const [left = "", right = ""] = host.split("::");
|
|
1136
|
-
const leftSegments = parseIpv6SegmentSide(left);
|
|
1137
|
-
const rightSegments = parseIpv6SegmentSide(right);
|
|
1138
|
-
|
|
1139
|
-
if (!leftSegments || !rightSegments) {
|
|
1140
|
-
return undefined;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (!host.includes("::")) {
|
|
1144
|
-
return leftSegments.length === 8 ? leftSegments : undefined;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
const missing = 8 - leftSegments.length - rightSegments.length;
|
|
1148
|
-
|
|
1149
|
-
if (missing < 1) {
|
|
1150
|
-
return undefined;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
return [...leftSegments, ...Array.from({ length: missing }, () => 0), ...rightSegments];
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
function parseIpv6SegmentSide(value: string): number[] | undefined {
|
|
1157
|
-
if (!value) {
|
|
1158
|
-
return [];
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
const parts = value.split(":");
|
|
1162
|
-
const segments: number[] = [];
|
|
1163
|
-
|
|
1164
|
-
for (const [index, part] of parts.entries()) {
|
|
1165
|
-
if (!part) {
|
|
1166
|
-
return undefined;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
if (part.includes(".")) {
|
|
1170
|
-
if (index !== parts.length - 1) {
|
|
1171
|
-
return undefined;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
const ipv4 = parseIpv4Octets(part);
|
|
1175
|
-
|
|
1176
|
-
if (!ipv4) {
|
|
1177
|
-
return undefined;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
segments.push((ipv4[0] << 8) | ipv4[1], (ipv4[2] << 8) | ipv4[3]);
|
|
1181
|
-
continue;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
if (!/^[0-9a-f]{1,4}$/i.test(part)) {
|
|
1185
|
-
return undefined;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
segments.push(Number.parseInt(part, 16));
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
return segments;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
function parseIpv4Octets(host: string): number[] | undefined {
|
|
1195
|
-
if (!/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
1196
|
-
return undefined;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
const parts = host.split(".").map(Number);
|
|
1200
|
-
|
|
1201
|
-
if (parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
1202
|
-
return undefined;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
return parts;
|
|
1206
|
-
}
|
|
1207
1076
|
|
|
1208
1077
|
function stripWhatsappJid(value: string): string {
|
|
1209
1078
|
return value
|
|
@@ -9,6 +9,12 @@ import type {
|
|
|
9
9
|
WebhookVerificationResult,
|
|
10
10
|
} from "../channels";
|
|
11
11
|
import { AgentKitError } from "../errors";
|
|
12
|
+
import {
|
|
13
|
+
CHANNEL_DOWNLOAD_TIMEOUT_MS,
|
|
14
|
+
CHANNEL_SEND_TIMEOUT_MS,
|
|
15
|
+
classifySendRequestError,
|
|
16
|
+
isRetryableReadRequestError,
|
|
17
|
+
} from "./provider-fetch";
|
|
12
18
|
|
|
13
19
|
export const zapsterWhatsappChannelAdapter: ChannelAdapter = {
|
|
14
20
|
type: "whatsapp",
|
|
@@ -364,6 +370,7 @@ export async function sendZapsterMessage(
|
|
|
364
370
|
|
|
365
371
|
try {
|
|
366
372
|
response = await fetcher(actualRequest.url, {
|
|
373
|
+
signal: AbortSignal.timeout(CHANNEL_SEND_TIMEOUT_MS),
|
|
367
374
|
method: actualRequest.method,
|
|
368
375
|
headers: {
|
|
369
376
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -373,13 +380,14 @@ export async function sendZapsterMessage(
|
|
|
373
380
|
body: JSON.stringify(actualRequest.body),
|
|
374
381
|
});
|
|
375
382
|
} catch (error) {
|
|
383
|
+
const failure = classifySendRequestError(error);
|
|
376
384
|
const errorMessage = redactString(error instanceof Error ? error.message : String(error), [apiKey, instanceId]);
|
|
377
385
|
|
|
378
386
|
return {
|
|
379
387
|
ok: false,
|
|
380
|
-
retryable:
|
|
381
|
-
code:
|
|
382
|
-
message: `Zapster sendMessage request
|
|
388
|
+
retryable: failure.retryable,
|
|
389
|
+
code: failure.code,
|
|
390
|
+
message: `Zapster sendMessage request ${failure.reason}: ${errorMessage}`,
|
|
383
391
|
providerRequestId: request.idempotencyKey,
|
|
384
392
|
providerMetadata: {
|
|
385
393
|
method: request.method,
|
|
@@ -476,6 +484,7 @@ export async function downloadZapsterMedia(
|
|
|
476
484
|
|
|
477
485
|
try {
|
|
478
486
|
response = await fetcher(mediaUrl.url, {
|
|
487
|
+
signal: AbortSignal.timeout(CHANNEL_DOWNLOAD_TIMEOUT_MS),
|
|
479
488
|
method: "GET",
|
|
480
489
|
headers: {
|
|
481
490
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -484,7 +493,7 @@ export async function downloadZapsterMedia(
|
|
|
484
493
|
} catch (error) {
|
|
485
494
|
return {
|
|
486
495
|
ok: false,
|
|
487
|
-
retryable:
|
|
496
|
+
retryable: isRetryableReadRequestError(error),
|
|
488
497
|
code: "channel_provider_unavailable",
|
|
489
498
|
message: `Zapster media download failed before a response: ${redactString(
|
|
490
499
|
error instanceof Error ? error.message : String(error),
|
|
@@ -591,8 +600,8 @@ async function readResponseBytesWithLimit(
|
|
|
591
600
|
}
|
|
592
601
|
| {
|
|
593
602
|
ok: false;
|
|
594
|
-
retryable:
|
|
595
|
-
code: "channel_audio_too_large";
|
|
603
|
+
retryable: boolean;
|
|
604
|
+
code: "channel_audio_too_large" | "channel_provider_unavailable";
|
|
596
605
|
message: string;
|
|
597
606
|
providerMetadata?: Record<string, unknown>;
|
|
598
607
|
}
|
|
@@ -613,7 +622,18 @@ async function readResponseBytesWithLimit(
|
|
|
613
622
|
}
|
|
614
623
|
|
|
615
624
|
if (!response.body) {
|
|
616
|
-
|
|
625
|
+
let audio: Uint8Array;
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
audio = new Uint8Array(await response.arrayBuffer());
|
|
629
|
+
} catch (error) {
|
|
630
|
+
return {
|
|
631
|
+
ok: false,
|
|
632
|
+
retryable: true,
|
|
633
|
+
code: "channel_provider_unavailable",
|
|
634
|
+
message: `Media download response could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
617
637
|
|
|
618
638
|
if (audio.byteLength > maxBytes) {
|
|
619
639
|
return {
|
|
@@ -659,6 +679,14 @@ async function readResponseBytesWithLimit(
|
|
|
659
679
|
|
|
660
680
|
chunks.push(chunk.value);
|
|
661
681
|
}
|
|
682
|
+
} catch (error) {
|
|
683
|
+
await reader.cancel().catch(() => undefined);
|
|
684
|
+
return {
|
|
685
|
+
ok: false,
|
|
686
|
+
retryable: true,
|
|
687
|
+
code: "channel_provider_unavailable",
|
|
688
|
+
message: `Media download response could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
689
|
+
};
|
|
662
690
|
} finally {
|
|
663
691
|
reader.releaseLock();
|
|
664
692
|
}
|
package/src/runtime/chat.ts
CHANGED
|
@@ -6,9 +6,8 @@ import type { ConversationRecord, ConversationSummary, PersistedChatRun } from "
|
|
|
6
6
|
import type { LoadedAgentCapsule } from "./config";
|
|
7
7
|
import { loadAgentCapsule } from "./config";
|
|
8
8
|
import { openPreparedCapsuleStore } from "./database";
|
|
9
|
-
import { loadCapsuleEnv } from "./env";
|
|
9
|
+
import { collectSecretValues, loadCapsuleEnv } from "./env";
|
|
10
10
|
import { AgentKitError, isAgentKitError } from "./errors";
|
|
11
|
-
import { createManagedComposioTools } from "./integrations/composio";
|
|
12
11
|
import { syncConfiguredKnowledgeSources } from "./knowledge/ingest";
|
|
13
12
|
import { appendKnowledgePromptPolicy } from "./knowledge/prompt-policy";
|
|
14
13
|
import { createKnowledgeSearchTool } from "./knowledge/tool";
|
|
@@ -46,6 +45,7 @@ export async function runAgentMessage(
|
|
|
46
45
|
|
|
47
46
|
const adapter = getProviderAdapter(capsule.config.provider);
|
|
48
47
|
const env = await loadCapsuleEnv(capsule.root);
|
|
48
|
+
const secretValues = collectSecretValues(capsule.config, env);
|
|
49
49
|
await syncConfiguredKnowledgeSources(capsule);
|
|
50
50
|
const conversationId = normalizeConversationId(options.conversationId);
|
|
51
51
|
const { store } = await openPreparedCapsuleStore(capsule);
|
|
@@ -81,6 +81,7 @@ export async function runAgentMessage(
|
|
|
81
81
|
runId: run.id,
|
|
82
82
|
conversationId: conversation.id,
|
|
83
83
|
env,
|
|
84
|
+
secretValues,
|
|
84
85
|
signal: options.signal,
|
|
85
86
|
clock,
|
|
86
87
|
runtime: {
|
|
@@ -140,6 +141,7 @@ export async function runAgentMessage(
|
|
|
140
141
|
status: "failed",
|
|
141
142
|
errorCode: isAgentKitError(error) ? error.code : "runtime_error",
|
|
142
143
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
144
|
+
secretValues,
|
|
143
145
|
});
|
|
144
146
|
}
|
|
145
147
|
|
|
@@ -150,7 +152,7 @@ export async function runAgentMessage(
|
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
function withKnowledgeTools(capsule: LoadedAgentCapsule): NonNullable<LoadedAgentCapsule["config"]["tools"]> {
|
|
153
|
-
const tools =
|
|
155
|
+
const tools = capsule.config.tools ?? [];
|
|
154
156
|
const knowledgeTool = createKnowledgeSearchTool(capsule);
|
|
155
157
|
|
|
156
158
|
if (knowledgeTool) {
|