Skip to content

Commit 275a9a7

Browse files
committed
simplify promise handle
1 parent 0c5648e commit 275a9a7

File tree

11 files changed

+196
-243
lines changed

11 files changed

+196
-243
lines changed

dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-options.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
sendDefaultPii: false,
99
transport: loggingTransport,
10-
registerEsmLoaderHooks: false,
1110
integrations: [
1211
Sentry.googleGenAIIntegration({
1312
recordInputs: true,
1413
recordOutputs: true,
1514
}),
1615
],
16+
beforeSendTransaction: event => {
17+
// Filter out mock express server transactions
18+
if (event.transaction.includes('/v1beta/')) {
19+
return null;
20+
}
21+
return event;
22+
},
1723
});

dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-pii.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
sendDefaultPii: true,
99
transport: loggingTransport,
10-
registerEsmLoaderHooks: false,
1110
integrations: [Sentry.googleGenAIIntegration()],
11+
beforeSendTransaction: event => {
12+
// Filter out mock express server transactions
13+
if (event.transaction.includes('/v1beta/')) {
14+
return null;
15+
}
16+
return event;
17+
},
1218
});

dev-packages/node-integration-tests/suites/tracing/google-genai/instrument.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
sendDefaultPii: false,
99
transport: loggingTransport,
10-
registerEsmLoaderHooks: false,
1110
integrations: [Sentry.googleGenAIIntegration()],
11+
beforeSendTransaction: event => {
12+
// Filter out mock express server transactions
13+
if (event.transaction.includes('/v1beta')) {
14+
return null;
15+
}
16+
return event;
17+
},
1218
});
Lines changed: 45 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,59 @@
1-
import { instrumentGoogleGenAIClient } from '@sentry/core';
21
import * as Sentry from '@sentry/node';
2+
import { GoogleGenAI } from '@google/genai';
33

4-
class MockGoogleGenAI {
5-
constructor(config) {
6-
this.apiKey = config.apiKey;
4+
import express from 'express';
75

8-
this.models = {
9-
generateContent: async params => {
10-
// Simulate processing time
11-
await new Promise(resolve => setTimeout(resolve, 10));
6+
const PORT = 3333;
127

13-
if (params.model === 'error-model') {
14-
const error = new Error('Model not found');
15-
error.status = 404;
16-
throw error;
17-
}
8+
function startMockGoogleGenAIServer() {
9+
const app = express();
10+
app.use(express.json());
1811

19-
return {
20-
candidates: [
21-
{
22-
content: {
23-
parts: [
24-
{
25-
text: params.contents ? 'The capital of France is Paris.' : 'Mock response from Google GenAI!',
26-
},
27-
],
28-
role: 'model',
29-
},
30-
finishReason: 'stop',
31-
index: 0,
32-
},
33-
],
34-
usageMetadata: {
35-
promptTokenCount: 8,
36-
candidatesTokenCount: 12,
37-
totalTokenCount: 20,
38-
},
39-
};
40-
},
41-
};
12+
app.post('/v1beta/models/:model\\:generateContent', (req, res) => {
13+
const model = req.params.model;
4214

43-
this.chats = {
44-
create: options => {
45-
// Return a chat instance with sendMessage method and model info
46-
return {
47-
model: options?.model || 'unknown', // Include model from create options
48-
sendMessage: async () => {
49-
// Simulate processing time
50-
await new Promise(resolve => setTimeout(resolve, 10));
15+
if (model === 'error-model') {
16+
res.status(404).set('x-request-id', 'mock-request-123').end('Model not found');
17+
return;
18+
}
5119

52-
return {
53-
candidates: [
54-
{
55-
content: {
56-
parts: [
57-
{
58-
text: 'Mock response from Google GenAI!',
59-
},
60-
],
61-
role: 'model',
62-
},
63-
finishReason: 'stop',
64-
index: 0,
65-
},
66-
],
67-
usageMetadata: {
68-
promptTokenCount: 10,
69-
candidatesTokenCount: 15,
70-
totalTokenCount: 25,
20+
res.send({
21+
candidates: [
22+
{
23+
content: {
24+
parts: [
25+
{
26+
text: 'Mock response from Google GenAI!',
7127
},
72-
};
28+
],
29+
role: 'model',
7330
},
74-
};
31+
finishReason: 'stop',
32+
index: 0,
33+
},
34+
],
35+
usageMetadata: {
36+
promptTokenCount: 8,
37+
candidatesTokenCount: 12,
38+
totalTokenCount: 20,
7539
},
76-
};
77-
}
40+
});
41+
});
42+
43+
return app.listen(PORT);
7844
}
7945

8046
async function run() {
81-
const genAI = new MockGoogleGenAI({ apiKey: 'test-api-key' });
82-
const instrumentedClient = instrumentGoogleGenAIClient(genAI);
47+
const server = startMockGoogleGenAIServer();
48+
49+
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
50+
const client = new GoogleGenAI({
51+
apiKey: 'mock-api-key',
52+
httpOptions: { baseUrl: `http://localhost:${PORT}` }
53+
});
8354

84-
await Sentry.startSpan({ name: 'main', op: 'function' }, async () => {
8555
// Test 1: chats.create and sendMessage flow
86-
const chat = instrumentedClient.chats.create({
56+
const chat = client.chats.create({
8757
model: 'gemini-1.5-pro',
8858
config: {
8959
temperature: 0.8,
@@ -103,7 +73,7 @@ async function run() {
10373
});
10474

10575
// Test 2: models.generateContent
106-
await instrumentedClient.models.generateContent({
76+
await client.models.generateContent({
10777
model: 'gemini-1.5-flash',
10878
config: {
10979
temperature: 0.7,
@@ -120,7 +90,7 @@ async function run() {
12090

12191
// Test 3: Error handling
12292
try {
123-
await instrumentedClient.models.generateContent({
93+
await client.models.generateContent({
12494
model: 'error-model',
12595
contents: [
12696
{
@@ -133,6 +103,8 @@ async function run() {
133103
// Expected error
134104
}
135105
});
106+
107+
server.close();
136108
}
137109

138110
run();

dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ describe('Google GenAI integration', () => {
3434
'sentry.origin': 'auto.ai.google_genai',
3535
'gen_ai.system': 'google_genai',
3636
'gen_ai.request.model': 'gemini-1.5-pro', // Should get from chat context
37-
'gen_ai.usage.input_tokens': 10,
38-
'gen_ai.usage.output_tokens': 15,
39-
'gen_ai.usage.total_tokens': 25,
37+
'gen_ai.usage.input_tokens': 8,
38+
'gen_ai.usage.output_tokens': 12,
39+
'gen_ai.usage.total_tokens': 20,
4040
},
4141
description: 'chat gemini-1.5-pro',
4242
op: 'gen_ai.chat',
@@ -111,9 +111,9 @@ describe('Google GenAI integration', () => {
111111
'gen_ai.request.model': 'gemini-1.5-pro',
112112
'gen_ai.request.messages': expect.any(String), // Should include message when recordInputs: true
113113
'gen_ai.response.text': expect.any(String), // Should include response when recordOutputs: true
114-
'gen_ai.usage.input_tokens': 10,
115-
'gen_ai.usage.output_tokens': 15,
116-
'gen_ai.usage.total_tokens': 25,
114+
'gen_ai.usage.input_tokens': 8,
115+
'gen_ai.usage.output_tokens': 12,
116+
'gen_ai.usage.total_tokens': 20,
117117
}),
118118
description: 'chat gemini-1.5-pro',
119119
op: 'gen_ai.chat',

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export { basename, dirname, isAbsolute, join, normalizePath, relative, resolve }
215215
export { makePromiseBuffer, SENTRY_BUFFER_FULL_ERROR } from './utils/promisebuffer';
216216
export type { PromiseBuffer } from './utils/promisebuffer';
217217
export { severityLevelFromString } from './utils/severity';
218+
export { replaceExports } from './utils/exports';
218219
export {
219220
UNKNOWN_FUNCTION,
220221
createStackParser,

packages/core/src/utils/exports.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Replaces constructor functions in module exports, handling read-only properties,
3+
* and both default and named exports by wrapping them with the constructor.
4+
*
5+
* @param exports The module exports object to modify
6+
* @param exportName The name of the export to replace (e.g., 'GoogleGenAI', 'Anthropic', 'OpenAI')
7+
* @param wrappedConstructor The wrapped constructor function to replace the original with
8+
* @returns void
9+
*/
10+
export function replaceExports(
11+
exports: { [key: string]: unknown },
12+
exportName: string,
13+
wrappedConstructor: unknown,
14+
): void {
15+
const original = exports[exportName];
16+
17+
if (typeof original !== 'function') {
18+
return;
19+
}
20+
21+
// Replace the named export - handle read-only properties
22+
try {
23+
exports[exportName] = wrappedConstructor;
24+
} catch (error) {
25+
// If direct assignment fails, override the property descriptor
26+
Object.defineProperty(exports, exportName, {
27+
value: wrappedConstructor,
28+
writable: true,
29+
configurable: true,
30+
enumerable: true,
31+
});
32+
}
33+
34+
// Replace the default export if it points to the original constructor
35+
if (exports.default === original) {
36+
try {
37+
exports.default = wrappedConstructor;
38+
} catch (error) {
39+
Object.defineProperty(exports, 'default', {
40+
value: wrappedConstructor,
41+
writable: true,
42+
configurable: true,
43+
enumerable: true,
44+
});
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)