agent-office 0.4.4 → 0.4.6

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/dist/cli.js CHANGED
@@ -15,6 +15,9 @@ program
15
15
  .option("--port <port>", "Port to serve on", "7654")
16
16
  .option("--password <password>", "REQUIRED. API password", process.env.AGENT_OFFICE_PASSWORD)
17
17
  .option("--opencode-url <url>", "URL of the OpenCode server (default: http://127.0.0.1:4096)", process.env.OPENCODE_URL ?? "http://127.0.0.1:4096")
18
+ .option("--pi-vendor <vendor>", "PI coding vendor (e.g., xai, openai, anthropic). Required when using PI coding server.", process.env.PI_VENDOR)
19
+ .option("--pi-model <model>", "PI coding model name (e.g., grok-code-fast-1). Required when using PI coding server.", process.env.PI_MODEL)
20
+ .option("--pi-api-key <key>", "PI coding API key. Required when using PI coding server.", process.env.PI_API_KEY)
18
21
  .action(async (options) => {
19
22
  const { serve } = await import("./commands/serve.js");
20
23
  await serve(options);
@@ -5,6 +5,9 @@ interface ServeOptions {
5
5
  port: string;
6
6
  password?: string;
7
7
  opencodeUrl: string;
8
+ piVendor?: string;
9
+ piModel?: string;
10
+ piApiKey?: string;
8
11
  }
9
12
  export declare function serve(options: ServeOptions): Promise<void>;
10
13
  export {};
@@ -1,6 +1,7 @@
1
1
  import { createPostgresqlStorage, createSqliteStorage } from "../db/index.js";
2
2
  import { runMigrations } from "../db/migrate.js";
3
3
  import { OpenCodeCodingServer } from "../lib/opencode-coding-server.js";
4
+ import { PiCodingServer } from "../lib/pi-coding-server.js";
4
5
  import { createApp } from "../server/index.js";
5
6
  import { CronScheduler } from "../server/cron.js";
6
7
  export async function serve(options) {
@@ -39,9 +40,35 @@ export async function serve(options) {
39
40
  await storage.close();
40
41
  process.exit(1);
41
42
  }
42
- // Init agentic coding server (OpenCode implementation)
43
- const agenticCodingServer = new OpenCodeCodingServer(options.opencodeUrl);
44
- console.log(`Connecting to OpenCode server at ${options.opencodeUrl}...`);
43
+ // Init agentic coding server
44
+ let agenticCodingServer;
45
+ const piVendor = options.piVendor;
46
+ const piModel = options.piModel;
47
+ const piApiKey = options.piApiKey;
48
+ const hasAllPiOptions = piVendor && piModel && piApiKey;
49
+ const hasSomePiOptions = piVendor || piModel || piApiKey;
50
+ if (hasAllPiOptions) {
51
+ // Use PI Coding Server
52
+ agenticCodingServer = new PiCodingServer(piVendor, piModel, piApiKey);
53
+ console.log(`Using PI coding server with ${piVendor} ${piModel}...`);
54
+ }
55
+ else if (hasSomePiOptions) {
56
+ // Partial PI options - error
57
+ console.error("Error: All PI coding options must be provided together: --pi-vendor, --pi-model, --pi-api-key");
58
+ console.error(" Or set environment variables: PI_VENDOR, PI_MODEL, PI_API_KEY");
59
+ await storage.close();
60
+ process.exit(1);
61
+ }
62
+ else {
63
+ // Use OpenCode Coding Server (default)
64
+ agenticCodingServer = new OpenCodeCodingServer(options.opencodeUrl);
65
+ if (options.opencodeUrl === "http://127.0.0.1:4096") {
66
+ console.log(`Connecting to default OpenCode server at ${options.opencodeUrl}...`);
67
+ }
68
+ else {
69
+ console.log(`Connecting to OpenCode server at ${options.opencodeUrl}...`);
70
+ }
71
+ }
45
72
  const serverUrl = `http://${options.host}:${port}`;
46
73
  // Create cron scheduler
47
74
  const cronScheduler = new CronScheduler();
@@ -0,0 +1,20 @@
1
+ import type { AgenticCodingServer, SessionMessage, AgentMode } from "./agentic-coding-server.js";
2
+ export declare class PiCodingServer implements AgenticCodingServer {
3
+ private sessions;
4
+ private authStorage;
5
+ private modelRegistry;
6
+ private selectedModel;
7
+ private vendor;
8
+ private modelName;
9
+ constructor(vendor: string, model: string, apiKey: string);
10
+ /** Shared creation logic (no duplication) */
11
+ private createSessionInternal;
12
+ /** Fallback helper: returns cached session OR creates new one with the exact provided ID */
13
+ private ensureSession;
14
+ createSession(_title: string): Promise<string>;
15
+ deleteSession(sessionID: string): Promise<void>;
16
+ sendMessage(sessionID: string, text: string, agent: string, system: string): Promise<void>;
17
+ getMessages(sessionID: string, limit?: number): Promise<SessionMessage[]>;
18
+ revertSession(sessionID: string, messageID: string): Promise<void>;
19
+ getAgentModes(): Promise<AgentMode[]>;
20
+ }
@@ -0,0 +1,162 @@
1
+ import { createAgentSession, SessionManager, AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
2
+ import { getModel } from "@mariozechner/pi-ai";
3
+ import crypto from "crypto";
4
+ import path from "path";
5
+ import fs from "fs/promises";
6
+ export class PiCodingServer {
7
+ sessions = new Map();
8
+ authStorage;
9
+ modelRegistry;
10
+ selectedModel;
11
+ vendor;
12
+ modelName;
13
+ constructor(vendor, model, apiKey) {
14
+ if (!vendor || !model || !apiKey?.trim()) {
15
+ throw new Error("vendor, model, and apiKey are all required and must not be empty");
16
+ }
17
+ this.vendor = vendor.trim().toLowerCase();
18
+ this.modelName = model.trim();
19
+ this.authStorage = AuthStorage.create();
20
+ this.authStorage.setRuntimeApiKey(this.vendor, apiKey);
21
+ this.modelRegistry = new ModelRegistry(this.authStorage);
22
+ this.selectedModel = getModel(this.vendor, this.modelName);
23
+ if (!this.selectedModel) {
24
+ throw new Error(`Model '${this.modelName}' not found for provider '${this.vendor}'. ` +
25
+ `Check spelling or run 'pi /model' in a terminal.`);
26
+ }
27
+ }
28
+ /** Shared creation logic (no duplication) */
29
+ async createSessionInternal(sessionID) {
30
+ const workspace = path.join(process.cwd(), "workspaces", sessionID);
31
+ await fs.mkdir(workspace, { recursive: true });
32
+ const sessionManager = SessionManager.create(workspace);
33
+ const { session } = await createAgentSession({
34
+ cwd: workspace,
35
+ model: this.selectedModel,
36
+ thinkingLevel: "medium",
37
+ sessionManager,
38
+ authStorage: this.authStorage,
39
+ modelRegistry: this.modelRegistry,
40
+ });
41
+ return session;
42
+ }
43
+ /** Fallback helper: returns cached session OR creates new one with the exact provided ID */
44
+ async ensureSession(sessionID) {
45
+ let session = this.sessions.get(sessionID);
46
+ if (!session) {
47
+ session = await this.createSessionInternal(sessionID);
48
+ this.sessions.set(sessionID, session);
49
+ }
50
+ return session;
51
+ }
52
+ async createSession(_title) {
53
+ const sessionID = crypto.randomUUID();
54
+ const session = await this.createSessionInternal(sessionID);
55
+ this.sessions.set(sessionID, session);
56
+ return sessionID;
57
+ }
58
+ async deleteSession(sessionID) {
59
+ const session = this.sessions.get(sessionID);
60
+ if (session) {
61
+ session.dispose?.();
62
+ this.sessions.delete(sessionID);
63
+ }
64
+ }
65
+ async sendMessage(sessionID, text, agent, system) {
66
+ const session = await this.ensureSession(sessionID);
67
+ const prefix = agent ? `[Message from coworker ${agent}]: ` : "";
68
+ const message = `${prefix}${text}`;
69
+ // Set the system prompt if provided
70
+ if (system && system.trim()) {
71
+ session.agent.setSystemPrompt(system);
72
+ }
73
+ // Create a promise that resolves when the agent finishes processing
74
+ const completionPromise = new Promise((resolve) => {
75
+ const unsubscribe = session.subscribe((event) => {
76
+ if (event.type === "agent_end") {
77
+ unsubscribe();
78
+ resolve();
79
+ }
80
+ });
81
+ // Timeout after 5 minutes in case agent never finishes
82
+ setTimeout(() => {
83
+ unsubscribe();
84
+ resolve();
85
+ }, 5 * 60 * 1000);
86
+ });
87
+ // Send the prompt
88
+ await session.prompt(message);
89
+ // Wait for processing to complete
90
+ await completionPromise;
91
+ }
92
+ async getMessages(sessionID, limit = 100) {
93
+ const session = await this.ensureSession(sessionID);
94
+ const msgs = session.messages || [];
95
+ return msgs.slice(-limit).map((m) => {
96
+ let role = "assistant";
97
+ let parts = [];
98
+ if (m.role === "user") {
99
+ role = "user";
100
+ // User message content can be string or array
101
+ if (typeof m.content === "string") {
102
+ parts = [{ type: "text", text: m.content }];
103
+ }
104
+ else if (Array.isArray(m.content)) {
105
+ parts = m.content.map((c) => {
106
+ if (c.type === "text")
107
+ return { type: "text", text: c.text };
108
+ return { type: c.type || "text", text: JSON.stringify(c) };
109
+ });
110
+ }
111
+ else {
112
+ parts = [{ type: "text", text: JSON.stringify(m.content) }];
113
+ }
114
+ }
115
+ else if (m.role === "assistant") {
116
+ role = "assistant";
117
+ // Assistant message content is always an array of TextContent | ThinkingContent | ToolCall
118
+ if (Array.isArray(m.content)) {
119
+ parts = m.content.map((c) => {
120
+ if (c.type === "text")
121
+ return { type: "text", text: c.text };
122
+ if (c.type === "thinking")
123
+ return { type: "thinking", text: c.thinking };
124
+ if (c.type === "toolCall")
125
+ return { type: "toolCall", text: `[Tool: ${c.name}]` };
126
+ return { type: c.type || "text", text: JSON.stringify(c) };
127
+ });
128
+ }
129
+ else if (typeof m.content === "string") {
130
+ parts = [{ type: "text", text: m.content }];
131
+ }
132
+ }
133
+ else {
134
+ // toolResult or other custom types
135
+ parts = [{ type: "text", text: JSON.stringify(m.content) }];
136
+ }
137
+ return {
138
+ id: m.id || m.entryId || crypto.randomUUID(),
139
+ role,
140
+ parts,
141
+ };
142
+ });
143
+ }
144
+ async revertSession(sessionID, messageID) {
145
+ const session = await this.ensureSession(sessionID);
146
+ try {
147
+ await session.navigateTree(messageID, { summarize: true });
148
+ }
149
+ catch (err) {
150
+ // Graceful: new/empty sessions can't revert → still succeed the call
151
+ }
152
+ }
153
+ async getAgentModes() {
154
+ return [
155
+ {
156
+ name: "coworker",
157
+ description: `${this.vendor.toUpperCase()} ${this.modelName} – fast agentic coding coworker`,
158
+ model: this.modelName,
159
+ },
160
+ ];
161
+ }
162
+ }
@@ -37,7 +37,7 @@ export function generateSystemPrompt(name, status, humanName, humanDescription,
37
37
  `The agent-office CLI is your PRIMARY means of communicating`,
38
38
  `with your human manager (${humanName}) and your coworkers.`,
39
39
  `Use it to send and receive messages, and to discover who`,
40
- `else is working.`,
40
+ `else is working. You have a special token that you use to use the CLI, and it's important you DO NOT SHARE THIS with other coworkers because it represents you`,
41
41
  ``,
42
42
  `════════════════════════════════════════════════════════`,
43
43
  ` AVAILABLE COMMANDS`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-office",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "An office for your AI agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,6 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@inkjs/ui": "^2.0.0",
36
+ "@mariozechner/pi-ai": "^0.54.2",
37
+ "@mariozechner/pi-coding-agent": "^0.54.2",
36
38
  "@opencode-ai/sdk": "^1.2.10",
37
39
  "agent-office": "^0.2.2",
38
40
  "better-sqlite3": "^12.6.2",