@alquimia-ai/tools 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +527 -23
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.d.ts +1 -1
- package/dist/hooks/index.d.mts +3 -3
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +62 -6
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +62 -6
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/next/index.js +4 -1
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +4 -1
- package/dist/next/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +3 -3
- package/dist/providers/index.d.ts +3 -3
- package/dist/{providers-DiR9cgyo.d.mts → providers-B4IOiGfr.d.mts} +1 -1
- package/dist/{providers-b5_yMyMo.d.ts → providers-mx21gNgZ.d.ts} +1 -1
- package/dist/proxy.js +4 -1
- package/dist/proxy.js.map +1 -1
- package/dist/proxy.mjs +4 -1
- package/dist/proxy.mjs.map +1 -1
- package/dist/sdk/index.d.mts +37 -4
- package/dist/sdk/index.d.ts +37 -4
- package/dist/sdk/index.js +43 -6
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/index.mjs +43 -6
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/services/index.d.mts +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/{type-DqX3-9QS.d.mts → type-61gaZga-.d.mts} +45 -1
- package/dist/{type-xksRnBUW.d.ts → type-D-JaSMkV.d.ts} +45 -1
- package/dist/types/index.d.mts +6 -4
- package/dist/types/index.d.ts +6 -4
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/{types-C-6fgK1_.d.mts → types-lOliroPw.d.mts} +1 -1
- package/dist/{types-C-6fgK1_.d.ts → types-lOliroPw.d.ts} +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/worklog/index.d.mts +2 -2
- package/dist/worklog/index.d.ts +2 -2
- package/dist/worklog/index.js +19 -0
- package/dist/worklog/index.js.map +1 -1
- package/dist/worklog/index.mjs +19 -0
- package/dist/worklog/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,548 @@
|
|
|
1
1
|
# @alquimia-ai/tools
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The runtime half of the Alquimia frontend SDK. It turns the Alquimia Runtime's HTTP + SSE API into React state: a chat hook, a builder-style SDK class, URL adapters, a server-side proxy, a generative-UI protocol, and a normalized execution trace.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It ships no UI. Pair it with [`@alquimia-ai/ui`](../ui) for the component library, or wire its return values to your own components.
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
7
|
+
- **Runtime compatibility:** Alquimia Runtime **v0.5.2** / alquimia-core **v0.5.3**
|
|
8
|
+
- **Peer deps:** React ≥ 18. `next` ≥ 14 only if you use the Next.js helpers.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of contents
|
|
13
|
+
|
|
14
|
+
- [Install](#install)
|
|
15
|
+
- [The mental model](#the-mental-model)
|
|
16
|
+
- [Quick start](#quick-start)
|
|
17
|
+
- [`useAlquimia`](#usealquimia)
|
|
18
|
+
- [`AlquimiaSDK`](#alquimiasdk)
|
|
19
|
+
- [Anatomy of a turn](#anatomy-of-a-turn)
|
|
20
|
+
- [Version-pinned agents](#version-pinned-agents)
|
|
21
|
+
- [Multimodal and audio input](#multimodal-and-audio-input)
|
|
22
|
+
- [Attachments and blobs](#attachments-and-blobs)
|
|
23
|
+
- [Client tools and GenUI](#client-tools-and-genui)
|
|
24
|
+
- [Worklog](#worklog)
|
|
25
|
+
- [Providers](#providers)
|
|
26
|
+
- [Server proxy](#server-proxy)
|
|
27
|
+
- [Distributed tracing](#distributed-tracing)
|
|
28
|
+
- [Types](#types)
|
|
29
|
+
- [Export map](#export-map)
|
|
10
30
|
- [Development](#development)
|
|
11
|
-
- [Contributing](#contributing)
|
|
12
|
-
- [License](#license)
|
|
13
31
|
|
|
14
|
-
|
|
32
|
+
---
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
## Install
|
|
17
35
|
|
|
18
36
|
```sh
|
|
19
|
-
|
|
37
|
+
npm install @alquimia-ai/tools
|
|
20
38
|
```
|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
Environment variables live on the **server**, never in the browser:
|
|
23
41
|
|
|
24
|
-
```
|
|
25
|
-
|
|
42
|
+
```bash
|
|
43
|
+
ASSISTANT_BASEURL=https://your-runtime.example.com
|
|
44
|
+
ALQUIMIA_ASSISTANT_API_KEY=your-secret-key
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## The mental model
|
|
50
|
+
|
|
51
|
+
Two pieces decide where every request goes and who holds the credentials.
|
|
52
|
+
|
|
53
|
+
**The adapter** resolves URLs on the client. It never adds an `Authorization` header.
|
|
54
|
+
|
|
55
|
+
**The proxy** runs on your server, injects the API key, and forwards to the runtime.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
browser your server alquimia runtime
|
|
59
|
+
─────── ─────────── ────────────────
|
|
60
|
+
useAlquimia / AlquimiaSDK
|
|
61
|
+
└─ adapter.resolveInferUrl() ──→ handleInfer ────────────────→ POST /event/infer/{assistantId}
|
|
62
|
+
└─ adapter.resolveStreamUrl() ─→ handleStream ───────────────→ GET /event/stream/{taskId}
|
|
63
|
+
└─ adapter.resolveBlobUploadUrl() → handleBlobUpload ────────→ POST /context/blob/upload
|
|
64
|
+
└─ adapter.resolveToolCompletionUrl() → handleToolCompletion → POST /event/tool-completion
|
|
65
|
+
↑
|
|
66
|
+
Authorization: Bearer <apiKey>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The proxy exists for two reasons. The obvious one is that the API key must not reach the browser. The load-bearing one is that the stream leg uses `EventSource`, and **`EventSource` cannot set request headers** — so `getHeaders()` is honoured on infer, blob upload and tool-completion (all axios POSTs) and silently ignored on the stream. If your runtime or its gateway requires auth on every request, a server hop on the stream is not optional.
|
|
70
|
+
|
|
71
|
+
Three supported topologies:
|
|
72
|
+
|
|
73
|
+
| Topology | Adapter | Server side |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Next.js App Router | `createNextJsAdapter()` | `createNextJsRouteHandlers()` |
|
|
76
|
+
| SPA + your own server (Express/Hono/Fastify/Bun/Deno) | `createFetchAdapter({ baseUrl })` | `createAlquimiaProxyHandler()` |
|
|
77
|
+
| SPA, no server | custom `AlquimiaAdapter` | a ~30-line proxy for the stream leg only |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quick start
|
|
82
|
+
|
|
83
|
+
### Next.js App Router
|
|
84
|
+
|
|
85
|
+
Four route files, five lines each:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// app/api/chat/[...path]/route.ts
|
|
89
|
+
import { createNextJsRouteHandlers } from '@alquimia-ai/tools/next';
|
|
90
|
+
|
|
91
|
+
const handlers = createNextJsRouteHandlers({
|
|
92
|
+
assistantBaseUrl: process.env.ASSISTANT_BASEURL!,
|
|
93
|
+
apiKey: process.env.ALQUIMIA_ASSISTANT_API_KEY!,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const POST = handlers.handleInfer;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// app/api/stream/[...path]/route.ts → export const GET = handlers.handleStream;
|
|
101
|
+
// app/api/blob/upload/route.ts → export const POST = handlers.handleBlobUpload;
|
|
102
|
+
// app/api/tool-completion/route.ts → export const POST = handlers.handleToolCompletion;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The last two take no `[...path]` segment: identity travels in headers, and the pending tool call is correlated by `control_id` in the body.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
'use client';
|
|
109
|
+
import { useAlquimia } from '@alquimia-ai/tools/hooks';
|
|
110
|
+
import { createNextJsAdapter } from '@alquimia-ai/tools/adapters/next';
|
|
111
|
+
|
|
112
|
+
const adapter = createNextJsAdapter();
|
|
113
|
+
|
|
114
|
+
export function Chat({ assistantId }: { assistantId: string }) {
|
|
115
|
+
const alquimia = useAlquimia({ assistantId, adapter });
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<form onSubmit={(e) => alquimia.handleSubmit(e)}>
|
|
119
|
+
{alquimia.messages.map((m) => <p key={m.id}>{m.content}</p>)}
|
|
120
|
+
<input value={alquimia.input} onChange={alquimia.handleInputChange} />
|
|
121
|
+
</form>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Any server
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { createAlquimiaProxyHandler } from '@alquimia-ai/tools/proxy';
|
|
130
|
+
|
|
131
|
+
const handler = createAlquimiaProxyHandler({
|
|
132
|
+
assistantBaseUrl: process.env.ASSISTANT_BASEURL!,
|
|
133
|
+
apiKey: process.env.ALQUIMIA_ASSISTANT_API_KEY!,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Hono
|
|
137
|
+
app.post('/chat/:path{.+}', (c) => handler.handleInfer(c.req.raw, c.req.param('path')));
|
|
138
|
+
app.get('/stream/:path{.+}', (c) => handler.handleStream(c.req.raw, c.req.param('path')));
|
|
139
|
+
app.post('/blob/upload', (c) => handler.handleBlobUpload(c.req.raw));
|
|
140
|
+
app.post('/tool-completion', (c) => handler.handleToolCompletion(c.req.raw));
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The handlers speak the Web Fetch API (`Request` → `Response`), so any modern runtime works.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { createFetchAdapter } from '@alquimia-ai/tools/adapters/fetch';
|
|
147
|
+
const adapter = createFetchAdapter({ baseUrl: 'https://my-api.example.com' });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Custom adapter
|
|
151
|
+
|
|
152
|
+
Implement the interface directly to split legs across hosts:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import type { AlquimiaAdapter } from '@alquimia-ai/tools/adapters';
|
|
156
|
+
|
|
157
|
+
const adapter: AlquimiaAdapter = {
|
|
158
|
+
resolveInferUrl: (assistantId) => `${BACKEND}/event/infer/${assistantId}`,
|
|
159
|
+
resolveStreamUrl: (taskId) => `${STREAM_PROXY}/event/stream/${taskId}`,
|
|
160
|
+
resolveBlobUploadUrl: () => `${BACKEND}/context/blob/upload`,
|
|
161
|
+
resolveToolCompletionUrl: () => `${BACKEND}/event/tool-completion`,
|
|
162
|
+
getHeaders: () => ({ Authorization: `Bearer ${API_KEY}` }),
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`busMode: true` on either built-in adapter appends `?bus_mode=true` to the stream URL, which makes the runtime emit granular worklog frames instead of only assistant deltas.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## `useAlquimia`
|
|
171
|
+
|
|
172
|
+
One hook owns the whole chat: message list, input, streaming flags, attachments, audio state, worklog, and the GenUI loop.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const alquimia = useAlquimia({
|
|
176
|
+
assistantId: 'support-agent', // required
|
|
177
|
+
adapter, // required
|
|
178
|
+
providers: { // all optional
|
|
179
|
+
whisper, stableDiffusion, characterization, ratings, logger,
|
|
180
|
+
},
|
|
181
|
+
options: {
|
|
182
|
+
enforceCharacterization: false,
|
|
183
|
+
userId: user?.email,
|
|
184
|
+
extraInstructions: { tone: 'formal' }, // injected as runtime prompt clauses
|
|
185
|
+
worklog: true, // build the execution trace
|
|
186
|
+
},
|
|
187
|
+
genui: {
|
|
188
|
+
catalog: coreCatalog, // default: the built-in ~40-component catalog
|
|
189
|
+
allow: 'all', // or an explicit component allowlist
|
|
190
|
+
source: 'agent', // 'agent' | 'client'
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### What it returns
|
|
196
|
+
|
|
197
|
+
**Messages and input**
|
|
198
|
+
|
|
199
|
+
| Key | Type | Notes |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `messages` | `AlquimiaMessage[]` | full conversation |
|
|
202
|
+
| `cleanMessages` | `AlquimiaMessage[]` | without system/internal entries |
|
|
203
|
+
| `input` | `string` | controlled input value |
|
|
204
|
+
| `handleInputChange` | `(e) => void` | textarea change handler |
|
|
205
|
+
| `handleReplaceInput` | `(text: string) => void` | used by speech-to-text |
|
|
206
|
+
| `populateMessages` | `(messages) => void` | hydrate from history |
|
|
207
|
+
| `createMessageId` | `() => string` | id helper |
|
|
208
|
+
|
|
209
|
+
**Sending**
|
|
210
|
+
|
|
211
|
+
| Key | Notes |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `handleSubmit(event, traceParentId?, sessionId?, additionalInfo?)` | the normal path |
|
|
214
|
+
| `sendMessage` | lower-level send |
|
|
215
|
+
| `handleSystemMessage(text, opts)` | inject a system turn |
|
|
216
|
+
| `handleLoadingCancel()` | abort an in-flight turn and close the stream |
|
|
217
|
+
| `lastRequest` / `setLastRequest` | for retry UX |
|
|
218
|
+
|
|
219
|
+
**Status flags**
|
|
220
|
+
|
|
221
|
+
`isLoading`, `isMessageLoading`, `isMessageStreaming`, `isStreamingLoading`, `streamingMessageId`, `hasThinkings`, `isUploadingAttachments`.
|
|
222
|
+
|
|
223
|
+
**Attachments**
|
|
224
|
+
|
|
225
|
+
`attachments`, `addAttachment`, `addAttachments`, `removeAttachment`, `clearAttachments`.
|
|
226
|
+
|
|
227
|
+
**Audio**
|
|
228
|
+
|
|
229
|
+
`isAudioRecording`, `setIsAudioRecording`.
|
|
230
|
+
|
|
231
|
+
**Tools and GenUI**
|
|
232
|
+
|
|
233
|
+
`activeTool`, `setActiveTool`, `pendingClientTool`, `completeToolExecution`, `genui`.
|
|
234
|
+
|
|
235
|
+
**Trace**
|
|
236
|
+
|
|
237
|
+
`worklog` — a `WorklogState`, or `undefined` unless `options.worklog` is on.
|
|
238
|
+
|
|
239
|
+
**Escape hatch**
|
|
240
|
+
|
|
241
|
+
`sdk` — the underlying `AlquimiaSDK` instance.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## `AlquimiaSDK`
|
|
246
|
+
|
|
247
|
+
A builder. Every `with*` returns `this`, so configuration chains; the getters read back what the runtime resolved.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { AlquimiaSDK } from '@alquimia-ai/tools/sdk';
|
|
251
|
+
|
|
252
|
+
const sdk = new AlquimiaSDK('support-agent', adapter)
|
|
253
|
+
.withConversationId(conversationId)
|
|
254
|
+
.withUserId('user-42')
|
|
255
|
+
.withExtraInstructions({ tone: 'formal' });
|
|
256
|
+
|
|
257
|
+
await sdk.sendMessage('hello');
|
|
258
|
+
const streamUrl = sdk.getUrlStream();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Configuration
|
|
262
|
+
|
|
263
|
+
| Method | Purpose |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `withConversationId(id)` | **required before `sendMessage`** — the runtime `session_id` |
|
|
266
|
+
| `withUserId(id)` | `user_id` on the payload and the `user-id` header |
|
|
267
|
+
| `withExtraInstructions(record)` | `extra_instructions` — prompt clauses merged by the runtime |
|
|
268
|
+
| `withAssistantConfig(config)` | full `AssistantConfig` override for the turn |
|
|
269
|
+
| `withTools(schemas)` | client-side tool schemas — sent as a `native` evaluation strategy |
|
|
270
|
+
| `withAttachments(payloads)` | attachment metadata for the next turn |
|
|
271
|
+
| `withVersionTag(tag)` | pin a registry version (see below) |
|
|
272
|
+
| `withWhisperProvider` / `withStableDiffusionProvider` / `withAnalyzeCharacterizationProvider` / `withRatingsProvider` / `withLoggerProvider` | inject optional providers |
|
|
273
|
+
|
|
274
|
+
### Turn
|
|
275
|
+
|
|
276
|
+
| Method | Purpose |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `sendMessage(query?, options?)` | POST infer. `options` is `{ traceParent?, inputAudio?, outputAudio? }`, or a bare traceparent string for backwards compatibility |
|
|
279
|
+
| `getUrlStream()` | SSE URL for the task, with the traceparent appended as a query param |
|
|
280
|
+
| `getStreamId()` / `getTaskId()` | the runtime `taskid` |
|
|
281
|
+
| `getVersionTag()` | the registry version the runtime resolved |
|
|
282
|
+
| `submitToolResult(response)` | POST tool-completion for a pending client tool |
|
|
283
|
+
|
|
284
|
+
### Side channels
|
|
285
|
+
|
|
286
|
+
| Method | Requires |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `uploadAttachment(file)` → `RuntimeBlob` | — |
|
|
289
|
+
| `getAttachmentResponses()` | — |
|
|
290
|
+
| `textToSpeech(text)` / `speechToText(audio)` | a whisper provider |
|
|
291
|
+
| `generateImage(query)` | a stable-diffusion provider |
|
|
292
|
+
| `analyzeCharacterization(text)` | a characterization provider |
|
|
293
|
+
| `rate(data)` | a ratings provider |
|
|
294
|
+
| `logInfo` / `logError` | a logger provider |
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Anatomy of a turn
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
1. POST /event/infer/{assistantId}
|
|
302
|
+
body { query, session_id, user_id, ... }
|
|
303
|
+
headers assistant-id, session-id, user-id, task-id, x-trace-parent
|
|
304
|
+
→ CommonAttributes { taskid, versiontag, attachments }
|
|
305
|
+
|
|
306
|
+
2. GET /event/stream/{taskid} (EventSource)
|
|
307
|
+
← assistant deltas, thinkings, tool events
|
|
308
|
+
← granular worklog frames when bus_mode=true
|
|
309
|
+
|
|
310
|
+
3. POST /event/tool-completion (only if the agent called a client tool)
|
|
311
|
+
body ToolExecutionResponse { control_id, tool_name, status, result }
|
|
312
|
+
headers taskid, sessionid, userid, assistantid, versiontag
|
|
313
|
+
→ the run resumes on the same stream
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Step 3 is what makes client tools and GenUI work: the runtime parks the task, the browser answers, the run continues. The correlation headers are not decoration — the runtime looks the parked task up by them, and rejects a completion whose `control_id` it is not awaiting with a `409`.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Version-pinned agents
|
|
321
|
+
|
|
322
|
+
The runtime keeps cached registry versions of an agentspace. Two ways to target one:
|
|
323
|
+
|
|
324
|
+
**A versioned assistant ref**, which works on every runtime version:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const sdk = new AlquimiaSDK('prod/support-agent:v1.2', adapter);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The runtime parses `<agentspace>/<assistant>[:<tag>]` out of the path itself.
|
|
331
|
+
|
|
332
|
+
**`withVersionTag()`**, which sends `?version_tag=` on infer and needs **runtime ≥ 0.5.2** — older runtimes ignore the parameter:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
sdk.withVersionTag('v1.2');
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Either way, the runtime answers with the version it actually resolved in `CommonAttributes.versiontag`. The SDK stores it and echoes it as the `versiontag` header on tool-completion, so both halves of a turn land on the same snapshot even when "latest" moves underneath a live conversation. Read it with `getVersionTag()`.
|
|
339
|
+
|
|
340
|
+
The tag is a header **only** on tool-completion and tool-approval, where the runtime binds it through a `CommonAttributes` header model. On infer it is a query parameter. Sending it as a header there does nothing.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Multimodal and audio input
|
|
345
|
+
|
|
346
|
+
`query` accepts the runtime's full `Content` shape — plain text, or an OpenAI-style list of content parts:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
await sdk.sendMessage([
|
|
350
|
+
{ type: 'text', text: 'what is in this picture?' },
|
|
351
|
+
{ type: 'image_url', image_url: { url: imageUrl, detail: 'low' } },
|
|
352
|
+
]);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Audio input is a two-step flow, because `input_audio` is a **blob reference**, not raw bytes:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
const blob = await sdk.uploadAttachment(audioFile); // POST /context/blob/upload → RuntimeBlob
|
|
359
|
+
await sdk.sendMessage(undefined, { inputAudio: blob }); // audio-only turn
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`query` is optional when audio is supplied, and both can be sent together. A turn with neither throws before any request is made.
|
|
363
|
+
|
|
364
|
+
Audio **output** is on by default server-side for an agent with a TTS adapter. To force a text-only reply:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
await sdk.sendMessage('hello', { outputAudio: false });
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The field is only sent when you set it, so leaving it alone preserves the runtime's own default.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Attachments and blobs
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
const blob = await sdk.uploadAttachment(file);
|
|
378
|
+
// { blob_id, filename, content_size, content_type, checksum, ... }
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Identity travels in kebab-case headers (`assistant-id`, `session-id`, `user-id`, `task-id`), which the proxy forwards; the path carries nothing. The infer response echoes the blob ids the runtime associated with the turn — read them with `getAttachmentResponses()`.
|
|
382
|
+
|
|
383
|
+
Through the hook, `addAttachments(files)` queues them and they upload automatically after the turn's infer call resolves.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Client tools and GenUI
|
|
388
|
+
|
|
389
|
+
A client tool is a tool the **browser** executes. The agent calls it, the runtime parks the task, the SDK surfaces it as `pendingClientTool`, and `completeToolExecution` posts the result back.
|
|
390
|
+
|
|
391
|
+
GenUI is that mechanism with one specific tool, `render_ui`, whose arguments are a declarative component tree. The agent never emits markup — only component names and props from a catalog you authorize.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
const alquimia = useAlquimia({
|
|
395
|
+
assistantId, adapter,
|
|
396
|
+
genui: { allow: ['Stack', 'TextField', 'Button'] },
|
|
397
|
+
});
|
|
26
398
|
```
|
|
27
399
|
|
|
28
|
-
|
|
400
|
+
The `/genui` module holds the protocol, independent of any renderer:
|
|
401
|
+
|
|
402
|
+
| Export | Purpose |
|
|
403
|
+
|---|---|
|
|
404
|
+
| `coreCatalog`, `CORE_CATALOG_ID` | the default ~40-component catalog with Zod prop schemas |
|
|
405
|
+
| `coreCatalogJson` | the published A2UI catalog artifact, as a value |
|
|
406
|
+
| `buildRenderUiSchema(manifest, allow)` | the JSON-schema the agent sees |
|
|
407
|
+
| `buildGenuiClause(manifest, allow)` | the prompt clause describing the catalog |
|
|
408
|
+
| `validateSurface(input, manifest)` | validates an agent-authored tree before render |
|
|
409
|
+
| `validateProps(node, def)` | per-component prop validation |
|
|
410
|
+
| `resolveDynamic`, `getPointer`, `setPointer`, `isPathRef` | data-binding against the surface's data model |
|
|
411
|
+
| `classifyAction`, `assertSafeActionName` | agent-bound vs local action handling |
|
|
412
|
+
| `buildSurfaceResult`, `buildDismissResult` | the payloads posted back on submit or dismiss |
|
|
413
|
+
| `emitA2uiCatalog` | export a manifest as an A2UI catalog artifact |
|
|
29
414
|
|
|
30
|
-
|
|
415
|
+
Components come from `@alquimia-ai/ui/components/genui` (`A2uiRenderer`, `coreUiRegistry`, `AssistantChat`), or from your own registry — pass your catalog to the hook and your components to the renderer, and the same agent composes your design system.
|
|
31
416
|
|
|
32
|
-
|
|
417
|
+
---
|
|
33
418
|
|
|
34
|
-
|
|
35
|
-
- **Hooks**: Main hook that applies the SDK and most important chat behaviour.
|
|
36
|
-
- **SDK Class and providers**: Main SDK class with its providers.
|
|
37
|
-
- **Worklog**: Runtime worklog reducer, SSE frame normalization, and audit API types.
|
|
38
|
-
- **Runtime event types**: TypeScript schemas for the `/event/*` payloads.
|
|
419
|
+
## Worklog
|
|
39
420
|
|
|
421
|
+
The worklog is the agent's execution trace, normalized into a tree you can render as "show your work".
|
|
40
422
|
|
|
41
|
-
|
|
423
|
+
```ts
|
|
424
|
+
const alquimia = useAlquimia({ assistantId, adapter, options: { worklog: true } });
|
|
425
|
+
alquimia.worklog?.nodes; // WorklogNode[]
|
|
426
|
+
alquimia.worklog?.status; // 'idle' | 'running' | 'success' | 'error'
|
|
427
|
+
alquimia.worklog?.answer;
|
|
428
|
+
```
|
|
42
429
|
|
|
43
|
-
|
|
430
|
+
Each runtime event class maps to an interpreter that says which bucket it belongs to (`NodeKind`) and whether it opens or closes a node. Commands and responses are merged by `control_id`.
|
|
431
|
+
|
|
432
|
+
| Bucket | Event classes |
|
|
433
|
+
|---|---|
|
|
434
|
+
| `answer` | `AssistantInference(+Response)` — the run envelope |
|
|
435
|
+
| `safeguard` | `ShieldInference(+Response)`, `ShieldBlockedResponse` |
|
|
436
|
+
| `reasoning` | `ResponseInference(+Response)` |
|
|
437
|
+
| `tool` | `ServerToolExecution`, `BuiltinToolExecution`, `ClientToolExecution`, `UnknownToolExecution`, `ToolSchema`, `HumanApprovalRequired`, and their responses |
|
|
438
|
+
| `a2a` | `A2AInference`, `AgentDiscovery(+Response)` |
|
|
439
|
+
| `memory` | `ContextPersistence`, `ContextFlush(+Response)` |
|
|
440
|
+
| `knowledge` | `KnowledgeRetrieval(+Response)` |
|
|
441
|
+
| `speech` | `SpeechTranscription(+Response)`, `SpeechSynthesis(+Response)` |
|
|
442
|
+
| `unknown` | anything unregistered — rendered by class name rather than dropped |
|
|
443
|
+
|
|
444
|
+
Standalone API, usable without the chat hook:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
import { foldWorklog, reduceWorklog, frameToRecord, useWorklog } from '@alquimia-ai/tools/worklog';
|
|
448
|
+
|
|
449
|
+
const state = foldWorklog(records); // fold a full history
|
|
450
|
+
const next = reduceWorklog(state, record); // fold one frame at a time
|
|
451
|
+
const record = frameToRecord(sseFrame); // normalize a raw SSE frame
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
The module also types the runtime's worklog REST API — `WorklogSummary`, `WorklogDetail`, `WorklogEventDetail`, `WorklogPage`, `WorklogEventPage`, `WorklogVerificationResult` — for reading `/worklog/*` from a backend. Those endpoints are audit surface; this package does not call them.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Providers
|
|
459
|
+
|
|
460
|
+
Providers are abstract classes with swappable implementations, so the app picks its vendor without the SDK depending on one.
|
|
461
|
+
|
|
462
|
+
| Abstract | Implementations |
|
|
463
|
+
|---|---|
|
|
464
|
+
| `WhisperProvider` (TTS + STT) | `AlquimiaWhisperProvider`, `OpenAIWhisperProvider`, `ElevenLabsWhisperProvider`, `OrpheusWhisperProvider` |
|
|
465
|
+
| `StableDiffusionProvider` | `OpenAIStableDiffusionProvider`, `StabilityProvider` |
|
|
466
|
+
| `CharacterizationProvider` | `OpenAIAnalyzeCharProvider` |
|
|
467
|
+
| `RatingsProvider` | `AlquimiaRatingsProvider` |
|
|
468
|
+
| `LoggerProvider` | `ElasticLoggerProvider` (`@alquimia-ai/tools/providers/elastic`) |
|
|
469
|
+
|
|
470
|
+
These are **client-side integrations**, separate from the runtime's own audio pipeline. An agent configured with a runtime STT/TTS adapter transcribes and synthesizes server-side — that path is `input_audio` / `output_audio` on infer, not a provider.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Server proxy
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
createAlquimiaProxyHandler({
|
|
478
|
+
assistantBaseUrl, // required
|
|
479
|
+
apiKey, // required
|
|
480
|
+
inferRoute: 'event/infer', // defaults shown
|
|
481
|
+
streamRoute: 'event/stream',
|
|
482
|
+
blobUploadRoute: 'context/blob/upload',
|
|
483
|
+
toolCompletionRoute: 'event/tool-completion',
|
|
484
|
+
})
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Returns `handleInfer`, `handleStream`, `handleBlobUpload`, `handleToolCompletion`.
|
|
488
|
+
|
|
489
|
+
What it does beyond adding the bearer token:
|
|
490
|
+
|
|
491
|
+
- Forwards identity headers on infer/stream/blob upload: `session-id`, `user-id`, `assistant-id`, `task-id`, `parent-task-id`, `agentspace-id`, `channel-id`, `depth`.
|
|
492
|
+
- Forwards correlation headers on tool-completion: `taskid`, `sessionid`, `userid`, `assistantid`, `parenttaskid`, `agentspaceid`, `channelid`, `depth`, `versiontag`.
|
|
493
|
+
- Preserves the infer query string, so `?version_tag=` and `?agentspace_id=` survive the hop.
|
|
494
|
+
- Streams the SSE body through untouched.
|
|
495
|
+
- Turns the stream's `?trace_parent=` query param back into an `x-trace-parent` header, and does not pass the param upstream.
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Distributed tracing
|
|
500
|
+
|
|
501
|
+
Infer and stream are two HTTP calls that must land in **one** trace, and `EventSource` cannot set headers. So the SDK sends `x-trace-parent` on infer, remembers the traceparent for the turn, and appends it to the stream URL as `?trace_parent=`; the proxy converts it back to a header and strips the param. When a turn has no trace context, the header is omitted entirely rather than sent empty.
|
|
502
|
+
|
|
503
|
+
`@alquimia-ai/tools/services` exposes the Elastic APM RUM integration, and `handleApmRequest` in `/actions` proxies the APM ingest endpoint.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Types
|
|
508
|
+
|
|
509
|
+
`@alquimia-ai/tools/types` covers both the wire and the view model.
|
|
510
|
+
|
|
511
|
+
**Wire:** `CommonAttributes` (with `versiontag`), `InferInitResponse`, `Content` / `ContentPart` / `TextContentPart` / `ImageContentPart` / `NonStandardContentPart`, `RuntimeBlob`, `AttachmentPayload`, `NativeToolsEvaluationStrategy`, `RuntimeEvent` and the `/event/*` payload union, `TTSResult`, `RatingData`.
|
|
512
|
+
|
|
513
|
+
**View:** `AlquimiaMessage`, `AlquimiaEventData`, `AIMessageChunk`, `AssistantInferenceResponse`, `ToolEvent`, `Tooler`, `ThinkingsInferenceResponse`, `GenuiSurfaceItem`, `ActionResponse`.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Export map
|
|
518
|
+
|
|
519
|
+
| Entry | Contents |
|
|
520
|
+
|---|---|
|
|
521
|
+
| `/adapters` | `AlquimiaAdapter`, `AlquimiaSDKOptions` |
|
|
522
|
+
| `/adapters/next` | `createNextJsAdapter` |
|
|
523
|
+
| `/adapters/fetch` | `createFetchAdapter` |
|
|
524
|
+
| `/sdk` | `AlquimiaSDK` |
|
|
525
|
+
| `/hooks` | `useAlquimia`, `useRatings` |
|
|
526
|
+
| `/proxy` | `createAlquimiaProxyHandler`, `ProxyConfig` |
|
|
527
|
+
| `/next` | `createNextJsRouteHandlers`, `handleChatRequest`, `handleStreamRequest`, `initConversation` |
|
|
528
|
+
| `/providers` | provider base classes and implementations |
|
|
529
|
+
| `/providers/elastic` | `ElasticLoggerProvider` |
|
|
530
|
+
| `/genui` | the GenUI protocol |
|
|
531
|
+
| `/worklog` | reducer, registry, types, `useWorklog` |
|
|
532
|
+
| `/actions` | `initConversation`, CRUD helpers, `handleApmRequest` |
|
|
533
|
+
| `/services` | Elastic APM RUM |
|
|
534
|
+
| `/types` | wire + view types |
|
|
535
|
+
| `/utils` | header/cookie/formatting helpers |
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Development
|
|
540
|
+
|
|
541
|
+
```sh
|
|
542
|
+
yarn test # vitest
|
|
543
|
+
yarn lint # eslint --max-warnings 0
|
|
544
|
+
yarn build # tsup
|
|
545
|
+
yarn genui:catalog # regenerate the published A2UI catalog artifact
|
|
546
|
+
```
|
|
44
547
|
|
|
548
|
+
Changes to published behaviour need a changeset — see [`docs/how-to-use-changeset.md`](../../docs/how-to-use-changeset.md).
|
package/dist/actions/index.d.mts
CHANGED
package/dist/actions/index.d.ts
CHANGED
package/dist/hooks/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { AlquimiaSDK } from '../sdk/index.mjs';
|
|
3
3
|
import { AlquimiaAdapter } from '../adapters/index.mjs';
|
|
4
|
-
import { W as WhisperProvider, S as StableDiffusionProvider, C as CharacterizationProvider, R as RatingsProvider, L as LoggerProvider } from '../providers-
|
|
5
|
-
import { G as GenuiSurfaceItem, e as AlquimiaMessage,
|
|
4
|
+
import { W as WhisperProvider, S as StableDiffusionProvider, C as CharacterizationProvider, R as RatingsProvider, L as LoggerProvider } from '../providers-B4IOiGfr.mjs';
|
|
5
|
+
import { G as GenuiSurfaceItem, e as AlquimiaMessage, F as ToolEvent, a as AIMessageChunk, R as RatingData } from '../type-61gaZga-.mjs';
|
|
6
6
|
import { T as ToolExecutionResponse } from '../types-CMVbyEv8.mjs';
|
|
7
|
-
import { a as WorklogState } from '../types-
|
|
7
|
+
import { a as WorklogState } from '../types-lOliroPw.mjs';
|
|
8
8
|
import { createMessageId } from '../utils/index.mjs';
|
|
9
9
|
import { C as CatalogManifest } from '../registry-Bcu_ZYL1.mjs';
|
|
10
10
|
import { U as UIAction } from '../actions-BjDPJdyA.mjs';
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { AlquimiaSDK } from '../sdk/index.js';
|
|
3
3
|
import { AlquimiaAdapter } from '../adapters/index.js';
|
|
4
|
-
import { W as WhisperProvider, S as StableDiffusionProvider, C as CharacterizationProvider, R as RatingsProvider, L as LoggerProvider } from '../providers-
|
|
5
|
-
import { G as GenuiSurfaceItem, e as AlquimiaMessage,
|
|
4
|
+
import { W as WhisperProvider, S as StableDiffusionProvider, C as CharacterizationProvider, R as RatingsProvider, L as LoggerProvider } from '../providers-mx21gNgZ.js';
|
|
5
|
+
import { G as GenuiSurfaceItem, e as AlquimiaMessage, F as ToolEvent, a as AIMessageChunk, R as RatingData } from '../type-D-JaSMkV.js';
|
|
6
6
|
import { T as ToolExecutionResponse } from '../types-CMVbyEv8.js';
|
|
7
|
-
import { a as WorklogState } from '../types-
|
|
7
|
+
import { a as WorklogState } from '../types-lOliroPw.js';
|
|
8
8
|
import { createMessageId } from '../utils/index.js';
|
|
9
9
|
import { C as CatalogManifest } from '../registry-Bcu_ZYL1.js';
|
|
10
10
|
import { U as UIAction } from '../actions-CP1d4Ye1.js';
|