@introspection-sdk/introspection-node 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/client.d.ts +42 -132
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +67 -366
- package/dist/client.js.map +1 -0
- package/dist/converters/claude.d.ts +4 -0
- package/dist/converters/claude.d.ts.map +1 -1
- package/dist/converters/claude.js +7 -0
- package/dist/converters/claude.js.map +1 -0
- package/dist/converters/gemini.d.ts +90 -0
- package/dist/converters/gemini.d.ts.map +1 -0
- package/dist/converters/gemini.js +171 -0
- package/dist/converters/gemini.js.map +1 -0
- package/dist/converters/index.d.ts +1 -1
- package/dist/converters/index.d.ts.map +1 -1
- package/dist/converters/index.js +2 -1
- package/dist/converters/index.js.map +1 -0
- package/dist/converters/openai.js +1 -0
- package/dist/converters/openai.js.map +1 -0
- package/dist/converters/openinference.js +1 -0
- package/dist/converters/openinference.js.map +1 -0
- package/dist/converters/vercel.d.ts.map +1 -1
- package/dist/converters/vercel.js +31 -3
- package/dist/converters/vercel.js.map +1 -0
- package/dist/http.d.ts +36 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +145 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +24 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -32
- package/dist/index.js.map +1 -0
- package/dist/{anthropic.d.ts → otel/anthropic.d.ts} +23 -13
- package/dist/otel/anthropic.d.ts.map +1 -0
- package/dist/{anthropic.js → otel/anthropic.js} +132 -40
- package/dist/otel/anthropic.js.map +1 -0
- package/dist/{claude-hooks.d.ts → otel/claude-hooks.d.ts} +19 -1
- package/dist/otel/claude-hooks.d.ts.map +1 -0
- package/dist/{claude-hooks.js → otel/claude-hooks.js} +94 -20
- package/dist/otel/claude-hooks.js.map +1 -0
- package/dist/otel/claude-wrapper.d.ts.map +1 -0
- package/dist/{claude-wrapper.js → otel/claude-wrapper.js} +2 -1
- package/dist/otel/claude-wrapper.js.map +1 -0
- package/dist/otel/gemini.d.ts +56 -0
- package/dist/otel/gemini.d.ts.map +1 -0
- package/dist/otel/gemini.js +348 -0
- package/dist/otel/gemini.js.map +1 -0
- package/dist/otel/index.d.ts +32 -0
- package/dist/otel/index.d.ts.map +1 -0
- package/dist/otel/index.js +41 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/otel/init.d.ts +126 -0
- package/dist/otel/init.d.ts.map +1 -0
- package/dist/otel/init.js +259 -0
- package/dist/otel/init.js.map +1 -0
- package/dist/otel/integrations/anthropic.d.ts +10 -0
- package/dist/otel/integrations/anthropic.d.ts.map +1 -0
- package/dist/otel/integrations/anthropic.js +22 -0
- package/dist/otel/integrations/anthropic.js.map +1 -0
- package/dist/otel/integrations/base.d.ts +83 -0
- package/dist/otel/integrations/base.d.ts.map +1 -0
- package/dist/otel/integrations/base.js +23 -0
- package/dist/otel/integrations/base.js.map +1 -0
- package/dist/otel/integrations/claude-agent.d.ts +13 -0
- package/dist/otel/integrations/claude-agent.d.ts.map +1 -0
- package/dist/otel/integrations/claude-agent.js +27 -0
- package/dist/otel/integrations/claude-agent.js.map +1 -0
- package/dist/otel/integrations/gemini.d.ts +11 -0
- package/dist/otel/integrations/gemini.d.ts.map +1 -0
- package/dist/otel/integrations/gemini.js +21 -0
- package/dist/otel/integrations/gemini.js.map +1 -0
- package/dist/otel/integrations/index.d.ts +39 -0
- package/dist/otel/integrations/index.d.ts.map +1 -0
- package/dist/otel/integrations/index.js +131 -0
- package/dist/otel/integrations/index.js.map +1 -0
- package/dist/otel/integrations/langchain.d.ts +16 -0
- package/dist/otel/integrations/langchain.d.ts.map +1 -0
- package/dist/otel/integrations/langchain.js +41 -0
- package/dist/otel/integrations/langchain.js.map +1 -0
- package/dist/otel/integrations/mastra.d.ts +14 -0
- package/dist/otel/integrations/mastra.d.ts.map +1 -0
- package/dist/otel/integrations/mastra.js +27 -0
- package/dist/otel/integrations/mastra.js.map +1 -0
- package/dist/otel/integrations/openai-agents.d.ts +11 -0
- package/dist/otel/integrations/openai-agents.d.ts.map +1 -0
- package/dist/otel/integrations/openai-agents.js +25 -0
- package/dist/otel/integrations/openai-agents.js.map +1 -0
- package/dist/otel/integrations/pi.d.ts +12 -0
- package/dist/otel/integrations/pi.d.ts.map +1 -0
- package/dist/otel/integrations/pi.js +20 -0
- package/dist/otel/integrations/pi.js.map +1 -0
- package/dist/otel/integrations/vercel.d.ts +15 -0
- package/dist/otel/integrations/vercel.d.ts.map +1 -0
- package/dist/otel/integrations/vercel.js +20 -0
- package/dist/otel/integrations/vercel.js.map +1 -0
- package/dist/{langchain-handler.d.ts → otel/langchain-handler.d.ts} +10 -1
- package/dist/otel/langchain-handler.d.ts.map +1 -0
- package/dist/{langchain-handler.js → otel/langchain-handler.js} +43 -6
- package/dist/otel/langchain-handler.js.map +1 -0
- package/dist/otel/logs.d.ts +82 -0
- package/dist/otel/logs.d.ts.map +1 -0
- package/dist/otel/logs.js +271 -0
- package/dist/otel/logs.js.map +1 -0
- package/dist/{mastra-exporter.d.ts → otel/mastra-exporter.d.ts} +3 -1
- package/dist/otel/mastra-exporter.d.ts.map +1 -0
- package/dist/{mastra-exporter.js → otel/mastra-exporter.js} +71 -17
- package/dist/otel/mastra-exporter.js.map +1 -0
- package/dist/otel/pi.d.ts +59 -0
- package/dist/otel/pi.d.ts.map +1 -0
- package/dist/otel/pi.js +67 -0
- package/dist/otel/pi.js.map +1 -0
- package/dist/otel/register.d.ts +8 -0
- package/dist/otel/register.d.ts.map +1 -0
- package/dist/otel/register.js +48 -0
- package/dist/otel/register.js.map +1 -0
- package/dist/otel/setup.d.ts +52 -0
- package/dist/otel/setup.d.ts.map +1 -0
- package/dist/otel/setup.js +146 -0
- package/dist/otel/setup.js.map +1 -0
- package/dist/{span-processor.d.ts → otel/span-processor.d.ts} +1 -1
- package/dist/otel/span-processor.d.ts.map +1 -0
- package/dist/{span-processor.js → otel/span-processor.js} +11 -5
- package/dist/otel/span-processor.js.map +1 -0
- package/dist/{tracing-processor.d.ts → otel/tracing-processor.d.ts} +4 -1
- package/dist/otel/tracing-processor.d.ts.map +1 -0
- package/dist/{tracing-processor.js → otel/tracing-processor.js} +64 -14
- package/dist/otel/tracing-processor.js.map +1 -0
- package/dist/resources/experiments.d.ts +31 -0
- package/dist/resources/experiments.d.ts.map +1 -0
- package/dist/resources/experiments.js +133 -0
- package/dist/resources/experiments.js.map +1 -0
- package/dist/resources/recipes.d.ts +22 -0
- package/dist/resources/recipes.d.ts.map +1 -0
- package/dist/resources/recipes.js +61 -0
- package/dist/resources/recipes.js.map +1 -0
- package/dist/resources/runtimes.d.ts +76 -0
- package/dist/resources/runtimes.d.ts.map +1 -0
- package/dist/resources/runtimes.js +179 -0
- package/dist/resources/runtimes.js.map +1 -0
- package/dist/runner-resources/files.d.ts +35 -0
- package/dist/runner-resources/files.d.ts.map +1 -0
- package/dist/runner-resources/files.js +131 -0
- package/dist/runner-resources/files.js.map +1 -0
- package/dist/runner-resources/tasks.d.ts +39 -0
- package/dist/runner-resources/tasks.d.ts.map +1 -0
- package/dist/runner-resources/tasks.js +129 -0
- package/dist/runner-resources/tasks.js.map +1 -0
- package/dist/runner.d.ts +80 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +159 -0
- package/dist/runner.js.map +1 -0
- package/dist/streaming.d.ts +8 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +67 -0
- package/dist/streaming.js.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +13 -4
- package/dist/utils.js.map +1 -0
- package/dist/version.js +1 -0
- package/dist/version.js.map +1 -0
- package/package.json +91 -22
- package/src/client.ts +76 -473
- package/src/converters/claude.ts +10 -0
- package/src/converters/gemini.ts +243 -0
- package/src/converters/index.ts +13 -7
- package/src/converters/vercel.ts +41 -3
- package/src/http.ts +163 -0
- package/src/index.ts +110 -75
- package/src/{anthropic.ts → otel/anthropic.ts} +206 -54
- package/src/{claude-hooks.ts → otel/claude-hooks.ts} +119 -20
- package/src/{claude-wrapper.ts → otel/claude-wrapper.ts} +1 -1
- package/src/otel/gemini.ts +491 -0
- package/src/otel/index.ts +121 -0
- package/src/otel/init.ts +411 -0
- package/src/otel/integrations/anthropic.ts +26 -0
- package/src/otel/integrations/base.ts +95 -0
- package/src/otel/integrations/claude-agent.ts +32 -0
- package/src/otel/integrations/gemini.ts +25 -0
- package/src/otel/integrations/index.ts +151 -0
- package/src/otel/integrations/langchain.ts +45 -0
- package/src/otel/integrations/mastra.ts +30 -0
- package/src/otel/integrations/openai-agents.ts +31 -0
- package/src/otel/integrations/pi.ts +24 -0
- package/src/otel/integrations/vercel.ts +24 -0
- package/src/{langchain-handler.ts → otel/langchain-handler.ts} +53 -6
- package/src/otel/logs.ts +394 -0
- package/src/{mastra-exporter.ts → otel/mastra-exporter.ts} +83 -22
- package/src/otel/pi.ts +88 -0
- package/src/otel/register.ts +50 -0
- package/src/otel/setup.ts +215 -0
- package/src/{span-processor.ts → otel/span-processor.ts} +11 -6
- package/src/{tracing-processor.ts → otel/tracing-processor.ts} +94 -19
- package/src/resources/experiments.ts +170 -0
- package/src/resources/recipes.ts +73 -0
- package/src/resources/runtimes.ts +231 -0
- package/src/runner-resources/files.ts +163 -0
- package/src/runner-resources/tasks.ts +156 -0
- package/src/runner.ts +194 -0
- package/src/streaming.ts +57 -0
- package/src/utils.ts +12 -5
- package/dist/ai-sdk-integration.d.ts +0 -123
- package/dist/ai-sdk-integration.d.ts.map +0 -1
- package/dist/ai-sdk-integration.js +0 -475
- package/dist/anthropic.d.ts.map +0 -1
- package/dist/claude-hooks.d.ts.map +0 -1
- package/dist/claude-wrapper.d.ts.map +0 -1
- package/dist/converters/ai-sdk.d.ts +0 -65
- package/dist/converters/ai-sdk.d.ts.map +0 -1
- package/dist/converters/ai-sdk.js +0 -261
- package/dist/langchain-handler.d.ts.map +0 -1
- package/dist/mastra-exporter.d.ts.map +0 -1
- package/dist/span-processor.d.ts.map +0 -1
- package/dist/tracing-processor.d.ts.map +0 -1
- package/src/ai-sdk-integration.ts +0 -691
- package/src/converters/ai-sdk.ts +0 -323
- /package/dist/{claude-wrapper.d.ts → otel/claude-wrapper.d.ts} +0 -0
package/src/otel/logs.ts
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IntrospectionLogs — OTel logs exporter for Introspection.
|
|
3
|
+
*
|
|
4
|
+
* Owns its own `LoggerProvider` and OTLP log exporter. Provides `track`,
|
|
5
|
+
* `feedback`, `identify`, and async-context helpers (`withBaggage`,
|
|
6
|
+
* `withAgent`, `withConversation`, etc.) that propagate identity and
|
|
7
|
+
* gen_ai context through OpenTelemetry baggage.
|
|
8
|
+
*
|
|
9
|
+
* Fully independent of {@link IntrospectionClient}. Construct it directly
|
|
10
|
+
* with the OTel peer dependencies installed.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { IntrospectionLogs } from "@introspection-sdk/introspection-node/otel";
|
|
15
|
+
*
|
|
16
|
+
* const logs = new IntrospectionLogs({
|
|
17
|
+
* token: process.env.INTROSPECTION_TOKEN,
|
|
18
|
+
* serviceName: "my-service",
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await logs.withAgent("support-bot", "agent_1", () =>
|
|
22
|
+
* logs.withConversation("conv_123", undefined, () => {
|
|
23
|
+
* logs.feedback("thumbs_up", { comments: "Great answer" });
|
|
24
|
+
* }),
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* await logs.shutdown();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { SeverityNumber, type LogAttributes } from "@opentelemetry/api-logs";
|
|
32
|
+
import {
|
|
33
|
+
LoggerProvider,
|
|
34
|
+
BatchLogRecordProcessor,
|
|
35
|
+
} from "@opentelemetry/sdk-logs";
|
|
36
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
37
|
+
import {
|
|
38
|
+
resourceFromAttributes,
|
|
39
|
+
defaultResource,
|
|
40
|
+
} from "@opentelemetry/resources";
|
|
41
|
+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
42
|
+
import { propagation, context } from "@opentelemetry/api";
|
|
43
|
+
|
|
44
|
+
import { logger as sdkLogger, withOtlpHttpsProxy } from "../utils.js";
|
|
45
|
+
import { VERSION } from "../version.js";
|
|
46
|
+
import {
|
|
47
|
+
generateEventId,
|
|
48
|
+
type FeedbackOptions,
|
|
49
|
+
type UserTraits,
|
|
50
|
+
type GenAiContext,
|
|
51
|
+
type IdentityContext,
|
|
52
|
+
} from "../types.js";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Configuration for {@link IntrospectionLogs}.
|
|
56
|
+
*/
|
|
57
|
+
export interface IntrospectionLogsOptions {
|
|
58
|
+
/** Authentication token (env: INTROSPECTION_TOKEN). */
|
|
59
|
+
token?: string;
|
|
60
|
+
/** Service name for telemetry (env: INTROSPECTION_SERVICE_NAME, default: "introspection-client"). */
|
|
61
|
+
serviceName?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Base URL for the OTLP collector (env: INTROSPECTION_BASE_OTEL_URL,
|
|
64
|
+
* default: "https://otel.introspection.dev").
|
|
65
|
+
*/
|
|
66
|
+
baseOtelUrl?: string;
|
|
67
|
+
/** Additional HTTP headers to include in exporter requests. */
|
|
68
|
+
additionalHeaders?: Record<string, string>;
|
|
69
|
+
/** Flush interval in milliseconds (default: 5000). */
|
|
70
|
+
flushInterval?: number;
|
|
71
|
+
/** Maximum queue size before auto-flush (default: 100). */
|
|
72
|
+
maxBatchSize?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class IntrospectionLogs {
|
|
76
|
+
private loggerProvider: LoggerProvider;
|
|
77
|
+
private otelLogger: ReturnType<LoggerProvider["getLogger"]>;
|
|
78
|
+
private userId: string | undefined;
|
|
79
|
+
private anonymousId: string | undefined;
|
|
80
|
+
private traits: Record<string, unknown> = {};
|
|
81
|
+
|
|
82
|
+
constructor(options: IntrospectionLogsOptions = {}) {
|
|
83
|
+
const token = options.token || process.env.INTROSPECTION_TOKEN || "";
|
|
84
|
+
const serviceName =
|
|
85
|
+
options.serviceName ||
|
|
86
|
+
process.env.INTROSPECTION_SERVICE_NAME ||
|
|
87
|
+
"introspection-client";
|
|
88
|
+
const baseOtelUrl =
|
|
89
|
+
options.baseOtelUrl ||
|
|
90
|
+
process.env.INTROSPECTION_BASE_OTEL_URL ||
|
|
91
|
+
"https://otel.introspection.dev";
|
|
92
|
+
|
|
93
|
+
if (!token) {
|
|
94
|
+
sdkLogger.warn(
|
|
95
|
+
"IntrospectionLogs: No token provided. Events will not be sent.",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Construct endpoint URL for logs.
|
|
100
|
+
const endpoint = baseOtelUrl.endsWith("/v1/logs")
|
|
101
|
+
? baseOtelUrl
|
|
102
|
+
: `${baseOtelUrl.replace(/\/$/, "")}/v1/logs`;
|
|
103
|
+
|
|
104
|
+
const headers: Record<string, string> = {
|
|
105
|
+
Authorization: `Bearer ${token}`,
|
|
106
|
+
...(options.additionalHeaders || {}),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const exporter = new OTLPLogExporter(
|
|
110
|
+
withOtlpHttpsProxy({
|
|
111
|
+
url: endpoint,
|
|
112
|
+
headers,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const processor = new BatchLogRecordProcessor(exporter, {
|
|
117
|
+
maxQueueSize: options.maxBatchSize ?? 100,
|
|
118
|
+
scheduledDelayMillis: options.flushInterval ?? 5000,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const baseResource = defaultResource();
|
|
122
|
+
const introspectionResource = resourceFromAttributes({
|
|
123
|
+
[ATTR_SERVICE_NAME]: serviceName,
|
|
124
|
+
});
|
|
125
|
+
const resource = baseResource.merge(introspectionResource);
|
|
126
|
+
|
|
127
|
+
this.loggerProvider = new LoggerProvider({
|
|
128
|
+
resource,
|
|
129
|
+
processors: [processor],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.otelLogger = this.loggerProvider.getLogger(
|
|
133
|
+
"@introspection-sdk/introspection-node",
|
|
134
|
+
VERSION,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
sdkLogger.info(
|
|
138
|
+
`IntrospectionLogs initialized: service=${serviceName}, otlp=${endpoint}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Get gen_ai context from baggage */
|
|
143
|
+
private getGenAiFromContext(): GenAiContext {
|
|
144
|
+
const baggage = propagation.getBaggage(context.active());
|
|
145
|
+
return {
|
|
146
|
+
conversationId: baggage?.getEntry("gen_ai.conversation.id")?.value,
|
|
147
|
+
previousResponseId: baggage?.getEntry(
|
|
148
|
+
"gen_ai.request.previous_response_id",
|
|
149
|
+
)?.value,
|
|
150
|
+
agentName: baggage?.getEntry("gen_ai.agent.name")?.value,
|
|
151
|
+
agentId: baggage?.getEntry("gen_ai.agent.id")?.value,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Get identity from baggage or instance state */
|
|
156
|
+
private getIdentityFromContext(): IdentityContext {
|
|
157
|
+
const baggage = propagation.getBaggage(context.active());
|
|
158
|
+
return {
|
|
159
|
+
userId: baggage?.getEntry("identity.user_id")?.value || this.userId,
|
|
160
|
+
anonymousId:
|
|
161
|
+
baggage?.getEntry("identity.anonymous_id")?.value || this.anonymousId,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private getTimestamp(): [number, number] {
|
|
166
|
+
const hrTimeNs = process.hrtime.bigint();
|
|
167
|
+
const epochNs = BigInt(Date.now()) * BigInt(1_000_000);
|
|
168
|
+
const offsetNs = hrTimeNs - process.hrtime.bigint() + epochNs;
|
|
169
|
+
const seconds = Number(offsetNs / BigInt(1_000_000_000));
|
|
170
|
+
const nanos = Number(offsetNs % BigInt(1_000_000_000));
|
|
171
|
+
return [seconds, nanos];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private buildAttributes(
|
|
175
|
+
eventName: string,
|
|
176
|
+
options: {
|
|
177
|
+
properties?: Record<string, unknown>;
|
|
178
|
+
traits?: Record<string, unknown>;
|
|
179
|
+
conversationId?: string;
|
|
180
|
+
previousResponseId?: string;
|
|
181
|
+
eventId?: string;
|
|
182
|
+
} = {},
|
|
183
|
+
): LogAttributes {
|
|
184
|
+
const { properties, traits, conversationId, previousResponseId, eventId } =
|
|
185
|
+
options;
|
|
186
|
+
|
|
187
|
+
const attributes: LogAttributes = {
|
|
188
|
+
"event.name": eventName,
|
|
189
|
+
"event.id": eventId || generateEventId(),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const identity = this.getIdentityFromContext();
|
|
193
|
+
if (identity.userId) attributes["identity.user.id"] = identity.userId;
|
|
194
|
+
if (identity.anonymousId)
|
|
195
|
+
attributes["identity.anonymous.id"] = identity.anonymousId;
|
|
196
|
+
|
|
197
|
+
const genAi = this.getGenAiFromContext();
|
|
198
|
+
const finalConversationId = conversationId || genAi.conversationId;
|
|
199
|
+
const finalPreviousResponseId =
|
|
200
|
+
previousResponseId || genAi.previousResponseId;
|
|
201
|
+
|
|
202
|
+
if (finalConversationId)
|
|
203
|
+
attributes["gen_ai.conversation.id"] = finalConversationId;
|
|
204
|
+
if (finalPreviousResponseId)
|
|
205
|
+
attributes["gen_ai.request.previous_response_id"] =
|
|
206
|
+
finalPreviousResponseId;
|
|
207
|
+
if (genAi.agentName) attributes["gen_ai.agent.name"] = genAi.agentName;
|
|
208
|
+
if (genAi.agentId) attributes["gen_ai.agent.id"] = genAi.agentId;
|
|
209
|
+
|
|
210
|
+
if (properties) {
|
|
211
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
212
|
+
if (value != null) {
|
|
213
|
+
const attrKey = `properties.${key}`;
|
|
214
|
+
if (typeof value === "object") {
|
|
215
|
+
attributes[attrKey] = JSON.stringify(value);
|
|
216
|
+
} else if (
|
|
217
|
+
typeof value === "string" ||
|
|
218
|
+
typeof value === "number" ||
|
|
219
|
+
typeof value === "boolean"
|
|
220
|
+
) {
|
|
221
|
+
attributes[attrKey] = value;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (traits) {
|
|
228
|
+
for (const [key, value] of Object.entries(traits)) {
|
|
229
|
+
if (value != null) {
|
|
230
|
+
const attrKey = `context.traits.${key}`;
|
|
231
|
+
if (typeof value === "object") {
|
|
232
|
+
attributes[attrKey] = JSON.stringify(value);
|
|
233
|
+
} else if (
|
|
234
|
+
typeof value === "string" ||
|
|
235
|
+
typeof value === "number" ||
|
|
236
|
+
typeof value === "boolean"
|
|
237
|
+
) {
|
|
238
|
+
attributes[attrKey] = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return attributes;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
track(
|
|
248
|
+
eventName: string,
|
|
249
|
+
properties?: Record<string, unknown>,
|
|
250
|
+
options?: { eventId?: string },
|
|
251
|
+
): void {
|
|
252
|
+
const attributes = this.buildAttributes(eventName, {
|
|
253
|
+
properties,
|
|
254
|
+
eventId: options?.eventId,
|
|
255
|
+
});
|
|
256
|
+
this.otelLogger.emit({
|
|
257
|
+
timestamp: this.getTimestamp(),
|
|
258
|
+
context: context.active(),
|
|
259
|
+
severityNumber: SeverityNumber.INFO,
|
|
260
|
+
severityText: "INFO",
|
|
261
|
+
attributes,
|
|
262
|
+
});
|
|
263
|
+
sdkLogger.debug(`Tracked: ${eventName}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
feedback(name: string, options: FeedbackOptions = {}): void {
|
|
267
|
+
const { comments, conversationId, previousResponseId, eventId, ...extra } =
|
|
268
|
+
options;
|
|
269
|
+
const properties: Record<string, unknown> = { name, ...extra };
|
|
270
|
+
if (comments) properties.comments = comments;
|
|
271
|
+
const attributes = this.buildAttributes("introspection.feedback", {
|
|
272
|
+
properties,
|
|
273
|
+
conversationId: conversationId as string | undefined,
|
|
274
|
+
previousResponseId: previousResponseId as string | undefined,
|
|
275
|
+
eventId: eventId as string | undefined,
|
|
276
|
+
});
|
|
277
|
+
this.otelLogger.emit({
|
|
278
|
+
timestamp: this.getTimestamp(),
|
|
279
|
+
context: context.active(),
|
|
280
|
+
severityNumber: SeverityNumber.INFO,
|
|
281
|
+
severityText: "INFO",
|
|
282
|
+
attributes,
|
|
283
|
+
});
|
|
284
|
+
sdkLogger.debug(`Feedback: ${name}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
identify(
|
|
288
|
+
userId: string,
|
|
289
|
+
traits?: UserTraits,
|
|
290
|
+
anonymousId?: string,
|
|
291
|
+
eventId?: string,
|
|
292
|
+
): void {
|
|
293
|
+
this.userId = userId;
|
|
294
|
+
if (anonymousId) this.anonymousId = anonymousId;
|
|
295
|
+
if (traits) this.traits = { ...this.traits, ...traits };
|
|
296
|
+
const attributes = this.buildAttributes("identify", { traits, eventId });
|
|
297
|
+
this.otelLogger.emit({
|
|
298
|
+
timestamp: this.getTimestamp(),
|
|
299
|
+
context: context.active(),
|
|
300
|
+
severityNumber: SeverityNumber.INFO,
|
|
301
|
+
severityText: "INFO",
|
|
302
|
+
attributes,
|
|
303
|
+
});
|
|
304
|
+
sdkLogger.debug(`Identified: ${userId}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
createBaggageContext(values: Record<string, string>) {
|
|
308
|
+
const ctx = context.active();
|
|
309
|
+
let bag = propagation.getBaggage(ctx) || propagation.createBaggage();
|
|
310
|
+
for (const [key, value] of Object.entries(values)) {
|
|
311
|
+
bag = bag.setEntry(key, { value });
|
|
312
|
+
}
|
|
313
|
+
return propagation.setBaggage(ctx, bag);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async withBaggage<T>(
|
|
317
|
+
values: Record<string, string>,
|
|
318
|
+
callback: () => T | Promise<T>,
|
|
319
|
+
): Promise<T> {
|
|
320
|
+
const ctx = this.createBaggageContext(values);
|
|
321
|
+
return await context.with(ctx, callback);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async withAgent<T>(
|
|
325
|
+
agentName: string,
|
|
326
|
+
agentId: string | undefined,
|
|
327
|
+
callback: () => T | Promise<T>,
|
|
328
|
+
): Promise<T> {
|
|
329
|
+
const values: Record<string, string> = { "gen_ai.agent.name": agentName };
|
|
330
|
+
if (agentId) values["gen_ai.agent.id"] = agentId;
|
|
331
|
+
return await context.with(this.createBaggageContext(values), callback);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async withConversation<T>(
|
|
335
|
+
conversationId: string | undefined,
|
|
336
|
+
previousResponseId: string | undefined,
|
|
337
|
+
callback: () => T | Promise<T>,
|
|
338
|
+
): Promise<T> {
|
|
339
|
+
const values: Record<string, string> = {};
|
|
340
|
+
if (conversationId) values["gen_ai.conversation.id"] = conversationId;
|
|
341
|
+
if (previousResponseId)
|
|
342
|
+
values["gen_ai.request.previous_response_id"] = previousResponseId;
|
|
343
|
+
return await context.with(this.createBaggageContext(values), callback);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
setUserId(userId: string): void {
|
|
347
|
+
this.userId = userId;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
setAnonymousId(anonymousId: string): void {
|
|
351
|
+
this.anonymousId = anonymousId;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async withUserId<T>(
|
|
355
|
+
userId: string,
|
|
356
|
+
callback: () => T | Promise<T>,
|
|
357
|
+
): Promise<T> {
|
|
358
|
+
return await context.with(
|
|
359
|
+
this.createBaggageContext({ "identity.user_id": userId }),
|
|
360
|
+
callback,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async withAnonymousId<T>(
|
|
365
|
+
anonymousId: string,
|
|
366
|
+
callback: () => T | Promise<T>,
|
|
367
|
+
): Promise<T> {
|
|
368
|
+
return await context.with(
|
|
369
|
+
this.createBaggageContext({ "identity.anonymous_id": anonymousId }),
|
|
370
|
+
callback,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
getAnonymousId(): string | undefined {
|
|
375
|
+
return this.anonymousId;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
reset(): void {
|
|
379
|
+
this.userId = undefined;
|
|
380
|
+
this.anonymousId = undefined;
|
|
381
|
+
this.traits = {};
|
|
382
|
+
sdkLogger.debug("IntrospectionLogs state reset");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async flush(): Promise<void> {
|
|
386
|
+
await this.loggerProvider.forceFlush();
|
|
387
|
+
sdkLogger.debug("Flushed pending log records");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async shutdown(): Promise<void> {
|
|
391
|
+
await this.loggerProvider.shutdown();
|
|
392
|
+
sdkLogger.debug("IntrospectionLogs shutdown complete");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -51,8 +51,8 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
|
51
51
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
52
52
|
import { randomUUID } from "crypto";
|
|
53
53
|
|
|
54
|
-
import { logger, withOtlpHttpsProxy } from "
|
|
55
|
-
import { VERSION } from "
|
|
54
|
+
import { logger, withOtlpHttpsProxy } from "../utils.js";
|
|
55
|
+
import { VERSION } from "../version.js";
|
|
56
56
|
import type {
|
|
57
57
|
InputMessage,
|
|
58
58
|
OutputMessage,
|
|
@@ -77,7 +77,7 @@ export interface MastraExporterAdvancedOptions {
|
|
|
77
77
|
export interface IntrospectionMastraExporterOptions {
|
|
78
78
|
/** Authentication token (env: INTROSPECTION_TOKEN). */
|
|
79
79
|
token?: string;
|
|
80
|
-
/** Base URL for the API (env:
|
|
80
|
+
/** Base URL for the API (env: INTROSPECTION_BASE_OTEL_URL). */
|
|
81
81
|
baseUrl?: string;
|
|
82
82
|
/** Additional headers to include in requests. */
|
|
83
83
|
additionalHeaders?: Record<string, string>;
|
|
@@ -152,7 +152,7 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
152
152
|
|
|
153
153
|
const baseUrl =
|
|
154
154
|
opts.baseUrl ||
|
|
155
|
-
process.env.
|
|
155
|
+
process.env.INTROSPECTION_BASE_OTEL_URL ||
|
|
156
156
|
"https://otel.introspection.dev";
|
|
157
157
|
const endpoint = baseUrl.endsWith("/v1/traces")
|
|
158
158
|
? baseUrl
|
|
@@ -291,7 +291,26 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
291
291
|
span.entityName || span.name || "agent",
|
|
292
292
|
);
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
// Prefer system messages from span.input (full prompt sent to LLM) over
|
|
295
|
+
// attrs.instructions (may be just the short placeholder string).
|
|
296
|
+
const inputMsgs = Array.isArray(span.input)
|
|
297
|
+
? (span.input as Record<string, unknown>[])
|
|
298
|
+
: undefined;
|
|
299
|
+
const systemMsgsFromInput = inputMsgs?.filter((m) => m.role === "system");
|
|
300
|
+
if (systemMsgsFromInput && systemMsgsFromInput.length > 0) {
|
|
301
|
+
rootSpan.setAttribute(
|
|
302
|
+
"gen_ai.system_instructions",
|
|
303
|
+
JSON.stringify(
|
|
304
|
+
systemMsgsFromInput.map((m) => ({
|
|
305
|
+
type: "text",
|
|
306
|
+
content: this._extractTextContent(m.content),
|
|
307
|
+
})),
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
} else if (
|
|
311
|
+
attrs?.["instructions"] &&
|
|
312
|
+
typeof attrs["instructions"] === "string"
|
|
313
|
+
) {
|
|
295
314
|
rootSpan.setAttribute(
|
|
296
315
|
"gen_ai.system_instructions",
|
|
297
316
|
JSON.stringify([{ type: "text", content: attrs["instructions"] }]),
|
|
@@ -345,12 +364,30 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
345
364
|
otelSpan.setAttribute("gen_ai.response.model", responseModel);
|
|
346
365
|
if (responseId) otelSpan.setAttribute("gen_ai.response.id", responseId);
|
|
347
366
|
|
|
348
|
-
// Input messages —
|
|
367
|
+
// Input messages — span.input is either:
|
|
368
|
+
// (a) a flat normalized array { role, content }[] from Mastra's inputMessages path, or
|
|
369
|
+
// (b) an object { body: { input/messages, tools } } from the raw HTTP request path.
|
|
349
370
|
if (span.input != null) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (Array.isArray(
|
|
371
|
+
let apiMessages: Record<string, unknown>[] | undefined;
|
|
372
|
+
let tools: unknown[] | undefined;
|
|
373
|
+
|
|
374
|
+
if (Array.isArray(span.input)) {
|
|
375
|
+
// Path (a): flat array already normalized by Mastra's normalizeMessages()
|
|
376
|
+
apiMessages = span.input as Record<string, unknown>[];
|
|
377
|
+
} else {
|
|
378
|
+
// Path (b): nested request body
|
|
379
|
+
const rawInput = span.input as Record<string, unknown>;
|
|
380
|
+
const body = rawInput?.body as Record<string, unknown> | undefined;
|
|
381
|
+
const msgs = body?.input ?? body?.messages;
|
|
382
|
+
if (Array.isArray(msgs)) {
|
|
383
|
+
apiMessages = msgs as Record<string, unknown>[];
|
|
384
|
+
}
|
|
385
|
+
if (Array.isArray(body?.tools)) {
|
|
386
|
+
tools = body.tools as unknown[];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (apiMessages && apiMessages.length > 0) {
|
|
354
391
|
const inputMessages = this._convertInput(apiMessages);
|
|
355
392
|
if (inputMessages.length > 0) {
|
|
356
393
|
otelSpan.setAttribute(
|
|
@@ -359,28 +396,22 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
359
396
|
);
|
|
360
397
|
}
|
|
361
398
|
|
|
362
|
-
// System instructions
|
|
363
|
-
const systemMsgs = (
|
|
364
|
-
(m) => m.role === "system",
|
|
365
|
-
);
|
|
399
|
+
// System instructions — combine all system messages into one attribute
|
|
400
|
+
const systemMsgs = apiMessages.filter((m) => m.role === "system");
|
|
366
401
|
if (systemMsgs.length > 0) {
|
|
367
402
|
otelSpan.setAttribute(
|
|
368
403
|
"gen_ai.system_instructions",
|
|
369
404
|
JSON.stringify(
|
|
370
405
|
systemMsgs.map((m) => ({
|
|
371
406
|
type: "text",
|
|
372
|
-
content:
|
|
373
|
-
typeof m.content === "string"
|
|
374
|
-
? m.content
|
|
375
|
-
: JSON.stringify(m.content),
|
|
407
|
+
content: this._extractTextContent(m.content),
|
|
376
408
|
})),
|
|
377
409
|
),
|
|
378
410
|
);
|
|
379
411
|
}
|
|
380
412
|
}
|
|
381
413
|
|
|
382
|
-
// Tool definitions
|
|
383
|
-
const tools = body?.tools;
|
|
414
|
+
// Tool definitions (only present on path (b))
|
|
384
415
|
if (Array.isArray(tools) && tools.length > 0) {
|
|
385
416
|
otelSpan.setAttribute(
|
|
386
417
|
"gen_ai.tool.definitions",
|
|
@@ -431,6 +462,11 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
431
462
|
"gen_ai.usage.cache_read.input_tokens",
|
|
432
463
|
inputDetails["cacheRead"],
|
|
433
464
|
);
|
|
465
|
+
if (typeof inputDetails?.["cacheWrite"] === "number")
|
|
466
|
+
otelSpan.setAttribute(
|
|
467
|
+
"gen_ai.usage.cache_creation.input_tokens",
|
|
468
|
+
inputDetails["cacheWrite"],
|
|
469
|
+
);
|
|
434
470
|
}
|
|
435
471
|
|
|
436
472
|
// Finish reason
|
|
@@ -505,11 +541,17 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
505
541
|
// Skip system messages (handled separately)
|
|
506
542
|
if (msg.role === "system") continue;
|
|
507
543
|
|
|
508
|
-
// Standard role-based messages
|
|
544
|
+
// Standard role-based messages (AIv4 uses msg.content; AIv5 may use msg.parts)
|
|
509
545
|
if (msg.role) {
|
|
546
|
+
const parts =
|
|
547
|
+
msg.content != null
|
|
548
|
+
? this._extractParts(msg.content)
|
|
549
|
+
: Array.isArray(msg.parts)
|
|
550
|
+
? this._extractParts(msg.parts)
|
|
551
|
+
: [];
|
|
510
552
|
result.push({
|
|
511
553
|
role: msg.role as InputMessage["role"],
|
|
512
|
-
parts
|
|
554
|
+
parts,
|
|
513
555
|
});
|
|
514
556
|
continue;
|
|
515
557
|
}
|
|
@@ -588,6 +630,25 @@ export class IntrospectionMastraExporter extends BaseExporter {
|
|
|
588
630
|
return [{ role: "assistant", parts, finish_reason: finishReason }];
|
|
589
631
|
}
|
|
590
632
|
|
|
633
|
+
/** Extract plain text from a message content field (string, array of parts, or AIv5 parts). */
|
|
634
|
+
private _extractTextContent(content: unknown): string {
|
|
635
|
+
if (typeof content === "string") return content;
|
|
636
|
+
if (Array.isArray(content)) {
|
|
637
|
+
return content
|
|
638
|
+
.map((p: unknown) => {
|
|
639
|
+
const part = p as Record<string, unknown>;
|
|
640
|
+
return typeof part.text === "string"
|
|
641
|
+
? part.text
|
|
642
|
+
: typeof part.content === "string"
|
|
643
|
+
? part.content
|
|
644
|
+
: "";
|
|
645
|
+
})
|
|
646
|
+
.filter(Boolean)
|
|
647
|
+
.join("\n");
|
|
648
|
+
}
|
|
649
|
+
return typeof content === "undefined" ? "" : JSON.stringify(content);
|
|
650
|
+
}
|
|
651
|
+
|
|
591
652
|
private _extractParts(content: unknown): MessagePart[] {
|
|
592
653
|
if (typeof content === "string") {
|
|
593
654
|
return [{ type: "text", content }];
|
package/src/otel/pi.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level pi Agent SDK integration for Introspection.
|
|
3
|
+
*
|
|
4
|
+
* Wraps {@link @introspection-sdk/introspection-pi} behind a zero-config
|
|
5
|
+
* interface so users don't need to manage OpenTelemetry tracers directly.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { IntrospectionPiInstrumentor, setupTracing } from "@introspection-sdk/introspection-node";
|
|
10
|
+
* import { Agent } from "@mariozechner/pi-agent-core";
|
|
11
|
+
*
|
|
12
|
+
* setupTracing({ serviceName: "my-app" });
|
|
13
|
+
* const instrumentor = new IntrospectionPiInstrumentor();
|
|
14
|
+
*
|
|
15
|
+
* const agent = new Agent({ ... });
|
|
16
|
+
* instrumentor.instrument(agent, {
|
|
17
|
+
* conversationId: "conv-123",
|
|
18
|
+
* agentId: "weather-agent",
|
|
19
|
+
* agentName: "Weather",
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await agent.prompt("What's the weather in Tokyo?");
|
|
23
|
+
* instrumentor.stop();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { trace } from "@opentelemetry/api";
|
|
28
|
+
import type { Agent } from "@mariozechner/pi-agent-core";
|
|
29
|
+
import {
|
|
30
|
+
instrumentStream,
|
|
31
|
+
instrumentAgent,
|
|
32
|
+
type AgentMeta,
|
|
33
|
+
type AgentInstrumentation,
|
|
34
|
+
} from "@introspection-sdk/introspection-pi";
|
|
35
|
+
import { VERSION } from "../version.js";
|
|
36
|
+
|
|
37
|
+
export type { AgentMeta } from "@introspection-sdk/introspection-pi";
|
|
38
|
+
|
|
39
|
+
export interface IntrospectionPiInstrumentorOptions {
|
|
40
|
+
/** Tracer name used for all spans produced by this instrumentor. */
|
|
41
|
+
tracerName?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Zero-config pi Agent SDK integration for Introspection.
|
|
46
|
+
*
|
|
47
|
+
* Uses the global OTel tracer provider (registered by {@link setupTracing}).
|
|
48
|
+
* Call {@link instrument} once per {@link Agent} instance, then {@link stop}
|
|
49
|
+
* to unsubscribe all tool instrumentation and finalize open spans.
|
|
50
|
+
*/
|
|
51
|
+
export class IntrospectionPiInstrumentor {
|
|
52
|
+
private _tracer: ReturnType<typeof trace.getTracer>;
|
|
53
|
+
private _instrumentations: AgentInstrumentation[] = [];
|
|
54
|
+
|
|
55
|
+
constructor(opts: IntrospectionPiInstrumentorOptions = {}) {
|
|
56
|
+
this._tracer = trace.getTracer(
|
|
57
|
+
opts.tracerName ?? "introspection-pi",
|
|
58
|
+
VERSION,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Instrument a pi {@link Agent}:
|
|
64
|
+
* - Wraps `agent.streamFn` to emit a `chat ${provider}` span per LLM call.
|
|
65
|
+
* - Subscribes to the agent loop to emit an `execute_tool ${name}` span per
|
|
66
|
+
* tool execution.
|
|
67
|
+
*
|
|
68
|
+
* @param agent - The pi Agent instance to instrument.
|
|
69
|
+
* @param meta - Identity metadata stamped on every span produced by this agent.
|
|
70
|
+
*/
|
|
71
|
+
instrument(agent: Agent, meta: AgentMeta): void {
|
|
72
|
+
agent.streamFn = instrumentStream(agent.streamFn, {
|
|
73
|
+
tracer: this._tracer,
|
|
74
|
+
meta,
|
|
75
|
+
});
|
|
76
|
+
this._instrumentations.push(
|
|
77
|
+
instrumentAgent(agent, { tracer: this._tracer, meta }),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Unsubscribe all active tool instrumentations and finalize any open spans.
|
|
83
|
+
*/
|
|
84
|
+
stop(): void {
|
|
85
|
+
for (const inst of this._instrumentations) inst.stop();
|
|
86
|
+
this._instrumentations = [];
|
|
87
|
+
}
|
|
88
|
+
}
|