@nxgt/ai-alxia 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +86 -2
- package/dist/agent-events.d.ts +32 -0
- package/dist/agent-events.d.ts.map +1 -0
- package/dist/ai-request.d.ts +32 -0
- package/dist/ai-request.d.ts.map +1 -0
- package/dist/events.d.ts +134 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +14 -0
- package/dist/user-message.d.ts +16 -0
- package/dist/user-message.d.ts.map +1 -0
- package/dist/wire.d.ts +275 -0
- package/dist/wire.d.ts.map +1 -0
- package/docs/README.md +9 -0
- package/docs/guide/request-and-sse.md +224 -0
- package/docs/roadmap.md +27 -0
- package/docs/troubleshooting.md +68 -0
- package/package.json +66 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Steve Tsala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nxgt/ai-alxia
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[alxia](https://www.npmjs.com/package/@alxia/core) glue for
|
|
4
|
+
[`@nxgt/ai-core`](https://www.npmjs.com/package/@nxgt/ai-core). `aiRequest` is a middleware that gives a handler
|
|
5
|
+
what `runAgent` needs from the request (an abort signal, the request language,
|
|
6
|
+
the app's observers). `agentEvents` streams a run's events as Server-Sent
|
|
7
|
+
Events, written by alxia's own SSE support. Peers: `@alxia/core`,
|
|
8
|
+
`@nxgt/ai-core`, `zod`. The language comes from `@alxia/language` or
|
|
9
|
+
`@alxia/i18n`, which you already use.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add @nxgt/ai-alxia @alxia/core @nxgt/ai-core zod
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The example below uses `fakeModel` from `@nxgt/ai-testing`, a separate install
|
|
16
|
+
(`bun add -d @nxgt/ai-testing`); in production give `runAgent` a real model.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { alxia, responds } from '@alxia/core';
|
|
20
|
+
import { language } from '@alxia/language';
|
|
21
|
+
import { runAgent } from '@nxgt/ai-core';
|
|
22
|
+
import { fakeModel } from '@nxgt/ai-testing';
|
|
23
|
+
import { agentEvents, agentEventStream, aiRequest } from '@nxgt/ai-alxia';
|
|
24
|
+
|
|
25
|
+
// A scripted model for the example; use @nxgt/ai-anthropic or @nxgt/ai-ollama in production.
|
|
26
|
+
const model = fakeModel([{ text: 'Hello!' }]);
|
|
27
|
+
|
|
28
|
+
const app = alxia()
|
|
29
|
+
.use(language({ supported: ['en', 'fr'] as const, fallback: 'en' }))
|
|
30
|
+
// observers: [aiTelemetry()] from @nxgt/ai-telemetry, for every run of the app
|
|
31
|
+
.use(aiRequest({ observers: [] }))
|
|
32
|
+
.post('/chat', responds({ 200: agentEventStream }), ({ ai, reply }) => {
|
|
33
|
+
const messages = [
|
|
34
|
+
{ role: 'user' as const, parts: [{ type: 'text' as const, text: 'Hi' }] },
|
|
35
|
+
];
|
|
36
|
+
// ai = { signal, language, observers }
|
|
37
|
+
const run = runAgent({
|
|
38
|
+
model,
|
|
39
|
+
system: `Answer in ${ai.language}.`,
|
|
40
|
+
messages,
|
|
41
|
+
...ai,
|
|
42
|
+
});
|
|
43
|
+
return reply(200, agentEvents(run, { ...ai, heartbeatMs: 15_000 }));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
app.listen(3000);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
curl -N -X POST 'localhost:3000/chat?lang=fr'
|
|
51
|
+
# event: step-start
|
|
52
|
+
# data: {"type":"step-start","step":1,"runId":"..."}
|
|
53
|
+
#
|
|
54
|
+
# event: text-delta
|
|
55
|
+
# data: {"type":"text-delta","text":"Hello!"}
|
|
56
|
+
# ...
|
|
57
|
+
# event: done
|
|
58
|
+
# data: {"type":"done","reason":"end","usage":{...}}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Imports carry no file extension, so consumers need `"moduleResolution": "bundler"`.
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
| Export | What it is |
|
|
66
|
+
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `aiRequest(options?)` | The middleware: puts `ctx.ai = { signal, language, observers }`; `options.observers` are given to every run |
|
|
68
|
+
| `AiRequest`, `AiRequestOptions` | The type of `ctx.ai` and of the options |
|
|
69
|
+
| `agentEvents(run, options?)` | An async generator of SSE items for `reply(200, …)`; `options.signal` and `options.heartbeatMs` |
|
|
70
|
+
| `AgentEventsOptions`, `AgentStreamItem` | Its options and what it yields: a named event or a comment |
|
|
71
|
+
| `agentEventStream` | The response schema (`eventStream`) of the route: one named event per `AgentEvent` type |
|
|
72
|
+
| `toWireEvent(event)`, `WireEvent` | The event as a client reads it: errors reduced to a code |
|
|
73
|
+
| `userMessageSchema`, `UserMessage` | The zod shape of a user message in a request body: `role: 'user'`, text parts only |
|
|
74
|
+
| `INTERNAL_ERROR` | The code of a failure that is not an `AiError` |
|
|
75
|
+
|
|
76
|
+
## Traps
|
|
77
|
+
|
|
78
|
+
- **Never append parts from the client's request body.** A client could forge a `proposed-action` part, which the memory store records as an action anyone can confirm. Append only the user's text (parse the body with `userMessageSchema`, which accepts text parts only) plus `run.result.messages` ([example](docs/guide/request-and-sse.md#a-request-body-and-memory)).
|
|
79
|
+
- `aiRequest` reads `language` from the context: put `@alxia/language` or `@alxia/i18n` before it, or alxia refuses the chain at compile time.
|
|
80
|
+
- Always write the pair `agentEvents(runAgent({ ...ai, … }), ai)`: spread `ai` into `runAgent` **and** pass it (its `signal`) to `agentEvents`. A run waiting on the model is only stopped on a disconnect through the signal.
|
|
81
|
+
- The wire has no error messages: an `error` event and every failed tool result (alone or inside a `message` event) carry `{ code }` only. `agentEventSchema` of `@nxgt/ai-core` does not parse the wire; validate with `wireEventSchema` (a [client example](docs/guide/request-and-sse.md#what-a-client-reads)).
|
|
82
|
+
- `data:` is JSON on one line: a line break in text is escaped (`\n`), never a second `data:` line. alxia writes the frames, the `content-type`, `cache-control: no-cache` and `x-accel-buffering: no`.
|
|
83
|
+
- alxia already writes `: keep-alive` every 8 s of silence. `heartbeatMs` adds a `: heartbeat` comment of your own period, for a proxy with a shorter timeout.
|
|
84
|
+
- The stream ends after the `done` event, and after an `error` event the run did not follow with `done` (a failure of the stream itself).
|
|
85
|
+
- Headers and status are gone after the first event: a failure after it is an `error` event, never a 500.
|
|
86
|
+
|
|
87
|
+
More: [guide](docs/guide/request-and-sse.md), [troubleshooting](docs/troubleshooting.md), [roadmap](docs/roadmap.md).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type SseComment } from '@alxia/core';
|
|
2
|
+
import type { AgentRun } from '@nxgt/ai-core';
|
|
3
|
+
import type { agentEventStream } from './events.js';
|
|
4
|
+
/** One value `agentEvents` yields: a named event, or a heartbeat comment. */
|
|
5
|
+
export type AgentStreamItem = ReturnType<(typeof agentEventStream)['event']> | SseComment;
|
|
6
|
+
export interface AgentEventsOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Stops the stream and aborts the run when it aborts: pass `ctx.ai.signal`.
|
|
9
|
+
* Without it the run is stopped only when alxia closes the generator, which
|
|
10
|
+
* a generator waiting on the model does not see until it next yields.
|
|
11
|
+
*/
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
/** Writes a `: heartbeat` comment after this many ms without an event. Default: none. */
|
|
14
|
+
heartbeatMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Streams a run's events as Server-Sent Events: reply with it from a route
|
|
18
|
+
* whose response is `agentEventStream`. alxia writes the frames and the
|
|
19
|
+
* headers (`text/event-stream`, `no-cache`, `x-accel-buffering: no`).
|
|
20
|
+
*
|
|
21
|
+
* - The stream ends after the run's `done` event.
|
|
22
|
+
* - On a disconnect (`options.signal`) or when alxia closes the generator,
|
|
23
|
+
* the run is aborted and the stream ends with no further event.
|
|
24
|
+
* - An `error` event holds a code only; a stream that fails ends with an
|
|
25
|
+
* `error` event of code `INTERNAL_ERROR` (or the `AiError`'s code).
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* ({ ai, reply }) => reply(200, agentEvents(runAgent({ model, messages, ...ai }), ai))
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function agentEvents(run: AgentRun, options?: AgentEventsOptions): AsyncGenerator<AgentStreamItem, void, undefined>;
|
|
32
|
+
//# sourceMappingURL=agent-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-events.d.ts","sourceRoot":"","sources":["../src/agent-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAc,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGjD,6EAA6E;AAC7E,MAAM,MAAM,eAAe,GACxB,UAAU,CAAC,CAAC,OAAO,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,GAC9C,UAAU,CAAC;AAEd,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAuB,WAAW,CACjC,GAAG,EAAE,QAAQ,EACb,OAAO,GAAE,kBAAuB,GAC9B,cAAc,CAAC,eAAe,EAAE,IAAI,EAAE,SAAS,CAAC,CAuClD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AgentObserver } from '@nxgt/ai-core';
|
|
2
|
+
/** What `aiRequest` puts on the request context as `ai`. */
|
|
3
|
+
export interface AiRequest {
|
|
4
|
+
/** Aborts when the client disconnects or the request is aborted. */
|
|
5
|
+
readonly signal: AbortSignal;
|
|
6
|
+
/** The request's language, as the middleware before `aiRequest` read it. */
|
|
7
|
+
readonly language: string;
|
|
8
|
+
/** The observers given to `aiRequest`; one array shared by every request. */
|
|
9
|
+
readonly observers: readonly AgentObserver[];
|
|
10
|
+
}
|
|
11
|
+
export interface AiRequestOptions {
|
|
12
|
+
/** Observers every run of the app gets, such as `aiTelemetry()`. */
|
|
13
|
+
observers?: readonly AgentObserver[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The alxia middleware that gives a handler what `runAgent` needs from the
|
|
17
|
+
* request: `ctx.ai` is `{ signal, language, observers }`, so a handler writes
|
|
18
|
+
* `runAgent({ model, messages, ...ai })`.
|
|
19
|
+
*
|
|
20
|
+
* It reads `language` from the context, so it comes after `@alxia/language`
|
|
21
|
+
* or `@alxia/i18n`.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* alxia().use(language({ supported: ['en', 'fr'], fallback: 'en' })).use(aiRequest());
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function aiRequest(options?: AiRequestOptions): import("@alxia/core").Middleware<{
|
|
28
|
+
language: string;
|
|
29
|
+
}, Promise<import("@alxia/core").Next<{
|
|
30
|
+
ai: AiRequest;
|
|
31
|
+
}, import("@alxia/core").Empty>>>;
|
|
32
|
+
//# sourceMappingURL=ai-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-request.d.ts","sourceRoot":"","sources":["../src/ai-request.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACzB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAChC,oEAAoE;IACpE,SAAS,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACrC;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;cAEnB,MAAM;;;kCAM1C"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The response schema of a route that replies with `agentEvents`: one named
|
|
3
|
+
* event per `AgentEvent` type, each with the exact wire shape of
|
|
4
|
+
* `wireEventSchema`. Errors are `{ code }` only.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* app.post('/chat', responds({ 200: agentEventStream }), handler);
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare const agentEventStream: import("@alxia/core").NamedEventStreamSchema<{
|
|
11
|
+
readonly 'step-start': import("zod").ZodObject<{
|
|
12
|
+
type: import("zod").ZodLiteral<"step-start">;
|
|
13
|
+
step: import("zod").ZodNumber;
|
|
14
|
+
runId: import("zod").ZodString;
|
|
15
|
+
}, import("zod/v4/core").$strip>;
|
|
16
|
+
readonly 'text-delta': import("zod").ZodObject<{
|
|
17
|
+
type: import("zod").ZodLiteral<"text-delta">;
|
|
18
|
+
text: import("zod").ZodString;
|
|
19
|
+
start: import("zod").ZodOptional<import("zod").ZodLiteral<true>>;
|
|
20
|
+
}, import("zod/v4/core").$strip>;
|
|
21
|
+
readonly 'thinking-delta': import("zod").ZodObject<{
|
|
22
|
+
type: import("zod").ZodLiteral<"thinking-delta">;
|
|
23
|
+
text: import("zod").ZodString;
|
|
24
|
+
start: import("zod").ZodOptional<import("zod").ZodLiteral<true>>;
|
|
25
|
+
signature: import("zod").ZodOptional<import("zod").ZodString>;
|
|
26
|
+
redacted: import("zod").ZodOptional<import("zod").ZodString>;
|
|
27
|
+
}, import("zod/v4/core").$strip>;
|
|
28
|
+
readonly 'tool-call': import("zod").ZodObject<{
|
|
29
|
+
type: import("zod").ZodLiteral<"tool-call">;
|
|
30
|
+
id: import("zod").ZodString;
|
|
31
|
+
name: import("zod").ZodString;
|
|
32
|
+
input: import("zod").ZodUnknown;
|
|
33
|
+
}, import("zod/v4/core").$strip>;
|
|
34
|
+
readonly 'tool-result': import("zod").ZodObject<{
|
|
35
|
+
type: import("zod").ZodLiteral<"tool-result">;
|
|
36
|
+
result: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
37
|
+
isError: import("zod").ZodLiteral<false>;
|
|
38
|
+
output: import("zod").ZodUnknown;
|
|
39
|
+
type: import("zod").ZodLiteral<"tool-result">;
|
|
40
|
+
callId: import("zod").ZodString;
|
|
41
|
+
name: import("zod").ZodString;
|
|
42
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
43
|
+
isError: import("zod").ZodLiteral<true>;
|
|
44
|
+
error: import("zod").ZodObject<{
|
|
45
|
+
code: import("zod").ZodString;
|
|
46
|
+
}, import("zod/v4/core").$strip>;
|
|
47
|
+
type: import("zod").ZodLiteral<"tool-result">;
|
|
48
|
+
callId: import("zod").ZodString;
|
|
49
|
+
name: import("zod").ZodString;
|
|
50
|
+
}, import("zod/v4/core").$strip>], "isError">;
|
|
51
|
+
}, import("zod/v4/core").$strip>;
|
|
52
|
+
readonly 'proposed-action': import("zod").ZodObject<{
|
|
53
|
+
type: import("zod").ZodLiteral<"proposed-action">;
|
|
54
|
+
action: import("zod").ZodObject<{
|
|
55
|
+
type: import("zod").ZodLiteral<"proposed-action">;
|
|
56
|
+
id: import("zod").ZodString;
|
|
57
|
+
tool: import("zod").ZodString;
|
|
58
|
+
input: import("zod").ZodUnknown;
|
|
59
|
+
summary: import("zod").ZodOptional<import("zod").ZodString>;
|
|
60
|
+
expiresAt: import("zod").ZodNumber;
|
|
61
|
+
}, import("zod/v4/core").$strip>;
|
|
62
|
+
}, import("zod/v4/core").$strip>;
|
|
63
|
+
readonly message: import("zod").ZodObject<{
|
|
64
|
+
type: import("zod").ZodLiteral<"message">;
|
|
65
|
+
message: import("zod").ZodObject<{
|
|
66
|
+
role: import("zod").ZodEnum<{
|
|
67
|
+
tool: "tool";
|
|
68
|
+
user: "user";
|
|
69
|
+
assistant: "assistant";
|
|
70
|
+
}>;
|
|
71
|
+
parts: import("zod").ZodArray<import("zod").ZodUnion<readonly [import("zod").ZodObject<{
|
|
72
|
+
type: import("zod").ZodLiteral<"text">;
|
|
73
|
+
text: import("zod").ZodString;
|
|
74
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
75
|
+
type: import("zod").ZodLiteral<"thinking">;
|
|
76
|
+
text: import("zod").ZodString;
|
|
77
|
+
signature: import("zod").ZodOptional<import("zod").ZodString>;
|
|
78
|
+
redacted: import("zod").ZodOptional<import("zod").ZodString>;
|
|
79
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
80
|
+
type: import("zod").ZodLiteral<"tool-call">;
|
|
81
|
+
id: import("zod").ZodString;
|
|
82
|
+
name: import("zod").ZodString;
|
|
83
|
+
input: import("zod").ZodUnknown;
|
|
84
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
85
|
+
isError: import("zod").ZodLiteral<false>;
|
|
86
|
+
output: import("zod").ZodUnknown;
|
|
87
|
+
type: import("zod").ZodLiteral<"tool-result">;
|
|
88
|
+
callId: import("zod").ZodString;
|
|
89
|
+
name: import("zod").ZodString;
|
|
90
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
91
|
+
isError: import("zod").ZodLiteral<true>;
|
|
92
|
+
error: import("zod").ZodObject<{
|
|
93
|
+
code: import("zod").ZodString;
|
|
94
|
+
}, import("zod/v4/core").$strip>;
|
|
95
|
+
type: import("zod").ZodLiteral<"tool-result">;
|
|
96
|
+
callId: import("zod").ZodString;
|
|
97
|
+
name: import("zod").ZodString;
|
|
98
|
+
}, import("zod/v4/core").$strip>], "isError">, import("zod").ZodObject<{
|
|
99
|
+
type: import("zod").ZodLiteral<"proposed-action">;
|
|
100
|
+
id: import("zod").ZodString;
|
|
101
|
+
tool: import("zod").ZodString;
|
|
102
|
+
input: import("zod").ZodUnknown;
|
|
103
|
+
summary: import("zod").ZodOptional<import("zod").ZodString>;
|
|
104
|
+
expiresAt: import("zod").ZodNumber;
|
|
105
|
+
}, import("zod/v4/core").$strip>]>>;
|
|
106
|
+
}, import("zod/v4/core").$strip>;
|
|
107
|
+
}, import("zod/v4/core").$strip>;
|
|
108
|
+
readonly usage: import("zod").ZodObject<{
|
|
109
|
+
type: import("zod").ZodLiteral<"usage">;
|
|
110
|
+
usage: import("zod").ZodObject<{
|
|
111
|
+
inputTokens: import("zod").ZodNumber;
|
|
112
|
+
outputTokens: import("zod").ZodNumber;
|
|
113
|
+
cacheReadTokens: import("zod").ZodNumber;
|
|
114
|
+
cacheWriteTokens: import("zod").ZodNumber;
|
|
115
|
+
}, import("zod/v4/core").$strip>;
|
|
116
|
+
}, import("zod/v4/core").$strip>;
|
|
117
|
+
readonly error: import("zod").ZodObject<{
|
|
118
|
+
type: import("zod").ZodLiteral<"error">;
|
|
119
|
+
error: import("zod").ZodObject<{
|
|
120
|
+
code: import("zod").ZodString;
|
|
121
|
+
}, import("zod/v4/core").$strip>;
|
|
122
|
+
}, import("zod/v4/core").$strip>;
|
|
123
|
+
readonly done: import("zod").ZodObject<{
|
|
124
|
+
type: import("zod").ZodLiteral<"done">;
|
|
125
|
+
reason: import("zod").ZodString;
|
|
126
|
+
usage: import("zod").ZodObject<{
|
|
127
|
+
inputTokens: import("zod").ZodNumber;
|
|
128
|
+
outputTokens: import("zod").ZodNumber;
|
|
129
|
+
cacheReadTokens: import("zod").ZodNumber;
|
|
130
|
+
cacheWriteTokens: import("zod").ZodNumber;
|
|
131
|
+
}, import("zod/v4/core").$strip>;
|
|
132
|
+
}, import("zod/v4/core").$strip>;
|
|
133
|
+
}>;
|
|
134
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAgC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { type AgentEventsOptions, type AgentStreamItem, agentEvents, } from './agent-events.js';
|
|
2
|
+
export { type AiRequest, type AiRequestOptions, aiRequest } from './ai-request.js';
|
|
3
|
+
export { agentEventStream } from './events.js';
|
|
4
|
+
export { type UserMessage, userMessageSchema } from './user-message.js';
|
|
5
|
+
export { INTERNAL_ERROR, toWireEvent, type WireEvent, type WireToolResult, wireEventSchema, wireToolResultSchema, } from './wire.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,WAAW,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,KAAK,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACN,cAAc,EACd,WAAW,EACX,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,eAAe,EACf,oBAAoB,GACpB,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/agent-events.ts
|
|
2
|
+
import { sseComment } from "@alxia/core";
|
|
3
|
+
|
|
4
|
+
// src/wire.ts
|
|
5
|
+
import {
|
|
6
|
+
AiError
|
|
7
|
+
} from "@nxgt/ai-core";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var INTERNAL_ERROR = "INTERNAL_ERROR";
|
|
10
|
+
function codeOf(error) {
|
|
11
|
+
return error instanceof AiError ? error.code : INTERNAL_ERROR;
|
|
12
|
+
}
|
|
13
|
+
var code = z.string();
|
|
14
|
+
var usageSchema = z.object({
|
|
15
|
+
inputTokens: z.number(),
|
|
16
|
+
outputTokens: z.number(),
|
|
17
|
+
cacheReadTokens: z.number(),
|
|
18
|
+
cacheWriteTokens: z.number()
|
|
19
|
+
});
|
|
20
|
+
var toolResultBase = {
|
|
21
|
+
type: z.literal("tool-result"),
|
|
22
|
+
callId: z.string(),
|
|
23
|
+
name: z.string()
|
|
24
|
+
};
|
|
25
|
+
var wireToolResultSchema = z.discriminatedUnion("isError", [
|
|
26
|
+
z.object({
|
|
27
|
+
...toolResultBase,
|
|
28
|
+
isError: z.literal(false),
|
|
29
|
+
output: z.unknown()
|
|
30
|
+
}),
|
|
31
|
+
z.object({
|
|
32
|
+
...toolResultBase,
|
|
33
|
+
isError: z.literal(true),
|
|
34
|
+
error: z.object({ code })
|
|
35
|
+
})
|
|
36
|
+
]);
|
|
37
|
+
var proposedActionSchema = z.object({
|
|
38
|
+
type: z.literal("proposed-action"),
|
|
39
|
+
id: z.string(),
|
|
40
|
+
tool: z.string(),
|
|
41
|
+
input: z.unknown(),
|
|
42
|
+
summary: z.string().optional(),
|
|
43
|
+
expiresAt: z.number()
|
|
44
|
+
});
|
|
45
|
+
var partSchema = z.union([
|
|
46
|
+
z.object({ type: z.literal("text"), text: z.string() }),
|
|
47
|
+
z.object({
|
|
48
|
+
type: z.literal("thinking"),
|
|
49
|
+
text: z.string(),
|
|
50
|
+
signature: z.string().optional(),
|
|
51
|
+
redacted: z.string().optional()
|
|
52
|
+
}),
|
|
53
|
+
z.object({
|
|
54
|
+
type: z.literal("tool-call"),
|
|
55
|
+
id: z.string(),
|
|
56
|
+
name: z.string(),
|
|
57
|
+
input: z.unknown()
|
|
58
|
+
}),
|
|
59
|
+
wireToolResultSchema,
|
|
60
|
+
proposedActionSchema
|
|
61
|
+
]);
|
|
62
|
+
var messageSchema = z.object({
|
|
63
|
+
role: z.enum(["user", "assistant", "tool"]),
|
|
64
|
+
parts: z.array(partSchema)
|
|
65
|
+
});
|
|
66
|
+
var wireEventSchemas = {
|
|
67
|
+
"step-start": z.object({
|
|
68
|
+
type: z.literal("step-start"),
|
|
69
|
+
step: z.number().int(),
|
|
70
|
+
runId: z.string()
|
|
71
|
+
}),
|
|
72
|
+
"text-delta": z.object({
|
|
73
|
+
type: z.literal("text-delta"),
|
|
74
|
+
text: z.string(),
|
|
75
|
+
start: z.literal(true).optional()
|
|
76
|
+
}),
|
|
77
|
+
"thinking-delta": z.object({
|
|
78
|
+
type: z.literal("thinking-delta"),
|
|
79
|
+
text: z.string(),
|
|
80
|
+
start: z.literal(true).optional(),
|
|
81
|
+
signature: z.string().optional(),
|
|
82
|
+
redacted: z.string().optional()
|
|
83
|
+
}),
|
|
84
|
+
"tool-call": z.object({
|
|
85
|
+
type: z.literal("tool-call"),
|
|
86
|
+
id: z.string(),
|
|
87
|
+
name: z.string(),
|
|
88
|
+
input: z.unknown()
|
|
89
|
+
}),
|
|
90
|
+
"tool-result": z.object({
|
|
91
|
+
type: z.literal("tool-result"),
|
|
92
|
+
result: wireToolResultSchema
|
|
93
|
+
}),
|
|
94
|
+
"proposed-action": z.object({
|
|
95
|
+
type: z.literal("proposed-action"),
|
|
96
|
+
action: proposedActionSchema
|
|
97
|
+
}),
|
|
98
|
+
message: z.object({ type: z.literal("message"), message: messageSchema }),
|
|
99
|
+
usage: z.object({ type: z.literal("usage"), usage: usageSchema }),
|
|
100
|
+
error: z.object({ type: z.literal("error"), error: z.object({ code }) }),
|
|
101
|
+
done: z.object({
|
|
102
|
+
type: z.literal("done"),
|
|
103
|
+
reason: z.string(),
|
|
104
|
+
usage: usageSchema
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
var wireEventSchema = z.discriminatedUnion("type", [
|
|
108
|
+
wireEventSchemas["step-start"],
|
|
109
|
+
wireEventSchemas["text-delta"],
|
|
110
|
+
wireEventSchemas["thinking-delta"],
|
|
111
|
+
wireEventSchemas["tool-call"],
|
|
112
|
+
wireEventSchemas["tool-result"],
|
|
113
|
+
wireEventSchemas["proposed-action"],
|
|
114
|
+
wireEventSchemas.message,
|
|
115
|
+
wireEventSchemas.usage,
|
|
116
|
+
wireEventSchemas.error,
|
|
117
|
+
wireEventSchemas.done
|
|
118
|
+
]);
|
|
119
|
+
function wireResult(result) {
|
|
120
|
+
if (!result.isError)
|
|
121
|
+
return result;
|
|
122
|
+
const { type, callId, name, error } = result;
|
|
123
|
+
return { type, callId, name, isError: true, error: { code: error.code } };
|
|
124
|
+
}
|
|
125
|
+
function wirePart(part) {
|
|
126
|
+
return part.type === "tool-result" ? wireResult(part) : part;
|
|
127
|
+
}
|
|
128
|
+
function wireMessage(message) {
|
|
129
|
+
return { role: message.role, parts: message.parts.map(wirePart) };
|
|
130
|
+
}
|
|
131
|
+
function toWireEvent(event) {
|
|
132
|
+
switch (event.type) {
|
|
133
|
+
case "error":
|
|
134
|
+
return { type: "error", error: { code: event.error.code } };
|
|
135
|
+
case "tool-result":
|
|
136
|
+
return { type: "tool-result", result: wireResult(event.result) };
|
|
137
|
+
case "message":
|
|
138
|
+
return { type: "message", message: wireMessage(event.message) };
|
|
139
|
+
default:
|
|
140
|
+
return event;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/agent-events.ts
|
|
145
|
+
var ABORTED = Symbol("aborted");
|
|
146
|
+
var BEAT = Symbol("beat");
|
|
147
|
+
function aborted(signal) {
|
|
148
|
+
let onAbort = () => {};
|
|
149
|
+
const promise = new Promise((resolve) => {
|
|
150
|
+
onAbort = () => resolve(ABORTED);
|
|
151
|
+
if (signal?.aborted)
|
|
152
|
+
onAbort();
|
|
153
|
+
else
|
|
154
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
155
|
+
});
|
|
156
|
+
return { promise, stop: () => signal?.removeEventListener("abort", onAbort) };
|
|
157
|
+
}
|
|
158
|
+
async function* agentEvents(run, options = {}) {
|
|
159
|
+
const { signal, heartbeatMs } = options;
|
|
160
|
+
const iterator = run[Symbol.asyncIterator]();
|
|
161
|
+
const stop = aborted(signal);
|
|
162
|
+
try {
|
|
163
|
+
let pending = iterator.next();
|
|
164
|
+
while (true) {
|
|
165
|
+
let beat;
|
|
166
|
+
const racers = [pending, stop.promise];
|
|
167
|
+
if (heartbeatMs !== undefined && heartbeatMs > 0) {
|
|
168
|
+
racers.push(new Promise((resolve) => {
|
|
169
|
+
beat = setTimeout(() => resolve(BEAT), heartbeatMs);
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
const won = await Promise.race(racers).finally(() => clearTimeout(beat));
|
|
173
|
+
if (won === ABORTED)
|
|
174
|
+
return;
|
|
175
|
+
if (won === BEAT) {
|
|
176
|
+
yield sseComment("heartbeat");
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const step = won;
|
|
180
|
+
if (step.done)
|
|
181
|
+
return;
|
|
182
|
+
const wire = toWireEvent(step.value);
|
|
183
|
+
yield { event: wire.type, data: wire };
|
|
184
|
+
if (step.value.type === "done")
|
|
185
|
+
return;
|
|
186
|
+
pending = iterator.next();
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (!signal?.aborted) {
|
|
190
|
+
const data = { type: "error", error: { code: codeOf(error) } };
|
|
191
|
+
yield { event: "error", data };
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
stop.stop();
|
|
195
|
+
run.abort();
|
|
196
|
+
run.result.catch(() => {});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// src/ai-request.ts
|
|
200
|
+
import { defineMiddleware } from "@alxia/core";
|
|
201
|
+
var NO_OBSERVERS = [];
|
|
202
|
+
function aiRequest(options = {}) {
|
|
203
|
+
const observers = options.observers ?? NO_OBSERVERS;
|
|
204
|
+
return defineMiddleware()(({ request, language }, next) => {
|
|
205
|
+
const ai = { signal: request.signal, language, observers };
|
|
206
|
+
return next({ ai });
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// src/events.ts
|
|
210
|
+
import { eventStream } from "@alxia/core";
|
|
211
|
+
var agentEventStream = eventStream(wireEventSchemas);
|
|
212
|
+
// src/user-message.ts
|
|
213
|
+
import { z as z2 } from "zod";
|
|
214
|
+
var userMessageSchema = z2.object({
|
|
215
|
+
role: z2.literal("user"),
|
|
216
|
+
parts: z2.array(z2.object({ type: z2.literal("text"), text: z2.string() })).min(1)
|
|
217
|
+
});
|
|
218
|
+
export {
|
|
219
|
+
INTERNAL_ERROR,
|
|
220
|
+
agentEventStream,
|
|
221
|
+
agentEvents,
|
|
222
|
+
aiRequest,
|
|
223
|
+
toWireEvent,
|
|
224
|
+
userMessageSchema,
|
|
225
|
+
wireEventSchema,
|
|
226
|
+
wireToolResultSchema
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
//# debugId=9611CD0C4527036B64756E2164756E21
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/agent-events.ts", "../src/wire.ts", "../src/ai-request.ts", "../src/events.ts", "../src/user-message.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { type SseComment, sseComment } from '@alxia/core';\nimport type { AgentEvent, AgentRun } from '@nxgt/ai-core';\nimport type { agentEventStream } from './events';\nimport { codeOf, toWireEvent } from './wire';\n\n/** One value `agentEvents` yields: a named event, or a heartbeat comment. */\nexport type AgentStreamItem =\n\t| ReturnType<(typeof agentEventStream)['event']>\n\t| SseComment;\n\nexport interface AgentEventsOptions {\n\t/**\n\t * Stops the stream and aborts the run when it aborts: pass `ctx.ai.signal`.\n\t * Without it the run is stopped only when alxia closes the generator, which\n\t * a generator waiting on the model does not see until it next yields.\n\t */\n\tsignal?: AbortSignal;\n\t/** Writes a `: heartbeat` comment after this many ms without an event. Default: none. */\n\theartbeatMs?: number;\n}\n\nconst ABORTED = Symbol('aborted');\nconst BEAT = Symbol('beat');\n\nfunction aborted(signal: AbortSignal | undefined) {\n\tlet onAbort = () => {};\n\tconst promise = new Promise<typeof ABORTED>((resolve) => {\n\t\tonAbort = () => resolve(ABORTED);\n\t\tif (signal?.aborted) onAbort();\n\t\telse signal?.addEventListener('abort', onAbort, { once: true });\n\t});\n\treturn { promise, stop: () => signal?.removeEventListener('abort', onAbort) };\n}\n\n/**\n * Streams a run's events as Server-Sent Events: reply with it from a route\n * whose response is `agentEventStream`. alxia writes the frames and the\n * headers (`text/event-stream`, `no-cache`, `x-accel-buffering: no`).\n *\n * - The stream ends after the run's `done` event.\n * - On a disconnect (`options.signal`) or when alxia closes the generator,\n * the run is aborted and the stream ends with no further event.\n * - An `error` event holds a code only; a stream that fails ends with an\n * `error` event of code `INTERNAL_ERROR` (or the `AiError`'s code).\n *\n * ```ts\n * ({ ai, reply }) => reply(200, agentEvents(runAgent({ model, messages, ...ai }), ai))\n * ```\n */\nexport async function* agentEvents(\n\trun: AgentRun,\n\toptions: AgentEventsOptions = {},\n): AsyncGenerator<AgentStreamItem, void, undefined> {\n\tconst { signal, heartbeatMs } = options;\n\tconst iterator = run[Symbol.asyncIterator]();\n\tconst stop = aborted(signal);\n\ttry {\n\t\tlet pending = iterator.next();\n\t\twhile (true) {\n\t\t\tlet beat: Timer | undefined;\n\t\t\tconst racers: Promise<unknown>[] = [pending, stop.promise];\n\t\t\tif (heartbeatMs !== undefined && heartbeatMs > 0) {\n\t\t\t\tracers.push(\n\t\t\t\t\tnew Promise((resolve) => {\n\t\t\t\t\t\tbeat = setTimeout(() => resolve(BEAT), heartbeatMs);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst won = await Promise.race(racers).finally(() => clearTimeout(beat));\n\t\t\tif (won === ABORTED) return;\n\t\t\tif (won === BEAT) {\n\t\t\t\tyield sseComment('heartbeat');\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst step = won as IteratorResult<AgentEvent>;\n\t\t\tif (step.done) return;\n\t\t\tconst wire = toWireEvent(step.value);\n\t\t\tyield { event: wire.type, data: wire } as AgentStreamItem;\n\t\t\tif (step.value.type === 'done') return;\n\t\t\tpending = iterator.next();\n\t\t}\n\t} catch (error) {\n\t\tif (!signal?.aborted) {\n\t\t\tconst data = { type: 'error' as const, error: { code: codeOf(error) } };\n\t\t\tyield { event: 'error' as const, data };\n\t\t}\n\t} finally {\n\t\tstop.stop();\n\t\trun.abort();\n\t\tvoid run.result.catch(() => {});\n\t}\n}\n",
|
|
6
|
+
"import {\n\ttype AgentEvent,\n\tAiError,\n\ttype Message,\n\ttype Part,\n\ttype ToolResultPart,\n} from '@nxgt/ai-core';\nimport { z } from 'zod';\n\n/** The code a client sees for a failure that has none of its own. */\nexport const INTERNAL_ERROR = 'INTERNAL_ERROR';\n\n/** The code of a thrown value: an `AiError`'s, else `INTERNAL_ERROR`. */\nexport function codeOf(error: unknown): string {\n\treturn error instanceof AiError ? error.code : INTERNAL_ERROR;\n}\n\nconst code = z.string();\n\nconst usageSchema = z.object({\n\tinputTokens: z.number(),\n\toutputTokens: z.number(),\n\tcacheReadTokens: z.number(),\n\tcacheWriteTokens: z.number(),\n});\n\nconst toolResultBase = {\n\ttype: z.literal('tool-result'),\n\tcallId: z.string(),\n\tname: z.string(),\n};\n\n/** A tool result as sent: a failure is its code, with no message and no issues. */\nexport const wireToolResultSchema = z.discriminatedUnion('isError', [\n\tz.object({\n\t\t...toolResultBase,\n\t\tisError: z.literal(false),\n\t\toutput: z.unknown(),\n\t}),\n\tz.object({\n\t\t...toolResultBase,\n\t\tisError: z.literal(true),\n\t\terror: z.object({ code }),\n\t}),\n]);\n\nconst proposedActionSchema = z.object({\n\ttype: z.literal('proposed-action'),\n\tid: z.string(),\n\ttool: z.string(),\n\tinput: z.unknown(),\n\tsummary: z.string().optional(),\n\texpiresAt: z.number(),\n});\n\nconst partSchema = z.union([\n\tz.object({ type: z.literal('text'), text: z.string() }),\n\tz.object({\n\t\ttype: z.literal('thinking'),\n\t\ttext: z.string(),\n\t\tsignature: z.string().optional(),\n\t\tredacted: z.string().optional(),\n\t}),\n\tz.object({\n\t\ttype: z.literal('tool-call'),\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tinput: z.unknown(),\n\t}),\n\twireToolResultSchema,\n\tproposedActionSchema,\n]);\n\nconst messageSchema = z.object({\n\trole: z.enum(['user', 'assistant', 'tool']),\n\tparts: z.array(partSchema),\n});\n\n/** Each wire event by its `type`, which is also its SSE `event:` name. */\nexport const wireEventSchemas = {\n\t'step-start': z.object({\n\t\ttype: z.literal('step-start'),\n\t\tstep: z.number().int(),\n\t\trunId: z.string(),\n\t}),\n\t'text-delta': z.object({\n\t\ttype: z.literal('text-delta'),\n\t\ttext: z.string(),\n\t\tstart: z.literal(true).optional(),\n\t}),\n\t'thinking-delta': z.object({\n\t\ttype: z.literal('thinking-delta'),\n\t\ttext: z.string(),\n\t\tstart: z.literal(true).optional(),\n\t\tsignature: z.string().optional(),\n\t\tredacted: z.string().optional(),\n\t}),\n\t'tool-call': z.object({\n\t\ttype: z.literal('tool-call'),\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tinput: z.unknown(),\n\t}),\n\t'tool-result': z.object({\n\t\ttype: z.literal('tool-result'),\n\t\tresult: wireToolResultSchema,\n\t}),\n\t'proposed-action': z.object({\n\t\ttype: z.literal('proposed-action'),\n\t\taction: proposedActionSchema,\n\t}),\n\tmessage: z.object({ type: z.literal('message'), message: messageSchema }),\n\tusage: z.object({ type: z.literal('usage'), usage: usageSchema }),\n\terror: z.object({ type: z.literal('error'), error: z.object({ code }) }),\n\tdone: z.object({\n\t\ttype: z.literal('done'),\n\t\treason: z.string(),\n\t\tusage: usageSchema,\n\t}),\n} as const;\n\n/**\n * Validates an event as read from the stream (unlike `agentEventSchema` of\n * `@nxgt/ai-core`, which wants the `message` the wire leaves out).\n */\nexport const wireEventSchema = z.discriminatedUnion('type', [\n\twireEventSchemas['step-start'],\n\twireEventSchemas['text-delta'],\n\twireEventSchemas['thinking-delta'],\n\twireEventSchemas['tool-call'],\n\twireEventSchemas['tool-result'],\n\twireEventSchemas['proposed-action'],\n\twireEventSchemas.message,\n\twireEventSchemas.usage,\n\twireEventSchemas.error,\n\twireEventSchemas.done,\n]);\n\n/** An event as the client reads it: errors are codes, never messages. */\nexport type WireEvent = z.infer<typeof wireEventSchema>;\nexport type WireToolResult = z.infer<typeof wireToolResultSchema>;\n\nfunction wireResult(result: ToolResultPart): WireToolResult {\n\tif (!result.isError) return result;\n\tconst { type, callId, name, error } = result;\n\treturn { type, callId, name, isError: true, error: { code: error.code } };\n}\n\nfunction wirePart(part: Part): z.infer<typeof partSchema> {\n\treturn part.type === 'tool-result' ? wireResult(part) : part;\n}\n\nfunction wireMessage(message: Message): z.infer<typeof messageSchema> {\n\treturn { role: message.role, parts: message.parts.map(wirePart) };\n}\n\n/**\n * The event a client may read. Everywhere an event holds an error (an\n * `error` event, a failed `tool-result`, a failed result inside a `message`)\n * only its `code` is kept: messages and issues can hold what a provider, a\n * tool or an exception said, so they stay on the server.\n */\nexport function toWireEvent(event: AgentEvent): WireEvent {\n\tswitch (event.type) {\n\t\tcase 'error':\n\t\t\treturn { type: 'error', error: { code: event.error.code } };\n\t\tcase 'tool-result':\n\t\t\treturn { type: 'tool-result', result: wireResult(event.result) };\n\t\tcase 'message':\n\t\t\treturn { type: 'message', message: wireMessage(event.message) };\n\t\tdefault:\n\t\t\treturn event;\n\t}\n}\n",
|
|
7
|
+
"import { defineMiddleware } from '@alxia/core';\nimport type { AgentObserver } from '@nxgt/ai-core';\n\n/** What `aiRequest` puts on the request context as `ai`. */\nexport interface AiRequest {\n\t/** Aborts when the client disconnects or the request is aborted. */\n\treadonly signal: AbortSignal;\n\t/** The request's language, as the middleware before `aiRequest` read it. */\n\treadonly language: string;\n\t/** The observers given to `aiRequest`; one array shared by every request. */\n\treadonly observers: readonly AgentObserver[];\n}\n\nexport interface AiRequestOptions {\n\t/** Observers every run of the app gets, such as `aiTelemetry()`. */\n\tobservers?: readonly AgentObserver[];\n}\n\nconst NO_OBSERVERS: readonly AgentObserver[] = [];\n\n/**\n * The alxia middleware that gives a handler what `runAgent` needs from the\n * request: `ctx.ai` is `{ signal, language, observers }`, so a handler writes\n * `runAgent({ model, messages, ...ai })`.\n *\n * It reads `language` from the context, so it comes after `@alxia/language`\n * or `@alxia/i18n`.\n *\n * ```ts\n * alxia().use(language({ supported: ['en', 'fr'], fallback: 'en' })).use(aiRequest());\n * ```\n */\nexport function aiRequest(options: AiRequestOptions = {}) {\n\tconst observers = options.observers ?? NO_OBSERVERS;\n\treturn defineMiddleware<{ language: string }>()(\n\t\t({ request, language }, next) => {\n\t\t\tconst ai: AiRequest = { signal: request.signal, language, observers };\n\t\t\treturn next({ ai });\n\t\t},\n\t);\n}\n",
|
|
8
|
+
"import { eventStream } from '@alxia/core';\nimport { wireEventSchemas } from './wire';\n\n/**\n * The response schema of a route that replies with `agentEvents`: one named\n * event per `AgentEvent` type, each with the exact wire shape of\n * `wireEventSchema`. Errors are `{ code }` only.\n *\n * ```ts\n * app.post('/chat', responds({ 200: agentEventStream }), handler);\n * ```\n */\nexport const agentEventStream = eventStream(wireEventSchemas);\n",
|
|
9
|
+
"import { z } from 'zod';\n\n/**\n * A user message as a request body may carry it: the `user` role and text\n * parts only. Any other part (a forged `proposed-action`, a `tool-result`)\n * fails the parse, so nothing but the user's text reaches the memory store.\n */\nexport const userMessageSchema = z.object({\n\trole: z.literal('user'),\n\tparts: z\n\t\t.array(z.object({ type: z.literal('text'), text: z.string() }))\n\t\t.min(1),\n});\n\n/** A user message of a request body, text parts only. */\nexport type UserMessage = z.infer<typeof userMessageSchema>;\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "9611CD0C4527036B64756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* A user message as a request body may carry it: the `user` role and text
|
|
4
|
+
* parts only. Any other part (a forged `proposed-action`, a `tool-result`)
|
|
5
|
+
* fails the parse, so nothing but the user's text reaches the memory store.
|
|
6
|
+
*/
|
|
7
|
+
export declare const userMessageSchema: z.ZodObject<{
|
|
8
|
+
role: z.ZodLiteral<"user">;
|
|
9
|
+
parts: z.ZodArray<z.ZodObject<{
|
|
10
|
+
type: z.ZodLiteral<"text">;
|
|
11
|
+
text: z.ZodString;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
/** A user message of a request body, text parts only. */
|
|
15
|
+
export type UserMessage = z.infer<typeof userMessageSchema>;
|
|
16
|
+
//# sourceMappingURL=user-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-message.d.ts","sourceRoot":"","sources":["../src/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;iBAK5B,CAAC;AAEH,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|