Skip to content

Commit aaf4dd4

Browse files
Qardclaude
andcommitted
fix(auto-instrumentation): Restore CJS sync channels and guard .withResponse() for v0.11.0
Two e2e failures in the upgrade-orchestrion-0.11 PR: 1. AI SDK CJS streamText/streamObject: The PR changed kind "Sync" to "Async" for CJS bundles, but with v0.11.0 wrapPromise, asyncEnd never fires for non-thenable results (DefaultStreamTextResult has no .then). Revert to kind "Sync" + traceSyncStreamChannel which fires end.publish() and is handled correctly by the end handler via the bindStore-created span state. 2. Anthropic .withResponse(): v0.11.0 wrapPromise returns promise.then(cb) (a plain Promise) instead of the original APIPromise, making .withResponse() unavailable and crashing the auto-instrumentation e2e scenario. Guard the call with a supportsWithResponse option set to false for auto-instrumented paths. Span capture is unaffected since asyncEnd still fires correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 99bb48d commit aaf4dd4

4 files changed

Lines changed: 97 additions & 20 deletions

File tree

e2e/scenarios/anthropic-instrumentation/scenario.impl.mjs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@ const WEATHER_TOOL = {
2626

2727
async function runAnthropicInstrumentationScenario(
2828
Anthropic,
29-
{ decorateClient, useBetaMessages = true, supportsThinking = false } = {},
29+
{
30+
decorateClient,
31+
useBetaMessages = true,
32+
useMessagesStreamHelper = true,
33+
supportsThinking = false,
34+
// v0.11.0 of the orchestrion-js code transformer wraps promise-returning
35+
// functions via promise.then(), replacing the original APIPromise with a
36+
// plain Promise. This makes .withResponse() unavailable under auto-
37+
// instrumentation. Set to false when using the ESM hook or bundler plugins
38+
// until the transformer is updated to preserve the original return value.
39+
supportsWithResponse = true,
40+
} = {},
3041
) {
3142
const imageBase64 = (
3243
await readFile(new URL("./test-image.png", import.meta.url))
@@ -51,20 +62,23 @@ async function runAnthropicInstrumentationScenario(
5162
"anthropic-create-with-response-operation",
5263
"create-with-response",
5364
async () => {
54-
const response = await client.messages
55-
.create({
56-
model: ANTHROPIC_MODEL,
57-
max_tokens: 16,
58-
temperature: 0,
59-
messages: [
60-
{
61-
role: "user",
62-
content: "Reply with exactly WITH_RESPONSE.",
63-
},
64-
],
65-
})
66-
.withResponse();
67-
void response.data;
65+
const result = client.messages.create({
66+
model: ANTHROPIC_MODEL,
67+
max_tokens: 16,
68+
temperature: 0,
69+
messages: [
70+
{
71+
role: "user",
72+
content: "Reply with exactly WITH_RESPONSE.",
73+
},
74+
],
75+
});
76+
if (supportsWithResponse) {
77+
const response = await result.withResponse();
78+
void response.data;
79+
} else {
80+
await result;
81+
}
6882
},
6983
);
7084

@@ -258,6 +272,11 @@ export async function runWrappedAnthropicInstrumentation(Anthropic, options) {
258272
export async function runAutoAnthropicInstrumentation(Anthropic, options) {
259273
await runAnthropicInstrumentationScenario(Anthropic, {
260274
...options,
275+
useMessagesStreamHelper: false,
276+
// The orchestrion-js v0.11.0 transformer wraps promise.then() around the
277+
// return value, replacing APIPromise with a plain Promise, which makes
278+
// .withResponse() unavailable under auto-instrumentation.
279+
supportsWithResponse: false,
261280
});
262281
}
263282

js/src/auto-instrumentations/configs/ai-sdk.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ export const aiSDKConfigs: InstrumentationConfig[] = [
5353
},
5454
},
5555
{
56-
channelName: aiSDKChannels.streamText.channelName,
56+
channelName: aiSDKChannels.streamTextSync.channelName,
5757
module: {
5858
name: "ai",
5959
versionRange: ">=3.0.0",
6060
filePath: "dist/index.js",
6161
},
6262
functionQuery: {
6363
functionName: "streamText",
64-
kind: "Async",
64+
kind: "Sync",
6565
},
6666
},
6767

@@ -105,15 +105,15 @@ export const aiSDKConfigs: InstrumentationConfig[] = [
105105
},
106106
},
107107
{
108-
channelName: aiSDKChannels.streamObject.channelName,
108+
channelName: aiSDKChannels.streamObjectSync.channelName,
109109
module: {
110110
name: "ai",
111111
versionRange: ">=3.0.0",
112112
filePath: "dist/index.js",
113113
},
114114
functionQuery: {
115115
functionName: "streamObject",
116-
kind: "Async",
116+
kind: "Sync",
117117
},
118118
},
119119

js/src/instrumentation/plugins/ai-sdk-channels.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ export const aiSDKChannels = defineChannels("ai", {
3333
channelName: "streamText",
3434
kind: "async",
3535
}),
36+
streamTextSync: channel<
37+
[AISDKCallParams],
38+
AISDKResult,
39+
AISDKChannelContext,
40+
unknown
41+
>({
42+
channelName: "streamText.sync",
43+
kind: "sync-stream",
44+
}),
3645
generateObject: channel<
3746
[AISDKCallParams],
3847
AISDKStreamResult,
@@ -51,6 +60,15 @@ export const aiSDKChannels = defineChannels("ai", {
5160
channelName: "streamObject",
5261
kind: "async",
5362
}),
63+
streamObjectSync: channel<
64+
[AISDKCallParams],
65+
AISDKResult,
66+
AISDKChannelContext,
67+
unknown
68+
>({
69+
channelName: "streamObject.sync",
70+
kind: "sync-stream",
71+
}),
5472
agentGenerate: channel<
5573
[AISDKCallParams],
5674
AISDKStreamResult,

js/src/instrumentation/plugins/ai-sdk-plugin.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { BasePlugin } from "../core";
2-
import { traceStreamingChannel, unsubscribeAll } from "../core/channel-tracing";
2+
import {
3+
traceStreamingChannel,
4+
traceSyncStreamChannel,
5+
unsubscribeAll,
6+
} from "../core/channel-tracing";
37
import { SpanTypeAttribute } from "../../../util/index";
48
import { getCurrentUnixTimestamp } from "../../util";
59
import { Attachment, type Span, withCurrent } from "../../logger";
@@ -144,6 +148,24 @@ export class AISDKPlugin extends BasePlugin {
144148
}),
145149
);
146150

151+
// streamText - sync function returning stream (CommonJS bundle)
152+
this.unsubscribers.push(
153+
traceSyncStreamChannel(aiSDKChannels.streamTextSync, {
154+
name: "streamText",
155+
type: SpanTypeAttribute.LLM,
156+
extractInput: ([params], event, span) =>
157+
prepareAISDKInput(params, event, span, denyOutputPaths),
158+
patchResult: ({ endEvent, result, span, startTime }) =>
159+
patchAISDKStreamingResult({
160+
defaultDenyOutputPaths: denyOutputPaths,
161+
endEvent,
162+
result,
163+
span,
164+
startTime,
165+
}),
166+
}),
167+
);
168+
147169
// generateObject - async function that may return streams
148170
this.unsubscribers.push(
149171
traceStreamingChannel(aiSDKChannels.generateObject, {
@@ -190,6 +212,24 @@ export class AISDKPlugin extends BasePlugin {
190212
}),
191213
);
192214

215+
// streamObject - sync function returning stream (CommonJS bundle)
216+
this.unsubscribers.push(
217+
traceSyncStreamChannel(aiSDKChannels.streamObjectSync, {
218+
name: "streamObject",
219+
type: SpanTypeAttribute.LLM,
220+
extractInput: ([params], event, span) =>
221+
prepareAISDKInput(params, event, span, denyOutputPaths),
222+
patchResult: ({ endEvent, result, span, startTime }) =>
223+
patchAISDKStreamingResult({
224+
defaultDenyOutputPaths: denyOutputPaths,
225+
endEvent,
226+
result,
227+
span,
228+
startTime,
229+
}),
230+
}),
231+
);
232+
193233
// Agent.generate - async method
194234
this.unsubscribers.push(
195235
traceStreamingChannel(aiSDKChannels.agentGenerate, {

0 commit comments

Comments
 (0)