@andreprado/agentkit 0.1.0-alpha.3 → 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/README.md +9 -3
- package/docs/guides/add-tool.md +1 -1
- package/docs/guides/channels-implementation-map.md +243 -0
- package/docs/guides/create-agent.md +22 -1
- package/docs/guides/prepare-deploy.md +17 -6
- package/docs/guides/run-evals.md +28 -2
- package/docs/guides/security-rules.md +1 -1
- package/docs/guides/use-provider.md +16 -2
- package/docs/llms-full.txt +67 -36
- package/docs/llms.txt +13 -3
- 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 +295 -1696
- package/src/cli/process.ts +31 -0
- package/src/providers/test.ts +51 -0
- package/src/runtime/chat.ts +59 -4
- package/src/runtime/dev-server.ts +154 -16
- package/src/runtime/runtime-contract.ts +16 -2
- package/src/runtime/targets/cloudflare/build.ts +79 -4
- package/src/storage/sqlite.ts +2 -2
- package/src/templates/blank.ts +41 -0
- package/src/templates/dentista.ts +988 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/support.ts +39 -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,177 +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 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
|
-
|
|
81
|
-
Usage:
|
|
82
|
-
agentkit new <name> [--template blank|support] [--no-install]
|
|
83
|
-
agentkit dev
|
|
84
|
-
agentkit chat-ui --deploy [--port <number>] [--token-file <path>]
|
|
85
|
-
agentkit chat --message <text>
|
|
86
|
-
agentkit tool <name> [--input <path-or-json>]
|
|
87
|
-
agentkit db migrate
|
|
88
|
-
agentkit db reset --yes
|
|
89
|
-
agentkit db shell
|
|
90
|
-
agentkit db seed [--file <path>]
|
|
91
|
-
agentkit eval run
|
|
92
|
-
agentkit conversations list
|
|
93
|
-
agentkit conversations show <conversation-id>
|
|
94
|
-
agentkit channels list
|
|
95
|
-
agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta] [--api <url>]
|
|
96
|
-
agentkit channels setup <name> [--apply] [--api <url>]
|
|
97
|
-
agentkit channels status <name> [--api <url>]
|
|
98
|
-
agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
|
|
99
|
-
agentkit channels deliveries list <name> [--api <url>]
|
|
100
|
-
agentkit channels deliveries show <delivery-id> [--api <url>]
|
|
101
|
-
agentkit inspect
|
|
102
|
-
agentkit build [--target cloudflare]
|
|
103
|
-
agentkit login --token <token> [--api <url>]
|
|
104
|
-
agentkit logout
|
|
105
|
-
agentkit deploy [--target cloudflare|vps] [--host <host>] [--api <url>] [--dry-run] [--anonymous] [--local-wrangler]
|
|
106
|
-
agentkit deploy doctor [--api <url>] [--anonymous]
|
|
107
|
-
agentkit deploy status [--api <url>]
|
|
108
|
-
agentkit deploy pause [--api <url>]
|
|
109
|
-
agentkit deploy resume [--api <url>]
|
|
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>]
|
|
114
|
-
agentkit secret list [--api <url>]
|
|
115
|
-
agentkit secret unset <NAME> [--api <url>]
|
|
116
|
-
agentkit access token create <name> [--api <url>] [--out <path>]
|
|
117
|
-
agentkit access token list [--api <url>]
|
|
118
|
-
agentkit access token revoke <token-id> [--api <url>]
|
|
119
|
-
agentkit env set <NAME> <VALUE>
|
|
120
|
-
agentkit env set <NAME> --stdin
|
|
121
|
-
agentkit env set <NAME> --from-env [ENV_NAME]
|
|
122
|
-
agentkit env list
|
|
123
|
-
agentkit env unset <NAME>
|
|
124
|
-
agentkit docs path
|
|
125
|
-
agentkit docs llms
|
|
126
|
-
agentkit docs full
|
|
127
|
-
agentkit handoff codex [goal]
|
|
128
|
-
agentkit help commands
|
|
129
|
-
agentkit --help
|
|
130
|
-
|
|
131
|
-
Commands:
|
|
132
|
-
new Create a new Agent Capsule
|
|
133
|
-
dev Run the Agent Capsule locally
|
|
134
|
-
chat-ui Run a local chat UI, optionally pointed at a hosted deploy
|
|
135
|
-
chat Send one message to the local Agent Capsule
|
|
136
|
-
tool Run one registered tool directly
|
|
137
|
-
db Manage the local SQLite database
|
|
138
|
-
eval Run local eval files
|
|
139
|
-
conversations List and inspect stored local conversations
|
|
140
|
-
channels Manage hosted channel resources
|
|
141
|
-
inspect Print machine-readable Agent Capsule state
|
|
142
|
-
build Build a hosted deploy artifact
|
|
143
|
-
login Store an AgentKit Cloud alpha token outside the capsule
|
|
144
|
-
logout Remove the stored AgentKit Cloud alpha token
|
|
145
|
-
deploy Build and send a hosted artifact to AgentKit Cloud
|
|
146
|
-
secret Manage hosted AgentKit Cloud secrets without readback
|
|
147
|
-
access Manage hosted deploy access tokens
|
|
148
|
-
env Manage local .env values without opening code
|
|
149
|
-
docs Print AgentKit docs locations
|
|
150
|
-
handoff Print a ready-to-paste prompt for another coding agent
|
|
151
|
-
help Print focused help or the full command reference
|
|
152
|
-
|
|
153
|
-
Examples:
|
|
154
|
-
agentkit new support-agent --template support
|
|
155
|
-
cd support-agent
|
|
156
|
-
agentkit dev
|
|
157
|
-
agentkit tool lookup_order --input '{"orderId":"A100"}'
|
|
158
|
-
agentkit eval run
|
|
159
|
-
`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
type ParsedArgs = {
|
|
163
|
-
command?: string;
|
|
164
|
-
positional: string[];
|
|
165
|
-
flags: Record<string, string | boolean>;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
function parseArgs(argv: string[]): ParsedArgs {
|
|
169
|
-
const [command, ...rest] = argv;
|
|
170
|
-
const positional: string[] = [];
|
|
171
|
-
const flags: Record<string, string | boolean> = {};
|
|
172
|
-
|
|
173
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
174
|
-
const value = rest[index];
|
|
175
|
-
|
|
176
|
-
if (!value.startsWith("--")) {
|
|
177
|
-
positional.push(value);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const name = value.slice(2);
|
|
182
|
-
const next = rest[index + 1];
|
|
183
|
-
|
|
184
|
-
if (next && !next.startsWith("--")) {
|
|
185
|
-
flags[name] = next;
|
|
186
|
-
index += 1;
|
|
187
|
-
} else {
|
|
188
|
-
flags[name] = true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return { command, positional, flags };
|
|
193
|
-
}
|
|
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";
|
|
194
64
|
|
|
195
65
|
async function main() {
|
|
196
66
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -200,6 +70,11 @@ async function main() {
|
|
|
200
70
|
return;
|
|
201
71
|
}
|
|
202
72
|
|
|
73
|
+
if (isHelpRequested(args)) {
|
|
74
|
+
console.log(renderCommandHelp(args.command));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
203
78
|
if (args.command === "help") {
|
|
204
79
|
const [topic] = args.positional;
|
|
205
80
|
|
|
@@ -222,7 +97,7 @@ async function main() {
|
|
|
222
97
|
const shouldInstall = args.flags["no-install"] !== true;
|
|
223
98
|
|
|
224
99
|
if (!name) {
|
|
225
|
-
throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support] [--no-install]");
|
|
100
|
+
throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support|dentista] [--no-install]");
|
|
226
101
|
}
|
|
227
102
|
|
|
228
103
|
const result = await createProject({ name, template, cwd: process.cwd() });
|
|
@@ -254,10 +129,17 @@ async function main() {
|
|
|
254
129
|
|
|
255
130
|
if (args.command === "dev") {
|
|
256
131
|
const port = parsePort(args.flags.port, "agentkit dev --port <number>");
|
|
257
|
-
const devServer = await startAgentDevServer(process.cwd(), { port });
|
|
132
|
+
const devServer = await startAgentDevServer(process.cwd(), { port, basePort: localDevBasePort() });
|
|
258
133
|
|
|
259
134
|
console.log("Agent Capsule running");
|
|
260
135
|
console.log("");
|
|
136
|
+
if (devServer.portConflict) {
|
|
137
|
+
const owner = devServer.portConflict.agent ? ` by ${devServer.portConflict.agent}` : "";
|
|
138
|
+
console.log(
|
|
139
|
+
`Port ${devServer.portConflict.port} is already used${owner}. Started ${devServer.capsule.config.name} at ${devServer.urls.chat}.`,
|
|
140
|
+
);
|
|
141
|
+
console.log("");
|
|
142
|
+
}
|
|
261
143
|
console.log(`Chat: ${devServer.urls.chat}`);
|
|
262
144
|
console.log(`API: ${devServer.urls.api}`);
|
|
263
145
|
console.log(`Inspect: ${devServer.urls.inspect}`);
|
|
@@ -266,6 +148,20 @@ async function main() {
|
|
|
266
148
|
return;
|
|
267
149
|
}
|
|
268
150
|
|
|
151
|
+
if (args.command === "open") {
|
|
152
|
+
const server = await findRunningLocalDevServer(process.cwd());
|
|
153
|
+
|
|
154
|
+
if (!server) {
|
|
155
|
+
console.log("No running AgentKit dev server found for this capsule.");
|
|
156
|
+
console.log("Run: npm run dev");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await openLocalUrl(server.url);
|
|
161
|
+
console.log(`Opened ${server.url}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
269
165
|
if (args.command === "chat-ui") {
|
|
270
166
|
if (!args.flags.deploy) {
|
|
271
167
|
throw new Error("Usage: agentkit chat-ui --deploy [--port <number>] [--token-file <path>]");
|
|
@@ -290,12 +186,17 @@ async function main() {
|
|
|
290
186
|
|
|
291
187
|
if (args.command === "chat") {
|
|
292
188
|
const message = args.flags.message;
|
|
189
|
+
const conversationId = parseOptionalStringFlag(
|
|
190
|
+
args.flags["conversation-id"] ?? args.flags.conversation,
|
|
191
|
+
"conversation id",
|
|
192
|
+
'agentkit chat --message "hello" [--conversation-id <id>]',
|
|
193
|
+
);
|
|
293
194
|
|
|
294
195
|
if (typeof message !== "string") {
|
|
295
|
-
throw new Error('Missing message. Usage: agentkit chat --message "hello"');
|
|
196
|
+
throw new Error('Missing message. Usage: agentkit chat --message "hello" [--conversation-id <id>]');
|
|
296
197
|
}
|
|
297
198
|
|
|
298
|
-
const result = await runAgentMessageFromCwd(process.cwd(), { message });
|
|
199
|
+
const result = await runAgentMessageFromCwd(process.cwd(), { message, conversationId });
|
|
299
200
|
console.log(result.message.content);
|
|
300
201
|
return;
|
|
301
202
|
}
|
|
@@ -485,6 +386,20 @@ async function main() {
|
|
|
485
386
|
if (args.command === "deploy") {
|
|
486
387
|
const [deploySubcommand] = args.positional;
|
|
487
388
|
|
|
389
|
+
if (deploySubcommand === "smoke") {
|
|
390
|
+
const message =
|
|
391
|
+
parseOptionalStringFlag(args.flags.message ?? args.flags.smoke, "message", 'agentkit deploy smoke --message "hello"') ??
|
|
392
|
+
"hello";
|
|
393
|
+
const state = await requireLocalDeployState();
|
|
394
|
+
const token = await readDeployAccessTokenForSmoke(process.cwd(), state);
|
|
395
|
+
await runDeploySmoke({
|
|
396
|
+
deployUrl: requireDeployUrl(state),
|
|
397
|
+
message,
|
|
398
|
+
token,
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
488
403
|
if (deploySubcommand === "doctor") {
|
|
489
404
|
const report = await checkDeployReadiness({
|
|
490
405
|
apiUrl: await resolveCloudApiUrl(args.flags.api),
|
|
@@ -520,6 +435,7 @@ async function main() {
|
|
|
520
435
|
|
|
521
436
|
const target = resolveDeployTarget(parseTargetFlag(args.flags.target, "deploy"));
|
|
522
437
|
const anonymous = Boolean(args.flags.anonymous);
|
|
438
|
+
const smokeMessage = parseOptionalStringFlag(args.flags.smoke, "smoke", 'agentkit deploy --smoke "hello"');
|
|
523
439
|
|
|
524
440
|
if (target.id === "vps") {
|
|
525
441
|
const host = parseRequiredStringFlag(args.flags.host, "host", "agentkit deploy --target vps --host <host>");
|
|
@@ -592,6 +508,24 @@ async function main() {
|
|
|
592
508
|
console.log(`Chat auth: could not create local chat token. ${formatReadinessError(chatAccess.error)}`);
|
|
593
509
|
console.log(`Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`);
|
|
594
510
|
}
|
|
511
|
+
|
|
512
|
+
if (smokeMessage) {
|
|
513
|
+
if (chatAccess?.status === "failed") {
|
|
514
|
+
throw new AgentKitError("deploy_smoke_failed", "Could not run deploy smoke because chat access token creation failed.");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
await runDeploySmoke({
|
|
518
|
+
deployUrl: requireDeployUrl({
|
|
519
|
+
deploy_id: deploy.deploy.id,
|
|
520
|
+
url: deploy.deploy.url,
|
|
521
|
+
}),
|
|
522
|
+
message: smokeMessage,
|
|
523
|
+
token:
|
|
524
|
+
chatAccess?.status === "ready"
|
|
525
|
+
? await readDeployAccessTokenForUi(chatAccess.path)
|
|
526
|
+
: undefined,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
595
529
|
return;
|
|
596
530
|
}
|
|
597
531
|
|
|
@@ -617,6 +551,7 @@ async function main() {
|
|
|
617
551
|
fromLocalEnvFlag: args.flags["from-local-env"],
|
|
618
552
|
localEnvRoot: args.flags["from-local-env"] !== undefined ? await findAgentCapsuleRoot(process.cwd()) : undefined,
|
|
619
553
|
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>]",
|
|
554
|
+
inlineSensitiveHelp: "Prefer --stdin, --from-env, or --from-local-env so the value does not enter shell history.",
|
|
620
555
|
});
|
|
621
556
|
|
|
622
557
|
if (!name) {
|
|
@@ -631,6 +566,15 @@ async function main() {
|
|
|
631
566
|
return;
|
|
632
567
|
}
|
|
633
568
|
|
|
569
|
+
if (subcommand === "sync") {
|
|
570
|
+
if (args.flags["from-local"] !== true) {
|
|
571
|
+
throw new Error("Usage: agentkit secret sync --from-local [--api <url>]");
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
await syncHostedSecretsFromLocal({ apiUrl, projectId });
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
634
578
|
if (subcommand === "unset") {
|
|
635
579
|
if (!name) {
|
|
636
580
|
throw new Error("Usage: agentkit secret unset <NAME> [--api <url>]");
|
|
@@ -643,7 +587,7 @@ async function main() {
|
|
|
643
587
|
return;
|
|
644
588
|
}
|
|
645
589
|
|
|
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>");
|
|
590
|
+
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 sync --from-local | agentkit secret list | agentkit secret unset <NAME>");
|
|
647
591
|
}
|
|
648
592
|
|
|
649
593
|
if (args.command === "access") {
|
|
@@ -718,6 +662,7 @@ async function main() {
|
|
|
718
662
|
fromEnvFlag: args.flags["from-env"],
|
|
719
663
|
fromLocalEnvFlag: undefined,
|
|
720
664
|
usage: "agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME]",
|
|
665
|
+
inlineSensitiveHelp: "Prefer --stdin or --from-env so the value does not enter shell history.",
|
|
721
666
|
});
|
|
722
667
|
|
|
723
668
|
if (!name) {
|
|
@@ -803,9 +748,12 @@ Rules:
|
|
|
803
748
|
- Follow the AgentKit contract in llms-full.txt.
|
|
804
749
|
- Treat the owner's request as the brief.
|
|
805
750
|
- Build or update the agent inside this capsule as far as you can without asking for obvious restatements.
|
|
751
|
+
- Do not wait for a wizard or recipe; implement directly in the capsule files.
|
|
806
752
|
- Edit agent behavior in prompts/instructions.md.
|
|
807
753
|
- Edit the agent contract in agentkit.config.ts.
|
|
808
754
|
- Add TypeScript tools under tools/.
|
|
755
|
+
- Keep the first useful version runnable with test/fake unless the owner has already chosen a real provider.
|
|
756
|
+
- Before testing real conversation behavior, ask the owner to choose OpenRouter, OpenAI, Anthropic, or another supported provider. Do not choose for them.
|
|
809
757
|
- Keep local secrets in .env only.
|
|
810
758
|
- Do not commit or print secret values.
|
|
811
759
|
- Do not edit .agentkit/ directly.
|
|
@@ -827,1212 +775,187 @@ If you add a tool, also run a tool smoke test with the fake provider, for exampl
|
|
|
827
775
|
npm run agentkit -- tool tool_name --input '{}'
|
|
828
776
|
\`\`\`
|
|
829
777
|
|
|
778
|
+
UI testing:
|
|
779
|
+
- For local UI testing, run \`npm run dev\`, open the printed \`Chat:\` URL, and tell the owner the exact URL.
|
|
780
|
+
- For hosted UI testing after deploy, run \`npm run agentkit -- chat-ui --deploy\`, open the printed \`Chat:\` URL, and tell the owner it is connected to the hosted deploy.
|
|
781
|
+
- If the provider is still \`test/fake\`, say the UI was tested only with the deterministic fake provider.
|
|
782
|
+
|
|
830
783
|
Final response:
|
|
831
784
|
- Summarize what changed.
|
|
832
785
|
- List files changed.
|
|
833
786
|
- Paste the verification commands and results.
|
|
787
|
+
- State whether testing used \`test/fake\` or a real provider selected by the owner.
|
|
834
788
|
- Call out any missing user details or blocked checks.
|
|
835
789
|
`;
|
|
836
790
|
}
|
|
837
791
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
status: DeployReadinessStatus;
|
|
842
|
-
title: string;
|
|
843
|
-
message: string;
|
|
844
|
-
action?: string;
|
|
845
|
-
};
|
|
792
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
793
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
794
|
+
}
|
|
846
795
|
|
|
847
|
-
|
|
848
|
-
ok: boolean;
|
|
849
|
-
apiUrl: string;
|
|
850
|
-
context: DeployReadinessContext;
|
|
851
|
-
checks: DeployReadinessCheck[];
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
type CloudMeResponse = {
|
|
855
|
-
account?: {
|
|
856
|
-
email?: string;
|
|
857
|
-
status?: string;
|
|
858
|
-
};
|
|
859
|
-
};
|
|
860
|
-
|
|
861
|
-
type CloudCapabilitiesResponse = {
|
|
862
|
-
capabilities?: Array<{
|
|
863
|
-
name?: string;
|
|
864
|
-
status?: string;
|
|
865
|
-
}>;
|
|
866
|
-
};
|
|
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
|
-
|
|
898
|
-
type CloudSecretsResponse = {
|
|
899
|
-
secrets?: Array<{
|
|
900
|
-
name?: string;
|
|
901
|
-
status?: string;
|
|
902
|
-
updated_at?: string;
|
|
903
|
-
}>;
|
|
904
|
-
};
|
|
905
|
-
|
|
906
|
-
async function checkDeployReadiness(options: {
|
|
796
|
+
async function ensureDeployChatAccessToken(options: {
|
|
907
797
|
apiUrl: string;
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
title: "Target runtime",
|
|
919
|
-
message: 'Cloudflare deploy requires runtime: "edge" in agentkit.config.ts.',
|
|
920
|
-
action: "set runtime: \"edge\" before deploying to AgentKit Cloud",
|
|
921
|
-
});
|
|
922
|
-
} else {
|
|
923
|
-
checks.push({
|
|
924
|
-
status: "pass",
|
|
925
|
-
title: "Target runtime",
|
|
926
|
-
message: "Capsule is configured for edge deploy.",
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
if (!auth?.token) {
|
|
931
|
-
checks.push({
|
|
932
|
-
status: options.requireLogin ? "fail" : "warn",
|
|
933
|
-
title: "Login",
|
|
934
|
-
message: options.requireLogin
|
|
935
|
-
? "No AgentKit Cloud token was found for hosted production deploy."
|
|
936
|
-
: "No AgentKit Cloud token was found; deploy will request anonymous mode.",
|
|
937
|
-
action: "agentkit login --token <token>",
|
|
938
|
-
});
|
|
939
|
-
} else {
|
|
940
|
-
try {
|
|
941
|
-
const me = (await cloudApiRequest(options.apiUrl, "/v1/me", { method: "GET" })) as CloudMeResponse;
|
|
942
|
-
const email = me.account?.email;
|
|
943
|
-
checks.push({
|
|
944
|
-
status: "pass",
|
|
945
|
-
title: "Login",
|
|
946
|
-
message: email ? `Authenticated as ${email}.` : "AgentKit Cloud token is accepted.",
|
|
947
|
-
});
|
|
948
|
-
cloudAuthUsable = true;
|
|
949
|
-
} catch (error) {
|
|
950
|
-
checks.push({
|
|
951
|
-
status: "fail",
|
|
952
|
-
title: "Login",
|
|
953
|
-
message: `AgentKit Cloud rejected the token from ${formatCloudAuthSource(auth)}. ${formatReadinessError(error)}`,
|
|
954
|
-
action: "agentkit login --token <token>",
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
if (cloudAuthUsable) {
|
|
960
|
-
try {
|
|
961
|
-
const payload = (await cloudApiRequest(options.apiUrl, "/v1/me/capabilities", {
|
|
962
|
-
method: "GET",
|
|
963
|
-
})) as CloudCapabilitiesResponse;
|
|
964
|
-
const capabilities = payload.capabilities ?? [];
|
|
965
|
-
const hasCloudflareAlpha = capabilities.some(
|
|
966
|
-
(capability) => capability.name === "cloudflare_deploy_alpha" && capability.status === "active",
|
|
967
|
-
);
|
|
968
|
-
|
|
969
|
-
checks.push({
|
|
970
|
-
status: hasCloudflareAlpha ? "pass" : "fail",
|
|
971
|
-
title: "Deploy entitlement",
|
|
972
|
-
message: hasCloudflareAlpha
|
|
973
|
-
? "cloudflare_deploy_alpha is active."
|
|
974
|
-
: "This account is missing the cloudflare_deploy_alpha entitlement.",
|
|
975
|
-
...(hasCloudflareAlpha ? {} : { action: "log in with an invited AgentKit Cloud alpha token" }),
|
|
976
|
-
});
|
|
977
|
-
} catch (error) {
|
|
978
|
-
checks.push({
|
|
979
|
-
status: "fail",
|
|
980
|
-
title: "Deploy entitlement",
|
|
981
|
-
message: `Could not verify AgentKit Cloud capabilities. ${formatReadinessError(error)}`,
|
|
982
|
-
});
|
|
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
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
if (context.agentKitManagedSecrets.length > 0) {
|
|
1039
|
-
checks.push({
|
|
1040
|
-
status: "info",
|
|
1041
|
-
title: "AgentKit-managed secrets",
|
|
1042
|
-
message: `${context.agentKitManagedSecrets.join(", ")} ${
|
|
1043
|
-
context.agentKitManagedSecrets.length === 1 ? "is" : "are"
|
|
1044
|
-
} provisioned by AgentKit Cloud during deploy.`,
|
|
1045
|
-
});
|
|
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" };
|
|
1046
808
|
}
|
|
1047
809
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
title: "Private access",
|
|
1052
|
-
message: "AGENTKIT_PRIVATE_TOKEN is required at runtime; AgentKit Cloud generates it and saves it in .agentkit/deploy.json.",
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
810
|
+
try {
|
|
811
|
+
const path = join(options.root, defaultDeployChatAccessTokenPath);
|
|
812
|
+
const existing = await readDeployAccessTokenFile(path);
|
|
1055
813
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
status: "pass",
|
|
1059
|
-
title: "Hosted secrets",
|
|
1060
|
-
message: "No user-managed hosted secrets are required by this capsule.",
|
|
1061
|
-
});
|
|
1062
|
-
} else if (!cloudAuthUsable) {
|
|
1063
|
-
checks.push({
|
|
1064
|
-
status: options.requireLogin ? "fail" : "warn",
|
|
1065
|
-
title: "Hosted secrets",
|
|
1066
|
-
message: `Could not check hosted secrets without a valid AgentKit Cloud login. Required: ${context.userManagedSecrets.join(", ")}.`,
|
|
1067
|
-
action: "agentkit secret list",
|
|
1068
|
-
});
|
|
1069
|
-
} else {
|
|
1070
|
-
try {
|
|
1071
|
-
const payload = (await cloudApiRequest(
|
|
814
|
+
if (existing?.deploy_id === options.deployId && existing.id) {
|
|
815
|
+
const listed = (await cloudApiRequest(
|
|
1072
816
|
options.apiUrl,
|
|
1073
|
-
`/v1/
|
|
817
|
+
`/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
|
|
1074
818
|
{ method: "GET" },
|
|
1075
|
-
)) as
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
checks.push({
|
|
1081
|
-
status: "pass",
|
|
1082
|
-
title: "Hosted secrets",
|
|
1083
|
-
message: "All user-managed hosted secrets are set.",
|
|
1084
|
-
});
|
|
1085
|
-
} else {
|
|
1086
|
-
for (const secret of missing) {
|
|
1087
|
-
const localSource = context.localSecrets[secret];
|
|
1088
|
-
checks.push({
|
|
1089
|
-
status: "fail",
|
|
1090
|
-
title: `Hosted secret ${secret}`,
|
|
1091
|
-
message: localSource
|
|
1092
|
-
? `${secret} is present locally but has not been set in AgentKit Cloud.`
|
|
1093
|
-
: `${secret} is required but missing from AgentKit Cloud.`,
|
|
1094
|
-
action: secretSetCommand(secret, localSource),
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
819
|
+
)) as DeployAccessTokenListResponse;
|
|
820
|
+
const stillExists = (listed.access_tokens ?? []).some((token) => token.id === existing.id);
|
|
821
|
+
|
|
822
|
+
if (stillExists) {
|
|
823
|
+
return { status: "ready", path, tokenId: existing.id, created: false };
|
|
1097
824
|
}
|
|
1098
|
-
} catch (error) {
|
|
1099
|
-
checks.push({
|
|
1100
|
-
status: "fail",
|
|
1101
|
-
title: "Hosted secrets",
|
|
1102
|
-
message: `Could not list hosted secrets for project ${context.projectId}. ${formatReadinessError(error)}`,
|
|
1103
|
-
action: "agentkit secret list",
|
|
1104
|
-
});
|
|
1105
825
|
}
|
|
1106
|
-
}
|
|
1107
826
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
console.log("AgentKit deploy readiness");
|
|
1118
|
-
console.log(`Project: ${report.context.projectName}`);
|
|
1119
|
-
console.log(`Project ID: ${report.context.projectId}`);
|
|
1120
|
-
console.log(`Target: ${report.context.target}`);
|
|
1121
|
-
console.log(`API: ${report.apiUrl}`);
|
|
1122
|
-
console.log("");
|
|
1123
|
-
|
|
1124
|
-
for (const check of report.checks) {
|
|
1125
|
-
console.log(`[${check.status}] ${check.title}: ${check.message}`);
|
|
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;
|
|
1126
836
|
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
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.");
|
|
1129
839
|
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
function secretSetCommand(secret: string, source: DeployReadinessContext["localSecrets"][string] | undefined): string {
|
|
1134
|
-
if (source?.dotenv) {
|
|
1135
|
-
return `agentkit secret set ${secret} --from-local-env`;
|
|
1136
|
-
}
|
|
1137
840
|
|
|
1138
|
-
|
|
1139
|
-
return
|
|
841
|
+
await writeSecretJson(path, created);
|
|
842
|
+
return { status: "ready", path, tokenId: token.id, created: true };
|
|
843
|
+
} catch (error) {
|
|
844
|
+
return { status: "failed", error };
|
|
1140
845
|
}
|
|
1141
|
-
|
|
1142
|
-
return `agentkit secret set ${secret} --stdin`;
|
|
1143
846
|
}
|
|
1144
847
|
|
|
1145
|
-
function
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
return `${error.code}: ${error.message}`;
|
|
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;
|
|
1152
854
|
}
|
|
1153
|
-
|
|
1154
|
-
return error instanceof Error ? error.message : String(error);
|
|
1155
855
|
}
|
|
1156
856
|
|
|
1157
|
-
function
|
|
1158
|
-
|
|
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 });
|
|
1159
860
|
}
|
|
1160
861
|
|
|
1161
|
-
async function
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
if (subcommand === "list") {
|
|
1165
|
-
const deployState = await readDeployStateIfExists(process.cwd());
|
|
1166
|
-
|
|
1167
|
-
if (deployState) {
|
|
1168
|
-
const channels = await cloudGet<{ channels: CloudChannel[] }>(
|
|
1169
|
-
parseCloudApiUrl(args.flags.api),
|
|
1170
|
-
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
1171
|
-
);
|
|
1172
|
-
printChannelsTable(channels.channels);
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
const capsule = await loadAgentCapsule(process.cwd());
|
|
1177
|
-
printChannelsTable(
|
|
1178
|
-
(capsule.config.channels ?? []).map((channel) => ({
|
|
1179
|
-
id: "",
|
|
1180
|
-
name: channel.name,
|
|
1181
|
-
type: channel.type,
|
|
1182
|
-
provider: channel.provider,
|
|
1183
|
-
status: "local_config",
|
|
1184
|
-
webhook_url: "",
|
|
1185
|
-
required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
|
|
1186
|
-
})),
|
|
1187
|
-
);
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
if (subcommand === "add") {
|
|
1192
|
-
const type = first;
|
|
1193
|
-
const name = second;
|
|
1194
|
-
|
|
1195
|
-
if (!type || !name || (type !== "website" && type !== "telegram" && type !== "whatsapp")) {
|
|
1196
|
-
throw new Error("Usage: agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta]");
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
const deployState = await readDeployStateRequired(process.cwd());
|
|
1200
|
-
const provider = channelProviderForCli(type, args.flags.provider);
|
|
1201
|
-
const payload = {
|
|
1202
|
-
name,
|
|
1203
|
-
type,
|
|
1204
|
-
provider,
|
|
1205
|
-
secrets: defaultChannelSecretsForCli(type, provider),
|
|
1206
|
-
};
|
|
1207
|
-
const response = await cloudPost<{ channel: CloudChannel }>(
|
|
1208
|
-
parseCloudApiUrl(args.flags.api),
|
|
1209
|
-
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
1210
|
-
payload,
|
|
1211
|
-
);
|
|
1212
|
-
printChannelStatus(response.channel);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
if (subcommand === "setup") {
|
|
1217
|
-
if (!first) {
|
|
1218
|
-
throw new Error("Usage: agentkit channels setup <name> [--apply]");
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
1222
|
-
printChannelStatus(channel);
|
|
1223
|
-
if (channel.type === "telegram") {
|
|
1224
|
-
if (args.flags.apply) {
|
|
1225
|
-
await applyTelegramWebhook(channel);
|
|
1226
|
-
console.log("Telegram webhook updated.");
|
|
1227
|
-
} else {
|
|
1228
|
-
console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
|
|
1229
|
-
console.log("Use --apply to mutate Telegram webhook settings.");
|
|
1230
|
-
}
|
|
1231
|
-
} else if (channel.type === "whatsapp") {
|
|
1232
|
-
console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
|
|
1233
|
-
} else {
|
|
1234
|
-
console.log(`Website endpoint: ${channel.webhook_url}`);
|
|
1235
|
-
console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
|
|
1236
|
-
}
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (subcommand === "status") {
|
|
1241
|
-
if (!first) {
|
|
1242
|
-
throw new Error("Usage: agentkit channels status <name>");
|
|
1243
|
-
}
|
|
862
|
+
async function requireLocalDeployState(): Promise<LocalDeployState> {
|
|
863
|
+
const state = await readLocalDeployStateIfExists(process.cwd());
|
|
1244
864
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
await printChannelDeliverySummary(channel, args.flags.api);
|
|
1248
|
-
return;
|
|
865
|
+
if (!state) {
|
|
866
|
+
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy first.");
|
|
1249
867
|
}
|
|
1250
868
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
1257
|
-
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
1258
|
-
const body = await channelTestBody(channel, args.flags.message, args.flags.fixture);
|
|
1259
|
-
const path = new URL(channel.webhook_url).pathname;
|
|
1260
|
-
const headers = channelTestHeaders(channel);
|
|
1261
|
-
const response = await cloudFetch(apiUrl, path, {
|
|
1262
|
-
method: channel.type === "whatsapp" && channel.provider === "meta" ? "POST" : "POST",
|
|
1263
|
-
headers,
|
|
1264
|
-
body,
|
|
1265
|
-
});
|
|
1266
|
-
const payload = await response.json();
|
|
869
|
+
return state;
|
|
870
|
+
}
|
|
1267
871
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
return;
|
|
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.`);
|
|
1273
875
|
}
|
|
1274
876
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
if (!second) {
|
|
1278
|
-
throw new Error("Usage: agentkit channels deliveries list <name>");
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
const channel = await resolveCloudChannelByName(second, args.flags.api);
|
|
1282
|
-
const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
|
|
1283
|
-
parseCloudApiUrl(args.flags.api),
|
|
1284
|
-
`/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
|
|
1285
|
-
);
|
|
1286
|
-
|
|
1287
|
-
if (response.deliveries.length === 0) {
|
|
1288
|
-
console.log("No channel deliveries found.");
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
console.log("ID\tCHANNEL\tDIRECTION\tSTATUS\tPROVIDER_EVENT\tRECEIVED");
|
|
1293
|
-
for (const delivery of response.deliveries) {
|
|
1294
|
-
console.log(
|
|
1295
|
-
[
|
|
1296
|
-
delivery.id,
|
|
1297
|
-
delivery.channel_id,
|
|
1298
|
-
delivery.direction,
|
|
1299
|
-
delivery.status,
|
|
1300
|
-
delivery.provider.event_id ?? "",
|
|
1301
|
-
delivery.received_at,
|
|
1302
|
-
].join("\t"),
|
|
1303
|
-
);
|
|
1304
|
-
}
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
if (first === "show") {
|
|
1309
|
-
if (!second) {
|
|
1310
|
-
throw new Error("Usage: agentkit channels deliveries show <delivery-id>");
|
|
1311
|
-
}
|
|
877
|
+
return state.url;
|
|
878
|
+
}
|
|
1312
879
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
);
|
|
1317
|
-
console.log(JSON.stringify(response.delivery, null, 2));
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
880
|
+
async function readDeployAccessTokenForSmoke(cwd: string, state: LocalDeployState): Promise<string | undefined> {
|
|
881
|
+
if (state.access?.mode === "public") {
|
|
882
|
+
return undefined;
|
|
1320
883
|
}
|
|
1321
884
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
);
|
|
885
|
+
const root = await findAgentCapsuleRoot(cwd);
|
|
886
|
+
return readDeployAccessTokenForUi(join(root, defaultDeployChatAccessTokenPath));
|
|
1325
887
|
}
|
|
1326
888
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
type: "website" | "telegram" | "whatsapp";
|
|
1333
|
-
provider: "agentkit" | "telegram" | "zapster" | "meta";
|
|
1334
|
-
status: string;
|
|
1335
|
-
webhook_url: string;
|
|
1336
|
-
required_secrets: Array<{ name: string; status: string }>;
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
type CloudDelivery = {
|
|
1340
|
-
id: string;
|
|
1341
|
-
channel_id: string;
|
|
1342
|
-
direction: string;
|
|
1343
|
-
status: string;
|
|
1344
|
-
provider: {
|
|
1345
|
-
event_id?: string;
|
|
1346
|
-
};
|
|
1347
|
-
received_at: string;
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
|
-
async function applyTelegramWebhook(channel: CloudChannel): Promise<void> {
|
|
1351
|
-
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
1352
|
-
const secretToken = process.env.TELEGRAM_WEBHOOK_SECRET;
|
|
1353
|
-
|
|
1354
|
-
if (!botToken || !secretToken) {
|
|
1355
|
-
throw new Error("TELEGRAM_BOT_TOKEN and TELEGRAM_WEBHOOK_SECRET must be set to apply Telegram setup.");
|
|
889
|
+
async function runDeploySmoke(input: { deployUrl: string; message: string; token?: string }): Promise<void> {
|
|
890
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
891
|
+
|
|
892
|
+
if (input.token) {
|
|
893
|
+
headers.set("Authorization", `Bearer ${input.token}`);
|
|
1356
894
|
}
|
|
1357
895
|
|
|
1358
896
|
let response: Response;
|
|
1359
897
|
|
|
1360
898
|
try {
|
|
1361
|
-
response = await fetch(
|
|
899
|
+
response = await fetch(new URL("/v1/chat", input.deployUrl).href, {
|
|
1362
900
|
method: "POST",
|
|
1363
|
-
headers
|
|
1364
|
-
body: JSON.stringify({
|
|
1365
|
-
url: channel.webhook_url,
|
|
1366
|
-
secret_token: secretToken,
|
|
1367
|
-
allowed_updates: ["message"],
|
|
1368
|
-
}),
|
|
901
|
+
headers,
|
|
902
|
+
body: JSON.stringify({ message: input.message }),
|
|
1369
903
|
});
|
|
1370
|
-
} catch {
|
|
1371
|
-
throw new
|
|
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
|
+
);
|
|
1372
909
|
}
|
|
1373
910
|
|
|
1374
|
-
const payload = await response.json().catch(() =>
|
|
911
|
+
const payload = await response.json().catch(() => null);
|
|
1375
912
|
|
|
1376
|
-
if (!response.ok
|
|
1377
|
-
throw new
|
|
913
|
+
if (!response.ok) {
|
|
914
|
+
throw new AgentKitError("deploy_smoke_failed", readCloudError(payload, response.status));
|
|
1378
915
|
}
|
|
1379
|
-
}
|
|
1380
916
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
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;
|
|
1384
921
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
922
|
+
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}`);
|
|
1388
928
|
}
|
|
1389
|
-
|
|
1390
|
-
return `Telegram setWebhook failed with HTTP ${status}.`;
|
|
1391
929
|
}
|
|
1392
930
|
|
|
1393
|
-
|
|
1394
|
-
|
|
931
|
+
function printJson(value: unknown): void {
|
|
932
|
+
console.log(JSON.stringify(value, null, 2));
|
|
1395
933
|
}
|
|
1396
934
|
|
|
1397
|
-
async function
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
if (!state?.deploy_id) {
|
|
1401
|
-
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy before creating hosted channels.");
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
return state;
|
|
1405
|
-
}
|
|
935
|
+
async function printJsonOrWrite(value: unknown, outFlag: string | boolean | undefined, usage: string): Promise<void> {
|
|
936
|
+
const outPath = parseOptionalStringFlag(outFlag, "out", usage);
|
|
1406
937
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
console.log("No channels found.");
|
|
938
|
+
if (!outPath) {
|
|
939
|
+
printJson(value);
|
|
1410
940
|
return;
|
|
1411
941
|
}
|
|
1412
942
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
function printChannelStatus(channel: CloudChannel): void {
|
|
1420
|
-
console.log(`Channel: ${channel.name}`);
|
|
1421
|
-
if (channel.id) {
|
|
1422
|
-
console.log(`ID: ${channel.id}`);
|
|
1423
|
-
}
|
|
1424
|
-
console.log(`Type: ${channel.type}`);
|
|
1425
|
-
console.log(`Provider: ${channel.provider}`);
|
|
1426
|
-
console.log(`Status: ${channel.status}`);
|
|
1427
|
-
if (channel.webhook_url) {
|
|
1428
|
-
console.log(`Webhook URL: ${channel.webhook_url}`);
|
|
1429
|
-
}
|
|
1430
|
-
console.log("Required secrets:");
|
|
1431
|
-
for (const secret of channel.required_secrets) {
|
|
1432
|
-
console.log(` ${secret.name}: ${secret.status}`);
|
|
1433
|
-
}
|
|
943
|
+
const absolutePath = resolve(process.cwd(), outPath);
|
|
944
|
+
await writeSecretJson(absolutePath, value);
|
|
945
|
+
console.log(`Deploy access token written to ${absolutePath}`);
|
|
1434
946
|
}
|
|
1435
947
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
): Promise<void> {
|
|
1440
|
-
const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
|
|
1441
|
-
parseCloudApiUrl(apiFlag),
|
|
1442
|
-
`/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
|
|
1443
|
-
);
|
|
1444
|
-
const inbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "inbound");
|
|
1445
|
-
const outbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "outbound");
|
|
1446
|
-
|
|
1447
|
-
console.log("Recent delivery:");
|
|
1448
|
-
console.log(` Last inbound: ${inbound ? `${inbound.status} ${inbound.received_at}` : "none"}`);
|
|
1449
|
-
console.log(` Last outbound: ${outbound ? `${outbound.status} ${outbound.received_at}` : "none"}`);
|
|
1450
|
-
console.log("Provider health: not_checked");
|
|
948
|
+
function relativePath(root: string, path: string): string {
|
|
949
|
+
const output = relative(root, path);
|
|
950
|
+
return output.startsWith("..") ? path : output || ".";
|
|
1451
951
|
}
|
|
1452
952
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
);
|
|
1459
|
-
const channel = response.channels.find((candidate) => candidate.name === name);
|
|
1460
|
-
|
|
1461
|
-
if (!channel) {
|
|
1462
|
-
throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
return channel;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
|
|
1469
|
-
if (type === "website") {
|
|
1470
|
-
return "agentkit";
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
if (type === "telegram") {
|
|
1474
|
-
return "telegram";
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
const provider = providerFlag ?? "zapster";
|
|
1478
|
-
|
|
1479
|
-
if (provider !== "zapster" && provider !== "meta") {
|
|
1480
|
-
throw new Error('WhatsApp provider must be "zapster" or "meta".');
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
return provider;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function defaultChannelSecretsForCli(type: string, provider: string): string[] {
|
|
1487
|
-
if (type === "website") {
|
|
1488
|
-
return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
if (type === "telegram") {
|
|
1492
|
-
return ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"];
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
if (type === "whatsapp" && provider === "zapster") {
|
|
1496
|
-
return ["ZAPSTER_API_KEY", "ZAPSTER_WEBHOOK_SECRET"];
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
if (type === "whatsapp" && provider === "meta") {
|
|
1500
|
-
return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
return [];
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
async function channelTestBody(
|
|
1507
|
-
channel: CloudChannel,
|
|
1508
|
-
messageFlag: string | boolean | undefined,
|
|
1509
|
-
fixtureFlag: string | boolean | undefined,
|
|
1510
|
-
): Promise<string> {
|
|
1511
|
-
if (typeof fixtureFlag === "string") {
|
|
1512
|
-
return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
const message = typeof messageFlag === "string" ? messageFlag : "hello";
|
|
1516
|
-
|
|
1517
|
-
if (channel.type === "telegram") {
|
|
1518
|
-
return JSON.stringify({
|
|
1519
|
-
update_id: Date.now(),
|
|
1520
|
-
message: {
|
|
1521
|
-
message_id: 1,
|
|
1522
|
-
chat: { id: 456, type: "private" },
|
|
1523
|
-
from: { id: 123, first_name: "AgentKit" },
|
|
1524
|
-
text: message,
|
|
1525
|
-
},
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
if (channel.type === "whatsapp") {
|
|
1530
|
-
return JSON.stringify({
|
|
1531
|
-
event_id: `evt_${Date.now()}`,
|
|
1532
|
-
instance_id: "local",
|
|
1533
|
-
message: {
|
|
1534
|
-
id: `msg_${Date.now()}`,
|
|
1535
|
-
from: "5511999999999",
|
|
1536
|
-
type: "text",
|
|
1537
|
-
text: { body: message },
|
|
1538
|
-
},
|
|
1539
|
-
});
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
return JSON.stringify({
|
|
1543
|
-
event_id: `web_${Date.now()}`,
|
|
1544
|
-
visitor_id: "visitor_test",
|
|
1545
|
-
message: {
|
|
1546
|
-
id: `msg_${Date.now()}`,
|
|
1547
|
-
text: message,
|
|
1548
|
-
},
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
function channelTestHeaders(channel: CloudChannel): Headers {
|
|
1553
|
-
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1554
|
-
|
|
1555
|
-
if (channel.type === "website") {
|
|
1556
|
-
const token = process.env.AGENTKIT_WEBSITE_CHANNEL_TOKEN;
|
|
1557
|
-
|
|
1558
|
-
if (token) {
|
|
1559
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
if (channel.type === "telegram") {
|
|
1564
|
-
headers.set("X-Telegram-Bot-Api-Secret-Token", process.env.TELEGRAM_WEBHOOK_SECRET ?? "");
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
1568
|
-
headers.set("X-Zapster-Webhook-Secret", process.env.ZAPSTER_WEBHOOK_SECRET ?? "");
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
return headers;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
async function cloudGet<T>(apiUrl: string, path: string): Promise<T> {
|
|
1575
|
-
const response = await cloudFetch(apiUrl, path);
|
|
1576
|
-
const payload = await response.json();
|
|
1577
|
-
|
|
1578
|
-
if (!response.ok) {
|
|
1579
|
-
throw new Error(readCloudError(payload, response.status));
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
return payload as T;
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
async function cloudPost<T>(apiUrl: string, path: string, body: unknown): Promise<T> {
|
|
1586
|
-
const response = await cloudFetch(apiUrl, path, {
|
|
1587
|
-
method: "POST",
|
|
1588
|
-
headers: { "Content-Type": "application/json" },
|
|
1589
|
-
body: JSON.stringify(body),
|
|
1590
|
-
});
|
|
1591
|
-
const payload = await response.json();
|
|
1592
|
-
|
|
1593
|
-
if (!response.ok) {
|
|
1594
|
-
throw new Error(readCloudError(payload, response.status));
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
return payload as T;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promise<Response> {
|
|
1601
|
-
const headers = new Headers(init.headers);
|
|
1602
|
-
const apiToken = process.env.AGENTKIT_CLOUD_API_TOKEN;
|
|
1603
|
-
|
|
1604
|
-
if (apiToken && !headers.has("Authorization")) {
|
|
1605
|
-
headers.set("Authorization", `Bearer ${apiToken}`);
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
return fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
1609
|
-
...init,
|
|
1610
|
-
headers,
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
type CloudAuthConfig = {
|
|
1615
|
-
apiUrl: string;
|
|
1616
|
-
token: string;
|
|
1617
|
-
source?: "environment" | "file";
|
|
1618
|
-
};
|
|
1619
|
-
|
|
1620
|
-
function cloudAuthPath(): string {
|
|
1621
|
-
return join(homedir(), ".agentkit", "cloud.json");
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
async function readCloudAuth(): Promise<CloudAuthConfig | null> {
|
|
1625
|
-
if (process.env.AGENTKIT_CLOUD_API_TOKEN) {
|
|
1626
|
-
return {
|
|
1627
|
-
apiUrl: process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl,
|
|
1628
|
-
token: process.env.AGENTKIT_CLOUD_API_TOKEN,
|
|
1629
|
-
source: "environment",
|
|
1630
|
-
};
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
try {
|
|
1634
|
-
const parsed = JSON.parse(await readFile(cloudAuthPath(), "utf8"));
|
|
1635
|
-
|
|
1636
|
-
if (parsed && typeof parsed === "object" && typeof parsed.token === "string") {
|
|
1637
|
-
return {
|
|
1638
|
-
apiUrl: typeof parsed.apiUrl === "string" ? parsed.apiUrl : defaultAgentKitCloudApiUrl,
|
|
1639
|
-
token: parsed.token,
|
|
1640
|
-
source: "file",
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
} catch {
|
|
1644
|
-
return null;
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
return null;
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
async function writeCloudAuth(config: CloudAuthConfig): Promise<void> {
|
|
1651
|
-
await mkdir(join(homedir(), ".agentkit"), { recursive: true });
|
|
1652
|
-
await writeFile(cloudAuthPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
1653
|
-
}
|
|
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
|
-
|
|
1736
|
-
async function clearCloudAuth(): Promise<void> {
|
|
1737
|
-
await rm(cloudAuthPath(), { force: true });
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
async function resolveCloudApiUrl(flag: string | boolean | undefined): Promise<string> {
|
|
1741
|
-
if (flag !== undefined) {
|
|
1742
|
-
return parseCloudApiUrl(flag);
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
const auth = await readCloudAuth();
|
|
1746
|
-
return auth?.apiUrl ?? parseCloudApiUrl(undefined);
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
async function requireLocalDeployState(): Promise<LocalDeployState> {
|
|
1750
|
-
const state = await readLocalDeployStateIfExists(process.cwd());
|
|
1751
|
-
|
|
1752
|
-
if (!state) {
|
|
1753
|
-
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy first.");
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
return state;
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
async function resolveProjectIdForSecretCommand(cwd: string): Promise<string> {
|
|
1760
|
-
const state = await readLocalDeployStateIfExists(cwd);
|
|
1761
|
-
|
|
1762
|
-
if (state?.project_id) {
|
|
1763
|
-
return state.project_id;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
const root = await findAgentCapsuleRoot(cwd);
|
|
1767
|
-
const capsule = await loadAgentCapsule(root);
|
|
1768
|
-
return stableCloudProjectId(capsule.config.name);
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit): Promise<unknown> {
|
|
1772
|
-
const auth = await readCloudAuth();
|
|
1773
|
-
const headers = new Headers(init.headers);
|
|
1774
|
-
|
|
1775
|
-
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
1776
|
-
headers.set("Content-Type", "application/json");
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
if (auth?.token && !headers.has("Authorization")) {
|
|
1780
|
-
headers.set("Authorization", `Bearer ${auth.token}`);
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
1784
|
-
...init,
|
|
1785
|
-
headers,
|
|
1786
|
-
});
|
|
1787
|
-
const payload = await response.json().catch(() => null);
|
|
1788
|
-
|
|
1789
|
-
if (!response.ok) {
|
|
1790
|
-
const code =
|
|
1791
|
-
payload && typeof payload === "object" && "error" in payload
|
|
1792
|
-
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
1793
|
-
: "cloud_request_failed";
|
|
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);
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
return payload;
|
|
1804
|
-
}
|
|
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
|
-
|
|
1837
|
-
function printJson(value: unknown): void {
|
|
1838
|
-
console.log(JSON.stringify(value, null, 2));
|
|
1839
|
-
}
|
|
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
|
-
|
|
1854
|
-
function readCloudError(payload: unknown, status: number): string {
|
|
1855
|
-
if (payload && typeof payload === "object" && "error" in payload) {
|
|
1856
|
-
const error = (payload as { error?: { message?: unknown } }).error;
|
|
1857
|
-
if (typeof error?.message === "string") {
|
|
1858
|
-
return error.message;
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
return `AgentKit Cloud request failed with HTTP ${status}.`;
|
|
1863
|
-
}
|
|
1864
|
-
|
|
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 {
|
|
1883
|
-
if (value === undefined) {
|
|
1884
|
-
return undefined;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
if (typeof value !== "string") {
|
|
1888
|
-
throw new Error(`Missing port. Usage: ${usage}`);
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
const port = Number(value);
|
|
1892
|
-
|
|
1893
|
-
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
1894
|
-
throw new Error(`Invalid port. Usage: ${usage}`);
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
return port;
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
function parseTargetFlag(value: string | boolean | undefined, capability: "build" | "deploy"): string {
|
|
1901
|
-
if (value === undefined) {
|
|
1902
|
-
return "cloudflare";
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
if (typeof value !== "string") {
|
|
1906
|
-
throw new Error(
|
|
1907
|
-
`Missing target. Usage: agentkit ${capability} --target ${listTargetsWithCapability(capability).join("|")}`,
|
|
1908
|
-
);
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
return value;
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
function parseCloudApiUrl(value: string | boolean | undefined): string {
|
|
1915
|
-
if (value === undefined) {
|
|
1916
|
-
return process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
if (typeof value !== "string") {
|
|
1920
|
-
throw new Error("Missing API URL. Usage: agentkit deploy --api <url>");
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
return value;
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
function parseRequiredStringFlag(value: string | boolean | undefined, name: string, usage: string): string {
|
|
1927
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1928
|
-
throw new Error(`Missing ${name}. Usage: ${usage}`);
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
return value.trim();
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
function parseOptionalStringFlag(value: string | boolean | undefined, name: string, usage: string): string | undefined {
|
|
1935
|
-
if (value === undefined) {
|
|
1936
|
-
return undefined;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1940
|
-
throw new Error(`Missing ${name}. Usage: ${usage}`);
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
return value.trim();
|
|
1944
|
-
}
|
|
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
|
-
|
|
2030
|
-
function printBuildResult(result: Awaited<ReturnType<typeof buildAgentCapsule>>): void {
|
|
2031
|
-
console.log("AgentKit build complete");
|
|
2032
|
-
console.log("");
|
|
2033
|
-
console.log(`Target: ${result.target}`);
|
|
2034
|
-
console.log(`Build dir: ${result.buildDir}`);
|
|
2035
|
-
console.log(`Manifest: ${result.manifestPath}`);
|
|
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}`);
|
|
2036
959
|
|
|
2037
960
|
if (result.target === "cloudflare") {
|
|
2038
961
|
console.log(`Worker: ${result.workerPath}`);
|
|
@@ -2092,18 +1015,6 @@ async function installProjectDependencies(projectPath: string): Promise<void> {
|
|
|
2092
1015
|
}
|
|
2093
1016
|
}
|
|
2094
1017
|
|
|
2095
|
-
function runInheritedCommand(command: string, args: string[], cwd: string): Promise<number> {
|
|
2096
|
-
return new Promise((resolvePromise, reject) => {
|
|
2097
|
-
const child = spawn(command, args, {
|
|
2098
|
-
cwd,
|
|
2099
|
-
stdio: "inherit",
|
|
2100
|
-
});
|
|
2101
|
-
|
|
2102
|
-
child.once("error", reject);
|
|
2103
|
-
child.once("close", (code) => resolvePromise(code ?? 1));
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
1018
|
async function parseToolInput(
|
|
2108
1019
|
cwd: string,
|
|
2109
1020
|
inputFlag: string | boolean | undefined,
|
|
@@ -2147,393 +1058,81 @@ function parseJsonInput(source: string, label: string): unknown {
|
|
|
2147
1058
|
}
|
|
2148
1059
|
}
|
|
2149
1060
|
|
|
2150
|
-
async function
|
|
2151
|
-
cwd
|
|
2152
|
-
|
|
2153
|
-
): Promise<{ url: string; deployUrl: string; tokenSource: string; stop(): Promise<void> }> {
|
|
2154
|
-
const root = await findAgentCapsuleRoot(cwd);
|
|
2155
|
-
const state = await readLocalDeployStateIfExists(root);
|
|
1061
|
+
async function findRunningLocalDevServer(cwd: string): Promise<{ url: string; agent: string } | null> {
|
|
1062
|
+
const capsule = await loadAgentCapsule(cwd);
|
|
1063
|
+
const basePort = localDevBasePort();
|
|
2156
1064
|
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
}
|
|
1065
|
+
for (let port = basePort; port <= basePort + 50; port += 1) {
|
|
1066
|
+
const state = await fetchLocalAgentKitState(`http://localhost:${port}/_agentkit`);
|
|
2160
1067
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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
|
-
});
|
|
1068
|
+
if (state?.agent === capsule.config.name) {
|
|
1069
|
+
return {
|
|
1070
|
+
url: `http://localhost:${port}`,
|
|
1071
|
+
agent: state.agent,
|
|
1072
|
+
};
|
|
2221
1073
|
}
|
|
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
1074
|
}
|
|
2272
1075
|
|
|
2273
|
-
return
|
|
1076
|
+
return null;
|
|
2274
1077
|
}
|
|
2275
1078
|
|
|
2276
|
-
function
|
|
2277
|
-
|
|
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
|
-
}
|
|
1079
|
+
function localDevBasePort(): number {
|
|
1080
|
+
const value = process.env.AGENTKIT_DEV_BASE_PORT;
|
|
2287
1081
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
}
|
|
1082
|
+
if (!value) {
|
|
1083
|
+
return defaultAgentKitDevPort;
|
|
1084
|
+
}
|
|
2292
1085
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
response.end(body);
|
|
1086
|
+
const parsed = Number(value);
|
|
1087
|
+
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : defaultAgentKitDevPort;
|
|
2296
1088
|
}
|
|
2297
1089
|
|
|
2298
|
-
function
|
|
2299
|
-
const
|
|
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
|
-
}
|
|
1090
|
+
async function fetchLocalAgentKitState(url: string): Promise<{ agent?: string } | null> {
|
|
1091
|
+
const controller = new AbortController();
|
|
1092
|
+
const timeout = setTimeout(() => controller.abort(), 300);
|
|
2335
1093
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
-
}
|
|
1094
|
+
try {
|
|
1095
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
1096
|
+
const payload = await response.json().catch(() => null);
|
|
2477
1097
|
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
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
|
-
}
|
|
1098
|
+
if (payload && typeof payload === "object") {
|
|
1099
|
+
return {
|
|
1100
|
+
agent: typeof (payload as { agent?: unknown }).agent === "string" ? (payload as { agent: string }).agent : undefined,
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
} catch {
|
|
1104
|
+
return null;
|
|
1105
|
+
} finally {
|
|
1106
|
+
clearTimeout(timeout);
|
|
1107
|
+
}
|
|
2512
1108
|
|
|
2513
|
-
|
|
2514
|
-
return value
|
|
2515
|
-
.replace(/&/g, "&")
|
|
2516
|
-
.replace(/</g, "<")
|
|
2517
|
-
.replace(/>/g, ">")
|
|
2518
|
-
.replace(/"/g, """)
|
|
2519
|
-
.replace(/'/g, "'");
|
|
1109
|
+
return null;
|
|
2520
1110
|
}
|
|
2521
1111
|
|
|
2522
|
-
async function
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
const shutdown = () => {
|
|
2527
|
-
if (stopped) {
|
|
2528
|
-
return;
|
|
2529
|
-
}
|
|
1112
|
+
async function openLocalUrl(url: string): Promise<void> {
|
|
1113
|
+
if (process.env.AGENTKIT_OPEN_COMMAND === "none") {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
2530
1116
|
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
1117
|
+
const command =
|
|
1118
|
+
process.env.AGENTKIT_OPEN_COMMAND ??
|
|
1119
|
+
(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open");
|
|
1120
|
+
const args = process.env.AGENTKIT_OPEN_COMMAND
|
|
1121
|
+
? [url]
|
|
1122
|
+
: process.platform === "win32"
|
|
1123
|
+
? ["/c", "start", "", url]
|
|
1124
|
+
: [url];
|
|
2534
1125
|
|
|
2535
|
-
|
|
2536
|
-
|
|
1126
|
+
await new Promise<void>((resolvePromise, reject) => {
|
|
1127
|
+
const child = spawn(command, args, {
|
|
1128
|
+
detached: true,
|
|
1129
|
+
stdio: "ignore",
|
|
1130
|
+
});
|
|
1131
|
+
child.once("error", reject);
|
|
1132
|
+
child.once("spawn", () => {
|
|
1133
|
+
child.unref();
|
|
1134
|
+
resolvePromise();
|
|
1135
|
+
});
|
|
2537
1136
|
});
|
|
2538
1137
|
}
|
|
2539
1138
|
|