@alia.onl/sdk 22.5.0 → 22.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @alia.onl/sdk
2
2
 
3
+ ## 22.7.0
4
+
5
+ - **Image results for awaited host tools.** An awaited tool's `execute` may answer
6
+ `result: { type: "image", mediaType, data, text? }` (`AliaHostImageResult`: `image/jpeg`,
7
+ `image/png` or `image/webp`, `data` in standard base64 with no `data:` prefix, an optional
8
+ caption of at most `HOST_IMAGE_TEXT_MAX_CHARS`, 4,000). The model sees the image itself in that
9
+ turn; the stored conversation and the `alia.tool_result` frame keep
10
+ `{ type, mediaType, text?, omitted: true }` instead. The whole result still counts toward
11
+ `HOST_RESULT_MAX_CHARS` (100,000 characters as JSON), so size a screenshot to fit.
12
+ - A result that claims `type: "image"` but is malformed is answered `failed` with the reason
13
+ (`hostImageResultProblem`), instead of being sent for the API to refuse while the model waits.
14
+ - Needs the Alia API that ships with this release (`docs/agent-embedding.md` §2); an older API
15
+ passes an image result to the model as JSON text.
16
+
17
+ ## 22.6.0
18
+
19
+ - **`@alia.onl/sdk/dictation`: dictation's request on its own.** `requestTranscription`,
20
+ `DictationError`, `dictationFailureCode`, `normalizeAudioMediaType` and `MAX_DICTATION_MS`, the
21
+ same functions `useDictation` and `AliaComposer` use, from an entry that reaches no React, no
22
+ native module, no recorder and no Oxy client (`check:entries` holds it). An app that records
23
+ its own audio (Astro's browser shell) sends its clips from here instead of keeping a copy of
24
+ the request. Nothing changes for the root entry, which still exports `requestTranscription`
25
+ and `DictationError`.
26
+
3
27
  ## 22.5.0
4
28
 
5
29
  - **`AliaApi.put`.** The REST API (`useAliaApi`, `createAliaApi`) sends PUT like PATCH: the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "22.5.0",
3
+ "version": "22.7.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",
@@ -13,6 +13,7 @@
13
13
  "./avatar": "./src/avatar.ts",
14
14
  "./content": "./src/lib/content-utils.ts",
15
15
  "./csp": "./src/csp.ts",
16
+ "./dictation": "./src/dictation.ts",
16
17
  "./model": "./src/runtime/model.ts",
17
18
  "./runtime": "./src/runtime/index.ts",
18
19
  "./text": "./src/text.ts",
@@ -0,0 +1,20 @@
1
+ // @alia.onl/sdk/dictation — dictation's request, without a component, a hook or a recorder
2
+ // `POST /v1/audio/transcriptions` (ADR 0017 §7) as plain functions: an app that records audio its
3
+ // own way (Astro's browser shell) sends its clips from here and reaches no React, no native module
4
+ // and no Oxy client. `useDictation` and `AliaComposer` use the same request.
5
+
6
+ export {
7
+ DictationError,
8
+ dictationFailureCode,
9
+ MAX_DICTATION_MS,
10
+ normalizeAudioMediaType,
11
+ requestTranscription,
12
+ } from './lib/dictation';
13
+ export type {
14
+ DictationClip,
15
+ DictationErrorCode,
16
+ TranscriptionRequest,
17
+ TranscriptionResult,
18
+ } from './lib/dictation';
19
+ export type { AudioRequestAuthentication } from './lib/audio-request-auth';
20
+ export type { VoiceErrorCode } from './lib/speech-messages';
@@ -9,7 +9,8 @@
9
9
  *
10
10
  * A tool declared with `awaitsResult: true` is read in the SAME turn instead ("this tab's text",
11
11
  * "the open tabs"): the server waits, and this client answers it at once on the turn's
12
- * `host-results` route with the outcome and the `result` its `execute` returned.
12
+ * `host-results` route with the outcome and the `result` its `execute` returned — text, JSON or
13
+ * an image ({@link AliaHostImageResult}) the model sees as one.
13
14
  *
14
15
  * Three rules hold here, in a queue that knows nothing of React:
15
16
  * - an action is performed at most once, whatever the stream repeats;
@@ -45,11 +46,61 @@ export interface AliaHostOutcome {
45
46
  detail?: string;
46
47
  /**
47
48
  * What an awaited tool read, for the model: a string or any JSON value, at most
48
- * {@link HOST_RESULT_MAX_CHARS} characters as JSON. Ignored for a tool that does not await.
49
+ * {@link HOST_RESULT_MAX_CHARS} characters as JSON, or an {@link AliaHostImageResult} the model
50
+ * sees as an image. Ignored for a tool that does not await.
49
51
  */
50
52
  result?: unknown;
51
53
  }
52
54
 
55
+ export const HOST_IMAGE_MEDIA_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;
56
+ export type AliaHostImageMediaType = (typeof HOST_IMAGE_MEDIA_TYPES)[number];
57
+
58
+ /** The longest caption an image result may carry. */
59
+ export const HOST_IMAGE_TEXT_MAX_CHARS = 4_000;
60
+
61
+ /**
62
+ * An awaited tool's image (a tab's screenshot): the model sees the image itself in that turn, and
63
+ * the stored conversation keeps `{ type, mediaType, text?, omitted: true }` in its place.
64
+ * `data` is the image's bytes in standard base64, with no `data:` prefix; the whole result counts
65
+ * toward {@link HOST_RESULT_MAX_CHARS}, so size the image to fit (a JPEG of the viewport).
66
+ */
67
+ export interface AliaHostImageResult {
68
+ type: 'image';
69
+ mediaType: AliaHostImageMediaType;
70
+ data: string;
71
+ /** A caption for the model (the tab's URL and title), at most {@link HOST_IMAGE_TEXT_MAX_CHARS}. */
72
+ text?: string;
73
+ }
74
+
75
+ const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
76
+ const IMAGE_KEYS = ['type', 'mediaType', 'data', 'text'];
77
+
78
+ /**
79
+ * Why a result that claims to be an image (`type: "image"`) is not a valid one, or `null`. The
80
+ * server refuses such an answer, which would leave the model waiting until the action goes stale;
81
+ * the runtime answers `failed` with this reason instead.
82
+ */
83
+ export function hostImageResultProblem(result: unknown): string | null {
84
+ if (typeof result !== 'object' || result === null || Array.isArray(result)) return null;
85
+ const image = result as Record<string, unknown>;
86
+ if (image.type !== 'image') return null;
87
+ const extra = Object.keys(image).find((key) => !IMAGE_KEYS.includes(key));
88
+ if (extra !== undefined) return `An image result has no "${extra}".`;
89
+ if (!HOST_IMAGE_MEDIA_TYPES.includes(image.mediaType as AliaHostImageMediaType)) {
90
+ return `An image result's mediaType is one of ${HOST_IMAGE_MEDIA_TYPES.join(', ')}.`;
91
+ }
92
+ if (typeof image.data !== 'string' || image.data === '' || !BASE64.test(image.data)) {
93
+ return "An image result's data is standard base64 with no data: prefix.";
94
+ }
95
+ if (
96
+ image.text !== undefined &&
97
+ (typeof image.text !== 'string' || image.text.length > HOST_IMAGE_TEXT_MAX_CHARS)
98
+ ) {
99
+ return `An image result's text is a string of at most ${HOST_IMAGE_TEXT_MAX_CHARS} characters.`;
100
+ }
101
+ return null;
102
+ }
103
+
53
104
  /**
54
105
  * The host tools a surface declares (`hostTools` on the chat). They are the tools of the chat's
55
106
  * `app`, which a surface with host tools must name: the app is said once, on the chat, and is what
@@ -96,8 +147,10 @@ const DETAIL_LIMIT = 500;
96
147
  /** The server's bound on an awaited action's `result`, as JSON. */
97
148
  export const HOST_RESULT_MAX_CHARS = 100_000;
98
149
 
99
- function resultOf(result: unknown): { result?: unknown } | 'too_large' {
150
+ function resultOf(result: unknown): { result?: unknown } | 'too_large' | { invalid: string } {
100
151
  if (result === undefined || result === null) return {};
152
+ const problem = hostImageResultProblem(result);
153
+ if (problem !== null) return { invalid: problem };
101
154
  let text: string | undefined;
102
155
  try {
103
156
  text = JSON.stringify(result);
@@ -158,7 +211,9 @@ export class HostOutcomeQueue {
158
211
  outcome: 'failed',
159
212
  detail: 'The result was too large to send.',
160
213
  })
161
- : { ...record(action.toolCallId, outcome), ...result };
214
+ : 'invalid' in result
215
+ ? record(action.toolCallId, { outcome: 'failed', detail: result.invalid })
216
+ : { ...record(action.toolCallId, outcome), ...result };
162
217
  try {
163
218
  await send(answer);
164
219
  } catch (error) {
@@ -33,9 +33,19 @@ export {
33
33
  useAgentProfile,
34
34
  } from './agents';
35
35
  export type { AliaAgentFeature, AliaAgentProfile, AliaAgentStarter, FeatureOffer } from './agents';
36
- export { HOST_OUTCOMES, HOST_RESULT_MAX_CHARS, HostOutcomeQueue, hostRequest } from './host-tools';
36
+ export {
37
+ HOST_IMAGE_MEDIA_TYPES,
38
+ HOST_IMAGE_TEXT_MAX_CHARS,
39
+ HOST_OUTCOMES,
40
+ HOST_RESULT_MAX_CHARS,
41
+ HostOutcomeQueue,
42
+ hostImageResultProblem,
43
+ hostRequest,
44
+ } from './host-tools';
37
45
  export type {
38
46
  AliaHostAction,
47
+ AliaHostImageMediaType,
48
+ AliaHostImageResult,
39
49
  AliaHostOutcome,
40
50
  AliaHostOutcomeKind,
41
51
  AliaHostToolDefinition,