@andreprado/agentkit 0.1.0-alpha.3 → 0.1.0-alpha.4

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/src/cli/index.ts CHANGED
@@ -29,6 +29,7 @@ import { AgentKitError, isAgentKitError } from "../runtime/errors";
29
29
  import { prepareVpsDeploy } from "../runtime/targets/vps/deploy";
30
30
 
31
31
  const defaultAgentKitCloudApiUrl = "https://agentkit-cloud.aibuilders.com.br";
32
+ const defaultAgentKitDevPort = 4123;
32
33
  const defaultDeployChatAccessTokenName = "agentkit-chat-ui";
33
34
  const defaultDeployChatAccessTokenPath = ".agentkit/chat-access-token.json";
34
35
 
@@ -47,10 +48,11 @@ Usage:
47
48
  agentkit <command> [options]
48
49
 
49
50
  Start:
50
- agentkit new <name> [--template blank|support] [--no-install]
51
+ agentkit new <name> [--template blank|support|dentista] [--no-install]
51
52
  agentkit dev
53
+ agentkit open
52
54
  agentkit chat-ui --deploy
53
- agentkit chat --message <text>
55
+ agentkit chat --message <text> [--conversation-id <id>]
54
56
  agentkit inspect
55
57
 
56
58
  Build:
@@ -63,7 +65,8 @@ Deploy:
63
65
  agentkit login --token <token>
64
66
  agentkit deploy doctor
65
67
  agentkit secret set <NAME> --stdin
66
- agentkit deploy
68
+ agentkit secret sync --from-local
69
+ agentkit deploy --smoke "hello"
67
70
  agentkit deploy status
68
71
 
69
72
  More:
@@ -79,10 +82,11 @@ function renderCommandReference(): string {
79
82
  return `AgentKit Command Reference
80
83
 
81
84
  Usage:
82
- agentkit new <name> [--template blank|support] [--no-install]
83
- agentkit dev
85
+ agentkit new <name> [--template blank|support|dentista] [--no-install]
86
+ agentkit dev [--port <number>]
87
+ agentkit open
84
88
  agentkit chat-ui --deploy [--port <number>] [--token-file <path>]
85
- agentkit chat --message <text>
89
+ agentkit chat --message <text> [--conversation-id <id>]
86
90
  agentkit tool <name> [--input <path-or-json>]
87
91
  agentkit db migrate
88
92
  agentkit db reset --yes
@@ -99,11 +103,12 @@ Usage:
99
103
  agentkit channels deliveries list <name> [--api <url>]
100
104
  agentkit channels deliveries show <delivery-id> [--api <url>]
101
105
  agentkit inspect
102
- agentkit build [--target cloudflare]
106
+ agentkit build [--target cloudflare|container]
103
107
  agentkit login --token <token> [--api <url>]
104
108
  agentkit logout
105
- agentkit deploy [--target cloudflare|vps] [--host <host>] [--api <url>] [--dry-run] [--anonymous] [--local-wrangler]
109
+ agentkit deploy [--target cloudflare|vps] [--host <host>] [--api <url>] [--dry-run] [--anonymous] [--local-wrangler] [--smoke <message>]
106
110
  agentkit deploy doctor [--api <url>] [--anonymous]
111
+ agentkit deploy smoke [--message <text>] [--api <url>]
107
112
  agentkit deploy status [--api <url>]
108
113
  agentkit deploy pause [--api <url>]
109
114
  agentkit deploy resume [--api <url>]
@@ -111,6 +116,7 @@ Usage:
111
116
  agentkit secret set <NAME> --stdin [--api <url>]
112
117
  agentkit secret set <NAME> --from-env [ENV_NAME] [--api <url>]
113
118
  agentkit secret set <NAME> --from-local-env [--api <url>]
119
+ agentkit secret sync --from-local [--api <url>]
114
120
  agentkit secret list [--api <url>]
115
121
  agentkit secret unset <NAME> [--api <url>]
116
122
  agentkit access token create <name> [--api <url>] [--out <path>]
@@ -131,6 +137,7 @@ Usage:
131
137
  Commands:
132
138
  new Create a new Agent Capsule
133
139
  dev Run the Agent Capsule locally
140
+ open Open the local Agent Capsule UI if it is already running
134
141
  chat-ui Run a local chat UI, optionally pointed at a hosted deploy
135
142
  chat Send one message to the local Agent Capsule
136
143
  tool Run one registered tool directly
@@ -154,8 +161,11 @@ Examples:
154
161
  agentkit new support-agent --template support
155
162
  cd support-agent
156
163
  agentkit dev
164
+ agentkit open
157
165
  agentkit tool lookup_order --input '{"orderId":"A100"}'
158
166
  agentkit eval run
167
+
168
+ agentkit new clara-dentista --template dentista
159
169
  `;
160
170
  }
161
171
 
@@ -192,6 +202,33 @@ function parseArgs(argv: string[]): ParsedArgs {
192
202
  return { command, positional, flags };
193
203
  }
194
204
 
205
+ function isHelpRequested(args: ParsedArgs): boolean {
206
+ return args.flags.help === true || args.flags.h === true || args.positional.includes("--help") || args.positional.includes("-h");
207
+ }
208
+
209
+ function renderCommandHelp(command: string): string {
210
+ const usage: Record<string, string> = {
211
+ new: "agentkit new <name> [--template blank|support|dentista] [--no-install]",
212
+ dev: "agentkit dev [--port <number>]",
213
+ open: "agentkit open",
214
+ "chat-ui": "agentkit chat-ui --deploy [--port <number>] [--token-file <path>]",
215
+ chat: 'agentkit chat --message "hello" [--conversation-id <id>]',
216
+ tool: "agentkit tool <name> [--input <path-or-json>]",
217
+ deploy:
218
+ 'agentkit deploy [--smoke "hello"] | agentkit deploy smoke [--message "hello"] | agentkit deploy doctor | status | pause | resume',
219
+ secret:
220
+ "agentkit secret set <NAME> --stdin | --from-env [ENV_NAME] | --from-local-env | agentkit secret sync --from-local | list | unset <NAME>",
221
+ access: "agentkit access token create <name> [--out <path>] | list | revoke <token-id>",
222
+ env: "agentkit env set <NAME> --stdin | --from-env [ENV_NAME] | list | unset <NAME>",
223
+ docs: "agentkit docs path | llms | full",
224
+ handoff: "agentkit handoff codex [goal] | agentkit handoff claude [goal]",
225
+ };
226
+
227
+ return `Usage: ${usage[command] ?? "agentkit <command> [options]"}
228
+
229
+ Run \`agentkit help commands\` for the full command reference.`;
230
+ }
231
+
195
232
  async function main() {
196
233
  const args = parseArgs(process.argv.slice(2));
197
234
 
@@ -200,6 +237,11 @@ async function main() {
200
237
  return;
201
238
  }
202
239
 
240
+ if (isHelpRequested(args)) {
241
+ console.log(renderCommandHelp(args.command));
242
+ return;
243
+ }
244
+
203
245
  if (args.command === "help") {
204
246
  const [topic] = args.positional;
205
247
 
@@ -222,7 +264,7 @@ async function main() {
222
264
  const shouldInstall = args.flags["no-install"] !== true;
223
265
 
224
266
  if (!name) {
225
- throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support] [--no-install]");
267
+ throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support|dentista] [--no-install]");
226
268
  }
227
269
 
228
270
  const result = await createProject({ name, template, cwd: process.cwd() });
@@ -254,10 +296,17 @@ async function main() {
254
296
 
255
297
  if (args.command === "dev") {
256
298
  const port = parsePort(args.flags.port, "agentkit dev --port <number>");
257
- const devServer = await startAgentDevServer(process.cwd(), { port });
299
+ const devServer = await startAgentDevServer(process.cwd(), { port, basePort: localDevBasePort() });
258
300
 
259
301
  console.log("Agent Capsule running");
260
302
  console.log("");
303
+ if (devServer.portConflict) {
304
+ const owner = devServer.portConflict.agent ? ` by ${devServer.portConflict.agent}` : "";
305
+ console.log(
306
+ `Port ${devServer.portConflict.port} is already used${owner}. Started ${devServer.capsule.config.name} at ${devServer.urls.chat}.`,
307
+ );
308
+ console.log("");
309
+ }
261
310
  console.log(`Chat: ${devServer.urls.chat}`);
262
311
  console.log(`API: ${devServer.urls.api}`);
263
312
  console.log(`Inspect: ${devServer.urls.inspect}`);
@@ -266,6 +315,20 @@ async function main() {
266
315
  return;
267
316
  }
268
317
 
318
+ if (args.command === "open") {
319
+ const server = await findRunningLocalDevServer(process.cwd());
320
+
321
+ if (!server) {
322
+ console.log("No running AgentKit dev server found for this capsule.");
323
+ console.log("Run: npm run dev");
324
+ return;
325
+ }
326
+
327
+ await openLocalUrl(server.url);
328
+ console.log(`Opened ${server.url}`);
329
+ return;
330
+ }
331
+
269
332
  if (args.command === "chat-ui") {
270
333
  if (!args.flags.deploy) {
271
334
  throw new Error("Usage: agentkit chat-ui --deploy [--port <number>] [--token-file <path>]");
@@ -290,12 +353,17 @@ async function main() {
290
353
 
291
354
  if (args.command === "chat") {
292
355
  const message = args.flags.message;
356
+ const conversationId = parseOptionalStringFlag(
357
+ args.flags["conversation-id"] ?? args.flags.conversation,
358
+ "conversation id",
359
+ 'agentkit chat --message "hello" [--conversation-id <id>]',
360
+ );
293
361
 
294
362
  if (typeof message !== "string") {
295
- throw new Error('Missing message. Usage: agentkit chat --message "hello"');
363
+ throw new Error('Missing message. Usage: agentkit chat --message "hello" [--conversation-id <id>]');
296
364
  }
297
365
 
298
- const result = await runAgentMessageFromCwd(process.cwd(), { message });
366
+ const result = await runAgentMessageFromCwd(process.cwd(), { message, conversationId });
299
367
  console.log(result.message.content);
300
368
  return;
301
369
  }
@@ -485,6 +553,20 @@ async function main() {
485
553
  if (args.command === "deploy") {
486
554
  const [deploySubcommand] = args.positional;
487
555
 
556
+ if (deploySubcommand === "smoke") {
557
+ const message =
558
+ parseOptionalStringFlag(args.flags.message ?? args.flags.smoke, "message", 'agentkit deploy smoke --message "hello"') ??
559
+ "hello";
560
+ const state = await requireLocalDeployState();
561
+ const token = await readDeployAccessTokenForSmoke(process.cwd(), state);
562
+ await runDeploySmoke({
563
+ deployUrl: requireDeployUrl(state),
564
+ message,
565
+ token,
566
+ });
567
+ return;
568
+ }
569
+
488
570
  if (deploySubcommand === "doctor") {
489
571
  const report = await checkDeployReadiness({
490
572
  apiUrl: await resolveCloudApiUrl(args.flags.api),
@@ -520,6 +602,7 @@ async function main() {
520
602
 
521
603
  const target = resolveDeployTarget(parseTargetFlag(args.flags.target, "deploy"));
522
604
  const anonymous = Boolean(args.flags.anonymous);
605
+ const smokeMessage = parseOptionalStringFlag(args.flags.smoke, "smoke", 'agentkit deploy --smoke "hello"');
523
606
 
524
607
  if (target.id === "vps") {
525
608
  const host = parseRequiredStringFlag(args.flags.host, "host", "agentkit deploy --target vps --host <host>");
@@ -592,6 +675,24 @@ async function main() {
592
675
  console.log(`Chat auth: could not create local chat token. ${formatReadinessError(chatAccess.error)}`);
593
676
  console.log(`Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`);
594
677
  }
678
+
679
+ if (smokeMessage) {
680
+ if (chatAccess?.status === "failed") {
681
+ throw new AgentKitError("deploy_smoke_failed", "Could not run deploy smoke because chat access token creation failed.");
682
+ }
683
+
684
+ await runDeploySmoke({
685
+ deployUrl: requireDeployUrl({
686
+ deploy_id: deploy.deploy.id,
687
+ url: deploy.deploy.url,
688
+ }),
689
+ message: smokeMessage,
690
+ token:
691
+ chatAccess?.status === "ready"
692
+ ? await readDeployAccessTokenForUi(chatAccess.path)
693
+ : undefined,
694
+ });
695
+ }
595
696
  return;
596
697
  }
597
698
 
@@ -617,6 +718,7 @@ async function main() {
617
718
  fromLocalEnvFlag: args.flags["from-local-env"],
618
719
  localEnvRoot: args.flags["from-local-env"] !== undefined ? await findAgentCapsuleRoot(process.cwd()) : undefined,
619
720
  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>]",
721
+ inlineSensitiveHelp: "Prefer --stdin, --from-env, or --from-local-env so the value does not enter shell history.",
620
722
  });
621
723
 
622
724
  if (!name) {
@@ -631,6 +733,15 @@ async function main() {
631
733
  return;
632
734
  }
633
735
 
736
+ if (subcommand === "sync") {
737
+ if (args.flags["from-local"] !== true) {
738
+ throw new Error("Usage: agentkit secret sync --from-local [--api <url>]");
739
+ }
740
+
741
+ await syncHostedSecretsFromLocal({ apiUrl, projectId });
742
+ return;
743
+ }
744
+
634
745
  if (subcommand === "unset") {
635
746
  if (!name) {
636
747
  throw new Error("Usage: agentkit secret unset <NAME> [--api <url>]");
@@ -643,7 +754,7 @@ async function main() {
643
754
  return;
644
755
  }
645
756
 
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>");
757
+ 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
758
  }
648
759
 
649
760
  if (args.command === "access") {
@@ -718,6 +829,7 @@ async function main() {
718
829
  fromEnvFlag: args.flags["from-env"],
719
830
  fromLocalEnvFlag: undefined,
720
831
  usage: "agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME]",
832
+ inlineSensitiveHelp: "Prefer --stdin or --from-env so the value does not enter shell history.",
721
833
  });
722
834
 
723
835
  if (!name) {
@@ -803,9 +915,12 @@ Rules:
803
915
  - Follow the AgentKit contract in llms-full.txt.
804
916
  - Treat the owner's request as the brief.
805
917
  - Build or update the agent inside this capsule as far as you can without asking for obvious restatements.
918
+ - Do not wait for a wizard or recipe; implement directly in the capsule files.
806
919
  - Edit agent behavior in prompts/instructions.md.
807
920
  - Edit the agent contract in agentkit.config.ts.
808
921
  - Add TypeScript tools under tools/.
922
+ - Keep the first useful version runnable with test/fake unless the owner has already chosen a real provider.
923
+ - Before testing real conversation behavior, ask the owner to choose OpenRouter, OpenAI, Anthropic, or another supported provider. Do not choose for them.
809
924
  - Keep local secrets in .env only.
810
925
  - Do not commit or print secret values.
811
926
  - Do not edit .agentkit/ directly.
@@ -827,10 +942,16 @@ If you add a tool, also run a tool smoke test with the fake provider, for exampl
827
942
  npm run agentkit -- tool tool_name --input '{}'
828
943
  \`\`\`
829
944
 
945
+ UI testing:
946
+ - For local UI testing, run \`npm run dev\`, open the printed \`Chat:\` URL, and tell the owner the exact URL.
947
+ - 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.
948
+ - If the provider is still \`test/fake\`, say the UI was tested only with the deterministic fake provider.
949
+
830
950
  Final response:
831
951
  - Summarize what changed.
832
952
  - List files changed.
833
953
  - Paste the verification commands and results.
954
+ - State whether testing used \`test/fake\` or a real provider selected by the owner.
834
955
  - Call out any missing user details or blocked checks.
835
956
  `;
836
957
  }
@@ -1142,6 +1263,38 @@ function secretSetCommand(secret: string, source: DeployReadinessContext["localS
1142
1263
  return `agentkit secret set ${secret} --stdin`;
1143
1264
  }
1144
1265
 
1266
+ async function syncHostedSecretsFromLocal(options: { apiUrl: string; projectId: string }): Promise<void> {
1267
+ const context = await loadDeployReadinessContext(process.cwd(), process.env);
1268
+ const localEnv = await loadCapsuleEnv(context.root, {});
1269
+ let synced = 0;
1270
+ let skipped = 0;
1271
+
1272
+ for (const secret of context.userManagedSecrets) {
1273
+ const value = localEnv[secret];
1274
+
1275
+ if (value === undefined) {
1276
+ skipped += 1;
1277
+ console.log(`${secret}\tskipped_missing_local`);
1278
+ continue;
1279
+ }
1280
+
1281
+ await cloudApiRequest(options.apiUrl, `/v1/projects/${encodeURIComponent(options.projectId)}/secrets/${encodeURIComponent(secret)}`, {
1282
+ method: "PUT",
1283
+ body: JSON.stringify({ value }),
1284
+ });
1285
+ synced += 1;
1286
+ console.log(`${secret}\tsynced`);
1287
+ }
1288
+
1289
+ if (context.userManagedSecrets.length === 0) {
1290
+ console.log("No user-managed hosted secrets are declared by this capsule.");
1291
+ return;
1292
+ }
1293
+
1294
+ console.log("");
1295
+ console.log(`${synced} synced, ${skipped} skipped`);
1296
+ }
1297
+
1145
1298
  function normalizeOptionalNumber(value: number | null | undefined): number | undefined {
1146
1299
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1147
1300
  }
@@ -1158,6 +1311,10 @@ function isString(value: unknown): value is string {
1158
1311
  return typeof value === "string";
1159
1312
  }
1160
1313
 
1314
+ function isRecord(value: unknown): value is Record<string, unknown> {
1315
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1316
+ }
1317
+
1161
1318
  async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
1162
1319
  const [subcommand, first, second, third] = args.positional;
1163
1320
 
@@ -1756,6 +1913,65 @@ async function requireLocalDeployState(): Promise<LocalDeployState> {
1756
1913
  return state;
1757
1914
  }
1758
1915
 
1916
+ function requireDeployUrl(state: Pick<LocalDeployState, "deploy_id" | "url">): string {
1917
+ if (!state.url) {
1918
+ throw new Error(`Deploy ${state.deploy_id} does not have a URL. Run agentkit deploy again.`);
1919
+ }
1920
+
1921
+ return state.url;
1922
+ }
1923
+
1924
+ async function readDeployAccessTokenForSmoke(cwd: string, state: LocalDeployState): Promise<string | undefined> {
1925
+ if (state.access?.mode === "public") {
1926
+ return undefined;
1927
+ }
1928
+
1929
+ const root = await findAgentCapsuleRoot(cwd);
1930
+ return readDeployAccessTokenForUi(join(root, defaultDeployChatAccessTokenPath));
1931
+ }
1932
+
1933
+ async function runDeploySmoke(input: { deployUrl: string; message: string; token?: string }): Promise<void> {
1934
+ const headers = new Headers({ "Content-Type": "application/json" });
1935
+
1936
+ if (input.token) {
1937
+ headers.set("Authorization", `Bearer ${input.token}`);
1938
+ }
1939
+
1940
+ let response: Response;
1941
+
1942
+ try {
1943
+ response = await fetch(new URL("/v1/chat", input.deployUrl).href, {
1944
+ method: "POST",
1945
+ headers,
1946
+ body: JSON.stringify({ message: input.message }),
1947
+ });
1948
+ } catch (error) {
1949
+ throw new AgentKitError(
1950
+ "deploy_smoke_failed",
1951
+ `Deploy smoke request failed before a response: ${error instanceof Error ? error.message : String(error)}`,
1952
+ );
1953
+ }
1954
+
1955
+ const payload = await response.json().catch(() => null);
1956
+
1957
+ if (!response.ok) {
1958
+ throw new AgentKitError("deploy_smoke_failed", readCloudError(payload, response.status));
1959
+ }
1960
+
1961
+ const content =
1962
+ payload && typeof payload === "object" && "message" in payload && isRecord((payload as { message?: unknown }).message)
1963
+ ? (payload as { message: { content?: unknown } }).message.content
1964
+ : undefined;
1965
+
1966
+ console.log("");
1967
+ console.log("Deploy smoke verified");
1968
+ console.log(`URL: ${input.deployUrl}`);
1969
+ console.log(`Message: ${input.message}`);
1970
+ if (typeof content === "string") {
1971
+ console.log(`Response: ${content}`);
1972
+ }
1973
+ }
1974
+
1759
1975
  async function resolveProjectIdForSecretCommand(cwd: string): Promise<string> {
1760
1976
  const state = await readLocalDeployStateIfExists(cwd);
1761
1977
 
@@ -1951,8 +2167,9 @@ async function resolveSecretInputValue(options: {
1951
2167
  fromLocalEnvFlag: string | boolean | undefined;
1952
2168
  localEnvRoot?: string;
1953
2169
  usage: string;
2170
+ inlineSensitiveHelp?: string;
1954
2171
  }): Promise<string> {
1955
- const { name, valueParts, stdinFlag, fromEnvFlag, fromLocalEnvFlag, localEnvRoot, usage } = options;
2172
+ const { name, valueParts, stdinFlag, fromEnvFlag, fromLocalEnvFlag, localEnvRoot, usage, inlineSensitiveHelp } = options;
1956
2173
 
1957
2174
  if (!name) {
1958
2175
  throw new Error(`Missing name. Usage: ${usage}`);
@@ -1972,6 +2189,11 @@ async function resolveSecretInputValue(options: {
1972
2189
 
1973
2190
  if (hasInlineValue) {
1974
2191
  value = valueParts.join(" ");
2192
+ if (looksLikeSecretName(name)) {
2193
+ console.error(
2194
+ `Warning: ${name} looks sensitive. ${inlineSensitiveHelp ?? "Prefer --stdin or --from-env so the value does not enter shell history."}`,
2195
+ );
2196
+ }
1975
2197
  } else if (hasStdin) {
1976
2198
  if (stdinFlag !== true) {
1977
2199
  throw new Error(`--stdin does not take a value. Usage: ${usage}`);
@@ -2013,6 +2235,10 @@ async function resolveSecretInputValue(options: {
2013
2235
  return value;
2014
2236
  }
2015
2237
 
2238
+ function looksLikeSecretName(name: string): boolean {
2239
+ return /(?:SECRET|TOKEN|KEY|PASSWORD|CREDENTIAL|PRIVATE)/i.test(name);
2240
+ }
2241
+
2016
2242
  async function readStdinText(): Promise<string> {
2017
2243
  const chunks: Buffer[] = [];
2018
2244
 
@@ -2147,6 +2373,84 @@ function parseJsonInput(source: string, label: string): unknown {
2147
2373
  }
2148
2374
  }
2149
2375
 
2376
+ async function findRunningLocalDevServer(cwd: string): Promise<{ url: string; agent: string } | null> {
2377
+ const capsule = await loadAgentCapsule(cwd);
2378
+ const basePort = localDevBasePort();
2379
+
2380
+ for (let port = basePort; port <= basePort + 50; port += 1) {
2381
+ const state = await fetchLocalAgentKitState(`http://localhost:${port}/_agentkit`);
2382
+
2383
+ if (state?.agent === capsule.config.name) {
2384
+ return {
2385
+ url: `http://localhost:${port}`,
2386
+ agent: state.agent,
2387
+ };
2388
+ }
2389
+ }
2390
+
2391
+ return null;
2392
+ }
2393
+
2394
+ function localDevBasePort(): number {
2395
+ const value = process.env.AGENTKIT_DEV_BASE_PORT;
2396
+
2397
+ if (!value) {
2398
+ return defaultAgentKitDevPort;
2399
+ }
2400
+
2401
+ const parsed = Number(value);
2402
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : defaultAgentKitDevPort;
2403
+ }
2404
+
2405
+ async function fetchLocalAgentKitState(url: string): Promise<{ agent?: string } | null> {
2406
+ const controller = new AbortController();
2407
+ const timeout = setTimeout(() => controller.abort(), 300);
2408
+
2409
+ try {
2410
+ const response = await fetch(url, { signal: controller.signal });
2411
+ const payload = await response.json().catch(() => null);
2412
+
2413
+ if (payload && typeof payload === "object") {
2414
+ return {
2415
+ agent: typeof (payload as { agent?: unknown }).agent === "string" ? (payload as { agent: string }).agent : undefined,
2416
+ };
2417
+ }
2418
+ } catch {
2419
+ return null;
2420
+ } finally {
2421
+ clearTimeout(timeout);
2422
+ }
2423
+
2424
+ return null;
2425
+ }
2426
+
2427
+ async function openLocalUrl(url: string): Promise<void> {
2428
+ if (process.env.AGENTKIT_OPEN_COMMAND === "none") {
2429
+ return;
2430
+ }
2431
+
2432
+ const command =
2433
+ process.env.AGENTKIT_OPEN_COMMAND ??
2434
+ (process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open");
2435
+ const args = process.env.AGENTKIT_OPEN_COMMAND
2436
+ ? [url]
2437
+ : process.platform === "win32"
2438
+ ? ["/c", "start", "", url]
2439
+ : [url];
2440
+
2441
+ await new Promise<void>((resolvePromise, reject) => {
2442
+ const child = spawn(command, args, {
2443
+ detached: true,
2444
+ stdio: "ignore",
2445
+ });
2446
+ child.once("error", reject);
2447
+ child.once("spawn", () => {
2448
+ child.unref();
2449
+ resolvePromise();
2450
+ });
2451
+ });
2452
+ }
2453
+
2150
2454
  async function startDeployChatUiServer(
2151
2455
  cwd: string,
2152
2456
  options: { port: number; tokenFile?: string },
@@ -37,6 +37,17 @@ export const testProviderAdapter: ProviderAdapter = {
37
37
  };
38
38
  }
39
39
 
40
+ const rememberedName = answerRememberedName(input.messages);
41
+
42
+ if (rememberedName) {
43
+ return {
44
+ message: {
45
+ role: "assistant",
46
+ content: rememberedName,
47
+ },
48
+ };
49
+ }
50
+
40
51
  return {
41
52
  message: {
42
53
  role: "assistant",
@@ -58,6 +69,46 @@ function findLastUserMessage(messages: AgentMessage[]): AgentMessage | undefined
58
69
  return undefined;
59
70
  }
60
71
 
72
+ function answerRememberedName(messages: AgentMessage[]): string | null {
73
+ const lastUserMessage = findLastUserMessage(messages);
74
+
75
+ if (!lastUserMessage || !asksForName(lastUserMessage.content)) {
76
+ return null;
77
+ }
78
+
79
+ for (let index = messages.length - 2; index >= 0; index -= 1) {
80
+ const message = messages[index];
81
+
82
+ if (message.role !== "user") {
83
+ continue;
84
+ }
85
+
86
+ const name = extractName(message.content);
87
+
88
+ if (name) {
89
+ return /meu nome|qual\s+(é|e)\s*meu nome|como\s+(é|e)\s*meu nome/i.test(lastUserMessage.content)
90
+ ? `Seu nome é ${name}.`
91
+ : `Your name is ${name}.`;
92
+ }
93
+ }
94
+
95
+ return null;
96
+ }
97
+
98
+ function asksForName(content: string): boolean {
99
+ return /what(?:'s| is) my name\??/i.test(content) || /(como|qual)\s+(é|e)\s*(o\s+)?meu nome\??/i.test(content);
100
+ }
101
+
102
+ function extractName(content: string): string | null {
103
+ const match = content.match(/\bmy name is\s+(.+)$/i) ?? content.match(/\bmeu nome (?:é|e)\s+(.+)$/i);
104
+
105
+ if (!match) {
106
+ return null;
107
+ }
108
+
109
+ return match[1].trim().replace(/[.!?]+$/g, "") || null;
110
+ }
111
+
61
112
  function parseToolRequest(content: string): { name: string; input?: unknown } | null {
62
113
  let parsed: unknown;
63
114