

















@@ -1,6 +1,11 @@
11import { describe, expect, it } from "vitest";
22import { EventHub, OpenClaw, normalizeGatewayEvent } from "./index.js";
3-import type { GatewayEvent, GatewayRequestOptions, OpenClawTransport } from "./types.js";
3+import type {
4+GatewayEvent,
5+GatewayRequestOptions,
6+OpenClawEvent,
7+OpenClawTransport,
8+} from "./types.js";
49510type RequestCall = {
611method: string;
@@ -355,6 +360,209 @@ describe("OpenClaw SDK", () => {
355360expect(seen).toEqual(["run.started", "assistant.delta", "run.completed"]);
356361});
357362363+it("does not surface raw chat projection events in per-run streams", async () => {
364+const ts = 1_777_000_000_100;
365+const transport = new FakeTransport({
366+agent: (
367+_params: unknown,
368+_options: GatewayRequestOptions | undefined,
369+fake: FakeTransport,
370+) => {
371+fake.emit({
372+event: "agent",
373+seq: 1,
374+payload: {
375+runId: "run_chat_projection",
376+stream: "lifecycle",
377+ ts,
378+data: { phase: "start" },
379+},
380+});
381+fake.emit({
382+event: "agent",
383+seq: 2,
384+payload: {
385+runId: "run_chat_projection",
386+stream: "assistant",
387+ts: ts + 1,
388+data: { delta: "hello" },
389+},
390+});
391+fake.emit({
392+event: "chat",
393+seq: 3,
394+payload: {
395+runId: "run_chat_projection",
396+sessionKey: "chat-projection",
397+state: "delta",
398+message: {
399+role: "assistant",
400+content: [{ type: "text", text: "hello" }],
401+timestamp: ts + 2,
402+},
403+},
404+});
405+fake.emit({
406+event: "agent",
407+seq: 4,
408+payload: {
409+runId: "run_chat_projection",
410+stream: "lifecycle",
411+ts: ts + 3,
412+data: { phase: "end" },
413+},
414+});
415+fake.emit({
416+event: "chat",
417+seq: 5,
418+payload: {
419+runId: "run_chat_projection",
420+sessionKey: "chat-projection",
421+state: "final",
422+message: {
423+role: "assistant",
424+content: [{ type: "text", text: "hello" }],
425+timestamp: ts + 4,
426+},
427+},
428+});
429+return {
430+status: "accepted",
431+runId: "run_chat_projection",
432+sessionKey: "chat-projection",
433+};
434+},
435+});
436+const oc = new OpenClaw({ transport });
437+438+const run = await oc.runs.create({
439+input: "stream with chat projection",
440+idempotencyKey: "chat-projection-events",
441+sessionKey: "chat-projection",
442+});
443+const seen: OpenClawEvent[] = [];
444+445+for await (const event of run.events()) {
446+seen.push(event);
447+if (event.type === "run.completed") {
448+break;
449+}
450+}
451+452+expect(seen.map((event) => event.type)).toEqual([
453+"run.started",
454+"assistant.delta",
455+"run.completed",
456+]);
457+expect(seen.map((event) => event.raw?.event)).toEqual(["agent", "agent", "agent"]);
458+});
459+460+it("normalizes chat-only projection events in per-run streams", async () => {
461+const ts = 1_777_000_000_200;
462+const transport = new FakeTransport({
463+agent: (
464+_params: unknown,
465+_options: GatewayRequestOptions | undefined,
466+fake: FakeTransport,
467+) => {
468+fake.emit({
469+event: "chat",
470+seq: 1,
471+payload: {
472+runId: "run_chat_only",
473+sessionKey: "chat-only",
474+state: "delta",
475+message: {
476+role: "assistant",
477+content: [{ type: "text", text: "hello" }],
478+timestamp: ts,
479+},
480+},
481+});
482+fake.emit({
483+event: "chat",
484+seq: 2,
485+payload: {
486+runId: "run_chat_only",
487+sessionKey: "chat-only",
488+state: "delta",
489+message: {
490+role: "assistant",
491+content: [{ type: "text", text: "hello again" }],
492+timestamp: ts + 1,
493+},
494+},
495+});
496+fake.emit({
497+event: "chat",
498+seq: 3,
499+payload: {
500+runId: "run_chat_only",
501+sessionKey: "chat-only",
502+state: "final",
503+message: {
504+role: "assistant",
505+content: [{ type: "text", text: "hello again" }],
506+timestamp: ts + 2,
507+},
508+},
509+});
510+fake.emit({
511+event: "custom.debug",
512+seq: 4,
513+payload: {
514+runId: "run_chat_only",
515+ts: ts + 3,
516+data: { ok: true },
517+},
518+});
519+return { status: "accepted", runId: "run_chat_only", sessionKey: "chat-only" };
520+},
521+});
522+const oc = new OpenClaw({ transport });
523+524+const run = await oc.runs.create({
525+input: "stream with chat-only projection",
526+idempotencyKey: "chat-only-events",
527+sessionKey: "chat-only",
528+});
529+const iterator = run.events()[Symbol.asyncIterator]();
530+531+try {
532+const first = await iterator.next();
533+expect(first).toMatchObject({
534+done: false,
535+value: {
536+type: "assistant.delta",
537+data: { delta: "hello" },
538+raw: { event: "chat" },
539+},
540+});
541+542+const second = await iterator.next();
543+expect(second).toMatchObject({
544+done: false,
545+value: {
546+type: "assistant.delta",
547+data: { delta: "hello again" },
548+raw: { event: "chat" },
549+},
550+});
551+552+const third = await iterator.next();
553+expect(third).toMatchObject({
554+done: false,
555+value: {
556+type: "run.completed",
557+data: { phase: "end", outputText: "hello again" },
558+raw: { event: "chat" },
559+},
560+});
561+} finally {
562+await iterator.return?.();
563+}
564+});
565+358566it("creates a session and sends a message as a run", async () => {
359567const transport = new FakeTransport({
360568"sessions.create": { key: "session-main", label: "Main" },
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。