@alia.onl/sdk 15.8.0 → 19.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/CHANGELOG.md +262 -13
- package/README.md +223 -151
- package/package.json +16 -6
- package/src/animations.ts +0 -1
- package/src/components/AliaAskProvider.tsx +127 -0
- package/src/components/AliaChatContent.tsx +604 -392
- package/src/components/AliaChatHeader.tsx +14 -38
- package/src/components/AliaChatScreen.tsx +56 -31
- package/src/components/AliaChatSheet.tsx +110 -36
- package/src/components/AliaComposer.tsx +1 -1
- package/src/components/AliaParticipants.tsx +202 -0
- package/src/components/AliaParticipantsHeader.tsx +60 -0
- package/src/components/AliaSharedConversation.tsx +86 -0
- package/src/components/AliaWelcomeMessage.tsx +18 -5
- package/src/components/Image.tsx +12 -0
- package/src/components/Markdown.tsx +498 -14
- package/src/components/PlanPreviewCard.tsx +7 -49
- package/src/components/ReplyMarkdown.tsx +359 -0
- package/src/components/RichBlocks.tsx +195 -0
- package/src/components/canvas/CanvasComponent.tsx +59 -0
- package/src/components/canvas/ChartRenderer.tsx +73 -0
- package/src/components/canvas/CodeRenderer.tsx +24 -0
- package/src/components/canvas/FormRenderer.tsx +101 -0
- package/src/components/canvas/MarkdownRenderer.tsx +14 -0
- package/src/components/canvas/TableRenderer.tsx +47 -0
- package/src/components/thought/FilesAndSources.tsx +120 -0
- package/src/components/thought/ThoughtPanel.tsx +417 -0
- package/src/components/thought/ToolStep.tsx +141 -0
- package/src/components/thread/AgentResultCard.tsx +125 -0
- package/src/components/thread/AgentTaskCard.tsx +127 -0
- package/src/components/thread/AliaThread.tsx +401 -0
- package/src/components/thread/MessageBlockBoundary.tsx +78 -0
- package/src/components/thread/NewConversationOffer.tsx +54 -0
- package/src/components/thread/ThreadRow.tsx +458 -0
- package/src/components/thread/ThreadSearch.tsx +129 -0
- package/src/components/thread/ToolResultCard.tsx +254 -0
- package/src/components/thread/TurnMenu.tsx +54 -0
- package/src/components/thread/TurnStatusLine.tsx +34 -0
- package/src/components/voice/AliaVoiceCall.tsx +21 -0
- package/src/components/voice/VoiceControls.tsx +2 -28
- package/src/components/voice/VoiceSession.tsx +39 -11
- package/src/hooks/useAliaActivity.ts +2 -2
- package/src/hooks/useAtBottom.ts +51 -0
- package/src/hooks/useAttachMenu.ts +94 -0
- package/src/hooks/useChatComposer.ts +299 -0
- package/src/hooks/useChatVoice.ts +129 -0
- package/src/hooks/useDocumentPicker.ts +3 -29
- package/src/hooks/useFailedTurnToast.ts +43 -0
- package/src/hooks/useMessageVote.ts +50 -0
- package/src/hooks/useOpenThought.ts +79 -0
- package/src/hooks/usePendingQuestions.ts +2 -2
- package/src/hooks/useReadAloud.ts +92 -0
- package/src/hooks/useShareHandler.ts +24 -0
- package/src/hooks/useSoundEffects.ts +1 -1
- package/src/hooks/useThoughtScope.ts +59 -0
- package/src/hooks/useThreadRows.ts +145 -0
- package/src/hooks/useThreadView.ts +68 -0
- package/src/hooks/useTimelineWindow.ts +256 -0
- package/src/hooks/useVoiceLauncher.ts +180 -0
- package/src/hooks/useVoiceRoom.ts +4 -4
- package/src/index.ts +43 -29
- package/src/lib/activity-store.ts +1 -1
- package/src/lib/add-menu.ts +7 -0
- package/src/lib/call-thread.ts +33 -0
- package/src/lib/citations.ts +79 -2
- package/src/lib/clipboard.ts +23 -0
- package/src/lib/content-utils.ts +10 -0
- package/src/lib/copy-en.ts +284 -0
- package/src/lib/copy.ts +32 -0
- package/src/lib/document-picker.ts +31 -0
- package/src/lib/flights.ts +22 -13
- package/src/lib/focus-return.ts +66 -0
- package/src/lib/keyboard.native.tsx +19 -0
- package/src/lib/keyboard.tsx +9 -0
- package/src/lib/labels.tsx +69 -17
- package/src/lib/participants.ts +86 -0
- package/src/lib/power-level-options.ts +66 -0
- package/src/lib/questions.ts +2 -2
- package/src/lib/task-labels.ts +4 -4
- package/src/lib/tool-call-status.ts +1 -1
- package/src/lib/tool-icons.ts +36 -0
- package/src/lib/tool-renderers.tsx +48 -0
- package/src/lib/turn-state.ts +2 -2
- package/src/lib/voice-call-request.ts +49 -0
- package/src/lib/voice-call.ts +98 -35
- package/src/lib/voice-controls-labels.ts +31 -0
- package/src/lib/voice-protocol.ts +34 -15
- package/src/lib/work-log.ts +6 -6
- package/src/runtime/agents.ts +151 -0
- package/src/runtime/api.ts +141 -0
- package/src/runtime/app-assertion.ts +53 -0
- package/src/runtime/attachment-utils.ts +96 -0
- package/src/runtime/composer-draft-store.ts +270 -0
- package/src/runtime/config.tsx +186 -0
- package/src/runtime/conversation-store.ts +49 -0
- package/src/runtime/conversations.ts +557 -0
- package/src/runtime/error-utils.ts +104 -0
- package/src/runtime/host-tools.ts +140 -0
- package/src/runtime/index.ts +113 -0
- package/src/runtime/message-history.ts +124 -0
- package/src/runtime/model.ts +39 -0
- package/src/runtime/oxy-files.ts +37 -0
- package/src/runtime/power-levels.ts +66 -0
- package/src/runtime/query-keys.ts +34 -0
- package/src/runtime/random-uuid.ts +25 -0
- package/src/runtime/routes.ts +51 -0
- package/src/runtime/sharing.ts +165 -0
- package/src/runtime/sse-frame-reader.ts +88 -0
- package/src/runtime/stream-events.ts +146 -0
- package/src/runtime/stream-outcome.ts +21 -0
- package/src/runtime/thought-store.ts +103 -0
- package/src/runtime/thread/agent-activity.ts +261 -0
- package/src/runtime/thread/automation-labels.ts +79 -0
- package/src/runtime/thread/conversation-export.ts +135 -0
- package/src/runtime/thread/message-days.ts +120 -0
- package/src/runtime/thread/message-processor.ts +93 -0
- package/src/runtime/thread/task-utils.ts +11 -0
- package/src/runtime/thread/thought-utils.ts +752 -0
- package/src/runtime/thread/thread-history.ts +139 -0
- package/src/runtime/thread/timeline.ts +202 -0
- package/src/runtime/thread/title-tags.ts +8 -0
- package/src/runtime/thread/tool-cards.ts +383 -0
- package/src/runtime/turn-failure.ts +108 -0
- package/src/runtime/types.ts +148 -0
- package/src/runtime/usage-limit-error.ts +44 -0
- package/src/runtime/use-agent-activity.ts +80 -0
- package/src/runtime/use-conversation-stream.ts +1433 -0
- package/src/runtime/use-conversation-thread.ts +556 -0
- package/src/runtime/use-start-conversation.ts +107 -0
- package/src/runtime/use-thread-history.ts +64 -0
- package/src/runtime/use-thread-search.ts +95 -0
- package/src/runtime/use-turn-edit.ts +99 -0
- package/src/tools.ts +2 -2
- package/src/types.ts +18 -47
- package/src/components/AliaChatMessageList.tsx +0 -532
- package/src/components/ThinkingBlock.tsx +0 -43
- package/src/hooks/useAliaChat.ts +0 -573
- package/src/lib/catalogue.ts +0 -236
- package/src/lib/chat-stream.ts +0 -655
- package/src/lib/chat-transport.ts +0 -98
- package/src/lib/delegation.ts +0 -34
- package/src/lib/work-log-copy.ts +0 -61
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @alia.onl/sdk
|
|
2
2
|
|
|
3
|
-
Alia's chat SDK for React Native and Expo apps built on Oxy: the chat sheet
|
|
4
|
-
screen, the
|
|
5
|
-
of. It renders inside the one `OxyProvider` your app already mounts and sends
|
|
3
|
+
Alia's chat SDK for React Native and Expo apps built on Oxy: the conversation (the chat sheet,
|
|
4
|
+
the screen, the thread, the thought panel and the composer), the streaming hook, speech in and
|
|
5
|
+
out, and the primitives they are made of. It renders inside the one `OxyProvider` your app already mounts and sends
|
|
6
6
|
the signed-in user's Oxy session with every request; it never holds a token of
|
|
7
7
|
its own.
|
|
8
8
|
|
|
@@ -17,8 +17,11 @@ a prebuilt library.
|
|
|
17
17
|
bun add @alia.onl/sdk
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`react`, `react-native`, `@oxy.so/core`, `@oxy.so/services`
|
|
21
|
-
|
|
20
|
+
`react`, `react-native`, `@oxy.so/core`, `@oxy.so/services`, `@tanstack/react-query`
|
|
21
|
+
(the conversation runtime's cache; `OxyProvider` mounts the client), `react-native-css`
|
|
22
|
+
(the thread's images take `className`) and the Expo modules listed under `peerDependencies` in
|
|
23
|
+
`package.json` — among them `expo-clipboard` and `expo-web-browser`, which the thread's copy
|
|
24
|
+
button and its sources use — are yours to install. Metro
|
|
22
25
|
must resolve package `exports` (`resolver.unstable_enablePackageExports = true`,
|
|
23
26
|
the default in Expo SDK 53+), because every icon import is a subpath.
|
|
24
27
|
|
|
@@ -59,11 +62,26 @@ English). Bloom's own strings follow `BloomProvider`'s `locale`.
|
|
|
59
62
|
|
|
60
63
|
Entry points:
|
|
61
64
|
|
|
62
|
-
- `@alia.onl/sdk` —
|
|
65
|
+
- `@alia.onl/sdk` — the conversation screen (`AliaChatScreen`, `AliaChatSheet`,
|
|
66
|
+
`AliaChatContent`, the thread `AliaThread`, the work panel `ThoughtPanel`) with its voice call included: the button that opens it, the
|
|
67
|
+
bar with its honest state, the notices (failure, moved to another device, time
|
|
68
|
+
limit) and the transcript in the same thread. Also dictation, read-aloud,
|
|
69
|
+
the conversation runtime, primitives. The call loads on the first call.
|
|
70
|
+
- `@alia.onl/sdk/runtime` — the conversation data layer alone (hooks, cache, stores,
|
|
71
|
+
`AliaRuntimeProvider`), for a sidebar or a test that must not load the chat UI.
|
|
72
|
+
- `@alia.onl/sdk/model` — the conversation as pure data (the records, the timeline, the thought
|
|
73
|
+
panel's steps and sources, tool cards, the markdown export): no React, no Oxy client.
|
|
63
74
|
- `@alia.onl/sdk/csp` — the CSP the SDK needs from a host page (above).
|
|
64
|
-
- `@alia.onl/sdk/voice` — `VoiceSession
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
- `@alia.onl/sdk/voice` — the call's parts (`VoiceSession`, `useVoiceRoom`,
|
|
76
|
+
the audio engine) for a host that builds its own screen.
|
|
77
|
+
|
|
78
|
+
Voice is on by default; `voice={false}` makes a chat text-only, and
|
|
79
|
+
`voice={{ canStartVoice, onVoiceBlocked, onCallEnded, onNotice, sounds }}` brings
|
|
80
|
+
what only the host knows (the plan and credits gate, what to refresh when a call
|
|
81
|
+
ends, where notices go, the cues it plays). Whether the screen holding the call is the one on
|
|
82
|
+
show is `isFocused` of the chat. The conversation a call lives in is the chat's own. Its words are `labels.voiceControls`,
|
|
83
|
+
`voiceError`, `voiceEnded`, `voiceEndingSoon` and `voiceBlocked`. A host with its
|
|
84
|
+
own screen uses `useVoiceLauncher` and `AliaVoiceCall`, the same pieces.
|
|
67
85
|
|
|
68
86
|
## Speech
|
|
69
87
|
|
|
@@ -86,7 +104,7 @@ transcript content.
|
|
|
86
104
|
|
|
87
105
|
On iOS and Android a call needs
|
|
88
106
|
[`react-native-audio-api`](https://github.com/software-mansion/react-native-audio-api)
|
|
89
|
-
(
|
|
107
|
+
(a peer of the SDK: the conversation screen includes the call)
|
|
90
108
|
and its config plugin, then a new native build:
|
|
91
109
|
|
|
92
110
|
```json
|
|
@@ -117,22 +135,59 @@ On iOS and Android its composer rises with the keyboard through
|
|
|
117
135
|
`react-native-keyboard-controller`, which needs the app wrapped in its
|
|
118
136
|
`KeyboardProvider` (re-exported here).
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
The sheet, the screen and the Alia app are three frames around ONE conversation runtime
|
|
139
|
+
(see [The conversation runtime](#the-conversation-runtime)): they list, open, create, send,
|
|
140
|
+
stop, retry, edit and clear conversations through the same hooks, and a conversation started in
|
|
141
|
+
one is the same conversation in the others.
|
|
121
142
|
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
## Any agent, from any app
|
|
144
|
+
|
|
145
|
+
Every agent — Alia included — is the same agent (ADR 0020), and any Oxy app opens any agent the
|
|
146
|
+
person can reach. The wire contract is [`docs/agent-embedding.md`](../../docs/agent-embedding.md).
|
|
124
147
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
```tsx
|
|
149
|
+
import { AliaAskProvider, useAskAgent } from '@alia.onl/sdk';
|
|
150
|
+
|
|
151
|
+
<AliaAskProvider
|
|
152
|
+
agents={{ alia: {}, sindi: { id: SINDI_AGENT_ID } }} // no id = Alia
|
|
153
|
+
clientContext={`Homiio — viewing listing ${listing.id}`} // the screen, built per turn
|
|
154
|
+
hostTools={{
|
|
155
|
+
app: 'homiio',
|
|
156
|
+
definitions: [{ name: 'apply_search', description: '…', inputSchema: { type: 'object' } }],
|
|
157
|
+
execute: async (action) => applySearch(action.input), // → { outcome: 'applied' | … }
|
|
158
|
+
}}
|
|
159
|
+
toolRenderers={{ 'homiio:searchListings': (r) => <ListingCards data={r.output} /> }}
|
|
160
|
+
onFeatureLocked={(key) => showHomiioPlus(key)}
|
|
161
|
+
onShare={({ token }) => shareSheet(`https://homiio.com/shared/${token}`)}
|
|
162
|
+
>
|
|
163
|
+
{children}
|
|
164
|
+
</AliaAskProvider>;
|
|
165
|
+
|
|
166
|
+
const { ask } = useAskAgent();
|
|
167
|
+
ask('sindi', { text: 'Is this flat a good price?' }); // signed out: Oxy's sign-in instead
|
|
128
168
|
```
|
|
129
169
|
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
170
|
+
- **The lead.** `agent={{ id }}` (on `AliaChatSheet`, `AliaChatScreen` and `AliaChatContent`) makes
|
|
171
|
+
that agent the conversation's lead: its greeting, name, character and starters
|
|
172
|
+
(`GET /v1/agents/:id/profile`) and the header's first avatar. Omitted, Alia.
|
|
173
|
+
- **The participants.** `AliaChatState.participants` is `{ lead, joined }`; draw it with
|
|
174
|
+
`AliaParticipants` (or `AliaParticipantsHeader` inside Bloom's AI chat shell) wherever the
|
|
175
|
+
chrome puts the conversation's title. When the lead consults an agent (`alia.agent_start`), the
|
|
176
|
+
agent's reply is drawn under its own identity and its avatar flies from there into the header —
|
|
177
|
+
Bloom's multi-agent join — which gains the agent when it lands.
|
|
178
|
+
- **Host tools** are UI effects the app's own client performs. An `alia.host_action` is executed
|
|
179
|
+
once, by `execute`, and its outcome is sent with the next request; an outcome whose request did
|
|
180
|
+
not get through goes with the one after. They are never authority over data.
|
|
181
|
+
- **Tool renderers** are keyed `<app>:<tool>`, parsed only from `oxy_<app>__<tool>` /
|
|
182
|
+
`self_<app>__<tool>`, and drawn before the generic tool card — for consulted agents too.
|
|
183
|
+
- **Feature gates.** The attach menu offers photos and files as the lead offers them
|
|
184
|
+
(`GET /v1/agents/:id/features`); a locked row, and a turn refused with
|
|
185
|
+
`feature_entitlement_required`, call `onFeatureLocked(key)`. Documents are uploaded to the
|
|
186
|
+
person's Oxy Files (`oxyServices.assets.upload`) and sent as a `file` part.
|
|
187
|
+
- **Sharing.** `onShare` receives `{ conversationId, token, expiresAt }` once the SDK has minted
|
|
188
|
+
the link; `useShareConversation()` mints and revokes, and `<AliaSharedConversation token>` reads
|
|
189
|
+
one, signed in or not. `useConversations({ agentId })` lists the person's conversations with one
|
|
190
|
+
agent.
|
|
136
191
|
|
|
137
192
|
## An "Ask Alia" button with her avatar
|
|
138
193
|
|
|
@@ -153,7 +208,7 @@ you do); run it too when a Bloom upgrade changes how the runtime draws her.
|
|
|
153
208
|
|
|
154
209
|
`useAliaActivity()` is the busiest of two sources, so a button reacts even with the sheet closed:
|
|
155
210
|
|
|
156
|
-
- a turn running in this app: every `
|
|
211
|
+
- a turn running in this app: every `useConversationThread` publishes its lifecycle to a shared store;
|
|
157
212
|
- her runs for the signed-in person elsewhere (background work, another device): the API's
|
|
158
213
|
`alia:activity` event on the person's `user:<id>` room, over ONE Socket.IO connection per
|
|
159
214
|
API origin, opened with the Oxy session's token only while someone is signed in and a
|
|
@@ -162,13 +217,52 @@ you do); run it too when a Bloom upgrade changes how the runtime draws her.
|
|
|
162
217
|
`socket.io-client` is a regular dependency, not a peer: the SDK owns this connection and an
|
|
163
218
|
app should not have to install a transport to use a button.
|
|
164
219
|
|
|
220
|
+
## The thread
|
|
221
|
+
|
|
222
|
+
The sheet, the screen and the Alia app draw ONE thread, `AliaThread` (inside `AliaChatContent`),
|
|
223
|
+
and nothing else draws turns: Bloom's AI Chat turns with their reveal, the feedback row and the
|
|
224
|
+
long-press menu; the reply's Markdown with its rich blocks, its code card and its references;
|
|
225
|
+
the work log (`WebSearch`, `TaskList`) and the "Worked for…" line; tool cards; the questions Alia
|
|
226
|
+
asked; the plan an agent shows; the failed turn's toast with its Retry; day lines; the window over a long
|
|
227
|
+
history with the scroll that goes with it; and the card of an agent's run. What a frame adds is
|
|
228
|
+
around it, never in it (`tests/components/thread/one-thread.test.tsx` mounts it both ways and
|
|
229
|
+
holds the rows equal, and fails if anything else imports Bloom's thread).
|
|
230
|
+
|
|
231
|
+
An agent's permanent thread is `threadHandle`: the conversations behind the open one are paged in
|
|
232
|
+
above it, the thread can be searched (`state.openSearch`), and a hit opens the stretch around it.
|
|
233
|
+
Those routes (`/agents/thread/:handle/…`) answer Alia's own origins only, as `/conversations` did
|
|
234
|
+
before `/v1`: an embedding Oxy app has no agents of its own and passes no handle.
|
|
235
|
+
|
|
236
|
+
### The work panel, and where it goes
|
|
237
|
+
|
|
238
|
+
A turn that used tools has a "Worked for…" line; pressing it selects the turn (`useThoughtStore`)
|
|
239
|
+
and asks the host to show it (`runtime.showThought`). `ThoughtPanel` — steps, sources, activity —
|
|
240
|
+
draws the selection wherever the host puts it: the Alia app's right panel, the sheet's own sheet
|
|
241
|
+
over the conversation. A host without `showThought` has no panel, and the line is plain text. A
|
|
242
|
+
running turn's first tool opens the panel by itself (`showThought('automatic')`) unless the host
|
|
243
|
+
declines because something else is open. A file the turn generated opens where the host keeps its
|
|
244
|
+
files (`ThoughtPanel`'s `outputActions`).
|
|
245
|
+
|
|
246
|
+
### Cards: why weather, market and FairCoin are the SDK's
|
|
247
|
+
|
|
248
|
+
The weather, market quote and FairCoin price cards are the thread's. Their tools
|
|
249
|
+
(`getWeather`, `getMarketQuote`, `getFairCoin`) are in the pipeline every Alia surface calls, and
|
|
250
|
+
the card's numbers travel in the message itself (the whole of every range is in the snapshot, so a
|
|
251
|
+
thread reopened next month shows the figures it was answered with). So any surface that draws
|
|
252
|
+
Alia's reply draws them. The scheduled-task card is the same card with one difference: it points at
|
|
253
|
+
a screen of the host's, so it is a link only where `runtime.openAutomation` says where it goes.
|
|
254
|
+
|
|
165
255
|
## The composer
|
|
166
256
|
|
|
167
257
|
The sheet, the screen and the Alia app share one composer, `AliaComposer`: Bloom's
|
|
168
258
|
`ComposerPill` with files by paste and drop (read with real progress, retried, refused with a
|
|
169
259
|
sentence), dictation, suggestions over the field and Alia's questions docked above it.
|
|
170
260
|
`AliaChatSheet` and `AliaChatScreen` render it with photos, voice and suggestions, and
|
|
171
|
-
`AliaChatSheet` focuses it once the sheet has presented.
|
|
261
|
+
`AliaChatSheet` focuses it once the sheet has presented. The conversation also owns the draft
|
|
262
|
+
(per conversation and account), sending, rewriting a question already sent, the questions Alia
|
|
263
|
+
asked and the power-level selector: give `AliaChatContent` an `onModelChange` and the composer
|
|
264
|
+
offers the seven levels (`auto` … `ultra`) with `deviceModels` of the person's own after them;
|
|
265
|
+
give it an `addMenu` (rows and what pressing one does) and that menu replaces the photos one. To build your own surface, mount it and
|
|
172
266
|
pass what the surface has:
|
|
173
267
|
|
|
174
268
|
```tsx
|
|
@@ -193,151 +287,129 @@ import { AliaComposer } from '@alia.onl/sdk';
|
|
|
193
287
|
/>;
|
|
194
288
|
```
|
|
195
289
|
|
|
196
|
-
##
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
one
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
290
|
+
## The conversation runtime
|
|
291
|
+
|
|
292
|
+
Everything that is a conversation lives in `src/runtime`, in layers that depend only downward:
|
|
293
|
+
|
|
294
|
+
| Layer | What | Public names |
|
|
295
|
+
| --- | --- | --- |
|
|
296
|
+
| Transport | Alia's REST API over the signed-in person's Oxy client (token, refresh and the single 401 replay are Oxy's); the turn's SSE stream | `useAliaApi`, `AliaApiError` |
|
|
297
|
+
| Model and state | the records, React Query keys and the cached lists; zustand stores for the composer drafts and the hand-off of a first message | `Message`, `Conversation`, `aliaQueryKeys`, `useComposerDraftStore`, `useConversationStore` |
|
|
298
|
+
| Hooks | `useConversations` (the list, paginated), `useConversation` (one, with its history), `useCreateConversation`, `useRenameConversation`, `useDeleteConversation`, `useClearConversation`, and `useConversationThread`: the live thread of one conversation — messages, `sendMessage`, `createNewConversation`, `editMessage`, `regenerateMessage`, `retryFailedTurn`, `stopGeneration`, `clearConversation`, `startOver`, `ensureConversation` | `use…` |
|
|
299
|
+
| UI | `AliaChatContent` (the conversation screen), `AliaChatScreen`, `AliaChatSheet`, `AliaThread`, `ThoughtPanel` | components |
|
|
300
|
+
|
|
301
|
+
Between the hooks and the components are the hooks of what a person does with a conversation —
|
|
302
|
+
`useChatComposer` (the draft, send, edit, suggestions), `useChatVoice` (the call and its
|
|
303
|
+
transcript in the thread), `useThreadView` (the present or a moment of an agent's thread),
|
|
304
|
+
`useOpenThought`, `useReadAloud`, `useMessageVote` — each used by exactly one layer above it.
|
|
305
|
+
|
|
306
|
+
An agent's thread and runs (`useThreadHistory`, `useThreadSearch`, `useThreadWindow`,
|
|
307
|
+
`useAgentActivity`) read the agent routes through the same API and cache.
|
|
308
|
+
|
|
309
|
+
A turn goes to `POST {apiUrl}/v1/chat/completions`; conversations to `{apiUrl}/v1/conversations`.
|
|
310
|
+
`apiUrl` defaults to `EXPO_PUBLIC_ALIA_API_URL`, then `https://api.alia.onl`. A failure to
|
|
311
|
+
answer is a typed error that keeps the person's turn in the thread with a retry (`FailedTurn`),
|
|
312
|
+
never a filler message; running out of credits or hitting a rate limit is a `UsageLimitError`.
|
|
313
|
+
|
|
314
|
+
### Configure it once: `AliaRuntimeProvider`
|
|
315
|
+
|
|
316
|
+
Providers nest and merge, so the sheet works with no provider at all and the Alia app mounts
|
|
317
|
+
one above its routes. Everything is optional.
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { AliaRuntimeProvider, type AliaRuntimeHost } from '@alia.onl/sdk';
|
|
321
|
+
|
|
322
|
+
const host: AliaRuntimeHost = {
|
|
323
|
+
// navigation: the runtime created or chose a conversation / the open one is gone
|
|
324
|
+
openConversation: (id) => router.push(`/c/${id}`),
|
|
325
|
+
closeConversation: () => router.replace('/'),
|
|
326
|
+
// what only the host can show: credits, an artifact panel, a haptic tick
|
|
327
|
+
onUsage: (frame) => updateCredits(frame.credits_remaining),
|
|
328
|
+
onArtifact: (artifact) => rightPanel.show(artifact),
|
|
329
|
+
turnRequest: () => ({ webSearch: composerMenu.webSearch, deepResearch: composerMenu.deepResearch }),
|
|
330
|
+
// where the work of a turn is read; return false to decline an automatic opening
|
|
331
|
+
showThought: (opening) => (opening === 'automatic' && panel.isOpen ? false : panel.show('thought')),
|
|
332
|
+
openAutomation: (id) => router.push(`/automations/${id}`), // a scheduled-task card is a link
|
|
333
|
+
faviconUrl: (url) => favicons.of(url), // the work log's source icons
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
<AliaRuntimeProvider
|
|
337
|
+
apiUrl="https://api.alia.onl"
|
|
338
|
+
offline={accountScopedStore} // optional: where a 401-ed session keeps its conversations
|
|
339
|
+
streamFetch={expoFetch} // optional: a fetch that can stream a body on native
|
|
340
|
+
speech={{ voice: 'female', tone: 'brief', language: 'es-ES' }} // read-aloud, dictation, calls
|
|
341
|
+
host={host} // memoise it
|
|
342
|
+
>
|
|
343
|
+
{children}
|
|
344
|
+
</AliaRuntimeProvider>;
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The sheet and the Alia app differ ONLY in what they put there:
|
|
240
348
|
|
|
241
349
|
```tsx
|
|
242
|
-
|
|
350
|
+
// The sheet: nothing to configure; the chat keeps its own conversation.
|
|
351
|
+
<AliaChatSheet ref={sheet} />
|
|
352
|
+
|
|
353
|
+
// The Alia app: its route owns the conversation, its sidebar reads the same list.
|
|
354
|
+
<AliaChatContent
|
|
355
|
+
surface // the container draws the page's card
|
|
356
|
+
conversationId={id}
|
|
357
|
+
threadHandle={handle} // an agent's thread: history above, search
|
|
358
|
+
model={powerLevel}
|
|
359
|
+
onModelChange={setPowerLevel} // the composer offers the levels
|
|
360
|
+
addMenu={{ groups, onSelect }} // the host's rows
|
|
361
|
+
chrome={(chat) => ({ header, title, project, actions: <Actions chat={chat} /> })}
|
|
362
|
+
renderUsageLimit={({ error, dismiss }) => <UsageLimitDialog error={error} onDismiss={dismiss} />}
|
|
363
|
+
/>
|
|
364
|
+
function Sidebar() {
|
|
365
|
+
const { data } = useConversations(); // the very query the thread keeps up to date
|
|
366
|
+
…
|
|
367
|
+
}
|
|
243
368
|
```
|
|
244
369
|
|
|
245
|
-
|
|
246
|
-
`POST /v1/chat/completions` and `GET /catalogue` under whatever base URL you
|
|
247
|
-
give it. Nothing about the stream changes: the SDK's parser reads the bytes Alia
|
|
248
|
-
wrote.
|
|
370
|
+
### Voice lives in the conversation
|
|
249
371
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
`
|
|
254
|
-
`packages/api/src/middleware/auth.ts`), which accepts an Oxy session token —
|
|
255
|
-
verified with Oxy, the same way it would be if the browser had called Alia
|
|
256
|
-
directly — and the turn is metered to that user's Alia entitlement, the same as
|
|
257
|
-
a direct call. The other credentials the middleware recognises are
|
|
258
|
-
Alia-internal. There is no consumer-application credential for this route yet — that is Oxy Applications' to issue (OxyHQ/oxy#972).
|
|
372
|
+
A call always has a home. With a conversation open the call lives in it; on a new-chat screen
|
|
373
|
+
the conversation is created when the call starts (after the plan/credits gate) and the call
|
|
374
|
+
opens on the screen that shows it. That is one code path for the sheet, `AliaChatScreen` and the
|
|
375
|
+
Alia app (`useVoiceLauncher`'s `resolveConversation` and `useVoiceCallRequests`):
|
|
259
376
|
|
|
260
|
-
|
|
377
|
+
- the sheet has no routes, so the chat itself switches to the new conversation and the call opens
|
|
378
|
+
in place;
|
|
379
|
+
- the Alia app's new-chat route is replaced by the conversation's (`host.openConversation`), and
|
|
380
|
+
the call opens on that screen, which takes the pending request.
|
|
261
381
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const ALIA = 'https://api.alia.onl';
|
|
266
|
-
const app = express();
|
|
267
|
-
app.use(express.json({ limit: '1mb' }));
|
|
268
|
-
|
|
269
|
-
/** Relay one request to Alia and stream the answer back unchanged. */
|
|
270
|
-
async function relay(
|
|
271
|
-
req: express.Request,
|
|
272
|
-
res: express.Response,
|
|
273
|
-
method: 'GET' | 'POST',
|
|
274
|
-
path: string,
|
|
275
|
-
): Promise<void> {
|
|
276
|
-
const controller = new AbortController();
|
|
277
|
-
req.on('close', () => controller.abort());
|
|
278
|
-
|
|
279
|
-
const authorization = req.get('authorization');
|
|
280
|
-
const upstream = await fetch(`${ALIA}${path}`, {
|
|
281
|
-
method,
|
|
282
|
-
headers: {
|
|
283
|
-
...(authorization ? { Authorization: authorization } : {}),
|
|
284
|
-
Accept: req.get('accept') ?? 'application/json',
|
|
285
|
-
...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),
|
|
286
|
-
},
|
|
287
|
-
body: method === 'POST' ? JSON.stringify(req.body) : undefined,
|
|
288
|
-
signal: controller.signal,
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
res.status(upstream.status);
|
|
292
|
-
res.setHeader('Content-Type', upstream.headers.get('content-type') ?? 'application/json');
|
|
293
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
294
|
-
res.setHeader('X-Accel-Buffering', 'no'); // keep proxies from buffering the SSE stream
|
|
295
|
-
res.flushHeaders();
|
|
296
|
-
|
|
297
|
-
if (upstream.body === null) {
|
|
298
|
-
res.end();
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
try {
|
|
302
|
-
for await (const chunk of upstream.body) res.write(chunk);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
if (!controller.signal.aborted) throw error;
|
|
305
|
-
} finally {
|
|
306
|
-
res.end();
|
|
307
|
-
}
|
|
308
|
-
}
|
|
382
|
+
**Who decides which conversation is open:** the host, when it navigates (it passes
|
|
383
|
+
`runtime.openConversation`, or an outer `AliaRuntimeProvider` has one); otherwise the chat.
|
|
309
384
|
|
|
310
|
-
|
|
311
|
-
app.post('/v1/chat/completions', (req, res) => relay(req, res, 'POST', '/alia/chat'));
|
|
385
|
+
## Which API the SDK calls
|
|
312
386
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
app.get('/catalogue', (req, res) => relay(req, res, 'GET', '/catalogue'));
|
|
387
|
+
The SDK is Alia's product client and speaks **only to `/v1`**, Alia's product API: a bearer of
|
|
388
|
+
the signed-in person's Oxy session, no cookies, and public CORS — so it works in the browser of
|
|
389
|
+
every Oxy app, on that app's own origin, with nothing to configure and no backend in between.
|
|
317
390
|
|
|
318
|
-
|
|
319
|
-
|
|
391
|
+
- A turn: `POST {apiUrl}/v1/chat/completions` — the same handler `/alia/chat` mounts, with every
|
|
392
|
+
`alia.*` stream event, `conversationId` and the stored history.
|
|
393
|
+
- Conversations: `{apiUrl}/v1/conversations` (list, open, create, save, rename, delete, clear,
|
|
394
|
+
vote). A person's session only: an application key or a bot secret is refused, because a
|
|
395
|
+
conversation is somebody's private history.
|
|
396
|
+
- Dictation, read-aloud and the voice call: `/v1/audio/*` and `/v1/voice/session`.
|
|
320
397
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
398
|
+
`apiUrl` defaults to `EXPO_PUBLIC_ALIA_API_URL`, then `https://api.alia.onl`. `routes.ts` holds
|
|
399
|
+
the paths, and `tests/runtime/v1-only.test.ts` fails if the transport builds a URL outside `/v1`.
|
|
400
|
+
The one exception is the agent routes (`AGENT_ROUTES`: an agent's thread and its runs), which only
|
|
401
|
+
Alia's own clients use.
|
|
325
402
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
— a CORS policy on `/alia/chat` for registered consumer origins, and the SDK
|
|
330
|
-
default moving to `/alia/chat` in a major — are recorded in #244: the first is
|
|
331
|
-
blocked on Oxy Applications carrying an origin registry (OxyHQ/oxy#972), and
|
|
332
|
-
the second is an adoption window rather than a switch, for the raw-source
|
|
333
|
-
reason above.
|
|
403
|
+
`model` is a power level (`auto`, `instant`, `medium`, `high`, `xhigh`, `pro`, `ultra`) or an
|
|
404
|
+
exact `publisher/model`; it is sent as given, and omitted the server's default answers. The SDK
|
|
405
|
+
ships no model identifier of its own.
|
|
334
406
|
|
|
335
407
|
## Scripts
|
|
336
408
|
|
|
337
409
|
```sh
|
|
338
410
|
bun run typecheck # tsc --noEmit
|
|
339
411
|
bun run test # vitest
|
|
340
|
-
bun run check:entries # the root entry
|
|
412
|
+
bun run check:entries # the root entry reaches the call only through its lazy import; no retired modules
|
|
341
413
|
```
|
|
342
414
|
|
|
343
415
|
## Licence
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia.onl/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.1.0",
|
|
4
|
+
"description": "Alia AI chat SDK — the conversation runtime, chat UI, voice calls and dictation for Oxy apps",
|
|
4
5
|
"main": "src/index.ts",
|
|
5
6
|
"react-native": "src/index.ts",
|
|
6
7
|
"source": "src/index.ts",
|
|
@@ -10,6 +11,8 @@
|
|
|
10
11
|
"./avatar": "./src/avatar.ts",
|
|
11
12
|
"./content": "./src/lib/content-utils.ts",
|
|
12
13
|
"./csp": "./src/csp.ts",
|
|
14
|
+
"./model": "./src/runtime/model.ts",
|
|
15
|
+
"./runtime": "./src/runtime/index.ts",
|
|
13
16
|
"./tools": "./src/tools.ts",
|
|
14
17
|
"./voice": "./src/voice.ts",
|
|
15
18
|
"./package.json": "./package.json"
|
|
@@ -22,6 +25,11 @@
|
|
|
22
25
|
"CHANGELOG.md"
|
|
23
26
|
],
|
|
24
27
|
"license": "Apache-2.0",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/OxyHQ/Alia.git",
|
|
31
|
+
"directory": "packages/alia-chat"
|
|
32
|
+
},
|
|
25
33
|
"private": false,
|
|
26
34
|
"publishConfig": {
|
|
27
35
|
"access": "public"
|
|
@@ -30,10 +38,12 @@
|
|
|
30
38
|
"typecheck": "tsc --noEmit",
|
|
31
39
|
"test": "vitest run",
|
|
32
40
|
"check:entries": "node scripts/check-entry-isolation.mjs",
|
|
33
|
-
"render:avatar-still": "bun scripts/render-avatar-still.ts"
|
|
41
|
+
"render:avatar-still": "bun scripts/render-avatar-still.ts",
|
|
42
|
+
"prepublishOnly": "bun run typecheck && bun run test && bun run check:entries"
|
|
34
43
|
},
|
|
35
44
|
"dependencies": {
|
|
36
45
|
"clsx": "^2.1.1",
|
|
46
|
+
"react-native-markdown-display": "^7.0.2",
|
|
37
47
|
"socket.io-client": "^4.8.3",
|
|
38
48
|
"tailwind-merge": "^3.7.0",
|
|
39
49
|
"zustand": "^5.0.15"
|
|
@@ -44,7 +54,8 @@
|
|
|
44
54
|
"@expo/vector-icons": "^15.1.1",
|
|
45
55
|
"@oxy.so/bloom": ">=10.9.0",
|
|
46
56
|
"@oxy.so/core": ">=3.0.0",
|
|
47
|
-
"@oxy.so/services": ">=
|
|
57
|
+
"@oxy.so/services": ">=11.12.0",
|
|
58
|
+
"@tanstack/react-query": "^5.101",
|
|
48
59
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
49
60
|
"expo-blur": "^57.0.0",
|
|
50
61
|
"react-native-reanimated": "^4.5.0",
|
|
@@ -60,6 +71,8 @@
|
|
|
60
71
|
"nativewind": "^5.0.0-preview.3",
|
|
61
72
|
"expo-clipboard": "^57.0.0",
|
|
62
73
|
"expo-file-system": "^57.0.0",
|
|
74
|
+
"expo-web-browser": "^57.0.0",
|
|
75
|
+
"react-native-css": "^3.0.7",
|
|
63
76
|
"react-native-audio-api": "^0.13.6"
|
|
64
77
|
},
|
|
65
78
|
"peerDependenciesMeta": {
|
|
@@ -68,9 +81,6 @@
|
|
|
68
81
|
},
|
|
69
82
|
"expo-clipboard": {
|
|
70
83
|
"optional": true
|
|
71
|
-
},
|
|
72
|
-
"react-native-audio-api": {
|
|
73
|
-
"optional": true
|
|
74
84
|
}
|
|
75
85
|
},
|
|
76
86
|
"devDependencies": {
|
package/src/animations.ts
CHANGED