@alia.onl/sdk 22.3.0 → 22.4.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,17 @@
1
1
  # @alia.onl/sdk
2
2
 
3
+ ## 22.4.0
4
+
5
+ - **A shared conversation shows its artifacts.** `readSharedConversation` keeps an assistant
6
+ message's `artifacts` (`AliaSharedArtifact`: `{ id, type, title, data }`, the canvas tool's
7
+ result) and `sharedMessages` hands each to the thread as the canvas call that drew it.
8
+ `AliaSharedConversation` draws them inline under their reply on the canvas renderers (reports,
9
+ decks, dashboards, charts, tables). An `AliaThread` that is not `readOnly` is unchanged: in the
10
+ app an artifact still opens in the canvas panel, not in the reply.
11
+ - The server sends `artifacts` only from the API that ships with this release
12
+ (`docs/agent-embedding.md` §6); against an older API the shared page shows words and images,
13
+ as before.
14
+
3
15
  ## 22.3.0
4
16
 
5
17
  - **Decks and dashboards on the canvas.** `CanvasComponent` draws the canvas tool's two new
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "22.3.0",
3
+ "version": "22.4.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",
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * A conversation somebody shared (`GET /v1/shared/:token`, `docs/agent-embedding.md` §6), read
3
3
  * by anybody with the link, signed in or not. It is the SDK's own thread (`AliaThread`), read-only:
4
- * words, pictures and who wrote them — the server never sends a tool's output, which can hold the
5
- * person's own data — and nothing to vote on, play, edit or regenerate.
4
+ * words, pictures, who wrote them and the canvas artifacts Alia drew (a report, deck or dashboard,
5
+ * drawn inline because there is no canvas panel here). The server sends no other tool's output,
6
+ * which can hold the person's own data, and there is nothing to vote on, play, edit or regenerate.
6
7
  */
7
8
 
8
9
  import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
@@ -24,6 +24,7 @@ import { readAgentCharacter } from '../../lib/agent-character';
24
24
  import { getImagesFromContent, messageText } from '../../lib/content-utils';
25
25
  import { ANCHORS, useAliaFlights } from '../../lib/flights';
26
26
  import { useLabels } from '../../lib/labels';
27
+ import { canvasResultOf } from '../../lib/canvas-result';
27
28
  import { askedQuestions, isQuestionInvocation } from '../../lib/questions';
28
29
  import { parseCatalogueTool, useToolRenderers } from '../../lib/tool-renderers';
29
30
  import { isWebInvocation, taskListLog, webSearchLog } from '../../lib/work-log';
@@ -33,6 +34,7 @@ import type { OpenThought } from '../../hooks/useOpenThought';
33
34
  import { Image } from '../Image';
34
35
  import { PlanPreviewCard } from '../PlanPreviewCard';
35
36
  import { AskedQuestions } from '../QuestionCard';
37
+ import { CanvasComponent } from '../canvas/CanvasComponent';
36
38
  import { ReplyMarkdown } from '../ReplyMarkdown';
37
39
  import { MessageBlockBoundary } from './MessageBlockBoundary';
38
40
  import { ToolResultCard } from './ToolResultCard';
@@ -240,7 +242,20 @@ function ReplyTurn({
240
242
  },
241
243
  ];
242
244
  });
243
- const renderedCalls = new Set(rendered.map(({ call }) => call));
245
+
246
+ // A shared conversation has no canvas panel to open an artifact in, so it draws each one inline.
247
+ const inlineCanvases = readOnly
248
+ ? toolInvocations.flatMap((call, index) => {
249
+ const canvas = canvasResultOf(call);
250
+ return canvas === null
251
+ ? []
252
+ : [{ key: call.toolCallId || `canvas-${message.id}-${index}`, call, canvas }];
253
+ })
254
+ : [];
255
+ const renderedCalls = new Set([
256
+ ...rendered.map(({ call }) => call),
257
+ ...inlineCanvases.map(({ call }) => call),
258
+ ]);
244
259
 
245
260
  // The calls the work summary lists: every one that did not draw its own card. Read on each
246
261
  // render — the list changes per streamed tool event and the row is memoised on `message`.
@@ -320,6 +335,7 @@ function ReplyTurn({
320
335
  hasWorkLog ||
321
336
  toolCards.length > 0 ||
322
337
  rendered.length > 0 ||
338
+ inlineCanvases.length > 0 ||
323
339
  (message.isStreaming === true && message.source === 'voice');
324
340
  /** Its avatar is the source of the flight into the header while it is answering. */
325
341
  const agentLive = agent !== undefined && message.isStreaming === true;
@@ -420,6 +436,13 @@ function ReplyTurn({
420
436
  {rendered.map(({ key, node }) => (
421
437
  <MessageBlockBoundary key={key}>{node}</MessageBlockBoundary>
422
438
  ))}
439
+ {inlineCanvases.map(({ key, canvas }) => (
440
+ <MessageBlockBoundary key={key}>
441
+ <View className="rounded-2xl border border-border p-4" testID="shared-canvas">
442
+ <CanvasComponent component={{ id: key, ...canvas }} />
443
+ </View>
444
+ </MessageBlockBoundary>
445
+ ))}
423
446
  {/* Each card in its own boundary: `cardOf` checks the card's NAME, and its data is
424
447
  cast unchecked. */}
425
448
  {toolCards.map(({ key, card }) => (
@@ -0,0 +1,21 @@
1
+ import type { ToolInvocation } from '../runtime/types';
2
+
3
+ /** A canvas artifact as `CanvasComponent` draws it. */
4
+ export interface CanvasResult {
5
+ type: string;
6
+ title: string;
7
+ data: unknown;
8
+ }
9
+
10
+ /**
11
+ * A finished canvas call's artifact; `null` for any other call, one still running, or one the tool
12
+ * refused (`{ error }`). An artifact without a title is titled by its type.
13
+ */
14
+ export function canvasResultOf(call: ToolInvocation): CanvasResult | null {
15
+ if (call.toolName !== 'canvas' || call.state !== 'result') return null;
16
+ const result: unknown = call.result;
17
+ if (result === null || typeof result !== 'object') return null;
18
+ const { type, title, data } = result as Record<string, unknown>;
19
+ if (typeof type !== 'string' || type === '' || data === undefined || data === null) return null;
20
+ return { type, title: typeof title === 'string' && title !== '' ? title : type, data };
21
+ }
@@ -5,7 +5,7 @@ import { useAliaApi } from './api';
5
5
  import { useAliaRuntime } from './config';
6
6
  import { aliaQueryKeys } from './query-keys';
7
7
  import { API_ROUTES } from './routes';
8
- import type { Message } from './types';
8
+ import type { Message, ToolInvocation } from './types';
9
9
 
10
10
  /** A conversation's read-only link (`POST /v1/conversations/:id/share`, `docs/agent-embedding.md` §6). */
11
11
  export interface AliaConversationShare {
@@ -65,11 +65,26 @@ export function useShareConversation() {
65
65
  );
66
66
  }
67
67
 
68
- /** A message of a shared conversation: words and pictures, and who wrote them. Never a tool's output. */
68
+ /**
69
+ * A canvas artifact of a shared conversation: the report, deck, dashboard, chart or table Alia drew,
70
+ * as the canvas tool returned it. `id` is the tool call that drew it.
71
+ */
72
+ export interface AliaSharedArtifact {
73
+ id: string;
74
+ type: string;
75
+ title: string;
76
+ data: unknown;
77
+ }
78
+
79
+ /**
80
+ * A message of a shared conversation: words and pictures, who wrote them, and the canvas artifacts
81
+ * it drew. Never any other tool's output.
82
+ */
69
83
  export interface AliaSharedMessage {
70
84
  role: 'user' | 'assistant';
71
85
  content: string | Array<{ type: string; [key: string]: unknown }>;
72
86
  agent?: AliaSharedAgent;
87
+ artifacts?: AliaSharedArtifact[];
73
88
  }
74
89
 
75
90
  export interface AliaSharedAgent {
@@ -100,6 +115,18 @@ function readSharedAgent(value: unknown): AliaSharedAgent | undefined {
100
115
  };
101
116
  }
102
117
 
118
+ /** The artifacts the server sent, each kept only with an id, a type, a title and its data. */
119
+ function readSharedArtifacts(value: unknown): AliaSharedArtifact[] {
120
+ if (!Array.isArray(value)) return [];
121
+ return value.flatMap((artifact): AliaSharedArtifact[] => {
122
+ if (artifact === null || typeof artifact !== 'object') return [];
123
+ const { id, type, title, data } = artifact as Record<string, unknown>;
124
+ if (typeof id !== 'string' || typeof type !== 'string' || type === '') return [];
125
+ if (data === undefined || data === null) return [];
126
+ return [{ id, type, title: typeof title === 'string' ? title : '', data }];
127
+ });
128
+ }
129
+
103
130
  /** The shared conversation as the contract names it; any message of another shape is left out. */
104
131
  export function readSharedConversation(value: unknown): AliaSharedConversationData | null {
105
132
  if (value === null || typeof value !== 'object') return null;
@@ -107,15 +134,17 @@ export function readSharedConversation(value: unknown): AliaSharedConversationDa
107
134
  if (!Array.isArray(raw.messages) || typeof raw.expiresAt !== 'string') return null;
108
135
  const messages = raw.messages.flatMap((message): AliaSharedMessage[] => {
109
136
  if (message === null || typeof message !== 'object') return [];
110
- const { role, content, agent } = message as Record<string, unknown>;
137
+ const { role, content, agent, artifacts } = message as Record<string, unknown>;
111
138
  if (role !== 'user' && role !== 'assistant') return [];
112
139
  if (typeof content !== 'string' && !Array.isArray(content)) return [];
113
140
  const author = readSharedAgent(agent);
141
+ const drawn = role === 'assistant' ? readSharedArtifacts(artifacts) : [];
114
142
  return [
115
143
  {
116
144
  role,
117
145
  content: content as AliaSharedMessage['content'],
118
146
  ...(author ? { agent: author } : {}),
147
+ ...(drawn.length > 0 ? { artifacts: drawn } : {}),
119
148
  },
120
149
  ];
121
150
  });
@@ -128,13 +157,28 @@ export function readSharedConversation(value: unknown): AliaSharedConversationDa
128
157
  };
129
158
  }
130
159
 
131
- /** The shared messages as the thread draws them: an agent's under its own identity. */
160
+ /**
161
+ * The shared messages as the thread draws them: an agent's under its own identity, and each artifact
162
+ * as the canvas call that drew it, which the read-only thread draws inline.
163
+ */
132
164
  export function sharedMessages(shared: AliaSharedConversationData): Message[] {
133
165
  return shared.messages.map((message, index) => ({
134
166
  id: `shared-${index}`,
135
167
  role: message.role,
136
168
  content: message.content,
137
169
  ...(message.agent ? { agentInfo: { ...message.agent } } : {}),
170
+ ...(message.artifacts
171
+ ? {
172
+ toolInvocations: message.artifacts.map(
173
+ (artifact): ToolInvocation => ({
174
+ toolCallId: artifact.id,
175
+ toolName: 'canvas',
176
+ state: 'result',
177
+ result: { type: artifact.type, title: artifact.title, data: artifact.data },
178
+ }),
179
+ ),
180
+ }
181
+ : {}),
138
182
  }));
139
183
  }
140
184