mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
docs: document embedded subscribe text streaming
This commit is contained in:
+4
@@ -1,3 +1,5 @@
|
||||
// Text-end repeat tests ensure repeated full snapshots do not duplicate block
|
||||
// replies or assistantTexts.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createTextEndBlockReplyHarness,
|
||||
@@ -21,6 +23,8 @@ describe("subscribeEmbeddedAgentSession", () => {
|
||||
expect(subscription.assistantTexts).toEqual(["Good morning!"]);
|
||||
});
|
||||
it("does not duplicate block chunks when text_end repeats full content", async () => {
|
||||
// Chunked deltas may already flush all visible text before text_end repeats
|
||||
// the same content; the snapshot must be ignored.
|
||||
const onBlockReply = vi.fn();
|
||||
const { emit } = createTextEndBlockReplyHarness({
|
||||
onBlockReply,
|
||||
|
||||
+4
@@ -1,3 +1,5 @@
|
||||
// Duplicate block reply tests cover repeated text_end and message_end events
|
||||
// from providers that replay assistant snapshots.
|
||||
import type { AssistantMessage } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -85,6 +87,8 @@ describe("subscribeEmbeddedAgentSession", () => {
|
||||
expect(subscription.assistantTexts).toEqual(["Hello world"]);
|
||||
});
|
||||
it("populates assistantTexts for non-streaming models with chunking enabled", () => {
|
||||
// Non-streaming providers may only send message_end; assistantTexts still
|
||||
// needs the final visible reply even when block chunking is enabled.
|
||||
// Non-streaming models (e.g. zai/glm-4.7): no text_delta events; message_end
|
||||
// must still populate assistantTexts so providers can deliver a final reply.
|
||||
const { session, emit } = createStubSessionHarness();
|
||||
|
||||
+7
@@ -1,3 +1,5 @@
|
||||
// Text-end block reply tests cover streamed block delivery, message_end
|
||||
// de-duplication, and OpenAI Responses phase handling.
|
||||
import type { AssistantMessage } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -24,6 +26,8 @@ function emitOpenAiResponsesTextEvent(params: {
|
||||
signaturePhase?: OpenAiResponsesTextEventPhase;
|
||||
partialPhase?: OpenAiResponsesTextEventPhase;
|
||||
}) {
|
||||
// Responses events carry item ids and phase signatures; tests preserve those
|
||||
// fields so commentary/final routing matches provider payloads.
|
||||
const { emit, ...eventParams } = params;
|
||||
emit(createOpenAiResponsesTextEvent(eventParams));
|
||||
}
|
||||
@@ -80,6 +84,8 @@ async function emitSuppressedCommentary(params: {
|
||||
emit: TextEndBlockReplyHarness["emit"];
|
||||
text: string;
|
||||
}) {
|
||||
// Commentary can stream before final_answer; this helper proves suppressed
|
||||
// commentary does not count as a delivered block.
|
||||
params.emit({ type: "message_start", message: { role: "assistant" } });
|
||||
emitOpenAiResponsesTextDeltaAndEnd({
|
||||
emit: params.emit,
|
||||
@@ -101,6 +107,7 @@ function expectSingleBlockReplyText(params: {
|
||||
}
|
||||
|
||||
function requireBlockReplyPayload(onBlockReply: OnBlockReplyMock): BlockReplyPayload {
|
||||
// Most cases expect exactly one user-visible block reply.
|
||||
const call = onBlockReply.mock.calls[0];
|
||||
if (!call) {
|
||||
throw new Error("expected first block reply call");
|
||||
|
||||
+6
@@ -1,3 +1,5 @@
|
||||
// Reasoning block reply tests cover message_end extraction of native reasoning
|
||||
// and tag-promoted thinking when reasoning output is enabled.
|
||||
import type { AssistantMessage } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -9,6 +11,8 @@ import { subscribeEmbeddedAgentSession } from "./embedded-agent-subscribe.js";
|
||||
|
||||
describe("subscribeEmbeddedAgentSession", () => {
|
||||
function createReasoningBlockReplyHarness(params: { thinkingLevel?: "off" | "medium" } = {}) {
|
||||
// message_end block replies let reasoning and final answer be emitted as
|
||||
// separate payloads without depending on streaming deltas.
|
||||
const { session, emit } = createStubSessionHarness();
|
||||
const onBlockReply = vi.fn();
|
||||
|
||||
@@ -33,6 +37,8 @@ describe("subscribeEmbeddedAgentSession", () => {
|
||||
}
|
||||
|
||||
function expectReasoningAndAnswerCalls(onBlockReply: ReturnType<typeof vi.fn>) {
|
||||
// The expected contract is two user-visible payloads: reasoning first,
|
||||
// final answer second.
|
||||
expect(onBlockReply).toHaveBeenCalledTimes(2);
|
||||
expect(blockReplyTextAt(onBlockReply, 0)).toBe("Because it helps");
|
||||
expect(blockReplyTextAt(onBlockReply, 1)).toBe("Final answer");
|
||||
|
||||
+6
@@ -1,3 +1,5 @@
|
||||
// Final-tag enforcement tests cover suppression of leaked reasoning and safe
|
||||
// extraction of <final> content across streamed code/fence boundaries.
|
||||
import type { AssistantMessage } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
@@ -13,6 +15,8 @@ type ReplyMock = ReturnType<typeof vi.fn>;
|
||||
type ReplyPayload = { text?: string };
|
||||
|
||||
function requireFirstReplyPayload(mock: ReplyMock): ReplyPayload {
|
||||
// Enforcement cases emit at most one visible reply before assertions inspect
|
||||
// whether the hidden prefix stayed suppressed.
|
||||
const call = mock.mock.calls[0];
|
||||
if (!call) {
|
||||
throw new Error("expected first reply call");
|
||||
@@ -96,6 +100,8 @@ describe("subscribeEmbeddedAgentSession", () => {
|
||||
});
|
||||
|
||||
it("keeps final tag literals inside streamed fenced code while enforcement is on", () => {
|
||||
// Literal <final> tags inside code fences remain hidden until a real final
|
||||
// tag appears outside the fence.
|
||||
const { session, emit } = createStubSessionHarness();
|
||||
|
||||
const onAgentEvent = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user