Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down Expand Up @@ -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<typeof vi.spyOn>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
20 changes: 15 additions & 5 deletions sdks/typescript/packages/client/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {

import {
AgentConfig,
AgentDebugConfig,
RunAgentParameters,
ResolvedAgentDebugConfig,
resolveAgentDebugConfig,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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];
Expand Down
3 changes: 2 additions & 1 deletion sdks/typescript/packages/client/src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
6 changes: 3 additions & 3 deletions sdks/typescript/packages/client/src/apply/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BaseEvent>,
agent: AbstractAgent,
subscribers: AgentSubscriber[],
debugLogger?: DebugLogger | false | null,
debugLogger?: DebugLoggerInput,
): Observable<AgentStateMutation> => {
const log = debugLogger || undefined;
const log = resolveDebugLogger(debugLogger);
let messages = structuredClone_(agent.messages);
let state = structuredClone_(input.state);
let currentMutation: AgentStateMutation = {};
Expand Down
6 changes: 3 additions & 3 deletions sdks/typescript/packages/client/src/chunks/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,9 +33,9 @@ interface ReasoningMessageFields {
}

export const transformChunks =
(debugLogger?: DebugLogger | false | null) =>
(debugLogger?: DebugLoggerInput) =>
(events$: Observable<BaseEvent>): Observable<BaseEvent> => {
const log = debugLogger || undefined;
const log = resolveDebugLogger(debugLogger);
let textMessageFields: TextMessageFields | undefined;
let toolCallFields: ToolCallFields | undefined;
let reasoningMessageFields: ReasoningMessageFields | undefined;
Expand Down
17 changes: 16 additions & 1 deletion sdks/typescript/packages/client/src/debug-logger.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 3 additions & 3 deletions sdks/typescript/packages/client/src/transform/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpEvent>,
debugLogger?: DebugLogger | false | null,
debugLogger?: DebugLoggerInput,
): Observable<BaseEvent> => {
const log = debugLogger || undefined;
const log = resolveDebugLogger(debugLogger);
const eventSubject = new Subject<BaseEvent>();

// Use ReplaySubject to buffer events until we decide on the parser
Expand Down
6 changes: 3 additions & 3 deletions sdks/typescript/packages/client/src/transform/sse.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -12,9 +12,9 @@ import { DebugLogger } from "@/debug-logger";
*/
export const parseSSEStream = (
source$: Observable<HttpEvent>,
debugLogger?: DebugLogger | false | null,
debugLogger?: DebugLoggerInput,
): Observable<any> => {
const log = debugLogger || undefined;
const log = resolveDebugLogger(debugLogger);
const jsonSubject = new Subject<any>();
// Create TextDecoder with stream option set to true to handle split UTF-8 characters
const decoder = new TextDecoder("utf-8", { fatal: false });
Expand Down
6 changes: 3 additions & 3 deletions sdks/typescript/packages/client/src/verify/verify.ts
Original file line number Diff line number Diff line change
@@ -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<BaseEvent>): Observable<BaseEvent> => {
const log = debugLogger || undefined;
const log = resolveDebugLogger(debugLogger);
// Declare variables in closure to maintain state across events
let activeMessages = new Map<string, boolean>(); // Map of message ID -> active status
let activeToolCalls = new Map<string, boolean>(); // Map of tool call ID -> active status
Expand Down
Loading