@andreprado/agentkit 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +39 -6
- package/docs/guides/create-agent.md +1 -3
- package/docs/guides/prepare-deploy.md +2 -1
- package/docs/llms-full.txt +5 -4
- package/docs/llms.txt +2 -1
- package/package.json +1 -2
- package/src/cli/index.ts +880 -30
- package/src/cloud/artifact.ts +48 -0
- package/src/cloud/client.ts +79 -0
- package/src/cloud/contracts.ts +47 -0
- package/src/cloud/index.ts +3 -0
- package/src/index.ts +1 -1
- package/src/runtime/deploy.ts +1 -1
- package/src/runtime/targets/cloudflare/build.ts +4 -0
- package/src/templates/blank.ts +11 -9
- package/src/templates/support.ts +11 -9
- package/src/runtime/targets/cloudflare/deploy.ts +0 -5475
package/src/cli/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
6
|
|
|
6
7
|
import { createProject } from "../create-project";
|
|
7
8
|
import { buildAgentCapsule } from "../runtime/build";
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
} from "../runtime/db-commands";
|
|
20
21
|
import { runEvalsFromCwd } from "../runtime/evals";
|
|
21
22
|
import { findAgentKitDocs } from "../runtime/docs";
|
|
22
|
-
import { listCapsuleEnv, setCapsuleEnv, unsetCapsuleEnv } from "../runtime/env";
|
|
23
|
+
import { listCapsuleEnv, loadCapsuleEnv, setCapsuleEnv, unsetCapsuleEnv } from "../runtime/env";
|
|
23
24
|
import { loadDeployReadinessContext, stableCloudProjectId, type DeployReadinessContext } from "../runtime/deploy-readiness";
|
|
24
25
|
import { runToolFromCwd } from "../runtime/tool-runner";
|
|
25
26
|
import { startAgentDevServer } from "../runtime/dev-server";
|
|
@@ -28,13 +29,59 @@ import { AgentKitError, isAgentKitError } from "../runtime/errors";
|
|
|
28
29
|
import { prepareVpsDeploy } from "../runtime/targets/vps/deploy";
|
|
29
30
|
|
|
30
31
|
const defaultAgentKitCloudApiUrl = "https://agentkit-cloud.aibuilders.com.br";
|
|
32
|
+
const defaultDeployChatAccessTokenName = "agentkit-chat-ui";
|
|
33
|
+
const defaultDeployChatAccessTokenPath = ".agentkit/chat-access-token.json";
|
|
31
34
|
|
|
32
35
|
function renderHelp(): string {
|
|
33
36
|
return `AgentKit
|
|
34
37
|
|
|
38
|
+
Create, run, inspect, and deploy Agent Capsules.
|
|
39
|
+
|
|
40
|
+
Quick start:
|
|
41
|
+
agentkit new support-agent --template support
|
|
42
|
+
cd support-agent
|
|
43
|
+
npm run dev
|
|
44
|
+
npm run chat -- --message "hello"
|
|
45
|
+
|
|
46
|
+
Usage:
|
|
47
|
+
agentkit <command> [options]
|
|
48
|
+
|
|
49
|
+
Start:
|
|
50
|
+
agentkit new <name> [--template blank|support] [--no-install]
|
|
51
|
+
agentkit dev
|
|
52
|
+
agentkit chat-ui --deploy
|
|
53
|
+
agentkit chat --message <text>
|
|
54
|
+
agentkit inspect
|
|
55
|
+
|
|
56
|
+
Build:
|
|
57
|
+
agentkit tool <name> [--input <path-or-json>]
|
|
58
|
+
agentkit db migrate
|
|
59
|
+
agentkit eval run
|
|
60
|
+
agentkit conversations list
|
|
61
|
+
|
|
62
|
+
Deploy:
|
|
63
|
+
agentkit login --token <token>
|
|
64
|
+
agentkit deploy doctor
|
|
65
|
+
agentkit secret set <NAME> --stdin
|
|
66
|
+
agentkit deploy
|
|
67
|
+
agentkit deploy status
|
|
68
|
+
|
|
69
|
+
More:
|
|
70
|
+
agentkit channels ...
|
|
71
|
+
agentkit access token ...
|
|
72
|
+
agentkit env ...
|
|
73
|
+
agentkit docs full
|
|
74
|
+
agentkit help commands
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderCommandReference(): string {
|
|
79
|
+
return `AgentKit Command Reference
|
|
80
|
+
|
|
35
81
|
Usage:
|
|
36
|
-
agentkit new <name> [--template blank|support]
|
|
82
|
+
agentkit new <name> [--template blank|support] [--no-install]
|
|
37
83
|
agentkit dev
|
|
84
|
+
agentkit chat-ui --deploy [--port <number>] [--token-file <path>]
|
|
38
85
|
agentkit chat --message <text>
|
|
39
86
|
agentkit tool <name> [--input <path-or-json>]
|
|
40
87
|
agentkit db migrate
|
|
@@ -61,23 +108,30 @@ Usage:
|
|
|
61
108
|
agentkit deploy pause [--api <url>]
|
|
62
109
|
agentkit deploy resume [--api <url>]
|
|
63
110
|
agentkit secret set <NAME> <VALUE> [--api <url>]
|
|
111
|
+
agentkit secret set <NAME> --stdin [--api <url>]
|
|
112
|
+
agentkit secret set <NAME> --from-env [ENV_NAME] [--api <url>]
|
|
113
|
+
agentkit secret set <NAME> --from-local-env [--api <url>]
|
|
64
114
|
agentkit secret list [--api <url>]
|
|
65
115
|
agentkit secret unset <NAME> [--api <url>]
|
|
66
|
-
agentkit access token create <name> [--api <url>]
|
|
116
|
+
agentkit access token create <name> [--api <url>] [--out <path>]
|
|
67
117
|
agentkit access token list [--api <url>]
|
|
68
118
|
agentkit access token revoke <token-id> [--api <url>]
|
|
69
119
|
agentkit env set <NAME> <VALUE>
|
|
120
|
+
agentkit env set <NAME> --stdin
|
|
121
|
+
agentkit env set <NAME> --from-env [ENV_NAME]
|
|
70
122
|
agentkit env list
|
|
71
123
|
agentkit env unset <NAME>
|
|
72
124
|
agentkit docs path
|
|
73
125
|
agentkit docs llms
|
|
74
126
|
agentkit docs full
|
|
75
127
|
agentkit handoff codex [goal]
|
|
128
|
+
agentkit help commands
|
|
76
129
|
agentkit --help
|
|
77
130
|
|
|
78
131
|
Commands:
|
|
79
132
|
new Create a new Agent Capsule
|
|
80
133
|
dev Run the Agent Capsule locally
|
|
134
|
+
chat-ui Run a local chat UI, optionally pointed at a hosted deploy
|
|
81
135
|
chat Send one message to the local Agent Capsule
|
|
82
136
|
tool Run one registered tool directly
|
|
83
137
|
db Manage the local SQLite database
|
|
@@ -94,6 +148,7 @@ Commands:
|
|
|
94
148
|
env Manage local .env values without opening code
|
|
95
149
|
docs Print AgentKit docs locations
|
|
96
150
|
handoff Print a ready-to-paste prompt for another coding agent
|
|
151
|
+
help Print focused help or the full command reference
|
|
97
152
|
|
|
98
153
|
Examples:
|
|
99
154
|
agentkit new support-agent --template support
|
|
@@ -145,34 +200,60 @@ async function main() {
|
|
|
145
200
|
return;
|
|
146
201
|
}
|
|
147
202
|
|
|
203
|
+
if (args.command === "help") {
|
|
204
|
+
const [topic] = args.positional;
|
|
205
|
+
|
|
206
|
+
if (!topic) {
|
|
207
|
+
console.log(renderHelp());
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (topic === "commands") {
|
|
212
|
+
console.log(renderCommandReference());
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
throw new Error("Usage: agentkit help [commands]");
|
|
217
|
+
}
|
|
218
|
+
|
|
148
219
|
if (args.command === "new") {
|
|
149
220
|
const [name] = args.positional;
|
|
150
221
|
const template = String(args.flags.template ?? "blank");
|
|
222
|
+
const shouldInstall = args.flags["no-install"] !== true;
|
|
151
223
|
|
|
152
224
|
if (!name) {
|
|
153
|
-
throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support]");
|
|
225
|
+
throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support] [--no-install]");
|
|
154
226
|
}
|
|
155
227
|
|
|
156
228
|
const result = await createProject({ name, template, cwd: process.cwd() });
|
|
157
229
|
console.log(`Created ${result.name} at ${result.path}`);
|
|
230
|
+
|
|
231
|
+
if (shouldInstall) {
|
|
232
|
+
console.log("");
|
|
233
|
+
console.log("Installing dependencies...");
|
|
234
|
+
await installProjectDependencies(result.path);
|
|
235
|
+
}
|
|
236
|
+
|
|
158
237
|
console.log("");
|
|
159
238
|
console.log("Next:");
|
|
160
239
|
if (resolve(result.path) !== resolve(process.cwd())) {
|
|
161
240
|
console.log(` cd ${result.name}`);
|
|
162
241
|
}
|
|
163
|
-
|
|
242
|
+
if (!shouldInstall) {
|
|
243
|
+
console.log(" npm install");
|
|
244
|
+
}
|
|
164
245
|
console.log(" open this folder in Codex or Claude Code");
|
|
165
246
|
console.log(' ask: "Develop an appointment and intake agent for an ophthalmology office"');
|
|
166
247
|
console.log(' npm run chat -- --message "hello"');
|
|
167
248
|
console.log(" npm run dev");
|
|
168
249
|
console.log(" npm run agentkit -- deploy");
|
|
169
250
|
console.log("");
|
|
170
|
-
console.log("Optional: manage local secrets with `npm run agentkit -- env set <NAME>
|
|
251
|
+
console.log("Optional: manage local secrets with `printf %s \"$VALUE\" | npm run agentkit -- env set <NAME> --stdin`.");
|
|
171
252
|
return;
|
|
172
253
|
}
|
|
173
254
|
|
|
174
255
|
if (args.command === "dev") {
|
|
175
|
-
const port = parsePort(args.flags.port);
|
|
256
|
+
const port = parsePort(args.flags.port, "agentkit dev --port <number>");
|
|
176
257
|
const devServer = await startAgentDevServer(process.cwd(), { port });
|
|
177
258
|
|
|
178
259
|
console.log("Agent Capsule running");
|
|
@@ -185,6 +266,28 @@ async function main() {
|
|
|
185
266
|
return;
|
|
186
267
|
}
|
|
187
268
|
|
|
269
|
+
if (args.command === "chat-ui") {
|
|
270
|
+
if (!args.flags.deploy) {
|
|
271
|
+
throw new Error("Usage: agentkit chat-ui --deploy [--port <number>] [--token-file <path>]");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const port = parsePort(args.flags.port, "agentkit chat-ui --deploy --port <number>") ?? 4124;
|
|
275
|
+
const tokenFile = parseOptionalStringFlag(
|
|
276
|
+
args.flags["token-file"],
|
|
277
|
+
"token-file",
|
|
278
|
+
"agentkit chat-ui --deploy --token-file <path>",
|
|
279
|
+
);
|
|
280
|
+
const server = await startDeployChatUiServer(process.cwd(), { port, tokenFile });
|
|
281
|
+
|
|
282
|
+
console.log("AgentKit hosted chat UI running");
|
|
283
|
+
console.log("");
|
|
284
|
+
console.log(`Chat: ${server.url}`);
|
|
285
|
+
console.log(`Production: ${server.deployUrl}`);
|
|
286
|
+
console.log(`Token: ${server.tokenSource}`);
|
|
287
|
+
await waitForShutdown(server.stop);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
188
291
|
if (args.command === "chat") {
|
|
189
292
|
const message = args.flags.message;
|
|
190
293
|
|
|
@@ -351,8 +454,25 @@ async function main() {
|
|
|
351
454
|
|
|
352
455
|
if (args.command === "login") {
|
|
353
456
|
const token = parseRequiredStringFlag(args.flags.token, "token", "agentkit login --token <token> [--api <url>]");
|
|
354
|
-
|
|
355
|
-
|
|
457
|
+
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
458
|
+
const verified = await verifyCloudLogin(apiUrl, token);
|
|
459
|
+
await writeCloudAuth({ apiUrl, token });
|
|
460
|
+
const email = verified.me.account?.email;
|
|
461
|
+
console.log(email ? `Logged in to AgentKit Cloud as ${email}.` : "AgentKit Cloud token verified and stored.");
|
|
462
|
+
|
|
463
|
+
const hasCloudflareAlpha = (verified.capabilities.capabilities ?? []).some(
|
|
464
|
+
(capability) => capability.name === "cloudflare_deploy_alpha" && capability.status === "active",
|
|
465
|
+
);
|
|
466
|
+
console.log(
|
|
467
|
+
hasCloudflareAlpha
|
|
468
|
+
? "Entitlement: cloudflare_deploy_alpha active."
|
|
469
|
+
: "Warning: cloudflare_deploy_alpha entitlement was not found for this account.",
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
if (process.env.AGENTKIT_CLOUD_API_TOKEN && process.env.AGENTKIT_CLOUD_API_TOKEN !== token) {
|
|
473
|
+
console.log("Warning: AGENTKIT_CLOUD_API_TOKEN is set and will override this saved login in the current shell.");
|
|
474
|
+
}
|
|
475
|
+
|
|
356
476
|
return;
|
|
357
477
|
}
|
|
358
478
|
|
|
@@ -450,6 +570,12 @@ async function main() {
|
|
|
450
570
|
anonymous: anonymous || !auth?.token,
|
|
451
571
|
});
|
|
452
572
|
const statePath = await writeLocalDeployState(result.root, deploy);
|
|
573
|
+
const chatAccess = await ensureDeployChatAccessToken({
|
|
574
|
+
apiUrl,
|
|
575
|
+
root: result.root,
|
|
576
|
+
deployId: deploy.deploy.id,
|
|
577
|
+
skip: anonymous || !auth?.token || deploy.deploy.access.mode === "public",
|
|
578
|
+
});
|
|
453
579
|
|
|
454
580
|
console.log("");
|
|
455
581
|
console.log("Deploy ready");
|
|
@@ -457,6 +583,15 @@ async function main() {
|
|
|
457
583
|
console.log(`Project: ${deploy.project.id}`);
|
|
458
584
|
console.log(`Deploy: ${deploy.deploy.id}`);
|
|
459
585
|
console.log(`State: ${statePath}`);
|
|
586
|
+
|
|
587
|
+
if (chatAccess?.status === "ready") {
|
|
588
|
+
console.log(`Chat auth: ${relativePath(process.cwd(), chatAccess.path)}`);
|
|
589
|
+
} else if (chatAccess?.status === "skipped") {
|
|
590
|
+
console.log("Chat auth: public or anonymous deploy; no local chat token was created.");
|
|
591
|
+
} else if (chatAccess?.status === "failed") {
|
|
592
|
+
console.log(`Chat auth: could not create local chat token. ${formatReadinessError(chatAccess.error)}`);
|
|
593
|
+
console.log(`Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`);
|
|
594
|
+
}
|
|
460
595
|
return;
|
|
461
596
|
}
|
|
462
597
|
|
|
@@ -474,10 +609,18 @@ async function main() {
|
|
|
474
609
|
}
|
|
475
610
|
|
|
476
611
|
if (subcommand === "set") {
|
|
477
|
-
const value =
|
|
612
|
+
const value = await resolveSecretInputValue({
|
|
613
|
+
name,
|
|
614
|
+
valueParts,
|
|
615
|
+
stdinFlag: args.flags.stdin,
|
|
616
|
+
fromEnvFlag: args.flags["from-env"],
|
|
617
|
+
fromLocalEnvFlag: args.flags["from-local-env"],
|
|
618
|
+
localEnvRoot: args.flags["from-local-env"] !== undefined ? await findAgentCapsuleRoot(process.cwd()) : undefined,
|
|
619
|
+
usage: "agentkit secret set <NAME> <VALUE> | agentkit secret set <NAME> --stdin | agentkit secret set <NAME> --from-env [ENV_NAME] | agentkit secret set <NAME> --from-local-env [--api <url>]",
|
|
620
|
+
});
|
|
478
621
|
|
|
479
|
-
if (!name
|
|
480
|
-
throw new Error("Usage: agentkit secret set <NAME> <VALUE> [--api <url>]");
|
|
622
|
+
if (!name) {
|
|
623
|
+
throw new Error("Usage: agentkit secret set <NAME> <VALUE> | agentkit secret set <NAME> --stdin | agentkit secret set <NAME> --from-env [ENV_NAME] | agentkit secret set <NAME> --from-local-env [--api <url>]");
|
|
481
624
|
}
|
|
482
625
|
|
|
483
626
|
const payload = await cloudApiRequest(apiUrl, `/v1/projects/${encodeURIComponent(projectId)}/secrets/${encodeURIComponent(name)}`, {
|
|
@@ -500,14 +643,14 @@ async function main() {
|
|
|
500
643
|
return;
|
|
501
644
|
}
|
|
502
645
|
|
|
503
|
-
throw new Error("Usage: agentkit secret set <NAME> <VALUE> | agentkit secret list | agentkit secret unset <NAME>");
|
|
646
|
+
throw new Error("Usage: agentkit secret set <NAME> <VALUE> | agentkit secret set <NAME> --stdin | agentkit secret set <NAME> --from-env [ENV_NAME] | agentkit secret set <NAME> --from-local-env | agentkit secret list | agentkit secret unset <NAME>");
|
|
504
647
|
}
|
|
505
648
|
|
|
506
649
|
if (args.command === "access") {
|
|
507
650
|
const [subcommand, noun, name] = args.positional;
|
|
508
651
|
|
|
509
652
|
if (subcommand !== "token") {
|
|
510
|
-
throw new Error("Usage: agentkit access token create <name> | list | revoke <token-id> [--api <url>]");
|
|
653
|
+
throw new Error("Usage: agentkit access token create <name> [--out <path>] | list | revoke <token-id> [--api <url>]");
|
|
511
654
|
}
|
|
512
655
|
|
|
513
656
|
const state = await requireLocalDeployState();
|
|
@@ -518,7 +661,7 @@ async function main() {
|
|
|
518
661
|
method: "POST",
|
|
519
662
|
body: JSON.stringify({ name }),
|
|
520
663
|
});
|
|
521
|
-
|
|
664
|
+
await printJsonOrWrite(payload, args.flags.out, "agentkit access token create <name> [--api <url>] [--out <path>]");
|
|
522
665
|
return;
|
|
523
666
|
}
|
|
524
667
|
|
|
@@ -540,7 +683,7 @@ async function main() {
|
|
|
540
683
|
return;
|
|
541
684
|
}
|
|
542
685
|
|
|
543
|
-
throw new Error("Usage: agentkit access token create <name> | list | revoke <token-id> [--api <url>]");
|
|
686
|
+
throw new Error("Usage: agentkit access token create <name> [--out <path>] | list | revoke <token-id> [--api <url>]");
|
|
544
687
|
}
|
|
545
688
|
|
|
546
689
|
if (args.command === "channels") {
|
|
@@ -568,10 +711,17 @@ async function main() {
|
|
|
568
711
|
}
|
|
569
712
|
|
|
570
713
|
if (subcommand === "set") {
|
|
571
|
-
const value =
|
|
714
|
+
const value = await resolveSecretInputValue({
|
|
715
|
+
name,
|
|
716
|
+
valueParts,
|
|
717
|
+
stdinFlag: args.flags.stdin,
|
|
718
|
+
fromEnvFlag: args.flags["from-env"],
|
|
719
|
+
fromLocalEnvFlag: undefined,
|
|
720
|
+
usage: "agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME]",
|
|
721
|
+
});
|
|
572
722
|
|
|
573
|
-
if (!name
|
|
574
|
-
throw new Error("Usage: agentkit env set <NAME> <VALUE>");
|
|
723
|
+
if (!name) {
|
|
724
|
+
throw new Error("Usage: agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME]");
|
|
575
725
|
}
|
|
576
726
|
|
|
577
727
|
await setCapsuleEnv(root, name, value);
|
|
@@ -589,7 +739,7 @@ async function main() {
|
|
|
589
739
|
return;
|
|
590
740
|
}
|
|
591
741
|
|
|
592
|
-
throw new Error("Usage: agentkit env list | agentkit env set <NAME> <VALUE> | agentkit env unset <NAME>");
|
|
742
|
+
throw new Error("Usage: agentkit env list | agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME] | agentkit env unset <NAME>");
|
|
593
743
|
}
|
|
594
744
|
|
|
595
745
|
if (args.command === "docs") {
|
|
@@ -666,7 +816,6 @@ Verification:
|
|
|
666
816
|
Run these before you finish:
|
|
667
817
|
|
|
668
818
|
\`\`\`sh
|
|
669
|
-
npm install
|
|
670
819
|
npm run typecheck
|
|
671
820
|
npm run agentkit -- inspect
|
|
672
821
|
npm run chat -- --message "hello"
|
|
@@ -716,6 +865,36 @@ type CloudCapabilitiesResponse = {
|
|
|
716
865
|
}>;
|
|
717
866
|
};
|
|
718
867
|
|
|
868
|
+
type CloudLimitsResponse = {
|
|
869
|
+
online_agents?: {
|
|
870
|
+
account?: {
|
|
871
|
+
used?: number | null;
|
|
872
|
+
limit?: number | null;
|
|
873
|
+
};
|
|
874
|
+
global?: {
|
|
875
|
+
used?: number | null;
|
|
876
|
+
limit?: number | null;
|
|
877
|
+
};
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
type DeployAccessTokenCreateResponse = {
|
|
882
|
+
access_token?: {
|
|
883
|
+
id?: string;
|
|
884
|
+
deploy_id?: string;
|
|
885
|
+
name?: string;
|
|
886
|
+
token?: string;
|
|
887
|
+
};
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
type DeployAccessTokenListResponse = {
|
|
891
|
+
access_tokens?: Array<{
|
|
892
|
+
id?: string;
|
|
893
|
+
deploy_id?: string;
|
|
894
|
+
name?: string;
|
|
895
|
+
}>;
|
|
896
|
+
};
|
|
897
|
+
|
|
719
898
|
type CloudSecretsResponse = {
|
|
720
899
|
secrets?: Array<{
|
|
721
900
|
name?: string;
|
|
@@ -771,7 +950,7 @@ async function checkDeployReadiness(options: {
|
|
|
771
950
|
checks.push({
|
|
772
951
|
status: "fail",
|
|
773
952
|
title: "Login",
|
|
774
|
-
message: `AgentKit Cloud rejected the
|
|
953
|
+
message: `AgentKit Cloud rejected the token from ${formatCloudAuthSource(auth)}. ${formatReadinessError(error)}`,
|
|
775
954
|
action: "agentkit login --token <token>",
|
|
776
955
|
});
|
|
777
956
|
}
|
|
@@ -802,6 +981,58 @@ async function checkDeployReadiness(options: {
|
|
|
802
981
|
message: `Could not verify AgentKit Cloud capabilities. ${formatReadinessError(error)}`,
|
|
803
982
|
});
|
|
804
983
|
}
|
|
984
|
+
|
|
985
|
+
try {
|
|
986
|
+
const payload = (await cloudApiRequest(options.apiUrl, "/v1/me/limits", {
|
|
987
|
+
method: "GET",
|
|
988
|
+
})) as CloudLimitsResponse;
|
|
989
|
+
const account = payload.online_agents?.account;
|
|
990
|
+
const global = payload.online_agents?.global;
|
|
991
|
+
const accountLimit = normalizeOptionalNumber(account?.limit);
|
|
992
|
+
const accountUsed = normalizeOptionalNumber(account?.used);
|
|
993
|
+
const globalLimit = normalizeOptionalNumber(global?.limit);
|
|
994
|
+
const globalUsed = normalizeOptionalNumber(global?.used);
|
|
995
|
+
|
|
996
|
+
if (accountLimit !== undefined && accountUsed !== undefined && accountUsed >= accountLimit) {
|
|
997
|
+
checks.push({
|
|
998
|
+
status: "fail",
|
|
999
|
+
title: "Online agent capacity",
|
|
1000
|
+
message: `This account is already using ${accountUsed}/${accountLimit} online AgentKit deploys.`,
|
|
1001
|
+
action: "agentkit deploy pause",
|
|
1002
|
+
});
|
|
1003
|
+
} else if (globalLimit !== undefined && globalUsed !== undefined && globalUsed >= globalLimit) {
|
|
1004
|
+
checks.push({
|
|
1005
|
+
status: "fail",
|
|
1006
|
+
title: "Online agent capacity",
|
|
1007
|
+
message: `AgentKit Cloud is already using ${globalUsed}/${globalLimit} global online deploy slots.`,
|
|
1008
|
+
action: "agentkit deploy status",
|
|
1009
|
+
});
|
|
1010
|
+
} else if (accountLimit !== undefined && accountUsed !== undefined) {
|
|
1011
|
+
checks.push({
|
|
1012
|
+
status: "pass",
|
|
1013
|
+
title: "Online agent capacity",
|
|
1014
|
+
message: `Account has capacity: ${accountUsed}/${accountLimit} online deploys are in use.`,
|
|
1015
|
+
});
|
|
1016
|
+
} else if (globalLimit !== undefined && globalUsed !== undefined) {
|
|
1017
|
+
checks.push({
|
|
1018
|
+
status: "pass",
|
|
1019
|
+
title: "Online agent capacity",
|
|
1020
|
+
message: `Global capacity is available: ${globalUsed}/${globalLimit} online deploys are in use.`,
|
|
1021
|
+
});
|
|
1022
|
+
} else {
|
|
1023
|
+
checks.push({
|
|
1024
|
+
status: "info",
|
|
1025
|
+
title: "Online agent capacity",
|
|
1026
|
+
message: "No online deploy limit is configured for this account.",
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
checks.push({
|
|
1031
|
+
status: "warn",
|
|
1032
|
+
title: "Online agent capacity",
|
|
1033
|
+
message: `Could not verify online deploy capacity. ${formatReadinessError(error)}`,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
805
1036
|
}
|
|
806
1037
|
|
|
807
1038
|
if (context.agentKitManagedSecrets.length > 0) {
|
|
@@ -901,14 +1132,18 @@ function printDeployReadinessReport(report: DeployReadinessReport): void {
|
|
|
901
1132
|
|
|
902
1133
|
function secretSetCommand(secret: string, source: DeployReadinessContext["localSecrets"][string] | undefined): string {
|
|
903
1134
|
if (source?.dotenv) {
|
|
904
|
-
return `agentkit secret set ${secret}
|
|
1135
|
+
return `agentkit secret set ${secret} --from-local-env`;
|
|
905
1136
|
}
|
|
906
1137
|
|
|
907
1138
|
if (source?.environment) {
|
|
908
|
-
return `agentkit secret set ${secret}
|
|
1139
|
+
return `agentkit secret set ${secret} --from-env`;
|
|
909
1140
|
}
|
|
910
1141
|
|
|
911
|
-
return `agentkit secret set ${secret}
|
|
1142
|
+
return `agentkit secret set ${secret} --stdin`;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function normalizeOptionalNumber(value: number | null | undefined): number | undefined {
|
|
1146
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
912
1147
|
}
|
|
913
1148
|
|
|
914
1149
|
function formatReadinessError(error: unknown): string {
|
|
@@ -1379,6 +1614,7 @@ function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promi
|
|
|
1379
1614
|
type CloudAuthConfig = {
|
|
1380
1615
|
apiUrl: string;
|
|
1381
1616
|
token: string;
|
|
1617
|
+
source?: "environment" | "file";
|
|
1382
1618
|
};
|
|
1383
1619
|
|
|
1384
1620
|
function cloudAuthPath(): string {
|
|
@@ -1390,6 +1626,7 @@ async function readCloudAuth(): Promise<CloudAuthConfig | null> {
|
|
|
1390
1626
|
return {
|
|
1391
1627
|
apiUrl: process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl,
|
|
1392
1628
|
token: process.env.AGENTKIT_CLOUD_API_TOKEN,
|
|
1629
|
+
source: "environment",
|
|
1393
1630
|
};
|
|
1394
1631
|
}
|
|
1395
1632
|
|
|
@@ -1400,6 +1637,7 @@ async function readCloudAuth(): Promise<CloudAuthConfig | null> {
|
|
|
1400
1637
|
return {
|
|
1401
1638
|
apiUrl: typeof parsed.apiUrl === "string" ? parsed.apiUrl : defaultAgentKitCloudApiUrl,
|
|
1402
1639
|
token: parsed.token,
|
|
1640
|
+
source: "file",
|
|
1403
1641
|
};
|
|
1404
1642
|
}
|
|
1405
1643
|
} catch {
|
|
@@ -1414,6 +1652,87 @@ async function writeCloudAuth(config: CloudAuthConfig): Promise<void> {
|
|
|
1414
1652
|
await writeFile(cloudAuthPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
1415
1653
|
}
|
|
1416
1654
|
|
|
1655
|
+
async function ensureDeployChatAccessToken(options: {
|
|
1656
|
+
apiUrl: string;
|
|
1657
|
+
root: string;
|
|
1658
|
+
deployId: string;
|
|
1659
|
+
skip: boolean;
|
|
1660
|
+
}): Promise<
|
|
1661
|
+
| { status: "ready"; path: string; tokenId: string; created: boolean }
|
|
1662
|
+
| { status: "skipped" }
|
|
1663
|
+
| { status: "failed"; error: unknown }
|
|
1664
|
+
> {
|
|
1665
|
+
if (options.skip) {
|
|
1666
|
+
return { status: "skipped" };
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
try {
|
|
1670
|
+
const path = join(options.root, defaultDeployChatAccessTokenPath);
|
|
1671
|
+
const existing = await readDeployAccessTokenFile(path);
|
|
1672
|
+
|
|
1673
|
+
if (existing?.deploy_id === options.deployId && existing.id) {
|
|
1674
|
+
const listed = (await cloudApiRequest(
|
|
1675
|
+
options.apiUrl,
|
|
1676
|
+
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
1677
|
+
{ method: "GET" },
|
|
1678
|
+
)) as DeployAccessTokenListResponse;
|
|
1679
|
+
const stillExists = (listed.access_tokens ?? []).some((token) => token.id === existing.id);
|
|
1680
|
+
|
|
1681
|
+
if (stillExists) {
|
|
1682
|
+
return { status: "ready", path, tokenId: existing.id, created: false };
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
const created = (await cloudApiRequest(
|
|
1687
|
+
options.apiUrl,
|
|
1688
|
+
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
1689
|
+
{
|
|
1690
|
+
method: "POST",
|
|
1691
|
+
body: JSON.stringify({ name: defaultDeployChatAccessTokenName }),
|
|
1692
|
+
},
|
|
1693
|
+
)) as DeployAccessTokenCreateResponse;
|
|
1694
|
+
const token = created.access_token;
|
|
1695
|
+
|
|
1696
|
+
if (!token?.id || token.deploy_id !== options.deployId || !token.token) {
|
|
1697
|
+
throw new AgentKitError("access_token_invalid", "AgentKit Cloud returned an invalid deploy access token payload.");
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
await writeSecretJson(path, created);
|
|
1701
|
+
return { status: "ready", path, tokenId: token.id, created: true };
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
return { status: "failed", error };
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
async function readDeployAccessTokenFile(path: string): Promise<{ id?: string; deploy_id?: string } | null> {
|
|
1708
|
+
try {
|
|
1709
|
+
const parsed = JSON.parse(await readFile(path, "utf8")) as DeployAccessTokenCreateResponse;
|
|
1710
|
+
return parsed.access_token ?? null;
|
|
1711
|
+
} catch {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
async function writeSecretJson(path: string, value: unknown): Promise<void> {
|
|
1717
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1718
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
async function verifyCloudLogin(
|
|
1722
|
+
apiUrl: string,
|
|
1723
|
+
token: string,
|
|
1724
|
+
): Promise<{ me: CloudMeResponse; capabilities: CloudCapabilitiesResponse }> {
|
|
1725
|
+
const me = await cloudApiRequestWithToken<CloudMeResponse>(apiUrl, "/v1/me", token, { method: "GET" });
|
|
1726
|
+
const capabilities = await cloudApiRequestWithToken<CloudCapabilitiesResponse>(
|
|
1727
|
+
apiUrl,
|
|
1728
|
+
"/v1/me/capabilities",
|
|
1729
|
+
token,
|
|
1730
|
+
{ method: "GET" },
|
|
1731
|
+
);
|
|
1732
|
+
|
|
1733
|
+
return { me, capabilities };
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1417
1736
|
async function clearCloudAuth(): Promise<void> {
|
|
1418
1737
|
await rm(cloudAuthPath(), { force: true });
|
|
1419
1738
|
}
|
|
@@ -1472,16 +1791,66 @@ async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit):
|
|
|
1472
1791
|
payload && typeof payload === "object" && "error" in payload
|
|
1473
1792
|
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
1474
1793
|
: "cloud_request_failed";
|
|
1475
|
-
|
|
1794
|
+
let message = readCloudError(payload, response.status);
|
|
1795
|
+
|
|
1796
|
+
if (code === "auth_token_invalid") {
|
|
1797
|
+
message = `${message} Token source: ${auth ? formatCloudAuthSource(auth) : "none"}. Run: agentkit login --token <token>`;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
throw new AgentKitError(code, message);
|
|
1476
1801
|
}
|
|
1477
1802
|
|
|
1478
1803
|
return payload;
|
|
1479
1804
|
}
|
|
1480
1805
|
|
|
1806
|
+
async function cloudApiRequestWithToken<T>(
|
|
1807
|
+
apiUrl: string,
|
|
1808
|
+
path: string,
|
|
1809
|
+
token: string,
|
|
1810
|
+
init: RequestInit,
|
|
1811
|
+
): Promise<T> {
|
|
1812
|
+
const headers = new Headers(init.headers);
|
|
1813
|
+
|
|
1814
|
+
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
1815
|
+
headers.set("Content-Type", "application/json");
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
1819
|
+
|
|
1820
|
+
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
1821
|
+
...init,
|
|
1822
|
+
headers,
|
|
1823
|
+
});
|
|
1824
|
+
const payload = await response.json().catch(() => null);
|
|
1825
|
+
|
|
1826
|
+
if (!response.ok) {
|
|
1827
|
+
const code =
|
|
1828
|
+
payload && typeof payload === "object" && "error" in payload
|
|
1829
|
+
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
1830
|
+
: "cloud_request_failed";
|
|
1831
|
+
throw new AgentKitError(code, readCloudError(payload, response.status));
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
return payload as T;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1481
1837
|
function printJson(value: unknown): void {
|
|
1482
1838
|
console.log(JSON.stringify(value, null, 2));
|
|
1483
1839
|
}
|
|
1484
1840
|
|
|
1841
|
+
async function printJsonOrWrite(value: unknown, outFlag: string | boolean | undefined, usage: string): Promise<void> {
|
|
1842
|
+
const outPath = parseOptionalStringFlag(outFlag, "out", usage);
|
|
1843
|
+
|
|
1844
|
+
if (!outPath) {
|
|
1845
|
+
printJson(value);
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
const absolutePath = resolve(process.cwd(), outPath);
|
|
1850
|
+
await writeSecretJson(absolutePath, value);
|
|
1851
|
+
console.log(`Deploy access token written to ${absolutePath}`);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1485
1854
|
function readCloudError(payload: unknown, status: number): string {
|
|
1486
1855
|
if (payload && typeof payload === "object" && "error" in payload) {
|
|
1487
1856
|
const error = (payload as { error?: { message?: unknown } }).error;
|
|
@@ -1493,19 +1862,36 @@ function readCloudError(payload: unknown, status: number): string {
|
|
|
1493
1862
|
return `AgentKit Cloud request failed with HTTP ${status}.`;
|
|
1494
1863
|
}
|
|
1495
1864
|
|
|
1496
|
-
function
|
|
1865
|
+
function formatCloudAuthSource(auth: CloudAuthConfig): string {
|
|
1866
|
+
if (auth.source === "environment") {
|
|
1867
|
+
return "AGENTKIT_CLOUD_API_TOKEN";
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
if (auth.source === "file") {
|
|
1871
|
+
return cloudAuthPath();
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
return "AgentKit Cloud auth";
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function relativePath(root: string, path: string): string {
|
|
1878
|
+
const output = relative(root, path);
|
|
1879
|
+
return output.startsWith("..") ? path : output || ".";
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function parsePort(value: string | boolean | undefined, usage = "agentkit dev --port <number>"): number | undefined {
|
|
1497
1883
|
if (value === undefined) {
|
|
1498
1884
|
return undefined;
|
|
1499
1885
|
}
|
|
1500
1886
|
|
|
1501
1887
|
if (typeof value !== "string") {
|
|
1502
|
-
throw new Error(
|
|
1888
|
+
throw new Error(`Missing port. Usage: ${usage}`);
|
|
1503
1889
|
}
|
|
1504
1890
|
|
|
1505
1891
|
const port = Number(value);
|
|
1506
1892
|
|
|
1507
1893
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
1508
|
-
throw new Error(
|
|
1894
|
+
throw new Error(`Invalid port. Usage: ${usage}`);
|
|
1509
1895
|
}
|
|
1510
1896
|
|
|
1511
1897
|
return port;
|
|
@@ -1557,6 +1943,90 @@ function parseOptionalStringFlag(value: string | boolean | undefined, name: stri
|
|
|
1557
1943
|
return value.trim();
|
|
1558
1944
|
}
|
|
1559
1945
|
|
|
1946
|
+
async function resolveSecretInputValue(options: {
|
|
1947
|
+
name: string | undefined;
|
|
1948
|
+
valueParts: string[];
|
|
1949
|
+
stdinFlag: string | boolean | undefined;
|
|
1950
|
+
fromEnvFlag: string | boolean | undefined;
|
|
1951
|
+
fromLocalEnvFlag: string | boolean | undefined;
|
|
1952
|
+
localEnvRoot?: string;
|
|
1953
|
+
usage: string;
|
|
1954
|
+
}): Promise<string> {
|
|
1955
|
+
const { name, valueParts, stdinFlag, fromEnvFlag, fromLocalEnvFlag, localEnvRoot, usage } = options;
|
|
1956
|
+
|
|
1957
|
+
if (!name) {
|
|
1958
|
+
throw new Error(`Missing name. Usage: ${usage}`);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
const hasInlineValue = valueParts.length > 0;
|
|
1962
|
+
const hasStdin = stdinFlag !== undefined;
|
|
1963
|
+
const hasFromEnv = fromEnvFlag !== undefined;
|
|
1964
|
+
const hasFromLocalEnv = fromLocalEnvFlag !== undefined;
|
|
1965
|
+
const inputModes = [hasInlineValue, hasStdin, hasFromEnv, hasFromLocalEnv].filter(Boolean).length;
|
|
1966
|
+
|
|
1967
|
+
if (inputModes !== 1) {
|
|
1968
|
+
throw new Error(`Provide exactly one value source. Usage: ${usage}`);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
let value: string;
|
|
1972
|
+
|
|
1973
|
+
if (hasInlineValue) {
|
|
1974
|
+
value = valueParts.join(" ");
|
|
1975
|
+
} else if (hasStdin) {
|
|
1976
|
+
if (stdinFlag !== true) {
|
|
1977
|
+
throw new Error(`--stdin does not take a value. Usage: ${usage}`);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
value = stripOneTrailingNewline(await readStdinText());
|
|
1981
|
+
} else if (hasFromEnv) {
|
|
1982
|
+
const envName = fromEnvFlag === true ? name : parseRequiredStringFlag(fromEnvFlag, "env name", usage);
|
|
1983
|
+
const envValue = process.env[envName];
|
|
1984
|
+
|
|
1985
|
+
if (envValue === undefined) {
|
|
1986
|
+
throw new AgentKitError("secret_not_found", `Environment variable ${envName} is not set.`);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
value = envValue;
|
|
1990
|
+
} else {
|
|
1991
|
+
if (fromLocalEnvFlag !== true) {
|
|
1992
|
+
throw new Error(`--from-local-env does not take a value. Usage: ${usage}`);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
if (!localEnvRoot) {
|
|
1996
|
+
throw new Error(`--from-local-env is not available for this command. Usage: ${usage}`);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
const localEnv = await loadCapsuleEnv(localEnvRoot, {});
|
|
2000
|
+
const localValue = localEnv[name];
|
|
2001
|
+
|
|
2002
|
+
if (localValue === undefined) {
|
|
2003
|
+
throw new AgentKitError("secret_not_found", `Local .env does not contain ${name}.`);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
value = localValue;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
if (value.length === 0) {
|
|
2010
|
+
throw new Error(`Secret value cannot be empty. Usage: ${usage}`);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
return value;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
async function readStdinText(): Promise<string> {
|
|
2017
|
+
const chunks: Buffer[] = [];
|
|
2018
|
+
|
|
2019
|
+
for await (const chunk of process.stdin) {
|
|
2020
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function stripOneTrailingNewline(value: string): string {
|
|
2027
|
+
return value.replace(/\r?\n$/, "");
|
|
2028
|
+
}
|
|
2029
|
+
|
|
1560
2030
|
function printBuildResult(result: Awaited<ReturnType<typeof buildAgentCapsule>>): void {
|
|
1561
2031
|
console.log("AgentKit build complete");
|
|
1562
2032
|
console.log("");
|
|
@@ -1614,6 +2084,14 @@ async function deployCloudflare(wranglerPath: string, buildDir: string): Promise
|
|
|
1614
2084
|
}
|
|
1615
2085
|
}
|
|
1616
2086
|
|
|
2087
|
+
async function installProjectDependencies(projectPath: string): Promise<void> {
|
|
2088
|
+
const exitCode = await runInheritedCommand("npm", ["install"], projectPath);
|
|
2089
|
+
|
|
2090
|
+
if (exitCode !== 0) {
|
|
2091
|
+
throw new Error(`Dependency install failed with exit code ${exitCode}. Run \`npm install\` in ${projectPath}.`);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
1617
2095
|
function runInheritedCommand(command: string, args: string[], cwd: string): Promise<number> {
|
|
1618
2096
|
return new Promise((resolvePromise, reject) => {
|
|
1619
2097
|
const child = spawn(command, args, {
|
|
@@ -1669,6 +2147,378 @@ function parseJsonInput(source: string, label: string): unknown {
|
|
|
1669
2147
|
}
|
|
1670
2148
|
}
|
|
1671
2149
|
|
|
2150
|
+
async function startDeployChatUiServer(
|
|
2151
|
+
cwd: string,
|
|
2152
|
+
options: { port: number; tokenFile?: string },
|
|
2153
|
+
): Promise<{ url: string; deployUrl: string; tokenSource: string; stop(): Promise<void> }> {
|
|
2154
|
+
const root = await findAgentCapsuleRoot(cwd);
|
|
2155
|
+
const state = await readLocalDeployStateIfExists(root);
|
|
2156
|
+
|
|
2157
|
+
if (!state?.url) {
|
|
2158
|
+
throw new Error("No hosted deploy URL found. Run agentkit deploy first.");
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
const tokenPath = options.tokenFile
|
|
2162
|
+
? resolve(root, options.tokenFile)
|
|
2163
|
+
: join(root, defaultDeployChatAccessTokenPath);
|
|
2164
|
+
const hostname = "localhost";
|
|
2165
|
+
const deployUrl = state.url;
|
|
2166
|
+
const server = createServer(async (request, response) => {
|
|
2167
|
+
try {
|
|
2168
|
+
const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? `${hostname}:${options.port}`}`);
|
|
2169
|
+
|
|
2170
|
+
if (request.method === "GET" && requestUrl.pathname === "/") {
|
|
2171
|
+
sendHtml(response, renderDeployChatUi({ deployUrl }));
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
if (request.method === "GET" && requestUrl.pathname === "/health") {
|
|
2176
|
+
sendJson(response, 200, { status: "ok", target: "deploy-chat-ui", deployUrl });
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
if (request.method === "GET" && requestUrl.pathname === "/_agentkit") {
|
|
2181
|
+
sendJson(response, 200, {
|
|
2182
|
+
deployUrl,
|
|
2183
|
+
access: "proxied",
|
|
2184
|
+
tokenSource: process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN ? "AGENTKIT_DEPLOY_ACCESS_TOKEN" : relativePath(root, tokenPath),
|
|
2185
|
+
});
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
if (request.method === "POST" && (requestUrl.pathname === "/v1/chat" || requestUrl.pathname === "/chat")) {
|
|
2190
|
+
const token = await readDeployAccessTokenForUi(tokenPath);
|
|
2191
|
+
const body = await readIncomingBody(request);
|
|
2192
|
+
const contentTypeHeader = request.headers["content-type"];
|
|
2193
|
+
const contentType = Array.isArray(contentTypeHeader)
|
|
2194
|
+
? contentTypeHeader[0] ?? "application/json"
|
|
2195
|
+
: contentTypeHeader ?? "application/json";
|
|
2196
|
+
const upstream = await fetch(new URL("/v1/chat", deployUrl).href, {
|
|
2197
|
+
method: "POST",
|
|
2198
|
+
headers: {
|
|
2199
|
+
"Authorization": `Bearer ${token}`,
|
|
2200
|
+
"Content-Type": contentType,
|
|
2201
|
+
},
|
|
2202
|
+
body,
|
|
2203
|
+
});
|
|
2204
|
+
const text = await upstream.text();
|
|
2205
|
+
|
|
2206
|
+
response.writeHead(upstream.status, {
|
|
2207
|
+
"content-type": upstream.headers.get("content-type") ?? "application/json",
|
|
2208
|
+
});
|
|
2209
|
+
response.end(text);
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
sendJson(response, 404, { error: { code: "not_found", message: "Route not found." } });
|
|
2214
|
+
} catch (error) {
|
|
2215
|
+
sendJson(response, 500, {
|
|
2216
|
+
error: {
|
|
2217
|
+
code: isAgentKitError(error) ? error.code : "deploy_chat_ui_error",
|
|
2218
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2219
|
+
},
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
});
|
|
2223
|
+
|
|
2224
|
+
await new Promise<void>((resolvePromise, reject) => {
|
|
2225
|
+
server.once("error", reject);
|
|
2226
|
+
server.listen(options.port, hostname, () => {
|
|
2227
|
+
server.off("error", reject);
|
|
2228
|
+
resolvePromise();
|
|
2229
|
+
});
|
|
2230
|
+
});
|
|
2231
|
+
|
|
2232
|
+
return {
|
|
2233
|
+
url: `http://${hostname}:${options.port}`,
|
|
2234
|
+
deployUrl,
|
|
2235
|
+
tokenSource: process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN ? "AGENTKIT_DEPLOY_ACCESS_TOKEN" : relativePath(root, tokenPath),
|
|
2236
|
+
stop: () =>
|
|
2237
|
+
new Promise<void>((resolvePromise, reject) => {
|
|
2238
|
+
server.close((error) => (error ? reject(error) : resolvePromise()));
|
|
2239
|
+
}),
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
async function readDeployAccessTokenForUi(tokenPath: string): Promise<string> {
|
|
2244
|
+
if (process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN) {
|
|
2245
|
+
return process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
let payload: unknown;
|
|
2249
|
+
|
|
2250
|
+
try {
|
|
2251
|
+
payload = JSON.parse(await readFile(tokenPath, "utf8"));
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
throw new AgentKitError(
|
|
2254
|
+
"deploy_access_token_missing",
|
|
2255
|
+
`Could not read a clean deploy access token file at ${tokenPath}. Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`,
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
const token =
|
|
2260
|
+
payload && typeof payload === "object"
|
|
2261
|
+
? (payload as { access_token?: { token?: unknown }; token?: unknown; accessToken?: unknown }).access_token?.token ??
|
|
2262
|
+
(payload as { token?: unknown }).token ??
|
|
2263
|
+
(payload as { accessToken?: unknown }).accessToken
|
|
2264
|
+
: undefined;
|
|
2265
|
+
|
|
2266
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
2267
|
+
throw new AgentKitError(
|
|
2268
|
+
"deploy_access_token_invalid",
|
|
2269
|
+
`Deploy access token file ${tokenPath} does not contain access_token.token. Recreate it with: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`,
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
return token.trim();
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
function readIncomingBody(request: IncomingMessage): Promise<string> {
|
|
2277
|
+
return new Promise((resolvePromise, reject) => {
|
|
2278
|
+
let body = "";
|
|
2279
|
+
request.setEncoding("utf8");
|
|
2280
|
+
request.on("data", (chunk) => {
|
|
2281
|
+
body += chunk;
|
|
2282
|
+
});
|
|
2283
|
+
request.on("end", () => resolvePromise(body));
|
|
2284
|
+
request.on("error", reject);
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function sendJson(response: ServerResponse, status: number, body: unknown): void {
|
|
2289
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
2290
|
+
response.end(JSON.stringify(body, null, 2));
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function sendHtml(response: ServerResponse, body: string): void {
|
|
2294
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2295
|
+
response.end(body);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
2299
|
+
const deployUrl = escapeHtml(input.deployUrl);
|
|
2300
|
+
|
|
2301
|
+
return `<!doctype html>
|
|
2302
|
+
<html lang="en">
|
|
2303
|
+
<head>
|
|
2304
|
+
<meta charset="utf-8">
|
|
2305
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2306
|
+
<title>AgentKit Hosted Chat</title>
|
|
2307
|
+
<style>
|
|
2308
|
+
:root {
|
|
2309
|
+
color-scheme: light;
|
|
2310
|
+
--bg: #f6f7f5;
|
|
2311
|
+
--panel: #ffffff;
|
|
2312
|
+
--ink: #182022;
|
|
2313
|
+
--muted: #667175;
|
|
2314
|
+
--line: #d9ded9;
|
|
2315
|
+
--accent: #176b5d;
|
|
2316
|
+
--danger: #9f2d2d;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
* { box-sizing: border-box; }
|
|
2320
|
+
|
|
2321
|
+
body {
|
|
2322
|
+
margin: 0;
|
|
2323
|
+
min-height: 100svh;
|
|
2324
|
+
background: var(--bg);
|
|
2325
|
+
color: var(--ink);
|
|
2326
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
main {
|
|
2330
|
+
min-height: 100svh;
|
|
2331
|
+
display: grid;
|
|
2332
|
+
grid-template-rows: auto 1fr auto;
|
|
2333
|
+
background: var(--panel);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
header {
|
|
2337
|
+
border-bottom: 1px solid var(--line);
|
|
2338
|
+
padding: 18px 22px;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
h1 {
|
|
2342
|
+
margin: 0 0 4px;
|
|
2343
|
+
font-size: 20px;
|
|
2344
|
+
line-height: 1.2;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
code {
|
|
2348
|
+
color: var(--muted);
|
|
2349
|
+
font-size: 12px;
|
|
2350
|
+
overflow-wrap: anywhere;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
#messages {
|
|
2354
|
+
padding: 22px;
|
|
2355
|
+
overflow: auto;
|
|
2356
|
+
display: flex;
|
|
2357
|
+
flex-direction: column;
|
|
2358
|
+
gap: 12px;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
.message {
|
|
2362
|
+
max-width: 780px;
|
|
2363
|
+
display: grid;
|
|
2364
|
+
gap: 4px;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.message.user {
|
|
2368
|
+
align-self: end;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
.role {
|
|
2372
|
+
color: var(--muted);
|
|
2373
|
+
font-size: 12px;
|
|
2374
|
+
font-weight: 700;
|
|
2375
|
+
text-transform: uppercase;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
.bubble {
|
|
2379
|
+
border: 1px solid var(--line);
|
|
2380
|
+
border-radius: 8px;
|
|
2381
|
+
padding: 12px 14px;
|
|
2382
|
+
white-space: pre-wrap;
|
|
2383
|
+
line-height: 1.45;
|
|
2384
|
+
background: #fbfbfa;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
.user .bubble {
|
|
2388
|
+
border-color: #b8d8ce;
|
|
2389
|
+
background: #e8f5f0;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
.error .bubble {
|
|
2393
|
+
border-color: #e1b1b1;
|
|
2394
|
+
color: var(--danger);
|
|
2395
|
+
background: #fff6f6;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
form {
|
|
2399
|
+
border-top: 1px solid var(--line);
|
|
2400
|
+
padding: 14px;
|
|
2401
|
+
display: grid;
|
|
2402
|
+
grid-template-columns: 1fr auto;
|
|
2403
|
+
gap: 10px;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
textarea {
|
|
2407
|
+
min-height: 52px;
|
|
2408
|
+
max-height: 180px;
|
|
2409
|
+
resize: vertical;
|
|
2410
|
+
border: 1px solid var(--line);
|
|
2411
|
+
border-radius: 8px;
|
|
2412
|
+
padding: 12px;
|
|
2413
|
+
color: var(--ink);
|
|
2414
|
+
font: inherit;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
button {
|
|
2418
|
+
min-width: 96px;
|
|
2419
|
+
border: 0;
|
|
2420
|
+
border-radius: 8px;
|
|
2421
|
+
padding: 0 18px;
|
|
2422
|
+
color: white;
|
|
2423
|
+
background: var(--accent);
|
|
2424
|
+
font: inherit;
|
|
2425
|
+
font-weight: 700;
|
|
2426
|
+
cursor: pointer;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
button:disabled {
|
|
2430
|
+
cursor: wait;
|
|
2431
|
+
opacity: 0.65;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
@media (max-width: 640px) {
|
|
2435
|
+
form {
|
|
2436
|
+
grid-template-columns: 1fr;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
button {
|
|
2440
|
+
min-height: 44px;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
</style>
|
|
2444
|
+
</head>
|
|
2445
|
+
<body>
|
|
2446
|
+
<main>
|
|
2447
|
+
<header>
|
|
2448
|
+
<h1>AgentKit Hosted Chat</h1>
|
|
2449
|
+
<code>${deployUrl}</code>
|
|
2450
|
+
</header>
|
|
2451
|
+
<section id="messages" aria-live="polite"></section>
|
|
2452
|
+
<form id="form">
|
|
2453
|
+
<textarea id="message" name="message" placeholder="Type a message" required autofocus></textarea>
|
|
2454
|
+
<button id="send" type="submit">Send</button>
|
|
2455
|
+
</form>
|
|
2456
|
+
</main>
|
|
2457
|
+
<script>
|
|
2458
|
+
const form = document.querySelector("#form");
|
|
2459
|
+
const textarea = document.querySelector("#message");
|
|
2460
|
+
const send = document.querySelector("#send");
|
|
2461
|
+
const messages = document.querySelector("#messages");
|
|
2462
|
+
let conversationId = null;
|
|
2463
|
+
|
|
2464
|
+
function addMessage(role, content, className = "") {
|
|
2465
|
+
const wrapper = document.createElement("article");
|
|
2466
|
+
wrapper.className = ["message", role, className].filter(Boolean).join(" ");
|
|
2467
|
+
const label = document.createElement("div");
|
|
2468
|
+
label.className = "role";
|
|
2469
|
+
label.textContent = role;
|
|
2470
|
+
const bubble = document.createElement("div");
|
|
2471
|
+
bubble.className = "bubble";
|
|
2472
|
+
bubble.textContent = content;
|
|
2473
|
+
wrapper.append(label, bubble);
|
|
2474
|
+
messages.append(wrapper);
|
|
2475
|
+
messages.scrollTop = messages.scrollHeight;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
form.addEventListener("submit", async (event) => {
|
|
2479
|
+
event.preventDefault();
|
|
2480
|
+
const message = textarea.value.trim();
|
|
2481
|
+
if (!message) return;
|
|
2482
|
+
|
|
2483
|
+
addMessage("user", message);
|
|
2484
|
+
textarea.value = "";
|
|
2485
|
+
send.disabled = true;
|
|
2486
|
+
|
|
2487
|
+
try {
|
|
2488
|
+
const response = await fetch("/v1/chat", {
|
|
2489
|
+
method: "POST",
|
|
2490
|
+
headers: { "content-type": "application/json" },
|
|
2491
|
+
body: JSON.stringify({ message, conversationId }),
|
|
2492
|
+
});
|
|
2493
|
+
const payload = await response.json();
|
|
2494
|
+
|
|
2495
|
+
if (!response.ok) {
|
|
2496
|
+
throw new Error(payload.error?.message || "Request failed");
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
conversationId = payload.conversationId || conversationId;
|
|
2500
|
+
addMessage("assistant", payload.message?.content || "");
|
|
2501
|
+
} catch (error) {
|
|
2502
|
+
addMessage("error", error instanceof Error ? error.message : String(error), "error");
|
|
2503
|
+
} finally {
|
|
2504
|
+
send.disabled = false;
|
|
2505
|
+
textarea.focus();
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
</script>
|
|
2509
|
+
</body>
|
|
2510
|
+
</html>`;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
function escapeHtml(value: string): string {
|
|
2514
|
+
return value
|
|
2515
|
+
.replace(/&/g, "&")
|
|
2516
|
+
.replace(/</g, "<")
|
|
2517
|
+
.replace(/>/g, ">")
|
|
2518
|
+
.replace(/"/g, """)
|
|
2519
|
+
.replace(/'/g, "'");
|
|
2520
|
+
}
|
|
2521
|
+
|
|
1672
2522
|
async function waitForShutdown(stop: () => Promise<void>): Promise<void> {
|
|
1673
2523
|
await new Promise<void>((resolve) => {
|
|
1674
2524
|
let stopped = false;
|