@agent-play/cli 3.0.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/README.md +76 -2
  2. package/dist/.root +1 -0
  3. package/dist/README.md +91 -0
  4. package/dist/cli.js +968 -184
  5. package/package.json +11 -5
  6. package/templates/agent-starter/langchain/.env.example +6 -0
  7. package/templates/agent-starter/langchain/README.md +24 -0
  8. package/templates/agent-starter/langchain/package.json +25 -0
  9. package/templates/agent-starter/langchain/src/bare-server.ts +12 -0
  10. package/templates/agent-starter/langchain/src/builtins/definitions.ts +66 -0
  11. package/templates/agent-starter/langchain/src/builtins/toolkits/starter-tools.ts +84 -0
  12. package/templates/agent-starter/langchain/src/express-server.ts +27 -0
  13. package/templates/agent-starter/langchain/src/index.ts +6 -0
  14. package/templates/agent-starter/langchain/src/load-env.ts +5 -0
  15. package/templates/agent-starter/langchain/src/register/register-builtins.ts +62 -0
  16. package/templates/agent-starter/langchain/src/register-agents.ts +1 -0
  17. package/templates/agent-starter/langchain/src/tool-handlers/assist-brainstorm.ts +8 -0
  18. package/templates/agent-starter/langchain/src/tool-handlers/assist-calculate-coefficient.ts +35 -0
  19. package/templates/agent-starter/langchain/src/tool-handlers/assist-collect-scene-details.ts +32 -0
  20. package/templates/agent-starter/langchain/src/tool-handlers/chat-tool.ts +6 -0
  21. package/templates/agent-starter/langchain/src/tool-handlers/execute-tool-capability.ts +12 -0
  22. package/templates/agent-starter/langchain/src/tool-handlers/tool-capability-registry.ts +49 -0
  23. package/templates/agent-starter/langchain/tsconfig.json +13 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agent-play/cli",
3
- "version": "3.0.1",
4
- "description": "Command-line tool for Agent Play: login, API keys, and agent registration against the web UI.",
3
+ "version": "3.2.0",
4
+ "description": "Command-line tool for Agent Play: node configurations, agent starter kit, and agent registration against the web UI.",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "bin": {
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
+ "templates",
12
13
  "README.md"
13
14
  ],
14
15
  "repository": {
@@ -20,12 +21,17 @@
20
21
  "access": "public"
21
22
  },
22
23
  "scripts": {
23
- "build": "tsup src/cli.ts --format esm --platform node --target node20 --out-dir dist --clean"
24
+ "build": "tsup src/cli.ts --format esm --platform node --target node20 --out-dir dist --clean --external @agent-play/node-tools && node ../../scripts/copy-root-file.mjs cli && cp README.md dist/README.md",
25
+ "test": "vitest run",
26
+ "smoke:initialize": "node ./scripts/smoke-initialize.mjs"
27
+ },
28
+ "dependencies": {
29
+ "@agent-play/node-tools": "1.0.0"
24
30
  },
25
- "dependencies": {},
26
31
  "devDependencies": {
27
32
  "@types/node": "^22.10.0",
28
33
  "tsup": "^8.5.1",
29
- "typescript": "~5.9.3"
34
+ "typescript": "~5.9.3",
35
+ "vitest": "^4.1.1"
30
36
  }
31
37
  }
@@ -0,0 +1,6 @@
1
+ AGENT_PLAY_WEB_UI_URL=https://agent-play.com
2
+ AGENT_PLAY_MAIN_NODE_ID=
3
+ AGENT_PLAY_AGENT_NODE_ID_1=
4
+ AGENT_PLAY_AGENT_NODE_ID_2=
5
+ OPENAI_API_KEY=
6
+ P2A_WEBRTC_ENABLED=1
@@ -0,0 +1,24 @@
1
+ # __PROJECT_NAME__
2
+
3
+ Starter scaffold generated by `agent-play initialize`.
4
+
5
+ ## Quick start
6
+
7
+ 1. Install dependencies:
8
+ - `npm install`
9
+ 2. Prepare local env:
10
+ - `cp .env.example .env`
11
+ 3. If you skipped bootstrap during initialize:
12
+ - `npx agent-play create-main-node`
13
+ - `npx agent-play create-agent-node` (once or twice)
14
+ - copy values from `~/.agent-play/credentials.json` into `.env`
15
+ 4. Run:
16
+ - `npm run dev`
17
+
18
+ ## Node identity env contract
19
+
20
+ - `AGENT_PLAY_MAIN_NODE_ID`: main developer node id.
21
+ - `AGENT_PLAY_AGENT_NODE_ID_1`: first agent node id.
22
+ - `AGENT_PLAY_AGENT_NODE_ID_2`: optional second agent node id.
23
+
24
+ The generated runtime uses env variables for node ids; no hardcoded identities are embedded.
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json",
8
+ "dev": "tsx src/index.ts",
9
+ "start": "node dist/index.js"
10
+ },
11
+ "dependencies": {
12
+ "@agent-play/sdk": "^3.2.1",
13
+ "@langchain/openai": "^1.3.0",
14
+ "dotenv": "^17.3.1",
15
+ "express": "^4.21.2",
16
+ "langchain": "^1.2.36",
17
+ "zod": "^3.24.2"
18
+ },
19
+ "devDependencies": {
20
+ "@types/express": "^4.17.21",
21
+ "@types/node": "^22.10.0",
22
+ "tsx": "^4.21.0",
23
+ "typescript": "~5.9.3"
24
+ }
25
+ }
@@ -0,0 +1,12 @@
1
+ import { loadEnv } from "./load-env.js";
2
+ import { registerAgents } from "./register-agents.js";
3
+
4
+ export async function startServer(): Promise<void> {
5
+ loadEnv();
6
+ const { world, registeredAgentIds } = await registerAgents();
7
+ console.log(`Registered ${String(registeredAgentIds.length)} agent(s):`);
8
+ for (const id of registeredAgentIds) {
9
+ console.log(` - ${id}`);
10
+ }
11
+ await world.hold().for(30 * 60);
12
+ }
@@ -0,0 +1,66 @@
1
+ import { ChatOpenAI } from "@langchain/openai";
2
+ import { createAgent } from "langchain";
3
+ import { calculatorTools, policeReportTools } from "./toolkits/starter-tools.js";
4
+
5
+ function randomAgentName(prefix: string): string {
6
+ return `${prefix}-${Math.random().toString(36).slice(2, 8)}`;
7
+ }
8
+
9
+ function randomSystemPrompt(prefix: string, rolePrompt: string): string {
10
+ const nonce = Math.random().toString(36).slice(2, 10);
11
+ return `${rolePrompt} Session nonce: ${nonce}. Agent label: ${prefix}.`;
12
+ }
13
+
14
+ function requiredEnv(name: string): string {
15
+ const value = process.env[name]?.trim();
16
+ if (value === undefined || value.length === 0) {
17
+ throw new Error(`Missing required env var: ${name}`);
18
+ }
19
+ return value;
20
+ }
21
+
22
+ export type StarterAgentDefinition = {
23
+ nodeId: string;
24
+ name: string;
25
+ type: "langchain";
26
+ agent: ReturnType<typeof createAgent>;
27
+ };
28
+
29
+ export function getStarterAgentDefinitions(agentCount: 1 | 2): StarterAgentDefinition[] {
30
+ const model = new ChatOpenAI({
31
+ apiKey: requiredEnv("OPENAI_API_KEY"),
32
+ model: process.env.OPENAI_MODEL?.trim() || "gpt-4.1",
33
+ });
34
+ const first = {
35
+ nodeId: requiredEnv("AGENT_PLAY_AGENT_NODE_ID_1"),
36
+ name: randomAgentName("CalculatorAgent"),
37
+ type: "langchain" as const,
38
+ agent: createAgent({
39
+ name: randomAgentName("lc-calculator-agent"),
40
+ model,
41
+ tools: [...calculatorTools],
42
+ systemPrompt: randomSystemPrompt(
43
+ "Calculator Agent",
44
+ "You are a rigorous calculator agent for production operations. Always validate the equation format, show the extracted coefficient and brief reasoning, and ask clarifying questions when equation syntax is ambiguous. Use chat_tool for conversational explanations and assist_calculate_coefficient for structured extraction."
45
+ ),
46
+ }),
47
+ };
48
+ if (agentCount === 1) {
49
+ return [first];
50
+ }
51
+ const second = {
52
+ nodeId: requiredEnv("AGENT_PLAY_AGENT_NODE_ID_2"),
53
+ name: randomAgentName("PoliceReportAgent"),
54
+ type: "langchain" as const,
55
+ agent: createAgent({
56
+ name: randomAgentName("lc-police-report-agent"),
57
+ model,
58
+ tools: [...policeReportTools],
59
+ systemPrompt: randomSystemPrompt(
60
+ "Police Report Agent",
61
+ "You are a police report intake agent. Collect factual, time-stamped incident details, separate witness statements from assumptions, and maintain neutral language suitable for official records. Use chat_tool for guided conversation and assist_collect_scene_details to gather structured scene data."
62
+ ),
63
+ }),
64
+ };
65
+ return [first, second];
66
+ }
@@ -0,0 +1,84 @@
1
+ import { tool } from "langchain";
2
+ import { z } from "zod";
3
+
4
+ const calculatorChatTool = tool(
5
+ ({ message }: { message: string }) => `calculator:${message}`,
6
+ {
7
+ name: "chat_tool",
8
+ description: "Production-style calculator assistant chat tool.",
9
+ schema: z.object({
10
+ message: z.string(),
11
+ }),
12
+ }
13
+ );
14
+
15
+ const assistCalculateCoefficient = tool(
16
+ ({
17
+ equation,
18
+ variable,
19
+ }: {
20
+ equation: string;
21
+ variable: string;
22
+ }) => `coefficient:${equation}:${variable}`,
23
+ {
24
+ name: "assist_calculate_coefficient",
25
+ description:
26
+ "Extract the coefficient of a variable from a linear or polynomial equation string.",
27
+ schema: z.object({
28
+ equation: z.string(),
29
+ variable: z.string(),
30
+ }),
31
+ }
32
+ );
33
+
34
+ const policeChatTool = tool(
35
+ ({ message }: { message: string }) => `police:${message}`,
36
+ {
37
+ name: "chat_tool",
38
+ description: "Production-style police incident reporting chat tool.",
39
+ schema: z.object({
40
+ message: z.string(),
41
+ }),
42
+ }
43
+ );
44
+
45
+ const assistCollectSceneDetails = tool(
46
+ ({
47
+ location,
48
+ incidentType,
49
+ witnesses,
50
+ injuriesReported,
51
+ suspectDescription,
52
+ immediateRisk,
53
+ }: {
54
+ location: string;
55
+ incidentType: string;
56
+ witnesses?: string;
57
+ injuriesReported?: boolean;
58
+ suspectDescription?: string;
59
+ immediateRisk?: "low" | "medium" | "high";
60
+ }) =>
61
+ `scene:${location}:${incidentType}:${witnesses ?? ""}:${String(injuriesReported ?? false)}:${
62
+ suspectDescription ?? ""
63
+ }:${immediateRisk ?? "low"}`,
64
+ {
65
+ name: "assist_collect_scene_details",
66
+ description:
67
+ "Collect structured details from an incident scene report for police documentation.",
68
+ schema: z.object({
69
+ location: z.string(),
70
+ incidentType: z.string(),
71
+ witnesses: z.string().optional(),
72
+ injuriesReported: z.boolean().optional(),
73
+ suspectDescription: z.string().optional(),
74
+ immediateRisk: z.enum(["low", "medium", "high"]).optional(),
75
+ }),
76
+ }
77
+ );
78
+
79
+ export const calculatorTools = [
80
+ calculatorChatTool,
81
+ assistCalculateCoefficient,
82
+ ] as const;
83
+
84
+ export const policeReportTools = [policeChatTool, assistCollectSceneDetails] as const;
@@ -0,0 +1,27 @@
1
+ import express from "express";
2
+ import { loadEnv } from "./load-env.js";
3
+ import { registerAgents } from "./register-agents.js";
4
+
5
+ export async function startServer(): Promise<void> {
6
+ loadEnv();
7
+ const port = Number(process.env.PORT ?? "3100");
8
+ const host = process.env.HOST ?? "0.0.0.0";
9
+
10
+ const app = express();
11
+ app.use(express.json());
12
+ app.get("/health", (_req, res) => {
13
+ res.json({ ok: true });
14
+ });
15
+
16
+ const { world, registeredAgentIds } = await registerAgents();
17
+ console.log(`Registered ${String(registeredAgentIds.length)} agent(s):`);
18
+ for (const id of registeredAgentIds) {
19
+ console.log(` - ${id}`);
20
+ }
21
+
22
+ app.listen(port, host, () => {
23
+ console.log(`[starter-express] listening on http://${host}:${String(port)}`);
24
+ });
25
+
26
+ await world.hold().for(30 * 60);
27
+ }
@@ -0,0 +1,6 @@
1
+ import { startServer } from "__SERVER_MODULE__";
2
+
3
+ void startServer().catch((error: unknown) => {
4
+ console.error(error);
5
+ process.exitCode = 1;
6
+ });
@@ -0,0 +1,5 @@
1
+ import { config } from "dotenv";
2
+
3
+ export function loadEnv(): void {
4
+ config();
5
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ langchainRegistration,
3
+ RemotePlayWorld,
4
+ type RegisteredPlayer,
5
+ } from "@agent-play/sdk";
6
+ import { getStarterAgentDefinitions } from "../builtins/definitions.js";
7
+ import { executeToolCapability } from "../tool-handlers/execute-tool-capability.js";
8
+
9
+ type RegisterResult = {
10
+ world: RemotePlayWorld;
11
+ registeredAgentIds: string[];
12
+ };
13
+
14
+ function requiredEnv(name: string): string {
15
+ const value = process.env[name]?.trim();
16
+ if (value === undefined || value.length === 0) {
17
+ throw new Error(`Missing required env var: ${name}`);
18
+ }
19
+ return value;
20
+ }
21
+
22
+ export async function registerBuiltinAgents(): Promise<RegisterResult> {
23
+ const mainNodeId = requiredEnv("AGENT_PLAY_MAIN_NODE_ID");
24
+ const world = new RemotePlayWorld({ logging: "on" });
25
+ const openAiApiKey = process.env.OPENAI_API_KEY?.trim();
26
+ if (openAiApiKey !== undefined && openAiApiKey.length > 0) {
27
+ world.initAudio({
28
+ openai: {
29
+ apiKey: openAiApiKey,
30
+ },
31
+ });
32
+ }
33
+ await world.connect({ mainNodeId });
34
+
35
+ const hasSecondAgent =
36
+ (process.env.AGENT_PLAY_AGENT_NODE_ID_2?.trim() ?? "").length > 0;
37
+ const definitions = getStarterAgentDefinitions(hasSecondAgent ? 2 : 1);
38
+ const registeredAgentIds: string[] = [];
39
+ const chatAgentsByPlayerId = new Map<string, unknown>();
40
+ const registeredPlayers: RegisteredPlayer[] = [];
41
+ for (const def of definitions) {
42
+ const registered = await world.addAgent({
43
+ name: def.name,
44
+ type: def.type,
45
+ agent: langchainRegistration(def.agent),
46
+ nodeId: def.nodeId,
47
+ enableP2a: "on",
48
+ });
49
+ registeredAgentIds.push(registered.id);
50
+ registeredPlayers.push(registered);
51
+ }
52
+ for (let i = 0; i < registeredPlayers.length; i++) {
53
+ chatAgentsByPlayerId.set(registeredPlayers[i].id, definitions[i].agent);
54
+ }
55
+ world.subscribeIntercomCommands({
56
+ playerIds: registeredPlayers.map((player) => player.id),
57
+ executeTool: executeToolCapability,
58
+ chatAgentsByPlayerId: chatAgentsByPlayerId as Map<string, never>,
59
+ });
60
+
61
+ return { world, registeredAgentIds };
62
+ }
@@ -0,0 +1 @@
1
+ export { registerBuiltinAgents as registerAgents } from "./register/register-builtins.js";
@@ -0,0 +1,8 @@
1
+ export function executeAssistBrainstorm(args: {
2
+ prompt?: string;
3
+ }): Record<string, unknown> {
4
+ const prompt = typeof args.prompt === "string" ? args.prompt : "";
5
+ return {
6
+ summary: `Brainstorm starter for: ${prompt}`,
7
+ };
8
+ }
@@ -0,0 +1,35 @@
1
+ function normalizeEquation(equation: string): string {
2
+ return equation.replace(/\s+/g, "");
3
+ }
4
+
5
+ export function executeAssistCalculateCoefficient(args: {
6
+ equation?: string;
7
+ variable?: string;
8
+ }): Record<string, unknown> {
9
+ const equation = typeof args.equation === "string" ? args.equation : "";
10
+ const variable = typeof args.variable === "string" ? args.variable : "";
11
+ if (equation.length === 0 || variable.length === 0) {
12
+ return {
13
+ ok: false,
14
+ error: "equation and variable are required.",
15
+ };
16
+ }
17
+ const normalized = normalizeEquation(equation);
18
+ const regex = new RegExp(`([+-]?\\d*\\.?\\d*)${variable}`);
19
+ const match = normalized.match(regex);
20
+ if (match === null) {
21
+ return {
22
+ ok: false,
23
+ error: `No coefficient found for variable ${variable}.`,
24
+ };
25
+ }
26
+ const raw = match[1];
27
+ const coefficient =
28
+ raw === "" || raw === "+" ? 1 : raw === "-" ? -1 : Number(raw);
29
+ return {
30
+ ok: Number.isFinite(coefficient),
31
+ variable,
32
+ coefficient,
33
+ equation,
34
+ };
35
+ }
@@ -0,0 +1,32 @@
1
+ export function executeAssistCollectSceneDetails(args: {
2
+ location?: string;
3
+ incidentType?: string;
4
+ witnesses?: string;
5
+ injuriesReported?: boolean;
6
+ suspectDescription?: string;
7
+ immediateRisk?: "low" | "medium" | "high";
8
+ }): Record<string, unknown> {
9
+ const location = typeof args.location === "string" ? args.location : "";
10
+ const incidentType =
11
+ typeof args.incidentType === "string" ? args.incidentType : "";
12
+ if (location.length === 0 || incidentType.length === 0) {
13
+ return {
14
+ ok: false,
15
+ error: "location and incidentType are required.",
16
+ };
17
+ }
18
+ return {
19
+ ok: true,
20
+ scene: {
21
+ location,
22
+ incidentType,
23
+ witnesses: typeof args.witnesses === "string" ? args.witnesses : "",
24
+ injuriesReported: args.injuriesReported === true,
25
+ suspectDescription:
26
+ typeof args.suspectDescription === "string"
27
+ ? args.suspectDescription
28
+ : "",
29
+ immediateRisk: args.immediateRisk ?? "low",
30
+ },
31
+ };
32
+ }
@@ -0,0 +1,6 @@
1
+ export function executeChatTool(args: { message?: string }): Record<string, unknown> {
2
+ const message = typeof args.message === "string" ? args.message : "";
3
+ return {
4
+ reply: `Echo from __PROJECT_NAME__: ${message}`,
5
+ };
6
+ }
@@ -0,0 +1,12 @@
1
+ import { resolveToolCapabilityHandler } from "./tool-capability-registry.js";
2
+
3
+ export async function executeToolCapability(options: {
4
+ toolName: string;
5
+ args: Record<string, unknown>;
6
+ }): Promise<Record<string, unknown>> {
7
+ const handler = resolveToolCapabilityHandler(options.toolName);
8
+ if (handler === null) {
9
+ throw new Error(`unknown tool capability: ${options.toolName}`);
10
+ }
11
+ return await Promise.resolve(handler(options.args));
12
+ }
@@ -0,0 +1,49 @@
1
+ import { executeAssistCalculateCoefficient } from "./assist-calculate-coefficient.js";
2
+ import { executeAssistCollectSceneDetails } from "./assist-collect-scene-details.js";
3
+ import { executeChatTool } from "./chat-tool.js";
4
+
5
+ export type ToolCapabilityHandler = (
6
+ args: Record<string, unknown>
7
+ ) => Record<string, unknown> | Promise<Record<string, unknown>>;
8
+
9
+ const registry = new Map<string, ToolCapabilityHandler>([
10
+ ["chat_tool", (args) => executeChatTool({ message: String(args.message ?? "") })],
11
+ [
12
+ "assist_calculate_coefficient",
13
+ (args) =>
14
+ executeAssistCalculateCoefficient({
15
+ equation: String(args.equation ?? ""),
16
+ variable: String(args.variable ?? ""),
17
+ }),
18
+ ],
19
+ [
20
+ "assist_collect_scene_details",
21
+ (args) =>
22
+ executeAssistCollectSceneDetails({
23
+ location: String(args.location ?? ""),
24
+ incidentType: String(args.incidentType ?? ""),
25
+ witnesses:
26
+ typeof args.witnesses === "string" ? args.witnesses : undefined,
27
+ injuriesReported:
28
+ typeof args.injuriesReported === "boolean"
29
+ ? args.injuriesReported
30
+ : undefined,
31
+ suspectDescription:
32
+ typeof args.suspectDescription === "string"
33
+ ? args.suspectDescription
34
+ : undefined,
35
+ immediateRisk:
36
+ args.immediateRisk === "low" ||
37
+ args.immediateRisk === "medium" ||
38
+ args.immediateRisk === "high"
39
+ ? args.immediateRisk
40
+ : undefined,
41
+ }),
42
+ ],
43
+ ]);
44
+
45
+ export function resolveToolCapabilityHandler(
46
+ toolName: string
47
+ ): ToolCapabilityHandler | null {
48
+ return registry.get(toolName) ?? null;
49
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "noEmit": false,
10
+ "outDir": "dist"
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }