@assistant-ui/react-google-adk 0.0.26 → 0.0.28
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/AdkEventAccumulator.d.ts +2 -0
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkEventAccumulator.js +21 -9
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/adkToolApproval.js +2 -2
- package/dist/adkToolApproval.js.map +1 -1
- package/dist/convertToAdkMessages.d.ts +49 -0
- package/dist/convertToAdkMessages.d.ts.map +1 -0
- package/dist/convertToAdkMessages.js +86 -0
- package/dist/convertToAdkMessages.js.map +1 -0
- package/dist/hooks.js +3 -3
- package/dist/hooks.js.map +1 -1
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +18 -22
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +56 -133
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +7 -8
- package/src/AdkEventAccumulator.test.ts +84 -1
- package/src/AdkEventAccumulator.ts +20 -7
- package/src/adkToolApproval.ts +2 -2
- package/src/{useAdkRuntime.test.ts → convertToAdkMessages.test.ts} +1 -1
- package/src/convertToAdkMessages.ts +139 -0
- package/src/hooks.ts +3 -3
- package/src/useAdkMessages.ts +30 -27
- package/src/useAdkRuntime.toolOwnership.test.tsx +236 -0
- package/src/useAdkRuntime.ts +88 -192
|
@@ -117,7 +117,7 @@ describe("AdkEventAccumulator - function calls", () => {
|
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
it("generates
|
|
120
|
+
it("generates an ID for functionCall without an id", () => {
|
|
121
121
|
const acc = new AdkEventAccumulator();
|
|
122
122
|
const msgs = acc.processEvent(
|
|
123
123
|
makeEvent({
|
|
@@ -1477,3 +1477,86 @@ describe("AdkEventAccumulator - user message handling", () => {
|
|
|
1477
1477
|
});
|
|
1478
1478
|
});
|
|
1479
1479
|
});
|
|
1480
|
+
|
|
1481
|
+
describe("AdkEventAccumulator - multiple parts in one event", () => {
|
|
1482
|
+
it("keeps every text part of a single non-partial event in order", () => {
|
|
1483
|
+
const acc = new AdkEventAccumulator();
|
|
1484
|
+
const msgs = acc.processEvent(
|
|
1485
|
+
makeEvent({
|
|
1486
|
+
author: "agent",
|
|
1487
|
+
content: { role: "model", parts: [{ text: "A" }, { text: "B" }] },
|
|
1488
|
+
}),
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1491
|
+
expect(msgs[0]?.content).toEqual([
|
|
1492
|
+
{ type: "text", text: "A" },
|
|
1493
|
+
{ type: "text", text: "B" },
|
|
1494
|
+
]);
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
it("preserves the Gemini code-execution shape", () => {
|
|
1498
|
+
const acc = new AdkEventAccumulator();
|
|
1499
|
+
const msgs = acc.processEvent(
|
|
1500
|
+
makeEvent({
|
|
1501
|
+
author: "agent",
|
|
1502
|
+
content: {
|
|
1503
|
+
role: "model",
|
|
1504
|
+
parts: [
|
|
1505
|
+
{ text: "Here is the code:" },
|
|
1506
|
+
{ executableCode: { code: "print(1)", language: "python" } },
|
|
1507
|
+
{ codeExecutionResult: { outcome: "OUTCOME_OK", output: "1" } },
|
|
1508
|
+
{ text: "The result is 1." },
|
|
1509
|
+
],
|
|
1510
|
+
},
|
|
1511
|
+
}),
|
|
1512
|
+
);
|
|
1513
|
+
|
|
1514
|
+
expect(msgs[0]?.content).toEqual([
|
|
1515
|
+
{ type: "text", text: "Here is the code:" },
|
|
1516
|
+
{ type: "code", code: "print(1)", language: "python" },
|
|
1517
|
+
{ type: "code_result", output: "1", outcome: "OUTCOME_OK" },
|
|
1518
|
+
{ type: "text", text: "The result is 1." },
|
|
1519
|
+
]);
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
it("keeps every reasoning part of a single non-partial event", () => {
|
|
1523
|
+
const acc = new AdkEventAccumulator();
|
|
1524
|
+
const msgs = acc.processEvent(
|
|
1525
|
+
makeEvent({
|
|
1526
|
+
author: "agent",
|
|
1527
|
+
content: {
|
|
1528
|
+
role: "model",
|
|
1529
|
+
parts: [
|
|
1530
|
+
{ text: "First thought", thought: true },
|
|
1531
|
+
{ text: "Second thought", thought: true },
|
|
1532
|
+
],
|
|
1533
|
+
},
|
|
1534
|
+
}),
|
|
1535
|
+
);
|
|
1536
|
+
|
|
1537
|
+
expect(msgs[0]?.content).toEqual([
|
|
1538
|
+
{ type: "reasoning", text: "First thought" },
|
|
1539
|
+
{ type: "reasoning", text: "Second thought" },
|
|
1540
|
+
]);
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
it("still replaces the streamed buffer with the final text", () => {
|
|
1544
|
+
const acc = new AdkEventAccumulator();
|
|
1545
|
+
acc.processEvent(makeTextEvent("Hel", true));
|
|
1546
|
+
acc.processEvent(makeTextEvent("lo", true));
|
|
1547
|
+
const msgs = acc.processEvent(
|
|
1548
|
+
makeEvent({
|
|
1549
|
+
author: "agent",
|
|
1550
|
+
content: {
|
|
1551
|
+
role: "model",
|
|
1552
|
+
parts: [{ text: "Hello" }, { text: "Again" }],
|
|
1553
|
+
},
|
|
1554
|
+
}),
|
|
1555
|
+
);
|
|
1556
|
+
|
|
1557
|
+
expect(msgs[0]?.content).toEqual([
|
|
1558
|
+
{ type: "text", text: "Hello" },
|
|
1559
|
+
{ type: "text", text: "Again" },
|
|
1560
|
+
]);
|
|
1561
|
+
});
|
|
1562
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateId } from "@assistant-ui/core";
|
|
2
2
|
import type { MessageStatus } from "@assistant-ui/core";
|
|
3
3
|
import type {
|
|
4
4
|
AdkEvent,
|
|
@@ -26,10 +26,10 @@ type InProgressMessage = AdkMessage & { type: "ai" };
|
|
|
26
26
|
* event has already opened.
|
|
27
27
|
*/
|
|
28
28
|
const toolMessageId = (event: AdkEvent, partIndex: number): string =>
|
|
29
|
-
event.id ? `${event.id}:${partIndex}` :
|
|
29
|
+
event.id ? `${event.id}:${partIndex}` : generateId();
|
|
30
30
|
|
|
31
31
|
const aiMessageId = (event: AdkEvent, ordinal: number): string =>
|
|
32
|
-
event.id ? `${event.id}:ai${ordinal === 0 ? "" : ordinal}` :
|
|
32
|
+
event.id ? `${event.id}:ai${ordinal === 0 ? "" : ordinal}` : generateId();
|
|
33
33
|
|
|
34
34
|
const ADK_REQUEST_CONFIRMATION = "adk_request_confirmation";
|
|
35
35
|
const ADK_REQUEST_CREDENTIAL = "adk_request_credential";
|
|
@@ -191,6 +191,8 @@ export class AdkEventAccumulator {
|
|
|
191
191
|
private messagesMap = new Map<string, AdkMessage>();
|
|
192
192
|
private currentMessageId: string | null = null;
|
|
193
193
|
private partialTextBuffer = "";
|
|
194
|
+
private finalTextReplacedThisEvent = false;
|
|
195
|
+
private finalReasoningReplacedThisEvent = false;
|
|
194
196
|
private partialReasoningBuffer = "";
|
|
195
197
|
private accumulatedStateDelta: Record<string, unknown> = {};
|
|
196
198
|
private accumulatedArtifactDelta: Record<string, number> = {};
|
|
@@ -368,7 +370,7 @@ export class AdkEventAccumulator {
|
|
|
368
370
|
this.messagesMap.set(toolMsg.id, toolMsg);
|
|
369
371
|
}
|
|
370
372
|
if (humanParts.length > 0) {
|
|
371
|
-
const id = event.id ??
|
|
373
|
+
const id = event.id ?? generateId();
|
|
372
374
|
const first = humanParts[0];
|
|
373
375
|
const content: string | AdkMessageContentPart[] =
|
|
374
376
|
humanParts.length === 1 && first?.type === "text"
|
|
@@ -387,6 +389,11 @@ export class AdkEventAccumulator {
|
|
|
387
389
|
}
|
|
388
390
|
}
|
|
389
391
|
|
|
392
|
+
// Replace-semantics close out the streamed partial buffer, which only
|
|
393
|
+
// the first final text/reasoning part of an event may do; later parts
|
|
394
|
+
// of the same event are distinct content and append.
|
|
395
|
+
this.finalTextReplacedThisEvent = false;
|
|
396
|
+
this.finalReasoningReplacedThisEvent = false;
|
|
390
397
|
for (const [index, part] of parts.entries()) {
|
|
391
398
|
this.processPart(part, event, index);
|
|
392
399
|
}
|
|
@@ -472,9 +479,12 @@ export class AdkEventAccumulator {
|
|
|
472
479
|
if (event.partial) {
|
|
473
480
|
this.partialReasoningBuffer += part.text;
|
|
474
481
|
this.replaceLastReasoningContent(msg, this.partialReasoningBuffer);
|
|
475
|
-
} else {
|
|
482
|
+
} else if (!this.finalReasoningReplacedThisEvent) {
|
|
483
|
+
this.finalReasoningReplacedThisEvent = true;
|
|
476
484
|
this.partialReasoningBuffer = "";
|
|
477
485
|
this.replaceLastReasoningContent(msg, part.text);
|
|
486
|
+
} else {
|
|
487
|
+
this.appendContent(msg, { type: "reasoning", text: part.text });
|
|
478
488
|
}
|
|
479
489
|
return;
|
|
480
490
|
}
|
|
@@ -485,9 +495,12 @@ export class AdkEventAccumulator {
|
|
|
485
495
|
if (event.partial) {
|
|
486
496
|
this.partialTextBuffer += part.text;
|
|
487
497
|
this.replaceLastTextContent(msg, this.partialTextBuffer);
|
|
488
|
-
} else {
|
|
498
|
+
} else if (!this.finalTextReplacedThisEvent) {
|
|
499
|
+
this.finalTextReplacedThisEvent = true;
|
|
489
500
|
this.partialTextBuffer = "";
|
|
490
501
|
this.replaceLastTextContent(msg, part.text);
|
|
502
|
+
} else {
|
|
503
|
+
this.appendContent(msg, { type: "text", text: part.text });
|
|
491
504
|
}
|
|
492
505
|
return;
|
|
493
506
|
}
|
|
@@ -497,7 +510,7 @@ export class AdkEventAccumulator {
|
|
|
497
510
|
if (event.partial) return;
|
|
498
511
|
const msg = this.getOrCreateAiMessage(event);
|
|
499
512
|
const toolCall: AdkToolCall = {
|
|
500
|
-
id: part.functionCall.id ??
|
|
513
|
+
id: part.functionCall.id ?? generateId(),
|
|
501
514
|
name: part.functionCall.name,
|
|
502
515
|
args: part.functionCall.args as ReadonlyJSONObject,
|
|
503
516
|
argsText: JSON.stringify(part.functionCall.args),
|
package/src/adkToolApproval.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { generateId } from "@assistant-ui/core";
|
|
1
2
|
import type {
|
|
2
3
|
RespondToToolApprovalOptions,
|
|
3
4
|
ToolCallMessagePart,
|
|
4
5
|
} from "@assistant-ui/core";
|
|
5
6
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
6
|
-
import { v4 as uuidv4 } from "uuid";
|
|
7
7
|
import type { AdkMessage } from "./types";
|
|
8
8
|
|
|
9
9
|
export type AdkToolApproval = NonNullable<ToolCallMessagePart["approval"]>;
|
|
@@ -185,7 +185,7 @@ export const toAdkConfirmationReply = (
|
|
|
185
185
|
confirmed: boolean,
|
|
186
186
|
payload?: ReadonlyJSONValue,
|
|
187
187
|
): AdkMessage & { type: "tool" } => ({
|
|
188
|
-
id:
|
|
188
|
+
id: generateId(),
|
|
189
189
|
type: "tool",
|
|
190
190
|
tool_call_id: toolCallId,
|
|
191
191
|
name: ADK_REQUEST_CONFIRMATION,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getMessageContent,
|
|
4
4
|
getPendingCancellations,
|
|
5
5
|
getPendingToolCalls,
|
|
6
|
-
} from "./
|
|
6
|
+
} from "./convertToAdkMessages";
|
|
7
7
|
import { convertAdkMessage } from "./convertAdkMessages";
|
|
8
8
|
import type { AppendMessage } from "@assistant-ui/core";
|
|
9
9
|
import type { AdkMessage } from "./types";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateId,
|
|
3
|
+
getExternalStoreMessages,
|
|
4
|
+
type AppendMessage,
|
|
5
|
+
type ThreadMessage,
|
|
6
|
+
} from "@assistant-ui/core";
|
|
7
|
+
import {
|
|
8
|
+
createToolCallCancellationStub,
|
|
9
|
+
parseDataUrl,
|
|
10
|
+
resolveFilePartSource,
|
|
11
|
+
scanPendingToolCalls,
|
|
12
|
+
} from "@assistant-ui/core/internal";
|
|
13
|
+
import type { AdkMessage } from "./types";
|
|
14
|
+
|
|
15
|
+
/** Exported for unit tests. */
|
|
16
|
+
export const getMessageContent = (msg: AppendMessage) => {
|
|
17
|
+
const allContent = [
|
|
18
|
+
...msg.content,
|
|
19
|
+
...(msg.attachments?.flatMap((a) => a.content) ?? []),
|
|
20
|
+
];
|
|
21
|
+
const content = allContent.flatMap((part) => {
|
|
22
|
+
const type = part.type;
|
|
23
|
+
switch (type) {
|
|
24
|
+
case "text":
|
|
25
|
+
return { type: "text" as const, text: part.text };
|
|
26
|
+
case "image":
|
|
27
|
+
return { type: "image_url" as const, url: part.image };
|
|
28
|
+
case "file": {
|
|
29
|
+
const source = resolveFilePartSource(part);
|
|
30
|
+
if (source.kind === "url") {
|
|
31
|
+
return {
|
|
32
|
+
type: "file_url" as const,
|
|
33
|
+
url: source.url,
|
|
34
|
+
mimeType: part.mimeType,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
type: "file" as const,
|
|
39
|
+
mimeType: part.mimeType,
|
|
40
|
+
// Lands in Gemini `inlineData.data`, which takes bare base64, so a
|
|
41
|
+
// data URL envelope is stripped rather than forwarded.
|
|
42
|
+
data: source.data,
|
|
43
|
+
...(part.filename != null && { filename: part.filename }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
case "audio": {
|
|
47
|
+
const parsed = parseDataUrl(part.audio.data);
|
|
48
|
+
return {
|
|
49
|
+
type: "file" as const,
|
|
50
|
+
mimeType: `audio/${part.audio.format}`,
|
|
51
|
+
data: parsed?.data ?? part.audio.data,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
case "data":
|
|
55
|
+
return [];
|
|
56
|
+
|
|
57
|
+
case "tool-call":
|
|
58
|
+
throw new Error("Tool call appends are not supported.");
|
|
59
|
+
|
|
60
|
+
default: {
|
|
61
|
+
const _exhaustiveCheck: "reasoning" | "source" | "generative-ui" = type;
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Unsupported append message part type: ${_exhaustiveCheck}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (content.length === 1 && content[0]?.type === "text") {
|
|
70
|
+
return content[0].text ?? "";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return content;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Exported for unit tests. */
|
|
77
|
+
export const getPendingToolCalls = (messages: AdkMessage[]) => {
|
|
78
|
+
return scanPendingToolCalls(
|
|
79
|
+
messages,
|
|
80
|
+
(message) => {
|
|
81
|
+
if (message.type === "ai") {
|
|
82
|
+
return { toolCalls: message.tool_calls ?? [] };
|
|
83
|
+
}
|
|
84
|
+
if (message.type === "tool") {
|
|
85
|
+
return { toolCallId: message.tool_call_id };
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
},
|
|
89
|
+
(toolCall) => toolCall.id,
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Exported for unit tests.
|
|
95
|
+
*
|
|
96
|
+
* Returns `{cancelled: true}` tool responses for pending tool calls when the
|
|
97
|
+
* user sends a new turn, EXCEPT for HITL interrupts marked via
|
|
98
|
+
* `long_running_tool_ids` (`adk_request_input`, `adk_request_confirmation`,
|
|
99
|
+
* `adk_request_credential`). Those must be answered through a dedicated tool
|
|
100
|
+
* UI + submit helper, not auto-cancelled.
|
|
101
|
+
*/
|
|
102
|
+
export const getPendingCancellations = (
|
|
103
|
+
messages: AdkMessage[],
|
|
104
|
+
longRunningToolIds: readonly string[],
|
|
105
|
+
): Array<AdkMessage & { type: "tool" }> => {
|
|
106
|
+
const longRunningSet = new Set(longRunningToolIds);
|
|
107
|
+
return getPendingToolCalls(messages)
|
|
108
|
+
.filter((t) => !longRunningSet.has(t.id))
|
|
109
|
+
.map(
|
|
110
|
+
(t) =>
|
|
111
|
+
({
|
|
112
|
+
id: generateId(),
|
|
113
|
+
...createToolCallCancellationStub(t),
|
|
114
|
+
}) satisfies AdkMessage & { type: "tool" },
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const truncateAdkMessages = (
|
|
119
|
+
threadMessages: readonly ThreadMessage[],
|
|
120
|
+
parentId: string | null,
|
|
121
|
+
): AdkMessage[] => {
|
|
122
|
+
if (parentId === null) return [];
|
|
123
|
+
const parentIndex = threadMessages.findIndex((m) => m.id === parentId);
|
|
124
|
+
if (parentIndex === -1) return [];
|
|
125
|
+
const truncated: AdkMessage[] = [];
|
|
126
|
+
for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {
|
|
127
|
+
truncated.push(...getExternalStoreMessages<AdkMessage>(threadMessages[i]!));
|
|
128
|
+
}
|
|
129
|
+
return truncated;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const toAdkUserMessage = (
|
|
133
|
+
msg: AppendMessage,
|
|
134
|
+
id = generateId(),
|
|
135
|
+
): AdkMessage & { type: "human"; id: string } => ({
|
|
136
|
+
id,
|
|
137
|
+
type: "human",
|
|
138
|
+
content: getMessageContent(msg),
|
|
139
|
+
});
|
package/src/hooks.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { generateId } from "@assistant-ui/core";
|
|
1
2
|
import { useAui } from "@assistant-ui/store";
|
|
2
|
-
import { v4 as uuidv4 } from "uuid";
|
|
3
3
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
4
4
|
import { adkExtras } from "./adkExtras";
|
|
5
5
|
import { toAdkConfirmationReply } from "./adkToolApproval";
|
|
@@ -79,7 +79,7 @@ export const useAdkSubmitAuth = () => {
|
|
|
79
79
|
adkExtras.get(aui).send(
|
|
80
80
|
[
|
|
81
81
|
{
|
|
82
|
-
id:
|
|
82
|
+
id: generateId(),
|
|
83
83
|
type: "tool",
|
|
84
84
|
tool_call_id: toolCallId,
|
|
85
85
|
name: "adk_request_credential",
|
|
@@ -98,7 +98,7 @@ export const useAdkSubmitInput = () => {
|
|
|
98
98
|
adkExtras.get(aui).send(
|
|
99
99
|
[
|
|
100
100
|
{
|
|
101
|
-
id:
|
|
101
|
+
id: generateId(),
|
|
102
102
|
type: "tool",
|
|
103
103
|
tool_call_id: toolCallId,
|
|
104
104
|
name: "adk_request_input",
|
package/src/useAdkMessages.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useInsertionEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useMemo,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { generateId } from "@assistant-ui/core";
|
|
3
10
|
import { useAui } from "@assistant-ui/store";
|
|
11
|
+
import { invokeUserCallback } from "@assistant-ui/core/internal";
|
|
4
12
|
import { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
5
13
|
import { contentToParts } from "./contentToParts";
|
|
6
14
|
import type {
|
|
@@ -28,24 +36,12 @@ export type UseAdkMessagesOptions = {
|
|
|
28
36
|
|
|
29
37
|
type AdkRuntimeCallbackName = "onError" | "onCustomEvent" | "onAgentTransfer";
|
|
30
38
|
|
|
31
|
-
const
|
|
32
|
-
console.error(`[react-google-adk] ${name} callback threw an error`, error);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const invokeAdkRuntimeCallback = <TArgs extends unknown[]>(
|
|
39
|
+
const invokeAdkRuntimeCallback = <TArgs extends readonly unknown[]>(
|
|
36
40
|
name: AdkRuntimeCallbackName,
|
|
37
|
-
callback: ((...args: TArgs) =>
|
|
41
|
+
callback: ((...args: TArgs) => unknown) | undefined,
|
|
38
42
|
...args: TArgs
|
|
39
|
-
) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
void Promise.resolve(callback(...args)).catch((error) => {
|
|
44
|
-
reportCallbackError(name, error);
|
|
45
|
-
});
|
|
46
|
-
} catch (error) {
|
|
47
|
-
reportCallbackError(name, error);
|
|
48
|
-
}
|
|
43
|
+
): void => {
|
|
44
|
+
void invokeUserCallback("react-google-adk", name, callback, ...args);
|
|
49
45
|
};
|
|
50
46
|
|
|
51
47
|
export const useAdkMessages = ({
|
|
@@ -71,14 +67,21 @@ export const useAdkMessages = ({
|
|
|
71
67
|
Map<string, AdkMessageMetadata>
|
|
72
68
|
>(new Map());
|
|
73
69
|
const lastTransferToAgentRef = useRef<string | undefined>(undefined);
|
|
70
|
+
// setMessagesImmediate is the only writer of the messages state and publishes
|
|
71
|
+
// this ref with it, so the ref never trails a commit.
|
|
74
72
|
const messagesRef = useRef(messages);
|
|
75
|
-
messagesRef.current = messages;
|
|
76
73
|
const stateDeltaRef = useRef(stateDelta);
|
|
77
|
-
|
|
74
|
+
useInsertionEffect(() => {
|
|
75
|
+
stateDeltaRef.current = stateDelta;
|
|
76
|
+
}, [stateDelta]);
|
|
78
77
|
const artifactDeltaRef = useRef(artifactDelta);
|
|
79
|
-
|
|
78
|
+
useInsertionEffect(() => {
|
|
79
|
+
artifactDeltaRef.current = artifactDelta;
|
|
80
|
+
}, [artifactDelta]);
|
|
80
81
|
const messageMetadataRef = useRef(messageMetadata);
|
|
81
|
-
|
|
82
|
+
useInsertionEffect(() => {
|
|
83
|
+
messageMetadataRef.current = messageMetadata;
|
|
84
|
+
}, [messageMetadata]);
|
|
82
85
|
|
|
83
86
|
const setMessagesImmediate = useCallback((msgs: AdkMessage[]) => {
|
|
84
87
|
messagesRef.current = msgs;
|
|
@@ -133,7 +136,7 @@ export const useAdkMessages = ({
|
|
|
133
136
|
const sendMessage = useCallback(
|
|
134
137
|
async (newMessages: AdkMessage[], config: AdkSendMessageConfig) => {
|
|
135
138
|
const newMessagesWithId = newMessages.map((m) =>
|
|
136
|
-
m.id ? m : { ...m, id:
|
|
139
|
+
m.id ? m : { ...m, id: generateId() },
|
|
137
140
|
) as AdkMessage[];
|
|
138
141
|
|
|
139
142
|
// A staged message is already in the thread under its own id, and the
|
|
@@ -306,7 +309,7 @@ export const messagesToEvents = (messages: AdkMessage[]): AdkEvent[] => {
|
|
|
306
309
|
// message. Emitting it here keeps the optimistic view equal to that replay.
|
|
307
310
|
if (parts.length === 0) parts.push({ text: "" });
|
|
308
311
|
|
|
309
|
-
const event: AdkEvent = { id: (human ?? run[0])?.id ??
|
|
312
|
+
const event: AdkEvent = { id: (human ?? run[0])?.id ?? generateId() };
|
|
310
313
|
if (human || run.length === 0) event.author = "user";
|
|
311
314
|
event.content = { role: "user", parts };
|
|
312
315
|
events.splice(run.length > 0 ? runIndex : events.length, 0, event);
|
|
@@ -318,7 +321,7 @@ export const messagesToEvents = (messages: AdkMessage[]): AdkEvent[] => {
|
|
|
318
321
|
export const messageToEvent = (msg: AdkMessage): AdkEvent => {
|
|
319
322
|
if (msg.type === "human") {
|
|
320
323
|
return {
|
|
321
|
-
id: msg.id ??
|
|
324
|
+
id: msg.id ?? generateId(),
|
|
322
325
|
author: "user",
|
|
323
326
|
content: { role: "user", parts: contentToParts(msg.content) },
|
|
324
327
|
};
|
|
@@ -332,7 +335,7 @@ export const messageToEvent = (msg: AdkMessage): AdkEvent => {
|
|
|
332
335
|
response = msg.content;
|
|
333
336
|
}
|
|
334
337
|
return {
|
|
335
|
-
id: msg.id ??
|
|
338
|
+
id: msg.id ?? generateId(),
|
|
336
339
|
content: {
|
|
337
340
|
role: "user",
|
|
338
341
|
parts: [
|
|
@@ -348,7 +351,7 @@ export const messageToEvent = (msg: AdkMessage): AdkEvent => {
|
|
|
348
351
|
};
|
|
349
352
|
}
|
|
350
353
|
|
|
351
|
-
const result: AdkEvent = { id: msg.id ??
|
|
354
|
+
const result: AdkEvent = { id: msg.id ?? generateId() };
|
|
352
355
|
if (msg.author != null) result.author = msg.author;
|
|
353
356
|
result.content = {
|
|
354
357
|
role: "model",
|