Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Anthropic from '@anthropic-ai/sdk';
import * as Sentry from '@sentry/node';
import express from 'express';

function startMockAnthropicServer() {
const app = express();
app.use(express.json());

// Anthropic can hand back an error-shaped body on an otherwise successful (HTTP 200) response.
// The SDK resolves it as data, so the caller never sees a thrown error.
// @see https://docs.anthropic.com/en/api/errors#error-shapes
app.post('/anthropic/v1/messages', (_req, res) => {
res
.status(200)
.set('x-request-id', 'mock-response-error')
.json({ type: 'error', error: { type: 'overloaded_error', message: 'Overloaded' } });
});

return new Promise(resolve => {
const server = app.listen(0, () => resolve(server));
});
}

async function run() {
const server = await startMockAnthropicServer();

await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
const client = new Anthropic({
apiKey: 'mock-api-key',
baseURL: `http://localhost:${server.address().port}/anthropic`,
});

// Resolves with the error-shaped body; no try/catch because nothing is thrown.
await client.messages.create({
model: 'claude-3-haiku-20240307',
messages: [{ role: 'user', content: 'What is the capital of France?' }],
max_tokens: 100,
});
});

await Sentry.flush(2000);
server.close();
}

run();
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-with-response.mjs', 'instrument.mjs', (createRunner, test) => {
test('preserves .withResponse() and .asResponse() for non-streaming and streaming', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'main',
Expand Down Expand Up @@ -216,7 +215,6 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-stream.mjs', 'instrument.mjs', (createRunner, test) => {
test('streams produce spans with token usage and metadata (PII false)', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: EXPECTED_STREAM_SPANS_PII_FALSE })
.expect({
span: container => {
Expand Down Expand Up @@ -270,7 +268,6 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-stream.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('streams record response text when PII true', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: EXPECTED_STREAM_SPANS_PII_TRUE })
.expect({
span: container => {
Expand Down Expand Up @@ -321,7 +318,6 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-stream-nested-create.mjs', 'instrument.mjs', (createRunner, test) => {
test('traces a create() invoked from a stream event handler (dedup does not over-suppress)', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -352,7 +348,6 @@ describe('Anthropic integration', () => {
const EXPECTED_TOOL_CALLS_JSON =
'[{"type":"tool_use","id":"tool_weather_1","name":"weather","input":{"city":"Paris"}}]';
await createRunner()
.ignore('event')
.expect({
transaction: {},
})
Expand Down Expand Up @@ -382,7 +377,6 @@ describe('Anthropic integration', () => {
const EXPECTED_TOOL_CALLS_JSON =
'[{"type":"tool_use","id":"tool_weather_2","name":"weather","input":{"city":"Paris"}}]';
await createRunner()
.ignore('event')
.expect({
transaction: {},
})
Expand Down Expand Up @@ -423,6 +417,9 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-stream-errors.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('handles streaming errors correctly', async () => {
await createRunner()
// Stream errors surface via the MessageStream `error` event; attaching that listener stops it
// being raised as an unhandled rejection, so the instrumentation captures it. This test only
// asserts the spans.
.ignore('event')
.expect({ transaction: EXPECTED_STREAM_ERROR_SPANS })
.expect({
Expand Down Expand Up @@ -471,7 +468,6 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-errors.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('handles tool errors correctly', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -501,7 +497,6 @@ describe('Anthropic integration', () => {
test('extracts system instructions from messages', async () => {
const expectedInstructions = JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]);
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'main',
Expand All @@ -525,7 +520,6 @@ describe('Anthropic integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-span-streaming.mjs', (createRunner, test) => {
test('creates anthropic related spans with span streaming enabled', async () => {
await createRunner()
.ignore('event')
.expect({
span: container => {
const completionSpan = container.items.find(
Expand All @@ -548,4 +542,28 @@ describe('Anthropic integration', () => {
.completed();
});
});

createEsmAndCjsTests(__dirname, 'scenario-response-error.mjs', 'instrument.mjs', (createRunner, test) => {
test('captures error-shaped responses returned as data', async () => {
await createRunner()
// The API returns the error as data on a 200 response, never as a thrown error to the caller,
// so the instrumentation intentionally captures it as an event.
.unordered()
.expect({
event: {
exception: {
values: [
{
value: 'Overloaded',
mechanism: { type: 'auto.ai.anthropic.anthropic_error', handled: false },
},
],
},
},
})
.expect({ transaction: { transaction: 'main' } })
.start()
.completed();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates google genai related spans with genAI recording disabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -86,7 +85,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('creates google genai related spans with genAI recording enabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -137,7 +135,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
test('creates google genai related spans with custom options', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -174,7 +171,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-tools.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
test('creates google genai related spans with tool calls', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -233,7 +229,21 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-streaming.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates google genai streaming spans with genAI recording disabled', async () => {
await createRunner()
.ignore('event')
// The provider surfaces blocked content within the stream and never returns it to the caller as
// a thrown error, so the instrumentation intentionally captures it as an event.
.unordered()
.expect({
event: {
exception: {
values: [
{
value: 'Content blocked: The prompt was blocked due to safety concerns',
mechanism: { type: 'auto.ai.google_genai', handled: false },
},
],
},
},
})
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -289,7 +299,21 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-streaming.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('creates google genai streaming spans with genAI recording enabled', async () => {
await createRunner()
.ignore('event')
// The provider surfaces blocked content within the stream and never returns it to the caller as
// a thrown error, so the instrumentation intentionally captures it as an event.
.unordered()
.expect({
event: {
exception: {
values: [
{
value: 'Content blocked: The prompt was blocked due to safety concerns',
mechanism: { type: 'auto.ai.google_genai', handled: false },
},
],
},
},
})
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -348,7 +372,6 @@ describe('Google GenAI integration', () => {
(createRunner, test) => {
test('extracts system instructions from messages', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand All @@ -372,7 +395,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates google genai embeddings spans with genAI recording disabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -411,7 +433,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('creates google genai embeddings spans with genAI recording enabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -458,7 +479,6 @@ describe('Google GenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-span-streaming.mjs', (createRunner, test) => {
test('creates google genai related spans with span streaming enabled', async () => {
await createRunner()
.ignore('event')
.expect({
span: container => {
const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ describe('OpenAI Tool Calls integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates openai tool calls related spans with genAI recording disabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -326,7 +325,6 @@ describe('OpenAI Tool Calls integration', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('creates openai tool calls related spans with genAI recording enabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates openai related spans with genAI recording disabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -333,7 +332,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('creates openai related spans with genAI recording enabled', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -681,7 +679,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
test('creates openai related spans with custom options', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -726,7 +723,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates openai related spans with genAI recording disabled', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'main',
Expand Down Expand Up @@ -859,7 +855,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('creates openai related spans with genAI recording enabled', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'main',
Expand Down Expand Up @@ -1089,7 +1084,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-conversation.mjs', 'instrument.mjs', (createRunner, test) => {
test('captures conversation ID from Conversations API and previous_response_id', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'conversation-test',
Expand Down Expand Up @@ -1209,7 +1203,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-manual-conversation-id.mjs', 'instrument.mjs', (createRunner, test) => {
test('attaches manual conversation ID set via setConversationId() to all chat spans', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'chat-with-manual-conversation-id',
Expand Down Expand Up @@ -1242,7 +1235,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-separate-scope-1.mjs', 'instrument.mjs', (createRunner, test) => {
test('isolates conversation IDs across separate scopes - conversation 1', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'GET /chat/conversation-1',
Expand Down Expand Up @@ -1274,7 +1266,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-separate-scope-2.mjs', 'instrument.mjs', (createRunner, test) => {
test('isolates conversation IDs across separate scopes - conversation 2', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'GET /chat/conversation-2',
Expand Down Expand Up @@ -1310,7 +1301,6 @@ describe('OpenAI integration', () => {
(createRunner, test) => {
test('extracts system instructions from messages', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'main',
Expand All @@ -1337,7 +1327,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-with-response.mjs', 'instrument.mjs', (createRunner, test) => {
test('preserves .withResponse() method and works correctly', async () => {
await createRunner()
.ignore('event')
.expect({
transaction: {
transaction: 'main',
Expand Down Expand Up @@ -1368,7 +1357,6 @@ describe('OpenAI integration', () => {
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-span-streaming.mjs', (createRunner, test) => {
test('creates openai related spans with span streaming enabled', async () => {
await createRunner()
.ignore('event')
.expect({
span: container => {
const chatCompletionSpan = container.items.find(
Expand Down
Loading
Loading