@alia.onl/sdk 15.7.0 → 19.0.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 +255 -12
- package/README.md +241 -155
- package/package.json +17 -5
- 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/csp.ts +50 -0
- package/src/hooks/useAliaActivity.ts +3 -3
- 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/useDictation.ts +2 -3
- 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/useTTS.ts +2 -3
- 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 +2 -6
- 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/character-runtime.ts +6 -4
- 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/origins.ts +15 -0
- 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-audio-engine.ts +14 -158
- package/src/lib/voice-call-request.ts +49 -0
- package/src/lib/voice-call.ts +76 -34
- package/src/lib/voice-controls-labels.ts +31 -0
- package/src/lib/voice-protocol.ts +32 -14
- package/src/lib/voice-worklets.ts +19 -0
- package/src/lib/work-log.ts +6 -6
- package/src/runtime/agents.ts +151 -0
- package/src/runtime/api.ts +123 -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 +1427 -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/worklets/voice-processors.js +152 -0
- package/src/components/AliaChatMessageList.tsx +0 -532
- package/src/components/ThinkingBlock.tsx +0 -43
- package/src/hooks/useAliaChat.ts +0 -573
- package/src/lib/api-url.ts +0 -2
- 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
|
|
|
@@ -28,12 +31,25 @@ The chat is built from Bloom (`@oxy.so/bloom`, a peer dependency): mount Bloom's
|
|
|
28
31
|
Alia's avatar is Bloom's 3D character (`AgentAvatar`). It draws through a runtime of
|
|
29
32
|
static files (about 214 MB), which the chat loads from Alia's own origin
|
|
30
33
|
(`ALIA_CHARACTER_RUNTIME_URL`, served with CORS open), so the app hosts nothing and mounts
|
|
31
|
-
no avatar provider.
|
|
32
|
-
`connect-src` (and `'wasm-unsafe-eval'`). To serve your own copy instead, serve the Bloom
|
|
34
|
+
no avatar provider. To serve your own copy instead, serve the Bloom
|
|
33
35
|
package's `assets/character-runtime/` directory unchanged and pass the URL of `runtime.mjs`
|
|
34
36
|
as `characterRuntimeUrl` to `AliaChatSheet` or `AliaChatScreen`; native avatars run in a
|
|
35
37
|
WebView, so there the URL must be absolute and reachable from the device.
|
|
36
38
|
|
|
39
|
+
A web app with a Content-Security-Policy composes the SDK's into its own instead of listing
|
|
40
|
+
Alia's origins by hand: `@alia.onl/sdk/csp` exports `ALIA_SDK_CSP` and `withAliaSdkCsp(extensions)`
|
|
41
|
+
in the shape of `@oxy.so/core`'s `OxyCspExtensions` (a leaf entry, safe on a server):
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { withAliaSdkCsp } from '@alia.onl/sdk/csp';
|
|
45
|
+
app.use(createOxySecurityHeaders({ csp: withAliaSdkCsp(APP_CSP_EXTENSIONS) }));
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
It declares the API and its WebSocket (`connect-src`), the avatar runtime and the voice call's
|
|
49
|
+
worklets on `https://alia.onl` (`script-src`, with `'wasm-unsafe-eval'` for the runtime's
|
|
50
|
+
WebAssembly), attachments and read-aloud clips (`img-src`, `media-src`). No `blob:` script:
|
|
51
|
+
the worklets are a content-addressed file on `alia.onl`, not a blob URL.
|
|
52
|
+
|
|
37
53
|
The empty chat greets the signed-in person by name ("Hi Nate, what's on your mind?"); pass
|
|
38
54
|
`welcomeGreeting` to say something else, or `labels.greeting` / `labels.greetingNamed` to translate it.
|
|
39
55
|
|
|
@@ -44,12 +60,28 @@ The chat's own copy is English. Pass `labels` to `AliaChatSheet` or
|
|
|
44
60
|
`AliaChatScreen` to translate it (`AliaChatLabels`; anything omitted stays
|
|
45
61
|
English). Bloom's own strings follow `BloomProvider`'s `locale`.
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- `@alia.onl/sdk` —
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
Entry points:
|
|
64
|
+
|
|
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.
|
|
74
|
+
- `@alia.onl/sdk/csp` — the CSP the SDK needs from a host page (above).
|
|
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.
|
|
53
85
|
|
|
54
86
|
## Speech
|
|
55
87
|
|
|
@@ -72,7 +104,7 @@ transcript content.
|
|
|
72
104
|
|
|
73
105
|
On iOS and Android a call needs
|
|
74
106
|
[`react-native-audio-api`](https://github.com/software-mansion/react-native-audio-api)
|
|
75
|
-
(
|
|
107
|
+
(a peer of the SDK: the conversation screen includes the call)
|
|
76
108
|
and its config plugin, then a new native build:
|
|
77
109
|
|
|
78
110
|
```json
|
|
@@ -103,22 +135,59 @@ On iOS and Android its composer rises with the keyboard through
|
|
|
103
135
|
`react-native-keyboard-controller`, which needs the app wrapped in its
|
|
104
136
|
`KeyboardProvider` (re-exported here).
|
|
105
137
|
|
|
106
|
-
|
|
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.
|
|
107
142
|
|
|
108
|
-
|
|
109
|
-
import { useAliaChat } from '@alia.onl/sdk';
|
|
143
|
+
## Any agent, from any app
|
|
110
144
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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).
|
|
147
|
+
|
|
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
|
|
114
168
|
```
|
|
115
169
|
|
|
116
|
-
`
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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.
|
|
122
191
|
|
|
123
192
|
## An "Ask Alia" button with her avatar
|
|
124
193
|
|
|
@@ -139,7 +208,7 @@ you do); run it too when a Bloom upgrade changes how the runtime draws her.
|
|
|
139
208
|
|
|
140
209
|
`useAliaActivity()` is the busiest of two sources, so a button reacts even with the sheet closed:
|
|
141
210
|
|
|
142
|
-
- a turn running in this app: every `
|
|
211
|
+
- a turn running in this app: every `useConversationThread` publishes its lifecycle to a shared store;
|
|
143
212
|
- her runs for the signed-in person elsewhere (background work, another device): the API's
|
|
144
213
|
`alia:activity` event on the person's `user:<id>` room, over ONE Socket.IO connection per
|
|
145
214
|
API origin, opened with the Oxy session's token only while someone is signed in and a
|
|
@@ -148,13 +217,52 @@ you do); run it too when a Bloom upgrade changes how the runtime draws her.
|
|
|
148
217
|
`socket.io-client` is a regular dependency, not a peer: the SDK owns this connection and an
|
|
149
218
|
app should not have to install a transport to use a button.
|
|
150
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
|
+
|
|
151
255
|
## The composer
|
|
152
256
|
|
|
153
257
|
The sheet, the screen and the Alia app share one composer, `AliaComposer`: Bloom's
|
|
154
258
|
`ComposerPill` with files by paste and drop (read with real progress, retried, refused with a
|
|
155
259
|
sentence), dictation, suggestions over the field and Alia's questions docked above it.
|
|
156
260
|
`AliaChatSheet` and `AliaChatScreen` render it with photos, voice and suggestions, and
|
|
157
|
-
`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
|
|
158
266
|
pass what the surface has:
|
|
159
267
|
|
|
160
268
|
```tsx
|
|
@@ -179,151 +287,129 @@ import { AliaComposer } from '@alia.onl/sdk';
|
|
|
179
287
|
/>;
|
|
180
288
|
```
|
|
181
289
|
|
|
182
|
-
##
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
one
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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:
|
|
226
348
|
|
|
227
349
|
```tsx
|
|
228
|
-
|
|
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
|
+
}
|
|
229
368
|
```
|
|
230
369
|
|
|
231
|
-
|
|
232
|
-
`POST /v1/chat/completions` and `GET /catalogue` under whatever base URL you
|
|
233
|
-
give it. Nothing about the stream changes: the SDK's parser reads the bytes Alia
|
|
234
|
-
wrote.
|
|
370
|
+
### Voice lives in the conversation
|
|
235
371
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
`
|
|
240
|
-
`packages/api/src/middleware/auth.ts`), which accepts an Oxy session token —
|
|
241
|
-
verified with Oxy, the same way it would be if the browser had called Alia
|
|
242
|
-
directly — and the turn is metered to that user's Alia entitlement, the same as
|
|
243
|
-
a direct call. The other credentials the middleware recognises are
|
|
244
|
-
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`):
|
|
245
376
|
|
|
246
|
-
|
|
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.
|
|
247
381
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const ALIA = 'https://api.alia.onl';
|
|
252
|
-
const app = express();
|
|
253
|
-
app.use(express.json({ limit: '1mb' }));
|
|
254
|
-
|
|
255
|
-
/** Relay one request to Alia and stream the answer back unchanged. */
|
|
256
|
-
async function relay(
|
|
257
|
-
req: express.Request,
|
|
258
|
-
res: express.Response,
|
|
259
|
-
method: 'GET' | 'POST',
|
|
260
|
-
path: string,
|
|
261
|
-
): Promise<void> {
|
|
262
|
-
const controller = new AbortController();
|
|
263
|
-
req.on('close', () => controller.abort());
|
|
264
|
-
|
|
265
|
-
const authorization = req.get('authorization');
|
|
266
|
-
const upstream = await fetch(`${ALIA}${path}`, {
|
|
267
|
-
method,
|
|
268
|
-
headers: {
|
|
269
|
-
...(authorization ? { Authorization: authorization } : {}),
|
|
270
|
-
Accept: req.get('accept') ?? 'application/json',
|
|
271
|
-
...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),
|
|
272
|
-
},
|
|
273
|
-
body: method === 'POST' ? JSON.stringify(req.body) : undefined,
|
|
274
|
-
signal: controller.signal,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
res.status(upstream.status);
|
|
278
|
-
res.setHeader('Content-Type', upstream.headers.get('content-type') ?? 'application/json');
|
|
279
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
280
|
-
res.setHeader('X-Accel-Buffering', 'no'); // keep proxies from buffering the SSE stream
|
|
281
|
-
res.flushHeaders();
|
|
282
|
-
|
|
283
|
-
if (upstream.body === null) {
|
|
284
|
-
res.end();
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
for await (const chunk of upstream.body) res.write(chunk);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
if (!controller.signal.aborted) throw error;
|
|
291
|
-
} finally {
|
|
292
|
-
res.end();
|
|
293
|
-
}
|
|
294
|
-
}
|
|
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.
|
|
295
384
|
|
|
296
|
-
|
|
297
|
-
app.post('/v1/chat/completions', (req, res) => relay(req, res, 'POST', '/alia/chat'));
|
|
385
|
+
## Which API the SDK calls
|
|
298
386
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
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.
|
|
303
390
|
|
|
304
|
-
|
|
305
|
-
|
|
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`.
|
|
306
397
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
311
402
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
— a CORS policy on `/alia/chat` for registered consumer origins, and the SDK
|
|
316
|
-
default moving to `/alia/chat` in a major — are recorded in #244: the first is
|
|
317
|
-
blocked on Oxy Applications carrying an origin registry (OxyHQ/oxy#972), and
|
|
318
|
-
the second is an adoption window rather than a switch, for the raw-source
|
|
319
|
-
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.
|
|
320
406
|
|
|
321
407
|
## Scripts
|
|
322
408
|
|
|
323
409
|
```sh
|
|
324
410
|
bun run typecheck # tsc --noEmit
|
|
325
411
|
bun run test # vitest
|
|
326
|
-
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
|
|
327
413
|
```
|
|
328
414
|
|
|
329
415
|
## Licence
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia.onl/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.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",
|
|
@@ -9,17 +10,26 @@
|
|
|
9
10
|
".": "./src/index.ts",
|
|
10
11
|
"./avatar": "./src/avatar.ts",
|
|
11
12
|
"./content": "./src/lib/content-utils.ts",
|
|
13
|
+
"./csp": "./src/csp.ts",
|
|
14
|
+
"./model": "./src/runtime/model.ts",
|
|
15
|
+
"./runtime": "./src/runtime/index.ts",
|
|
12
16
|
"./tools": "./src/tools.ts",
|
|
13
17
|
"./voice": "./src/voice.ts",
|
|
14
18
|
"./package.json": "./package.json"
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
17
21
|
"src",
|
|
22
|
+
"worklets",
|
|
18
23
|
"NOTICE",
|
|
19
24
|
"tsconfig.json",
|
|
20
25
|
"CHANGELOG.md"
|
|
21
26
|
],
|
|
22
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
|
+
},
|
|
23
33
|
"private": false,
|
|
24
34
|
"publishConfig": {
|
|
25
35
|
"access": "public"
|
|
@@ -28,10 +38,12 @@
|
|
|
28
38
|
"typecheck": "tsc --noEmit",
|
|
29
39
|
"test": "vitest run",
|
|
30
40
|
"check:entries": "node scripts/check-entry-isolation.mjs",
|
|
31
|
-
"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"
|
|
32
43
|
},
|
|
33
44
|
"dependencies": {
|
|
34
45
|
"clsx": "^2.1.1",
|
|
46
|
+
"react-native-markdown-display": "^7.0.2",
|
|
35
47
|
"socket.io-client": "^4.8.3",
|
|
36
48
|
"tailwind-merge": "^3.7.0",
|
|
37
49
|
"zustand": "^5.0.15"
|
|
@@ -43,6 +55,7 @@
|
|
|
43
55
|
"@oxy.so/bloom": ">=10.9.0",
|
|
44
56
|
"@oxy.so/core": ">=3.0.0",
|
|
45
57
|
"@oxy.so/services": ">=8.0.0",
|
|
58
|
+
"@tanstack/react-query": "^5.101",
|
|
46
59
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
47
60
|
"expo-blur": "^57.0.0",
|
|
48
61
|
"react-native-reanimated": "^4.5.0",
|
|
@@ -58,6 +71,8 @@
|
|
|
58
71
|
"nativewind": "^5.0.0-preview.3",
|
|
59
72
|
"expo-clipboard": "^57.0.0",
|
|
60
73
|
"expo-file-system": "^57.0.0",
|
|
74
|
+
"expo-web-browser": "^57.0.0",
|
|
75
|
+
"react-native-css": "^3.0.7",
|
|
61
76
|
"react-native-audio-api": "^0.13.6"
|
|
62
77
|
},
|
|
63
78
|
"peerDependenciesMeta": {
|
|
@@ -66,9 +81,6 @@
|
|
|
66
81
|
},
|
|
67
82
|
"expo-clipboard": {
|
|
68
83
|
"optional": true
|
|
69
|
-
},
|
|
70
|
-
"react-native-audio-api": {
|
|
71
|
-
"optional": true
|
|
72
84
|
}
|
|
73
85
|
},
|
|
74
86
|
"devDependencies": {
|
package/src/animations.ts
CHANGED