@alia.onl/sdk 21.1.0 → 21.3.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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # @alia.onl/sdk
2
2
 
3
+ ## 21.3.0
4
+
5
+ The SDK runs on Expo SDK 58 (React Native 0.88, React 19.3) as well as Expo 57.
6
+
7
+ ### Changed
8
+
9
+ - **Peers admit Expo SDK 58.** Every `expo-*` peer is `^57.0.0 || ^58.0.0`, `react-native` is
10
+ `^0.86.0 || ^0.88.0` and `react-native-gesture-handler` `^2.32.0 || ^3.3.0` (SDK 58 ships 3.3).
11
+ `@oxy.so/bloom` is `>=10.21.0`, the first Bloom whose `TextInput` ref props type-check under
12
+ React Native 0.88's strict TypeScript API.
13
+
14
+ ### Fixed
15
+
16
+ - **A picked image is read with `expo-file-system`'s `File` API** (`new File(uri).base64()`). The
17
+ root entry's `readAsStringAsync` is the legacy API, which throws at runtime since the package
18
+ moved it to `expo-file-system/legacy`, so attaching a photo on iOS or Android failed.
19
+ - **An edit and a regenerate call `AliaRuntimeHost.onTurnStart`**, like a send from the composer
20
+ or a retry: each sends a new turn. A host that judges what a turn does against the moment it
21
+ was sent (a host tool's staleness check) used to judge an edited or regenerated turn against the
22
+ turn before it. An edit that sends nothing (an unknown message, empty content) calls nothing.
23
+
24
+ ## 21.2.0
25
+
26
+ ### Added
27
+
28
+ - **`parseCatalogueTool` and `toolRendererKey` from `@alia.onl/sdk/tools`.** Which app a tool call
29
+ belongs to (`oxy_<app>__<tool>` / `self_<app>__<tool>`) is now readable without loading React
30
+ Native, so a host's own pure models and tests — the code that decides what a screen shows —
31
+ can tell its catalogue's results apart. The main entry exports the same two functions as before.
32
+
3
33
  ## 21.1.0
4
34
 
5
35
  A shared conversation has one address, Alia's, whichever app minted it (`docs/agent-embedding.md`
package/README.md CHANGED
@@ -357,6 +357,8 @@ const host: AliaRuntimeHost = {
357
357
  </AliaRuntimeProvider>;
358
358
  ```
359
359
 
360
+ `onTurnStart` is called just before each turn the runtime sends — from the composer, a retry,
361
+ an edit or a regenerate — so a host can take what the turn will be judged against at that send.
360
362
  `onTurnEnd` is called exactly once for each turn the runtime sends, when it stops streaming —
361
363
  the answer was written, the person pressed Stop, or the turn failed — and only while the
362
364
  conversation that sent it is still mounted. A host that holds back what would take the chat off
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "21.1.0",
3
+ "version": "21.3.0",
4
4
  "description": "Alia AI chat SDK — the conversation runtime, chat UI, voice calls and dictation for Oxy apps",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -50,28 +50,28 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "^19.2.3",
53
- "react-native": "^0.86.0",
53
+ "react-native": "^0.86.0 || ^0.88.0",
54
54
  "@expo/vector-icons": "^15.1.1",
55
- "@oxy.so/bloom": ">=10.17.0",
55
+ "@oxy.so/bloom": ">=10.21.0",
56
56
  "@oxy.so/core": ">=3.0.0",
57
57
  "@oxy.so/services": ">=11.12.0",
58
58
  "@tanstack/react-query": "^5.101",
59
59
  "@react-native-async-storage/async-storage": "^2.2.0",
60
- "expo-blur": "^57.0.0",
60
+ "expo-blur": "^57.0.0 || ^58.0.0",
61
61
  "react-native-reanimated": "^4.5.0",
62
- "react-native-gesture-handler": "^2.32.0",
62
+ "react-native-gesture-handler": "^2.32.0 || ^3.3.0",
63
63
  "react-native-safe-area-context": "^5.8.0",
64
64
  "react-native-keyboard-controller": "^1.21.9",
65
65
  "react-native-svg": "^15.15.5",
66
- "expo-audio": "^57.0.0",
67
- "expo-image": "^57.0.0",
68
- "expo-image-picker": "^57.0.2",
69
- "expo-document-picker": "^57.0.0",
70
- "expo-haptics": "^57.0.0",
66
+ "expo-audio": "^57.0.0 || ^58.0.0",
67
+ "expo-image": "^57.0.0 || ^58.0.0",
68
+ "expo-image-picker": "^57.0.2 || ^58.0.0",
69
+ "expo-document-picker": "^57.0.0 || ^58.0.0",
70
+ "expo-haptics": "^57.0.0 || ^58.0.0",
71
71
  "nativewind": "^5.0.0-preview.3",
72
- "expo-clipboard": "^57.0.0",
73
- "expo-file-system": "^57.0.0",
74
- "expo-web-browser": "^57.0.0",
72
+ "expo-clipboard": "^57.0.0 || ^58.0.0",
73
+ "expo-file-system": "^57.0.0 || ^58.0.0",
74
+ "expo-web-browser": "^57.0.0 || ^58.0.0",
75
75
  "react-native-css": "^3.0.7",
76
76
  "react-native-audio-api": "^0.13.6"
77
77
  },
@@ -85,7 +85,7 @@
85
85
  },
86
86
  "devDependencies": {
87
87
  "@types/react-test-renderer": "^19.1.0",
88
- "react-test-renderer": "19.2.3",
88
+ "react-test-renderer": "19.3.0",
89
89
  "typescript": "^5.9.3",
90
90
  "vitest": "^4.1.10",
91
91
  "react-native-audio-api": "0.13.6",
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ export type { AliaParticipantsHeaderProps } from './components/AliaParticipantsH
17
17
  export { ALIA_LEAD, leadOf } from './lib/participants';
18
18
  export type { AliaChatParticipants, AliaLead, AliaParticipant } from './lib/participants';
19
19
  export { ANCHORS, leadToProfile } from './lib/flights';
20
- export { parseCatalogueTool, toolRendererKey } from './lib/tool-renderers';
20
+ export { parseCatalogueTool, toolRendererKey } from './lib/catalogue-tool';
21
21
  export type { AliaToolRenderers, AliaToolResult } from './lib/tool-renderers';
22
22
  export { AliaAskProvider, useAskAgent } from './components/AliaAskProvider';
23
23
  export type { AliaAsk, AliaAskAgents, AliaAskProviderProps } from './components/AliaAskProvider';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The canonical name of an app's catalogue tool — `oxy_<app>__<tool>` for the person's
3
+ * account, `self_<app>__<tool>` for the agent's own — read back into its app and tool.
4
+ *
5
+ * Pure (no React, no React Native), so a host's own models and tests can tell which
6
+ * app a tool call belongs to: it is the `@alia.onl/sdk/tools` entry's, and the renderers
7
+ * (`tool-renderers.tsx`) key on it. The app part is an Oxy app slug; the tool part is
8
+ * whatever follows the separator. Nothing else names a renderer — never the model's text.
9
+ */
10
+ const CANONICAL_TOOL = /^(?:oxy|self)_([a-z][a-z0-9-]{0,63})__([A-Za-z0-9_-]+)$/;
11
+
12
+ /** The app and tool of a canonical catalogue tool name, or `null` for any other name. */
13
+ export function parseCatalogueTool(toolName: string): { app: string; tool: string } | null {
14
+ const match = CANONICAL_TOOL.exec(toolName);
15
+ return match ? { app: match[1], tool: match[2] } : null;
16
+ }
17
+
18
+ /** The renderer key of a tool name (`<app>:<tool>`), or `null` when it is not a catalogue tool. */
19
+ export function toolRendererKey(toolName: string): string | null {
20
+ const parsed = parseCatalogueTool(toolName);
21
+ return parsed ? `${parsed.app}:${parsed.tool}` : null;
22
+ }
@@ -1,4 +1,4 @@
1
- import * as FileSystem from 'expo-file-system';
1
+ import { File } from 'expo-file-system';
2
2
  import { Platform } from 'react-native';
3
3
 
4
4
  /**
@@ -12,9 +12,8 @@ import { Platform } from 'react-native';
12
12
  export async function imageUriToDataUrl(uri: string, mimeType?: string): Promise<string> {
13
13
  if (uri.startsWith('data:')) return uri;
14
14
  if (Platform.OS !== 'web') {
15
- const base64 = await FileSystem.readAsStringAsync(uri, {
16
- encoding: FileSystem.EncodingType.Base64,
17
- });
15
+ // The `File` API: the legacy `readAsStringAsync` from the package root throws at runtime.
16
+ const base64 = await new File(uri).base64();
18
17
  return `data:${mimeType || 'image/jpeg'};base64,${base64}`;
19
18
  }
20
19
  const blob = await (await fetch(uri)).blob();
@@ -4,6 +4,7 @@ import type { TextInput } from 'react-native';
4
4
  /**
5
5
  * What a ref to a `TextInput` holds. `ComponentRef`, not `TextInput` itself: React Native 0.88
6
6
  * types `TextInput` as a function component, so the class is no longer its instance type, and
7
- * `focus()` is only on the instance.
7
+ * `focus()` is only on the instance. An interface rather than an alias, so a declaration that
8
+ * returns one can name it: on 0.88 the instance type lives in a file React Native does not export.
8
9
  */
9
- export type TextInputHandle = ComponentRef<typeof TextInput>;
10
+ export interface TextInputHandle extends ComponentRef<typeof TextInput> {}
@@ -22,23 +22,7 @@ export interface AliaToolResult {
22
22
  */
23
23
  export type AliaToolRenderers = Readonly<Record<string, (result: AliaToolResult) => ReactNode>>;
24
24
 
25
- /**
26
- * `oxy_<app>__<tool>` or `self_<app>__<tool>`: the only names a renderer is keyed from. The app
27
- * part is an Oxy app slug; the tool part is whatever follows the separator.
28
- */
29
- const CANONICAL_TOOL = /^(?:oxy|self)_([a-z][a-z0-9-]{0,63})__([A-Za-z0-9_-]+)$/;
30
-
31
- /** The app and tool of a canonical catalogue tool name, or `null` for any other name. */
32
- export function parseCatalogueTool(toolName: string): { app: string; tool: string } | null {
33
- const match = CANONICAL_TOOL.exec(toolName);
34
- return match ? { app: match[1], tool: match[2] } : null;
35
- }
36
-
37
- /** The renderer key of a tool name (`<app>:<tool>`), or `null` when it is not a catalogue tool. */
38
- export function toolRendererKey(toolName: string): string | null {
39
- const parsed = parseCatalogueTool(toolName);
40
- return parsed ? `${parsed.app}:${parsed.tool}` : null;
41
- }
25
+ export { parseCatalogueTool, toolRendererKey } from './catalogue-tool';
42
26
 
43
27
  const RenderersContext = createContext<AliaToolRenderers>({});
44
28
 
@@ -70,7 +70,7 @@ export interface AliaRuntimeHost {
70
70
  deviceInfo?: () => Promise<unknown>;
71
71
  /** The modes the host's composer controls own; read when the turn is sent, not when it is built. */
72
72
  turnRequest?: () => AliaTurnRequest;
73
- /** A turn is about to be sent from the composer or a retry. */
73
+ /** A turn is about to be sent: from the composer, a retry, an edit or a regenerate. */
74
74
  onTurnStart?: () => void;
75
75
  /**
76
76
  * A turn this runtime sent has stopped streaming: it finished, was stopped, or failed. Called
@@ -394,6 +394,9 @@ export function useConversationThread({
394
394
  }
395
395
  if (isEmptyContent(content)) return false;
396
396
 
397
+ // An edit sends a new turn (so does a regenerate, through here).
398
+ host.onTurnStart?.();
399
+
397
400
  setMessages((prev) => {
398
401
  const idx = prev.findIndex((msg) => msg.id === messageId);
399
402
  return idx < 0 ? prev : prev.slice(0, idx);
@@ -420,7 +423,7 @@ export function useConversationThread({
420
423
  releaseAttachments(attachments ?? []);
421
424
  return true;
422
425
  },
423
- [setMessages, append, messages, conversationId, labels, upload],
426
+ [setMessages, append, messages, conversationId, host, labels, upload],
424
427
  );
425
428
 
426
429
  /** The user turn a reply answers: the nearest one before it, or `undefined`. */
package/src/tools.ts CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  normalizeCitationMarkers,
10
10
  } from './lib/citations';
11
11
  export { domainOf } from './lib/domain';
12
+ export { parseCatalogueTool, toolRendererKey } from './lib/catalogue-tool';
12
13
  export type { CitationSource } from './lib/citations';
13
14
  export { getToolDoneLabel, getToolPillLabel } from './lib/task-labels';
14
15
  export {