From e1c70e28c294773b3889a6b451cb5c6eef23dc5b Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 2 Apr 2026 11:06:44 +0200 Subject: [PATCH 1/2] fix(client): accept boolean for debugLogger in pipeline functions Pipeline functions (verifyEvents, transformChunks, parseSSEStream, etc.) only accepted `DebugLogger | false | null`, rejecting `true`. Added `DebugLoggerInput` type and `resolveDebugLogger()` helper so passing `true` auto-creates a DebugLogger with all logging enabled. --- .../client/src/__tests__/debug-logger.test.ts | 36 ++++++++++++++++++- .../packages/client/src/agent/index.ts | 3 +- .../packages/client/src/apply/default.ts | 6 ++-- .../packages/client/src/chunks/transform.ts | 6 ++-- .../packages/client/src/debug-logger.ts | 17 ++++++++- .../packages/client/src/transform/http.ts | 6 ++-- .../packages/client/src/transform/sse.ts | 6 ++-- .../packages/client/src/verify/verify.ts | 6 ++-- 8 files changed, 68 insertions(+), 18 deletions(-) diff --git a/sdks/typescript/packages/client/src/__tests__/debug-logger.test.ts b/sdks/typescript/packages/client/src/__tests__/debug-logger.test.ts index df123af528..ad2c1249c1 100644 --- a/sdks/typescript/packages/client/src/__tests__/debug-logger.test.ts +++ b/sdks/typescript/packages/client/src/__tests__/debug-logger.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { DebugLogger, createDebugLogger } from "@/debug-logger"; +import { DebugLogger, createDebugLogger, resolveDebugLogger } from "@/debug-logger"; import { resolveAgentDebugConfig, ResolvedAgentDebugConfig } from "@/agent/types"; describe("resolveAgentDebugConfig", () => { @@ -148,6 +148,40 @@ describe("createDebugLogger", () => { }); }); +describe("resolveDebugLogger", () => { + it("returns undefined for undefined", () => { + expect(resolveDebugLogger(undefined)).toBeUndefined(); + }); + + it("returns undefined for null", () => { + expect(resolveDebugLogger(null)).toBeUndefined(); + }); + + it("returns undefined for false", () => { + expect(resolveDebugLogger(false)).toBeUndefined(); + }); + + it("returns a DebugLogger instance for true", () => { + const logger = resolveDebugLogger(true); + expect(logger).toBeInstanceOf(DebugLogger); + expect(logger!.enabled).toBe(true); + expect(logger!.eventsEnabled).toBe(true); + expect(logger!.lifecycleEnabled).toBe(true); + }); + + it("returns the same DebugLogger instance when given one", () => { + const config: ResolvedAgentDebugConfig = { + enabled: true, + events: true, + lifecycle: false, + verbose: false, + }; + const original = new DebugLogger(config); + const resolved = resolveDebugLogger(original); + expect(resolved).toBe(original); + }); +}); + describe("DebugLogger.event()", () => { let debugSpy: ReturnType; diff --git a/sdks/typescript/packages/client/src/agent/index.ts b/sdks/typescript/packages/client/src/agent/index.ts index b47f424ca9..9ff480c201 100644 --- a/sdks/typescript/packages/client/src/agent/index.ts +++ b/sdks/typescript/packages/client/src/agent/index.ts @@ -10,4 +10,5 @@ export type { } from "./types"; export { resolveAgentDebugConfig } from "./types"; export type { AgentSubscriber, AgentStateMutation, AgentSubscriberParams } from "./subscriber"; -export { DebugLogger, createDebugLogger } from "../debug-logger"; +export { DebugLogger, createDebugLogger, resolveDebugLogger } from "../debug-logger"; +export type { DebugLoggerInput } from "../debug-logger"; diff --git a/sdks/typescript/packages/client/src/apply/default.ts b/sdks/typescript/packages/client/src/apply/default.ts index c668e845f4..6b30985691 100644 --- a/sdks/typescript/packages/client/src/apply/default.ts +++ b/sdks/typescript/packages/client/src/apply/default.ts @@ -48,16 +48,16 @@ import type { Observable } from "rxjs"; import { concatMap, defaultIfEmpty, mergeAll, mergeMap } from "rxjs/operators"; import untruncateJson from "untruncate-json"; import { structuredClone_ } from "../utils"; -import type { DebugLogger } from "@/debug-logger"; +import { type DebugLoggerInput, resolveDebugLogger } from "@/debug-logger"; export const defaultApplyEvents = ( input: RunAgentInput, events$: Observable, agent: AbstractAgent, subscribers: AgentSubscriber[], - debugLogger?: DebugLogger | false | null, + debugLogger?: DebugLoggerInput, ): Observable => { - const log = debugLogger || undefined; + const log = resolveDebugLogger(debugLogger); let messages = structuredClone_(agent.messages); let state = structuredClone_(input.state); let currentMutation: AgentStateMutation = {}; diff --git a/sdks/typescript/packages/client/src/chunks/transform.ts b/sdks/typescript/packages/client/src/chunks/transform.ts index 8a6c73b51d..9c735ee8d3 100644 --- a/sdks/typescript/packages/client/src/chunks/transform.ts +++ b/sdks/typescript/packages/client/src/chunks/transform.ts @@ -15,7 +15,7 @@ import { ReasoningMessageStartEvent, } from "@ag-ui/core"; import { EventType } from "@ag-ui/core"; -import { DebugLogger } from "@/debug-logger"; +import { type DebugLoggerInput, resolveDebugLogger } from "@/debug-logger"; interface TextMessageFields { messageId: string; @@ -33,9 +33,9 @@ interface ReasoningMessageFields { } export const transformChunks = - (debugLogger?: DebugLogger | false | null) => + (debugLogger?: DebugLoggerInput) => (events$: Observable): Observable => { - const log = debugLogger || undefined; + const log = resolveDebugLogger(debugLogger); let textMessageFields: TextMessageFields | undefined; let toolCallFields: ToolCallFields | undefined; let reasoningMessageFields: ReasoningMessageFields | undefined; diff --git a/sdks/typescript/packages/client/src/debug-logger.ts b/sdks/typescript/packages/client/src/debug-logger.ts index cacaca8101..0d61ac2570 100644 --- a/sdks/typescript/packages/client/src/debug-logger.ts +++ b/sdks/typescript/packages/client/src/debug-logger.ts @@ -1,4 +1,19 @@ -import { ResolvedAgentDebugConfig } from "@/agent/types"; +import { ResolvedAgentDebugConfig, resolveAgentDebugConfig } from "@/agent/types"; + +/** Accepted input wherever a debug logger can be provided. */ +export type DebugLoggerInput = DebugLogger | boolean | null | undefined; + +/** + * Resolves a DebugLoggerInput into a DebugLogger or undefined. + * - `true` → creates a new DebugLogger with all logging enabled + * - `DebugLogger` instance → returned as-is + * - `false`, `null`, `undefined` → returns undefined + */ +export function resolveDebugLogger(input: DebugLoggerInput): DebugLogger | undefined { + if (input instanceof DebugLogger) return input; + if (input === true) return new DebugLogger(resolveAgentDebugConfig(true)); + return undefined; +} /** * Centralized debug logger for the AG-UI event pipeline. diff --git a/sdks/typescript/packages/client/src/transform/http.ts b/sdks/typescript/packages/client/src/transform/http.ts index 567c99ca3e..60446868f8 100644 --- a/sdks/typescript/packages/client/src/transform/http.ts +++ b/sdks/typescript/packages/client/src/transform/http.ts @@ -5,16 +5,16 @@ import { parseSSEStream } from "./sse"; import { parseProtoStream } from "./proto"; import * as proto from "@ag-ui/proto"; import { EventType } from "@ag-ui/core"; -import { DebugLogger } from "@/debug-logger"; +import { type DebugLoggerInput, resolveDebugLogger } from "@/debug-logger"; /** * Transforms HTTP events into BaseEvents using the appropriate format parser based on content type. */ export const transformHttpEventStream = ( source$: Observable, - debugLogger?: DebugLogger | false | null, + debugLogger?: DebugLoggerInput, ): Observable => { - const log = debugLogger || undefined; + const log = resolveDebugLogger(debugLogger); const eventSubject = new Subject(); // Use ReplaySubject to buffer events until we decide on the parser diff --git a/sdks/typescript/packages/client/src/transform/sse.ts b/sdks/typescript/packages/client/src/transform/sse.ts index 8575fd04a8..aa32f5de39 100644 --- a/sdks/typescript/packages/client/src/transform/sse.ts +++ b/sdks/typescript/packages/client/src/transform/sse.ts @@ -1,6 +1,6 @@ import { Observable, Subject } from "rxjs"; import { HttpEvent, HttpEventType } from "../run/http-request"; -import { DebugLogger } from "@/debug-logger"; +import { type DebugLoggerInput, resolveDebugLogger } from "@/debug-logger"; /** * Parses a stream of HTTP events into a stream of JSON objects using Server-Sent Events (SSE) format. @@ -12,9 +12,9 @@ import { DebugLogger } from "@/debug-logger"; */ export const parseSSEStream = ( source$: Observable, - debugLogger?: DebugLogger | false | null, + debugLogger?: DebugLoggerInput, ): Observable => { - const log = debugLogger || undefined; + const log = resolveDebugLogger(debugLogger); const jsonSubject = new Subject(); // Create TextDecoder with stream option set to true to handle split UTF-8 characters const decoder = new TextDecoder("utf-8", { fatal: false }); diff --git a/sdks/typescript/packages/client/src/verify/verify.ts b/sdks/typescript/packages/client/src/verify/verify.ts index f3b6ba5aab..df304ddffd 100644 --- a/sdks/typescript/packages/client/src/verify/verify.ts +++ b/sdks/typescript/packages/client/src/verify/verify.ts @@ -1,12 +1,12 @@ import { BaseEvent, EventType, AGUIError } from "@ag-ui/core"; import { Observable, throwError, of } from "rxjs"; import { mergeMap } from "rxjs/operators"; -import { DebugLogger } from "@/debug-logger"; +import { type DebugLoggerInput, resolveDebugLogger } from "@/debug-logger"; export const verifyEvents = - (debugLogger?: DebugLogger | false | null) => + (debugLogger?: DebugLoggerInput) => (source$: Observable): Observable => { - const log = debugLogger || undefined; + const log = resolveDebugLogger(debugLogger); // Declare variables in closure to maintain state across events let activeMessages = new Map(); // Map of message ID -> active status let activeToolCalls = new Map(); // Map of tool call ID -> active status From 7de7a5c2bbcf699207698a8846a72b83073cb00d Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 2 Apr 2026 11:19:27 +0200 Subject: [PATCH 2/2] fix(client): make agent.debug setter accept boolean for backwards compat The public `debug` property was changed from boolean to ResolvedAgentDebugConfig, breaking `agent.debug = true/false`. Converted to a getter/setter: setter accepts AgentDebugConfig (boolean or object) and auto-syncs debugLogger, getter returns ResolvedAgentDebugConfig. --- .../src/agent/__tests__/agent-debug.test.ts | 28 +++++++++++++++++++ .../packages/client/src/agent/agent.ts | 20 +++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/sdks/typescript/packages/client/src/agent/__tests__/agent-debug.test.ts b/sdks/typescript/packages/client/src/agent/__tests__/agent-debug.test.ts index f4a7753f1f..2efb6b0bcf 100644 --- a/sdks/typescript/packages/client/src/agent/__tests__/agent-debug.test.ts +++ b/sdks/typescript/packages/client/src/agent/__tests__/agent-debug.test.ts @@ -95,6 +95,34 @@ describe("Agent construction debug config", () => { verbose: false, }); }); + + it("setting debug = true after construction enables logging", () => { + const agent = new TestAgent({ debug: false }); + expect(agent.debugLogger).toBeUndefined(); + + agent.debug = true; + expect(agent.debugLogger).toBeInstanceOf(DebugLogger); + expect(agent.debug).toEqual({ + enabled: true, + events: true, + lifecycle: true, + verbose: true, + }); + }); + + it("setting debug = false after construction disables logging", () => { + const agent = new TestAgent({ debug: true }); + expect(agent.debugLogger).toBeInstanceOf(DebugLogger); + + agent.debug = false; + expect(agent.debugLogger).toBeUndefined(); + expect(agent.debug).toEqual({ + enabled: false, + events: false, + lifecycle: false, + verbose: false, + }); + }); }); describe("Agent run lifecycle logging", () => { diff --git a/sdks/typescript/packages/client/src/agent/agent.ts b/sdks/typescript/packages/client/src/agent/agent.ts index 80a7fc1358..122d5ab503 100644 --- a/sdks/typescript/packages/client/src/agent/agent.ts +++ b/sdks/typescript/packages/client/src/agent/agent.ts @@ -11,6 +11,7 @@ import { import { AgentConfig, + AgentDebugConfig, RunAgentParameters, ResolvedAgentDebugConfig, resolveAgentDebugConfig, @@ -50,7 +51,7 @@ export abstract class AbstractAgent { public threadId: string; public messages: Message[]; public state: State; - public debug: ResolvedAgentDebugConfig; + private _debug: ResolvedAgentDebugConfig; private _debugLogger: DebugLogger | undefined; public subscribers: AgentSubscriber[] = []; public isRunning: boolean = false; @@ -63,6 +64,15 @@ export abstract class AbstractAgent { return packageJson.version; } + get debug(): ResolvedAgentDebugConfig { + return this._debug; + } + + set debug(value: AgentDebugConfig | ResolvedAgentDebugConfig) { + this._debug = resolveAgentDebugConfig(value as AgentDebugConfig); + this._debugLogger = createDebugLogger(this._debug); + } + get debugLogger(): DebugLogger | undefined { return this._debugLogger; } @@ -90,8 +100,8 @@ export abstract class AbstractAgent { this.threadId = threadId ?? uuidv4(); this.messages = structuredClone_(initialMessages ?? []); this.state = structuredClone_(initialState ?? {}); - this.debug = resolveAgentDebugConfig(debug); - this.debugLogger = createDebugLogger(this.debug); + this._debug = resolveAgentDebugConfig(debug); + this._debugLogger = createDebugLogger(this._debug); if (compareVersions(this.maxVersion, "0.0.39") <= 0) { this.middlewares.unshift(new BackwardCompatibility_0_0_39()); @@ -497,8 +507,8 @@ export abstract class AbstractAgent { cloned.threadId = this.threadId; cloned.messages = structuredClone_(this.messages); cloned.state = structuredClone_(this.state); - cloned.debug = this.debug; - cloned.debugLogger = this.debugLogger; + cloned._debug = this._debug; + cloned._debugLogger = this._debugLogger; cloned.isRunning = this.isRunning; cloned.subscribers = [...this.subscribers]; cloned.middlewares = [...this.middlewares];