@maximai/maxim-js
Version:
Maxim AI JS SDK. Visit https://getmaxim.ai for more info.
370 lines • 17.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MaximOpenAIAgentsProcessor = void 0;
const uuid_1 = require("uuid");
const utils_1 = require("./utils");
/**
* A tracing processor for the OpenAI Agents SDK that forwards trace and span
* lifecycle events into Maxim.
*
* @example
* ```ts
* import { addTraceProcessor, setTraceProcessors } from "@openai/agents";
* import { Maxim } from "@maximai/maxim-js";
* import { MaximOpenAIAgentsProcessor } from "@maximai/maxim-js/openai-agents";
*
* const maxim = new Maxim({ apiKey: process.env.MAXIM_API_KEY! });
* const logger = await maxim.logger({ id: "my-app" });
*
* // Add alongside the default OpenAI exporter
* addTraceProcessor(new MaximOpenAIAgentsProcessor(logger));
*
* // Or replace all processors (disables default OpenAI exporter)
* // setTraceProcessors([new MaximOpenAIAgentsProcessor(logger)]);
* ```
*
* Trace metadata recognized on the OpenAI Agents trace
* (only `traceTags`, `traceMetadata`, and `traceMetrics` are read at both onTraceStart and onTraceEnd, the rest is only read at onTraceStart):
* - `traceId`: string — override the Maxim trace id (default: Agent's `traceId`)
* - `traceName`: string — set the Maxim trace name
* - `traceSessionId`: string — associate the trace to a session
* - `traceTags`: Record<string,string> — tags to add on the trace
* - `traceMetadata`: Record<string,unknown> — metadata to add on the trace
* - `traceMetrics`: Record<string,number> — numeric metrics to add on the trace
* - `traceSpanId`: string — id for the single top-level span
* - `traceSpanName`: string — name for the top-level span
* - `traceSpanTags`: Record<string,string> — tags for the top-level span
*
* Unsupported span types:
* - "speech" | "transcription" | "speech_group" (currently ignored)
*
* Notes:
* - No automatic flush is performed. Manage shutdown/flush in your app
* lifecycle (e.g., `await maxim.cleanup()`).
*/
class MaximOpenAIAgentsProcessor {
constructor(logger) {
this.traceStates = new Map();
this.spanStates = new Map();
this.generationStates = new Map();
this.toolCallStates = new Map();
this.logger = logger;
}
async shutdown() {
await this.logger.cleanup();
}
async forceFlush() {
await this.logger.flush();
}
async onTraceStart(trace) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
const traceId = (_b = (_a = trace.metadata) === null || _a === void 0 ? void 0 : _a["traceId"]) !== null && _b !== void 0 ? _b : trace.traceId;
const traceName = (_e = (_d = (_c = trace.metadata) === null || _c === void 0 ? void 0 : _c["traceName"]) !== null && _d !== void 0 ? _d : ("name" in trace ? trace.name : undefined)) !== null && _e !== void 0 ? _e : "agents-trace";
const maximTrace = this.logger.trace({
id: traceId,
name: traceName,
tags: (_f = trace.metadata) === null || _f === void 0 ? void 0 : _f["traceTags"],
sessionId: (_g = trace.metadata) === null || _g === void 0 ? void 0 : _g["traceSessionId"],
});
if ((_h = trace.metadata) === null || _h === void 0 ? void 0 : _h["traceMetadata"]) {
this.logger.traceMetadata(traceId, trace.metadata["traceMetadata"]);
}
if ((_j = trace.metadata) === null || _j === void 0 ? void 0 : _j["traceMetrics"]) {
for (const [k, v] of Object.entries(trace.metadata["traceMetrics"])) {
if (typeof v === "number") {
this.logger.traceAddMetric(traceId, k, v);
}
else {
console.warn(`Skipping trace metric ${k} with value ${v} because it is not a number`);
}
}
}
// Create a single top-level span per trace
const spanId = (_l = (_k = trace.metadata) === null || _k === void 0 ? void 0 : _k["traceSpanId"]) !== null && _l !== void 0 ? _l : (0, uuid_1.v4)();
const spanName = (_o = (_m = trace.metadata) === null || _m === void 0 ? void 0 : _m["traceSpanName"]) !== null && _o !== void 0 ? _o : "agent-run";
const topSpan = maximTrace.span({ id: spanId, name: spanName, tags: (_p = trace.metadata) === null || _p === void 0 ? void 0 : _p["traceSpanTags"] });
// store state
this.traceStates.set(trace.traceId, { trace: maximTrace, topSpan, createdByUs: true });
}
async onTraceEnd(trace) {
var _a, _b, _c, _d;
const state = this.traceStates.get(trace.traceId);
if (!state)
return;
// Use the Maxim trace id from traceState if provided, otherwise fall back to the Agents traceId
const maximId = (_a = state.trace.id) !== null && _a !== void 0 ? _a : trace.traceId;
if ((_b = trace.metadata) === null || _b === void 0 ? void 0 : _b["traceTags"]) {
for (const [k, v] of Object.entries(trace.metadata["traceTags"])) {
if (typeof v === "string") {
this.logger.traceTag(maximId, k, v);
}
else {
console.warn(`Skipping trace tag ${k} with value ${v} because it is not a string`);
}
}
}
if ((_c = trace.metadata) === null || _c === void 0 ? void 0 : _c["traceMetadata"]) {
this.logger.traceMetadata(maximId, trace.metadata["traceMetadata"]);
}
if ((_d = trace.metadata) === null || _d === void 0 ? void 0 : _d["traceMetrics"]) {
for (const [k, v] of Object.entries(trace.metadata["traceMetrics"])) {
if (typeof v === "number") {
this.logger.traceAddMetric(maximId, k, v);
}
else {
console.warn(`Skipping trace metric ${k} with value ${v} because it is not a number`);
}
}
}
// End the top-level span if still open
this.logger.spanEnd(state.topSpan.id);
// End trace
this.logger.traceEnd(state.trace.id);
// cleanup state
this.traceStates.delete(trace.traceId);
}
async onSpanStart(span) {
var _a, _b, _c, _d, _e, _f, _g, _h;
const traceState = this.traceStates.get(span.traceId);
if (!traceState) {
console.warn(`onSpanStart called for span ${span.spanId} but no trace state found`);
return;
}
const data = span.spanData;
const topSpanId = traceState.topSpan.id;
if (span.error) {
this.logger.spanError(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: (0, uuid_1.v4)(),
message: span.error.message,
metadata: span.error.data,
});
}
if (data.type === "agent") {
const child = this.logger.spanSpan(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: span.spanId,
name: data.name,
});
if (data.handoffs) {
child.addMetadata({ handoffs: data.handoffs });
}
if (data.output_type) {
child.addMetadata({ output_type: data.output_type });
}
if (data.tools) {
child.addMetadata({ tools: data.tools });
}
this.spanStates.set(span.spanId, child);
return;
}
if (data.type === "generation") {
const model = (_a = data.model) !== null && _a !== void 0 ? _a : "openai";
const generation = this.logger.spanGeneration(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: span.spanId,
provider: "openai",
model,
messages: data.input,
modelParameters: (_b = data.model_config) !== null && _b !== void 0 ? _b : {},
});
this.generationStates.set(span.spanId, generation);
return;
}
if (data.type === "response") {
const messages = typeof data._input === "string"
? [{ role: "user", content: data._input }]
: Array.isArray(data._input)
? (0, utils_1.convertOpenAIResponsesMessagesToCompletionMessages)(data._input)
: [];
const generation = this.logger.spanGeneration(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: span.spanId,
provider: "openai",
model: "unknown",
messages,
modelParameters: {},
});
this.generationStates.set(span.spanId, generation);
return;
}
if (data.type === "function") {
const tool = this.logger.spanToolCall(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: span.spanId,
name: data.name,
description: data.name,
args: data.input,
});
if (data.mcp_data) {
tool.addMetadata({ mcp_data: data.mcp_data });
}
if (data.output) {
tool.result(data.output);
tool.end();
}
else {
this.toolCallStates.set(span.spanId, tool);
}
return;
}
if (data.type === "mcp_tools") {
const tool = this.logger.spanToolCall(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: span.spanId,
name: (_c = data.server) !== null && _c !== void 0 ? _c : "unknown_mcp_tool_server",
description: (_d = data.server) !== null && _d !== void 0 ? _d : "unknown_mcp_tool_server",
args: "",
});
if (data.result) {
tool.result(JSON.stringify(data.result, null, 2));
tool.end();
}
else {
this.toolCallStates.set(span.spanId, tool);
}
return;
}
if (data.type === "guardrail") {
this.logger.spanEvent(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, span.spanId, data.name, {
triggered: data.triggered ? "true" : "false",
type: data.type,
}, undefined);
return;
}
if (data.type === "handoff") {
this.logger.spanEvent(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, span.spanId, `Handoff: ${(_e = data.from_agent) !== null && _e !== void 0 ? _e : "unknown_agent"}<>${(_f = data.to_agent) !== null && _f !== void 0 ? _f : "unknown_agent"}`, { type: data.type, from_agent: (_g = data.from_agent) !== null && _g !== void 0 ? _g : "unknown_agent", to_agent: (_h = data.to_agent) !== null && _h !== void 0 ? _h : "unknown_agent" }, undefined);
return;
}
if (data.type === "custom") {
this.logger.spanEvent(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, span.spanId, data.name, {}, { data: data.data });
return;
}
// For other kinds, do nothing
// Not Handled by Maxim: "speech" | "transcription" | "speech_group"
}
async onSpanEnd(span) {
var _a, _b, _c, _d;
const traceState = this.traceStates.get(span.traceId);
if (!traceState) {
console.warn(`onSpanEnd called for span ${span.spanId} but no trace state found`);
return;
}
const data = span.spanData;
const topSpanId = traceState.topSpan.id;
if (span.error) {
this.logger.spanError(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, {
id: (0, uuid_1.v4)(),
message: span.error.message,
metadata: span.error.data,
});
}
if (data.type === "agent") {
const childToEnd = this.spanStates.get(span.spanId);
if (!childToEnd) {
console.warn(`onSpanEnd called for span ${span.spanId} but no child span found`);
return;
}
if (data.handoffs) {
childToEnd.addMetadata({ handoffs: data.handoffs });
}
if (data.output_type) {
childToEnd.addMetadata({ output_type: data.output_type });
}
if (data.tools) {
childToEnd.addMetadata({ tools: data.tools });
}
childToEnd.end();
this.spanStates.delete(span.spanId);
return;
}
if (data.type === "generation") {
const generationToEnd = this.generationStates.get(span.spanId);
if (!generationToEnd) {
console.warn(`onSpanEnd called for span ${span.spanId} but no generation found`);
return;
}
if (data.output && data.output.length > 0) {
generationToEnd.result(data.output.at(-1));
}
generationToEnd.end();
this.generationStates.delete(span.spanId);
return;
}
if (data.type === "response") {
const generationToEnd = this.generationStates.get(span.spanId);
if (!generationToEnd) {
console.warn(`onSpanEnd called for span ${span.spanId} but no generation found`);
return;
}
if (data._input) {
const messages = typeof data._input === "string"
? [{ role: "user", content: data._input }]
: Array.isArray(data._input)
? (0, utils_1.convertOpenAIResponsesMessagesToCompletionMessages)(data._input)
: [];
if (messages.length) {
generationToEnd.addMessages(messages);
}
}
if (data._response) {
const { completionResult, modelParameters } = (0, utils_1.convertOpenAIResponsesToCompletionResult)(data._response);
if (completionResult.model) {
generationToEnd.setModel(completionResult.model);
}
if (modelParameters && Object.keys(modelParameters).length > 0) {
generationToEnd.setModelParameters(modelParameters);
}
generationToEnd.result(completionResult);
}
if (data.response_id) {
generationToEnd.addTag("response_id", data.response_id);
}
generationToEnd.end();
this.generationStates.delete(span.spanId);
return;
}
if (data.type === "function") {
const toolToEnd = this.toolCallStates.get(span.spanId);
if (!toolToEnd) {
console.warn(`onSpanEnd called for span ${span.spanId} but no tool call found`);
return;
}
if (data.mcp_data) {
toolToEnd.addMetadata({ mcp_data: data.mcp_data });
}
if (data.output) {
toolToEnd.result(data.output);
}
toolToEnd.end();
this.toolCallStates.delete(span.spanId);
return;
}
if (data.type === "mcp_tools") {
const toolToEnd = this.toolCallStates.get(span.spanId);
if (!toolToEnd) {
console.warn(`onSpanEnd called for span ${span.spanId} but no tool call found`);
return;
}
if (data.result) {
toolToEnd.result(JSON.stringify(data.result, null, 2));
}
toolToEnd.end();
this.toolCallStates.delete(span.spanId);
return;
}
if (data.type === "guardrail") {
this.logger.spanEvent(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, span.spanId, data.name, {
triggered: data.triggered ? "true" : "false",
type: data.type,
}, undefined);
return;
}
if (data.type === "handoff") {
this.logger.spanEvent(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, span.spanId, `Handoff: ${(_a = data.from_agent) !== null && _a !== void 0 ? _a : "unknown_agent"}<>${(_b = data.to_agent) !== null && _b !== void 0 ? _b : "unknown_agent"}`, { type: data.type, from_agent: (_c = data.from_agent) !== null && _c !== void 0 ? _c : "unknown_agent", to_agent: (_d = data.to_agent) !== null && _d !== void 0 ? _d : "unknown_agent" }, undefined);
return;
}
if (data.type === "custom") {
this.logger.spanEvent(span.traceId !== span.parentId && span.parentId ? span.parentId : topSpanId, span.spanId, data.name, {}, { data: data.data });
return;
}
// For other kinds, do nothing
// Not Handled by Maxim: "speech" | "transcription" | "speech_group"
}
}
exports.MaximOpenAIAgentsProcessor = MaximOpenAIAgentsProcessor;
//# sourceMappingURL=index.js.map