@assistant-ui/react-google-adk 0.0.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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/AdkClient.d.ts +45 -0
- package/dist/AdkClient.d.ts.map +1 -0
- package/dist/AdkClient.js +204 -0
- package/dist/AdkClient.js.map +1 -0
- package/dist/AdkEventAccumulator.d.ts +45 -0
- package/dist/AdkEventAccumulator.d.ts.map +1 -0
- package/dist/AdkEventAccumulator.js +508 -0
- package/dist/AdkEventAccumulator.js.map +1 -0
- package/dist/AdkSessionAdapter.d.ts +61 -0
- package/dist/AdkSessionAdapter.d.ts.map +1 -0
- package/dist/AdkSessionAdapter.js +159 -0
- package/dist/AdkSessionAdapter.js.map +1 -0
- package/dist/convertAdkMessages.d.ts +4 -0
- package/dist/convertAdkMessages.d.ts.map +1 -0
- package/dist/convertAdkMessages.js +75 -0
- package/dist/convertAdkMessages.js.map +1 -0
- package/dist/hooks.d.ts +50 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +173 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/server/adkEventStream.d.ts +42 -0
- package/dist/server/adkEventStream.d.ts.map +1 -0
- package/dist/server/adkEventStream.js +135 -0
- package/dist/server/adkEventStream.js.map +1 -0
- package/dist/server/createAdkApiRoute.d.ts +47 -0
- package/dist/server/createAdkApiRoute.d.ts.map +1 -0
- package/dist/server/createAdkApiRoute.js +41 -0
- package/dist/server/createAdkApiRoute.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/parseAdkRequest.d.ts +56 -0
- package/dist/server/parseAdkRequest.d.ts.map +1 -0
- package/dist/server/parseAdkRequest.js +93 -0
- package/dist/server/parseAdkRequest.js.map +1 -0
- package/dist/structuredEvents.d.ts +7 -0
- package/dist/structuredEvents.d.ts.map +1 -0
- package/dist/structuredEvents.js +79 -0
- package/dist/structuredEvents.js.map +1 -0
- package/dist/types.d.ts +253 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/useAdkMessages.d.ts +28 -0
- package/dist/useAdkMessages.d.ts.map +1 -0
- package/dist/useAdkMessages.js +198 -0
- package/dist/useAdkMessages.js.map +1 -0
- package/dist/useAdkRuntime.d.ts +36 -0
- package/dist/useAdkRuntime.d.ts.map +1 -0
- package/dist/useAdkRuntime.js +252 -0
- package/dist/useAdkRuntime.js.map +1 -0
- package/package.json +83 -0
- package/server/package.json +4 -0
- package/src/AdkClient.test.ts +662 -0
- package/src/AdkClient.ts +274 -0
- package/src/AdkEventAccumulator.test.ts +591 -0
- package/src/AdkEventAccumulator.ts +602 -0
- package/src/AdkSessionAdapter.test.ts +362 -0
- package/src/AdkSessionAdapter.ts +245 -0
- package/src/convertAdkMessages.test.ts +209 -0
- package/src/convertAdkMessages.ts +93 -0
- package/src/hooks.ts +217 -0
- package/src/index.ts +66 -0
- package/src/server/adkEventStream.test.ts +78 -0
- package/src/server/adkEventStream.ts +161 -0
- package/src/server/createAdkApiRoute.test.ts +370 -0
- package/src/server/createAdkApiRoute.ts +86 -0
- package/src/server/index.ts +6 -0
- package/src/server/parseAdkRequest.test.ts +152 -0
- package/src/server/parseAdkRequest.ts +122 -0
- package/src/structuredEvents.ts +81 -0
- package/src/types.ts +265 -0
- package/src/useAdkMessages.ts +259 -0
- package/src/useAdkRuntime.ts +398 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { convertAdkMessage } from "./convertAdkMessages";
|
|
3
|
+
import type { AdkMessage } from "./types";
|
|
4
|
+
|
|
5
|
+
describe("convertAdkMessage - human messages", () => {
|
|
6
|
+
it("converts a human message with string content to user role", () => {
|
|
7
|
+
const msg: AdkMessage = { id: "m1", type: "human", content: "Hello" };
|
|
8
|
+
const result = convertAdkMessage(msg);
|
|
9
|
+
expect(result).toMatchObject({
|
|
10
|
+
role: "user",
|
|
11
|
+
id: "m1",
|
|
12
|
+
content: [{ type: "text", text: "Hello" }],
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("converts a human message with text content parts", () => {
|
|
17
|
+
const msg: AdkMessage = {
|
|
18
|
+
id: "m1",
|
|
19
|
+
type: "human",
|
|
20
|
+
content: [{ type: "text", text: "Hello" }],
|
|
21
|
+
};
|
|
22
|
+
const result = convertAdkMessage(msg);
|
|
23
|
+
expect(result).toMatchObject({
|
|
24
|
+
role: "user",
|
|
25
|
+
content: [{ type: "text", text: "Hello" }],
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("convertAdkMessage - ai messages", () => {
|
|
31
|
+
it("converts an ai message with text content", () => {
|
|
32
|
+
const msg: AdkMessage = {
|
|
33
|
+
id: "m1",
|
|
34
|
+
type: "ai",
|
|
35
|
+
content: [{ type: "text", text: "Hi there" }],
|
|
36
|
+
};
|
|
37
|
+
const result = convertAdkMessage(msg);
|
|
38
|
+
expect(result).toMatchObject({
|
|
39
|
+
role: "assistant",
|
|
40
|
+
id: "m1",
|
|
41
|
+
content: [{ type: "text", text: "Hi there" }],
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("converts reasoning content parts", () => {
|
|
46
|
+
const msg: AdkMessage = {
|
|
47
|
+
id: "m1",
|
|
48
|
+
type: "ai",
|
|
49
|
+
content: [{ type: "reasoning", text: "Let me think..." }],
|
|
50
|
+
};
|
|
51
|
+
const result = convertAdkMessage(msg);
|
|
52
|
+
expect(result).toMatchObject({
|
|
53
|
+
content: [{ type: "reasoning", text: "Let me think..." }],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("converts image content parts to data URI", () => {
|
|
58
|
+
const msg: AdkMessage = {
|
|
59
|
+
id: "m1",
|
|
60
|
+
type: "ai",
|
|
61
|
+
content: [{ type: "image", mimeType: "image/png", data: "abc123" }],
|
|
62
|
+
};
|
|
63
|
+
const result = convertAdkMessage(msg);
|
|
64
|
+
expect(result).toMatchObject({
|
|
65
|
+
content: [{ type: "image", image: "data:image/png;base64,abc123" }],
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("converts image_url content parts", () => {
|
|
70
|
+
const msg: AdkMessage = {
|
|
71
|
+
id: "m1",
|
|
72
|
+
type: "ai",
|
|
73
|
+
content: [{ type: "image_url", url: "https://example.com/img.png" }],
|
|
74
|
+
};
|
|
75
|
+
const result = convertAdkMessage(msg);
|
|
76
|
+
expect(result).toMatchObject({
|
|
77
|
+
content: [{ type: "image", image: "https://example.com/img.png" }],
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("converts code content parts to data parts", () => {
|
|
82
|
+
const msg: AdkMessage = {
|
|
83
|
+
id: "m1",
|
|
84
|
+
type: "ai",
|
|
85
|
+
content: [{ type: "code", code: "print(1)", language: "python" }],
|
|
86
|
+
};
|
|
87
|
+
const result = convertAdkMessage(msg);
|
|
88
|
+
expect(result).toMatchObject({
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: "data",
|
|
92
|
+
name: "executable_code",
|
|
93
|
+
data: { code: "print(1)", language: "python" },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("converts code_result content parts to data parts", () => {
|
|
100
|
+
const msg: AdkMessage = {
|
|
101
|
+
id: "m1",
|
|
102
|
+
type: "ai",
|
|
103
|
+
content: [{ type: "code_result", output: "1", outcome: "OUTCOME_OK" }],
|
|
104
|
+
};
|
|
105
|
+
const result = convertAdkMessage(msg);
|
|
106
|
+
expect(result).toMatchObject({
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "data",
|
|
110
|
+
name: "code_execution_result",
|
|
111
|
+
data: { output: "1", outcome: "OUTCOME_OK" },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("converts tool_calls to tool-call content parts", () => {
|
|
118
|
+
const msg: AdkMessage = {
|
|
119
|
+
id: "m1",
|
|
120
|
+
type: "ai",
|
|
121
|
+
content: [],
|
|
122
|
+
tool_calls: [{ id: "tc-1", name: "search", args: { q: "test" } }],
|
|
123
|
+
};
|
|
124
|
+
const result = convertAdkMessage(msg);
|
|
125
|
+
expect(result).toMatchObject({
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "tool-call",
|
|
129
|
+
toolCallId: "tc-1",
|
|
130
|
+
toolName: "search",
|
|
131
|
+
args: { q: "test" },
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("includes status when present", () => {
|
|
138
|
+
const msg: AdkMessage = {
|
|
139
|
+
id: "m1",
|
|
140
|
+
type: "ai",
|
|
141
|
+
content: [{ type: "text", text: "done" }],
|
|
142
|
+
status: { type: "complete", reason: "stop" },
|
|
143
|
+
};
|
|
144
|
+
const result = convertAdkMessage(msg);
|
|
145
|
+
expect(result).toMatchObject({
|
|
146
|
+
status: { type: "complete", reason: "stop" },
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("includes author/branch metadata when author is set", () => {
|
|
151
|
+
const msg: AdkMessage = {
|
|
152
|
+
id: "m1",
|
|
153
|
+
type: "ai",
|
|
154
|
+
content: [{ type: "text", text: "hi" }],
|
|
155
|
+
author: "search_agent",
|
|
156
|
+
branch: "root.search_agent",
|
|
157
|
+
};
|
|
158
|
+
const result = convertAdkMessage(msg);
|
|
159
|
+
expect(result).toMatchObject({
|
|
160
|
+
metadata: {
|
|
161
|
+
custom: { author: "search_agent", branch: "root.search_agent" },
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("omits metadata when author is not set", () => {
|
|
167
|
+
const msg: AdkMessage = {
|
|
168
|
+
id: "m1",
|
|
169
|
+
type: "ai",
|
|
170
|
+
content: [{ type: "text", text: "hi" }],
|
|
171
|
+
};
|
|
172
|
+
const result = convertAdkMessage(msg);
|
|
173
|
+
expect(result).not.toHaveProperty("metadata");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("convertAdkMessage - tool messages", () => {
|
|
178
|
+
it("converts a tool message to tool role", () => {
|
|
179
|
+
const msg: AdkMessage = {
|
|
180
|
+
id: "m1",
|
|
181
|
+
type: "tool",
|
|
182
|
+
tool_call_id: "tc-1",
|
|
183
|
+
name: "search",
|
|
184
|
+
content: '{"results":[]}',
|
|
185
|
+
status: "success",
|
|
186
|
+
};
|
|
187
|
+
const result = convertAdkMessage(msg);
|
|
188
|
+
expect(result).toMatchObject({
|
|
189
|
+
role: "tool",
|
|
190
|
+
toolCallId: "tc-1",
|
|
191
|
+
toolName: "search",
|
|
192
|
+
result: '{"results":[]}',
|
|
193
|
+
isError: false,
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("sets isError to true when status is error", () => {
|
|
198
|
+
const msg: AdkMessage = {
|
|
199
|
+
id: "m1",
|
|
200
|
+
type: "tool",
|
|
201
|
+
tool_call_id: "tc-1",
|
|
202
|
+
name: "search",
|
|
203
|
+
content: "Failed",
|
|
204
|
+
status: "error",
|
|
205
|
+
};
|
|
206
|
+
const result = convertAdkMessage(msg);
|
|
207
|
+
expect(result).toMatchObject({ isError: true });
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ToolCallMessagePart } from "@assistant-ui/core";
|
|
4
|
+
import { useExternalMessageConverter } from "@assistant-ui/core/react";
|
|
5
|
+
import type { AdkMessage, AdkMessageContentPart } from "./types";
|
|
6
|
+
|
|
7
|
+
type ContentPart =
|
|
8
|
+
| { type: "text"; text: string }
|
|
9
|
+
| { type: "reasoning"; text: string }
|
|
10
|
+
| { type: "image"; image: string }
|
|
11
|
+
| { type: "data"; name: string; data: unknown };
|
|
12
|
+
|
|
13
|
+
const contentToParts = (content: AdkMessage["content"]): ContentPart[] => {
|
|
14
|
+
if (typeof content === "string")
|
|
15
|
+
return [{ type: "text" as const, text: content }];
|
|
16
|
+
|
|
17
|
+
return (content as AdkMessageContentPart[])
|
|
18
|
+
.map((part): ContentPart | null => {
|
|
19
|
+
switch (part.type) {
|
|
20
|
+
case "text":
|
|
21
|
+
return { type: "text", text: part.text };
|
|
22
|
+
case "reasoning":
|
|
23
|
+
return { type: "reasoning", text: part.text };
|
|
24
|
+
case "image":
|
|
25
|
+
return {
|
|
26
|
+
type: "image",
|
|
27
|
+
image: `data:${part.mimeType};base64,${part.data}`,
|
|
28
|
+
};
|
|
29
|
+
case "image_url":
|
|
30
|
+
return { type: "image", image: part.url };
|
|
31
|
+
case "code":
|
|
32
|
+
return {
|
|
33
|
+
type: "data",
|
|
34
|
+
name: "executable_code",
|
|
35
|
+
data: { code: part.code, language: part.language },
|
|
36
|
+
};
|
|
37
|
+
case "code_result":
|
|
38
|
+
return {
|
|
39
|
+
type: "data",
|
|
40
|
+
name: "code_execution_result",
|
|
41
|
+
data: { output: part.output, outcome: part.outcome },
|
|
42
|
+
};
|
|
43
|
+
default:
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.filter((p): p is NonNullable<typeof p> => p !== null);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const convertAdkMessage: useExternalMessageConverter.Callback<
|
|
51
|
+
AdkMessage
|
|
52
|
+
> = (message) => {
|
|
53
|
+
switch (message.type) {
|
|
54
|
+
case "human":
|
|
55
|
+
return {
|
|
56
|
+
role: "user",
|
|
57
|
+
id: message.id,
|
|
58
|
+
content: contentToParts(message.content),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
case "ai": {
|
|
62
|
+
const toolCallParts: ToolCallMessagePart[] =
|
|
63
|
+
message.tool_calls?.map((tc) => ({
|
|
64
|
+
type: "tool-call",
|
|
65
|
+
toolCallId: tc.id,
|
|
66
|
+
toolName: tc.name,
|
|
67
|
+
args: tc.args,
|
|
68
|
+
argsText: tc.argsText ?? JSON.stringify(tc.args),
|
|
69
|
+
})) ?? [];
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
role: "assistant",
|
|
73
|
+
id: message.id,
|
|
74
|
+
content: [...contentToParts(message.content), ...toolCallParts],
|
|
75
|
+
...(message.status && { status: message.status }),
|
|
76
|
+
...(message.author && {
|
|
77
|
+
metadata: {
|
|
78
|
+
custom: { author: message.author, branch: message.branch },
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
case "tool":
|
|
85
|
+
return {
|
|
86
|
+
role: "tool",
|
|
87
|
+
toolCallId: message.tool_call_id,
|
|
88
|
+
toolName: message.name,
|
|
89
|
+
result: message.content,
|
|
90
|
+
isError: message.status === "error",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
};
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/// <reference types="@assistant-ui/core/store" />
|
|
2
|
+
|
|
3
|
+
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
5
|
+
import type {
|
|
6
|
+
AdkMessage,
|
|
7
|
+
AdkSendMessageConfig,
|
|
8
|
+
AdkToolConfirmation,
|
|
9
|
+
AdkAuthCredential,
|
|
10
|
+
AdkAuthRequest,
|
|
11
|
+
AdkMessageMetadata,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
export const symbolAdkRuntimeExtras = Symbol("adk-runtime-extras");
|
|
15
|
+
|
|
16
|
+
export type AdkRuntimeExtras = {
|
|
17
|
+
[symbolAdkRuntimeExtras]: true;
|
|
18
|
+
send: (messages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
|
|
19
|
+
agentInfo: { name?: string | undefined; branch?: string | undefined };
|
|
20
|
+
stateDelta: Record<string, unknown>;
|
|
21
|
+
artifactDelta: Record<string, number>;
|
|
22
|
+
longRunningToolIds: string[];
|
|
23
|
+
toolConfirmations: AdkToolConfirmation[];
|
|
24
|
+
authRequests: AdkAuthRequest[];
|
|
25
|
+
escalated: boolean;
|
|
26
|
+
messageMetadata: Map<string, AdkMessageMetadata>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const asAdkRuntimeExtras = (extras: unknown): AdkRuntimeExtras => {
|
|
30
|
+
if (
|
|
31
|
+
typeof extras !== "object" ||
|
|
32
|
+
extras == null ||
|
|
33
|
+
!(symbolAdkRuntimeExtras in extras)
|
|
34
|
+
)
|
|
35
|
+
throw new Error(
|
|
36
|
+
"This method can only be called when you are using useAdkRuntime",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return extras as AdkRuntimeExtras;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Returns the name and branch of the currently active ADK agent. */
|
|
43
|
+
export const useAdkAgentInfo = () => {
|
|
44
|
+
return useAuiState((s) => {
|
|
45
|
+
const extras = s.thread.extras;
|
|
46
|
+
if (!extras) return undefined;
|
|
47
|
+
return asAdkRuntimeExtras(extras).agentInfo;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Returns the accumulated session state delta from ADK events. */
|
|
52
|
+
export const useAdkSessionState = () => {
|
|
53
|
+
return useAuiState((s) => {
|
|
54
|
+
const extras = s.thread.extras;
|
|
55
|
+
if (!extras) return {};
|
|
56
|
+
return asAdkRuntimeExtras(extras).stateDelta;
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Returns a function to send raw ADK messages. */
|
|
61
|
+
export const useAdkSend = () => {
|
|
62
|
+
const aui = useAui();
|
|
63
|
+
return (messages: AdkMessage[], config: AdkSendMessageConfig) => {
|
|
64
|
+
const extras = aui.thread().getState().extras;
|
|
65
|
+
const { send } = asAdkRuntimeExtras(extras);
|
|
66
|
+
return send(messages, config);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Returns the IDs of long-running tools awaiting external input. */
|
|
71
|
+
export const useAdkLongRunningToolIds = () => {
|
|
72
|
+
return useAuiState((s) => {
|
|
73
|
+
const extras = s.thread.extras;
|
|
74
|
+
if (!extras) return [];
|
|
75
|
+
return asAdkRuntimeExtras(extras).longRunningToolIds;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Returns pending tool confirmation requests (from SecurityPlugin etc). */
|
|
80
|
+
export const useAdkToolConfirmations = () => {
|
|
81
|
+
return useAuiState((s) => {
|
|
82
|
+
const extras = s.thread.extras;
|
|
83
|
+
if (!extras) return [];
|
|
84
|
+
return asAdkRuntimeExtras(extras).toolConfirmations;
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Returns pending auth credential requests from tools. */
|
|
89
|
+
export const useAdkAuthRequests = () => {
|
|
90
|
+
return useAuiState((s) => {
|
|
91
|
+
const extras = s.thread.extras;
|
|
92
|
+
if (!extras) return [];
|
|
93
|
+
return asAdkRuntimeExtras(extras).authRequests;
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Returns the accumulated artifact delta (filename → version). */
|
|
98
|
+
export const useAdkArtifacts = () => {
|
|
99
|
+
return useAuiState((s) => {
|
|
100
|
+
const extras = s.thread.extras;
|
|
101
|
+
if (!extras) return {};
|
|
102
|
+
return asAdkRuntimeExtras(extras).artifactDelta;
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Returns whether any agent has escalated (requested human handoff). */
|
|
107
|
+
export const useAdkEscalation = () => {
|
|
108
|
+
return useAuiState((s) => {
|
|
109
|
+
const extras = s.thread.extras;
|
|
110
|
+
if (!extras) return false;
|
|
111
|
+
return asAdkRuntimeExtras(extras).escalated;
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Returns per-message metadata (grounding, citation, usage). Keyed by message ID. */
|
|
116
|
+
export const useAdkMessageMetadata = () => {
|
|
117
|
+
return useAuiState((s) => {
|
|
118
|
+
const extras = s.thread.extras;
|
|
119
|
+
if (!extras) return new Map<string, AdkMessageMetadata>();
|
|
120
|
+
return asAdkRuntimeExtras(extras).messageMetadata;
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// ── Convenience helpers for interactive flows ──
|
|
125
|
+
|
|
126
|
+
/** Returns a function to confirm or deny a pending tool confirmation. */
|
|
127
|
+
export const useAdkConfirmTool = () => {
|
|
128
|
+
const aui = useAui();
|
|
129
|
+
return (toolCallId: string, confirmed: boolean, payload?: unknown) => {
|
|
130
|
+
const extras = aui.thread().getState().extras;
|
|
131
|
+
const { send } = asAdkRuntimeExtras(extras);
|
|
132
|
+
return send(
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
id: uuidv4(),
|
|
136
|
+
type: "tool",
|
|
137
|
+
tool_call_id: toolCallId,
|
|
138
|
+
name: "adk_request_confirmation",
|
|
139
|
+
content: JSON.stringify({
|
|
140
|
+
confirmed,
|
|
141
|
+
...(payload != null && { payload }),
|
|
142
|
+
}),
|
|
143
|
+
status: "success",
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
{},
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Returns a function to submit auth credentials for a pending auth request. */
|
|
152
|
+
export const useAdkSubmitAuth = () => {
|
|
153
|
+
const aui = useAui();
|
|
154
|
+
return (toolCallId: string, credential: AdkAuthCredential) => {
|
|
155
|
+
const extras = aui.thread().getState().extras;
|
|
156
|
+
const { send } = asAdkRuntimeExtras(extras);
|
|
157
|
+
return send(
|
|
158
|
+
[
|
|
159
|
+
{
|
|
160
|
+
id: uuidv4(),
|
|
161
|
+
type: "tool",
|
|
162
|
+
tool_call_id: toolCallId,
|
|
163
|
+
name: "adk_request_credential",
|
|
164
|
+
content: JSON.stringify(credential),
|
|
165
|
+
status: "success",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
{},
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// ── State prefix helpers ──
|
|
174
|
+
|
|
175
|
+
const APP_PREFIX = "app:";
|
|
176
|
+
const USER_PREFIX = "user:";
|
|
177
|
+
const TEMP_PREFIX = "temp:";
|
|
178
|
+
|
|
179
|
+
const filterByPrefix = (
|
|
180
|
+
state: Record<string, unknown>,
|
|
181
|
+
prefix: string,
|
|
182
|
+
): Record<string, unknown> => {
|
|
183
|
+
const result: Record<string, unknown> = {};
|
|
184
|
+
for (const key of Object.keys(state)) {
|
|
185
|
+
if (key.startsWith(prefix)) {
|
|
186
|
+
result[key.slice(prefix.length)] = state[key];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Returns app-level state (keys prefixed with `app:`, prefix stripped). */
|
|
193
|
+
export const useAdkAppState = () => {
|
|
194
|
+
return useAuiState((s) => {
|
|
195
|
+
const extras = s.thread.extras;
|
|
196
|
+
if (!extras) return {};
|
|
197
|
+
return filterByPrefix(asAdkRuntimeExtras(extras).stateDelta, APP_PREFIX);
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/** Returns user-level state (keys prefixed with `user:`, prefix stripped). */
|
|
202
|
+
export const useAdkUserState = () => {
|
|
203
|
+
return useAuiState((s) => {
|
|
204
|
+
const extras = s.thread.extras;
|
|
205
|
+
if (!extras) return {};
|
|
206
|
+
return filterByPrefix(asAdkRuntimeExtras(extras).stateDelta, USER_PREFIX);
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** Returns temp state (keys prefixed with `temp:`, prefix stripped). Not persisted. */
|
|
211
|
+
export const useAdkTempState = () => {
|
|
212
|
+
return useAuiState((s) => {
|
|
213
|
+
const extras = s.thread.extras;
|
|
214
|
+
if (!extras) return {};
|
|
215
|
+
return filterByPrefix(asAdkRuntimeExtras(extras).stateDelta, TEMP_PREFIX);
|
|
216
|
+
});
|
|
217
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export {
|
|
2
|
+
useAdkRuntime,
|
|
3
|
+
type UseAdkRuntimeOptions,
|
|
4
|
+
} from "./useAdkRuntime";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
useAdkMessages,
|
|
8
|
+
type UseAdkMessagesOptions,
|
|
9
|
+
} from "./useAdkMessages";
|
|
10
|
+
|
|
11
|
+
export { convertAdkMessage } from "./convertAdkMessages";
|
|
12
|
+
|
|
13
|
+
export type {
|
|
14
|
+
AdkEvent,
|
|
15
|
+
AdkEventPart,
|
|
16
|
+
AdkEventActions,
|
|
17
|
+
AdkMessage,
|
|
18
|
+
AdkMessageContentPart,
|
|
19
|
+
AdkToolCall,
|
|
20
|
+
AdkToolConfirmation,
|
|
21
|
+
AdkAuthCredential,
|
|
22
|
+
AdkAuthCredentialType,
|
|
23
|
+
AdkAuthRequest,
|
|
24
|
+
AdkMessageMetadata,
|
|
25
|
+
AdkRunConfig,
|
|
26
|
+
AdkSendMessageConfig,
|
|
27
|
+
AdkStreamCallback,
|
|
28
|
+
AdkStructuredEvent,
|
|
29
|
+
OnAdkErrorCallback,
|
|
30
|
+
OnAdkCustomEventCallback,
|
|
31
|
+
OnAdkAgentTransferCallback,
|
|
32
|
+
} from "./types";
|
|
33
|
+
|
|
34
|
+
export { AdkEventType } from "./types";
|
|
35
|
+
|
|
36
|
+
export { toAdkStructuredEvents } from "./structuredEvents";
|
|
37
|
+
|
|
38
|
+
export { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
createAdkStream,
|
|
42
|
+
type CreateAdkStreamOptions,
|
|
43
|
+
} from "./AdkClient";
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
createAdkSessionAdapter,
|
|
47
|
+
type AdkSessionAdapterOptions,
|
|
48
|
+
type AdkArtifactData,
|
|
49
|
+
} from "./AdkSessionAdapter";
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
useAdkAgentInfo,
|
|
53
|
+
useAdkSessionState,
|
|
54
|
+
useAdkSend,
|
|
55
|
+
useAdkLongRunningToolIds,
|
|
56
|
+
useAdkToolConfirmations,
|
|
57
|
+
useAdkAuthRequests,
|
|
58
|
+
useAdkArtifacts,
|
|
59
|
+
useAdkEscalation,
|
|
60
|
+
useAdkMessageMetadata,
|
|
61
|
+
useAdkConfirmTool,
|
|
62
|
+
useAdkSubmitAuth,
|
|
63
|
+
useAdkAppState,
|
|
64
|
+
useAdkUserState,
|
|
65
|
+
useAdkTempState,
|
|
66
|
+
} from "./hooks";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { adkEventStream } from "./adkEventStream";
|
|
3
|
+
|
|
4
|
+
async function* yieldEvents(events: unknown[]) {
|
|
5
|
+
for (const event of events) {
|
|
6
|
+
yield event as any;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function readSSE(response: Response): Promise<string> {
|
|
11
|
+
const reader = response.body!.getReader();
|
|
12
|
+
const decoder = new TextDecoder();
|
|
13
|
+
let result = "";
|
|
14
|
+
while (true) {
|
|
15
|
+
const { done, value } = await reader.read();
|
|
16
|
+
if (done) break;
|
|
17
|
+
result += decoder.decode(value, { stream: true });
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("adkEventStream", () => {
|
|
23
|
+
it("returns a Response with text/event-stream content-type", () => {
|
|
24
|
+
const response = adkEventStream(yieldEvents([]));
|
|
25
|
+
expect(response.headers.get("Content-Type")).toBe("text/event-stream");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns Cache-Control: no-cache header", () => {
|
|
29
|
+
const response = adkEventStream(yieldEvents([]));
|
|
30
|
+
expect(response.headers.get("Cache-Control")).toBe("no-cache");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("emits an initial :ok SSE comment", async () => {
|
|
34
|
+
const response = adkEventStream(yieldEvents([]));
|
|
35
|
+
const text = await readSSE(response);
|
|
36
|
+
expect(text).toMatch(/^:ok\n\n/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("serializes events as SSE data lines", async () => {
|
|
40
|
+
const events = [
|
|
41
|
+
{ id: "e1", content: { parts: [{ text: "hello" }] } },
|
|
42
|
+
{ id: "e2", content: { parts: [{ text: "world" }] } },
|
|
43
|
+
];
|
|
44
|
+
const response = adkEventStream(yieldEvents(events));
|
|
45
|
+
const text = await readSSE(response);
|
|
46
|
+
const lines = text.split("\n\n").filter((l) => l.startsWith("data: "));
|
|
47
|
+
expect(lines).toHaveLength(2);
|
|
48
|
+
expect(JSON.parse(lines[0]!.slice(6))).toMatchObject({ id: "e1" });
|
|
49
|
+
expect(JSON.parse(lines[1]!.slice(6))).toMatchObject({ id: "e2" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("emits an error event when the generator throws", async () => {
|
|
53
|
+
// biome-ignore lint/correctness/useYield: intentionally throws before yielding
|
|
54
|
+
async function* throwingGen() {
|
|
55
|
+
throw new Error("Test error");
|
|
56
|
+
}
|
|
57
|
+
const response = adkEventStream(throwingGen() as any);
|
|
58
|
+
const text = await readSSE(response);
|
|
59
|
+
const lines = text.split("\n\n").filter((l) => l.startsWith("data: "));
|
|
60
|
+
expect(lines).toHaveLength(1);
|
|
61
|
+
const errorEvent = JSON.parse(lines[0]!.slice(6));
|
|
62
|
+
expect(errorEvent).toMatchObject({
|
|
63
|
+
errorCode: "STREAM_ERROR",
|
|
64
|
+
errorMessage: "Test error",
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("calls onError callback when the generator throws", async () => {
|
|
69
|
+
const onError = vi.fn();
|
|
70
|
+
// biome-ignore lint/correctness/useYield: intentionally throws before yielding
|
|
71
|
+
async function* throwingGen() {
|
|
72
|
+
throw new Error("Test error");
|
|
73
|
+
}
|
|
74
|
+
const response = adkEventStream(throwingGen() as any, { onError });
|
|
75
|
+
await readSSE(response);
|
|
76
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
77
|
+
});
|
|
78
|
+
});
|