@eidentic/server 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.
@@ -0,0 +1,1068 @@
1
+ import * as hono from 'hono';
2
+ import { Hono } from 'hono';
3
+ import { cors } from 'hono/cors';
4
+ import { Agent } from '@eidentic/core';
5
+ import { RateLimiterPort, RateLimitResult, QuotaPort, QuotaLimits, QuotaCheck, StreamEvent, LoggerPort, Usage, CostBreakdown, AuthPrincipal, AuthPort } from '@eidentic/types';
6
+ export { AuthPort, AuthPrincipal, AuthRequest, QuotaCheck, QuotaLimits, QuotaPort, QuotaUsage, RateLimiterPort } from '@eidentic/types';
7
+ import { WorkflowResult, WorkflowRunOwner, RecordOptions, WorkflowRunError, WorkflowRunRegistry, StepTrace } from '@eidentic/workflow';
8
+ export { RecordOptions, WorkflowRunError, WorkflowRunOwner, WorkflowRunRegistry } from '@eidentic/workflow';
9
+
10
+ interface TokenBucketOptions {
11
+ /** Maximum number of tokens the bucket can hold. */
12
+ capacity: number;
13
+ /** Tokens added per second. Set to 0 for a one-shot allow-N-then-block bucket. */
14
+ refillPerSec: number;
15
+ /** Injectable clock for testing. Defaults to Date.now. */
16
+ now?: () => number;
17
+ }
18
+ /**
19
+ * In-process token-bucket rate limiter implementing `RateLimiterPort` (§20.3).
20
+ *
21
+ * Each unique `key` gets an independent bucket, created lazily on first `acquire`.
22
+ * On each call the bucket is refilled based on elapsed time before the token check,
23
+ * so callers never see more than `capacity` tokens regardless of how long they wait.
24
+ *
25
+ * ## Memory management
26
+ *
27
+ * Bucket entries are evicted opportunistically on `acquire`. A sweep runs at most
28
+ * once per full-refill window (`capacity / refillPerSec * 1000` ms) to keep cost
29
+ * amortised. Entries older than twice the full-refill window are dropped — at that
30
+ * age the bucket would be fully refilled anyway, so eviction is semantically
31
+ * lossless. No background timer is used.
32
+ *
33
+ * @note For multi-process deployments use a store-backed or Redis-backed limiter.
34
+ */
35
+ declare class InMemoryTokenBucketLimiter implements RateLimiterPort {
36
+ private readonly capacity;
37
+ private readonly refillPerSec;
38
+ private readonly now;
39
+ private readonly buckets;
40
+ /**
41
+ * The eviction threshold in ms: entries older than this are guaranteed to be
42
+ * at full capacity, so removing them is lossless.
43
+ * = (capacity / refillPerSec) * 1000 * 2
44
+ * For zero-refill configs a fixed 24-hour window bounds growth.
45
+ */
46
+ private readonly evictThresholdMs;
47
+ /**
48
+ * Sweep runs at most once per this interval. Set to half the eviction
49
+ * threshold so that stale entries are caught within at most one extra window.
50
+ */
51
+ private readonly sweepIntervalMs;
52
+ private lastSweepMs;
53
+ constructor(opts: TokenBucketOptions);
54
+ /**
55
+ * Test-only accessor: number of entries currently held in the buckets map.
56
+ * Not part of the `RateLimiterPort` contract.
57
+ */
58
+ get bucketCount(): number;
59
+ acquire(key: string, cost?: number): RateLimitResult;
60
+ /**
61
+ * Evict bucket entries older than `evictThresholdMs`. Called opportunistically
62
+ * from `acquire` — no timer involved.
63
+ */
64
+ private _sweep;
65
+ }
66
+
67
+ /** Token produced by `check` and consumed by `record` or `release` to pair a reservation with its settlement. */
68
+ interface QuotaReservation {
69
+ readonly key: string;
70
+ /** Opaque generation counter — ensures stale tokens from a reset() don't settle against fresh state. */
71
+ readonly generation: number;
72
+ /** Wall-clock timestamp (ms) when the reservation was created — used by the max-age sweep. */
73
+ readonly createdAt: number;
74
+ }
75
+ /**
76
+ * In-process cumulative quota ledger implementing `QuotaPort` (§20.4) with reserve-on-check /
77
+ * settle-on-record semantics to prevent concurrent requests from bursting past hard caps.
78
+ *
79
+ * Each unique `key` gets an independent usage counter, created lazily on first access.
80
+ *
81
+ * **Reserve-settle protocol (A8)**
82
+ * `check(key)` now also reserves 1 run in an in-flight counter. Ceilings are checked against
83
+ * `committed + reserved`, so concurrent callers that have not yet settled see each other's
84
+ * pending runs. `record(key, spend, reservation)` settles the reservation (replacing the
85
+ * 1-run hold with the actual spend) and increments the committed counter.
86
+ *
87
+ * Callers SHOULD call `record` or `release` after every successful `check` to avoid leaking
88
+ * reservations. The `reservation` token returned on `ok:true` should be stored and passed back.
89
+ * Legacy callers that omit the `reservation` argument to `record` are still supported
90
+ * (backward-compatible): the committed counter increments normally, but the in-flight count
91
+ * is not decremented (they were never reserving anyway).
92
+ *
93
+ * **Max-age sweep (Fix #4 defense-in-depth)**
94
+ * Reservations that are never settled (e.g. the server process crashes mid-run without calling
95
+ * `record`/`release`) would permanently inflate the in-flight counter and eventually block all
96
+ * new runs. A background sweep runs every `reservationMaxAgeMs` (default 5 minutes) and
97
+ * discards any reservation older than that threshold, freeing the leaked slot.
98
+ * Call `destroy()` when shutting down to stop the sweep interval.
99
+ *
100
+ * @note In-memory + unbounded map = v1. Real deployments should back this with a store
101
+ * or Redis. A `reset(key?)` helper is provided for tests and dev tooling.
102
+ *
103
+ * Deferred (v1): storage quotas, monthly-window reset/approval flow,
104
+ * model-downgrade-on-soft, persistent/Redis ledger, USD/token-level reservations
105
+ * (current implementation reserves 1 run; usd/token over-burst under extreme concurrency
106
+ * is bounded to at most `concurrency * limit_per_call`, significantly tighter than without).
107
+ */
108
+ declare class InMemoryQuota implements QuotaPort {
109
+ private readonly resolve;
110
+ private readonly ledger;
111
+ /** In-flight run count per key: incremented on check, decremented on record/release. */
112
+ private readonly reserved;
113
+ /** Per-key monotonic generation counter, bumped on reset() to invalidate outstanding tokens. */
114
+ private readonly generations;
115
+ /** All live reservation tokens, used by the max-age sweep. */
116
+ private readonly activeReservations;
117
+ /** Max-age sweep interval handle — cleared on destroy(). */
118
+ private readonly sweepInterval;
119
+ /** How old a reservation must be (ms) before the sweep discards it. Default 5 minutes. */
120
+ private readonly reservationMaxAgeMs;
121
+ constructor(limits: QuotaLimits | ((key: string) => QuotaLimits), options?: {
122
+ /**
123
+ * Maximum age (milliseconds) of an unsettled reservation before the sweep
124
+ * automatically releases it. Defaults to 300_000 (5 minutes).
125
+ * Set to 0 or Infinity to disable the sweep.
126
+ */
127
+ reservationMaxAgeMs?: number;
128
+ });
129
+ /**
130
+ * Release all stale reservations (older than `reservationMaxAgeMs`).
131
+ * Called automatically by the background sweep, but also available for testing.
132
+ */
133
+ sweepStaleReservations(): void;
134
+ /**
135
+ * Stop the background sweep interval. Call this when shutting down to prevent
136
+ * open handle warnings in tests and to release resources cleanly.
137
+ */
138
+ destroy(): void;
139
+ private getUsage;
140
+ private getGeneration;
141
+ private getReserved;
142
+ /**
143
+ * Check whether `key` may start another run. On success, reserves 1 run in the in-flight
144
+ * counter and returns a `reservation` token. Hard ceilings are checked against
145
+ * `committed + reserved` so concurrent callers see each other's pending runs.
146
+ *
147
+ * Always call `record(key, spend, reservation)` or `release(reservation)` after `check`
148
+ * to free the reservation and prevent in-flight count leakage.
149
+ */
150
+ check(key: string): QuotaCheck & {
151
+ reservation?: QuotaReservation;
152
+ };
153
+ /**
154
+ * Settle a reservation by recording actual spend. The 1-run reservation is consumed and the
155
+ * committed counter is updated with the real spend. If `reservation` is provided and stale
156
+ * (reset() was called between check and record), the settle is a no-op — no double-counting.
157
+ *
158
+ * Legacy callers that omit `reservation` still have their spend committed (backward-compatible),
159
+ * but the in-flight counter is not decremented (they weren't reserving).
160
+ */
161
+ record(key: string, spend: {
162
+ usd: number;
163
+ tokens: number;
164
+ }, reservation?: QuotaReservation): void;
165
+ /**
166
+ * Release a reservation WITHOUT recording spend (error / abort path).
167
+ * If the token is stale (reset() was called), this is a no-op.
168
+ */
169
+ release(reservation: QuotaReservation): void;
170
+ /**
171
+ * Reset usage counters for a specific key, or ALL keys when called with no argument.
172
+ * Increments the generation counter for affected keys so outstanding reservation tokens
173
+ * become stale and cannot settle against the fresh state.
174
+ * Useful for tests and dev tooling.
175
+ */
176
+ reset(key?: string): void;
177
+ }
178
+
179
+ /**
180
+ * AI SDK UI message-stream bridge (§ feat/ai-sdk-ui-stream).
181
+ *
182
+ * Converts Eidentic's `StreamEvent` async iterable into the Vercel AI SDK v6
183
+ * UI message-stream protocol so that a Next.js `app/api/chat/route.ts` can
184
+ * call `return toUIMessageStreamResponse(agent.query(input, { sessionId }))`
185
+ * and a `useChat` (or CopilotKit) frontend just works.
186
+ *
187
+ * ## Mapping
188
+ *
189
+ * | Eidentic `StreamEvent` | AI SDK `UIMessageChunk` |
190
+ * |-------------------------------------|------------------------------------------|
191
+ * | `stream.delta` | `text-delta` (streaming token) |
192
+ * | `assistant` text blocks | `text-start` + `text-delta` + `text-end` |
193
+ * | `assistant` tool_use blocks | `tool-input-available` |
194
+ * | `tool.result` (success) | `tool-output-available` |
195
+ * | `tool.result` (error) | `tool-output-error` |
196
+ * | `result` (terminal, any subtype) | `finish` |
197
+ * | `error` (thrown inside generator) | `error` chunk (via `onError`) |
198
+ *
199
+ * `session.init` and `compaction` events are silently ignored — they carry
200
+ * metadata/audit information that is not meaningful to a chat UI.
201
+ *
202
+ * ## Usage (Next.js App Router)
203
+ *
204
+ * ```ts
205
+ * // app/api/chat/route.ts
206
+ * import { toUIMessageStreamResponse } from "@eidentic/server";
207
+ * import { myAgent } from "@/lib/agent";
208
+ *
209
+ * export async function POST(req: Request) {
210
+ * const { messages, sessionId } = await req.json();
211
+ * const input = messages.at(-1)?.content ?? "";
212
+ * return toUIMessageStreamResponse(
213
+ * myAgent.query(input, { sessionId }),
214
+ * );
215
+ * }
216
+ * ```
217
+ *
218
+ * On the client, configure `useChat` to point at this route — no further
219
+ * changes are needed since the response uses the standard AI SDK SSE wire
220
+ * format.
221
+ */
222
+
223
+ interface ToUIMessageStreamOptions {
224
+ /**
225
+ * Optional HTTP headers to include in the `Response` returned by
226
+ * `toUIMessageStreamResponse`. Useful for CORS, cache-control, etc.
227
+ */
228
+ headers?: Headers | Record<string, string>;
229
+ /**
230
+ * HTTP status code for the response. Defaults to 200.
231
+ */
232
+ status?: number;
233
+ }
234
+ /**
235
+ * Converts a Eidentic `StreamEvent` async iterable into a
236
+ * `ReadableStream<UIMessageChunk>` compatible with the Vercel AI SDK v6
237
+ * UI message-stream protocol.
238
+ *
239
+ * Use `toUIMessageStreamResponse` for the common case of returning a `Response`
240
+ * from a Next.js route handler. Use this function directly when you need the
241
+ * raw stream (e.g. for piping to a Node.js `ServerResponse`).
242
+ */
243
+ declare function toUIMessageStream(events: AsyncIterable<StreamEvent>): ReadableStream;
244
+ /**
245
+ * Converts a Eidentic `StreamEvent` async iterable into a `Response` that
246
+ * streams AI SDK UI message chunks to the client (SSE format).
247
+ *
248
+ * This is the primary integration point for Next.js App Router route handlers.
249
+ * The returned `Response` is directly compatible with `useChat` from the
250
+ * `@ai-sdk/react` package and frameworks that speak the AI SDK UI wire format
251
+ * (CopilotKit, etc.).
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * // app/api/chat/route.ts
256
+ * import { toUIMessageStreamResponse } from "@eidentic/server";
257
+ * import { myAgent } from "@/lib/agent";
258
+ *
259
+ * export async function POST(req: Request) {
260
+ * const { messages, sessionId } = await req.json();
261
+ * const input = messages.at(-1)?.content ?? "";
262
+ * return toUIMessageStreamResponse(
263
+ * myAgent.query(input, { sessionId }),
264
+ * );
265
+ * }
266
+ * ```
267
+ */
268
+ declare function toUIMessageStreamResponse(events: AsyncIterable<StreamEvent>, opts?: ToUIMessageStreamOptions): Response;
269
+
270
+ /**
271
+ * @eidentic/server — in-process agent scheduler.
272
+ *
273
+ * Registers tasks that fire an agent-run callback on a fixed interval or cron
274
+ * expression. Designed for in-process background work; persistence and
275
+ * multi-instance leader-election are **out of scope** (no DB, no distributed
276
+ * lock). For durable/multi-instance scheduling, pair this with a purpose-built
277
+ * durable job queue or distributed scheduler as a follow-up.
278
+ *
279
+ * ## Clock / timer injection
280
+ *
281
+ * The scheduler accepts an optional `ClockPort` (a thin `{ now(): number }`
282
+ * interface) and an optional `TimerPort` (wraps `setInterval` / `clearInterval`).
283
+ * In production, the defaults delegate to the real `Date.now()` and global
284
+ * `setInterval`. In tests, inject fakes to drive time without real sleeping.
285
+ *
286
+ * ## Overlap / concurrency
287
+ *
288
+ * If a task's previous invocation is still in flight when the next tick fires,
289
+ * the tick is **skipped silently** — runs never pile up. This is intentional: the
290
+ * scheduler is optimistic ("at-most-once per interval") rather than catch-up
291
+ * ("missed runs queue up"). Document this clearly to callers.
292
+ *
293
+ * ## Error isolation
294
+ *
295
+ * Each task's run callback is invoked in its own microtask chain. An unhandled
296
+ * rejection or thrown error is caught, logged via the injected `LoggerPort`, and
297
+ * swallowed so that one failing task never kills the scheduler or other tasks.
298
+ */
299
+
300
+ /**
301
+ * Fire the task every `everyMs` milliseconds (wall-clock elapsed since the
302
+ * last trigger, not since epoch). First fire happens after one full interval.
303
+ */
304
+ interface IntervalSchedule {
305
+ kind: "interval";
306
+ everyMs: number;
307
+ }
308
+ /**
309
+ * Fire the task according to a standard 5-field cron expression
310
+ * (`"* * * * *"` — minute hour dom month dow). The next run time is computed
311
+ * by `cron-parser` from the current clock value on each tick.
312
+ *
313
+ * Timezone is UTC by default; pass `tz` to override.
314
+ */
315
+ interface CronSchedule {
316
+ kind: "cron";
317
+ expression: string;
318
+ /** IANA timezone identifier, e.g. `"America/New_York"`. Defaults to UTC. */
319
+ tz?: string;
320
+ }
321
+ /** Union of all supported schedule shapes. */
322
+ type Schedule = IntervalSchedule | CronSchedule;
323
+ /** Context passed to a task's run callback on each trigger. */
324
+ interface RunContext {
325
+ /** The task's registered id. */
326
+ taskId: string;
327
+ /** Wall-clock timestamp (ms since epoch) when this trigger fired. */
328
+ triggeredAt: number;
329
+ }
330
+ /** A function that performs the agent work for a scheduled task. */
331
+ type RunCallback = (ctx: RunContext) => void | Promise<void>;
332
+ interface ScheduledTask {
333
+ /** Unique identifier for this task. Used with `remove(id)`. */
334
+ id: string;
335
+ /** When to fire. */
336
+ schedule: Schedule;
337
+ /**
338
+ * Called on each trigger. Typically invokes `agent.query(...)` with the
339
+ * relevant input and owner scope.
340
+ *
341
+ * The callback runs asynchronously; errors are caught and logged.
342
+ * If the previous invocation is still in-flight when the next tick fires,
343
+ * this fire is **skipped** (no overlap — at-most-once-per-interval semantics).
344
+ */
345
+ run: RunCallback;
346
+ }
347
+ /** Minimal clock interface — injectable for deterministic tests. */
348
+ interface ClockPort {
349
+ now(): number;
350
+ }
351
+ /** Injectable timer port — wraps setInterval / clearInterval. */
352
+ interface TimerPort {
353
+ setInterval(fn: () => void, ms: number): unknown;
354
+ clearInterval(handle: unknown): void;
355
+ }
356
+ interface SchedulerOptions {
357
+ /**
358
+ * How often the scheduler checks for due tasks.
359
+ * Defaults to `1000` ms (1 second). Tune lower for sub-second granularity;
360
+ * cron resolution is 1 minute regardless (cron-parser is minute-granular).
361
+ */
362
+ tickIntervalMs?: number;
363
+ /** Injectable clock — defaults to `Date.now()`. Inject a fake in tests. */
364
+ clock?: ClockPort;
365
+ /** Injectable timer — defaults to `globalThis.setInterval`. Inject a fake in tests. */
366
+ timer?: TimerPort;
367
+ /**
368
+ * Structured logger for task error reporting.
369
+ * Pass `NoopLogger` to silence all output.
370
+ * Defaults to a minimal `console.error` shim.
371
+ */
372
+ logger?: LoggerPort;
373
+ }
374
+ /**
375
+ * In-process agent scheduler.
376
+ *
377
+ * ```ts
378
+ * const scheduler = new Scheduler({ logger: myLogger });
379
+ *
380
+ * scheduler.add({
381
+ * id: "digest",
382
+ * schedule: { kind: "cron", expression: "0 9 * * *", tz: "America/New_York" },
383
+ * run: async () => {
384
+ * for await (const _ of agent.query("Generate daily digest", { sessionId: crypto.randomUUID() })) {}
385
+ * },
386
+ * });
387
+ *
388
+ * scheduler.start();
389
+ * // …
390
+ * scheduler.stop();
391
+ * ```
392
+ *
393
+ * ### Overlap skip
394
+ * If a task's previous run is still in flight when its next fire time arrives,
395
+ * the tick is silently skipped. This prevents unbounded pile-up on slow tasks.
396
+ *
397
+ * ### Error isolation
398
+ * Each task's `run` callback error is caught and logged; it does NOT propagate
399
+ * to the scheduler or affect other tasks.
400
+ *
401
+ * ### Persistence / distribution
402
+ * This is a **purely in-process** scheduler — state lives in memory, is lost
403
+ * on restart, and is NOT coordinated across multiple instances. For durable or
404
+ * multi-instance scheduling, wire an external queue/workflow engine.
405
+ */
406
+ declare class Scheduler {
407
+ private readonly tickIntervalMs;
408
+ private readonly clock;
409
+ private readonly timer;
410
+ private readonly logger;
411
+ private readonly tasks;
412
+ private timerHandle;
413
+ private running;
414
+ constructor(opts?: SchedulerOptions);
415
+ /**
416
+ * Start the scheduler's internal tick loop.
417
+ * Calling `start()` while already running is a no-op.
418
+ */
419
+ start(): void;
420
+ /**
421
+ * Stop the scheduler. All in-flight callbacks are allowed to complete
422
+ * (they are not aborted), but no new runs will be triggered.
423
+ * Calling `stop()` while not running is a no-op.
424
+ */
425
+ stop(): void;
426
+ /**
427
+ * Register a scheduled task. If a task with the same `id` already exists,
428
+ * it is replaced (the old state is discarded).
429
+ *
430
+ * For cron tasks, the first next-fire time is computed immediately from the
431
+ * current clock value.
432
+ */
433
+ add(task: ScheduledTask): void;
434
+ /**
435
+ * Remove a registered task by id.
436
+ * Any in-flight callback for this task will be allowed to complete, but no
437
+ * further fires will occur.
438
+ * Returns `true` if the task was found and removed, `false` otherwise.
439
+ */
440
+ remove(id: string): boolean;
441
+ /** Returns the ids of all currently registered tasks. */
442
+ taskIds(): string[];
443
+ /**
444
+ * Evaluate all registered tasks against the given timestamp and fire any
445
+ * that are due.
446
+ *
447
+ * This is the core scheduling method. The `start()` loop calls it
448
+ * automatically at `tickIntervalMs` granularity, but tests can call it
449
+ * directly to drive time without real timers.
450
+ */
451
+ tick(now: number): void;
452
+ private _fire;
453
+ }
454
+
455
+ /**
456
+ * @eidentic/server — BatchRunner: bounded-concurrency offline/batch agent processing.
457
+ *
458
+ * ## Overview
459
+ *
460
+ * `BatchRunner` takes an array of inputs and runs each through an agent with a
461
+ * configurable concurrency cap (default 4). Items that throw are captured as
462
+ * per-item errors — a single bad item never aborts the batch. Aggregate
463
+ * usage/cost totals are accumulated using the existing `Usage` / `CostBreakdown`
464
+ * types from `@eidentic/types`.
465
+ *
466
+ * ## Provider-native batch (e.g. Anthropic Message Batches API)
467
+ *
468
+ * The Anthropic Message Batches API offers ~50% cost savings for async jobs but
469
+ * is exposed via a separate REST API, NOT through the AI SDK v6 `generateText` /
470
+ * `streamText` surface. Wiring it would require either a dedicated HTTP client
471
+ * per provider or a non-trivial AI-SDK fork — too large for v1.
472
+ *
473
+ * Instead, the `BatchRunner` accepts an optional `backend` parameter (the
474
+ * `BatchBackend` interface). The default `"concurrent"` backend runs items via
475
+ * `agent.query()` with bounded parallelism. A future `"anthropic-batch"` or
476
+ * `"openai-batch"` backend could implement the provider REST APIs behind this
477
+ * seam without changing any public BatchRunner API.
478
+ *
479
+ * ## Concurrency guarantee
480
+ *
481
+ * At most `concurrency` items are ever in-flight simultaneously. The runner uses
482
+ * a slot-based semaphore — no library dep. Items are dispatched in input order;
483
+ * results are collected as they complete (output order may differ when
484
+ * `concurrency > 1`).
485
+ *
486
+ * ## Cancellation
487
+ *
488
+ * Pass an `AbortSignal` via `BatchRunOptions.signal`. Once aborted, no further
489
+ * items are dispatched. Items already in-flight receive the signal and may abort
490
+ * early (dependent on the agent's internal abort handling). A cancelled batch
491
+ * returns partial results up to that point with `aggregate.cancelled: true`.
492
+ *
493
+ * ## Progress callback
494
+ *
495
+ * `onProgress(item)` is called once per completed item (success OR error),
496
+ * passing the completed `BatchItemResult`. Useful for streaming output to a UI
497
+ * or writing partial results to disk.
498
+ */
499
+
500
+ /** A single item to process in a batch. */
501
+ interface BatchItem {
502
+ /**
503
+ * Optional stable identifier for this item.
504
+ * Defaults to the item's zero-based index string ("0", "1", …) when absent.
505
+ */
506
+ id?: string;
507
+ /** The user message to pass to `agent.query()`. */
508
+ input: string;
509
+ /** Optional per-item `userId` forwarded to `agent.query()`. */
510
+ userId?: string;
511
+ /** Optional per-item `orgId` forwarded to `agent.query()`. */
512
+ orgId?: string;
513
+ /** Optional per-item `sessionId`. Generated (UUID) when absent. */
514
+ sessionId?: string;
515
+ }
516
+ /** Outcome of a successfully completed item. */
517
+ interface BatchItemSuccess {
518
+ status: "success";
519
+ id: string;
520
+ /** The agent's final text output (from the terminal `result` event). */
521
+ output: string;
522
+ /** Per-item token usage (foreground totals, same units as `Usage`). */
523
+ usage: Usage;
524
+ /**
525
+ * Per-item cost breakdown. Present only when the agent has a `PriceTable`
526
+ * configured (same condition as `result.cost` being non-undefined).
527
+ */
528
+ cost?: CostBreakdown;
529
+ /** The sessionId used for this item's run. */
530
+ sessionId: string;
531
+ }
532
+ /** Outcome of a failed item (the error is captured; the batch continues). */
533
+ interface BatchItemError {
534
+ status: "error";
535
+ id: string;
536
+ /** Error message. */
537
+ error: string;
538
+ /** The sessionId used for this item's run (may be undefined if dispatch never started). */
539
+ sessionId?: string;
540
+ }
541
+ type BatchItemResult = BatchItemSuccess | BatchItemError;
542
+ /** Aggregate totals across all items in the batch. */
543
+ interface BatchAggregate {
544
+ /** Total input + output tokens across all SUCCESSFUL items. */
545
+ totalUsage: Usage;
546
+ /**
547
+ * Summed USD cost across all successful items that had a `CostBreakdown`.
548
+ * `undefined` when no items had pricing.
549
+ */
550
+ totalUsd?: number;
551
+ /** Number of items that completed with `status: "success"`. */
552
+ successCount: number;
553
+ /** Number of items that completed with `status: "error"`. */
554
+ errorCount: number;
555
+ /**
556
+ * `true` when the batch was stopped early by an AbortSignal.
557
+ * Items that were not yet dispatched when the signal fired are absent from
558
+ * `results` — they were never started.
559
+ */
560
+ cancelled: boolean;
561
+ }
562
+ /** The return value of `BatchRunner.run()`. */
563
+ interface BatchResult {
564
+ /** Per-item outcome, in completion order. May be shorter than `items` if cancelled. */
565
+ results: BatchItemResult[];
566
+ /** Aggregate totals. */
567
+ aggregate: BatchAggregate;
568
+ }
569
+ /** Called once per completed item (success or error), in completion order. */
570
+ type OnProgress = (item: BatchItemResult) => void;
571
+ /**
572
+ * Interface for the batch execution backend.
573
+ *
574
+ * The default `"concurrent"` backend calls `agent.query()` directly with
575
+ * bounded parallelism. Future implementations may use provider-native APIs:
576
+ *
577
+ * ```ts
578
+ * // Future usage (not implemented in v1):
579
+ * const runner = new BatchRunner(agent, {
580
+ * backend: new AnthropicBatchBackend({ apiKey: process.env.ANTHROPIC_API_KEY }),
581
+ * });
582
+ * ```
583
+ *
584
+ * A backend receives one item at a time and is responsible for:
585
+ * 1. Dispatching the item to the underlying inference service.
586
+ * 2. Returning a `BatchItemResult` (NEVER throwing — capture errors as `status:"error"`).
587
+ * 3. Respecting `signal` for cancellation.
588
+ *
589
+ * The `BatchRunner` handles concurrency, cancellation bookkeeping, progress
590
+ * callbacks, and aggregate accumulation — the backend only needs to run ONE item.
591
+ */
592
+ interface BatchBackend {
593
+ /**
594
+ * Execute a single batch item and return its result.
595
+ *
596
+ * Called by `BatchRunner` up to `concurrency` times in parallel. The backend
597
+ * MUST honour `signal.aborted` and terminate early when the signal fires.
598
+ *
599
+ * @param item - The resolved item (id, input, sessionId, userId, orgId all populated).
600
+ * @param signal - The batch's cancellation signal (may already be aborted).
601
+ * @returns The per-item outcome. Should not throw; capture errors as `{ status: "error" }`.
602
+ */
603
+ run(item: Required<Pick<BatchItem, "id" | "input">> & {
604
+ userId: string;
605
+ orgId: string;
606
+ sessionId: string;
607
+ }, signal: AbortSignal): Promise<BatchItemResult>;
608
+ }
609
+ interface BatchRunnerOptions {
610
+ /**
611
+ * Maximum number of items to process concurrently.
612
+ * @default 4
613
+ */
614
+ concurrency?: number;
615
+ /**
616
+ * Batch execution backend.
617
+ * Defaults to `ConcurrentAgentBackend` (calls `agent.query()` directly).
618
+ *
619
+ * Swap this to integrate provider-native batch APIs (e.g. Anthropic Message
620
+ * Batches, OpenAI Batch API) in a future release without changing any other
621
+ * `BatchRunner` API surface.
622
+ */
623
+ backend?: BatchBackend;
624
+ }
625
+ interface BatchRunOptions {
626
+ /**
627
+ * AbortSignal for cancelling the batch. Once aborted, no further items are
628
+ * dispatched. In-flight items receive the signal and may terminate early.
629
+ */
630
+ signal?: AbortSignal;
631
+ /**
632
+ * Optional callback invoked once per completed item (success or error).
633
+ * Called in completion order (not necessarily input order when `concurrency > 1`).
634
+ */
635
+ onProgress?: OnProgress;
636
+ /**
637
+ * Whether to accumulate per-item results in the returned `BatchResult.results` array.
638
+ *
639
+ * @default true
640
+ *
641
+ * For large batches (thousands of items or more) holding all results in memory may
642
+ * exhaust the heap. Set `collectResults: false` to skip in-memory accumulation;
643
+ * `BatchResult.results` will be an empty array while `aggregate` totals remain accurate.
644
+ * Use the `onProgress` callback to drain results incrementally instead:
645
+ *
646
+ * ```ts
647
+ * await runner.run(items, {
648
+ * collectResults: false,
649
+ * onProgress: (item) => db.insert(item), // stream results to persistent storage
650
+ * });
651
+ * ```
652
+ */
653
+ collectResults?: boolean;
654
+ }
655
+ /**
656
+ * Bounded-concurrency batch processor for agent inputs.
657
+ *
658
+ * ```ts
659
+ * const runner = new BatchRunner(agent, { concurrency: 8 });
660
+ *
661
+ * const { results, aggregate } = await runner.run(
662
+ * inputs.map((text) => ({ input: text })),
663
+ * {
664
+ * signal: controller.signal,
665
+ * onProgress: (item) => console.log(item.status, item.id),
666
+ * },
667
+ * );
668
+ *
669
+ * console.log(
670
+ * `${aggregate.successCount} ok, ${aggregate.errorCount} err, ` +
671
+ * `${aggregate.totalUsage.inputTokens + aggregate.totalUsage.outputTokens} tokens total`,
672
+ * );
673
+ * ```
674
+ *
675
+ * ### Error isolation
676
+ * A failed item (agent error, network error, aborted sub-run) is captured as
677
+ * `{ status: "error", ... }` — it does NOT abort the batch.
678
+ *
679
+ * ### Provider-native batch (deferred, v1)
680
+ * v1 uses `agent.query()` directly (the `"concurrent"` backend). To integrate
681
+ * Anthropic Message Batches or OpenAI Batch API, implement `BatchBackend` and
682
+ * pass it via `options.backend`.
683
+ */
684
+ declare class BatchRunner {
685
+ private readonly concurrency;
686
+ private readonly backend;
687
+ constructor(agent: Agent, options?: BatchRunnerOptions);
688
+ /**
689
+ * Process a list of inputs with bounded concurrency.
690
+ *
691
+ * @param items - Items to process. Each must have at least `input` set.
692
+ * @param opts - Run-level options (signal, progress callback, collectResults).
693
+ * @returns `BatchResult` containing per-item outcomes and aggregate totals.
694
+ *
695
+ * ### Large-batch tip
696
+ * For very large batches (thousands of items), holding all results in memory may
697
+ * be impractical. Pass `collectResults: false` to skip in-memory accumulation:
698
+ * `BatchResult.results` will be empty while `aggregate` totals remain accurate.
699
+ * Drain results incrementally via `onProgress` instead.
700
+ */
701
+ run(items: BatchItem[], opts?: BatchRunOptions): Promise<BatchResult>;
702
+ }
703
+
704
+ /** Status of an async run. */
705
+ type AsyncRunStatus = "running" | "completed" | "failed" | "aborted";
706
+ /**
707
+ * Registry entry for one async run.
708
+ * `owner` mirrors the principal that started the run — used to enforce that
709
+ * only the owning tenant may poll the run's status.
710
+ */
711
+ interface AsyncRunEntry {
712
+ runId: string;
713
+ sessionId: string;
714
+ agentId: string;
715
+ status: AsyncRunStatus;
716
+ /** Text output when status is "completed". */
717
+ output?: string;
718
+ /** Error message when status is "failed". */
719
+ error?: string;
720
+ /** Principal identifiers used for ownership checks on the status endpoint. */
721
+ owner: {
722
+ userId?: string;
723
+ orgId?: string;
724
+ apiKey?: string;
725
+ };
726
+ createdAt: number;
727
+ settledAt?: number;
728
+ }
729
+ /**
730
+ * In-process registry of async runs.
731
+ * Exported so tests and tooling can inspect entries directly when needed.
732
+ * In production, this is a module-private detail behind the server factory.
733
+ *
734
+ * [M10] Bounded retention: once the registry reaches `maxRuns` entries, the
735
+ * oldest *settled* runs (completed/failed/aborted) are evicted first.
736
+ * In-flight runs (status="running") are never evicted under normal cap pressure.
737
+ * If eviction of settled runs is not enough to make room (all runs are in-flight),
738
+ * the new entry is still accepted — the cap is a best-effort bound, not a hard gate.
739
+ */
740
+ declare class AsyncRunRegistry {
741
+ private readonly runs;
742
+ private readonly maxRuns;
743
+ constructor(options?: {
744
+ maxRuns?: number;
745
+ });
746
+ set(entry: AsyncRunEntry): void;
747
+ get(runId: string): AsyncRunEntry | undefined;
748
+ settle(runId: string, patch: Partial<AsyncRunEntry>): void;
749
+ /** Evict the single oldest settled (non-in-flight) entry to make room. */
750
+ private _evictOldestSettled;
751
+ /** Return all entries (copy of values). Used by graceful drain to check in-flight count. */
752
+ values(): AsyncRunEntry[];
753
+ }
754
+
755
+ /**
756
+ * No-op auth: always returns an empty principal (single-tenant mode).
757
+ *
758
+ * **Warning:** When used with `exposeEvents: true` or in any multi-tenant
759
+ * deployment, all requests are treated as the same anonymous principal.
760
+ * Every client can read every session's events. Only use `NoAuth` for
761
+ * trusted single-tenant environments (local dev, internal services) — do
762
+ * NOT expose a server using `NoAuth` to the public internet.
763
+ */
764
+ declare const NoAuth: AuthPort;
765
+ /**
766
+ * API-key auth: reads `Authorization: Bearer <key>` or `x-api-key` header,
767
+ * looks it up in the provided key→principal map, returns null on mismatch.
768
+ *
769
+ * `runAuth` lowercases all header keys before the adapter sees them, so only
770
+ * the lowercase variants are reachable here. The capitalised fallback branches
771
+ * were dead code and have been removed.
772
+ *
773
+ * **Security note — plain-object key lookup is not constant-time.**
774
+ * Operators with high-security needs (timing-attack resistance) should use a
775
+ * hashed-key comparison (e.g. HMAC) rather than a plain Map lookup. This
776
+ * implementation is sufficient for most deployments but not for environments
777
+ * where side-channel timing attacks are a credible threat model.
778
+ *
779
+ * **Prototype-pollution guard:** `Object.hasOwn` is used before the lookup so
780
+ * that keys such as `"__proto__"`, `"constructor"`, or `"toString"` — which
781
+ * exist on every plain object's prototype — never resolve to a principal.
782
+ */
783
+ declare function ApiKeyAuth(keys: Record<string, AuthPrincipal>): AuthPort;
784
+ type AgentResolver = (agentId: string) => Agent | undefined;
785
+ interface ServerOptions {
786
+ /** Resolve an agent by id. Accepts a plain record or a resolver function. */
787
+ agents: Record<string, Agent> | AgentResolver;
788
+ /**
789
+ * Authentication adapter. Defaults to `NoAuth` (single-tenant).
790
+ *
791
+ * **Warning:** `NoAuth` must not be used in publicly exposed multi-tenant
792
+ * deployments — all clients share a single anonymous principal and can read
793
+ * each other's sessions when `exposeEvents: true` is set. See `NoAuth` for details.
794
+ */
795
+ auth?: AuthPort;
796
+ /** Optional base path prefix, e.g. "/api". Default "". */
797
+ basePath?: string;
798
+ /**
799
+ * Expose the `GET /v1/agents/:agentId/sessions/:sessionId/events` audit
800
+ * endpoint. Defaults to **false** (secure-by-default).
801
+ *
802
+ * The endpoint enforces per-principal session ownership: a principal may only
803
+ * read events for sessions it owns (matching `userId`/`orgId`/`apiKey`). Sessions
804
+ * with no recorded owner (legacy / `NoAuth`) remain readable for back-compat, so in
805
+ * multi-tenant deployments ensure sessions are created through an authenticated
806
+ * principal so they carry an owner.
807
+ */
808
+ exposeEvents?: boolean;
809
+ /**
810
+ * Token-bucket rate limiter (§20.3). When set, every POST /query and /resume
811
+ * request is checked AFTER auth resolves and BEFORE agent work begins.
812
+ * Throttled requests receive 429 + Retry-After. When absent, the check is
813
+ * skipped entirely — the hot path is byte-identical to the pre-rate-limit behaviour.
814
+ */
815
+ rateLimiter?: RateLimiterPort;
816
+ /**
817
+ * Derive the rate-limit bucket key from the authenticated principal and agentId.
818
+ * Defaults to: `principal.apiKey ?? principal.userId ?? principal.orgId ?? "anonymous"`.
819
+ */
820
+ rateLimitKey?: (principal: AuthPrincipal, agentId: string) => string;
821
+ /**
822
+ * Pre-authentication rate limiter applied to all /v1 routes BEFORE auth runs.
823
+ * Defends against unauthenticated hammering, credential brute-force, and
824
+ * enumeration attacks that would otherwise be unthrottled.
825
+ *
826
+ * Keyed by client IP (see `getClientKey` for customisation). Default limit is
827
+ * 60 requests per minute per client key using an internal `InMemoryTokenBucketLimiter`.
828
+ *
829
+ * Set to `null` to **explicitly disable** pre-auth rate limiting (not recommended
830
+ * for public-facing deployments).
831
+ *
832
+ * When absent, an internal limiter with safe defaults (60 req/min) is used.
833
+ */
834
+ preAuthRateLimiter?: RateLimiterPort | null;
835
+ /**
836
+ * Derive the pre-auth rate-limit bucket key from the raw Hono context.
837
+ * Defaults to the remote address from the Node.js socket (`c.env?.incoming?.socket?.remoteAddress`),
838
+ * falling back to the constant `"unknown"` on non-Node runtimes.
839
+ *
840
+ * When `trustProxy: true`, the FIRST entry of the `x-forwarded-for` header is
841
+ * used instead of the socket address.
842
+ */
843
+ getClientKey?: (c: hono.Context) => string;
844
+ /**
845
+ * When `true`, the first entry of the `x-forwarded-for` header is trusted as
846
+ * the real client IP for pre-auth rate-limiting. Defaults to `false`.
847
+ *
848
+ * Only set this to `true` when the server is behind a trusted reverse proxy
849
+ * that overwrites `x-forwarded-for` — otherwise clients can spoof their IP
850
+ * to bypass the pre-auth rate limiter.
851
+ */
852
+ trustProxy?: boolean;
853
+ /**
854
+ * Per-tenant cumulative quota ledger (§20.4). When set, every POST /query and /resume
855
+ * request is checked AFTER auth + rate-limit + body validation + agent resolution and
856
+ * BEFORE agent work begins. Hard-cap exceeded → HTTP 402 Payment Required + JSON error body.
857
+ * Soft-cap crossed → `X-Eidentic-Quota-Warning: soft-limit` header (still streams).
858
+ * After a run completes the terminal usage/cost is recorded into the ledger.
859
+ * When absent, the check is skipped — the hot path is byte-identical to the no-quota behaviour.
860
+ *
861
+ * Quota is checked AFTER body validation and agent resolution so that malformed requests
862
+ * and requests for unknown agents never consume a reservation slot (Fix #4).
863
+ */
864
+ quota?: QuotaPort;
865
+ /**
866
+ * Derive the quota ledger key from the authenticated principal and agentId.
867
+ * Defaults to the same derivation as `rateLimitKey`:
868
+ * `principal.apiKey ?? principal.userId ?? principal.orgId ?? "anonymous"`.
869
+ */
870
+ quotaKey?: (principal: AuthPrincipal, agentId: string) => string;
871
+ /**
872
+ * Maximum number of characters allowed in the `input` field of /query and /runs
873
+ * requests, and in a string `decision` on /resume requests.
874
+ * Defaults to 32,000. Requests exceeding this limit receive a 400 error.
875
+ */
876
+ maxInputChars?: number;
877
+ /**
878
+ * [M10] Maximum number of async-run entries retained in the in-process registry.
879
+ * Once the limit is reached, the oldest *settled* run (completed/failed/aborted)
880
+ * is evicted to make room. In-flight runs are never evicted.
881
+ * Defaults to 1000. Mirror of the workflow registry's bounded pattern.
882
+ */
883
+ maxAsyncRuns?: number;
884
+ /**
885
+ * Webhook delivery configuration for async runs started via `POST /v1/agents/:id/runs`.
886
+ *
887
+ * When provided, a `callbackUrl` field may be included in the runs request body.
888
+ * On run completion (success or error) the server POSTs a JSON payload to that URL:
889
+ *
890
+ * ```json
891
+ * { "runId": "…", "agentId": "…", "status": "completed"|"failed",
892
+ * "output": "…", "error": "…", "usage": { "inputTokens": 0, "outputTokens": 0 } }
893
+ * ```
894
+ *
895
+ * ### Signature verification recipe
896
+ *
897
+ * The request carries two headers:
898
+ * - `X-Eidentic-Timestamp` — Unix timestamp in milliseconds (string).
899
+ * - `X-Eidentic-Signature` — `sha256=<hex HMAC-SHA256>` where the HMAC key is
900
+ * `signingSecret` and the message is `<timestamp>.<rawBody>`.
901
+ *
902
+ * To verify on your server (Node.js example):
903
+ * ```ts
904
+ * import { createHmac } from "node:crypto";
905
+ *
906
+ * function verify(secret: string, timestamp: string, rawBody: string, signature: string) {
907
+ * const expected = "sha256=" + createHmac("sha256", secret)
908
+ * .update(timestamp + "." + rawBody).digest("hex");
909
+ * // Use a constant-time comparison in production:
910
+ * return expected === signature;
911
+ * }
912
+ * ```
913
+ *
914
+ * **Delivery guarantees:** one attempt + up to 2 retries (1s, 2s backoff), 10 s timeout
915
+ * per attempt, redirects never followed. Failures are logged but never surface to the caller.
916
+ *
917
+ * **Security:** `callbackUrl` must be an http/https URL with a public (non-private) host.
918
+ * Set `allowPrivateHosts: true` ONLY in development / test environments.
919
+ *
920
+ * Callbacks are **disabled** unless this option is set. Sending `callbackUrl` in the
921
+ * request body while `webhooks` is not configured returns `400 Bad Request`.
922
+ */
923
+ webhooks?: {
924
+ /** HMAC-SHA256 signing secret. Used to sign every webhook delivery. */
925
+ signingSecret: string;
926
+ /**
927
+ * When `true`, private/loopback/link-local addresses are allowed as callback
928
+ * hosts. Defaults to `false`. Only enable in controlled test environments.
929
+ */
930
+ allowPrivateHosts?: boolean;
931
+ };
932
+ /**
933
+ * CORS options passed through to the `hono/cors` middleware.
934
+ *
935
+ * **Default:** no CORS headers are added (safest default).
936
+ * When provided, the middleware is applied to all routes.
937
+ *
938
+ * **Warning:** `{ origin: "*", credentials: true }` is rejected by browsers.
939
+ * Do not combine a wildcard `origin` with `credentials: true`.
940
+ *
941
+ * @example
942
+ * // Allow a specific origin
943
+ * cors: { origin: "https://app.example.com", credentials: true }
944
+ *
945
+ * @example
946
+ * // Allow any origin (unauthenticated public APIs only)
947
+ * cors: { origin: "*" }
948
+ */
949
+ cors?: Parameters<typeof cors>[0];
950
+ /**
951
+ * External workflow run registry to use instead of the server's own internal one.
952
+ *
953
+ * When provided the server will use this registry for all `GET /v1/workflows` and
954
+ * `GET /v1/workflows/:id` endpoints and for `handle.recordWorkflow()` / `handle.recordWorkflowError()`.
955
+ * This enables durable or cross-instance registries (e.g. backed by a database).
956
+ *
957
+ * When absent, an in-memory bounded registry is created automatically.
958
+ */
959
+ workflowRuns?: WorkflowRunRegistry;
960
+ }
961
+ /**
962
+ * Workflow run summary — the shape returned in the `GET /v1/workflows` list.
963
+ * Full detail (including trace/output/error) is available via `GET /v1/workflows/:id`.
964
+ */
965
+ interface WorkflowRunSummary {
966
+ id: string;
967
+ name: string;
968
+ status: "ok" | "error";
969
+ startedAt: number;
970
+ durationMs: number;
971
+ stepCount: number;
972
+ }
973
+ /**
974
+ * Workflow run detail — the shape returned by `GET /v1/workflows/:id`.
975
+ * Extends `WorkflowRunSummary` with trace, optional output, and optional error.
976
+ */
977
+ interface WorkflowRunDetail extends WorkflowRunSummary {
978
+ trace: StepTrace[];
979
+ output?: unknown;
980
+ error?: string;
981
+ }
982
+ /**
983
+ * Programmatic handle returned by `createServer`.
984
+ *
985
+ * `handle.recordWorkflow(name, result)` ingests a completed workflow run into
986
+ * the server's registry, making it queryable via the workflow endpoints.
987
+ * Returns the generated record `id` so callers can reference it immediately.
988
+ */
989
+ interface ServerHandle {
990
+ /**
991
+ * Ingest a completed workflow run.
992
+ *
993
+ * @param name — human-readable workflow name
994
+ * @param result — `WorkflowResult<O>` returned by `workflow.run()`
995
+ * @param owner — optional principal to attach for per-tenant filtering
996
+ * @param opts — optional record options (e.g. the workflow `version`)
997
+ * @returns the generated record id
998
+ */
999
+ recordWorkflow<O>(name: string, result: WorkflowResult<O>, owner?: WorkflowRunOwner, opts?: RecordOptions): string;
1000
+ /**
1001
+ * Ingest a failed workflow run from a `WorkflowRunError`.
1002
+ * Records the partial step trace and error message so crashed runs appear
1003
+ * in the workflow run registry with `status: "error"`.
1004
+ *
1005
+ * @param err — `WorkflowRunError` caught from `workflow.run()`
1006
+ * @param owner — optional principal to attach for per-tenant filtering
1007
+ * @param opts — optional record options (e.g. the workflow `version`)
1008
+ * @returns the generated record id
1009
+ */
1010
+ recordWorkflowError(err: WorkflowRunError, owner?: WorkflowRunOwner, opts?: RecordOptions): string;
1011
+ }
1012
+
1013
+ /**
1014
+ * Return value of `createServer`.
1015
+ *
1016
+ * Extends Hono so existing `const app = createServer(...)` usage remains valid:
1017
+ * `app.request(...)`, `app.fetch`, etc. all work as before.
1018
+ * `app.handle` is the programmatic ingestion surface (new, non-breaking addition).
1019
+ */
1020
+ type EidenticServer = Hono & {
1021
+ handle: ServerHandle;
1022
+ };
1023
+ /** Payload sent to a callbackUrl on run completion. */
1024
+ interface WebhookPayload {
1025
+ runId: string;
1026
+ agentId: string;
1027
+ status: "completed" | "failed";
1028
+ output?: string;
1029
+ error?: string;
1030
+ usage?: {
1031
+ inputTokens: number;
1032
+ outputTokens: number;
1033
+ };
1034
+ }
1035
+ declare function createServer(opts: ServerOptions): EidenticServer;
1036
+ interface ServeNodeHandle {
1037
+ close(): void;
1038
+ /**
1039
+ * Gracefully drain the server:
1040
+ * 1. Stops accepting new connections.
1041
+ * 2. Returns `503 Service Unavailable` (with `Retry-After: 5`) to any new
1042
+ * `/v1/*` requests that arrive while draining.
1043
+ * 3. Waits until all in-flight async runs settle (polls every 100 ms), or
1044
+ * until `timeoutMs` elapses.
1045
+ * 4. Calls `close()` to shut down the underlying HTTP server.
1046
+ *
1047
+ * @param timeoutMs — maximum time to wait for in-flight runs to settle.
1048
+ * Defaults to 30 000 ms (30 s).
1049
+ */
1050
+ drain(timeoutMs?: number): Promise<void>;
1051
+ }
1052
+ /**
1053
+ * Serve a Hono app on Node.js using `@hono/node-server`.
1054
+ * This is an optional convenience; install `@hono/node-server` separately.
1055
+ * The core `createServer` return value is runtime-agnostic and works on any
1056
+ * Hono-compatible runtime (Cloudflare Workers, Bun, Deno, etc.).
1057
+ *
1058
+ * Returns a `ServeNodeHandle` with:
1059
+ * - `close()` — immediately close the HTTP server.
1060
+ * - `drain(timeoutMs?)` — gracefully drain: stop accepting new connections,
1061
+ * return 503 to new `/v1/*` requests, wait for in-flight async runs to settle,
1062
+ * then close. Defaults to 30 s timeout.
1063
+ */
1064
+ declare function serveNode(app: Hono, opts?: {
1065
+ port?: number;
1066
+ }): Promise<ServeNodeHandle>;
1067
+
1068
+ export { type AgentResolver, ApiKeyAuth, type AsyncRunEntry, AsyncRunRegistry, type AsyncRunStatus, type BatchAggregate, type BatchBackend, type BatchItem, type BatchItemError, type BatchItemResult, type BatchItemSuccess, type BatchResult, type BatchRunOptions, BatchRunner, type BatchRunnerOptions, type ClockPort, type CronSchedule, type EidenticServer, InMemoryQuota, InMemoryTokenBucketLimiter, type IntervalSchedule, NoAuth, type OnProgress, type RunCallback, type RunContext, type Schedule, type ScheduledTask, Scheduler, type SchedulerOptions, type ServeNodeHandle, type ServerHandle, type ServerOptions, type TimerPort, type ToUIMessageStreamOptions, type TokenBucketOptions, type WebhookPayload, type WorkflowRunDetail, type WorkflowRunSummary, createServer, serveNode, toUIMessageStream, toUIMessageStreamResponse };