roboport 0.0.1 → 0.1.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/README.md +3 -2
- package/core/agent.d.ts +2 -1
- package/gateways/core.d.ts +27 -0
- package/gateways/index.d.ts +5 -0
- package/gateways/index.js +575 -0
- package/gateways/serve.d.ts +19 -0
- package/gateways/sources/telegram.d.ts +28 -0
- package/gateways/store.d.ts +9 -0
- package/harness/index.js +15 -6
- package/index.js +15 -6
- package/mcp/index.js +15 -6
- package/models/index.js +15 -6
- package/package.json +9 -5
- package/skills/index.js +15 -6
- package/triggers/core.d.ts +1 -1
- package/triggers/index.js +10 -2
- package/triggers/sources/telegram.d.ts +9 -1
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@ Minimal TypeScript framework for building LLM agents.
|
|
|
8
8
|
bun add roboport zod
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`
|
|
12
|
-
|
|
11
|
+
`roboport` is built for and tested on [Bun](https://bun.sh) and uses Bun-native
|
|
12
|
+
APIs. `zod` is a required peer dependency — it is imported at runtime, so
|
|
13
|
+
install it alongside the package.
|
|
13
14
|
|
|
14
15
|
## Exports
|
|
15
16
|
|
package/core/agent.d.ts
CHANGED
|
@@ -25,11 +25,12 @@ declare class Agent {
|
|
|
25
25
|
on<T>(trigger: Trigger<T>, handler: TriggerHandler<T>): void;
|
|
26
26
|
start(): Promise<void>;
|
|
27
27
|
stop(): Promise<void>;
|
|
28
|
-
buildSystem(allTools: Tool[]): string;
|
|
28
|
+
buildSystem(allTools: Tool[], systemExtension?: string): string;
|
|
29
29
|
private buildSkillTool;
|
|
30
30
|
session(init?: {
|
|
31
31
|
messages?: Message[];
|
|
32
32
|
cwd?: string;
|
|
33
|
+
systemExtension?: string;
|
|
33
34
|
}): Session;
|
|
34
35
|
}
|
|
35
36
|
export { Agent };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Turn } from '../core';
|
|
2
|
+
import type { MaybePromise, Unsub } from '../triggers/core';
|
|
3
|
+
interface InboundMessage {
|
|
4
|
+
id: string;
|
|
5
|
+
conversationId: string;
|
|
6
|
+
text: string;
|
|
7
|
+
user?: {
|
|
8
|
+
id: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
};
|
|
11
|
+
replyToId?: string;
|
|
12
|
+
raw?: unknown;
|
|
13
|
+
}
|
|
14
|
+
interface Channel {
|
|
15
|
+
conversationId: string;
|
|
16
|
+
send(text: string): Promise<void>;
|
|
17
|
+
thinking?(): () => void;
|
|
18
|
+
}
|
|
19
|
+
type GatewayHandler<In extends InboundMessage, Ch extends Channel> = (message: In, channel: Ch) => MaybePromise<void>;
|
|
20
|
+
type Relay<In extends InboundMessage, Ch extends Channel> = (turn: Turn, channel: Ch, message: In) => Promise<void>;
|
|
21
|
+
interface Gateway<In extends InboundMessage = InboundMessage, Ch extends Channel = Channel> {
|
|
22
|
+
name: string;
|
|
23
|
+
open(handler: GatewayHandler<In, Ch>): MaybePromise<Unsub>;
|
|
24
|
+
handle?(req: Request): Promise<Response>;
|
|
25
|
+
relay?: Relay<In, Ch>;
|
|
26
|
+
}
|
|
27
|
+
export type { Channel, Gateway, GatewayHandler, InboundMessage, Relay };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Channel, type Gateway, type GatewayHandler, type InboundMessage, type Relay } from './core';
|
|
2
|
+
import { serve, type GatewayRuntime, type ServeOptions } from './serve';
|
|
3
|
+
import { stream, telegramGateway, type TelegramChannel, type TelegramGateway, type TelegramGatewayOptions, type TelegramTransport } from './sources/telegram';
|
|
4
|
+
import { fileStore, memoryStore, type ConversationStore } from './store';
|
|
5
|
+
export { fileStore, memoryStore, serve, stream, telegramGateway, type Channel, type ConversationStore, type Gateway, type GatewayHandler, type GatewayRuntime, type InboundMessage, type Relay, type ServeOptions, type TelegramChannel, type TelegramGateway, type TelegramGatewayOptions, type TelegramTransport, };
|
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/gateways/store.ts
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
function memoryStore() {
|
|
7
|
+
const byId = new Map;
|
|
8
|
+
return {
|
|
9
|
+
load(id) {
|
|
10
|
+
const existing = byId.get(id);
|
|
11
|
+
return existing ? [...existing] : null;
|
|
12
|
+
},
|
|
13
|
+
append(id, ...messages) {
|
|
14
|
+
const existing = byId.get(id);
|
|
15
|
+
if (existing)
|
|
16
|
+
existing.push(...messages);
|
|
17
|
+
else
|
|
18
|
+
byId.set(id, [...messages]);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function fileNameFor(id) {
|
|
23
|
+
const prefix = id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
24
|
+
const hash = createHash("sha256").update(id).digest("hex").slice(0, 16);
|
|
25
|
+
return `${prefix}-${hash}.jsonl`;
|
|
26
|
+
}
|
|
27
|
+
function fileStore(dir) {
|
|
28
|
+
function fileFor(id) {
|
|
29
|
+
return join(dir, fileNameFor(id));
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
async load(id) {
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = await readFile(fileFor(id), "utf8");
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const messages = [];
|
|
40
|
+
for (const line of raw.split(`
|
|
41
|
+
`)) {
|
|
42
|
+
if (!line.trim())
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
messages.push(JSON.parse(line));
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return messages;
|
|
49
|
+
},
|
|
50
|
+
async append(id, ...messages) {
|
|
51
|
+
if (messages.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
await mkdir(dir, { recursive: true });
|
|
54
|
+
const lines = messages.map((message) => JSON.stringify(message)).join(`
|
|
55
|
+
`);
|
|
56
|
+
await appendFile(fileFor(id), `${lines}
|
|
57
|
+
`, "utf8");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/gateways/serve.ts
|
|
63
|
+
function toUserMessage(prompt) {
|
|
64
|
+
return { role: "user", content: prompt };
|
|
65
|
+
}
|
|
66
|
+
function newMessages(session, seedLength) {
|
|
67
|
+
const all = session.messages;
|
|
68
|
+
const head = all[0]?.role === "system" ? 1 : 0;
|
|
69
|
+
return [...all.slice(head + seedLength + 1)];
|
|
70
|
+
}
|
|
71
|
+
async function bufferReplies(turn, channel) {
|
|
72
|
+
const blocks = [];
|
|
73
|
+
let failure = null;
|
|
74
|
+
for await (const event of turn) {
|
|
75
|
+
if (event.type === "text")
|
|
76
|
+
blocks.push(event.text);
|
|
77
|
+
else if (event.type === "error")
|
|
78
|
+
failure = event.error;
|
|
79
|
+
}
|
|
80
|
+
const reply = blocks.join(`
|
|
81
|
+
|
|
82
|
+
`).trim();
|
|
83
|
+
if (failure && !reply)
|
|
84
|
+
throw failure;
|
|
85
|
+
await channel.send(reply || "(no response)");
|
|
86
|
+
}
|
|
87
|
+
async function defaultError(error, channel) {
|
|
88
|
+
console.error("[gateways] turn failed:", error);
|
|
89
|
+
await channel.send("Sorry \u2014 something went wrong.").catch(() => {});
|
|
90
|
+
}
|
|
91
|
+
function serve(agent, gateway, options = {}) {
|
|
92
|
+
const store = options.store ?? memoryStore();
|
|
93
|
+
const keyOf = options.conversation ?? ((message) => message.conversationId);
|
|
94
|
+
const relay = options.relay ?? gateway.relay ?? bufferReplies;
|
|
95
|
+
const queues = new Map;
|
|
96
|
+
async function runTurn(message, channel, id) {
|
|
97
|
+
let stopThinking;
|
|
98
|
+
try {
|
|
99
|
+
if (options.authorize && !await options.authorize(message, channel)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const promptValue = options.prompt ? options.prompt(message) : message.text;
|
|
103
|
+
if (promptValue === null)
|
|
104
|
+
return;
|
|
105
|
+
const stored = await store.load(id) ?? [];
|
|
106
|
+
const seed = options.context ? await options.context(stored, message) : stored;
|
|
107
|
+
const seedLength = seed.length;
|
|
108
|
+
const systemExtension = options.systemExtension ? await options.systemExtension(message) : undefined;
|
|
109
|
+
const session = agent.session({ messages: seed, systemExtension });
|
|
110
|
+
await store.append(id, toUserMessage(promptValue));
|
|
111
|
+
stopThinking = channel.thinking?.();
|
|
112
|
+
try {
|
|
113
|
+
const turn = session.send(promptValue);
|
|
114
|
+
await relay(turn, channel, message);
|
|
115
|
+
stopThinking?.();
|
|
116
|
+
stopThinking = undefined;
|
|
117
|
+
await store.append(id, ...newMessages(session, seedLength));
|
|
118
|
+
} finally {
|
|
119
|
+
await session.close();
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
stopThinking?.();
|
|
123
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
124
|
+
if (options.onError)
|
|
125
|
+
await options.onError(err, channel, message);
|
|
126
|
+
else
|
|
127
|
+
await defaultError(err, channel);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function handler(message, channel) {
|
|
131
|
+
const id = keyOf(message);
|
|
132
|
+
const prior = queues.get(id) ?? Promise.resolve();
|
|
133
|
+
const next = prior.then(() => runTurn(message, channel, id)).catch((error) => {
|
|
134
|
+
console.error(`[gateways] ${gateway.name} ${id}:`, error);
|
|
135
|
+
});
|
|
136
|
+
queues.set(id, next);
|
|
137
|
+
next.finally(() => {
|
|
138
|
+
if (queues.get(id) === next)
|
|
139
|
+
queues.delete(id);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const opened = Promise.resolve(gateway.open(handler));
|
|
143
|
+
opened.catch((error) => {
|
|
144
|
+
console.error(`[gateways] ${gateway.name} failed to open:`, error);
|
|
145
|
+
});
|
|
146
|
+
function notWebhook() {
|
|
147
|
+
return Promise.resolve(new Response("gateway is not in webhook mode", { status: 404 }));
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
async stop() {
|
|
151
|
+
const unsub = await opened.catch(() => {
|
|
152
|
+
return;
|
|
153
|
+
});
|
|
154
|
+
if (unsub)
|
|
155
|
+
await unsub();
|
|
156
|
+
},
|
|
157
|
+
handle: gateway.handle ? (req) => gateway.handle(req) : notWebhook
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/triggers/bus.ts
|
|
162
|
+
function makeBus() {
|
|
163
|
+
return { subs: new Set };
|
|
164
|
+
}
|
|
165
|
+
function subscribe(bus, emit, filter) {
|
|
166
|
+
const wrapped = filter ? (event) => {
|
|
167
|
+
if (filter(event))
|
|
168
|
+
emit(event);
|
|
169
|
+
} : emit;
|
|
170
|
+
bus.subs.add(wrapped);
|
|
171
|
+
return () => {
|
|
172
|
+
bus.subs.delete(wrapped);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function dispatch(bus, event) {
|
|
176
|
+
for (const sub of bus.subs)
|
|
177
|
+
sub(event);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/triggers/sources/telegram.ts
|
|
181
|
+
var DEFAULT_UPDATE_CACHE_SIZE = 1024;
|
|
182
|
+
var TELEGRAM_API_BASE = "https://api.telegram.org";
|
|
183
|
+
var MAX_MESSAGE_LENGTH = 4096;
|
|
184
|
+
function timingSafeEqual(a, b) {
|
|
185
|
+
if (a.length !== b.length)
|
|
186
|
+
return false;
|
|
187
|
+
let diff = 0;
|
|
188
|
+
for (let i = 0;i < a.length; i++) {
|
|
189
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
190
|
+
}
|
|
191
|
+
return diff === 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class UpdateCache {
|
|
195
|
+
seen = new Set;
|
|
196
|
+
order = [];
|
|
197
|
+
maxSize;
|
|
198
|
+
constructor(maxSize) {
|
|
199
|
+
this.maxSize = maxSize;
|
|
200
|
+
}
|
|
201
|
+
has(id) {
|
|
202
|
+
return this.seen.has(id);
|
|
203
|
+
}
|
|
204
|
+
add(id) {
|
|
205
|
+
if (this.seen.has(id))
|
|
206
|
+
return;
|
|
207
|
+
this.seen.add(id);
|
|
208
|
+
this.order.push(id);
|
|
209
|
+
while (this.order.length > this.maxSize) {
|
|
210
|
+
const dropped = this.order.shift();
|
|
211
|
+
if (dropped !== undefined)
|
|
212
|
+
this.seen.delete(dropped);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function matchesCommand(message, commands, botUsername) {
|
|
217
|
+
const text = message.text;
|
|
218
|
+
if (!text || !text.startsWith("/"))
|
|
219
|
+
return false;
|
|
220
|
+
const token = text.slice(1).split(/\s/, 1)[0] ?? "";
|
|
221
|
+
const [name, target] = token.split("@");
|
|
222
|
+
if (target && botUsername) {
|
|
223
|
+
const normalized = botUsername.replace(/^@/, "").toLowerCase();
|
|
224
|
+
if (target.toLowerCase() !== normalized)
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return commands.some((command) => (command.startsWith("/") ? command.slice(1) : command) === name);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
class TelegramReceiver {
|
|
231
|
+
messageBus = makeBus();
|
|
232
|
+
editedMessageBus = makeBus();
|
|
233
|
+
secretToken;
|
|
234
|
+
updates;
|
|
235
|
+
constructor(options) {
|
|
236
|
+
if (!options.secretToken) {
|
|
237
|
+
throw new Error("TelegramReceiver requires a non-empty secretToken");
|
|
238
|
+
}
|
|
239
|
+
this.secretToken = options.secretToken;
|
|
240
|
+
this.updates = new UpdateCache(options.updateCacheSize ?? DEFAULT_UPDATE_CACHE_SIZE);
|
|
241
|
+
}
|
|
242
|
+
message(opts) {
|
|
243
|
+
const bus = this.messageBus;
|
|
244
|
+
const commands = opts?.commands;
|
|
245
|
+
const botUsername = opts?.botUsername;
|
|
246
|
+
return {
|
|
247
|
+
name: "telegram:message",
|
|
248
|
+
start: (emit) => subscribe(bus, emit, commands ? (m) => matchesCommand(m, commands, botUsername) : undefined)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
editedMessage() {
|
|
252
|
+
const bus = this.editedMessageBus;
|
|
253
|
+
return {
|
|
254
|
+
name: "telegram:edited_message",
|
|
255
|
+
start: (emit) => subscribe(bus, emit)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
handle = async (req) => {
|
|
259
|
+
const provided = req.headers.get("x-telegram-bot-api-secret-token");
|
|
260
|
+
if (!provided || !timingSafeEqual(provided, this.secretToken)) {
|
|
261
|
+
return new Response("invalid secret token", { status: 401 });
|
|
262
|
+
}
|
|
263
|
+
let update;
|
|
264
|
+
try {
|
|
265
|
+
update = await req.json();
|
|
266
|
+
} catch {
|
|
267
|
+
return new Response("invalid json", { status: 400 });
|
|
268
|
+
}
|
|
269
|
+
if (typeof update.update_id !== "number") {
|
|
270
|
+
return new Response("missing update_id", { status: 400 });
|
|
271
|
+
}
|
|
272
|
+
if (this.updates.has(update.update_id)) {
|
|
273
|
+
return new Response("duplicate", { status: 200 });
|
|
274
|
+
}
|
|
275
|
+
if (update.message) {
|
|
276
|
+
dispatch(this.messageBus, update.message);
|
|
277
|
+
} else if (update.edited_message) {
|
|
278
|
+
dispatch(this.editedMessageBus, update.edited_message);
|
|
279
|
+
}
|
|
280
|
+
this.updates.add(update.update_id);
|
|
281
|
+
return new Response("ok", { status: 200 });
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function splitMessage(text, max = MAX_MESSAGE_LENGTH) {
|
|
285
|
+
if (max < 1)
|
|
286
|
+
throw new Error("splitMessage requires max >= 1");
|
|
287
|
+
if (text.length <= max)
|
|
288
|
+
return [text];
|
|
289
|
+
const chunks = [];
|
|
290
|
+
let remaining = text;
|
|
291
|
+
while (remaining.length > max) {
|
|
292
|
+
let cut = remaining.lastIndexOf(`
|
|
293
|
+
`, max);
|
|
294
|
+
if (cut <= 0) {
|
|
295
|
+
cut = max;
|
|
296
|
+
const code = remaining.charCodeAt(cut - 1);
|
|
297
|
+
if (code >= 55296 && code <= 56319)
|
|
298
|
+
cut -= 1;
|
|
299
|
+
}
|
|
300
|
+
chunks.push(remaining.slice(0, cut));
|
|
301
|
+
remaining = remaining.slice(cut).replace(/^\n/, "");
|
|
302
|
+
}
|
|
303
|
+
if (remaining.length > 0)
|
|
304
|
+
chunks.push(remaining);
|
|
305
|
+
return chunks;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
class TelegramClient {
|
|
309
|
+
token;
|
|
310
|
+
baseUrl;
|
|
311
|
+
constructor(token, opts) {
|
|
312
|
+
if (!token)
|
|
313
|
+
throw new Error("TelegramClient requires a bot token");
|
|
314
|
+
this.token = token;
|
|
315
|
+
this.baseUrl = (opts?.baseUrl ?? TELEGRAM_API_BASE).replace(/\/+$/, "");
|
|
316
|
+
}
|
|
317
|
+
async call(method, params, signal) {
|
|
318
|
+
const response = await fetch(`${this.baseUrl}/bot${this.token}/${method}`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: { "content-type": "application/json" },
|
|
321
|
+
body: JSON.stringify(params),
|
|
322
|
+
...signal ? { signal } : {}
|
|
323
|
+
});
|
|
324
|
+
const data = await response.json();
|
|
325
|
+
if (!data.ok) {
|
|
326
|
+
throw new Error(`Telegram ${method} failed (${response.status}): ${data.description ?? "unknown error"}`);
|
|
327
|
+
}
|
|
328
|
+
return data.result;
|
|
329
|
+
}
|
|
330
|
+
getMe() {
|
|
331
|
+
return this.call("getMe", {});
|
|
332
|
+
}
|
|
333
|
+
sendChatAction(chatId, action = "typing") {
|
|
334
|
+
return this.call("sendChatAction", { chat_id: chatId, action });
|
|
335
|
+
}
|
|
336
|
+
async sendMessage(chatId, text, opts) {
|
|
337
|
+
if (opts?.parseMode && text.length > MAX_MESSAGE_LENGTH) {
|
|
338
|
+
throw new Error(`sendMessage: text exceeds ${MAX_MESSAGE_LENGTH} chars with parse_mode ${opts.parseMode}; auto-splitting could break entities. Split it yourself.`);
|
|
339
|
+
}
|
|
340
|
+
const sent = [];
|
|
341
|
+
for (const chunk of splitMessage(text)) {
|
|
342
|
+
sent.push(await this.call("sendMessage", {
|
|
343
|
+
chat_id: chatId,
|
|
344
|
+
text: chunk,
|
|
345
|
+
...opts?.parseMode ? { parse_mode: opts.parseMode } : {},
|
|
346
|
+
...opts?.messageThreadId !== undefined ? { message_thread_id: opts.messageThreadId } : {},
|
|
347
|
+
...opts?.replyToMessageId ? { reply_parameters: { message_id: opts.replyToMessageId } } : {},
|
|
348
|
+
...opts?.disableNotification ? { disable_notification: true } : {},
|
|
349
|
+
...opts?.linkPreview === false ? { link_preview_options: { is_disabled: true } } : {}
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
return sent;
|
|
353
|
+
}
|
|
354
|
+
editMessageText(chatId, messageId, text, opts) {
|
|
355
|
+
return this.call("editMessageText", {
|
|
356
|
+
chat_id: chatId,
|
|
357
|
+
message_id: messageId,
|
|
358
|
+
text,
|
|
359
|
+
...opts?.parseMode ? { parse_mode: opts.parseMode } : {}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
async sendMessageDraft(chatId, draftId, text, opts) {
|
|
363
|
+
if (!Number.isInteger(draftId) || draftId === 0) {
|
|
364
|
+
throw new Error("sendMessageDraft: draftId must be a non-zero integer");
|
|
365
|
+
}
|
|
366
|
+
if (text.length > MAX_MESSAGE_LENGTH) {
|
|
367
|
+
throw new Error(`sendMessageDraft: text exceeds ${MAX_MESSAGE_LENGTH} chars; a draft is a single bubble and can't be split. Truncate it and persist the full reply via sendMessage.`);
|
|
368
|
+
}
|
|
369
|
+
return this.call("sendMessageDraft", {
|
|
370
|
+
chat_id: chatId,
|
|
371
|
+
draft_id: draftId,
|
|
372
|
+
text,
|
|
373
|
+
...opts?.parseMode ? { parse_mode: opts.parseMode } : {},
|
|
374
|
+
...opts?.messageThreadId !== undefined ? { message_thread_id: opts.messageThreadId } : {}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
setWebhook(url, opts) {
|
|
378
|
+
return this.call("setWebhook", {
|
|
379
|
+
url,
|
|
380
|
+
...opts?.secretToken ? { secret_token: opts.secretToken } : {},
|
|
381
|
+
...opts?.allowedUpdates ? { allowed_updates: opts.allowedUpdates } : {}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
deleteWebhook(opts) {
|
|
385
|
+
return this.call("deleteWebhook", {
|
|
386
|
+
...opts?.dropPendingUpdates ? { drop_pending_updates: true } : {}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
getUpdates(opts) {
|
|
390
|
+
return this.call("getUpdates", {
|
|
391
|
+
...opts?.offset !== undefined ? { offset: opts.offset } : {},
|
|
392
|
+
...opts?.timeout !== undefined ? { timeout: opts.timeout } : {},
|
|
393
|
+
...opts?.allowedUpdates ? { allowed_updates: opts.allowedUpdates } : {}
|
|
394
|
+
}, opts?.signal);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function telegram(options) {
|
|
398
|
+
return new TelegramReceiver(options);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/gateways/sources/telegram.ts
|
|
402
|
+
var TYPING_INTERVAL_MS = 4000;
|
|
403
|
+
var POLL_TIMEOUT_SECONDS = 25;
|
|
404
|
+
var POLL_BACKOFF_MS = 1000;
|
|
405
|
+
var DRAFT_THROTTLE_MS = 500;
|
|
406
|
+
var DRAFT_MAX_LENGTH = 4096;
|
|
407
|
+
function conversationKey(message) {
|
|
408
|
+
return message.message_thread_id !== undefined ? `${message.chat.id}:${message.message_thread_id}` : String(message.chat.id);
|
|
409
|
+
}
|
|
410
|
+
function toInbound(message) {
|
|
411
|
+
return {
|
|
412
|
+
id: String(message.message_id),
|
|
413
|
+
conversationId: conversationKey(message),
|
|
414
|
+
text: message.text ?? message.caption ?? "",
|
|
415
|
+
user: message.from ? {
|
|
416
|
+
id: String(message.from.id),
|
|
417
|
+
name: message.from.username ?? message.from.first_name
|
|
418
|
+
} : undefined,
|
|
419
|
+
replyToId: message.reply_to_message ? String(message.reply_to_message.message_id) : undefined,
|
|
420
|
+
raw: message
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function startTyping(client, chatId) {
|
|
424
|
+
let stopped = false;
|
|
425
|
+
function tick() {
|
|
426
|
+
if (!stopped)
|
|
427
|
+
client.sendChatAction(chatId, "typing").catch(() => {});
|
|
428
|
+
}
|
|
429
|
+
tick();
|
|
430
|
+
const interval = setInterval(tick, TYPING_INTERVAL_MS);
|
|
431
|
+
return () => {
|
|
432
|
+
stopped = true;
|
|
433
|
+
clearInterval(interval);
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function sleep(ms) {
|
|
437
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
438
|
+
}
|
|
439
|
+
function telegramGateway(options) {
|
|
440
|
+
const client = new TelegramClient(options.token);
|
|
441
|
+
const transport = options.transport ?? { mode: "polling" };
|
|
442
|
+
function channelFor(message) {
|
|
443
|
+
const chatId = message.chat.id;
|
|
444
|
+
const threadId = message.message_thread_id;
|
|
445
|
+
return {
|
|
446
|
+
conversationId: conversationKey(message),
|
|
447
|
+
chatId,
|
|
448
|
+
client,
|
|
449
|
+
send: async (text, opts) => {
|
|
450
|
+
await client.sendMessage(chatId, text, {
|
|
451
|
+
messageThreadId: threadId,
|
|
452
|
+
...opts
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
draft: async (text) => {
|
|
456
|
+
await client.sendMessageDraft(chatId, message.message_id, text, {
|
|
457
|
+
messageThreadId: threadId
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
thinking: () => startTyping(client, chatId)
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function forwards(message) {
|
|
464
|
+
return !options.commands || matchesCommand(message, options.commands, options.botUsername);
|
|
465
|
+
}
|
|
466
|
+
function deliver(handler, message) {
|
|
467
|
+
Promise.resolve(handler(toInbound(message), channelFor(message))).catch((error) => {
|
|
468
|
+
console.error("[gateways] telegram handler error:", error);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (transport.mode === "webhook") {
|
|
472
|
+
const receiver = new TelegramReceiver({
|
|
473
|
+
secretToken: transport.secretToken
|
|
474
|
+
});
|
|
475
|
+
return {
|
|
476
|
+
name: "telegram",
|
|
477
|
+
client,
|
|
478
|
+
handle: receiver.handle,
|
|
479
|
+
open(handler) {
|
|
480
|
+
const trigger = receiver.message(options.commands ? { commands: options.commands, botUsername: options.botUsername } : undefined);
|
|
481
|
+
return trigger.start((message) => deliver(handler, message));
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
name: "telegram",
|
|
487
|
+
client,
|
|
488
|
+
open(handler) {
|
|
489
|
+
const controller = new AbortController;
|
|
490
|
+
(async () => {
|
|
491
|
+
await client.deleteWebhook().catch(() => {});
|
|
492
|
+
let offset;
|
|
493
|
+
while (!controller.signal.aborted) {
|
|
494
|
+
try {
|
|
495
|
+
const updates = await client.getUpdates({
|
|
496
|
+
offset,
|
|
497
|
+
timeout: POLL_TIMEOUT_SECONDS,
|
|
498
|
+
allowedUpdates: ["message"],
|
|
499
|
+
signal: controller.signal
|
|
500
|
+
});
|
|
501
|
+
for (const update of updates) {
|
|
502
|
+
offset = update.update_id + 1;
|
|
503
|
+
const message = update.message;
|
|
504
|
+
if (message && forwards(message))
|
|
505
|
+
deliver(handler, message);
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (controller.signal.aborted)
|
|
509
|
+
break;
|
|
510
|
+
console.error("[gateways] telegram polling error:", error);
|
|
511
|
+
await sleep(POLL_BACKOFF_MS);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
})();
|
|
515
|
+
return () => controller.abort();
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function stream(options = {}) {
|
|
520
|
+
const throttleMs = options.throttleMs ?? DRAFT_THROTTLE_MS;
|
|
521
|
+
return async (turn, channel) => {
|
|
522
|
+
const blocks = [];
|
|
523
|
+
let current = "";
|
|
524
|
+
let failure = null;
|
|
525
|
+
let inFlight = false;
|
|
526
|
+
let lastSentAt = 0;
|
|
527
|
+
let lastSentText = "";
|
|
528
|
+
function preview() {
|
|
529
|
+
return [blocks.join(`
|
|
530
|
+
|
|
531
|
+
`), current].filter((part) => part.length > 0).join(`
|
|
532
|
+
|
|
533
|
+
`);
|
|
534
|
+
}
|
|
535
|
+
function refresh() {
|
|
536
|
+
const body = preview();
|
|
537
|
+
if (!body || body === lastSentText || body.length > DRAFT_MAX_LENGTH) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (inFlight || Date.now() - lastSentAt < throttleMs)
|
|
541
|
+
return;
|
|
542
|
+
inFlight = true;
|
|
543
|
+
lastSentAt = Date.now();
|
|
544
|
+
lastSentText = body;
|
|
545
|
+
channel.draft(body).catch(() => {}).finally(() => {
|
|
546
|
+
inFlight = false;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
for await (const event of turn) {
|
|
550
|
+
if (event.type === "text-delta") {
|
|
551
|
+
current += event.text;
|
|
552
|
+
refresh();
|
|
553
|
+
} else if (event.type === "text") {
|
|
554
|
+
blocks.push(event.text);
|
|
555
|
+
current = "";
|
|
556
|
+
refresh();
|
|
557
|
+
} else if (event.type === "error") {
|
|
558
|
+
failure = event.error;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const reply = blocks.join(`
|
|
562
|
+
|
|
563
|
+
`).trim();
|
|
564
|
+
if (failure && !reply)
|
|
565
|
+
throw failure;
|
|
566
|
+
await channel.send(reply || "(no response)");
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
export {
|
|
570
|
+
telegramGateway,
|
|
571
|
+
stream,
|
|
572
|
+
serve,
|
|
573
|
+
memoryStore,
|
|
574
|
+
fileStore
|
|
575
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Agent, type Message, type TextPart } from '../core';
|
|
2
|
+
import type { Channel, Gateway, InboundMessage, Relay } from './core';
|
|
3
|
+
import { type ConversationStore } from './store';
|
|
4
|
+
interface ServeOptions<In extends InboundMessage, Ch extends Channel> {
|
|
5
|
+
conversation?: (message: In) => string;
|
|
6
|
+
authorize?: (message: In, channel: Ch) => boolean | Promise<boolean>;
|
|
7
|
+
systemExtension?: (message: In) => string | Promise<string>;
|
|
8
|
+
prompt?: (message: In) => string | TextPart[] | null;
|
|
9
|
+
context?: (stored: Message[], message: In) => Message[] | Promise<Message[]>;
|
|
10
|
+
relay?: Relay<In, Ch>;
|
|
11
|
+
store?: ConversationStore;
|
|
12
|
+
onError?: (error: Error, channel: Ch, message: In) => void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
interface GatewayRuntime {
|
|
15
|
+
stop(): Promise<void>;
|
|
16
|
+
handle(req: Request): Promise<Response>;
|
|
17
|
+
}
|
|
18
|
+
declare function serve<In extends InboundMessage, Ch extends Channel>(agent: Agent, gateway: Gateway<In, Ch>, options?: ServeOptions<In, Ch>): GatewayRuntime;
|
|
19
|
+
export { serve, type GatewayRuntime, type ServeOptions };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TelegramClient, type SendMessageOptions } from '../../triggers/sources/telegram';
|
|
2
|
+
import type { Channel, Gateway, InboundMessage, Relay } from '../core';
|
|
3
|
+
interface TelegramChannel extends Channel {
|
|
4
|
+
chatId: number;
|
|
5
|
+
client: TelegramClient;
|
|
6
|
+
send(text: string, opts?: SendMessageOptions): Promise<void>;
|
|
7
|
+
draft(text: string): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
type TelegramTransport = {
|
|
10
|
+
mode: 'polling';
|
|
11
|
+
} | {
|
|
12
|
+
mode: 'webhook';
|
|
13
|
+
secretToken: string;
|
|
14
|
+
};
|
|
15
|
+
interface TelegramGatewayOptions {
|
|
16
|
+
token: string;
|
|
17
|
+
transport?: TelegramTransport;
|
|
18
|
+
commands?: string[];
|
|
19
|
+
botUsername?: string;
|
|
20
|
+
}
|
|
21
|
+
interface TelegramGateway extends Gateway<InboundMessage, TelegramChannel> {
|
|
22
|
+
client: TelegramClient;
|
|
23
|
+
}
|
|
24
|
+
declare function telegramGateway(options: TelegramGatewayOptions): TelegramGateway;
|
|
25
|
+
declare function stream(options?: {
|
|
26
|
+
throttleMs?: number;
|
|
27
|
+
}): Relay<InboundMessage, TelegramChannel>;
|
|
28
|
+
export { stream, telegramGateway, type TelegramChannel, type TelegramGateway, type TelegramGatewayOptions, type TelegramTransport, };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Message } from '../core';
|
|
2
|
+
import type { MaybePromise } from '../triggers/core';
|
|
3
|
+
interface ConversationStore {
|
|
4
|
+
load(id: string): MaybePromise<Message[] | null>;
|
|
5
|
+
append(id: string, ...messages: Message[]): MaybePromise<void>;
|
|
6
|
+
}
|
|
7
|
+
declare function memoryStore(): ConversationStore;
|
|
8
|
+
declare function fileStore(dir: string): ConversationStore;
|
|
9
|
+
export { fileStore, memoryStore, type ConversationStore };
|
package/harness/index.js
CHANGED
|
@@ -210,7 +210,7 @@ class Agent {
|
|
|
210
210
|
this.unsubs = [];
|
|
211
211
|
await Promise.all(unsubs.map((u) => u()));
|
|
212
212
|
}
|
|
213
|
-
buildSystem(allTools) {
|
|
213
|
+
buildSystem(allTools, systemExtension) {
|
|
214
214
|
let system = this.system;
|
|
215
215
|
if (this.skills.length > 0) {
|
|
216
216
|
const skillsList = this.skills.map((skill) => `- ${skill.name}: ${skill.description}`).join(`
|
|
@@ -231,6 +231,11 @@ ${skillsList}`;
|
|
|
231
231
|
# Deferred tools
|
|
232
232
|
These tools are available but their schemas are not loaded. Use ToolSearch to load them before calling.
|
|
233
233
|
${list}`;
|
|
234
|
+
}
|
|
235
|
+
if (systemExtension) {
|
|
236
|
+
system = `${system}
|
|
237
|
+
|
|
238
|
+
${systemExtension}`;
|
|
234
239
|
}
|
|
235
240
|
return system;
|
|
236
241
|
}
|
|
@@ -257,6 +262,7 @@ ${found.content}
|
|
|
257
262
|
session(init) {
|
|
258
263
|
const initialMessages = init?.messages ? [...init.messages] : [];
|
|
259
264
|
const sessionCwd = init?.cwd ?? this.cwd ?? process.cwd();
|
|
265
|
+
const systemExtension = init?.systemExtension;
|
|
260
266
|
const state = {
|
|
261
267
|
messages: initialMessages,
|
|
262
268
|
store: new Map
|
|
@@ -289,11 +295,14 @@ ${found.content}
|
|
|
289
295
|
tools: registry,
|
|
290
296
|
cwd: sessionCwd
|
|
291
297
|
};
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
298
|
+
const systemMessage = {
|
|
299
|
+
role: "system",
|
|
300
|
+
content: this.buildSystem(allTools, systemExtension)
|
|
301
|
+
};
|
|
302
|
+
if (state.messages[0]?.role === "system") {
|
|
303
|
+
state.messages[0] = systemMessage;
|
|
304
|
+
} else {
|
|
305
|
+
state.messages.unshift(systemMessage);
|
|
297
306
|
}
|
|
298
307
|
}
|
|
299
308
|
return { tools: allTools, registry, ctx };
|
package/index.js
CHANGED
|
@@ -205,7 +205,7 @@ class Agent {
|
|
|
205
205
|
this.unsubs = [];
|
|
206
206
|
await Promise.all(unsubs.map((u) => u()));
|
|
207
207
|
}
|
|
208
|
-
buildSystem(allTools) {
|
|
208
|
+
buildSystem(allTools, systemExtension) {
|
|
209
209
|
let system = this.system;
|
|
210
210
|
if (this.skills.length > 0) {
|
|
211
211
|
const skillsList = this.skills.map((skill) => `- ${skill.name}: ${skill.description}`).join(`
|
|
@@ -226,6 +226,11 @@ ${skillsList}`;
|
|
|
226
226
|
# Deferred tools
|
|
227
227
|
These tools are available but their schemas are not loaded. Use ToolSearch to load them before calling.
|
|
228
228
|
${list}`;
|
|
229
|
+
}
|
|
230
|
+
if (systemExtension) {
|
|
231
|
+
system = `${system}
|
|
232
|
+
|
|
233
|
+
${systemExtension}`;
|
|
229
234
|
}
|
|
230
235
|
return system;
|
|
231
236
|
}
|
|
@@ -252,6 +257,7 @@ ${found.content}
|
|
|
252
257
|
session(init) {
|
|
253
258
|
const initialMessages = init?.messages ? [...init.messages] : [];
|
|
254
259
|
const sessionCwd = init?.cwd ?? this.cwd ?? process.cwd();
|
|
260
|
+
const systemExtension = init?.systemExtension;
|
|
255
261
|
const state = {
|
|
256
262
|
messages: initialMessages,
|
|
257
263
|
store: new Map
|
|
@@ -284,11 +290,14 @@ ${found.content}
|
|
|
284
290
|
tools: registry,
|
|
285
291
|
cwd: sessionCwd
|
|
286
292
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
const systemMessage = {
|
|
294
|
+
role: "system",
|
|
295
|
+
content: this.buildSystem(allTools, systemExtension)
|
|
296
|
+
};
|
|
297
|
+
if (state.messages[0]?.role === "system") {
|
|
298
|
+
state.messages[0] = systemMessage;
|
|
299
|
+
} else {
|
|
300
|
+
state.messages.unshift(systemMessage);
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
return { tools: allTools, registry, ctx };
|
package/mcp/index.js
CHANGED
|
@@ -205,7 +205,7 @@ class Agent {
|
|
|
205
205
|
this.unsubs = [];
|
|
206
206
|
await Promise.all(unsubs.map((u) => u()));
|
|
207
207
|
}
|
|
208
|
-
buildSystem(allTools) {
|
|
208
|
+
buildSystem(allTools, systemExtension) {
|
|
209
209
|
let system = this.system;
|
|
210
210
|
if (this.skills.length > 0) {
|
|
211
211
|
const skillsList = this.skills.map((skill) => `- ${skill.name}: ${skill.description}`).join(`
|
|
@@ -226,6 +226,11 @@ ${skillsList}`;
|
|
|
226
226
|
# Deferred tools
|
|
227
227
|
These tools are available but their schemas are not loaded. Use ToolSearch to load them before calling.
|
|
228
228
|
${list}`;
|
|
229
|
+
}
|
|
230
|
+
if (systemExtension) {
|
|
231
|
+
system = `${system}
|
|
232
|
+
|
|
233
|
+
${systemExtension}`;
|
|
229
234
|
}
|
|
230
235
|
return system;
|
|
231
236
|
}
|
|
@@ -252,6 +257,7 @@ ${found.content}
|
|
|
252
257
|
session(init) {
|
|
253
258
|
const initialMessages = init?.messages ? [...init.messages] : [];
|
|
254
259
|
const sessionCwd = init?.cwd ?? this.cwd ?? process.cwd();
|
|
260
|
+
const systemExtension = init?.systemExtension;
|
|
255
261
|
const state = {
|
|
256
262
|
messages: initialMessages,
|
|
257
263
|
store: new Map
|
|
@@ -284,11 +290,14 @@ ${found.content}
|
|
|
284
290
|
tools: registry,
|
|
285
291
|
cwd: sessionCwd
|
|
286
292
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
const systemMessage = {
|
|
294
|
+
role: "system",
|
|
295
|
+
content: this.buildSystem(allTools, systemExtension)
|
|
296
|
+
};
|
|
297
|
+
if (state.messages[0]?.role === "system") {
|
|
298
|
+
state.messages[0] = systemMessage;
|
|
299
|
+
} else {
|
|
300
|
+
state.messages.unshift(systemMessage);
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
return { tools: allTools, registry, ctx };
|
package/models/index.js
CHANGED
|
@@ -205,7 +205,7 @@ class Agent {
|
|
|
205
205
|
this.unsubs = [];
|
|
206
206
|
await Promise.all(unsubs.map((u) => u()));
|
|
207
207
|
}
|
|
208
|
-
buildSystem(allTools) {
|
|
208
|
+
buildSystem(allTools, systemExtension) {
|
|
209
209
|
let system = this.system;
|
|
210
210
|
if (this.skills.length > 0) {
|
|
211
211
|
const skillsList = this.skills.map((skill) => `- ${skill.name}: ${skill.description}`).join(`
|
|
@@ -226,6 +226,11 @@ ${skillsList}`;
|
|
|
226
226
|
# Deferred tools
|
|
227
227
|
These tools are available but their schemas are not loaded. Use ToolSearch to load them before calling.
|
|
228
228
|
${list}`;
|
|
229
|
+
}
|
|
230
|
+
if (systemExtension) {
|
|
231
|
+
system = `${system}
|
|
232
|
+
|
|
233
|
+
${systemExtension}`;
|
|
229
234
|
}
|
|
230
235
|
return system;
|
|
231
236
|
}
|
|
@@ -252,6 +257,7 @@ ${found.content}
|
|
|
252
257
|
session(init) {
|
|
253
258
|
const initialMessages = init?.messages ? [...init.messages] : [];
|
|
254
259
|
const sessionCwd = init?.cwd ?? this.cwd ?? process.cwd();
|
|
260
|
+
const systemExtension = init?.systemExtension;
|
|
255
261
|
const state = {
|
|
256
262
|
messages: initialMessages,
|
|
257
263
|
store: new Map
|
|
@@ -284,11 +290,14 @@ ${found.content}
|
|
|
284
290
|
tools: registry,
|
|
285
291
|
cwd: sessionCwd
|
|
286
292
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
const systemMessage = {
|
|
294
|
+
role: "system",
|
|
295
|
+
content: this.buildSystem(allTools, systemExtension)
|
|
296
|
+
};
|
|
297
|
+
if (state.messages[0]?.role === "system") {
|
|
298
|
+
state.messages[0] = systemMessage;
|
|
299
|
+
} else {
|
|
300
|
+
state.messages.unshift(systemMessage);
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
return { tools: allTools, registry, ctx };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roboport",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Minimal TypeScript framework for building LLM agents.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Timur Badretdinov",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"llm",
|
|
21
21
|
"agent",
|
|
22
22
|
"ai",
|
|
23
|
-
"mcp"
|
|
24
|
-
"anthropic",
|
|
25
|
-
"openai",
|
|
26
|
-
"typescript"
|
|
23
|
+
"mcp"
|
|
27
24
|
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.0.0"
|
|
27
|
+
},
|
|
28
28
|
"type": "module",
|
|
29
29
|
"main": "./index.js",
|
|
30
30
|
"types": "./index.d.ts",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"types": "./index.d.ts",
|
|
34
34
|
"import": "./index.js"
|
|
35
35
|
},
|
|
36
|
+
"./gateways": {
|
|
37
|
+
"types": "./gateways/index.d.ts",
|
|
38
|
+
"import": "./gateways/index.js"
|
|
39
|
+
},
|
|
36
40
|
"./harness": {
|
|
37
41
|
"types": "./harness/index.d.ts",
|
|
38
42
|
"import": "./harness/index.js"
|
package/skills/index.js
CHANGED
|
@@ -205,7 +205,7 @@ class Agent {
|
|
|
205
205
|
this.unsubs = [];
|
|
206
206
|
await Promise.all(unsubs.map((u) => u()));
|
|
207
207
|
}
|
|
208
|
-
buildSystem(allTools) {
|
|
208
|
+
buildSystem(allTools, systemExtension) {
|
|
209
209
|
let system = this.system;
|
|
210
210
|
if (this.skills.length > 0) {
|
|
211
211
|
const skillsList = this.skills.map((skill) => `- ${skill.name}: ${skill.description}`).join(`
|
|
@@ -226,6 +226,11 @@ ${skillsList}`;
|
|
|
226
226
|
# Deferred tools
|
|
227
227
|
These tools are available but their schemas are not loaded. Use ToolSearch to load them before calling.
|
|
228
228
|
${list}`;
|
|
229
|
+
}
|
|
230
|
+
if (systemExtension) {
|
|
231
|
+
system = `${system}
|
|
232
|
+
|
|
233
|
+
${systemExtension}`;
|
|
229
234
|
}
|
|
230
235
|
return system;
|
|
231
236
|
}
|
|
@@ -252,6 +257,7 @@ ${found.content}
|
|
|
252
257
|
session(init) {
|
|
253
258
|
const initialMessages = init?.messages ? [...init.messages] : [];
|
|
254
259
|
const sessionCwd = init?.cwd ?? this.cwd ?? process.cwd();
|
|
260
|
+
const systemExtension = init?.systemExtension;
|
|
255
261
|
const state = {
|
|
256
262
|
messages: initialMessages,
|
|
257
263
|
store: new Map
|
|
@@ -284,11 +290,14 @@ ${found.content}
|
|
|
284
290
|
tools: registry,
|
|
285
291
|
cwd: sessionCwd
|
|
286
292
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
const systemMessage = {
|
|
294
|
+
role: "system",
|
|
295
|
+
content: this.buildSystem(allTools, systemExtension)
|
|
296
|
+
};
|
|
297
|
+
if (state.messages[0]?.role === "system") {
|
|
298
|
+
state.messages[0] = systemMessage;
|
|
299
|
+
} else {
|
|
300
|
+
state.messages.unshift(systemMessage);
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
return { tools: allTools, registry, ctx };
|
package/triggers/core.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ interface CustomTriggerInit<T> {
|
|
|
11
11
|
start: (emit: Emit<T>) => MaybePromise<Unsub>;
|
|
12
12
|
}
|
|
13
13
|
declare function trigger<T = unknown>(init: CustomTriggerInit<T>): Trigger<T>;
|
|
14
|
-
export { trigger, type Emit, type Trigger, type TriggerHandler, type Unsub };
|
|
14
|
+
export { trigger, type Emit, type MaybePromise, type Trigger, type TriggerHandler, type Unsub, };
|
package/triggers/index.js
CHANGED
|
@@ -497,11 +497,12 @@ class TelegramClient {
|
|
|
497
497
|
this.token = token;
|
|
498
498
|
this.baseUrl = (opts?.baseUrl ?? TELEGRAM_API_BASE).replace(/\/+$/, "");
|
|
499
499
|
}
|
|
500
|
-
async call(method, params) {
|
|
500
|
+
async call(method, params, signal) {
|
|
501
501
|
const response = await fetch(`${this.baseUrl}/bot${this.token}/${method}`, {
|
|
502
502
|
method: "POST",
|
|
503
503
|
headers: { "content-type": "application/json" },
|
|
504
|
-
body: JSON.stringify(params)
|
|
504
|
+
body: JSON.stringify(params),
|
|
505
|
+
...signal ? { signal } : {}
|
|
505
506
|
});
|
|
506
507
|
const data = await response.json();
|
|
507
508
|
if (!data.ok) {
|
|
@@ -568,6 +569,13 @@ class TelegramClient {
|
|
|
568
569
|
...opts?.dropPendingUpdates ? { drop_pending_updates: true } : {}
|
|
569
570
|
});
|
|
570
571
|
}
|
|
572
|
+
getUpdates(opts) {
|
|
573
|
+
return this.call("getUpdates", {
|
|
574
|
+
...opts?.offset !== undefined ? { offset: opts.offset } : {},
|
|
575
|
+
...opts?.timeout !== undefined ? { timeout: opts.timeout } : {},
|
|
576
|
+
...opts?.allowedUpdates ? { allowed_updates: opts.allowedUpdates } : {}
|
|
577
|
+
}, opts?.signal);
|
|
578
|
+
}
|
|
571
579
|
}
|
|
572
580
|
function telegram(options) {
|
|
573
581
|
return new TelegramReceiver(options);
|
|
@@ -17,6 +17,7 @@ interface TelegramChat {
|
|
|
17
17
|
}
|
|
18
18
|
interface TelegramMessage {
|
|
19
19
|
message_id: number;
|
|
20
|
+
message_thread_id?: number;
|
|
20
21
|
from?: TelegramUser;
|
|
21
22
|
chat: TelegramChat;
|
|
22
23
|
date: number;
|
|
@@ -45,6 +46,7 @@ interface TelegramReceiverOptions {
|
|
|
45
46
|
secretToken: string;
|
|
46
47
|
updateCacheSize?: number;
|
|
47
48
|
}
|
|
49
|
+
declare function matchesCommand(message: TelegramMessage, commands: string[], botUsername?: string): boolean;
|
|
48
50
|
declare class TelegramReceiver {
|
|
49
51
|
private messageBus;
|
|
50
52
|
private editedMessageBus;
|
|
@@ -80,6 +82,12 @@ declare class TelegramClient {
|
|
|
80
82
|
deleteWebhook(opts?: {
|
|
81
83
|
dropPendingUpdates?: boolean;
|
|
82
84
|
}): Promise<boolean>;
|
|
85
|
+
getUpdates(opts?: {
|
|
86
|
+
offset?: number;
|
|
87
|
+
timeout?: number;
|
|
88
|
+
allowedUpdates?: string[];
|
|
89
|
+
signal?: AbortSignal;
|
|
90
|
+
}): Promise<TelegramUpdate[]>;
|
|
83
91
|
}
|
|
84
92
|
declare function telegram(options: TelegramReceiverOptions): TelegramReceiver;
|
|
85
|
-
export { telegram, TelegramClient, TelegramReceiver, splitMessage, type SendMessageDraftOptions, type SendMessageOptions, type TelegramChat, type TelegramChatAction, type TelegramMessage, type TelegramReceiverOptions, type TelegramUpdate, type TelegramUser, };
|
|
93
|
+
export { matchesCommand, telegram, TelegramClient, TelegramReceiver, splitMessage, type SendMessageDraftOptions, type SendMessageOptions, type TelegramChat, type TelegramChatAction, type TelegramMessage, type TelegramReceiverOptions, type TelegramUpdate, type TelegramUser, };
|