@andreprado/agentkit 0.1.0-alpha.4 → 0.1.0-alpha.5
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/package.json +1 -1
- package/src/cli/args.ts +36 -0
- package/src/cli/cloud-client.ts +257 -0
- package/src/cli/commands/channels.ts +421 -0
- package/src/cli/constants.ts +4 -0
- package/src/cli/deploy-chat-ui.ts +385 -0
- package/src/cli/deploy-readiness.ts +329 -0
- package/src/cli/flags.ts +162 -0
- package/src/cli/help.ts +158 -0
- package/src/cli/index.ts +203 -1908
- package/src/cli/process.ts +31 -0
package/src/cli/index.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { mkdir, readFile,
|
|
3
|
-
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
4
|
-
import { homedir } from "node:os";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
4
|
|
|
7
5
|
import { createProject } from "../create-project";
|
|
8
6
|
import { buildAgentCapsule } from "../runtime/build";
|
|
9
7
|
import { findAgentCapsuleRoot, loadAgentCapsule } from "../runtime/config";
|
|
10
8
|
import { readLocalDeployStateIfExists, type LocalDeployState } from "../runtime/core/deploy-state";
|
|
11
|
-
import {
|
|
9
|
+
import { resolveBuildTarget, resolveDeployTarget } from "../runtime/core/targets";
|
|
12
10
|
import { getConversationFromCwd, listConversationsFromCwd } from "../runtime/conversations";
|
|
13
11
|
import { runAgentMessageFromCwd } from "../runtime/chat";
|
|
14
12
|
import { deployBuildToAgentKitCloud, writeLocalDeployState } from "../runtime/deploy";
|
|
@@ -20,214 +18,49 @@ import {
|
|
|
20
18
|
} from "../runtime/db-commands";
|
|
21
19
|
import { runEvalsFromCwd } from "../runtime/evals";
|
|
22
20
|
import { findAgentKitDocs } from "../runtime/docs";
|
|
23
|
-
import { listCapsuleEnv,
|
|
24
|
-
import { loadDeployReadinessContext, stableCloudProjectId, type DeployReadinessContext } from "../runtime/deploy-readiness";
|
|
21
|
+
import { listCapsuleEnv, setCapsuleEnv, unsetCapsuleEnv } from "../runtime/env";
|
|
25
22
|
import { runToolFromCwd } from "../runtime/tool-runner";
|
|
26
23
|
import { startAgentDevServer } from "../runtime/dev-server";
|
|
27
24
|
import { inspectAgentCapsule } from "../runtime/inspect";
|
|
28
25
|
import { AgentKitError, isAgentKitError } from "../runtime/errors";
|
|
29
26
|
import { prepareVpsDeploy } from "../runtime/targets/vps/deploy";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
agentkit secret set <NAME> --stdin
|
|
68
|
-
agentkit secret sync --from-local
|
|
69
|
-
agentkit deploy --smoke "hello"
|
|
70
|
-
agentkit deploy status
|
|
71
|
-
|
|
72
|
-
More:
|
|
73
|
-
agentkit channels ...
|
|
74
|
-
agentkit access token ...
|
|
75
|
-
agentkit env ...
|
|
76
|
-
agentkit docs full
|
|
77
|
-
agentkit help commands
|
|
78
|
-
`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function renderCommandReference(): string {
|
|
82
|
-
return `AgentKit Command Reference
|
|
83
|
-
|
|
84
|
-
Usage:
|
|
85
|
-
agentkit new <name> [--template blank|support|dentista] [--no-install]
|
|
86
|
-
agentkit dev [--port <number>]
|
|
87
|
-
agentkit open
|
|
88
|
-
agentkit chat-ui --deploy [--port <number>] [--token-file <path>]
|
|
89
|
-
agentkit chat --message <text> [--conversation-id <id>]
|
|
90
|
-
agentkit tool <name> [--input <path-or-json>]
|
|
91
|
-
agentkit db migrate
|
|
92
|
-
agentkit db reset --yes
|
|
93
|
-
agentkit db shell
|
|
94
|
-
agentkit db seed [--file <path>]
|
|
95
|
-
agentkit eval run
|
|
96
|
-
agentkit conversations list
|
|
97
|
-
agentkit conversations show <conversation-id>
|
|
98
|
-
agentkit channels list
|
|
99
|
-
agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta] [--api <url>]
|
|
100
|
-
agentkit channels setup <name> [--apply] [--api <url>]
|
|
101
|
-
agentkit channels status <name> [--api <url>]
|
|
102
|
-
agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
|
|
103
|
-
agentkit channels deliveries list <name> [--api <url>]
|
|
104
|
-
agentkit channels deliveries show <delivery-id> [--api <url>]
|
|
105
|
-
agentkit inspect
|
|
106
|
-
agentkit build [--target cloudflare|container]
|
|
107
|
-
agentkit login --token <token> [--api <url>]
|
|
108
|
-
agentkit logout
|
|
109
|
-
agentkit deploy [--target cloudflare|vps] [--host <host>] [--api <url>] [--dry-run] [--anonymous] [--local-wrangler] [--smoke <message>]
|
|
110
|
-
agentkit deploy doctor [--api <url>] [--anonymous]
|
|
111
|
-
agentkit deploy smoke [--message <text>] [--api <url>]
|
|
112
|
-
agentkit deploy status [--api <url>]
|
|
113
|
-
agentkit deploy pause [--api <url>]
|
|
114
|
-
agentkit deploy resume [--api <url>]
|
|
115
|
-
agentkit secret set <NAME> <VALUE> [--api <url>]
|
|
116
|
-
agentkit secret set <NAME> --stdin [--api <url>]
|
|
117
|
-
agentkit secret set <NAME> --from-env [ENV_NAME] [--api <url>]
|
|
118
|
-
agentkit secret set <NAME> --from-local-env [--api <url>]
|
|
119
|
-
agentkit secret sync --from-local [--api <url>]
|
|
120
|
-
agentkit secret list [--api <url>]
|
|
121
|
-
agentkit secret unset <NAME> [--api <url>]
|
|
122
|
-
agentkit access token create <name> [--api <url>] [--out <path>]
|
|
123
|
-
agentkit access token list [--api <url>]
|
|
124
|
-
agentkit access token revoke <token-id> [--api <url>]
|
|
125
|
-
agentkit env set <NAME> <VALUE>
|
|
126
|
-
agentkit env set <NAME> --stdin
|
|
127
|
-
agentkit env set <NAME> --from-env [ENV_NAME]
|
|
128
|
-
agentkit env list
|
|
129
|
-
agentkit env unset <NAME>
|
|
130
|
-
agentkit docs path
|
|
131
|
-
agentkit docs llms
|
|
132
|
-
agentkit docs full
|
|
133
|
-
agentkit handoff codex [goal]
|
|
134
|
-
agentkit help commands
|
|
135
|
-
agentkit --help
|
|
136
|
-
|
|
137
|
-
Commands:
|
|
138
|
-
new Create a new Agent Capsule
|
|
139
|
-
dev Run the Agent Capsule locally
|
|
140
|
-
open Open the local Agent Capsule UI if it is already running
|
|
141
|
-
chat-ui Run a local chat UI, optionally pointed at a hosted deploy
|
|
142
|
-
chat Send one message to the local Agent Capsule
|
|
143
|
-
tool Run one registered tool directly
|
|
144
|
-
db Manage the local SQLite database
|
|
145
|
-
eval Run local eval files
|
|
146
|
-
conversations List and inspect stored local conversations
|
|
147
|
-
channels Manage hosted channel resources
|
|
148
|
-
inspect Print machine-readable Agent Capsule state
|
|
149
|
-
build Build a hosted deploy artifact
|
|
150
|
-
login Store an AgentKit Cloud alpha token outside the capsule
|
|
151
|
-
logout Remove the stored AgentKit Cloud alpha token
|
|
152
|
-
deploy Build and send a hosted artifact to AgentKit Cloud
|
|
153
|
-
secret Manage hosted AgentKit Cloud secrets without readback
|
|
154
|
-
access Manage hosted deploy access tokens
|
|
155
|
-
env Manage local .env values without opening code
|
|
156
|
-
docs Print AgentKit docs locations
|
|
157
|
-
handoff Print a ready-to-paste prompt for another coding agent
|
|
158
|
-
help Print focused help or the full command reference
|
|
159
|
-
|
|
160
|
-
Examples:
|
|
161
|
-
agentkit new support-agent --template support
|
|
162
|
-
cd support-agent
|
|
163
|
-
agentkit dev
|
|
164
|
-
agentkit open
|
|
165
|
-
agentkit tool lookup_order --input '{"orderId":"A100"}'
|
|
166
|
-
agentkit eval run
|
|
167
|
-
|
|
168
|
-
agentkit new clara-dentista --template dentista
|
|
169
|
-
`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
type ParsedArgs = {
|
|
173
|
-
command?: string;
|
|
174
|
-
positional: string[];
|
|
175
|
-
flags: Record<string, string | boolean>;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
function parseArgs(argv: string[]): ParsedArgs {
|
|
179
|
-
const [command, ...rest] = argv;
|
|
180
|
-
const positional: string[] = [];
|
|
181
|
-
const flags: Record<string, string | boolean> = {};
|
|
182
|
-
|
|
183
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
184
|
-
const value = rest[index];
|
|
185
|
-
|
|
186
|
-
if (!value.startsWith("--")) {
|
|
187
|
-
positional.push(value);
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const name = value.slice(2);
|
|
192
|
-
const next = rest[index + 1];
|
|
193
|
-
|
|
194
|
-
if (next && !next.startsWith("--")) {
|
|
195
|
-
flags[name] = next;
|
|
196
|
-
index += 1;
|
|
197
|
-
} else {
|
|
198
|
-
flags[name] = true;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return { command, positional, flags };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function isHelpRequested(args: ParsedArgs): boolean {
|
|
206
|
-
return args.flags.help === true || args.flags.h === true || args.positional.includes("--help") || args.positional.includes("-h");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function renderCommandHelp(command: string): string {
|
|
210
|
-
const usage: Record<string, string> = {
|
|
211
|
-
new: "agentkit new <name> [--template blank|support|dentista] [--no-install]",
|
|
212
|
-
dev: "agentkit dev [--port <number>]",
|
|
213
|
-
open: "agentkit open",
|
|
214
|
-
"chat-ui": "agentkit chat-ui --deploy [--port <number>] [--token-file <path>]",
|
|
215
|
-
chat: 'agentkit chat --message "hello" [--conversation-id <id>]',
|
|
216
|
-
tool: "agentkit tool <name> [--input <path-or-json>]",
|
|
217
|
-
deploy:
|
|
218
|
-
'agentkit deploy [--smoke "hello"] | agentkit deploy smoke [--message "hello"] | agentkit deploy doctor | status | pause | resume',
|
|
219
|
-
secret:
|
|
220
|
-
"agentkit secret set <NAME> --stdin | --from-env [ENV_NAME] | --from-local-env | agentkit secret sync --from-local | list | unset <NAME>",
|
|
221
|
-
access: "agentkit access token create <name> [--out <path>] | list | revoke <token-id>",
|
|
222
|
-
env: "agentkit env set <NAME> --stdin | --from-env [ENV_NAME] | list | unset <NAME>",
|
|
223
|
-
docs: "agentkit docs path | llms | full",
|
|
224
|
-
handoff: "agentkit handoff codex [goal] | agentkit handoff claude [goal]",
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
return `Usage: ${usage[command] ?? "agentkit <command> [options]"}
|
|
228
|
-
|
|
229
|
-
Run \`agentkit help commands\` for the full command reference.`;
|
|
230
|
-
}
|
|
27
|
+
import { parseArgs, isHelpRequested, type ParsedArgs } from "./args";
|
|
28
|
+
import {
|
|
29
|
+
defaultAgentKitDevPort,
|
|
30
|
+
defaultDeployChatAccessTokenName,
|
|
31
|
+
defaultDeployChatAccessTokenPath,
|
|
32
|
+
} from "./constants";
|
|
33
|
+
import {
|
|
34
|
+
clearCloudAuth,
|
|
35
|
+
cloudApiRequest,
|
|
36
|
+
readCloudAuth,
|
|
37
|
+
readCloudError,
|
|
38
|
+
resolveCloudApiUrl,
|
|
39
|
+
verifyCloudLogin,
|
|
40
|
+
writeCloudAuth,
|
|
41
|
+
type CloudMeResponse,
|
|
42
|
+
type DeployAccessTokenCreateResponse,
|
|
43
|
+
type DeployAccessTokenListResponse,
|
|
44
|
+
} from "./cloud-client";
|
|
45
|
+
import {
|
|
46
|
+
checkDeployReadiness,
|
|
47
|
+
formatReadinessError,
|
|
48
|
+
printDeployReadinessReport,
|
|
49
|
+
resolveProjectIdForSecretCommand,
|
|
50
|
+
syncHostedSecretsFromLocal,
|
|
51
|
+
} from "./deploy-readiness";
|
|
52
|
+
import { readDeployAccessTokenForUi, startDeployChatUiServer } from "./deploy-chat-ui";
|
|
53
|
+
import {
|
|
54
|
+
parseCloudApiUrl,
|
|
55
|
+
parseOptionalStringFlag,
|
|
56
|
+
parsePort,
|
|
57
|
+
parseRequiredStringFlag,
|
|
58
|
+
parseTargetFlag,
|
|
59
|
+
resolveSecretInputValue,
|
|
60
|
+
} from "./flags";
|
|
61
|
+
import { renderCommandHelp, renderCommandReference, renderHelp } from "./help";
|
|
62
|
+
import { runInheritedCommand, waitForShutdown } from "./process";
|
|
63
|
+
import { handleChannelsCommand } from "./commands/channels";
|
|
231
64
|
|
|
232
65
|
async function main() {
|
|
233
66
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -956,1379 +789,231 @@ Final response:
|
|
|
956
789
|
`;
|
|
957
790
|
}
|
|
958
791
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
status: DeployReadinessStatus;
|
|
963
|
-
title: string;
|
|
964
|
-
message: string;
|
|
965
|
-
action?: string;
|
|
966
|
-
};
|
|
792
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
793
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
794
|
+
}
|
|
967
795
|
|
|
968
|
-
|
|
969
|
-
ok: boolean;
|
|
970
|
-
apiUrl: string;
|
|
971
|
-
context: DeployReadinessContext;
|
|
972
|
-
checks: DeployReadinessCheck[];
|
|
973
|
-
};
|
|
974
|
-
|
|
975
|
-
type CloudMeResponse = {
|
|
976
|
-
account?: {
|
|
977
|
-
email?: string;
|
|
978
|
-
status?: string;
|
|
979
|
-
};
|
|
980
|
-
};
|
|
981
|
-
|
|
982
|
-
type CloudCapabilitiesResponse = {
|
|
983
|
-
capabilities?: Array<{
|
|
984
|
-
name?: string;
|
|
985
|
-
status?: string;
|
|
986
|
-
}>;
|
|
987
|
-
};
|
|
988
|
-
|
|
989
|
-
type CloudLimitsResponse = {
|
|
990
|
-
online_agents?: {
|
|
991
|
-
account?: {
|
|
992
|
-
used?: number | null;
|
|
993
|
-
limit?: number | null;
|
|
994
|
-
};
|
|
995
|
-
global?: {
|
|
996
|
-
used?: number | null;
|
|
997
|
-
limit?: number | null;
|
|
998
|
-
};
|
|
999
|
-
};
|
|
1000
|
-
};
|
|
1001
|
-
|
|
1002
|
-
type DeployAccessTokenCreateResponse = {
|
|
1003
|
-
access_token?: {
|
|
1004
|
-
id?: string;
|
|
1005
|
-
deploy_id?: string;
|
|
1006
|
-
name?: string;
|
|
1007
|
-
token?: string;
|
|
1008
|
-
};
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
type DeployAccessTokenListResponse = {
|
|
1012
|
-
access_tokens?: Array<{
|
|
1013
|
-
id?: string;
|
|
1014
|
-
deploy_id?: string;
|
|
1015
|
-
name?: string;
|
|
1016
|
-
}>;
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
type CloudSecretsResponse = {
|
|
1020
|
-
secrets?: Array<{
|
|
1021
|
-
name?: string;
|
|
1022
|
-
status?: string;
|
|
1023
|
-
updated_at?: string;
|
|
1024
|
-
}>;
|
|
1025
|
-
};
|
|
1026
|
-
|
|
1027
|
-
async function checkDeployReadiness(options: {
|
|
796
|
+
async function ensureDeployChatAccessToken(options: {
|
|
1028
797
|
apiUrl: string;
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
title: "Target runtime",
|
|
1040
|
-
message: 'Cloudflare deploy requires runtime: "edge" in agentkit.config.ts.',
|
|
1041
|
-
action: "set runtime: \"edge\" before deploying to AgentKit Cloud",
|
|
1042
|
-
});
|
|
1043
|
-
} else {
|
|
1044
|
-
checks.push({
|
|
1045
|
-
status: "pass",
|
|
1046
|
-
title: "Target runtime",
|
|
1047
|
-
message: "Capsule is configured for edge deploy.",
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
if (!auth?.token) {
|
|
1052
|
-
checks.push({
|
|
1053
|
-
status: options.requireLogin ? "fail" : "warn",
|
|
1054
|
-
title: "Login",
|
|
1055
|
-
message: options.requireLogin
|
|
1056
|
-
? "No AgentKit Cloud token was found for hosted production deploy."
|
|
1057
|
-
: "No AgentKit Cloud token was found; deploy will request anonymous mode.",
|
|
1058
|
-
action: "agentkit login --token <token>",
|
|
1059
|
-
});
|
|
1060
|
-
} else {
|
|
1061
|
-
try {
|
|
1062
|
-
const me = (await cloudApiRequest(options.apiUrl, "/v1/me", { method: "GET" })) as CloudMeResponse;
|
|
1063
|
-
const email = me.account?.email;
|
|
1064
|
-
checks.push({
|
|
1065
|
-
status: "pass",
|
|
1066
|
-
title: "Login",
|
|
1067
|
-
message: email ? `Authenticated as ${email}.` : "AgentKit Cloud token is accepted.",
|
|
1068
|
-
});
|
|
1069
|
-
cloudAuthUsable = true;
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
checks.push({
|
|
1072
|
-
status: "fail",
|
|
1073
|
-
title: "Login",
|
|
1074
|
-
message: `AgentKit Cloud rejected the token from ${formatCloudAuthSource(auth)}. ${formatReadinessError(error)}`,
|
|
1075
|
-
action: "agentkit login --token <token>",
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
798
|
+
root: string;
|
|
799
|
+
deployId: string;
|
|
800
|
+
skip: boolean;
|
|
801
|
+
}): Promise<
|
|
802
|
+
| { status: "ready"; path: string; tokenId: string; created: boolean }
|
|
803
|
+
| { status: "skipped" }
|
|
804
|
+
| { status: "failed"; error: unknown }
|
|
805
|
+
> {
|
|
806
|
+
if (options.skip) {
|
|
807
|
+
return { status: "skipped" };
|
|
1078
808
|
}
|
|
1079
809
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
method: "GET",
|
|
1084
|
-
})) as CloudCapabilitiesResponse;
|
|
1085
|
-
const capabilities = payload.capabilities ?? [];
|
|
1086
|
-
const hasCloudflareAlpha = capabilities.some(
|
|
1087
|
-
(capability) => capability.name === "cloudflare_deploy_alpha" && capability.status === "active",
|
|
1088
|
-
);
|
|
810
|
+
try {
|
|
811
|
+
const path = join(options.root, defaultDeployChatAccessTokenPath);
|
|
812
|
+
const existing = await readDeployAccessTokenFile(path);
|
|
1089
813
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
});
|
|
1098
|
-
} catch (error) {
|
|
1099
|
-
checks.push({
|
|
1100
|
-
status: "fail",
|
|
1101
|
-
title: "Deploy entitlement",
|
|
1102
|
-
message: `Could not verify AgentKit Cloud capabilities. ${formatReadinessError(error)}`,
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
814
|
+
if (existing?.deploy_id === options.deployId && existing.id) {
|
|
815
|
+
const listed = (await cloudApiRequest(
|
|
816
|
+
options.apiUrl,
|
|
817
|
+
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
818
|
+
{ method: "GET" },
|
|
819
|
+
)) as DeployAccessTokenListResponse;
|
|
820
|
+
const stillExists = (listed.access_tokens ?? []).some((token) => token.id === existing.id);
|
|
1105
821
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
method: "GET",
|
|
1109
|
-
})) as CloudLimitsResponse;
|
|
1110
|
-
const account = payload.online_agents?.account;
|
|
1111
|
-
const global = payload.online_agents?.global;
|
|
1112
|
-
const accountLimit = normalizeOptionalNumber(account?.limit);
|
|
1113
|
-
const accountUsed = normalizeOptionalNumber(account?.used);
|
|
1114
|
-
const globalLimit = normalizeOptionalNumber(global?.limit);
|
|
1115
|
-
const globalUsed = normalizeOptionalNumber(global?.used);
|
|
1116
|
-
|
|
1117
|
-
if (accountLimit !== undefined && accountUsed !== undefined && accountUsed >= accountLimit) {
|
|
1118
|
-
checks.push({
|
|
1119
|
-
status: "fail",
|
|
1120
|
-
title: "Online agent capacity",
|
|
1121
|
-
message: `This account is already using ${accountUsed}/${accountLimit} online AgentKit deploys.`,
|
|
1122
|
-
action: "agentkit deploy pause",
|
|
1123
|
-
});
|
|
1124
|
-
} else if (globalLimit !== undefined && globalUsed !== undefined && globalUsed >= globalLimit) {
|
|
1125
|
-
checks.push({
|
|
1126
|
-
status: "fail",
|
|
1127
|
-
title: "Online agent capacity",
|
|
1128
|
-
message: `AgentKit Cloud is already using ${globalUsed}/${globalLimit} global online deploy slots.`,
|
|
1129
|
-
action: "agentkit deploy status",
|
|
1130
|
-
});
|
|
1131
|
-
} else if (accountLimit !== undefined && accountUsed !== undefined) {
|
|
1132
|
-
checks.push({
|
|
1133
|
-
status: "pass",
|
|
1134
|
-
title: "Online agent capacity",
|
|
1135
|
-
message: `Account has capacity: ${accountUsed}/${accountLimit} online deploys are in use.`,
|
|
1136
|
-
});
|
|
1137
|
-
} else if (globalLimit !== undefined && globalUsed !== undefined) {
|
|
1138
|
-
checks.push({
|
|
1139
|
-
status: "pass",
|
|
1140
|
-
title: "Online agent capacity",
|
|
1141
|
-
message: `Global capacity is available: ${globalUsed}/${globalLimit} online deploys are in use.`,
|
|
1142
|
-
});
|
|
1143
|
-
} else {
|
|
1144
|
-
checks.push({
|
|
1145
|
-
status: "info",
|
|
1146
|
-
title: "Online agent capacity",
|
|
1147
|
-
message: "No online deploy limit is configured for this account.",
|
|
1148
|
-
});
|
|
822
|
+
if (stillExists) {
|
|
823
|
+
return { status: "ready", path, tokenId: existing.id, created: false };
|
|
1149
824
|
}
|
|
1150
|
-
} catch (error) {
|
|
1151
|
-
checks.push({
|
|
1152
|
-
status: "warn",
|
|
1153
|
-
title: "Online agent capacity",
|
|
1154
|
-
message: `Could not verify online deploy capacity. ${formatReadinessError(error)}`,
|
|
1155
|
-
});
|
|
1156
825
|
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
if (context.agentKitManagedSecrets.length > 0) {
|
|
1160
|
-
checks.push({
|
|
1161
|
-
status: "info",
|
|
1162
|
-
title: "AgentKit-managed secrets",
|
|
1163
|
-
message: `${context.agentKitManagedSecrets.join(", ")} ${
|
|
1164
|
-
context.agentKitManagedSecrets.length === 1 ? "is" : "are"
|
|
1165
|
-
} provisioned by AgentKit Cloud during deploy.`,
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
1168
826
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
827
|
+
const created = (await cloudApiRequest(
|
|
828
|
+
options.apiUrl,
|
|
829
|
+
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
830
|
+
{
|
|
831
|
+
method: "POST",
|
|
832
|
+
body: JSON.stringify({ name: defaultDeployChatAccessTokenName }),
|
|
833
|
+
},
|
|
834
|
+
)) as DeployAccessTokenCreateResponse;
|
|
835
|
+
const token = created.access_token;
|
|
1176
836
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
status: "pass",
|
|
1180
|
-
title: "Hosted secrets",
|
|
1181
|
-
message: "No user-managed hosted secrets are required by this capsule.",
|
|
1182
|
-
});
|
|
1183
|
-
} else if (!cloudAuthUsable) {
|
|
1184
|
-
checks.push({
|
|
1185
|
-
status: options.requireLogin ? "fail" : "warn",
|
|
1186
|
-
title: "Hosted secrets",
|
|
1187
|
-
message: `Could not check hosted secrets without a valid AgentKit Cloud login. Required: ${context.userManagedSecrets.join(", ")}.`,
|
|
1188
|
-
action: "agentkit secret list",
|
|
1189
|
-
});
|
|
1190
|
-
} else {
|
|
1191
|
-
try {
|
|
1192
|
-
const payload = (await cloudApiRequest(
|
|
1193
|
-
options.apiUrl,
|
|
1194
|
-
`/v1/projects/${encodeURIComponent(context.projectId)}/secrets`,
|
|
1195
|
-
{ method: "GET" },
|
|
1196
|
-
)) as CloudSecretsResponse;
|
|
1197
|
-
const hostedSecretNames = new Set((payload.secrets ?? []).map((secret) => secret.name).filter(isString));
|
|
1198
|
-
const missing = context.userManagedSecrets.filter((name) => !hostedSecretNames.has(name));
|
|
1199
|
-
|
|
1200
|
-
if (missing.length === 0) {
|
|
1201
|
-
checks.push({
|
|
1202
|
-
status: "pass",
|
|
1203
|
-
title: "Hosted secrets",
|
|
1204
|
-
message: "All user-managed hosted secrets are set.",
|
|
1205
|
-
});
|
|
1206
|
-
} else {
|
|
1207
|
-
for (const secret of missing) {
|
|
1208
|
-
const localSource = context.localSecrets[secret];
|
|
1209
|
-
checks.push({
|
|
1210
|
-
status: "fail",
|
|
1211
|
-
title: `Hosted secret ${secret}`,
|
|
1212
|
-
message: localSource
|
|
1213
|
-
? `${secret} is present locally but has not been set in AgentKit Cloud.`
|
|
1214
|
-
: `${secret} is required but missing from AgentKit Cloud.`,
|
|
1215
|
-
action: secretSetCommand(secret, localSource),
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
checks.push({
|
|
1221
|
-
status: "fail",
|
|
1222
|
-
title: "Hosted secrets",
|
|
1223
|
-
message: `Could not list hosted secrets for project ${context.projectId}. ${formatReadinessError(error)}`,
|
|
1224
|
-
action: "agentkit secret list",
|
|
1225
|
-
});
|
|
837
|
+
if (!token?.id || token.deploy_id !== options.deployId || !token.token) {
|
|
838
|
+
throw new AgentKitError("access_token_invalid", "AgentKit Cloud returned an invalid deploy access token payload.");
|
|
1226
839
|
}
|
|
840
|
+
|
|
841
|
+
await writeSecretJson(path, created);
|
|
842
|
+
return { status: "ready", path, tokenId: token.id, created: true };
|
|
843
|
+
} catch (error) {
|
|
844
|
+
return { status: "failed", error };
|
|
1227
845
|
}
|
|
846
|
+
}
|
|
1228
847
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
848
|
+
async function readDeployAccessTokenFile(path: string): Promise<{ id?: string; deploy_id?: string } | null> {
|
|
849
|
+
try {
|
|
850
|
+
const parsed = JSON.parse(await readFile(path, "utf8")) as DeployAccessTokenCreateResponse;
|
|
851
|
+
return parsed.access_token ?? null;
|
|
852
|
+
} catch {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
1235
855
|
}
|
|
1236
856
|
|
|
1237
|
-
function
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
console.log(`Target: ${report.context.target}`);
|
|
1242
|
-
console.log(`API: ${report.apiUrl}`);
|
|
1243
|
-
console.log("");
|
|
857
|
+
async function writeSecretJson(path: string, value: unknown): Promise<void> {
|
|
858
|
+
await mkdir(dirname(path), { recursive: true });
|
|
859
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
860
|
+
}
|
|
1244
861
|
|
|
1245
|
-
|
|
1246
|
-
|
|
862
|
+
async function requireLocalDeployState(): Promise<LocalDeployState> {
|
|
863
|
+
const state = await readLocalDeployStateIfExists(process.cwd());
|
|
1247
864
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
865
|
+
if (!state) {
|
|
866
|
+
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy first.");
|
|
1251
867
|
}
|
|
868
|
+
|
|
869
|
+
return state;
|
|
1252
870
|
}
|
|
1253
871
|
|
|
1254
|
-
function
|
|
1255
|
-
if (
|
|
1256
|
-
|
|
872
|
+
function requireDeployUrl(state: Pick<LocalDeployState, "deploy_id" | "url">): string {
|
|
873
|
+
if (!state.url) {
|
|
874
|
+
throw new Error(`Deploy ${state.deploy_id} does not have a URL. Run agentkit deploy again.`);
|
|
1257
875
|
}
|
|
1258
876
|
|
|
1259
|
-
|
|
1260
|
-
|
|
877
|
+
return state.url;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function readDeployAccessTokenForSmoke(cwd: string, state: LocalDeployState): Promise<string | undefined> {
|
|
881
|
+
if (state.access?.mode === "public") {
|
|
882
|
+
return undefined;
|
|
1261
883
|
}
|
|
1262
884
|
|
|
1263
|
-
|
|
885
|
+
const root = await findAgentCapsuleRoot(cwd);
|
|
886
|
+
return readDeployAccessTokenForUi(join(root, defaultDeployChatAccessTokenPath));
|
|
1264
887
|
}
|
|
1265
888
|
|
|
1266
|
-
async function
|
|
1267
|
-
const
|
|
1268
|
-
const localEnv = await loadCapsuleEnv(context.root, {});
|
|
1269
|
-
let synced = 0;
|
|
1270
|
-
let skipped = 0;
|
|
889
|
+
async function runDeploySmoke(input: { deployUrl: string; message: string; token?: string }): Promise<void> {
|
|
890
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1271
891
|
|
|
1272
|
-
|
|
1273
|
-
|
|
892
|
+
if (input.token) {
|
|
893
|
+
headers.set("Authorization", `Bearer ${input.token}`);
|
|
894
|
+
}
|
|
1274
895
|
|
|
1275
|
-
|
|
1276
|
-
skipped += 1;
|
|
1277
|
-
console.log(`${secret}\tskipped_missing_local`);
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
896
|
+
let response: Response;
|
|
1280
897
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
898
|
+
try {
|
|
899
|
+
response = await fetch(new URL("/v1/chat", input.deployUrl).href, {
|
|
900
|
+
method: "POST",
|
|
901
|
+
headers,
|
|
902
|
+
body: JSON.stringify({ message: input.message }),
|
|
1284
903
|
});
|
|
1285
|
-
|
|
1286
|
-
|
|
904
|
+
} catch (error) {
|
|
905
|
+
throw new AgentKitError(
|
|
906
|
+
"deploy_smoke_failed",
|
|
907
|
+
`Deploy smoke request failed before a response: ${error instanceof Error ? error.message : String(error)}`,
|
|
908
|
+
);
|
|
1287
909
|
}
|
|
1288
910
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
911
|
+
const payload = await response.json().catch(() => null);
|
|
912
|
+
|
|
913
|
+
if (!response.ok) {
|
|
914
|
+
throw new AgentKitError("deploy_smoke_failed", readCloudError(payload, response.status));
|
|
1292
915
|
}
|
|
1293
916
|
|
|
917
|
+
const content =
|
|
918
|
+
payload && typeof payload === "object" && "message" in payload && isRecord((payload as { message?: unknown }).message)
|
|
919
|
+
? (payload as { message: { content?: unknown } }).message.content
|
|
920
|
+
: undefined;
|
|
921
|
+
|
|
1294
922
|
console.log("");
|
|
1295
|
-
console.log(
|
|
923
|
+
console.log("Deploy smoke verified");
|
|
924
|
+
console.log(`URL: ${input.deployUrl}`);
|
|
925
|
+
console.log(`Message: ${input.message}`);
|
|
926
|
+
if (typeof content === "string") {
|
|
927
|
+
console.log(`Response: ${content}`);
|
|
928
|
+
}
|
|
1296
929
|
}
|
|
1297
930
|
|
|
1298
|
-
function
|
|
1299
|
-
|
|
931
|
+
function printJson(value: unknown): void {
|
|
932
|
+
console.log(JSON.stringify(value, null, 2));
|
|
1300
933
|
}
|
|
1301
934
|
|
|
1302
|
-
function
|
|
1303
|
-
|
|
1304
|
-
return `${error.code}: ${error.message}`;
|
|
1305
|
-
}
|
|
935
|
+
async function printJsonOrWrite(value: unknown, outFlag: string | boolean | undefined, usage: string): Promise<void> {
|
|
936
|
+
const outPath = parseOptionalStringFlag(outFlag, "out", usage);
|
|
1306
937
|
|
|
1307
|
-
|
|
1308
|
-
|
|
938
|
+
if (!outPath) {
|
|
939
|
+
printJson(value);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
1309
942
|
|
|
1310
|
-
|
|
1311
|
-
|
|
943
|
+
const absolutePath = resolve(process.cwd(), outPath);
|
|
944
|
+
await writeSecretJson(absolutePath, value);
|
|
945
|
+
console.log(`Deploy access token written to ${absolutePath}`);
|
|
1312
946
|
}
|
|
1313
947
|
|
|
1314
|
-
function
|
|
1315
|
-
|
|
948
|
+
function relativePath(root: string, path: string): string {
|
|
949
|
+
const output = relative(root, path);
|
|
950
|
+
return output.startsWith("..") ? path : output || ".";
|
|
1316
951
|
}
|
|
1317
952
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
953
|
+
function printBuildResult(result: Awaited<ReturnType<typeof buildAgentCapsule>>): void {
|
|
954
|
+
console.log("AgentKit build complete");
|
|
955
|
+
console.log("");
|
|
956
|
+
console.log(`Target: ${result.target}`);
|
|
957
|
+
console.log(`Build dir: ${result.buildDir}`);
|
|
958
|
+
console.log(`Manifest: ${result.manifestPath}`);
|
|
1323
959
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
);
|
|
1329
|
-
printChannelsTable(channels.channels);
|
|
1330
|
-
return;
|
|
960
|
+
if (result.target === "cloudflare") {
|
|
961
|
+
console.log(`Worker: ${result.workerPath}`);
|
|
962
|
+
console.log(`Wrangler: ${result.wranglerPath}`);
|
|
963
|
+
if (result.tursoSchemaPath) {
|
|
964
|
+
console.log(`Turso: ${result.tursoSchemaPath}`);
|
|
1331
965
|
}
|
|
966
|
+
console.log(`Bundle: ${result.bundleSha256}`);
|
|
967
|
+
}
|
|
1332
968
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
name: channel.name,
|
|
1338
|
-
type: channel.type,
|
|
1339
|
-
provider: channel.provider,
|
|
1340
|
-
status: "local_config",
|
|
1341
|
-
webhook_url: "",
|
|
1342
|
-
required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
|
|
1343
|
-
})),
|
|
1344
|
-
);
|
|
1345
|
-
return;
|
|
969
|
+
if (result.target === "container") {
|
|
970
|
+
console.log(`Server: ${result.serverPath}`);
|
|
971
|
+
console.log(`Docker: ${result.dockerfilePath}`);
|
|
972
|
+
console.log(`Migrations:${result.migrationsDir}`);
|
|
1346
973
|
}
|
|
1347
974
|
|
|
1348
|
-
if (
|
|
1349
|
-
|
|
1350
|
-
|
|
975
|
+
if (result.warnings.length > 0) {
|
|
976
|
+
console.log("");
|
|
977
|
+
console.log("Warnings:");
|
|
1351
978
|
|
|
1352
|
-
|
|
1353
|
-
|
|
979
|
+
for (const warning of result.warnings) {
|
|
980
|
+
console.log(` - ${warning}`);
|
|
1354
981
|
}
|
|
1355
|
-
|
|
1356
|
-
const deployState = await readDeployStateRequired(process.cwd());
|
|
1357
|
-
const provider = channelProviderForCli(type, args.flags.provider);
|
|
1358
|
-
const payload = {
|
|
1359
|
-
name,
|
|
1360
|
-
type,
|
|
1361
|
-
provider,
|
|
1362
|
-
secrets: defaultChannelSecretsForCli(type, provider),
|
|
1363
|
-
};
|
|
1364
|
-
const response = await cloudPost<{ channel: CloudChannel }>(
|
|
1365
|
-
parseCloudApiUrl(args.flags.api),
|
|
1366
|
-
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
1367
|
-
payload,
|
|
1368
|
-
);
|
|
1369
|
-
printChannelStatus(response.channel);
|
|
1370
|
-
return;
|
|
1371
982
|
}
|
|
983
|
+
}
|
|
1372
984
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
985
|
+
function printVpsDeployPlan(plan: Awaited<ReturnType<typeof prepareVpsDeploy>>): void {
|
|
986
|
+
console.log("");
|
|
987
|
+
console.log("VPS deploy plan ready");
|
|
988
|
+
console.log(`Host: ${plan.host}`);
|
|
989
|
+
console.log(`Compose: ${plan.composePath}`);
|
|
990
|
+
console.log(`Env: ${plan.envExamplePath}`);
|
|
991
|
+
console.log(`Secrets: ${plan.secretsPath}`);
|
|
992
|
+
console.log(`Handoff: ${plan.handoffPath}`);
|
|
993
|
+
console.log(`State: ${plan.statePath}`);
|
|
994
|
+
console.log("");
|
|
995
|
+
console.log("Commands:");
|
|
1377
996
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
if (channel.type === "telegram") {
|
|
1381
|
-
if (args.flags.apply) {
|
|
1382
|
-
await applyTelegramWebhook(channel);
|
|
1383
|
-
console.log("Telegram webhook updated.");
|
|
1384
|
-
} else {
|
|
1385
|
-
console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
|
|
1386
|
-
console.log("Use --apply to mutate Telegram webhook settings.");
|
|
1387
|
-
}
|
|
1388
|
-
} else if (channel.type === "whatsapp") {
|
|
1389
|
-
console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
|
|
1390
|
-
} else {
|
|
1391
|
-
console.log(`Website endpoint: ${channel.webhook_url}`);
|
|
1392
|
-
console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
|
|
1393
|
-
}
|
|
1394
|
-
return;
|
|
997
|
+
for (const command of plan.commands) {
|
|
998
|
+
console.log(` ${command}`);
|
|
1395
999
|
}
|
|
1000
|
+
}
|
|
1396
1001
|
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
throw new Error("Usage: agentkit channels status <name>");
|
|
1400
|
-
}
|
|
1002
|
+
async function deployCloudflare(wranglerPath: string, buildDir: string): Promise<void> {
|
|
1003
|
+
const exitCode = await runInheritedCommand("npx", ["wrangler", "deploy", "--config", wranglerPath], buildDir);
|
|
1401
1004
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
await printChannelDeliverySummary(channel, args.flags.api);
|
|
1405
|
-
return;
|
|
1005
|
+
if (exitCode !== 0) {
|
|
1006
|
+
throw new Error(`Cloudflare deploy failed with exit code ${exitCode}.`);
|
|
1406
1007
|
}
|
|
1008
|
+
}
|
|
1407
1009
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
1414
|
-
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
1415
|
-
const body = await channelTestBody(channel, args.flags.message, args.flags.fixture);
|
|
1416
|
-
const path = new URL(channel.webhook_url).pathname;
|
|
1417
|
-
const headers = channelTestHeaders(channel);
|
|
1418
|
-
const response = await cloudFetch(apiUrl, path, {
|
|
1419
|
-
method: channel.type === "whatsapp" && channel.provider === "meta" ? "POST" : "POST",
|
|
1420
|
-
headers,
|
|
1421
|
-
body,
|
|
1422
|
-
});
|
|
1423
|
-
const payload = await response.json();
|
|
1010
|
+
async function installProjectDependencies(projectPath: string): Promise<void> {
|
|
1011
|
+
const exitCode = await runInheritedCommand("npm", ["install"], projectPath);
|
|
1424
1012
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
process.exitCode = 1;
|
|
1428
|
-
}
|
|
1429
|
-
return;
|
|
1013
|
+
if (exitCode !== 0) {
|
|
1014
|
+
throw new Error(`Dependency install failed with exit code ${exitCode}. Run \`npm install\` in ${projectPath}.`);
|
|
1430
1015
|
}
|
|
1431
|
-
|
|
1432
|
-
if (subcommand === "deliveries") {
|
|
1433
|
-
if (first === "list") {
|
|
1434
|
-
if (!second) {
|
|
1435
|
-
throw new Error("Usage: agentkit channels deliveries list <name>");
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
const channel = await resolveCloudChannelByName(second, args.flags.api);
|
|
1439
|
-
const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
|
|
1440
|
-
parseCloudApiUrl(args.flags.api),
|
|
1441
|
-
`/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
|
|
1442
|
-
);
|
|
1443
|
-
|
|
1444
|
-
if (response.deliveries.length === 0) {
|
|
1445
|
-
console.log("No channel deliveries found.");
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
console.log("ID\tCHANNEL\tDIRECTION\tSTATUS\tPROVIDER_EVENT\tRECEIVED");
|
|
1450
|
-
for (const delivery of response.deliveries) {
|
|
1451
|
-
console.log(
|
|
1452
|
-
[
|
|
1453
|
-
delivery.id,
|
|
1454
|
-
delivery.channel_id,
|
|
1455
|
-
delivery.direction,
|
|
1456
|
-
delivery.status,
|
|
1457
|
-
delivery.provider.event_id ?? "",
|
|
1458
|
-
delivery.received_at,
|
|
1459
|
-
].join("\t"),
|
|
1460
|
-
);
|
|
1461
|
-
}
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
if (first === "show") {
|
|
1466
|
-
if (!second) {
|
|
1467
|
-
throw new Error("Usage: agentkit channels deliveries show <delivery-id>");
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
const response = await cloudGet<{ delivery: CloudDelivery }>(
|
|
1471
|
-
parseCloudApiUrl(args.flags.api),
|
|
1472
|
-
`/v1/channel-deliveries/${encodeURIComponent(second)}`,
|
|
1473
|
-
);
|
|
1474
|
-
console.log(JSON.stringify(response.delivery, null, 2));
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
throw new Error(
|
|
1480
|
-
"Usage: agentkit channels list | add | setup | status | test | deliveries list | deliveries show",
|
|
1481
|
-
);
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
type DeployState = LocalDeployState;
|
|
1485
|
-
|
|
1486
|
-
type CloudChannel = {
|
|
1487
|
-
id: string;
|
|
1488
|
-
name: string;
|
|
1489
|
-
type: "website" | "telegram" | "whatsapp";
|
|
1490
|
-
provider: "agentkit" | "telegram" | "zapster" | "meta";
|
|
1491
|
-
status: string;
|
|
1492
|
-
webhook_url: string;
|
|
1493
|
-
required_secrets: Array<{ name: string; status: string }>;
|
|
1494
|
-
};
|
|
1495
|
-
|
|
1496
|
-
type CloudDelivery = {
|
|
1497
|
-
id: string;
|
|
1498
|
-
channel_id: string;
|
|
1499
|
-
direction: string;
|
|
1500
|
-
status: string;
|
|
1501
|
-
provider: {
|
|
1502
|
-
event_id?: string;
|
|
1503
|
-
};
|
|
1504
|
-
received_at: string;
|
|
1505
|
-
};
|
|
1506
|
-
|
|
1507
|
-
async function applyTelegramWebhook(channel: CloudChannel): Promise<void> {
|
|
1508
|
-
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
1509
|
-
const secretToken = process.env.TELEGRAM_WEBHOOK_SECRET;
|
|
1510
|
-
|
|
1511
|
-
if (!botToken || !secretToken) {
|
|
1512
|
-
throw new Error("TELEGRAM_BOT_TOKEN and TELEGRAM_WEBHOOK_SECRET must be set to apply Telegram setup.");
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
let response: Response;
|
|
1516
|
-
|
|
1517
|
-
try {
|
|
1518
|
-
response = await fetch(`https://api.telegram.org/bot${botToken}/setWebhook`, {
|
|
1519
|
-
method: "POST",
|
|
1520
|
-
headers: { "Content-Type": "application/json" },
|
|
1521
|
-
body: JSON.stringify({
|
|
1522
|
-
url: channel.webhook_url,
|
|
1523
|
-
secret_token: secretToken,
|
|
1524
|
-
allowed_updates: ["message"],
|
|
1525
|
-
}),
|
|
1526
|
-
});
|
|
1527
|
-
} catch {
|
|
1528
|
-
throw new Error("Telegram setWebhook request failed before Telegram returned a response.");
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
const payload = await response.json().catch(() => ({}));
|
|
1532
|
-
|
|
1533
|
-
if (!response.ok || !isTelegramOk(payload)) {
|
|
1534
|
-
throw new Error(readTelegramSetupError(payload, response.status));
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
function isTelegramOk(payload: unknown): payload is { ok: true } {
|
|
1539
|
-
return Boolean(payload && typeof payload === "object" && "ok" in payload && payload.ok === true);
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
function readTelegramSetupError(payload: unknown, status: number): string {
|
|
1543
|
-
if (payload && typeof payload === "object" && "description" in payload && typeof payload.description === "string") {
|
|
1544
|
-
return `Telegram setWebhook failed: ${payload.description}`;
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
return `Telegram setWebhook failed with HTTP ${status}.`;
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
|
|
1551
|
-
return readLocalDeployStateIfExists(cwd);
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
async function readDeployStateRequired(cwd: string): Promise<DeployState> {
|
|
1555
|
-
const state = await readDeployStateIfExists(cwd);
|
|
1556
|
-
|
|
1557
|
-
if (!state?.deploy_id) {
|
|
1558
|
-
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy before creating hosted channels.");
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
return state;
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
function printChannelsTable(channels: CloudChannel[]): void {
|
|
1565
|
-
if (channels.length === 0) {
|
|
1566
|
-
console.log("No channels found.");
|
|
1567
|
-
return;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
console.log("NAME\tTYPE\tPROVIDER\tSTATUS\tLAST_EVENT");
|
|
1571
|
-
for (const channel of channels) {
|
|
1572
|
-
console.log([channel.name, channel.type, channel.provider, channel.status, ""].join("\t"));
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
function printChannelStatus(channel: CloudChannel): void {
|
|
1577
|
-
console.log(`Channel: ${channel.name}`);
|
|
1578
|
-
if (channel.id) {
|
|
1579
|
-
console.log(`ID: ${channel.id}`);
|
|
1580
|
-
}
|
|
1581
|
-
console.log(`Type: ${channel.type}`);
|
|
1582
|
-
console.log(`Provider: ${channel.provider}`);
|
|
1583
|
-
console.log(`Status: ${channel.status}`);
|
|
1584
|
-
if (channel.webhook_url) {
|
|
1585
|
-
console.log(`Webhook URL: ${channel.webhook_url}`);
|
|
1586
|
-
}
|
|
1587
|
-
console.log("Required secrets:");
|
|
1588
|
-
for (const secret of channel.required_secrets) {
|
|
1589
|
-
console.log(` ${secret.name}: ${secret.status}`);
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
async function printChannelDeliverySummary(
|
|
1594
|
-
channel: CloudChannel,
|
|
1595
|
-
apiFlag: string | boolean | undefined,
|
|
1596
|
-
): Promise<void> {
|
|
1597
|
-
const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
|
|
1598
|
-
parseCloudApiUrl(apiFlag),
|
|
1599
|
-
`/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
|
|
1600
|
-
);
|
|
1601
|
-
const inbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "inbound");
|
|
1602
|
-
const outbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "outbound");
|
|
1603
|
-
|
|
1604
|
-
console.log("Recent delivery:");
|
|
1605
|
-
console.log(` Last inbound: ${inbound ? `${inbound.status} ${inbound.received_at}` : "none"}`);
|
|
1606
|
-
console.log(` Last outbound: ${outbound ? `${outbound.status} ${outbound.received_at}` : "none"}`);
|
|
1607
|
-
console.log("Provider health: not_checked");
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
|
|
1611
|
-
const deployState = await readDeployStateRequired(process.cwd());
|
|
1612
|
-
const response = await cloudGet<{ channels: CloudChannel[] }>(
|
|
1613
|
-
parseCloudApiUrl(apiFlag),
|
|
1614
|
-
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
1615
|
-
);
|
|
1616
|
-
const channel = response.channels.find((candidate) => candidate.name === name);
|
|
1617
|
-
|
|
1618
|
-
if (!channel) {
|
|
1619
|
-
throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
return channel;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
|
|
1626
|
-
if (type === "website") {
|
|
1627
|
-
return "agentkit";
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
if (type === "telegram") {
|
|
1631
|
-
return "telegram";
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
const provider = providerFlag ?? "zapster";
|
|
1635
|
-
|
|
1636
|
-
if (provider !== "zapster" && provider !== "meta") {
|
|
1637
|
-
throw new Error('WhatsApp provider must be "zapster" or "meta".');
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
return provider;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
function defaultChannelSecretsForCli(type: string, provider: string): string[] {
|
|
1644
|
-
if (type === "website") {
|
|
1645
|
-
return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
if (type === "telegram") {
|
|
1649
|
-
return ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"];
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
if (type === "whatsapp" && provider === "zapster") {
|
|
1653
|
-
return ["ZAPSTER_API_KEY", "ZAPSTER_WEBHOOK_SECRET"];
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
if (type === "whatsapp" && provider === "meta") {
|
|
1657
|
-
return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
return [];
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
async function channelTestBody(
|
|
1664
|
-
channel: CloudChannel,
|
|
1665
|
-
messageFlag: string | boolean | undefined,
|
|
1666
|
-
fixtureFlag: string | boolean | undefined,
|
|
1667
|
-
): Promise<string> {
|
|
1668
|
-
if (typeof fixtureFlag === "string") {
|
|
1669
|
-
return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
const message = typeof messageFlag === "string" ? messageFlag : "hello";
|
|
1673
|
-
|
|
1674
|
-
if (channel.type === "telegram") {
|
|
1675
|
-
return JSON.stringify({
|
|
1676
|
-
update_id: Date.now(),
|
|
1677
|
-
message: {
|
|
1678
|
-
message_id: 1,
|
|
1679
|
-
chat: { id: 456, type: "private" },
|
|
1680
|
-
from: { id: 123, first_name: "AgentKit" },
|
|
1681
|
-
text: message,
|
|
1682
|
-
},
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
if (channel.type === "whatsapp") {
|
|
1687
|
-
return JSON.stringify({
|
|
1688
|
-
event_id: `evt_${Date.now()}`,
|
|
1689
|
-
instance_id: "local",
|
|
1690
|
-
message: {
|
|
1691
|
-
id: `msg_${Date.now()}`,
|
|
1692
|
-
from: "5511999999999",
|
|
1693
|
-
type: "text",
|
|
1694
|
-
text: { body: message },
|
|
1695
|
-
},
|
|
1696
|
-
});
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
return JSON.stringify({
|
|
1700
|
-
event_id: `web_${Date.now()}`,
|
|
1701
|
-
visitor_id: "visitor_test",
|
|
1702
|
-
message: {
|
|
1703
|
-
id: `msg_${Date.now()}`,
|
|
1704
|
-
text: message,
|
|
1705
|
-
},
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
function channelTestHeaders(channel: CloudChannel): Headers {
|
|
1710
|
-
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1711
|
-
|
|
1712
|
-
if (channel.type === "website") {
|
|
1713
|
-
const token = process.env.AGENTKIT_WEBSITE_CHANNEL_TOKEN;
|
|
1714
|
-
|
|
1715
|
-
if (token) {
|
|
1716
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
if (channel.type === "telegram") {
|
|
1721
|
-
headers.set("X-Telegram-Bot-Api-Secret-Token", process.env.TELEGRAM_WEBHOOK_SECRET ?? "");
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
1725
|
-
headers.set("X-Zapster-Webhook-Secret", process.env.ZAPSTER_WEBHOOK_SECRET ?? "");
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
return headers;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
async function cloudGet<T>(apiUrl: string, path: string): Promise<T> {
|
|
1732
|
-
const response = await cloudFetch(apiUrl, path);
|
|
1733
|
-
const payload = await response.json();
|
|
1734
|
-
|
|
1735
|
-
if (!response.ok) {
|
|
1736
|
-
throw new Error(readCloudError(payload, response.status));
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
return payload as T;
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
async function cloudPost<T>(apiUrl: string, path: string, body: unknown): Promise<T> {
|
|
1743
|
-
const response = await cloudFetch(apiUrl, path, {
|
|
1744
|
-
method: "POST",
|
|
1745
|
-
headers: { "Content-Type": "application/json" },
|
|
1746
|
-
body: JSON.stringify(body),
|
|
1747
|
-
});
|
|
1748
|
-
const payload = await response.json();
|
|
1749
|
-
|
|
1750
|
-
if (!response.ok) {
|
|
1751
|
-
throw new Error(readCloudError(payload, response.status));
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
return payload as T;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promise<Response> {
|
|
1758
|
-
const headers = new Headers(init.headers);
|
|
1759
|
-
const apiToken = process.env.AGENTKIT_CLOUD_API_TOKEN;
|
|
1760
|
-
|
|
1761
|
-
if (apiToken && !headers.has("Authorization")) {
|
|
1762
|
-
headers.set("Authorization", `Bearer ${apiToken}`);
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
return fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
1766
|
-
...init,
|
|
1767
|
-
headers,
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
type CloudAuthConfig = {
|
|
1772
|
-
apiUrl: string;
|
|
1773
|
-
token: string;
|
|
1774
|
-
source?: "environment" | "file";
|
|
1775
|
-
};
|
|
1776
|
-
|
|
1777
|
-
function cloudAuthPath(): string {
|
|
1778
|
-
return join(homedir(), ".agentkit", "cloud.json");
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
async function readCloudAuth(): Promise<CloudAuthConfig | null> {
|
|
1782
|
-
if (process.env.AGENTKIT_CLOUD_API_TOKEN) {
|
|
1783
|
-
return {
|
|
1784
|
-
apiUrl: process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl,
|
|
1785
|
-
token: process.env.AGENTKIT_CLOUD_API_TOKEN,
|
|
1786
|
-
source: "environment",
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
try {
|
|
1791
|
-
const parsed = JSON.parse(await readFile(cloudAuthPath(), "utf8"));
|
|
1792
|
-
|
|
1793
|
-
if (parsed && typeof parsed === "object" && typeof parsed.token === "string") {
|
|
1794
|
-
return {
|
|
1795
|
-
apiUrl: typeof parsed.apiUrl === "string" ? parsed.apiUrl : defaultAgentKitCloudApiUrl,
|
|
1796
|
-
token: parsed.token,
|
|
1797
|
-
source: "file",
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
} catch {
|
|
1801
|
-
return null;
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
return null;
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
async function writeCloudAuth(config: CloudAuthConfig): Promise<void> {
|
|
1808
|
-
await mkdir(join(homedir(), ".agentkit"), { recursive: true });
|
|
1809
|
-
await writeFile(cloudAuthPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
async function ensureDeployChatAccessToken(options: {
|
|
1813
|
-
apiUrl: string;
|
|
1814
|
-
root: string;
|
|
1815
|
-
deployId: string;
|
|
1816
|
-
skip: boolean;
|
|
1817
|
-
}): Promise<
|
|
1818
|
-
| { status: "ready"; path: string; tokenId: string; created: boolean }
|
|
1819
|
-
| { status: "skipped" }
|
|
1820
|
-
| { status: "failed"; error: unknown }
|
|
1821
|
-
> {
|
|
1822
|
-
if (options.skip) {
|
|
1823
|
-
return { status: "skipped" };
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
try {
|
|
1827
|
-
const path = join(options.root, defaultDeployChatAccessTokenPath);
|
|
1828
|
-
const existing = await readDeployAccessTokenFile(path);
|
|
1829
|
-
|
|
1830
|
-
if (existing?.deploy_id === options.deployId && existing.id) {
|
|
1831
|
-
const listed = (await cloudApiRequest(
|
|
1832
|
-
options.apiUrl,
|
|
1833
|
-
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
1834
|
-
{ method: "GET" },
|
|
1835
|
-
)) as DeployAccessTokenListResponse;
|
|
1836
|
-
const stillExists = (listed.access_tokens ?? []).some((token) => token.id === existing.id);
|
|
1837
|
-
|
|
1838
|
-
if (stillExists) {
|
|
1839
|
-
return { status: "ready", path, tokenId: existing.id, created: false };
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
const created = (await cloudApiRequest(
|
|
1844
|
-
options.apiUrl,
|
|
1845
|
-
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
1846
|
-
{
|
|
1847
|
-
method: "POST",
|
|
1848
|
-
body: JSON.stringify({ name: defaultDeployChatAccessTokenName }),
|
|
1849
|
-
},
|
|
1850
|
-
)) as DeployAccessTokenCreateResponse;
|
|
1851
|
-
const token = created.access_token;
|
|
1852
|
-
|
|
1853
|
-
if (!token?.id || token.deploy_id !== options.deployId || !token.token) {
|
|
1854
|
-
throw new AgentKitError("access_token_invalid", "AgentKit Cloud returned an invalid deploy access token payload.");
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
await writeSecretJson(path, created);
|
|
1858
|
-
return { status: "ready", path, tokenId: token.id, created: true };
|
|
1859
|
-
} catch (error) {
|
|
1860
|
-
return { status: "failed", error };
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
async function readDeployAccessTokenFile(path: string): Promise<{ id?: string; deploy_id?: string } | null> {
|
|
1865
|
-
try {
|
|
1866
|
-
const parsed = JSON.parse(await readFile(path, "utf8")) as DeployAccessTokenCreateResponse;
|
|
1867
|
-
return parsed.access_token ?? null;
|
|
1868
|
-
} catch {
|
|
1869
|
-
return null;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
async function writeSecretJson(path: string, value: unknown): Promise<void> {
|
|
1874
|
-
await mkdir(dirname(path), { recursive: true });
|
|
1875
|
-
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
async function verifyCloudLogin(
|
|
1879
|
-
apiUrl: string,
|
|
1880
|
-
token: string,
|
|
1881
|
-
): Promise<{ me: CloudMeResponse; capabilities: CloudCapabilitiesResponse }> {
|
|
1882
|
-
const me = await cloudApiRequestWithToken<CloudMeResponse>(apiUrl, "/v1/me", token, { method: "GET" });
|
|
1883
|
-
const capabilities = await cloudApiRequestWithToken<CloudCapabilitiesResponse>(
|
|
1884
|
-
apiUrl,
|
|
1885
|
-
"/v1/me/capabilities",
|
|
1886
|
-
token,
|
|
1887
|
-
{ method: "GET" },
|
|
1888
|
-
);
|
|
1889
|
-
|
|
1890
|
-
return { me, capabilities };
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
async function clearCloudAuth(): Promise<void> {
|
|
1894
|
-
await rm(cloudAuthPath(), { force: true });
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
async function resolveCloudApiUrl(flag: string | boolean | undefined): Promise<string> {
|
|
1898
|
-
if (flag !== undefined) {
|
|
1899
|
-
return parseCloudApiUrl(flag);
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
const auth = await readCloudAuth();
|
|
1903
|
-
return auth?.apiUrl ?? parseCloudApiUrl(undefined);
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
async function requireLocalDeployState(): Promise<LocalDeployState> {
|
|
1907
|
-
const state = await readLocalDeployStateIfExists(process.cwd());
|
|
1908
|
-
|
|
1909
|
-
if (!state) {
|
|
1910
|
-
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy first.");
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
return state;
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
function requireDeployUrl(state: Pick<LocalDeployState, "deploy_id" | "url">): string {
|
|
1917
|
-
if (!state.url) {
|
|
1918
|
-
throw new Error(`Deploy ${state.deploy_id} does not have a URL. Run agentkit deploy again.`);
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
return state.url;
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
async function readDeployAccessTokenForSmoke(cwd: string, state: LocalDeployState): Promise<string | undefined> {
|
|
1925
|
-
if (state.access?.mode === "public") {
|
|
1926
|
-
return undefined;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
const root = await findAgentCapsuleRoot(cwd);
|
|
1930
|
-
return readDeployAccessTokenForUi(join(root, defaultDeployChatAccessTokenPath));
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
async function runDeploySmoke(input: { deployUrl: string; message: string; token?: string }): Promise<void> {
|
|
1934
|
-
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1935
|
-
|
|
1936
|
-
if (input.token) {
|
|
1937
|
-
headers.set("Authorization", `Bearer ${input.token}`);
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
let response: Response;
|
|
1941
|
-
|
|
1942
|
-
try {
|
|
1943
|
-
response = await fetch(new URL("/v1/chat", input.deployUrl).href, {
|
|
1944
|
-
method: "POST",
|
|
1945
|
-
headers,
|
|
1946
|
-
body: JSON.stringify({ message: input.message }),
|
|
1947
|
-
});
|
|
1948
|
-
} catch (error) {
|
|
1949
|
-
throw new AgentKitError(
|
|
1950
|
-
"deploy_smoke_failed",
|
|
1951
|
-
`Deploy smoke request failed before a response: ${error instanceof Error ? error.message : String(error)}`,
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
const payload = await response.json().catch(() => null);
|
|
1956
|
-
|
|
1957
|
-
if (!response.ok) {
|
|
1958
|
-
throw new AgentKitError("deploy_smoke_failed", readCloudError(payload, response.status));
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
const content =
|
|
1962
|
-
payload && typeof payload === "object" && "message" in payload && isRecord((payload as { message?: unknown }).message)
|
|
1963
|
-
? (payload as { message: { content?: unknown } }).message.content
|
|
1964
|
-
: undefined;
|
|
1965
|
-
|
|
1966
|
-
console.log("");
|
|
1967
|
-
console.log("Deploy smoke verified");
|
|
1968
|
-
console.log(`URL: ${input.deployUrl}`);
|
|
1969
|
-
console.log(`Message: ${input.message}`);
|
|
1970
|
-
if (typeof content === "string") {
|
|
1971
|
-
console.log(`Response: ${content}`);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
async function resolveProjectIdForSecretCommand(cwd: string): Promise<string> {
|
|
1976
|
-
const state = await readLocalDeployStateIfExists(cwd);
|
|
1977
|
-
|
|
1978
|
-
if (state?.project_id) {
|
|
1979
|
-
return state.project_id;
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
const root = await findAgentCapsuleRoot(cwd);
|
|
1983
|
-
const capsule = await loadAgentCapsule(root);
|
|
1984
|
-
return stableCloudProjectId(capsule.config.name);
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit): Promise<unknown> {
|
|
1988
|
-
const auth = await readCloudAuth();
|
|
1989
|
-
const headers = new Headers(init.headers);
|
|
1990
|
-
|
|
1991
|
-
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
1992
|
-
headers.set("Content-Type", "application/json");
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
if (auth?.token && !headers.has("Authorization")) {
|
|
1996
|
-
headers.set("Authorization", `Bearer ${auth.token}`);
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
2000
|
-
...init,
|
|
2001
|
-
headers,
|
|
2002
|
-
});
|
|
2003
|
-
const payload = await response.json().catch(() => null);
|
|
2004
|
-
|
|
2005
|
-
if (!response.ok) {
|
|
2006
|
-
const code =
|
|
2007
|
-
payload && typeof payload === "object" && "error" in payload
|
|
2008
|
-
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
2009
|
-
: "cloud_request_failed";
|
|
2010
|
-
let message = readCloudError(payload, response.status);
|
|
2011
|
-
|
|
2012
|
-
if (code === "auth_token_invalid") {
|
|
2013
|
-
message = `${message} Token source: ${auth ? formatCloudAuthSource(auth) : "none"}. Run: agentkit login --token <token>`;
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
throw new AgentKitError(code, message);
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
return payload;
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
async function cloudApiRequestWithToken<T>(
|
|
2023
|
-
apiUrl: string,
|
|
2024
|
-
path: string,
|
|
2025
|
-
token: string,
|
|
2026
|
-
init: RequestInit,
|
|
2027
|
-
): Promise<T> {
|
|
2028
|
-
const headers = new Headers(init.headers);
|
|
2029
|
-
|
|
2030
|
-
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
2031
|
-
headers.set("Content-Type", "application/json");
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
2035
|
-
|
|
2036
|
-
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
2037
|
-
...init,
|
|
2038
|
-
headers,
|
|
2039
|
-
});
|
|
2040
|
-
const payload = await response.json().catch(() => null);
|
|
2041
|
-
|
|
2042
|
-
if (!response.ok) {
|
|
2043
|
-
const code =
|
|
2044
|
-
payload && typeof payload === "object" && "error" in payload
|
|
2045
|
-
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
2046
|
-
: "cloud_request_failed";
|
|
2047
|
-
throw new AgentKitError(code, readCloudError(payload, response.status));
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
return payload as T;
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
function printJson(value: unknown): void {
|
|
2054
|
-
console.log(JSON.stringify(value, null, 2));
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
async function printJsonOrWrite(value: unknown, outFlag: string | boolean | undefined, usage: string): Promise<void> {
|
|
2058
|
-
const outPath = parseOptionalStringFlag(outFlag, "out", usage);
|
|
2059
|
-
|
|
2060
|
-
if (!outPath) {
|
|
2061
|
-
printJson(value);
|
|
2062
|
-
return;
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
const absolutePath = resolve(process.cwd(), outPath);
|
|
2066
|
-
await writeSecretJson(absolutePath, value);
|
|
2067
|
-
console.log(`Deploy access token written to ${absolutePath}`);
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
function readCloudError(payload: unknown, status: number): string {
|
|
2071
|
-
if (payload && typeof payload === "object" && "error" in payload) {
|
|
2072
|
-
const error = (payload as { error?: { message?: unknown } }).error;
|
|
2073
|
-
if (typeof error?.message === "string") {
|
|
2074
|
-
return error.message;
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
return `AgentKit Cloud request failed with HTTP ${status}.`;
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
function formatCloudAuthSource(auth: CloudAuthConfig): string {
|
|
2082
|
-
if (auth.source === "environment") {
|
|
2083
|
-
return "AGENTKIT_CLOUD_API_TOKEN";
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
if (auth.source === "file") {
|
|
2087
|
-
return cloudAuthPath();
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
return "AgentKit Cloud auth";
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
function relativePath(root: string, path: string): string {
|
|
2094
|
-
const output = relative(root, path);
|
|
2095
|
-
return output.startsWith("..") ? path : output || ".";
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
function parsePort(value: string | boolean | undefined, usage = "agentkit dev --port <number>"): number | undefined {
|
|
2099
|
-
if (value === undefined) {
|
|
2100
|
-
return undefined;
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
if (typeof value !== "string") {
|
|
2104
|
-
throw new Error(`Missing port. Usage: ${usage}`);
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
const port = Number(value);
|
|
2108
|
-
|
|
2109
|
-
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
2110
|
-
throw new Error(`Invalid port. Usage: ${usage}`);
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
return port;
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
function parseTargetFlag(value: string | boolean | undefined, capability: "build" | "deploy"): string {
|
|
2117
|
-
if (value === undefined) {
|
|
2118
|
-
return "cloudflare";
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
if (typeof value !== "string") {
|
|
2122
|
-
throw new Error(
|
|
2123
|
-
`Missing target. Usage: agentkit ${capability} --target ${listTargetsWithCapability(capability).join("|")}`,
|
|
2124
|
-
);
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
return value;
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
function parseCloudApiUrl(value: string | boolean | undefined): string {
|
|
2131
|
-
if (value === undefined) {
|
|
2132
|
-
return process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl;
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
if (typeof value !== "string") {
|
|
2136
|
-
throw new Error("Missing API URL. Usage: agentkit deploy --api <url>");
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
return value;
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
function parseRequiredStringFlag(value: string | boolean | undefined, name: string, usage: string): string {
|
|
2143
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2144
|
-
throw new Error(`Missing ${name}. Usage: ${usage}`);
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
return value.trim();
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
function parseOptionalStringFlag(value: string | boolean | undefined, name: string, usage: string): string | undefined {
|
|
2151
|
-
if (value === undefined) {
|
|
2152
|
-
return undefined;
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2156
|
-
throw new Error(`Missing ${name}. Usage: ${usage}`);
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
return value.trim();
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
async function resolveSecretInputValue(options: {
|
|
2163
|
-
name: string | undefined;
|
|
2164
|
-
valueParts: string[];
|
|
2165
|
-
stdinFlag: string | boolean | undefined;
|
|
2166
|
-
fromEnvFlag: string | boolean | undefined;
|
|
2167
|
-
fromLocalEnvFlag: string | boolean | undefined;
|
|
2168
|
-
localEnvRoot?: string;
|
|
2169
|
-
usage: string;
|
|
2170
|
-
inlineSensitiveHelp?: string;
|
|
2171
|
-
}): Promise<string> {
|
|
2172
|
-
const { name, valueParts, stdinFlag, fromEnvFlag, fromLocalEnvFlag, localEnvRoot, usage, inlineSensitiveHelp } = options;
|
|
2173
|
-
|
|
2174
|
-
if (!name) {
|
|
2175
|
-
throw new Error(`Missing name. Usage: ${usage}`);
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
const hasInlineValue = valueParts.length > 0;
|
|
2179
|
-
const hasStdin = stdinFlag !== undefined;
|
|
2180
|
-
const hasFromEnv = fromEnvFlag !== undefined;
|
|
2181
|
-
const hasFromLocalEnv = fromLocalEnvFlag !== undefined;
|
|
2182
|
-
const inputModes = [hasInlineValue, hasStdin, hasFromEnv, hasFromLocalEnv].filter(Boolean).length;
|
|
2183
|
-
|
|
2184
|
-
if (inputModes !== 1) {
|
|
2185
|
-
throw new Error(`Provide exactly one value source. Usage: ${usage}`);
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
let value: string;
|
|
2189
|
-
|
|
2190
|
-
if (hasInlineValue) {
|
|
2191
|
-
value = valueParts.join(" ");
|
|
2192
|
-
if (looksLikeSecretName(name)) {
|
|
2193
|
-
console.error(
|
|
2194
|
-
`Warning: ${name} looks sensitive. ${inlineSensitiveHelp ?? "Prefer --stdin or --from-env so the value does not enter shell history."}`,
|
|
2195
|
-
);
|
|
2196
|
-
}
|
|
2197
|
-
} else if (hasStdin) {
|
|
2198
|
-
if (stdinFlag !== true) {
|
|
2199
|
-
throw new Error(`--stdin does not take a value. Usage: ${usage}`);
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
value = stripOneTrailingNewline(await readStdinText());
|
|
2203
|
-
} else if (hasFromEnv) {
|
|
2204
|
-
const envName = fromEnvFlag === true ? name : parseRequiredStringFlag(fromEnvFlag, "env name", usage);
|
|
2205
|
-
const envValue = process.env[envName];
|
|
2206
|
-
|
|
2207
|
-
if (envValue === undefined) {
|
|
2208
|
-
throw new AgentKitError("secret_not_found", `Environment variable ${envName} is not set.`);
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
value = envValue;
|
|
2212
|
-
} else {
|
|
2213
|
-
if (fromLocalEnvFlag !== true) {
|
|
2214
|
-
throw new Error(`--from-local-env does not take a value. Usage: ${usage}`);
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
|
-
if (!localEnvRoot) {
|
|
2218
|
-
throw new Error(`--from-local-env is not available for this command. Usage: ${usage}`);
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
const localEnv = await loadCapsuleEnv(localEnvRoot, {});
|
|
2222
|
-
const localValue = localEnv[name];
|
|
2223
|
-
|
|
2224
|
-
if (localValue === undefined) {
|
|
2225
|
-
throw new AgentKitError("secret_not_found", `Local .env does not contain ${name}.`);
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
value = localValue;
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
if (value.length === 0) {
|
|
2232
|
-
throw new Error(`Secret value cannot be empty. Usage: ${usage}`);
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
return value;
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
function looksLikeSecretName(name: string): boolean {
|
|
2239
|
-
return /(?:SECRET|TOKEN|KEY|PASSWORD|CREDENTIAL|PRIVATE)/i.test(name);
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
async function readStdinText(): Promise<string> {
|
|
2243
|
-
const chunks: Buffer[] = [];
|
|
2244
|
-
|
|
2245
|
-
for await (const chunk of process.stdin) {
|
|
2246
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
function stripOneTrailingNewline(value: string): string {
|
|
2253
|
-
return value.replace(/\r?\n$/, "");
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
function printBuildResult(result: Awaited<ReturnType<typeof buildAgentCapsule>>): void {
|
|
2257
|
-
console.log("AgentKit build complete");
|
|
2258
|
-
console.log("");
|
|
2259
|
-
console.log(`Target: ${result.target}`);
|
|
2260
|
-
console.log(`Build dir: ${result.buildDir}`);
|
|
2261
|
-
console.log(`Manifest: ${result.manifestPath}`);
|
|
2262
|
-
|
|
2263
|
-
if (result.target === "cloudflare") {
|
|
2264
|
-
console.log(`Worker: ${result.workerPath}`);
|
|
2265
|
-
console.log(`Wrangler: ${result.wranglerPath}`);
|
|
2266
|
-
if (result.tursoSchemaPath) {
|
|
2267
|
-
console.log(`Turso: ${result.tursoSchemaPath}`);
|
|
2268
|
-
}
|
|
2269
|
-
console.log(`Bundle: ${result.bundleSha256}`);
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
if (result.target === "container") {
|
|
2273
|
-
console.log(`Server: ${result.serverPath}`);
|
|
2274
|
-
console.log(`Docker: ${result.dockerfilePath}`);
|
|
2275
|
-
console.log(`Migrations:${result.migrationsDir}`);
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
if (result.warnings.length > 0) {
|
|
2279
|
-
console.log("");
|
|
2280
|
-
console.log("Warnings:");
|
|
2281
|
-
|
|
2282
|
-
for (const warning of result.warnings) {
|
|
2283
|
-
console.log(` - ${warning}`);
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
function printVpsDeployPlan(plan: Awaited<ReturnType<typeof prepareVpsDeploy>>): void {
|
|
2289
|
-
console.log("");
|
|
2290
|
-
console.log("VPS deploy plan ready");
|
|
2291
|
-
console.log(`Host: ${plan.host}`);
|
|
2292
|
-
console.log(`Compose: ${plan.composePath}`);
|
|
2293
|
-
console.log(`Env: ${plan.envExamplePath}`);
|
|
2294
|
-
console.log(`Secrets: ${plan.secretsPath}`);
|
|
2295
|
-
console.log(`Handoff: ${plan.handoffPath}`);
|
|
2296
|
-
console.log(`State: ${plan.statePath}`);
|
|
2297
|
-
console.log("");
|
|
2298
|
-
console.log("Commands:");
|
|
2299
|
-
|
|
2300
|
-
for (const command of plan.commands) {
|
|
2301
|
-
console.log(` ${command}`);
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
async function deployCloudflare(wranglerPath: string, buildDir: string): Promise<void> {
|
|
2306
|
-
const exitCode = await runInheritedCommand("npx", ["wrangler", "deploy", "--config", wranglerPath], buildDir);
|
|
2307
|
-
|
|
2308
|
-
if (exitCode !== 0) {
|
|
2309
|
-
throw new Error(`Cloudflare deploy failed with exit code ${exitCode}.`);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
async function installProjectDependencies(projectPath: string): Promise<void> {
|
|
2314
|
-
const exitCode = await runInheritedCommand("npm", ["install"], projectPath);
|
|
2315
|
-
|
|
2316
|
-
if (exitCode !== 0) {
|
|
2317
|
-
throw new Error(`Dependency install failed with exit code ${exitCode}. Run \`npm install\` in ${projectPath}.`);
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
function runInheritedCommand(command: string, args: string[], cwd: string): Promise<number> {
|
|
2322
|
-
return new Promise((resolvePromise, reject) => {
|
|
2323
|
-
const child = spawn(command, args, {
|
|
2324
|
-
cwd,
|
|
2325
|
-
stdio: "inherit",
|
|
2326
|
-
});
|
|
2327
|
-
|
|
2328
|
-
child.once("error", reject);
|
|
2329
|
-
child.once("close", (code) => resolvePromise(code ?? 1));
|
|
2330
|
-
});
|
|
2331
|
-
}
|
|
1016
|
+
}
|
|
2332
1017
|
|
|
2333
1018
|
async function parseToolInput(
|
|
2334
1019
|
cwd: string,
|
|
@@ -2451,396 +1136,6 @@ async function openLocalUrl(url: string): Promise<void> {
|
|
|
2451
1136
|
});
|
|
2452
1137
|
}
|
|
2453
1138
|
|
|
2454
|
-
async function startDeployChatUiServer(
|
|
2455
|
-
cwd: string,
|
|
2456
|
-
options: { port: number; tokenFile?: string },
|
|
2457
|
-
): Promise<{ url: string; deployUrl: string; tokenSource: string; stop(): Promise<void> }> {
|
|
2458
|
-
const root = await findAgentCapsuleRoot(cwd);
|
|
2459
|
-
const state = await readLocalDeployStateIfExists(root);
|
|
2460
|
-
|
|
2461
|
-
if (!state?.url) {
|
|
2462
|
-
throw new Error("No hosted deploy URL found. Run agentkit deploy first.");
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
const tokenPath = options.tokenFile
|
|
2466
|
-
? resolve(root, options.tokenFile)
|
|
2467
|
-
: join(root, defaultDeployChatAccessTokenPath);
|
|
2468
|
-
const hostname = "localhost";
|
|
2469
|
-
const deployUrl = state.url;
|
|
2470
|
-
const server = createServer(async (request, response) => {
|
|
2471
|
-
try {
|
|
2472
|
-
const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? `${hostname}:${options.port}`}`);
|
|
2473
|
-
|
|
2474
|
-
if (request.method === "GET" && requestUrl.pathname === "/") {
|
|
2475
|
-
sendHtml(response, renderDeployChatUi({ deployUrl }));
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
|
-
if (request.method === "GET" && requestUrl.pathname === "/health") {
|
|
2480
|
-
sendJson(response, 200, { status: "ok", target: "deploy-chat-ui", deployUrl });
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
if (request.method === "GET" && requestUrl.pathname === "/_agentkit") {
|
|
2485
|
-
sendJson(response, 200, {
|
|
2486
|
-
deployUrl,
|
|
2487
|
-
access: "proxied",
|
|
2488
|
-
tokenSource: process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN ? "AGENTKIT_DEPLOY_ACCESS_TOKEN" : relativePath(root, tokenPath),
|
|
2489
|
-
});
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
if (request.method === "POST" && (requestUrl.pathname === "/v1/chat" || requestUrl.pathname === "/chat")) {
|
|
2494
|
-
const token = await readDeployAccessTokenForUi(tokenPath);
|
|
2495
|
-
const body = await readIncomingBody(request);
|
|
2496
|
-
const contentTypeHeader = request.headers["content-type"];
|
|
2497
|
-
const contentType = Array.isArray(contentTypeHeader)
|
|
2498
|
-
? contentTypeHeader[0] ?? "application/json"
|
|
2499
|
-
: contentTypeHeader ?? "application/json";
|
|
2500
|
-
const upstream = await fetch(new URL("/v1/chat", deployUrl).href, {
|
|
2501
|
-
method: "POST",
|
|
2502
|
-
headers: {
|
|
2503
|
-
"Authorization": `Bearer ${token}`,
|
|
2504
|
-
"Content-Type": contentType,
|
|
2505
|
-
},
|
|
2506
|
-
body,
|
|
2507
|
-
});
|
|
2508
|
-
const text = await upstream.text();
|
|
2509
|
-
|
|
2510
|
-
response.writeHead(upstream.status, {
|
|
2511
|
-
"content-type": upstream.headers.get("content-type") ?? "application/json",
|
|
2512
|
-
});
|
|
2513
|
-
response.end(text);
|
|
2514
|
-
return;
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
sendJson(response, 404, { error: { code: "not_found", message: "Route not found." } });
|
|
2518
|
-
} catch (error) {
|
|
2519
|
-
sendJson(response, 500, {
|
|
2520
|
-
error: {
|
|
2521
|
-
code: isAgentKitError(error) ? error.code : "deploy_chat_ui_error",
|
|
2522
|
-
message: error instanceof Error ? error.message : String(error),
|
|
2523
|
-
},
|
|
2524
|
-
});
|
|
2525
|
-
}
|
|
2526
|
-
});
|
|
2527
|
-
|
|
2528
|
-
await new Promise<void>((resolvePromise, reject) => {
|
|
2529
|
-
server.once("error", reject);
|
|
2530
|
-
server.listen(options.port, hostname, () => {
|
|
2531
|
-
server.off("error", reject);
|
|
2532
|
-
resolvePromise();
|
|
2533
|
-
});
|
|
2534
|
-
});
|
|
2535
|
-
|
|
2536
|
-
return {
|
|
2537
|
-
url: `http://${hostname}:${options.port}`,
|
|
2538
|
-
deployUrl,
|
|
2539
|
-
tokenSource: process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN ? "AGENTKIT_DEPLOY_ACCESS_TOKEN" : relativePath(root, tokenPath),
|
|
2540
|
-
stop: () =>
|
|
2541
|
-
new Promise<void>((resolvePromise, reject) => {
|
|
2542
|
-
server.close((error) => (error ? reject(error) : resolvePromise()));
|
|
2543
|
-
}),
|
|
2544
|
-
};
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
async function readDeployAccessTokenForUi(tokenPath: string): Promise<string> {
|
|
2548
|
-
if (process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN) {
|
|
2549
|
-
return process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN;
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
let payload: unknown;
|
|
2553
|
-
|
|
2554
|
-
try {
|
|
2555
|
-
payload = JSON.parse(await readFile(tokenPath, "utf8"));
|
|
2556
|
-
} catch (error) {
|
|
2557
|
-
throw new AgentKitError(
|
|
2558
|
-
"deploy_access_token_missing",
|
|
2559
|
-
`Could not read a clean deploy access token file at ${tokenPath}. Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`,
|
|
2560
|
-
);
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
const token =
|
|
2564
|
-
payload && typeof payload === "object"
|
|
2565
|
-
? (payload as { access_token?: { token?: unknown }; token?: unknown; accessToken?: unknown }).access_token?.token ??
|
|
2566
|
-
(payload as { token?: unknown }).token ??
|
|
2567
|
-
(payload as { accessToken?: unknown }).accessToken
|
|
2568
|
-
: undefined;
|
|
2569
|
-
|
|
2570
|
-
if (typeof token !== "string" || token.trim().length === 0) {
|
|
2571
|
-
throw new AgentKitError(
|
|
2572
|
-
"deploy_access_token_invalid",
|
|
2573
|
-
`Deploy access token file ${tokenPath} does not contain access_token.token. Recreate it with: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`,
|
|
2574
|
-
);
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
return token.trim();
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
function readIncomingBody(request: IncomingMessage): Promise<string> {
|
|
2581
|
-
return new Promise((resolvePromise, reject) => {
|
|
2582
|
-
let body = "";
|
|
2583
|
-
request.setEncoding("utf8");
|
|
2584
|
-
request.on("data", (chunk) => {
|
|
2585
|
-
body += chunk;
|
|
2586
|
-
});
|
|
2587
|
-
request.on("end", () => resolvePromise(body));
|
|
2588
|
-
request.on("error", reject);
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
function sendJson(response: ServerResponse, status: number, body: unknown): void {
|
|
2593
|
-
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
2594
|
-
response.end(JSON.stringify(body, null, 2));
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
|
-
function sendHtml(response: ServerResponse, body: string): void {
|
|
2598
|
-
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2599
|
-
response.end(body);
|
|
2600
|
-
}
|
|
2601
|
-
|
|
2602
|
-
function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
2603
|
-
const deployUrl = escapeHtml(input.deployUrl);
|
|
2604
|
-
|
|
2605
|
-
return `<!doctype html>
|
|
2606
|
-
<html lang="en">
|
|
2607
|
-
<head>
|
|
2608
|
-
<meta charset="utf-8">
|
|
2609
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2610
|
-
<title>AgentKit Hosted Chat</title>
|
|
2611
|
-
<style>
|
|
2612
|
-
:root {
|
|
2613
|
-
color-scheme: light;
|
|
2614
|
-
--bg: #f6f7f5;
|
|
2615
|
-
--panel: #ffffff;
|
|
2616
|
-
--ink: #182022;
|
|
2617
|
-
--muted: #667175;
|
|
2618
|
-
--line: #d9ded9;
|
|
2619
|
-
--accent: #176b5d;
|
|
2620
|
-
--danger: #9f2d2d;
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
* { box-sizing: border-box; }
|
|
2624
|
-
|
|
2625
|
-
body {
|
|
2626
|
-
margin: 0;
|
|
2627
|
-
min-height: 100svh;
|
|
2628
|
-
background: var(--bg);
|
|
2629
|
-
color: var(--ink);
|
|
2630
|
-
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
main {
|
|
2634
|
-
min-height: 100svh;
|
|
2635
|
-
display: grid;
|
|
2636
|
-
grid-template-rows: auto 1fr auto;
|
|
2637
|
-
background: var(--panel);
|
|
2638
|
-
}
|
|
2639
|
-
|
|
2640
|
-
header {
|
|
2641
|
-
border-bottom: 1px solid var(--line);
|
|
2642
|
-
padding: 18px 22px;
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
h1 {
|
|
2646
|
-
margin: 0 0 4px;
|
|
2647
|
-
font-size: 20px;
|
|
2648
|
-
line-height: 1.2;
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
code {
|
|
2652
|
-
color: var(--muted);
|
|
2653
|
-
font-size: 12px;
|
|
2654
|
-
overflow-wrap: anywhere;
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
#messages {
|
|
2658
|
-
padding: 22px;
|
|
2659
|
-
overflow: auto;
|
|
2660
|
-
display: flex;
|
|
2661
|
-
flex-direction: column;
|
|
2662
|
-
gap: 12px;
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
.message {
|
|
2666
|
-
max-width: 780px;
|
|
2667
|
-
display: grid;
|
|
2668
|
-
gap: 4px;
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
.message.user {
|
|
2672
|
-
align-self: end;
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
.role {
|
|
2676
|
-
color: var(--muted);
|
|
2677
|
-
font-size: 12px;
|
|
2678
|
-
font-weight: 700;
|
|
2679
|
-
text-transform: uppercase;
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
.bubble {
|
|
2683
|
-
border: 1px solid var(--line);
|
|
2684
|
-
border-radius: 8px;
|
|
2685
|
-
padding: 12px 14px;
|
|
2686
|
-
white-space: pre-wrap;
|
|
2687
|
-
line-height: 1.45;
|
|
2688
|
-
background: #fbfbfa;
|
|
2689
|
-
}
|
|
2690
|
-
|
|
2691
|
-
.user .bubble {
|
|
2692
|
-
border-color: #b8d8ce;
|
|
2693
|
-
background: #e8f5f0;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
.error .bubble {
|
|
2697
|
-
border-color: #e1b1b1;
|
|
2698
|
-
color: var(--danger);
|
|
2699
|
-
background: #fff6f6;
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
form {
|
|
2703
|
-
border-top: 1px solid var(--line);
|
|
2704
|
-
padding: 14px;
|
|
2705
|
-
display: grid;
|
|
2706
|
-
grid-template-columns: 1fr auto;
|
|
2707
|
-
gap: 10px;
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
textarea {
|
|
2711
|
-
min-height: 52px;
|
|
2712
|
-
max-height: 180px;
|
|
2713
|
-
resize: vertical;
|
|
2714
|
-
border: 1px solid var(--line);
|
|
2715
|
-
border-radius: 8px;
|
|
2716
|
-
padding: 12px;
|
|
2717
|
-
color: var(--ink);
|
|
2718
|
-
font: inherit;
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
button {
|
|
2722
|
-
min-width: 96px;
|
|
2723
|
-
border: 0;
|
|
2724
|
-
border-radius: 8px;
|
|
2725
|
-
padding: 0 18px;
|
|
2726
|
-
color: white;
|
|
2727
|
-
background: var(--accent);
|
|
2728
|
-
font: inherit;
|
|
2729
|
-
font-weight: 700;
|
|
2730
|
-
cursor: pointer;
|
|
2731
|
-
}
|
|
2732
|
-
|
|
2733
|
-
button:disabled {
|
|
2734
|
-
cursor: wait;
|
|
2735
|
-
opacity: 0.65;
|
|
2736
|
-
}
|
|
2737
|
-
|
|
2738
|
-
@media (max-width: 640px) {
|
|
2739
|
-
form {
|
|
2740
|
-
grid-template-columns: 1fr;
|
|
2741
|
-
}
|
|
2742
|
-
|
|
2743
|
-
button {
|
|
2744
|
-
min-height: 44px;
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
</style>
|
|
2748
|
-
</head>
|
|
2749
|
-
<body>
|
|
2750
|
-
<main>
|
|
2751
|
-
<header>
|
|
2752
|
-
<h1>AgentKit Hosted Chat</h1>
|
|
2753
|
-
<code>${deployUrl}</code>
|
|
2754
|
-
</header>
|
|
2755
|
-
<section id="messages" aria-live="polite"></section>
|
|
2756
|
-
<form id="form">
|
|
2757
|
-
<textarea id="message" name="message" placeholder="Type a message" required autofocus></textarea>
|
|
2758
|
-
<button id="send" type="submit">Send</button>
|
|
2759
|
-
</form>
|
|
2760
|
-
</main>
|
|
2761
|
-
<script>
|
|
2762
|
-
const form = document.querySelector("#form");
|
|
2763
|
-
const textarea = document.querySelector("#message");
|
|
2764
|
-
const send = document.querySelector("#send");
|
|
2765
|
-
const messages = document.querySelector("#messages");
|
|
2766
|
-
let conversationId = null;
|
|
2767
|
-
|
|
2768
|
-
function addMessage(role, content, className = "") {
|
|
2769
|
-
const wrapper = document.createElement("article");
|
|
2770
|
-
wrapper.className = ["message", role, className].filter(Boolean).join(" ");
|
|
2771
|
-
const label = document.createElement("div");
|
|
2772
|
-
label.className = "role";
|
|
2773
|
-
label.textContent = role;
|
|
2774
|
-
const bubble = document.createElement("div");
|
|
2775
|
-
bubble.className = "bubble";
|
|
2776
|
-
bubble.textContent = content;
|
|
2777
|
-
wrapper.append(label, bubble);
|
|
2778
|
-
messages.append(wrapper);
|
|
2779
|
-
messages.scrollTop = messages.scrollHeight;
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
|
-
form.addEventListener("submit", async (event) => {
|
|
2783
|
-
event.preventDefault();
|
|
2784
|
-
const message = textarea.value.trim();
|
|
2785
|
-
if (!message) return;
|
|
2786
|
-
|
|
2787
|
-
addMessage("user", message);
|
|
2788
|
-
textarea.value = "";
|
|
2789
|
-
send.disabled = true;
|
|
2790
|
-
|
|
2791
|
-
try {
|
|
2792
|
-
const response = await fetch("/v1/chat", {
|
|
2793
|
-
method: "POST",
|
|
2794
|
-
headers: { "content-type": "application/json" },
|
|
2795
|
-
body: JSON.stringify({ message, conversationId }),
|
|
2796
|
-
});
|
|
2797
|
-
const payload = await response.json();
|
|
2798
|
-
|
|
2799
|
-
if (!response.ok) {
|
|
2800
|
-
throw new Error(payload.error?.message || "Request failed");
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
conversationId = payload.conversationId || conversationId;
|
|
2804
|
-
addMessage("assistant", payload.message?.content || "");
|
|
2805
|
-
} catch (error) {
|
|
2806
|
-
addMessage("error", error instanceof Error ? error.message : String(error), "error");
|
|
2807
|
-
} finally {
|
|
2808
|
-
send.disabled = false;
|
|
2809
|
-
textarea.focus();
|
|
2810
|
-
}
|
|
2811
|
-
});
|
|
2812
|
-
</script>
|
|
2813
|
-
</body>
|
|
2814
|
-
</html>`;
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
function escapeHtml(value: string): string {
|
|
2818
|
-
return value
|
|
2819
|
-
.replace(/&/g, "&")
|
|
2820
|
-
.replace(/</g, "<")
|
|
2821
|
-
.replace(/>/g, ">")
|
|
2822
|
-
.replace(/"/g, """)
|
|
2823
|
-
.replace(/'/g, "'");
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
|
-
async function waitForShutdown(stop: () => Promise<void>): Promise<void> {
|
|
2827
|
-
await new Promise<void>((resolve) => {
|
|
2828
|
-
let stopped = false;
|
|
2829
|
-
|
|
2830
|
-
const shutdown = () => {
|
|
2831
|
-
if (stopped) {
|
|
2832
|
-
return;
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
stopped = true;
|
|
2836
|
-
void stop().finally(resolve);
|
|
2837
|
-
};
|
|
2838
|
-
|
|
2839
|
-
process.once("SIGINT", shutdown);
|
|
2840
|
-
process.once("SIGTERM", shutdown);
|
|
2841
|
-
});
|
|
2842
|
-
}
|
|
2843
|
-
|
|
2844
1139
|
main().catch((error: unknown) => {
|
|
2845
1140
|
const message = error instanceof Error ? error.message : String(error);
|
|
2846
1141
|
const prefix = isAgentKitError(error) ? `agentkit ${error.code}` : "agentkit";
|