11'use strict'
22
3- const { channel } = require ( 'dc-polyfill' )
3+ const { channel, tracingChannel } = require ( 'dc-polyfill' )
44const shimmer = require ( '../../datadog-shimmer' )
5- const { addHook } = require ( './helpers/instrument' )
5+ const { addHook, getHooks } = require ( './helpers/instrument' )
66
77// `WeakSet` keyed by module exports — replaces the underscored
88// `mod._datadogPatched` flag while keeping dedupe semantics. Mods are kept
99// alive by `require.cache` anyway, so this doesn't add lifetime to anything.
1010const patchedMods = new WeakSet ( )
11+ const modelBaseURLs = new WeakMap ( )
12+ const chatCompletionsModelConstructorCh =
13+ tracingChannel ( 'orchestrion:@openai/agents-openai:OpenAIChatCompletionsModel_constructor' )
14+
15+ /**
16+ * Capture the client base URL before the SDK stores the client in a #private field.
17+ *
18+ * @param {{ arguments?: [{ baseURL?: string }], self?: object } } ctx
19+ */
20+ function captureChatCompletionsModelBaseURL ( ctx ) {
21+ const baseURL = ctx . arguments ?. [ 0 ] ?. baseURL
22+ if ( ctx . self && typeof baseURL === 'string' ) modelBaseURLs . set ( ctx . self , baseURL )
23+ }
24+
25+ chatCompletionsModelConstructorCh . end . subscribe ( captureChatCompletionsModelBaseURL )
26+
27+ for ( const hook of getHooks ( '@openai/agents-openai' ) ) {
28+ addHook ( hook , moduleExports => moduleExports )
29+ }
1130
1231// Plugin subscribes to this and registers its TracingProcessor when
1332// `@openai/agents` loads. Publishing from here keeps this file free of
1433// any cross-package import from the plugin.
1534const agentsCoreLoadedCh = channel ( 'apm:openai-agents:agents-core:loaded' )
1635
17- // Plugin subscribes here to keep track of the OpenAI-compatible client's
18- // baseURL — used to resolve `model_provider` (openai / azure_openai /
19- // deepseek / unknown).
20- const responseClientCh = channel ( 'apm:openai-agents:response:client' )
21-
2236// Plugin uses addBind on this channel so that legacyStorage.run(store, fn) wraps
2337// the model call — including async iterator advancement for streaming responses.
2438// This ensures the active dd-trace span is visible to the openai plugin when it
2539// creates its openai.request span, correctly parenting it under the agent span.
2640const modelStartCh = channel ( 'apm:openai-agents:model:start' )
2741
42+ // Tool.invoke runs inside agents-core's public function-span context. Bind the
43+ // corresponding dd-trace tool span around that public invocation boundary so
44+ // spans created by user tool code inherit it.
45+ const toolStartCh = channel ( 'apm:openai-agents:tool:start' )
46+
2847// Reference to the loaded @openai /agents module, captured in the first hook
2948// so that wrapResponseMethod can call getCurrentSpan() without an additional
3049// require (and without triggering n/no-missing-require on agents-core internals).
@@ -36,60 +55,84 @@ let agentsMod
3655// provider.getCurrentSpan() (replaces getCurrentSpan)
3756// Both APIs are tried so this file works across the full supported version range.
3857// The plugin subscriber (index.js) handles processor registration via the channel.
39- function getCurrentSpanId ( mod ) {
40- if ( typeof mod ?. getCurrentSpan === 'function' ) {
41- return mod . getCurrentSpan ( ) ?. spanId
58+ function getCurrentSpan ( ) {
59+ if ( typeof agentsMod ?. getCurrentSpan === 'function' ) {
60+ return agentsMod . getCurrentSpan ( )
4261 }
43- if ( typeof mod ?. getGlobalTraceProvider === 'function' ) {
44- return mod . getGlobalTraceProvider ( ) . getCurrentSpan ( ) ?. spanId
62+ if ( typeof agentsMod ?. getGlobalTraceProvider === 'function' ) {
63+ return agentsMod . getGlobalTraceProvider ( ) . getCurrentSpan ( )
4564 }
4665}
4766
67+ function getCurrentSpanId ( ) {
68+ return getCurrentSpan ( ) ?. spanId
69+ }
70+
4871addHook ( { name : '@openai/agents' , versions : [ '>=0.7.0' ] } , ( mod ) => {
4972 if ( patchedMods . has ( mod ) ) return mod
5073 if ( typeof mod ?. addTraceProcessor !== 'function' && typeof mod ?. getGlobalTraceProvider !== 'function' ) return mod
5174 patchedMods . add ( mod )
5275 agentsMod = mod
76+ if ( typeof mod . tool === 'function' ) {
77+ shimmer . wrap ( mod , 'tool' , wrapToolFactory , { replaceGetter : true } )
78+ }
5379 agentsCoreLoadedCh . publish ( { mod } )
5480 return mod
5581} )
5682
83+ function wrapToolFactory ( original ) {
84+ return function ( ...args ) {
85+ const tool = original . apply ( this , args )
86+ if ( typeof tool ?. invoke === 'function' ) {
87+ shimmer . wrap ( tool , 'invoke' , wrapToolInvoke )
88+ }
89+ return tool
90+ }
91+ }
92+
93+ function wrapToolInvoke ( original ) {
94+ return function ( ...args ) {
95+ const agentsCoreSpan = getCurrentSpan ( )
96+ return toolStartCh . runStores ( { agentsCoreSpan } , ( ) => original . apply ( this , args ) )
97+ }
98+ }
99+
57100function wrapResponseMethod ( original ) {
58101 return function ( ...args ) {
59- const agentsCoreSpanId = getCurrentSpanId ( agentsMod )
60- publishClientBaseURL ( this )
61- return modelStartCh . runStores ( { agentsCoreSpanId } , ( ) => original . apply ( this , args ) )
102+ const agentsCoreSpanId = getCurrentSpanId ( )
103+ const baseURL = getClientBaseURL ( this )
104+ return modelStartCh . runStores ( { agentsCoreSpanId, baseURL } , ( ) => original . apply ( this , args ) )
62105 }
63106}
64107
65108function wrapStreamedResponseMethod ( original ) {
66109 return function ( ...args ) {
67- const agentsCoreSpanId = getCurrentSpanId ( agentsMod )
68- publishClientBaseURL ( this )
69- const iterator = modelStartCh . runStores ( { agentsCoreSpanId } , ( ) => original . apply ( this , args ) )
70- return wrapAsyncIterator ( iterator , agentsCoreSpanId )
110+ const agentsCoreSpanId = getCurrentSpanId ( )
111+ const baseURL = getClientBaseURL ( this )
112+ const context = { agentsCoreSpanId, baseURL }
113+ const iterator = modelStartCh . runStores ( context , ( ) => original . apply ( this , args ) )
114+ return wrapAsyncIterator ( iterator , context )
71115 }
72116}
73117
74- function publishClientBaseURL ( model ) {
75- const baseURL = model ?. client ?. baseURL ?? model ?. _client ?. baseURL
76- if ( baseURL ) responseClientCh . publish ( { baseURL } )
118+ function getClientBaseURL ( model ) {
119+ return model ?. client ?. baseURL ?? model ?. _client ?. baseURL ?? modelBaseURLs . get ( model )
77120}
78121
79- function wrapAsyncIterator ( iterator , agentsCoreSpanId ) {
122+ function wrapAsyncIterator ( iterator , context ) {
80123 if ( ! iterator || typeof iterator !== 'object' ) return iterator
81124
82125 return {
83126 next ( ) {
84- return modelStartCh . runStores ( { agentsCoreSpanId } , ( ) => iterator . next . apply ( iterator , arguments ) )
127+ return modelStartCh . runStores ( context , ( ) => iterator . next . apply ( iterator , arguments ) )
85128 } ,
86129 throw ( ) {
87130 if ( typeof iterator . throw !== 'function' ) return Promise . reject ( arguments [ 0 ] )
88- return modelStartCh . runStores ( { agentsCoreSpanId } , ( ) => iterator . throw . apply ( iterator , arguments ) )
131+ return modelStartCh . runStores ( context , ( ) => iterator . throw . apply ( iterator , arguments ) )
89132 } ,
90133 return ( ) {
91134 if ( typeof iterator . return !== 'function' ) return Promise . resolve ( { done : true , value : arguments [ 0 ] } )
92- return modelStartCh . runStores ( { agentsCoreSpanId } , ( ) => iterator . return . apply ( iterator , arguments ) )
135+ return modelStartCh . runStores ( context , ( ) => iterator . return . apply ( iterator , arguments ) )
93136 } ,
94137 [ Symbol . asyncIterator ] ( ) {
95138 return this
@@ -99,11 +142,18 @@ function wrapAsyncIterator (iterator, agentsCoreSpanId) {
99142
100143addHook ( { name : '@openai/agents-openai' , versions : [ '>=0.7.0' ] } , ( mod ) => {
101144 if ( patchedMods . has ( mod ) ) return mod
102- const proto = mod ?. OpenAIResponsesModel ?. prototype
103- if ( ! proto ) return mod
145+ const responseProto = mod ?. OpenAIResponsesModel ?. prototype
146+ const chatCompletionsProto = mod ?. OpenAIChatCompletionsModel ?. prototype
147+ if ( ! responseProto && ! chatCompletionsProto ) return mod
104148
105149 patchedMods . add ( mod )
106- shimmer . wrap ( proto , 'getResponse' , wrapResponseMethod )
107- shimmer . wrap ( proto , 'getStreamedResponse' , wrapStreamedResponseMethod )
150+ for ( const proto of [ responseProto , chatCompletionsProto ] ) {
151+ if ( typeof proto ?. getResponse === 'function' ) {
152+ shimmer . wrap ( proto , 'getResponse' , wrapResponseMethod )
153+ }
154+ if ( typeof proto ?. getStreamedResponse === 'function' ) {
155+ shimmer . wrap ( proto , 'getStreamedResponse' , wrapStreamedResponseMethod )
156+ }
157+ }
108158 return mod
109159} )
0 commit comments