Skip to content
Open
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
88 changes: 61 additions & 27 deletions e2e/scenarios/anthropic-instrumentation/scenario.impl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ const WEATHER_TOOL = {

async function runAnthropicInstrumentationScenario(
Anthropic,
{ decorateClient, useBetaMessages = true, supportsThinking = false } = {},
{
decorateClient,
useBetaMessages = true,
useMessagesStreamHelper = true,
supportsThinking = false,
// v0.11.0 of the orchestrion-js code transformer wraps promise-returning
// functions via promise.then(), replacing the original APIPromise with a
// plain Promise. This makes .withResponse() unavailable under auto-
// instrumentation. Set to false when using the ESM hook or bundler plugins
// until the transformer is updated to preserve the original return value.
supportsWithResponse = true,
} = {},
) {
const imageBase64 = (
await readFile(new URL("./test-image.png", import.meta.url))
Expand All @@ -51,20 +62,23 @@ async function runAnthropicInstrumentationScenario(
"anthropic-create-with-response-operation",
"create-with-response",
async () => {
const response = await client.messages
.create({
model: ANTHROPIC_MODEL,
max_tokens: 16,
temperature: 0,
messages: [
{
role: "user",
content: "Reply with exactly WITH_RESPONSE.",
},
],
})
.withResponse();
void response.data;
const result = client.messages.create({
model: ANTHROPIC_MODEL,
max_tokens: 16,
temperature: 0,
messages: [
{
role: "user",
content: "Reply with exactly WITH_RESPONSE.",
},
],
});
if (supportsWithResponse) {
const response = await result.withResponse();
void response.data;
} else {
await result;
}
},
);

Expand Down Expand Up @@ -119,18 +133,33 @@ async function runAnthropicInstrumentationScenario(
"anthropic-stream-with-response-operation",
"stream-with-response",
async () => {
const stream = client.messages.stream({
model: ANTHROPIC_MODEL,
max_tokens: 32,
temperature: 0,
messages: [
{
role: "user",
content:
"Count from 1 to 3 and include the words one two three.",
},
],
});
const stream =
useMessagesStreamHelper === false
? await client.messages.create({
model: ANTHROPIC_MODEL,
max_tokens: 32,
temperature: 0,
stream: true,
messages: [
{
role: "user",
content:
"Count from 1 to 3 and include the words one two three.",
},
],
})
: client.messages.stream({
model: ANTHROPIC_MODEL,
max_tokens: 32,
temperature: 0,
messages: [
{
role: "user",
content:
"Count from 1 to 3 and include the words one two three.",
},
],
});
await collectAsync(stream);
},
);
Expand Down Expand Up @@ -258,6 +287,11 @@ export async function runWrappedAnthropicInstrumentation(Anthropic, options) {
export async function runAutoAnthropicInstrumentation(Anthropic, options) {
await runAnthropicInstrumentationScenario(Anthropic, {
...options,
useMessagesStreamHelper: false,
// The orchestrion-js v0.11.0 transformer wraps promise.then() around the
// return value, replacing APIPromise with a plain Promise, which makes
// .withResponse() unavailable under auto-instrumentation.
supportsWithResponse: false,
});
}

Expand Down
4 changes: 2 additions & 2 deletions js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
},
"dependencies": {
"@ai-sdk/provider": "^1.1.3",
"@apm-js-collab/code-transformer": "^0.9.0",
"@apm-js-collab/code-transformer": "^0.11.0",
"@next/env": "^14.2.3",
"@vercel/functions": "^1.0.2",
"ajv": "^8.17.1",
Expand All @@ -209,7 +209,7 @@
"cli-progress": "^3.12.0",
"cli-table3": "^0.6.5",
"cors": "^2.8.5",
"dc-browser": "^1.0.3",
"dc-browser": "^1.0.4",
"dotenv": "^16.4.5",
"esbuild": "^0.27.0",
"eventsource-parser": "^1.1.2",
Expand Down
2 changes: 1 addition & 1 deletion js/src/auto-instrumentations/bundler/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const unplugin = createUnplugin<BundlerPluginOptions>((options = {}) => {
}

// Try to get a transformer for this file
// Normalize the module path for Windows compatibility (WASM transformer expects forward slashes)
// Normalize the module path for Windows compatibility (transformer expects forward slashes)
const normalizedModulePath = moduleDetails.path.replace(/\\/g, "/");
const transformer = instrumentationMatcher.getTransformer(
moduleName,
Expand Down
2 changes: 1 addition & 1 deletion js/src/auto-instrumentations/bundler/webpack-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function codeTransformerLoader(
return callback(null, code, inputSourceMap);
}

// Normalize the module path for Windows compatibility (WASM transformer expects forward slashes)
// Normalize the module path for Windows compatibility (transformer expects forward slashes)
const normalizedModulePath = moduleDetails.path.replace(/\\/g, "/");

const matcher = getMatcher(options);
Expand Down
8 changes: 4 additions & 4 deletions js/src/auto-instrumentations/configs/ai-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ export const aiSDKConfigs: InstrumentationConfig[] = [
},
},
{
channelName: aiSDKChannels.streamText.channelName,
channelName: aiSDKChannels.streamTextSync.channelName,
module: {
name: "ai",
versionRange: ">=3.0.0",
filePath: "dist/index.js",
},
functionQuery: {
functionName: "streamText",
kind: "Async",
kind: "Sync",
},
},

Expand Down Expand Up @@ -105,15 +105,15 @@ export const aiSDKConfigs: InstrumentationConfig[] = [
},
},
{
channelName: aiSDKChannels.streamObject.channelName,
channelName: aiSDKChannels.streamObjectSync.channelName,
module: {
name: "ai",
versionRange: ">=3.0.0",
filePath: "dist/index.js",
},
functionQuery: {
functionName: "streamObject",
kind: "Async",
kind: "Sync",
},
},

Expand Down
2 changes: 1 addition & 1 deletion js/src/auto-instrumentations/loader/cjs-patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class ModulePatch {

const version = getPackageVersion(resolvedModule.basedir);

// Normalize module path for WASM transformer (expects forward slashes)
// Normalize module path for transformer (expects forward slashes)
const normalizedModulePath = resolvedModule.path.replace(/\\/g, "/");

const transformer = self.instrumentator.getTransformer(
Expand Down
10 changes: 8 additions & 2 deletions js/src/auto-instrumentations/loader/esm-hook.mts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function resolve(
}

const version = getPackageVersion(resolvedModule.basedir);
// Normalize module path for WASM transformer (expects forward slashes)
// Normalize module path for transformer (expects forward slashes)
const normalizedModulePath = resolvedModule.path.replace(/\\/g, "/");

const transformer = instrumentator.getTransformer(
Expand Down Expand Up @@ -86,9 +86,15 @@ export async function load(url: string, context: any, nextLoad: Function) {
if (code) {
const transformer = transformers.get(url);
try {
const moduleType =
result.format === "module"
? "esm"
: result.format === "commonjs"
? "cjs"
: "unknown";
const transformedCode = transformer.transform(
code.toString("utf8"),
"unknown",
moduleType,
);
result.source = transformedCode?.code;
result.shortCircuit = true;
Expand Down
18 changes: 18 additions & 0 deletions js/src/instrumentation/plugins/ai-sdk-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export const aiSDKChannels = defineChannels("ai", {
channelName: "streamText",
kind: "async",
}),
streamTextSync: channel<
[AISDKCallParams],
AISDKResult,
AISDKChannelContext,
unknown
>({
channelName: "streamText.sync",
kind: "sync-stream",
}),
generateObject: channel<
[AISDKCallParams],
AISDKStreamResult,
Expand All @@ -51,6 +60,15 @@ export const aiSDKChannels = defineChannels("ai", {
channelName: "streamObject",
kind: "async",
}),
streamObjectSync: channel<
[AISDKCallParams],
AISDKResult,
AISDKChannelContext,
unknown
>({
channelName: "streamObject.sync",
kind: "sync-stream",
}),
agentGenerate: channel<
[AISDKCallParams],
AISDKStreamResult,
Expand Down
42 changes: 41 additions & 1 deletion js/src/instrumentation/plugins/ai-sdk-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { BasePlugin } from "../core";
import { traceStreamingChannel, unsubscribeAll } from "../core/channel-tracing";
import {
traceStreamingChannel,
traceSyncStreamChannel,
unsubscribeAll,
} from "../core/channel-tracing";
import { SpanTypeAttribute } from "../../../util/index";
import { getCurrentUnixTimestamp } from "../../util";
import { Attachment, type Span, withCurrent } from "../../logger";
Expand Down Expand Up @@ -144,6 +148,24 @@ export class AISDKPlugin extends BasePlugin {
}),
);

// streamText - sync function returning stream (CommonJS bundle)
this.unsubscribers.push(
traceSyncStreamChannel(aiSDKChannels.streamTextSync, {
name: "streamText",
type: SpanTypeAttribute.LLM,
extractInput: ([params], event, span) =>
prepareAISDKInput(params, event, span, denyOutputPaths),
patchResult: ({ endEvent, result, span, startTime }) =>
patchAISDKStreamingResult({
defaultDenyOutputPaths: denyOutputPaths,
endEvent,
result,
span,
startTime,
}),
}),
);

// generateObject - async function that may return streams
this.unsubscribers.push(
traceStreamingChannel(aiSDKChannels.generateObject, {
Expand Down Expand Up @@ -190,6 +212,24 @@ export class AISDKPlugin extends BasePlugin {
}),
);

// streamObject - sync function returning stream (CommonJS bundle)
this.unsubscribers.push(
traceSyncStreamChannel(aiSDKChannels.streamObjectSync, {
name: "streamObject",
type: SpanTypeAttribute.LLM,
extractInput: ([params], event, span) =>
prepareAISDKInput(params, event, span, denyOutputPaths),
patchResult: ({ endEvent, result, span, startTime }) =>
patchAISDKStreamingResult({
defaultDenyOutputPaths: denyOutputPaths,
endEvent,
result,
span,
startTime,
}),
}),
);

// Agent.generate - async method
this.unsubscribers.push(
traceStreamingChannel(aiSDKChannels.agentGenerate, {
Expand Down
5 changes: 0 additions & 5 deletions js/tests/auto-instrumentations/transformation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ describe("Orchestrion Transformation Tests", () => {
// Verify orchestrion transformed the code
expect(output).toContain("tracingChannel");
expect(output).toContain("orchestrion:openai:chat.completions.create");
expect(output).toContain("tracePromise");
});

it("should bundle dc-browser module when browser: true", async () => {
Expand Down Expand Up @@ -149,7 +148,6 @@ describe("Orchestrion Transformation Tests", () => {
// Verify orchestrion transformed the code
expect(output).toContain("tracingChannel");
expect(output).toContain("orchestrion:openai:chat.completions.create");
expect(output).toContain("tracePromise");
});

it("should bundle dc-browser module when browser: true", async () => {
Expand Down Expand Up @@ -244,7 +242,6 @@ describe("Orchestrion Transformation Tests", () => {
expect(errors).toHaveLength(0);
expect(output).toContain("tracingChannel");
expect(output).toContain("orchestrion:openai:chat.completions.create");
expect(output).toContain("tracePromise");
});

it("should bundle dc-browser module when browser: true", async () => {
Expand Down Expand Up @@ -334,7 +331,6 @@ describe("Orchestrion Transformation Tests", () => {
expect(errors).toHaveLength(0);
expect(output).toContain("tracingChannel");
expect(output).toContain("orchestrion:openai:chat.completions.create");
expect(output).toContain("tracePromise");
});

it("should bundle dc-browser polyfill when browser: true (turbopack loader-only mode)", async () => {
Expand Down Expand Up @@ -418,7 +414,6 @@ describe("Orchestrion Transformation Tests", () => {
// Verify orchestrion transformed the code
expect(output).toContain("tracingChannel");
expect(output).toContain("orchestrion:openai:chat.completions.create");
expect(output).toContain("tracePromise");
});

it("should bundle dc-browser module when browser: true", async () => {
Expand Down
Loading
Loading