diff --git a/CHANGELOG.md b/CHANGELOG.md index 237cad79..4de4f307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @agentuity/sdk Changelog +## 0.0.152 + +### Patch Changes + +- Fixed issue with otel headers not propagating for outbound services + ## 0.0.151 ### Patch Changes diff --git a/package-lock.json b/package-lock.json index c076c0dd..99189b1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agentuity/sdk", - "version": "0.0.150", + "version": "0.0.152", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agentuity/sdk", - "version": "0.0.150", + "version": "0.0.152", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/package.json b/package.json index fe9a3433..d7ef323a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agentuity/sdk", - "version": "0.0.151", + "version": "0.0.152", "description": "The Agentuity SDK for NodeJS and Bun", "license": "Apache-2.0", "public": true, @@ -97,4 +97,4 @@ "mailparser": "^3.7.4", "nodemailer": "^7.0.3" } -} \ No newline at end of file +} diff --git a/src/apis/api.ts b/src/apis/api.ts index dfec458c..024ac48f 100644 --- a/src/apis/api.ts +++ b/src/apis/api.ts @@ -1,3 +1,4 @@ +import { context, propagation } from '@opentelemetry/api'; import { getSDKVersion } from '../router/router'; import { isReadableStream } from '../types'; @@ -141,6 +142,15 @@ export async function send( for (const key in request.headers) { headers[key] = request.headers[key]; } + + // inject trace context headers if there's an active context + const currentContext = context.active(); + propagation.inject(currentContext, headers, { + set: (carrier, key, value) => { + carrier[key] = value; + }, + }); + // this shouldn't be overridden headers.Authorization = `Bearer ${apiKey}`; const init: RequestInit & { duplex?: 'half' } = { diff --git a/src/apis/prompt/generated/_index.js b/src/apis/prompt/generated/_index.js index d134ce22..31846a8c 100644 --- a/src/apis/prompt/generated/_index.js +++ b/src/apis/prompt/generated/_index.js @@ -1,5 +1,4 @@ -export const prompts = { -}; +export const prompts = {}; // Export types for compatibility export const PromptConfig = undefined; // Type-only export, value is not used diff --git a/src/apis/vector.ts b/src/apis/vector.ts index db808371..ee86d58a 100644 --- a/src/apis/vector.ts +++ b/src/apis/vector.ts @@ -137,7 +137,7 @@ export default class VectorAPI implements VectorStorage { ); if (resp.status === 200) { if (resp.json?.success) { - const json = resp.json as unknown as { data: { id: string; }[]; }; + const json = resp.json as unknown as { data: { id: string }[] }; span.setStatus({ code: SpanStatusCode.OK }); return json.data.map((o) => o.id); } diff --git a/test/apis/api.test.ts b/test/apis/api.test.ts index 180d8a61..844366cd 100644 --- a/test/apis/api.test.ts +++ b/test/apis/api.test.ts @@ -11,6 +11,7 @@ import { } from '../../src/apis/api'; import { createMockFetch } from '../setup'; import { ReadableStream } from 'node:stream/web'; +import { context, ROOT_CONTEXT } from '@opentelemetry/api'; describe('API Client', () => { let originalEnv: NodeJS.ProcessEnv; @@ -646,4 +647,87 @@ describe('API Client', () => { expect(getFetch()).toBe(globalThis.fetch); }); }); + + describe('OpenTelemetry trace context propagation', () => { + it('should pass through traceparent header when provided', async () => { + const traceparent = + '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'; + + await send({ + method: 'GET', + path: '/test', + body: undefined as never, + headers: { + traceparent, + }, + }); + + expect(fetchCalls.length).toBeGreaterThan(0); + const [, options] = fetchCalls[0]; + const headers = options?.headers as Record; + + expect(headers?.traceparent).toBe(traceparent); + }); + + it('should not inject traceparent header when there is no active context', async () => { + await context.with(ROOT_CONTEXT, async () => { + await send({ + method: 'GET', + path: '/test', + body: undefined as never, + }); + + expect(fetchCalls.length).toBeGreaterThan(0); + const [, options] = fetchCalls[0]; + const headers = options?.headers as Record; + + expect(headers?.traceparent).toBeUndefined(); + }); + }); + + it('should preserve existing trace headers passed in request', async () => { + const existingTraceparent = + '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'; + + await send({ + method: 'POST', + path: '/test', + body: JSON.stringify({ test: 'data' }), + headers: { + traceparent: existingTraceparent, + }, + }); + + expect(fetchCalls.length).toBeGreaterThan(0); + const [, options] = fetchCalls[0]; + const headers = options?.headers as Record; + + expect(headers?.traceparent).toBe(existingTraceparent); + }); + + it('should preserve custom headers when traceparent is included', async () => { + const traceparent = + '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'; + + await send({ + method: 'GET', + path: '/test', + body: undefined as never, + headers: { + traceparent, + 'X-Custom-Header': 'custom-value', + 'X-Another-Header': 'another-value', + }, + }); + + expect(fetchCalls.length).toBeGreaterThan(0); + const [, options] = fetchCalls[0]; + const headers = options?.headers as Record; + + expect(headers?.traceparent).toBe(traceparent); + expect(headers?.['X-Custom-Header']).toBe('custom-value'); + expect(headers?.['X-Another-Header']).toBe('another-value'); + expect(headers?.Authorization).toBe('Bearer test-api-key'); + }); + }); });