@openape/ape-agent 2.8.15 → 2.9.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.
- package/dist/bridge.mjs +16 -154
- package/package.json +3 -3
package/dist/bridge.mjs
CHANGED
|
@@ -1398,117 +1398,9 @@ function createHeuristicDetector() {
|
|
|
1398
1398
|
import { decodeJwt } from "jose";
|
|
1399
1399
|
import WebSocket from "ws";
|
|
1400
1400
|
|
|
1401
|
-
// src/chat-api.ts
|
|
1402
|
-
import { ofetch as ofetch6 } from "ofetch";
|
|
1403
|
-
var MAX_BODY = 1e4;
|
|
1404
|
-
var ChatApi = class {
|
|
1405
|
-
constructor(endpoint, bearer) {
|
|
1406
|
-
this.endpoint = endpoint;
|
|
1407
|
-
this.bearer = bearer;
|
|
1408
|
-
}
|
|
1409
|
-
endpoint;
|
|
1410
|
-
bearer;
|
|
1411
|
-
async postMessage(roomId, body, opts = {}) {
|
|
1412
|
-
const bodyForServer = opts.streaming ? body : clamp(body, MAX_BODY);
|
|
1413
|
-
const url = `${this.endpoint}/api/rooms/${encodeURIComponent(roomId)}/messages`;
|
|
1414
|
-
const payload = { body: bodyForServer };
|
|
1415
|
-
if (opts.replyTo) payload.reply_to = opts.replyTo;
|
|
1416
|
-
if (opts.threadId) payload.thread_id = opts.threadId;
|
|
1417
|
-
if (opts.streaming) payload.streaming = true;
|
|
1418
|
-
const result = await ofetch6(url, {
|
|
1419
|
-
method: "POST",
|
|
1420
|
-
headers: { Authorization: await this.bearer() },
|
|
1421
|
-
body: payload
|
|
1422
|
-
});
|
|
1423
|
-
return result;
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Fetch the most recent `limit` messages in a thread, oldest-first.
|
|
1427
|
-
* Used by ThreadSession to seed `history` so the agent has the
|
|
1428
|
-
* full conversation context after a bridge restart — otherwise it
|
|
1429
|
-
* only sees messages that arrived via WS since the process boot.
|
|
1430
|
-
*/
|
|
1431
|
-
async listMessages(roomId, threadId, limit = 50) {
|
|
1432
|
-
const url = `${this.endpoint}/api/rooms/${encodeURIComponent(roomId)}/messages?thread_id=${encodeURIComponent(threadId)}&limit=${limit}`;
|
|
1433
|
-
return await ofetch6(url, {
|
|
1434
|
-
method: "GET",
|
|
1435
|
-
headers: { Authorization: await this.bearer() }
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
async requestContact(peerEmail) {
|
|
1439
|
-
const url = `${this.endpoint}/api/contacts`;
|
|
1440
|
-
return await ofetch6(url, {
|
|
1441
|
-
method: "POST",
|
|
1442
|
-
headers: { Authorization: await this.bearer() },
|
|
1443
|
-
body: { email: peerEmail }
|
|
1444
|
-
});
|
|
1445
|
-
}
|
|
1446
|
-
async listContacts() {
|
|
1447
|
-
const url = `${this.endpoint}/api/contacts`;
|
|
1448
|
-
return await ofetch6(url, {
|
|
1449
|
-
method: "GET",
|
|
1450
|
-
headers: { Authorization: await this.bearer() }
|
|
1451
|
-
});
|
|
1452
|
-
}
|
|
1453
|
-
async acceptContact(peerEmail) {
|
|
1454
|
-
const url = `${this.endpoint}/api/contacts/${encodeURIComponent(peerEmail)}/accept`;
|
|
1455
|
-
return await ofetch6(url, {
|
|
1456
|
-
method: "POST",
|
|
1457
|
-
headers: { Authorization: await this.bearer() }
|
|
1458
|
-
});
|
|
1459
|
-
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Create a named thread in a room. Used by the cron-runner to drop
|
|
1462
|
-
* each task's runs into its own thread so the chat sidebar shows
|
|
1463
|
-
* one thread per task instead of all task DMs piling into the
|
|
1464
|
-
* main thread.
|
|
1465
|
-
*/
|
|
1466
|
-
async createThread(roomId, name) {
|
|
1467
|
-
const url = `${this.endpoint}/api/rooms/${encodeURIComponent(roomId)}/threads`;
|
|
1468
|
-
return await ofetch6(url, {
|
|
1469
|
-
method: "POST",
|
|
1470
|
-
headers: { Authorization: await this.bearer() },
|
|
1471
|
-
body: { name: name.slice(0, 100) }
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
|
-
* Update an in-flight or completed message. The server differentiates
|
|
1476
|
-
* three modes via the message's current `streaming` state and the
|
|
1477
|
-
* `streaming` field in this call:
|
|
1478
|
-
*
|
|
1479
|
-
* - Stream tick: pass `body` only (current accumulated text).
|
|
1480
|
-
* Server keeps streaming=true and does NOT bump edited_at.
|
|
1481
|
-
* - Stream end: pass `body` + `streaming: false`. Server clears
|
|
1482
|
-
* the streaming flag and triggers the user-facing push.
|
|
1483
|
-
* - Tool-call status: pass `streamingStatus` only (no body).
|
|
1484
|
-
* Renders as "🔧 time.now" in the typing-subtitle.
|
|
1485
|
-
* - Tool-call cleared: pass `streamingStatus: null`.
|
|
1486
|
-
*/
|
|
1487
|
-
async patchMessage(messageId, opts = {}) {
|
|
1488
|
-
const url = `${this.endpoint}/api/messages/${encodeURIComponent(messageId)}`;
|
|
1489
|
-
const payload = {};
|
|
1490
|
-
if (opts.body !== void 0) {
|
|
1491
|
-
payload.body = opts.streaming === false && opts.body.trim().length === 0 ? clamp(opts.body, MAX_BODY) : opts.body.length <= MAX_BODY ? opts.body : `${opts.body.slice(0, MAX_BODY - 1)}\u2026`;
|
|
1492
|
-
}
|
|
1493
|
-
if (opts.streaming !== void 0) payload.streaming = opts.streaming;
|
|
1494
|
-
if (opts.streamingStatus !== void 0) payload.streaming_status = opts.streamingStatus;
|
|
1495
|
-
if (Object.keys(payload).length === 0) return;
|
|
1496
|
-
await ofetch6(url, {
|
|
1497
|
-
method: "PATCH",
|
|
1498
|
-
headers: { Authorization: await this.bearer() },
|
|
1499
|
-
body: payload
|
|
1500
|
-
});
|
|
1501
|
-
}
|
|
1502
|
-
};
|
|
1503
|
-
function clamp(s2, max) {
|
|
1504
|
-
if (s2.trim().length === 0) return "\u2026";
|
|
1505
|
-
if (s2.length <= max) return s2;
|
|
1506
|
-
return `${s2.slice(0, max - 1)}\u2026`;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1401
|
// src/troop-chat-api.ts
|
|
1510
|
-
import { ofetch as
|
|
1511
|
-
var
|
|
1402
|
+
import { ofetch as ofetch6 } from "ofetch";
|
|
1403
|
+
var MAX_BODY = 64 * 1024;
|
|
1512
1404
|
var SYNTHETIC_THREAD_ID = "main";
|
|
1513
1405
|
function asHistory(msg, agentEmail, ownerEmail) {
|
|
1514
1406
|
return {
|
|
@@ -1542,7 +1434,7 @@ var TroopChatApi = class {
|
|
|
1542
1434
|
/** Resolve + cache the agent's chat row (lazy fetch on first use). */
|
|
1543
1435
|
async getBootstrap() {
|
|
1544
1436
|
if (this.bootstrap) return this.bootstrap;
|
|
1545
|
-
this.bootstrap = await
|
|
1437
|
+
this.bootstrap = await ofetch6(`${this.endpoint}/api/agents/me/chat`, {
|
|
1546
1438
|
method: "GET",
|
|
1547
1439
|
headers: { Authorization: await this.bearer() }
|
|
1548
1440
|
});
|
|
@@ -1557,11 +1449,11 @@ var TroopChatApi = class {
|
|
|
1557
1449
|
void roomId;
|
|
1558
1450
|
void opts.threadId;
|
|
1559
1451
|
const payload = {
|
|
1560
|
-
body: body.length >
|
|
1452
|
+
body: body.length > MAX_BODY ? `${body.slice(0, MAX_BODY - 1)}\u2026` : body
|
|
1561
1453
|
};
|
|
1562
1454
|
if (opts.replyTo) payload.reply_to = opts.replyTo;
|
|
1563
1455
|
if (opts.streaming) payload.streaming = true;
|
|
1564
|
-
const msg = await
|
|
1456
|
+
const msg = await ofetch6(`${this.endpoint}/api/agents/me/chat/messages`, {
|
|
1565
1457
|
method: "POST",
|
|
1566
1458
|
headers: { Authorization: await this.bearer() },
|
|
1567
1459
|
body: payload
|
|
@@ -1572,7 +1464,7 @@ var TroopChatApi = class {
|
|
|
1572
1464
|
void roomId;
|
|
1573
1465
|
void threadId;
|
|
1574
1466
|
void limit;
|
|
1575
|
-
const fresh = await
|
|
1467
|
+
const fresh = await ofetch6(`${this.endpoint}/api/agents/me/chat`, {
|
|
1576
1468
|
method: "GET",
|
|
1577
1469
|
headers: { Authorization: await this.bearer() }
|
|
1578
1470
|
});
|
|
@@ -1582,12 +1474,12 @@ var TroopChatApi = class {
|
|
|
1582
1474
|
async patchMessage(messageId, opts = {}) {
|
|
1583
1475
|
const payload = {};
|
|
1584
1476
|
if (opts.body !== void 0) {
|
|
1585
|
-
payload.body = opts.body.length >
|
|
1477
|
+
payload.body = opts.body.length > MAX_BODY ? `${opts.body.slice(0, MAX_BODY - 1)}\u2026` : opts.body;
|
|
1586
1478
|
}
|
|
1587
1479
|
if (opts.streaming !== void 0) payload.streaming = opts.streaming;
|
|
1588
1480
|
if (opts.streamingStatus !== void 0) payload.streaming_status = opts.streamingStatus;
|
|
1589
1481
|
if (Object.keys(payload).length === 0) return;
|
|
1590
|
-
await
|
|
1482
|
+
await ofetch6(`${this.endpoint}/api/agents/me/chat/messages/${encodeURIComponent(messageId)}`, {
|
|
1591
1483
|
method: "PATCH",
|
|
1592
1484
|
headers: { Authorization: await this.bearer() },
|
|
1593
1485
|
body: payload
|
|
@@ -5014,7 +4906,7 @@ function resolveTools(envFallback) {
|
|
|
5014
4906
|
}
|
|
5015
4907
|
return envFallback;
|
|
5016
4908
|
}
|
|
5017
|
-
var DEFAULT_ENDPOINT = "https://
|
|
4909
|
+
var DEFAULT_ENDPOINT = "https://troop.openape.ai";
|
|
5018
4910
|
var DEFAULT_APES_BIN = "apes";
|
|
5019
4911
|
var DEFAULT_MAX_STEPS = 10;
|
|
5020
4912
|
var DEFAULT_SYSTEM_PROMPT = `You are a helpful assistant in a 1:1 chat. Be concise and friendly. When asked for facts, say "I don't know" rather than guess.`;
|
|
@@ -5022,28 +4914,7 @@ var PING_INTERVAL_MS = 3e4;
|
|
|
5022
4914
|
var RECONNECT_BASE_MS = 1e3;
|
|
5023
4915
|
var RECONNECT_MAX_MS = 3e4;
|
|
5024
4916
|
var ALLOWLIST_POLL_INTERVAL_MS = 3e4;
|
|
5025
|
-
function loadBridgeEnvFile() {
|
|
5026
|
-
const path = join8(homedir9(), "Library", "Application Support", "openape", "bridge", ".env");
|
|
5027
|
-
if (!existsSync6(path)) return;
|
|
5028
|
-
try {
|
|
5029
|
-
const raw = readFileSync8(path, "utf8");
|
|
5030
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
5031
|
-
const trimmed = line.trim();
|
|
5032
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
5033
|
-
const eq = trimmed.indexOf("=");
|
|
5034
|
-
if (eq < 0) continue;
|
|
5035
|
-
const key = trimmed.slice(0, eq).trim();
|
|
5036
|
-
const value = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
5037
|
-
if (!key) continue;
|
|
5038
|
-
if (process3.env[key] === void 0) {
|
|
5039
|
-
process3.env[key] = value;
|
|
5040
|
-
}
|
|
5041
|
-
}
|
|
5042
|
-
} catch {
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
4917
|
function readConfig() {
|
|
5046
|
-
loadBridgeEnvFile();
|
|
5047
4918
|
const toolsRaw = process3.env.APE_CHAT_BRIDGE_TOOLS ?? "";
|
|
5048
4919
|
const tools = toolsRaw.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
5049
4920
|
const maxStepsRaw = process3.env.APE_CHAT_BRIDGE_MAX_STEPS;
|
|
@@ -5051,20 +4922,17 @@ function readConfig() {
|
|
|
5051
4922
|
const model = process3.env.APE_CHAT_BRIDGE_MODEL;
|
|
5052
4923
|
if (!model) {
|
|
5053
4924
|
throw new Error(
|
|
5054
|
-
"APE_CHAT_BRIDGE_MODEL is not set. Set it in the
|
|
4925
|
+
"APE_CHAT_BRIDGE_MODEL is not set. Set it in the container env (compose environment: block) or globally in `~/litellm/.env`. Common values: `gpt-5.4` (ChatGPT-only LiteLLM proxy), `claude-haiku-4-5` (Anthropic-only)."
|
|
5055
4926
|
);
|
|
5056
4927
|
}
|
|
5057
|
-
const targetRaw = (process3.env.OPENAPE_BRIDGE_TARGET ?? "chat").toLowerCase();
|
|
5058
|
-
const target = targetRaw === "troop" ? "troop" : "chat";
|
|
5059
4928
|
return {
|
|
5060
|
-
endpoint: (process3.env.
|
|
4929
|
+
endpoint: (process3.env.OPENAPE_TROOP_URL ?? DEFAULT_ENDPOINT).replace(/\/$/, ""),
|
|
5061
4930
|
apesBin: process3.env.APE_CHAT_BRIDGE_APES_BIN ?? DEFAULT_APES_BIN,
|
|
5062
4931
|
model,
|
|
5063
4932
|
systemPrompt: process3.env.APE_CHAT_BRIDGE_SYSTEM_PROMPT ?? DEFAULT_SYSTEM_PROMPT,
|
|
5064
4933
|
tools,
|
|
5065
4934
|
maxSteps: Number.isFinite(maxSteps) && maxSteps > 0 ? maxSteps : DEFAULT_MAX_STEPS,
|
|
5066
|
-
roomFilter: process3.env.APE_CHAT_BRIDGE_ROOM
|
|
5067
|
-
target
|
|
4935
|
+
roomFilter: process3.env.APE_CHAT_BRIDGE_ROOM
|
|
5068
4936
|
};
|
|
5069
4937
|
}
|
|
5070
4938
|
async function getIdentity() {
|
|
@@ -5100,13 +4968,13 @@ var Bridge = class {
|
|
|
5100
4968
|
const idp = await ensureFreshIdpAuth();
|
|
5101
4969
|
return `Bearer ${idp.access_token}`;
|
|
5102
4970
|
};
|
|
5103
|
-
this.chat =
|
|
4971
|
+
this.chat = new TroopChatApi(this.cfg.endpoint, this.bearer);
|
|
5104
4972
|
this.cron = new CronRunner({
|
|
5105
4973
|
runtimeConfig: this.runtimeConfig(),
|
|
5106
4974
|
chat: this.chat,
|
|
5107
4975
|
ownerEmail: this.ownerEmail,
|
|
5108
4976
|
log,
|
|
5109
|
-
troopUrl:
|
|
4977
|
+
troopUrl: this.cfg.endpoint,
|
|
5110
4978
|
bearer: this.bearer
|
|
5111
4979
|
});
|
|
5112
4980
|
this.cron.start();
|
|
@@ -5118,11 +4986,6 @@ var Bridge = class {
|
|
|
5118
4986
|
// its own message history and calls @openape/apes' runLoop directly
|
|
5119
4987
|
// (no stdio JSON-RPC subprocess — see thread-session.ts).
|
|
5120
4988
|
threads = /* @__PURE__ */ new Map();
|
|
5121
|
-
// ChatApi and TroopChatApi expose the same surface (postMessage /
|
|
5122
|
-
// listMessages / patchMessage / listContacts / requestContact /
|
|
5123
|
-
// acceptContact / createThread) so the rest of the bridge calls
|
|
5124
|
-
// through a structurally-typed reference without caring which
|
|
5125
|
-
// backend is in play. Picked at construction time from cfg.target.
|
|
5126
4989
|
chat;
|
|
5127
4990
|
bearer;
|
|
5128
4991
|
cron;
|
|
@@ -5268,8 +5131,7 @@ var Bridge = class {
|
|
|
5268
5131
|
}
|
|
5269
5132
|
async pumpOnce() {
|
|
5270
5133
|
const bearer = await this.bearer();
|
|
5271
|
-
const
|
|
5272
|
-
const wsUrl = `${this.cfg.endpoint.replace(/^http/, "ws")}${wsPath}?token=${encodeURIComponent(bearer.replace(/^Bearer\s+/i, ""))}`;
|
|
5134
|
+
const wsUrl = `${this.cfg.endpoint.replace(/^http/, "ws")}/_ws/chat?token=${encodeURIComponent(bearer.replace(/^Bearer\s+/i, ""))}`;
|
|
5273
5135
|
const ws = new WebSocket(wsUrl);
|
|
5274
5136
|
return new Promise((resolve4, reject) => {
|
|
5275
5137
|
let pingTimer;
|
|
@@ -5299,7 +5161,7 @@ var Bridge = class {
|
|
|
5299
5161
|
return;
|
|
5300
5162
|
}
|
|
5301
5163
|
if (frame.type !== "message" || !frame.payload) return;
|
|
5302
|
-
const msg = this.
|
|
5164
|
+
const msg = this.translateTroopPayload(frame.chat_id ?? "", frame.payload);
|
|
5303
5165
|
void this.handleInbound(msg);
|
|
5304
5166
|
});
|
|
5305
5167
|
ws.on("close", () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openape/ape-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "OpenApe agent runtime: per-agent process that connects to chat.openape.ai, runs the LLM loop with tools + cron tasks, and streams replies back to owners.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"ofetch": "^1.4.1",
|
|
24
24
|
"ws": "^8.18.0",
|
|
25
25
|
"yaml": "^2.8.0",
|
|
26
|
+
"@openape/cli-auth": "0.5.0",
|
|
26
27
|
"@openape/apes": "1.29.0",
|
|
27
|
-
"@openape/prompt-injection-detector": "0.1.0"
|
|
28
|
-
"@openape/cli-auth": "0.5.0"
|
|
28
|
+
"@openape/prompt-injection-detector": "0.1.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@antfu/eslint-config": "^7.6.1",
|