hybrid 1.3.0 → 1.3.1

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.
@@ -27,7 +27,7 @@ type DefaultRuntimeExtension = Record<string, never>;
27
27
  /**
28
28
  * Configuration interface for creating custom tools that integrate with AI SDK.
29
29
  */
30
- interface ToolConfig<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
30
+ interface ToolConfig<TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
31
31
  /** Unique identifier for the tool */
32
32
  id: string;
33
33
  /** Human-readable description of what the tool does */
@@ -47,7 +47,7 @@ interface ToolConfig<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends
47
47
  * Internal tool interface used throughout the agent framework.
48
48
  * Similar to ToolConfig but without the ID field, used after tool creation.
49
49
  */
50
- interface Tool<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
50
+ interface Tool<TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
51
51
  /** Human-readable description of what the tool does */
52
52
  description: string;
53
53
  /** Zod schema for validating tool input */
@@ -65,12 +65,12 @@ interface Tool<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.Zod
65
65
  * Factory function to create tools with custom runtime extensions.
66
66
  * Provides proper type inference for input/output schemas and runtime extensions.
67
67
  */
68
- declare function toolFactory<TRuntimeExtension = DefaultRuntimeExtension>(): <TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, TRuntimeExtension>) => Tool<TInput, TOutput, TRuntimeExtension>;
68
+ declare function toolFactory<TRuntimeExtension = DefaultRuntimeExtension>(): <TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny>, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, TRuntimeExtension>) => Tool<TInput, TOutput, TRuntimeExtension>;
69
69
  /**
70
70
  * Default tool factory with no runtime extensions.
71
71
  * Type-safe at creation time with proper schema inference.
72
72
  */
73
- declare const createTool: <TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, DefaultRuntimeExtension>) => Tool<TInput, TOutput, DefaultRuntimeExtension>;
73
+ declare const createTool: <TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny>, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, DefaultRuntimeExtension>) => Tool<TInput, TOutput, DefaultRuntimeExtension>;
74
74
 
75
75
  declare const SUPPORTED_CHAINS: {
76
76
  readonly mainnet: {
@@ -27,7 +27,7 @@ type DefaultRuntimeExtension = Record<string, never>;
27
27
  /**
28
28
  * Configuration interface for creating custom tools that integrate with AI SDK.
29
29
  */
30
- interface ToolConfig<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
30
+ interface ToolConfig<TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
31
31
  /** Unique identifier for the tool */
32
32
  id: string;
33
33
  /** Human-readable description of what the tool does */
@@ -47,7 +47,7 @@ interface ToolConfig<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends
47
47
  * Internal tool interface used throughout the agent framework.
48
48
  * Similar to ToolConfig but without the ID field, used after tool creation.
49
49
  */
50
- interface Tool<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
50
+ interface Tool<TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny, TRuntimeExtension = DefaultRuntimeExtension> {
51
51
  /** Human-readable description of what the tool does */
52
52
  description: string;
53
53
  /** Zod schema for validating tool input */
@@ -65,12 +65,12 @@ interface Tool<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.Zod
65
65
  * Factory function to create tools with custom runtime extensions.
66
66
  * Provides proper type inference for input/output schemas and runtime extensions.
67
67
  */
68
- declare function toolFactory<TRuntimeExtension = DefaultRuntimeExtension>(): <TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, TRuntimeExtension>) => Tool<TInput, TOutput, TRuntimeExtension>;
68
+ declare function toolFactory<TRuntimeExtension = DefaultRuntimeExtension>(): <TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny>, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, TRuntimeExtension>) => Tool<TInput, TOutput, TRuntimeExtension>;
69
69
  /**
70
70
  * Default tool factory with no runtime extensions.
71
71
  * Type-safe at creation time with proper schema inference.
72
72
  */
73
- declare const createTool: <TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, DefaultRuntimeExtension>) => Tool<TInput, TOutput, DefaultRuntimeExtension>;
73
+ declare const createTool: <TInput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny>, TOutput extends z.ZodTypeAny | z.ZodEffects<z.ZodTypeAny> = z.ZodTypeAny>(config: ToolConfig<TInput, TOutput, DefaultRuntimeExtension>) => Tool<TInput, TOutput, DefaultRuntimeExtension>;
74
74
 
75
75
  declare const SUPPORTED_CHAINS: {
76
76
  readonly mainnet: {
package/dist/index.cjs CHANGED
@@ -81,16 +81,16 @@ var import_chains = require("viem/chains");
81
81
  // src/lib/jwt.ts
82
82
  var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
83
83
  var JWT_SECRET = (() => {
84
- const secret = process.env.XMTP_JWT_SECRET;
84
+ const secret = process.env.XMTP_ENCRYPTION_KEY;
85
85
  const nodeEnv = process.env.NODE_ENV || "development";
86
86
  if (nodeEnv === "production" && !secret) {
87
87
  throw new Error(
88
- "XMTP_JWT_SECRET environment variable is required in production. Generate a secure random secret for JWT token signing."
88
+ "XMTP_ENCRYPTION_KEY environment variable is required in production. Generate a secure random secret for JWT token signing."
89
89
  );
90
90
  }
91
91
  if (!secret) {
92
92
  console.warn(
93
- "\u26A0\uFE0F [SECURITY] Using fallback JWT secret for development. Set XMTP_JWT_SECRET environment variable for production."
93
+ "\u26A0\uFE0F [SECURITY] Using fallback JWT secret for development. Set XMTP_ENCRYPTION_KEY environment variable for production."
94
94
  );
95
95
  return "fallback-secret-for-dev-only";
96
96
  }
@@ -129,6 +129,221 @@ function generateXMTPToolsToken(payload) {
129
129
  var BG_STARTED = Symbol("BG_STARTED");
130
130
  var BG_STATE = Symbol("BG_STATE");
131
131
  var BG_STOP = Symbol("BG_STOP");
132
+ function sleep(ms, signal) {
133
+ return new Promise((resolve, reject) => {
134
+ if (signal?.aborted) {
135
+ reject(new Error("AbortError"));
136
+ return;
137
+ }
138
+ const timeout = setTimeout(resolve, ms);
139
+ if (signal) {
140
+ signal.addEventListener(
141
+ "abort",
142
+ () => {
143
+ clearTimeout(timeout);
144
+ reject(new Error("AbortError"));
145
+ },
146
+ { once: true }
147
+ );
148
+ }
149
+ });
150
+ }
151
+ function createBackgroundMessageProcessor(opts) {
152
+ if (!opts?.agent || !opts?.xmtpClient) {
153
+ throw new Error(
154
+ "createBackgroundMessageProcessor: agent and xmtpClient are required"
155
+ );
156
+ }
157
+ const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
158
+ const baseBackoffMs = Math.max(100, opts.backoffMs ?? 1e3);
159
+ const maxBackoffMs = Math.max(baseBackoffMs, opts.maxBackoffMs ?? 3e4);
160
+ if (!globalThis[BG_STARTED]) {
161
+ ;
162
+ globalThis[BG_STARTED] = true;
163
+ const state = {
164
+ running: true,
165
+ lastStartAt: Date.now(),
166
+ consecutiveErrors: 0,
167
+ messagesProcessed: 0,
168
+ listenerRunning: false
169
+ };
170
+ globalThis[BG_STATE] = state;
171
+ const ac = new AbortController();
172
+ const signal = ac.signal;
173
+ const stop = () => {
174
+ if (!state.running) return;
175
+ state.running = false;
176
+ ac.abort();
177
+ console.log("[XMTP Background] Stopping message processor...");
178
+ };
179
+ globalThis[BG_STOP] = stop;
180
+ process.once("SIGINT", stop);
181
+ process.once("SIGTERM", stop);
182
+ const publicClient = (0, import_viem.createPublicClient)({
183
+ chain: import_chains.base,
184
+ transport: (0, import_viem.http)()
185
+ });
186
+ const listener = new import_xmtp.MessageListener({
187
+ publicClient,
188
+ xmtpClient: opts.xmtpClient,
189
+ filter: opts.messageFilter,
190
+ heartbeatInterval: 5 * 60 * 1e3,
191
+ // 5 minutes
192
+ conversationCheckInterval: 30 * 1e3
193
+ // 30 seconds
194
+ });
195
+ listener.on("message", async (messageEvent) => {
196
+ try {
197
+ console.log(
198
+ `[XMTP Background] Processing message: ${messageEvent.message.content}`
199
+ );
200
+ console.log(
201
+ `[XMTP Background] Sender: ${messageEvent.sender?.address || "unknown"}`
202
+ );
203
+ console.log(
204
+ `[XMTP Background] Conversation: ${messageEvent.message.conversationId}`
205
+ );
206
+ const messages = [
207
+ {
208
+ id: (0, import_node_crypto.randomUUID)(),
209
+ role: "user",
210
+ parts: [
211
+ {
212
+ type: "text",
213
+ text: messageEvent.message.content?.toString() || ""
214
+ }
215
+ ]
216
+ }
217
+ ];
218
+ const serviceUrl = process.env.AGENT_URL || "http://localhost:8454";
219
+ const serviceToken = generateXMTPToolsToken({
220
+ action: "send",
221
+ conversationId: messageEvent.message.conversationId,
222
+ content: messageEvent.message.content?.toString() || ""
223
+ });
224
+ const serviceClient = (0, import_xmtp.createAuthenticatedXmtpClient)(
225
+ serviceUrl,
226
+ serviceToken
227
+ );
228
+ const baseRuntime = {
229
+ chatId: messageEvent.message.conversationId,
230
+ messages,
231
+ conversation: messageEvent.conversation,
232
+ message: messageEvent.message,
233
+ parentMessage: messageEvent.parentMessage,
234
+ rootMessage: messageEvent.rootMessage,
235
+ sender: messageEvent.sender,
236
+ subjects: messageEvent.subjects,
237
+ xmtpClient: serviceClient
238
+ };
239
+ const runtime = await opts.agent.createRuntimeContext(baseRuntime);
240
+ console.log("[XMTP Background] Calling agent to process message...");
241
+ const result = await opts.agent.generate(messages, { runtime });
242
+ if (result.text) {
243
+ await messageEvent.conversation.send(result.text);
244
+ console.log(`[XMTP Background] Agent response sent: ${result.text}`);
245
+ }
246
+ state.messagesProcessed++;
247
+ state.lastOkAt = Date.now();
248
+ state.consecutiveErrors = 0;
249
+ console.log(
250
+ `[XMTP Background] Message processed successfully. Total: ${state.messagesProcessed}`
251
+ );
252
+ } catch (error) {
253
+ state.lastErrAt = Date.now();
254
+ state.consecutiveErrors++;
255
+ console.error("[XMTP Background] Error processing message:", error);
256
+ try {
257
+ await messageEvent.conversation.send(
258
+ "Sorry, I encountered an error processing your message."
259
+ );
260
+ } catch (sendError) {
261
+ console.error(
262
+ "[XMTP Background] Failed to send error message:",
263
+ sendError
264
+ );
265
+ }
266
+ }
267
+ });
268
+ listener.on("error", (error) => {
269
+ state.lastErrAt = Date.now();
270
+ state.consecutiveErrors++;
271
+ console.error("[XMTP Background] Message listener error:", error);
272
+ });
273
+ listener.on("started", () => {
274
+ state.listenerRunning = true;
275
+ console.log("[XMTP Background] Message listener started");
276
+ });
277
+ listener.on("stopped", () => {
278
+ state.listenerRunning = false;
279
+ console.log("[XMTP Background] Message listener stopped");
280
+ });
281
+ listener.on("heartbeat", (stats) => {
282
+ console.log(
283
+ `[XMTP Background] Heartbeat - Messages: ${stats.messageCount}, Conversations: ${stats.conversationCount}`
284
+ );
285
+ });
286
+ (async function supervise() {
287
+ let nextDelay = intervalMs;
288
+ while (state.running) {
289
+ try {
290
+ if (!state.listenerRunning) {
291
+ console.log("[XMTP Background] Starting message listener...");
292
+ await listener.start();
293
+ }
294
+ if (state.listenerRunning) {
295
+ state.lastOkAt = Date.now();
296
+ state.consecutiveErrors = 0;
297
+ nextDelay = intervalMs;
298
+ } else {
299
+ throw new Error("Message listener is not running");
300
+ }
301
+ } catch (error) {
302
+ state.lastErrAt = Date.now();
303
+ state.consecutiveErrors += 1;
304
+ const backoff = Math.min(
305
+ maxBackoffMs,
306
+ baseBackoffMs * 2 ** (state.consecutiveErrors - 1)
307
+ );
308
+ nextDelay = backoff;
309
+ console.error("[XMTP Background] Supervisor error:", error);
310
+ try {
311
+ if (state.listenerRunning) {
312
+ await listener.stop();
313
+ }
314
+ await sleep(1e3);
315
+ } catch (restartError) {
316
+ console.error(
317
+ "[XMTP Background] Error restarting listener:",
318
+ restartError
319
+ );
320
+ }
321
+ }
322
+ try {
323
+ await sleep(nextDelay, signal);
324
+ } catch (error) {
325
+ if (error instanceof Error && error.name === "AbortError") {
326
+ break;
327
+ }
328
+ throw error;
329
+ }
330
+ }
331
+ try {
332
+ if (state.listenerRunning) {
333
+ console.log("[XMTP Background] Stopping message listener...");
334
+ await listener.stop();
335
+ }
336
+ } catch (error) {
337
+ console.error("[XMTP Background] Error stopping listener:", error);
338
+ }
339
+ console.log("[XMTP Background] Supervisor stopped");
340
+ })();
341
+ console.log(
342
+ "[XMTP Background] Message processor started (always-on, in-process)"
343
+ );
344
+ }
345
+ return async (_c, next) => next();
346
+ }
132
347
  function getBgState() {
133
348
  return globalThis[BG_STATE];
134
349
  }
@@ -138,6 +353,12 @@ function stopBackground() {
138
353
  }
139
354
 
140
355
  // src/server/listen.ts
356
+ function createHonoMiddleware(client) {
357
+ return async (c, next) => {
358
+ c.set("xmtpClient", client);
359
+ return next();
360
+ };
361
+ }
141
362
  async function listen({
142
363
  agent,
143
364
  port,
@@ -148,6 +369,33 @@ async function listen({
148
369
  const context = {
149
370
  agent
150
371
  };
372
+ const { XMTP_WALLET_KEY, XMTP_ENCRYPTION_KEY } = process.env;
373
+ if (!XMTP_WALLET_KEY) {
374
+ throw new Error("XMTP_WALLET_KEY must be set");
375
+ }
376
+ if (!XMTP_ENCRYPTION_KEY) {
377
+ throw new Error("XMTP_ENCRYPTION_KEY must be set");
378
+ }
379
+ const cloudflareStoragePath = (0, import_utils.getCloudflareStoragePath)("xmtp");
380
+ const xmtpClient = await (0, import_xmtp2.createXMTPClient)(XMTP_WALLET_KEY, {
381
+ persist: true,
382
+ storagePath: cloudflareStoragePath
383
+ });
384
+ app.use(createHonoMiddleware(xmtpClient));
385
+ app.use(
386
+ createBackgroundMessageProcessor({
387
+ agent,
388
+ xmtpClient,
389
+ messageFilter: filter,
390
+ // Use the provided filter
391
+ intervalMs: 5e3,
392
+ // Check every 5 seconds
393
+ backoffMs: 1e3,
394
+ // Start with 1 second backoff
395
+ maxBackoffMs: 3e4
396
+ // Max 30 seconds backoff
397
+ })
398
+ );
151
399
  const xmtpPlugin = (0, import_xmtp2.XMTPPlugin)({
152
400
  filter
153
401
  });
@@ -206,7 +454,8 @@ async function listen({
206
454
  fetch: app.fetch,
207
455
  port: httpPort
208
456
  });
209
- console.log(`\u2705 XMTP Tools HTTP Server running on port ${httpPort}`);
457
+ console.log(`\u2705 Hybrid server running on port ${httpPort}`);
458
+ console.log(`\u{1F3A7} Background message listener is active`);
210
459
  } catch (error) {
211
460
  if (error.code === "EADDRINUSE") {
212
461
  console.error(