@dudousxd/nestjs-agent 1.36.1 → 1.36.2
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 +2 -2
- package/dist/a2ui/index.cjs +11 -1
- package/dist/a2ui/index.cjs.map +1 -1
- package/dist/a2ui/index.d.cts +2 -2
- package/dist/a2ui/index.d.ts +2 -2
- package/dist/a2ui/index.js +11 -1
- package/dist/a2ui/index.js.map +1 -1
- package/dist/{chat-queue.service-CzXsKVb1.d.ts → chat-queue.service-YjiKVTKE.d.ts} +2 -2
- package/dist/{chat-queue.service-CF2fcJDx.d.cts → chat-queue.service-fopUN313.d.cts} +2 -2
- package/dist/durable/index.cjs +228 -47
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +6 -18
- package/dist/durable/index.d.ts +6 -18
- package/dist/durable/index.js +228 -47
- package/dist/durable/index.js.map +1 -1
- package/dist/{index-NF93YDF4.d.cts → index-CJdTomOT.d.cts} +6 -4
- package/dist/{index-DpS9P7so.d.ts → index-D1xRrnCq.d.ts} +6 -4
- package/dist/index.cjs +222 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +222 -44
- package/dist/index.js.map +1 -1
- package/dist/sink-redis/index.cjs +164 -18
- package/dist/sink-redis/index.cjs.map +1 -1
- package/dist/sink-redis/index.d.cts +36 -7
- package/dist/sink-redis/index.d.ts +36 -7
- package/dist/sink-redis/index.js +165 -19
- package/dist/sink-redis/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sink-redis/redis-token-stream-sink.ts"],"sourcesContent":["import {\n AgentStreamError,\n type SinkWriter,\n type StreamError,\n type TokenStreamSink,\n} from '@dudousxd/nestjs-agent-core';\nimport type { Redis } from 'ioredis';\n\nexport interface RedisTokenStreamSinkOptions {\n /** Key namespace for the per-run streams. Default `agent:sink:`. */\n keyPrefix?: string;\n /**\n * How long (seconds) a run's stream outlives its last write, so streams self-expire instead of\n * accumulating in Redis — ended runs past their replay window and runs that crashed without\n * ending alike (nothing calls `close()` on its own). A subscriber that reconnects within the window\n * still replays. Default 3600 (1h). `0` keeps the stream until `close()`.\n */\n ttlSeconds?: number;\n /** `XREAD BLOCK` timeout (ms) between follow polls. Default 5000. Purely an internal cadence. */\n blockMs?: number;\n}\n\n// One-character field names keep each stream entry's payload minimal (one XADD per token delta).\nconst CHUNK_FIELD = 'c';\nconst END_FIELD = 'e';\nconst FAIL_FIELD = 'f';\n\n/**\n * A cross-process {@link TokenStreamSink} backed by Redis Streams — the \"data plane\" for durable\n * agent turns whose model call runs on a WORKER pod while the SSE connection is served by an API\n * pod. The turn `XADD`s each delta to `agent:sink:<runId>`; a subscriber `XREAD`s from `0` so it\n * REPLAYS everything buffered so far (a reconnect or a subscriber that attaches after the run\n * started misses nothing), then blocks for live entries until an `end`/`fail` sentinel.\n *\n * The consumer supplies the ioredis client (so the lib stays Redis-driver-agnostic and reuses the\n * app's connection config). Blocking reads monopolise a connection, so each `subscribe` runs on a\n * short-lived `duplicate()` that is disconnected when the subscriber stops.\n */\nexport class RedisTokenStreamSink implements TokenStreamSink {\n private readonly keyPrefix: string;\n private readonly ttlSeconds: number;\n private readonly blockMs: number;\n\n constructor(\n private readonly redis: Redis,\n options: RedisTokenStreamSinkOptions = {},\n ) {\n this.keyPrefix = options.keyPrefix ?? 'agent:sink:';\n this.ttlSeconds = options.ttlSeconds ?? 3600;\n this.blockMs = options.blockMs ?? 5000;\n }\n\n private key(runId: string): string {\n return `${this.keyPrefix}${runId}`;\n }\n\n open(runId: string): SinkWriter {\n const key = this.key(runId);\n return {\n write: async (chunk: Uint8Array) => {\n await this.redis.xadd(key, '*', CHUNK_FIELD, Buffer.from(chunk));\n await this.expire(key);\n },\n end: async () => {\n await this.redis.xadd(key, '*', END_FIELD, '1');\n await this.expire(key);\n },\n fail: async (error: StreamError) => {\n await this.redis.xadd(key, '*', FAIL_FIELD, JSON.stringify(error));\n await this.expire(key);\n },\n };\n }\n\n /** (Re)arm the TTL — sliding from the run's last write. `EXPIRE 0` would delete, so 0 skips. */\n private async expire(key: string): Promise<void> {\n if (this.ttlSeconds > 0) await this.redis.expire(key, this.ttlSeconds);\n }\n\n async *subscribe(runId: string): AsyncIterable<Uint8Array> {\n const key = this.key(runId);\n // Blocking XREAD holds the connection for the whole poll, so follow on a dedicated one.\n const reader = this.redis.duplicate();\n try {\n let lastId = '0';\n while (true) {\n const response = await reader.xreadBuffer('BLOCK', this.blockMs, 'STREAMS', key, lastId);\n for (const entry of readEntries(response)) {\n lastId = entry.id;\n if (entry.field === CHUNK_FIELD && entry.value !== undefined) {\n yield new Uint8Array(entry.value);\n } else if (entry.field === END_FIELD) {\n return;\n } else if (entry.field === FAIL_FIELD && entry.value !== undefined) {\n throw new AgentStreamError(parseStreamError(entry.value.toString()));\n }\n }\n }\n } finally {\n reader.disconnect();\n }\n }\n\n async close(runId: string): Promise<void> {\n await this.redis.del(this.key(runId));\n }\n}\n\ninterface StreamEntry {\n id: string;\n field: string;\n value: Buffer | undefined;\n}\n\n/**\n * The per-stream entry lists of an `XREAD` reply. ioredis 5 (and ioredis 6 under its default\n * `replyMapping: \"legacy\"`) returns `[[key, entries], …]`; ioredis 6 with `replyMapping: \"resp3\"`\n * surfaces the RESP3 map as a plain object `{ [key]: entries }`. Accept both, so the sink works with\n * whatever client the app hands it.\n */\nfunction streamEntryLists(response: unknown): unknown[] {\n if (Array.isArray(response)) {\n return response.map((stream) => (Array.isArray(stream) ? stream[1] : undefined));\n }\n if (typeof response === 'object' && response !== null && !Buffer.isBuffer(response)) {\n return Object.values(response);\n }\n return [];\n}\n\n/**\n * Flatten ioredis's `XREAD` buffer response — `[[key, [[id, [field, value, …]], …]], …] | null`,\n * or the RESP3 map form (see {@link streamEntryLists}) — into the entries we care about. Validated\n * defensively at runtime (it crosses the Redis boundary) rather than trusting the driver's loose\n * typing.\n */\nfunction readEntries(response: unknown): StreamEntry[] {\n const entries: StreamEntry[] = [];\n for (const list of streamEntryLists(response)) {\n if (!Array.isArray(list)) {\n continue;\n }\n for (const item of list) {\n if (!Array.isArray(item) || item.length < 2) {\n continue;\n }\n const [id, fields] = item;\n if (!Buffer.isBuffer(id) || !Array.isArray(fields)) {\n continue;\n }\n const field = Buffer.isBuffer(fields[0]) ? fields[0].toString() : undefined;\n if (field === undefined) {\n continue;\n }\n entries.push({\n id: id.toString(),\n field,\n value: Buffer.isBuffer(fields[1]) ? fields[1] : undefined,\n });\n }\n }\n return entries;\n}\n\n/** Parse a `fail` sentinel's payload back into a {@link StreamError}, tolerating malformed JSON. */\nfunction parseStreamError(raw: string): StreamError {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'code' in parsed &&\n 'message' in parsed &&\n typeof parsed.code === 'string' &&\n typeof parsed.message === 'string'\n ) {\n return { code: parsed.code, message: parsed.message };\n }\n } catch {\n // fall through to the generic terminal below\n }\n return { code: 'stream_failed', message: raw };\n}\n"],"mappings":"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","names":["AgentStreamError","CHUNK_FIELD","END_FIELD","FAIL_FIELD","RedisTokenStreamSink","keyPrefix","ttlSeconds","blockMs","redis","options","key","runId","open","write","chunk","xadd","Buffer","from","expire","end","fail","error","JSON","stringify","subscribe","reader","duplicate","lastId","response","xreadBuffer","entry","readEntries","id","field","value","undefined","Uint8Array","AgentStreamError","parseStreamError","toString","disconnect","close","del","streamEntryLists","Array","isArray","map","stream","isBuffer","Object","values","entries","list","item","length","fields","push","raw","parsed","parse","code","message"]}
|
|
1
|
+
{"version":3,"sources":["../../src/sink-redis/redis-token-stream-sink.ts"],"sourcesContent":["import {\n AgentStreamError,\n type SinkWriter,\n type StreamError,\n type SubscribeOptions,\n type TokenStreamSink,\n encodeStreamEvent,\n} from '@dudousxd/nestjs-agent-core';\nimport type { Redis } from 'ioredis';\n\nexport interface RedisTokenStreamSinkOptions {\n /** Key namespace for the per-run streams. Default `agent:sink:`. */\n keyPrefix?: string;\n /**\n * How long (seconds) a run's stream outlives its last write, so streams self-expire instead of\n * accumulating in Redis — ended runs past their replay window and runs that crashed without\n * ending alike (nothing calls `close()` on its own). A subscriber that reconnects within the window\n * still replays; a subscriber of a run that never writes within the window stops following it.\n * Default 3600 (1h). `0` keeps the stream until `close()`.\n */\n ttlSeconds?: number;\n /** `XREAD BLOCK` timeout (ms) between follow polls. Default 5000. Purely an internal cadence. */\n blockMs?: number;\n}\n\n// One-character field names keep each stream entry's payload minimal.\nconst CHUNK_FIELD = 'c';\nconst END_FIELD = 'e';\nconst FAIL_FIELD = 'f';\n\nconst decoder = new TextDecoder();\n\n/** The text of a chunk that is exactly one `{\"kind\":\"text\",\"text\":…}` line, else `null`. */\nfunction textOf(chunk: Uint8Array): string | null {\n const line = decoder.decode(chunk).trimEnd();\n if (!line.startsWith('{\"kind\":\"text\"')) return null;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n Object.keys(parsed).length === 2 &&\n (parsed as { kind?: unknown }).kind === 'text' &&\n typeof (parsed as { text?: unknown }).text === 'string'\n ) {\n return (parsed as { text: string }).text;\n }\n } catch {\n /* not one event: written as it came */\n }\n return null;\n}\n\n/** One run's write side in this process: the text being gathered, and the sends already queued. */\ninterface RunWrites {\n text: string | null;\n /** A send of the gathered text is queued and has not started yet. */\n flushQueued: boolean;\n /** Settles when every send queued so far has been attempted. Never rejects. */\n tail: Promise<void>;\n pending: number;\n /** A gathered send that failed with nobody awaiting it — raised by the next call. */\n failure: { error: unknown } | undefined;\n}\n\n/**\n * A cross-process {@link TokenStreamSink} backed by Redis Streams — the \"data plane\" for durable\n * agent turns whose model call runs on a WORKER pod while the SSE connection is served by an API\n * pod. The turn `XADD`s its frames to `agent:sink:<runId>`; a subscriber `XREAD`s from `0` so it\n * REPLAYS everything buffered so far (a reconnect or a subscriber that attaches after the run\n * started misses nothing), then blocks for live entries until an `end`/`fail` sentinel — or until\n * the stream is gone: expired or closed after it had entries, or never created within the TTL.\n *\n * Consecutive `text` frames are gathered while the previous send is still on the wire and go out as\n * ONE `text` entry, so a model streaming faster than a Redis round trip costs one round trip per\n * batch rather than per token, and a slow stream still goes out a token at a time. Every subscriber\n * reads the same stored entries in the same order, so frame numbering stays exact across replicas.\n *\n * The consumer supplies the ioredis client (so the lib stays Redis-driver-agnostic and reuses the\n * app's connection config). Blocking reads monopolise a connection, so each `subscribe` runs on a\n * short-lived `duplicate()` that is disconnected when the subscriber stops.\n */\nexport class RedisTokenStreamSink implements TokenStreamSink {\n private readonly keyPrefix: string;\n private readonly ttlSeconds: number;\n private readonly blockMs: number;\n private readonly writes = new Map<string, RunWrites>();\n\n constructor(\n private readonly redis: Redis,\n options: RedisTokenStreamSinkOptions = {},\n ) {\n this.keyPrefix = options.keyPrefix ?? 'agent:sink:';\n this.ttlSeconds = options.ttlSeconds ?? 3600;\n this.blockMs = options.blockMs ?? 5000;\n }\n\n private key(runId: string): string {\n return `${this.keyPrefix}${runId}`;\n }\n\n private runWrites(runId: string): RunWrites {\n let state = this.writes.get(runId);\n if (state === undefined) {\n state = {\n text: null,\n flushQueued: false,\n tail: Promise.resolve(),\n pending: 0,\n failure: undefined,\n };\n this.writes.set(runId, state);\n }\n return state;\n }\n\n /** Forget a run's write state once nothing is gathered, queued or left to report. */\n private release(runId: string, state: RunWrites): void {\n if (\n state.pending === 0 &&\n state.text === null &&\n state.failure === undefined &&\n this.writes.get(runId) === state\n ) {\n this.writes.delete(runId);\n }\n }\n\n /** Queue `work` behind every send already queued for the run, so entries land in call order. */\n private enqueue(runId: string, state: RunWrites, work: () => Promise<void>): Promise<void> {\n state.pending += 1;\n const next = state.tail.then(work);\n state.tail = next\n .then(\n () => undefined,\n () => undefined,\n )\n .then(() => {\n state.pending -= 1;\n this.release(runId, state);\n });\n return next;\n }\n\n /**\n * One round trip: the entries, in order, then the TTL re-armed — sliding from the run's last\n * write. `EXPIRE 0` would delete, so 0 skips it.\n */\n private async send(\n runId: string,\n entries: ReadonlyArray<readonly [string, string | Buffer]>,\n ): Promise<void> {\n const key = this.key(runId);\n const pipeline = this.redis.pipeline();\n for (const [field, value] of entries) {\n pipeline.xadd(key, '*', field, value);\n }\n if (this.ttlSeconds > 0) pipeline.expire(key, this.ttlSeconds);\n const results = (await pipeline.exec()) ?? [];\n for (const [error] of results) {\n if (error) throw error;\n }\n }\n\n /** The gathered text as one entry (nothing if there is none), followed by `entries`. */\n private drain(state: RunWrites): Array<readonly [string, Buffer]> {\n if (state.text === null) return [];\n const text = state.text;\n state.text = null;\n return [[CHUNK_FIELD, Buffer.from(encodeStreamEvent({ kind: 'text', text }))]];\n }\n\n /** Queue a send of the gathered text, unless one is already queued and has not started. */\n private queueText(runId: string, state: RunWrites): void {\n if (state.flushQueued) return;\n state.flushQueued = true;\n this.enqueue(runId, state, async () => {\n state.flushQueued = false;\n const entries = this.drain(state);\n if (entries.length > 0) await this.send(runId, entries);\n }).catch((error: unknown) => {\n state.failure ??= { error };\n });\n }\n\n /** Raise, once, a gathered send that failed with nobody awaiting it. */\n private raiseFailure(state: RunWrites): void {\n const failure = state.failure;\n if (failure !== undefined) {\n state.failure = undefined;\n throw failure.error;\n }\n }\n\n /** Queue the gathered text and then `entry`, as one send; raise a failure nobody saw. */\n private async write(runId: string, entry: readonly [string, string | Buffer]): Promise<void> {\n const state = this.runWrites(runId);\n this.raiseFailure(state);\n await this.enqueue(runId, state, () => this.send(runId, [...this.drain(state), entry]));\n this.raiseFailure(state);\n this.release(runId, state);\n }\n\n /**\n * The run's writer. Writers opened for the same run in one process share what they are gathering,\n * so a delegated run's text (forwarded through a child writer) and its parent's next frame keep\n * the order they were written in.\n */\n open(runId: string): SinkWriter {\n return {\n write: async (chunk: Uint8Array) => {\n const text = textOf(chunk);\n if (text === null) {\n await this.write(runId, [CHUNK_FIELD, Buffer.from(chunk)]);\n return;\n }\n const state = this.runWrites(runId);\n this.raiseFailure(state);\n state.text = (state.text ?? '') + text;\n this.queueText(runId, state);\n },\n flush: async () => {\n const state = this.runWrites(runId);\n this.queueText(runId, state);\n await state.tail;\n this.raiseFailure(state);\n this.release(runId, state);\n },\n end: () => this.write(runId, [END_FIELD, '1']),\n fail: (error: StreamError) => this.write(runId, [FAIL_FIELD, JSON.stringify(error)]),\n };\n }\n\n async *subscribe(runId: string, options: SubscribeOptions = {}): AsyncIterable<Uint8Array> {\n const { signal } = options;\n if (signal?.aborted) return;\n const key = this.key(runId);\n // Blocking XREAD holds the connection for the whole poll, so follow on a dedicated one — and\n // drop it on abort, which is the only way to cut a blocked read short.\n const reader = this.redis.duplicate();\n const onAbort = () => reader.disconnect();\n signal?.addEventListener('abort', onAbort, { once: true });\n // A run writes within its TTL or its stream would have expired, so one silent that long is gone.\n const giveUpAt =\n this.ttlSeconds > 0 ? Date.now() + this.ttlSeconds * 1000 : Number.POSITIVE_INFINITY;\n try {\n let lastId = '0';\n while (true) {\n let response: unknown;\n try {\n response = await reader.xreadBuffer('BLOCK', this.blockMs, 'STREAMS', key, lastId);\n } catch (error) {\n if (signal?.aborted) return;\n throw error;\n }\n const entries = readEntries(response);\n if (entries.length === 0) {\n // A stream that had entries and is now gone expired or was closed without a terminal\n // (its run crashed, or is parked past the TTL): nothing will ever be appended to it.\n if (lastId !== '0' && (await this.redis.exists(key)) === 0) return;\n if (lastId === '0' && Date.now() >= giveUpAt) return;\n continue;\n }\n for (const entry of entries) {\n lastId = entry.id;\n if (entry.field === CHUNK_FIELD && entry.value !== undefined) {\n yield new Uint8Array(entry.value);\n } else if (entry.field === END_FIELD) {\n return;\n } else if (entry.field === FAIL_FIELD && entry.value !== undefined) {\n throw new AgentStreamError(parseStreamError(entry.value.toString()));\n }\n }\n }\n } finally {\n signal?.removeEventListener('abort', onAbort);\n reader.disconnect();\n }\n }\n\n async close(runId: string): Promise<void> {\n await this.redis.del(this.key(runId));\n }\n}\n\ninterface StreamEntry {\n id: string;\n field: string;\n value: Buffer | undefined;\n}\n\n/**\n * The per-stream entry lists of an `XREAD` reply. ioredis 5 (and ioredis 6 under its default\n * `replyMapping: \"legacy\"`) returns `[[key, entries], …]`; ioredis 6 with `replyMapping: \"resp3\"`\n * surfaces the RESP3 map as a plain object `{ [key]: entries }`. Accept both, so the sink works with\n * whatever client the app hands it.\n */\nfunction streamEntryLists(response: unknown): unknown[] {\n if (Array.isArray(response)) {\n return response.map((stream) => (Array.isArray(stream) ? stream[1] : undefined));\n }\n if (typeof response === 'object' && response !== null && !Buffer.isBuffer(response)) {\n return Object.values(response);\n }\n return [];\n}\n\n/**\n * Flatten ioredis's `XREAD` buffer response — `[[key, [[id, [field, value, …]], …]], …] | null`,\n * or the RESP3 map form (see {@link streamEntryLists}) — into the entries we care about. Validated\n * defensively at runtime (it crosses the Redis boundary) rather than trusting the driver's loose\n * typing.\n */\nfunction readEntries(response: unknown): StreamEntry[] {\n const entries: StreamEntry[] = [];\n for (const list of streamEntryLists(response)) {\n if (!Array.isArray(list)) {\n continue;\n }\n for (const item of list) {\n if (!Array.isArray(item) || item.length < 2) {\n continue;\n }\n const [id, fields] = item;\n if (!Buffer.isBuffer(id) || !Array.isArray(fields)) {\n continue;\n }\n const field = Buffer.isBuffer(fields[0]) ? fields[0].toString() : undefined;\n if (field === undefined) {\n continue;\n }\n entries.push({\n id: id.toString(),\n field,\n value: Buffer.isBuffer(fields[1]) ? fields[1] : undefined,\n });\n }\n }\n return entries;\n}\n\n/** Parse a `fail` sentinel's payload back into a {@link StreamError}, tolerating malformed JSON. */\nfunction parseStreamError(raw: string): StreamError {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'code' in parsed &&\n 'message' in parsed &&\n typeof parsed.code === 'string' &&\n typeof parsed.message === 'string'\n ) {\n return { code: parsed.code, message: parsed.message };\n }\n } catch {\n // fall through to the generic terminal below\n }\n return { code: 'stream_failed', message: raw };\n}\n"],"mappings":"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","names":["AgentStreamError","encodeStreamEvent","CHUNK_FIELD","END_FIELD","FAIL_FIELD","decoder","TextDecoder","textOf","chunk","line","decode","trimEnd","startsWith","parsed","JSON","parse","Object","keys","length","kind","text","RedisTokenStreamSink","keyPrefix","ttlSeconds","blockMs","writes","Map","redis","options","key","runId","runWrites","state","get","undefined","flushQueued","tail","Promise","resolve","pending","failure","set","release","delete","enqueue","work","next","then","send","entries","pipeline","field","value","xadd","expire","results","exec","error","drain","Buffer","from","encodeStreamEvent","queueText","catch","raiseFailure","write","entry","open","flush","end","fail","stringify","subscribe","signal","aborted","reader","duplicate","onAbort","disconnect","addEventListener","once","giveUpAt","Date","now","Number","POSITIVE_INFINITY","lastId","response","xreadBuffer","readEntries","exists","id","Uint8Array","AgentStreamError","parseStreamError","toString","removeEventListener","close","del","streamEntryLists","Array","isArray","map","stream","isBuffer","values","list","item","fields","push","raw","code","message"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-agent",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/DavideCarvalho/nestjs-agent.git",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"dependencies": {
|
|
111
111
|
"@standard-schema/spec": "1.1.0",
|
|
112
112
|
"zod": "3.25.76",
|
|
113
|
-
"@dudousxd/nestjs-agent-core": "0.58.
|
|
113
|
+
"@dudousxd/nestjs-agent-core": "0.58.1"
|
|
114
114
|
},
|
|
115
115
|
"peerDependencies": {
|
|
116
116
|
"@dudousxd/nestjs-durable": ">=0.40.0",
|