agent-office 0.4.5 → 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 +3 -0
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.js +30 -3
- package/dist/lib/pi-coding-server.d.ts +20 -0
- package/dist/lib/pi-coding-server.js +162 -0
- package/package.json +3 -1
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);
|
package/dist/commands/serve.d.ts
CHANGED
package/dist/commands/serve.js
CHANGED
|
@@ -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
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-office",
|
|
3
|
-
"version": "0.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",
|