@subako-ai/assistant-ui 0.2.0 → 0.2.1
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/dist/convert.d.ts +8 -1
- package/dist/convert.d.ts.map +1 -1
- package/dist/convert.js +53 -0
- package/dist/convert.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +18 -4
- package/dist/runtime.js.map +1 -1
- package/package.json +5 -5
- package/src/convert.ts +61 -1
- package/src/index.ts +1 -1
- package/src/runtime.ts +29 -7
package/dist/convert.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThreadMessageLike } from "@assistant-ui/react";
|
|
2
|
-
import type
|
|
2
|
+
import { type LiveMessage, type TranscriptMessage } from "@subako-ai/sdk";
|
|
3
3
|
/**
|
|
4
4
|
* One message of the log as assistant-ui reads it: `text` becomes a text part,
|
|
5
5
|
* `thinking` a reasoning part, and every `tool_call` the joined call, so its
|
|
@@ -11,4 +11,11 @@ import type { TranscriptMessage } from "@subako-ai/sdk";
|
|
|
11
11
|
* handing back the same message until an event touches it.
|
|
12
12
|
*/
|
|
13
13
|
export declare function convertMessage(message: TranscriptMessage): ThreadMessageLike;
|
|
14
|
+
/**
|
|
15
|
+
* A message a model call is still producing, as assistant-ui reads it: an
|
|
16
|
+
* assistant message marked running, whose tool calls carry the argument text
|
|
17
|
+
* streamed so far. Its id is the model call's, so it keeps its place in the
|
|
18
|
+
* thread from one frame to the next until the logged message replaces it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function convertLiveMessage(message: LiveMessage): ThreadMessageLike;
|
|
14
21
|
//# sourceMappingURL=convert.d.ts.map
|
package/dist/convert.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAGN,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACtB,MAAM,gBAAgB,CAAC;AAkDxB;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CA0B5E;AAyCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,CAO1E"}
|
package/dist/convert.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { liveKey, } from "@subako-ai/sdk";
|
|
1
2
|
/** The arguments arrive as JSON off the wire; assistant-ui asks for them typed as JSON. */
|
|
2
3
|
const asArguments = (args) => args;
|
|
3
4
|
/** A tool result is content blocks; assistant-ui renders one value, so the blocks are read as text. */
|
|
@@ -68,4 +69,56 @@ export function convertMessage(message) {
|
|
|
68
69
|
...(message.source === undefined ? {} : { metadata: { custom: { source: message.source } } }),
|
|
69
70
|
};
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The arguments a call streamed so far, once they parse; `undefined` while
|
|
74
|
+
* they are still partial. JSON that ends in `}` and parses is an object, as
|
|
75
|
+
* arguments are, and most frames leave the text mid-value, so nothing else is
|
|
76
|
+
* parsed at all.
|
|
77
|
+
*/
|
|
78
|
+
function partialArguments(text) {
|
|
79
|
+
if (!text.trimEnd().endsWith("}")) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return asArguments(JSON.parse(text));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function livePart(block) {
|
|
90
|
+
switch (block.type) {
|
|
91
|
+
case "text":
|
|
92
|
+
return { type: "text", text: block.text };
|
|
93
|
+
case "thinking":
|
|
94
|
+
return { type: "reasoning", text: block.thinking };
|
|
95
|
+
case "tool_call": {
|
|
96
|
+
const args = partialArguments(block.arguments);
|
|
97
|
+
return {
|
|
98
|
+
type: "tool-call",
|
|
99
|
+
toolCallId: block.id,
|
|
100
|
+
toolName: block.name,
|
|
101
|
+
argsText: block.arguments,
|
|
102
|
+
...(args === undefined ? {} : { args }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// COVERAGE(never): a live block has no other type, which `satisfies never` proves.
|
|
106
|
+
default:
|
|
107
|
+
return block;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A message a model call is still producing, as assistant-ui reads it: an
|
|
112
|
+
* assistant message marked running, whose tool calls carry the argument text
|
|
113
|
+
* streamed so far. Its id is the model call's, so it keeps its place in the
|
|
114
|
+
* thread from one frame to the next until the logged message replaces it.
|
|
115
|
+
*/
|
|
116
|
+
export function convertLiveMessage(message) {
|
|
117
|
+
return {
|
|
118
|
+
role: "assistant",
|
|
119
|
+
id: `live:${liveKey(message.target)}`,
|
|
120
|
+
content: message.content.map(livePart),
|
|
121
|
+
status: { type: "running" },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
71
124
|
//# sourceMappingURL=convert.js.map
|
package/dist/convert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { SubakoChat, type SubakoChatProps, SubakoToolApproval, type SubakoToolApprovalProps } from "./chat.js";
|
|
2
|
-
export { convertMessage } from "./convert.js";
|
|
2
|
+
export { convertLiveMessage, convertMessage } from "./convert.js";
|
|
3
3
|
export { type SubakoSession, useSubakoRuntime } from "./runtime.js";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,kBAAkB,EAAgC,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,kBAAkB,EAAgC,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface SubakoSession {
|
|
|
22
22
|
*
|
|
23
23
|
* | assistant-ui | Subako |
|
|
24
24
|
* | ------------------------- | --------------------------------------------- |
|
|
25
|
-
* | `messages` | `state.transcript`,
|
|
25
|
+
* | `messages` | `state.transcript`, then `state.live` |
|
|
26
26
|
* | `isRunning` | `state.isRunning` |
|
|
27
27
|
* | `onNew` | `send(text)` |
|
|
28
28
|
* | `onCancel` | `cancel()` |
|
|
@@ -36,6 +36,10 @@ export interface SubakoSession {
|
|
|
36
36
|
* The session is a value, not something React re-renders for, so the runtime
|
|
37
37
|
* subscribes to it here: a chat handed the session by a parent that does not
|
|
38
38
|
* re-render still follows the log.
|
|
39
|
+
*
|
|
40
|
+
* A session opened with `deltas` also shows what a model is writing while it
|
|
41
|
+
* writes it: each of `state.live` follows the log as a running assistant
|
|
42
|
+
* message, which the logged message replaces once it lands.
|
|
39
43
|
*/
|
|
40
44
|
export declare function useSubakoRuntime(session: SubakoSession | null): AssistantRuntime;
|
|
41
45
|
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,KAAK,EAAe,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AAInF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC5E,sEAAsE;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,8BAA8B;IAC9B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7F;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,gBAAgB,CA+BhF"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useExternalStoreRuntime } from "@assistant-ui/react";
|
|
3
3
|
import { useSessionState } from "@subako-ai/react";
|
|
4
|
-
import {
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { convertLiveMessage, convertMessage } from "./convert.js";
|
|
5
6
|
/** The thread before the connection is open: no messages, and the same array on every render. */
|
|
6
7
|
const NOTHING = [];
|
|
8
|
+
const convertEntry = (entry) => ("target" in entry ? convertLiveMessage(entry) : convertMessage(entry));
|
|
7
9
|
/** assistant-ui hands the composer's parts; a session takes one string. */
|
|
8
10
|
function appendedText(message) {
|
|
9
11
|
let text = "";
|
|
@@ -20,7 +22,7 @@ function appendedText(message) {
|
|
|
20
22
|
*
|
|
21
23
|
* | assistant-ui | Subako |
|
|
22
24
|
* | ------------------------- | --------------------------------------------- |
|
|
23
|
-
* | `messages` | `state.transcript`,
|
|
25
|
+
* | `messages` | `state.transcript`, then `state.live` |
|
|
24
26
|
* | `isRunning` | `state.isRunning` |
|
|
25
27
|
* | `onNew` | `send(text)` |
|
|
26
28
|
* | `onCancel` | `cancel()` |
|
|
@@ -34,15 +36,27 @@ function appendedText(message) {
|
|
|
34
36
|
* The session is a value, not something React re-renders for, so the runtime
|
|
35
37
|
* subscribes to it here: a chat handed the session by a parent that does not
|
|
36
38
|
* re-render still follows the log.
|
|
39
|
+
*
|
|
40
|
+
* A session opened with `deltas` also shows what a model is writing while it
|
|
41
|
+
* writes it: each of `state.live` follows the log as a running assistant
|
|
42
|
+
* message, which the logged message replaces once it lands.
|
|
37
43
|
*/
|
|
38
44
|
export function useSubakoRuntime(session) {
|
|
39
45
|
// The snapshot this render was woken for, which is what the thread is built from.
|
|
40
46
|
const state = useSessionState(session);
|
|
47
|
+
const transcript = state?.transcript;
|
|
48
|
+
const live = state?.live;
|
|
49
|
+
// Rebuilt only when either part changes, so assistant-ui's per-message cache stays warm.
|
|
50
|
+
const messages = useMemo(() => transcript === undefined
|
|
51
|
+
? NOTHING
|
|
52
|
+
: live === undefined || live.length === 0
|
|
53
|
+
? transcript
|
|
54
|
+
: [...transcript, ...live], [transcript, live]);
|
|
41
55
|
return useExternalStoreRuntime({
|
|
42
|
-
messages
|
|
56
|
+
messages,
|
|
43
57
|
isRunning: state?.isRunning ?? false,
|
|
44
58
|
isDisabled: session === null,
|
|
45
|
-
convertMessage,
|
|
59
|
+
convertMessage: convertEntry,
|
|
46
60
|
onNew: async (message) => {
|
|
47
61
|
await session?.send(appendedText(message));
|
|
48
62
|
},
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAsBlE,iGAAiG;AACjG,MAAM,OAAO,GAA2B,EAAE,CAAC;AAE3C,MAAM,YAAY,GAAG,CAAC,KAAkB,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AAErH,2EAA2E;AAC3E,SAAS,YAAY,CAAC,OAAsB;IAC3C,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC7D,kFAAkF;IAClF,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC;IACzB,yFAAyF;IACzF,MAAM,QAAQ,GAAG,OAAO,CACvB,GAA2B,EAAE,CAC5B,UAAU,KAAK,SAAS;QACvB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YACxC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC,EAC7B,CAAC,UAAU,EAAE,IAAI,CAAC,CAClB,CAAC;IACF,OAAO,uBAAuB,CAAc;QAC3C,QAAQ;QACR,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,KAAK;QACpC,UAAU,EAAE,OAAO,KAAK,IAAI;QAC5B,cAAc,EAAE,YAAY;QAC5B,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,MAAM,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,QAAQ,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,OAAO,EAAE,MAAM,EAAE,CAAC;QACzB,CAAC;QACD,gFAAgF;QAChF,uBAAuB,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;YACnE,MAAM,OAAO,EAAE,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjF,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subako-ai/assistant-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "assistant-ui runtime and chat for Subako sessions",
|
|
5
5
|
"homepage": "https://subako.ai",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
"react": "^19.2.0",
|
|
40
40
|
"react-dom": "^19.2.0",
|
|
41
41
|
"typescript": "^7",
|
|
42
|
-
"@subako-ai/
|
|
43
|
-
"@subako-ai/
|
|
42
|
+
"@subako-ai/react": "0.2.1",
|
|
43
|
+
"@subako-ai/sdk": "0.2.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@assistant-ui/react": "^0.15.0",
|
|
47
47
|
"@assistant-ui/react-markdown": "^0.14.15",
|
|
48
48
|
"react": "^19.0.0",
|
|
49
|
-
"@subako-ai/react": "^0.2.
|
|
50
|
-
"@subako-ai/sdk": "^0.2.
|
|
49
|
+
"@subako-ai/react": "^0.2.1",
|
|
50
|
+
"@subako-ai/sdk": "^0.2.1"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=22"
|
package/src/convert.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ThreadMessageLike } from "@assistant-ui/react";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
type ContentBlockBody,
|
|
4
|
+
type LiveBlock,
|
|
5
|
+
type LiveMessage,
|
|
6
|
+
liveKey,
|
|
7
|
+
type TranscriptMessage,
|
|
8
|
+
} from "@subako-ai/sdk";
|
|
3
9
|
|
|
4
10
|
/** One part of an assistant-ui message; its content is either a string or these. */
|
|
5
11
|
type ContentPart = Extract<ThreadMessageLike["content"], readonly unknown[]>[number];
|
|
@@ -86,3 +92,57 @@ export function convertMessage(message: TranscriptMessage): ThreadMessageLike {
|
|
|
86
92
|
...(message.source === undefined ? {} : { metadata: { custom: { source: message.source } } }),
|
|
87
93
|
};
|
|
88
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The arguments a call streamed so far, once they parse; `undefined` while
|
|
98
|
+
* they are still partial. JSON that ends in `}` and parses is an object, as
|
|
99
|
+
* arguments are, and most frames leave the text mid-value, so nothing else is
|
|
100
|
+
* parsed at all.
|
|
101
|
+
*/
|
|
102
|
+
function partialArguments(text: string): NonNullable<ToolCallPart["args"]> | undefined {
|
|
103
|
+
if (!text.trimEnd().endsWith("}")) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return asArguments(JSON.parse(text) as Record<string, unknown>);
|
|
108
|
+
} catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function livePart(block: LiveBlock): ContentPart {
|
|
114
|
+
switch (block.type) {
|
|
115
|
+
case "text":
|
|
116
|
+
return { type: "text", text: block.text };
|
|
117
|
+
case "thinking":
|
|
118
|
+
return { type: "reasoning", text: block.thinking };
|
|
119
|
+
case "tool_call": {
|
|
120
|
+
const args = partialArguments(block.arguments);
|
|
121
|
+
return {
|
|
122
|
+
type: "tool-call",
|
|
123
|
+
toolCallId: block.id,
|
|
124
|
+
toolName: block.name,
|
|
125
|
+
argsText: block.arguments,
|
|
126
|
+
...(args === undefined ? {} : { args }),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// COVERAGE(never): a live block has no other type, which `satisfies never` proves.
|
|
130
|
+
default:
|
|
131
|
+
return block satisfies never;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A message a model call is still producing, as assistant-ui reads it: an
|
|
137
|
+
* assistant message marked running, whose tool calls carry the argument text
|
|
138
|
+
* streamed so far. Its id is the model call's, so it keeps its place in the
|
|
139
|
+
* thread from one frame to the next until the logged message replaces it.
|
|
140
|
+
*/
|
|
141
|
+
export function convertLiveMessage(message: LiveMessage): ThreadMessageLike {
|
|
142
|
+
return {
|
|
143
|
+
role: "assistant",
|
|
144
|
+
id: `live:${liveKey(message.target)}`,
|
|
145
|
+
content: message.content.map(livePart),
|
|
146
|
+
status: { type: "running" },
|
|
147
|
+
};
|
|
148
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { SubakoChat, type SubakoChatProps, SubakoToolApproval, type SubakoToolApprovalProps } from "./chat.js";
|
|
2
|
-
export { convertMessage } from "./convert.js";
|
|
2
|
+
export { convertLiveMessage, convertMessage } from "./convert.js";
|
|
3
3
|
export { type SubakoSession, useSubakoRuntime } from "./runtime.js";
|
package/src/runtime.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import type { AppendMessage, AssistantRuntime } from "@assistant-ui/react";
|
|
4
4
|
import { useExternalStoreRuntime } from "@assistant-ui/react";
|
|
5
5
|
import { useSessionState } from "@subako-ai/react";
|
|
6
|
-
import type { SessionState, TranscriptMessage } from "@subako-ai/sdk";
|
|
7
|
-
import {
|
|
6
|
+
import type { LiveMessage, SessionState, TranscriptMessage } from "@subako-ai/sdk";
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
import { convertLiveMessage, convertMessage } from "./convert.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* What the runtime reads off a session: the SDK's `SessionConnection`, or
|
|
@@ -23,8 +24,13 @@ export interface SubakoSession {
|
|
|
23
24
|
resolveApproval(callId: string, decision: "allow" | "deny", message?: string): Promise<void>;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/** One message of the thread: one the log holds, or one a model call is still producing. */
|
|
28
|
+
type ThreadEntry = TranscriptMessage | LiveMessage;
|
|
29
|
+
|
|
26
30
|
/** The thread before the connection is open: no messages, and the same array on every render. */
|
|
27
|
-
const NOTHING: readonly
|
|
31
|
+
const NOTHING: readonly ThreadEntry[] = [];
|
|
32
|
+
|
|
33
|
+
const convertEntry = (entry: ThreadEntry) => ("target" in entry ? convertLiveMessage(entry) : convertMessage(entry));
|
|
28
34
|
|
|
29
35
|
/** assistant-ui hands the composer's parts; a session takes one string. */
|
|
30
36
|
function appendedText(message: AppendMessage): string {
|
|
@@ -43,7 +49,7 @@ function appendedText(message: AppendMessage): string {
|
|
|
43
49
|
*
|
|
44
50
|
* | assistant-ui | Subako |
|
|
45
51
|
* | ------------------------- | --------------------------------------------- |
|
|
46
|
-
* | `messages` | `state.transcript`,
|
|
52
|
+
* | `messages` | `state.transcript`, then `state.live` |
|
|
47
53
|
* | `isRunning` | `state.isRunning` |
|
|
48
54
|
* | `onNew` | `send(text)` |
|
|
49
55
|
* | `onCancel` | `cancel()` |
|
|
@@ -57,15 +63,31 @@ function appendedText(message: AppendMessage): string {
|
|
|
57
63
|
* The session is a value, not something React re-renders for, so the runtime
|
|
58
64
|
* subscribes to it here: a chat handed the session by a parent that does not
|
|
59
65
|
* re-render still follows the log.
|
|
66
|
+
*
|
|
67
|
+
* A session opened with `deltas` also shows what a model is writing while it
|
|
68
|
+
* writes it: each of `state.live` follows the log as a running assistant
|
|
69
|
+
* message, which the logged message replaces once it lands.
|
|
60
70
|
*/
|
|
61
71
|
export function useSubakoRuntime(session: SubakoSession | null): AssistantRuntime {
|
|
62
72
|
// The snapshot this render was woken for, which is what the thread is built from.
|
|
63
73
|
const state = useSessionState(session);
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
const transcript = state?.transcript;
|
|
75
|
+
const live = state?.live;
|
|
76
|
+
// Rebuilt only when either part changes, so assistant-ui's per-message cache stays warm.
|
|
77
|
+
const messages = useMemo(
|
|
78
|
+
(): readonly ThreadEntry[] =>
|
|
79
|
+
transcript === undefined
|
|
80
|
+
? NOTHING
|
|
81
|
+
: live === undefined || live.length === 0
|
|
82
|
+
? transcript
|
|
83
|
+
: [...transcript, ...live],
|
|
84
|
+
[transcript, live],
|
|
85
|
+
);
|
|
86
|
+
return useExternalStoreRuntime<ThreadEntry>({
|
|
87
|
+
messages,
|
|
66
88
|
isRunning: state?.isRunning ?? false,
|
|
67
89
|
isDisabled: session === null,
|
|
68
|
-
convertMessage,
|
|
90
|
+
convertMessage: convertEntry,
|
|
69
91
|
onNew: async (message) => {
|
|
70
92
|
await session?.send(appendedText(message));
|
|
71
93
|
},
|