@assistant-ui/react-google-adk 0.0.17 → 0.0.19
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/AdkClient.js +11 -0
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +54 -9
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +1 -1
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.js +4 -4
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +6 -6
- package/src/AdkClient.test.ts +133 -65
- package/src/AdkClient.ts +26 -0
- package/src/AdkEventAccumulator.test.ts +0 -36
- package/src/AdkSessionAdapter.test.ts +200 -11
- package/src/AdkSessionAdapter.ts +141 -18
- package/src/useAdkMessages.ts +1 -1
- package/src/useAdkRuntime.ts +4 -4
- package/src/server/createAdkApiRoute.test.ts +0 -370
package/src/AdkClient.test.ts
CHANGED
|
@@ -34,6 +34,17 @@ const sseBody = (text: string): ReadableStream<Uint8Array> => {
|
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
const sseResponse = (
|
|
38
|
+
body: BodyInit | null,
|
|
39
|
+
contentType: string | null = "text/event-stream",
|
|
40
|
+
): Response =>
|
|
41
|
+
new Response(body, {
|
|
42
|
+
status: 200,
|
|
43
|
+
...(contentType === null
|
|
44
|
+
? {}
|
|
45
|
+
: { headers: { "Content-Type": contentType } }),
|
|
46
|
+
});
|
|
47
|
+
|
|
37
48
|
const nextWithTimeout = async <T>(
|
|
38
49
|
promise: Promise<IteratorResult<T>>,
|
|
39
50
|
): Promise<IteratorResult<T> | "timeout"> => {
|
|
@@ -57,7 +68,7 @@ beforeEach(() => {
|
|
|
57
68
|
|
|
58
69
|
describe("createAdkStream - proxy mode", () => {
|
|
59
70
|
it("POSTs to the api URL directly", async () => {
|
|
60
|
-
mockFetch.mockResolvedValueOnce(
|
|
71
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
61
72
|
|
|
62
73
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
63
74
|
const messages: AdkMessage[] = [
|
|
@@ -78,7 +89,7 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
78
89
|
});
|
|
79
90
|
|
|
80
91
|
it("sends runConfig and checkpointId in proxy body", async () => {
|
|
81
|
-
mockFetch.mockResolvedValueOnce(
|
|
92
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
82
93
|
|
|
83
94
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
84
95
|
const gen = await stream(
|
|
@@ -95,7 +106,7 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
95
106
|
});
|
|
96
107
|
|
|
97
108
|
it("sends a tool-result body when message type is tool", async () => {
|
|
98
|
-
mockFetch.mockResolvedValueOnce(
|
|
109
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
99
110
|
|
|
100
111
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
101
112
|
const messages: AdkMessage[] = [
|
|
@@ -122,7 +133,7 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
122
133
|
});
|
|
123
134
|
|
|
124
135
|
it("sends parts when message has multimodal content", async () => {
|
|
125
|
-
mockFetch.mockResolvedValueOnce(
|
|
136
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
126
137
|
|
|
127
138
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
128
139
|
const messages: AdkMessage[] = [
|
|
@@ -149,7 +160,7 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
149
160
|
});
|
|
150
161
|
|
|
151
162
|
it("sends file parts as inlineData in proxy mode", async () => {
|
|
152
|
-
mockFetch.mockResolvedValueOnce(
|
|
163
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
153
164
|
|
|
154
165
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
155
166
|
const messages: AdkMessage[] = [
|
|
@@ -180,7 +191,7 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
180
191
|
});
|
|
181
192
|
|
|
182
193
|
it("sends parts array when multiple messages are provided", async () => {
|
|
183
|
-
mockFetch.mockResolvedValueOnce(
|
|
194
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
184
195
|
|
|
185
196
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
186
197
|
const messages: AdkMessage[] = [
|
|
@@ -205,7 +216,7 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
205
216
|
});
|
|
206
217
|
|
|
207
218
|
it("marks isError=true when tool status is error", async () => {
|
|
208
|
-
mockFetch.mockResolvedValueOnce(
|
|
219
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
209
220
|
|
|
210
221
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
211
222
|
const messages: AdkMessage[] = [
|
|
@@ -231,8 +242,35 @@ describe("createAdkStream - proxy mode", () => {
|
|
|
231
242
|
// ── Direct mode ──
|
|
232
243
|
|
|
233
244
|
describe("createAdkStream - direct mode", () => {
|
|
245
|
+
it("rejects direct mode with an empty appName", () => {
|
|
246
|
+
expect(() =>
|
|
247
|
+
createAdkStream({
|
|
248
|
+
api: "http://localhost:8000",
|
|
249
|
+
appName: "",
|
|
250
|
+
userId: "user-1",
|
|
251
|
+
}),
|
|
252
|
+
).toThrow('createAdkStream direct mode requires a non-empty "appName".');
|
|
253
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it.each([
|
|
257
|
+
["missing", undefined],
|
|
258
|
+
["empty", ""],
|
|
259
|
+
])("rejects direct mode with a %s userId", (_label, userId) => {
|
|
260
|
+
expect(() =>
|
|
261
|
+
createAdkStream({
|
|
262
|
+
api: "http://localhost:8000",
|
|
263
|
+
appName: "my-app",
|
|
264
|
+
userId,
|
|
265
|
+
}),
|
|
266
|
+
).toThrow(
|
|
267
|
+
'createAdkStream direct mode requires "userId" when "appName" is provided.',
|
|
268
|
+
);
|
|
269
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
270
|
+
});
|
|
271
|
+
|
|
234
272
|
it("POSTs to /run_sse with ADK-native body", async () => {
|
|
235
|
-
mockFetch.mockResolvedValueOnce(
|
|
273
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
236
274
|
|
|
237
275
|
const stream = createAdkStream({
|
|
238
276
|
api: "http://localhost:8000",
|
|
@@ -264,9 +302,7 @@ describe("createAdkStream - direct mode", () => {
|
|
|
264
302
|
it.each(["http://localhost:8000/", "http://localhost:8000//"])(
|
|
265
303
|
"normalizes trailing slashes in the api URL: %s",
|
|
266
304
|
async (api) => {
|
|
267
|
-
mockFetch.mockResolvedValueOnce(
|
|
268
|
-
new Response(sseBody(""), { status: 200 }),
|
|
269
|
-
);
|
|
305
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
270
306
|
|
|
271
307
|
const stream = createAdkStream({
|
|
272
308
|
api,
|
|
@@ -289,7 +325,7 @@ describe("createAdkStream - direct mode", () => {
|
|
|
289
325
|
const initialize = vi
|
|
290
326
|
.fn()
|
|
291
327
|
.mockResolvedValue({ remoteId: "r1", externalId: "s-42" });
|
|
292
|
-
mockFetch.mockResolvedValueOnce(
|
|
328
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
293
329
|
|
|
294
330
|
const stream = createAdkStream({
|
|
295
331
|
api: "http://localhost:8000",
|
|
@@ -310,7 +346,7 @@ describe("createAdkStream - direct mode", () => {
|
|
|
310
346
|
});
|
|
311
347
|
|
|
312
348
|
it("converts tool messages to functionResponse parts", async () => {
|
|
313
|
-
mockFetch.mockResolvedValueOnce(
|
|
349
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
314
350
|
|
|
315
351
|
const stream = createAdkStream({
|
|
316
352
|
api: "http://localhost:8000",
|
|
@@ -342,7 +378,7 @@ describe("createAdkStream - direct mode", () => {
|
|
|
342
378
|
});
|
|
343
379
|
|
|
344
380
|
it("falls back to raw string when tool content is not valid JSON", async () => {
|
|
345
|
-
mockFetch.mockResolvedValueOnce(
|
|
381
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
346
382
|
|
|
347
383
|
const stream = createAdkStream({
|
|
348
384
|
api: "http://localhost:8000",
|
|
@@ -368,7 +404,7 @@ describe("createAdkStream - direct mode", () => {
|
|
|
368
404
|
});
|
|
369
405
|
|
|
370
406
|
it("sends empty text part when no messages provided", async () => {
|
|
371
|
-
mockFetch.mockResolvedValueOnce(
|
|
407
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
372
408
|
|
|
373
409
|
const stream = createAdkStream({
|
|
374
410
|
api: "http://localhost:8000",
|
|
@@ -394,9 +430,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
394
430
|
{ id: "e2", content: { parts: [{ text: "world" }] } },
|
|
395
431
|
];
|
|
396
432
|
const text = events.map((e) => `data: ${JSON.stringify(e)}\n\n`).join("");
|
|
397
|
-
mockFetch.mockResolvedValueOnce(
|
|
398
|
-
new Response(sseBody(text), { status: 200 }),
|
|
399
|
-
);
|
|
433
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody(text)));
|
|
400
434
|
|
|
401
435
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
402
436
|
const gen = await stream(
|
|
@@ -413,11 +447,29 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
413
447
|
expect(collected[1]!.id).toBe("e2");
|
|
414
448
|
});
|
|
415
449
|
|
|
416
|
-
it("
|
|
417
|
-
const
|
|
450
|
+
it("accepts parameterized event-stream content types", async () => {
|
|
451
|
+
const event: AdkEvent = { id: "e1" };
|
|
418
452
|
mockFetch.mockResolvedValueOnce(
|
|
419
|
-
|
|
453
|
+
sseResponse(
|
|
454
|
+
sseBody(`data: ${JSON.stringify(event)}\n\n`),
|
|
455
|
+
"Text/Event-Stream; charset=utf-8",
|
|
456
|
+
),
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
460
|
+
const gen = await stream(
|
|
461
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
462
|
+
makeConfig(),
|
|
420
463
|
);
|
|
464
|
+
const collected: AdkEvent[] = [];
|
|
465
|
+
for await (const value of gen) collected.push(value);
|
|
466
|
+
|
|
467
|
+
expect(collected).toEqual([event]);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("skips :ok SSE comments", async () => {
|
|
471
|
+
const text = `:ok\n\ndata: ${JSON.stringify({ id: "e1" })}\n\n`;
|
|
472
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody(text)));
|
|
421
473
|
|
|
422
474
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
423
475
|
const gen = await stream(
|
|
@@ -435,9 +487,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
435
487
|
|
|
436
488
|
it("parses data fields without a space after the colon", async () => {
|
|
437
489
|
const text = `data:${JSON.stringify({ id: "e1" })}\n\n`;
|
|
438
|
-
mockFetch.mockResolvedValueOnce(
|
|
439
|
-
new Response(sseBody(text), { status: 200 }),
|
|
440
|
-
);
|
|
490
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody(text)));
|
|
441
491
|
|
|
442
492
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
443
493
|
const gen = await stream(
|
|
@@ -456,9 +506,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
456
506
|
it("parses CR-delimited SSE events", async () => {
|
|
457
507
|
const events: AdkEvent[] = [{ id: "e1" }, { id: "e2" }];
|
|
458
508
|
const text = events.map((e) => `data: ${JSON.stringify(e)}\r\r`).join("");
|
|
459
|
-
mockFetch.mockResolvedValueOnce(
|
|
460
|
-
new Response(sseBody(text), { status: 200 }),
|
|
461
|
-
);
|
|
509
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody(text)));
|
|
462
510
|
|
|
463
511
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
464
512
|
const gen = await stream(
|
|
@@ -483,7 +531,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
483
531
|
controller = nextController;
|
|
484
532
|
},
|
|
485
533
|
});
|
|
486
|
-
mockFetch.mockResolvedValueOnce(
|
|
534
|
+
mockFetch.mockResolvedValueOnce(sseResponse(body));
|
|
487
535
|
|
|
488
536
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
489
537
|
const gen = await stream(
|
|
@@ -520,7 +568,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
520
568
|
controller.close();
|
|
521
569
|
},
|
|
522
570
|
});
|
|
523
|
-
mockFetch.mockResolvedValueOnce(
|
|
571
|
+
mockFetch.mockResolvedValueOnce(sseResponse(body));
|
|
524
572
|
|
|
525
573
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
526
574
|
const gen = await stream(
|
|
@@ -547,7 +595,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
547
595
|
controller.close();
|
|
548
596
|
},
|
|
549
597
|
});
|
|
550
|
-
mockFetch.mockResolvedValueOnce(
|
|
598
|
+
mockFetch.mockResolvedValueOnce(sseResponse(body));
|
|
551
599
|
|
|
552
600
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
553
601
|
const gen = await stream(
|
|
@@ -567,9 +615,7 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
567
615
|
// No trailing \n\n
|
|
568
616
|
const event = { id: "e1" };
|
|
569
617
|
const text = `data: ${JSON.stringify(event)}\n`;
|
|
570
|
-
mockFetch.mockResolvedValueOnce(
|
|
571
|
-
new Response(sseBody(text), { status: 200 }),
|
|
572
|
-
);
|
|
618
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody(text)));
|
|
573
619
|
|
|
574
620
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
575
621
|
const gen = await stream(
|
|
@@ -605,13 +651,57 @@ describe("createAdkStream - error handling", () => {
|
|
|
605
651
|
}
|
|
606
652
|
}).rejects.toThrow("ADK request failed: 404 Not Found");
|
|
607
653
|
});
|
|
654
|
+
|
|
655
|
+
it("rejects successful HTML responses and cancels their body", async () => {
|
|
656
|
+
const cancelBody = vi.fn();
|
|
657
|
+
const body = new ReadableStream<Uint8Array>({
|
|
658
|
+
start(controller) {
|
|
659
|
+
controller.enqueue(new TextEncoder().encode("<html>Sign in</html>"));
|
|
660
|
+
},
|
|
661
|
+
cancel: cancelBody,
|
|
662
|
+
});
|
|
663
|
+
mockFetch.mockResolvedValueOnce(
|
|
664
|
+
sseResponse(body, "text/html; charset=utf-8"),
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
668
|
+
await expect(async () => {
|
|
669
|
+
const gen = await stream(
|
|
670
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
671
|
+
makeConfig(),
|
|
672
|
+
);
|
|
673
|
+
for await (const _ of gen) {
|
|
674
|
+
/* noop */
|
|
675
|
+
}
|
|
676
|
+
}).rejects.toThrow(
|
|
677
|
+
'Expected ADK stream response Content-Type "text/event-stream", received "text/html; charset=utf-8"',
|
|
678
|
+
);
|
|
679
|
+
expect(cancelBody).toHaveBeenCalledOnce();
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it("rejects successful responses without a content type", async () => {
|
|
683
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody(""), null));
|
|
684
|
+
|
|
685
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
686
|
+
await expect(async () => {
|
|
687
|
+
const gen = await stream(
|
|
688
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
689
|
+
makeConfig(),
|
|
690
|
+
);
|
|
691
|
+
for await (const _ of gen) {
|
|
692
|
+
/* noop */
|
|
693
|
+
}
|
|
694
|
+
}).rejects.toThrow(
|
|
695
|
+
'Expected ADK stream response Content-Type "text/event-stream", received no Content-Type header',
|
|
696
|
+
);
|
|
697
|
+
});
|
|
608
698
|
});
|
|
609
699
|
|
|
610
700
|
// ── Headers ──
|
|
611
701
|
|
|
612
702
|
describe("createAdkStream - headers", () => {
|
|
613
703
|
it("sends static headers", async () => {
|
|
614
|
-
mockFetch.mockResolvedValueOnce(
|
|
704
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
615
705
|
|
|
616
706
|
const stream = createAdkStream({
|
|
617
707
|
api: "/api/adk",
|
|
@@ -633,30 +723,8 @@ describe("createAdkStream - headers", () => {
|
|
|
633
723
|
expect(headers["Content-Type"]).toBe("application/json");
|
|
634
724
|
});
|
|
635
725
|
|
|
636
|
-
it("resolves dynamic headers from a function", async () => {
|
|
637
|
-
mockFetch.mockResolvedValueOnce(new Response(sseBody(""), { status: 200 }));
|
|
638
|
-
|
|
639
|
-
const stream = createAdkStream({
|
|
640
|
-
api: "/api/adk",
|
|
641
|
-
headers: () => ({ "X-Custom": "dynamic" }),
|
|
642
|
-
});
|
|
643
|
-
const gen = await stream(
|
|
644
|
-
[{ id: "m1", type: "human", content: "Hi" }],
|
|
645
|
-
makeConfig(),
|
|
646
|
-
);
|
|
647
|
-
for await (const _ of gen) {
|
|
648
|
-
/* noop */
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const headers = mockFetch.mock.calls[0]![1]?.headers as Record<
|
|
652
|
-
string,
|
|
653
|
-
string
|
|
654
|
-
>;
|
|
655
|
-
expect(headers["X-Custom"]).toBe("dynamic");
|
|
656
|
-
});
|
|
657
|
-
|
|
658
726
|
it("resolves async dynamic headers", async () => {
|
|
659
|
-
mockFetch.mockResolvedValueOnce(
|
|
727
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
660
728
|
|
|
661
729
|
const stream = createAdkStream({
|
|
662
730
|
api: "/api/adk",
|
|
@@ -678,7 +746,7 @@ describe("createAdkStream - headers", () => {
|
|
|
678
746
|
});
|
|
679
747
|
|
|
680
748
|
it("sends no extra headers when headers option is undefined", async () => {
|
|
681
|
-
mockFetch.mockResolvedValueOnce(
|
|
749
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
682
750
|
|
|
683
751
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
684
752
|
const gen = await stream(
|
|
@@ -701,7 +769,7 @@ describe("createAdkStream - headers", () => {
|
|
|
701
769
|
|
|
702
770
|
describe("createAdkStream - AbortSignal", () => {
|
|
703
771
|
it("forwards the AbortSignal to fetch", async () => {
|
|
704
|
-
mockFetch.mockResolvedValueOnce(
|
|
772
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
705
773
|
|
|
706
774
|
const controller = new AbortController();
|
|
707
775
|
const stream = createAdkStream({ api: "/api/adk" });
|
|
@@ -721,7 +789,7 @@ describe("createAdkStream - AbortSignal", () => {
|
|
|
721
789
|
|
|
722
790
|
describe("createAdkStream - content conversion", () => {
|
|
723
791
|
it("converts reasoning content parts to thought parts in direct mode", async () => {
|
|
724
|
-
mockFetch.mockResolvedValueOnce(
|
|
792
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
725
793
|
|
|
726
794
|
const stream = createAdkStream({
|
|
727
795
|
api: "http://localhost:8000",
|
|
@@ -748,7 +816,7 @@ describe("createAdkStream - content conversion", () => {
|
|
|
748
816
|
});
|
|
749
817
|
|
|
750
818
|
it("converts image_url content parts to fileData in direct mode", async () => {
|
|
751
|
-
mockFetch.mockResolvedValueOnce(
|
|
819
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
752
820
|
|
|
753
821
|
const stream = createAdkStream({
|
|
754
822
|
api: "http://localhost:8000",
|
|
@@ -774,7 +842,7 @@ describe("createAdkStream - content conversion", () => {
|
|
|
774
842
|
});
|
|
775
843
|
|
|
776
844
|
it("converts file content parts to inlineData in direct mode", async () => {
|
|
777
|
-
mockFetch.mockResolvedValueOnce(
|
|
845
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
778
846
|
|
|
779
847
|
const stream = createAdkStream({
|
|
780
848
|
api: "http://localhost:8000",
|
|
@@ -807,7 +875,7 @@ describe("createAdkStream - content conversion", () => {
|
|
|
807
875
|
});
|
|
808
876
|
|
|
809
877
|
it("converts file_url content parts to fileData with mimeType in direct mode", async () => {
|
|
810
|
-
mockFetch.mockResolvedValueOnce(
|
|
878
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
811
879
|
|
|
812
880
|
const stream = createAdkStream({
|
|
813
881
|
api: "http://localhost:8000",
|
|
@@ -842,7 +910,7 @@ describe("createAdkStream - content conversion", () => {
|
|
|
842
910
|
});
|
|
843
911
|
|
|
844
912
|
it("converts code content parts to executableCode", async () => {
|
|
845
|
-
mockFetch.mockResolvedValueOnce(
|
|
913
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
846
914
|
|
|
847
915
|
const stream = createAdkStream({
|
|
848
916
|
api: "http://localhost:8000",
|
|
@@ -868,7 +936,7 @@ describe("createAdkStream - content conversion", () => {
|
|
|
868
936
|
});
|
|
869
937
|
|
|
870
938
|
it("converts code_result content parts to codeExecutionResult", async () => {
|
|
871
|
-
mockFetch.mockResolvedValueOnce(
|
|
939
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("")));
|
|
872
940
|
|
|
873
941
|
const stream = createAdkStream({
|
|
874
942
|
api: "http://localhost:8000",
|
package/src/AdkClient.ts
CHANGED
|
@@ -60,7 +60,18 @@ export type CreateAdkStreamOptions = {
|
|
|
60
60
|
export function createAdkStream(
|
|
61
61
|
options: CreateAdkStreamOptions,
|
|
62
62
|
): AdkStreamCallback {
|
|
63
|
+
if (options.appName === "") {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'createAdkStream direct mode requires a non-empty "appName".',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
const isDirect = options.appName != null;
|
|
70
|
+
if (isDirect && (options.userId == null || options.userId === "")) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'createAdkStream direct mode requires "userId" when "appName" is provided.',
|
|
73
|
+
);
|
|
74
|
+
}
|
|
64
75
|
|
|
65
76
|
return async function* (messages, config) {
|
|
66
77
|
const headers = await resolveHeaders(options.headers);
|
|
@@ -99,12 +110,27 @@ export function createAdkStream(
|
|
|
99
110
|
);
|
|
100
111
|
}
|
|
101
112
|
|
|
113
|
+
validateEventStreamContentType(response);
|
|
102
114
|
yield* parseSSEResponse(response);
|
|
103
115
|
};
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
// ── Internal helpers ──
|
|
107
119
|
|
|
120
|
+
function validateEventStreamContentType(response: Response): void {
|
|
121
|
+
const contentType = response.headers.get("Content-Type");
|
|
122
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
123
|
+
if (mediaType !== "text/event-stream") {
|
|
124
|
+
const received = contentType
|
|
125
|
+
? `"${contentType}"`
|
|
126
|
+
: "no Content-Type header";
|
|
127
|
+
void response.body?.cancel().catch(() => undefined);
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Expected ADK stream response Content-Type "text/event-stream", received ${received}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
async function resolveHeaders(
|
|
109
135
|
headers:
|
|
110
136
|
| Record<string, string>
|
|
@@ -452,13 +452,6 @@ describe("AdkEventAccumulator - HITL requires-action", () => {
|
|
|
452
452
|
expect(aiMsg.status).toBeUndefined();
|
|
453
453
|
});
|
|
454
454
|
|
|
455
|
-
it("matches tool call id to the longRunningToolIds entry", () => {
|
|
456
|
-
const acc = new AdkEventAccumulator();
|
|
457
|
-
const msgs = acc.processEvent(makeHitlEvent("tc-1"));
|
|
458
|
-
const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
|
|
459
|
-
expect(aiMsg.tool_calls![0]!.id).toBe("tc-1");
|
|
460
|
-
});
|
|
461
|
-
|
|
462
455
|
it("stays pending across a subsequent bookkeeping event", () => {
|
|
463
456
|
const acc = new AdkEventAccumulator();
|
|
464
457
|
acc.processEvent(makeHitlEvent("tc-1", { author: "WorkflowA" }));
|
|
@@ -480,35 +473,6 @@ describe("AdkEventAccumulator - HITL requires-action", () => {
|
|
|
480
473
|
expect(aiMsg!.status).toBeUndefined();
|
|
481
474
|
});
|
|
482
475
|
|
|
483
|
-
it("assigns manual complete status on non-HITL final event", () => {
|
|
484
|
-
const acc = new AdkEventAccumulator();
|
|
485
|
-
const msgs = acc.processEvent(
|
|
486
|
-
makeEvent({
|
|
487
|
-
author: "agent",
|
|
488
|
-
content: { role: "model", parts: [{ text: "Done." }] },
|
|
489
|
-
}),
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
expect(msgs[0]).toMatchObject({
|
|
493
|
-
status: { type: "complete", reason: "stop" },
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
it("assigns manual complete status on skipSummarization final event", () => {
|
|
498
|
-
const acc = new AdkEventAccumulator();
|
|
499
|
-
const msgs = acc.processEvent(
|
|
500
|
-
makeEvent({
|
|
501
|
-
author: "agent",
|
|
502
|
-
actions: { skipSummarization: true },
|
|
503
|
-
content: { role: "model", parts: [{ text: "skipped" }] },
|
|
504
|
-
}),
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
expect(msgs[0]).toMatchObject({
|
|
508
|
-
status: { type: "complete", reason: "stop" },
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
|
|
512
476
|
it("prioritizes skipSummarization over longRunningToolIds", () => {
|
|
513
477
|
const acc = new AdkEventAccumulator();
|
|
514
478
|
const msgs = acc.processEvent(
|