@alia.onl/sdk 10.0.3 → 10.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 +17 -0
- package/package.json +4 -4
- package/src/index.ts +2 -0
- package/src/lib/chat-stream.ts +62 -2
- package/src/lib/chat-transport.ts +32 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @alia.onl/sdk
|
|
2
2
|
|
|
3
|
+
## 10.1.0
|
|
4
|
+
|
|
5
|
+
A failed turn is a typed error. Alia now answers an inference failure before any
|
|
6
|
+
output with its error envelope (an in-stream error frame, or an HTTP 503/500)
|
|
7
|
+
instead of an "all models are busy" answer. `AliaChatStreamError` (now exported,
|
|
8
|
+
with `AliaChatFailure`) carries `status`, `code`, `retryable`, `retryAfter` and
|
|
9
|
+
`reference` from either. The stream reader also accepts `alia.context` and
|
|
10
|
+
`alia.agent_turn`, which it used to reject as unknown events, ending a streaming
|
|
11
|
+
turn before its answer or its error could be read.
|
|
12
|
+
|
|
13
|
+
## 10.0.4
|
|
14
|
+
|
|
15
|
+
Admit `@oxy.so/services` 11 and `@oxy.so/core` 4 alongside the earlier majors.
|
|
16
|
+
Services 11 / core 4 move Android identity to Commons over signature-protected
|
|
17
|
+
IPC (OxyHQ/oxy#1388); the SDK imports only `useOxy` from
|
|
18
|
+
`@oxy.so/services/ui/client`, unchanged, and nothing from core. No code changed.
|
|
19
|
+
|
|
3
20
|
## 10.0.3
|
|
4
21
|
|
|
5
22
|
Admit `@oxy.so/services` 10 alongside 8 and 9. The SDK uses the unchanged
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia.onl/sdk",
|
|
3
|
-
"version": "10.0
|
|
4
|
-
"description": "Alia AI chat SDK
|
|
3
|
+
"version": "10.1.0",
|
|
4
|
+
"description": "Alia AI chat SDK \u2014 reusable chat UI, voice, and streaming components for Oxy apps",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
7
7
|
"source": "src/index.ts",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": "^19.2.3",
|
|
44
44
|
"react-native": "^0.86.0",
|
|
45
|
-
"@oxy.so/core": "^3.0.0",
|
|
46
|
-
"@oxy.so/services": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
|
45
|
+
"@oxy.so/core": "^3.0.0 || ^4.0.0",
|
|
46
|
+
"@oxy.so/services": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
47
47
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
48
48
|
"expo-blur": "^57.0.0",
|
|
49
49
|
"react-native-reanimated": "^4.5.0",
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export { PlanPreviewCard } from './components/PlanPreviewCard';
|
|
|
22
22
|
// ── Chat hook ──
|
|
23
23
|
export { useAliaChat } from './hooks/useAliaChat';
|
|
24
24
|
export type { UseAliaChatOptions, UseAliaChatReturn } from './hooks/useAliaChat';
|
|
25
|
+
export { AliaChatStreamError } from './lib/chat-stream';
|
|
26
|
+
export type { AliaChatFailure } from './lib/chat-stream';
|
|
25
27
|
|
|
26
28
|
// ── Text-to-speech / speech-to-text (no livekit) ──
|
|
27
29
|
export { useTTS } from './hooks/useTTS';
|
package/src/lib/chat-stream.ts
CHANGED
|
@@ -38,13 +38,61 @@ export interface AliaChatStreamResult {
|
|
|
38
38
|
readonly finishReason: string;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* What Alia said about a failed turn, when it said anything.
|
|
43
|
+
*
|
|
44
|
+
* A turn that fails — before any output or part-way — ends with Alia's
|
|
45
|
+
* OpenAI-shaped error envelope: an in-stream `{"error": …}` frame, or an HTTP
|
|
46
|
+
* error status with the same body when nothing was sent yet. `code` is Alia's
|
|
47
|
+
* stable error code, `retryable` whether sending the same turn again can help,
|
|
48
|
+
* `retryAfter` the seconds to wait first, and `reference` the run id support
|
|
49
|
+
* traces it by. None of it names the serving operator.
|
|
50
|
+
*/
|
|
51
|
+
export interface AliaChatFailure {
|
|
52
|
+
readonly status?: number;
|
|
53
|
+
readonly code?: string;
|
|
54
|
+
readonly retryable?: boolean;
|
|
55
|
+
readonly retryAfter?: number;
|
|
56
|
+
readonly reference?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
41
59
|
export class AliaChatStreamError extends Error {
|
|
42
|
-
|
|
60
|
+
readonly status?: number;
|
|
61
|
+
readonly code?: string;
|
|
62
|
+
readonly retryable?: boolean;
|
|
63
|
+
readonly retryAfter?: number;
|
|
64
|
+
readonly reference?: string;
|
|
65
|
+
|
|
66
|
+
constructor(message: string, failure: AliaChatFailure = {}) {
|
|
43
67
|
super(message);
|
|
44
68
|
this.name = 'AliaChatStreamError';
|
|
69
|
+
if (failure.status !== undefined) this.status = failure.status;
|
|
70
|
+
if (failure.code !== undefined) this.code = failure.code;
|
|
71
|
+
if (failure.retryable !== undefined) this.retryable = failure.retryable;
|
|
72
|
+
if (failure.retryAfter !== undefined) this.retryAfter = failure.retryAfter;
|
|
73
|
+
if (failure.reference !== undefined) this.reference = failure.reference;
|
|
45
74
|
}
|
|
46
75
|
}
|
|
47
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Read the failure fields off Alia's error envelope, leniently: a field of the
|
|
79
|
+
* wrong type is left out rather than failing the read of the rest.
|
|
80
|
+
*/
|
|
81
|
+
export function readAliaChatFailure(body: unknown): AliaChatFailure {
|
|
82
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body)) return {};
|
|
83
|
+
const error = (body as JsonObject).error;
|
|
84
|
+
if (typeof error !== 'object' || error === null || Array.isArray(error)) return {};
|
|
85
|
+
const detail = error as JsonObject;
|
|
86
|
+
return {
|
|
87
|
+
...(typeof detail.code === 'string' && detail.code !== '' ? { code: detail.code } : {}),
|
|
88
|
+
...(typeof detail.retryable === 'boolean' ? { retryable: detail.retryable } : {}),
|
|
89
|
+
...(typeof detail.retryAfter === 'number' && Number.isFinite(detail.retryAfter) && detail.retryAfter >= 0
|
|
90
|
+
? { retryAfter: detail.retryAfter }
|
|
91
|
+
: {}),
|
|
92
|
+
...(typeof detail.reference === 'string' && detail.reference !== '' ? { reference: detail.reference } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
48
96
|
function abortError(): Error {
|
|
49
97
|
const error = new Error('The Alia stream was aborted.');
|
|
50
98
|
error.name = 'AbortError';
|
|
@@ -177,6 +225,16 @@ function validateIgnoredNamedEvent(eventName: string, payload: JsonObject): void
|
|
|
177
225
|
requireEventVersion(payload, eventName);
|
|
178
226
|
|
|
179
227
|
switch (eventName) {
|
|
228
|
+
// Sent on every streaming turn before inference starts (the context-window
|
|
229
|
+
// breakdown) and on a linked agent's turn. Neither is something this SDK
|
|
230
|
+
// shows, and an unknown one ended the turn before its answer — or its
|
|
231
|
+
// error — could be read.
|
|
232
|
+
case 'alia.context':
|
|
233
|
+
return;
|
|
234
|
+
case 'alia.agent_turn':
|
|
235
|
+
asString(payload.turnId, `${eventName} turnId`);
|
|
236
|
+
asString(payload.agentId, `${eventName} agentId`);
|
|
237
|
+
return;
|
|
180
238
|
case 'alia.suggest_new_conversation':
|
|
181
239
|
assertOnlyKeys(payload, ['eventVersion', 'reason'], eventName);
|
|
182
240
|
asString(payload.reason, `${eventName} reason`);
|
|
@@ -318,7 +376,9 @@ function parseOpenAIFrame(payload: unknown): ParsedOpenAIFrame {
|
|
|
318
376
|
if (body.error !== undefined) {
|
|
319
377
|
const error = asObject(body.error, 'OpenAI stream error');
|
|
320
378
|
asString(error.message, 'OpenAI stream error message');
|
|
321
|
-
|
|
379
|
+
// The server's prose is not carried: the code and retryability are what a
|
|
380
|
+
// caller acts on, and the caller words it in its own language.
|
|
381
|
+
throw new AliaChatStreamError('Alia ended the stream with an error.', readAliaChatFailure(body));
|
|
322
382
|
}
|
|
323
383
|
assertOnlyKeys(
|
|
324
384
|
body,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AliaChatStreamError,
|
|
2
3
|
consumeAliaChatStream,
|
|
4
|
+
readAliaChatFailure,
|
|
3
5
|
type AliaChatStreamEvent,
|
|
4
6
|
type AliaChatStreamResult,
|
|
5
7
|
} from './chat-stream';
|
|
@@ -59,9 +61,37 @@ export async function streamAliaChat(
|
|
|
59
61
|
});
|
|
60
62
|
|
|
61
63
|
if (!response.ok) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
throw new AliaChatStreamError(`Alia request failed with status ${response.status}.`, {
|
|
65
|
+
...(await readErrorEnvelope(response)),
|
|
66
|
+
status: response.status,
|
|
67
|
+
});
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
return consumeAliaChatStream(response, onEvent, signal);
|
|
67
71
|
}
|
|
72
|
+
|
|
73
|
+
/** Bodies larger than this are not an Alia error envelope and are not read. */
|
|
74
|
+
const MAX_ERROR_BODY_BYTES = 16 * 1024;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The code, retryability and reference of an HTTP refusal or failed turn.
|
|
78
|
+
*
|
|
79
|
+
* Without them a caller cannot tell "send it again" (a 503 for a turn that
|
|
80
|
+
* failed before any output) from "this will not work" (a 400), so the status
|
|
81
|
+
* is not enough. A body that is large, not JSON or not the envelope yields
|
|
82
|
+
* nothing, and the status alone still reaches the caller.
|
|
83
|
+
*/
|
|
84
|
+
async function readErrorEnvelope(response: Response) {
|
|
85
|
+
const length = Number(response.headers.get('content-length') ?? '0');
|
|
86
|
+
if (Number.isFinite(length) && length > MAX_ERROR_BODY_BYTES) {
|
|
87
|
+
await response.body?.cancel().catch(() => undefined);
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const text = await response.text();
|
|
92
|
+
if (text.length > MAX_ERROR_BODY_BYTES) return {};
|
|
93
|
+
return readAliaChatFailure(JSON.parse(text));
|
|
94
|
+
} catch {
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
}
|