Skip to content

Commit 3adefaf

Browse files
committed
ref(node): Refactor orchestrion config into separate files
1 parent 68fe9e8 commit 3adefaf

13 files changed

Lines changed: 309 additions & 246 deletions

File tree

packages/server-utils/rollup.npm.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default [
1919
...orchestrionRuntimeHooks,
2020
...makeNPMConfigVariants(
2121
makeBaseNPMConfig({
22-
// `src/orchestrion/config.ts` and `src/orchestrion/bundler/vite.ts` are
22+
// `src/orchestrion/config/index.ts` and `src/orchestrion/bundler/vite.ts` are
2323
// loaded via dedicated subpath exports (`.../orchestrion/config`,
2424
// `.../orchestrion/vite`) — neither is reachable from `src/index.ts`, so we
2525
// list them as separate entrypoints to guarantee they end up in build/esm
@@ -28,7 +28,7 @@ export default [
2828
entrypoints: [
2929
'src/index.ts',
3030
'src/orchestrion/index.ts',
31-
'src/orchestrion/config.ts',
31+
'src/orchestrion/config/index.ts',
3232
// `src/orchestrion/runtime/register.ts` backs the `./orchestrion/register`
3333
// subpath export; the Node SDK `require`s it synchronously from
3434
// `Sentry.init()` to install the channel-injection hooks.
Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import { mysqlChannels } from './config/mysql';
2+
import { lruMemoizerChannels } from './config/lru-memoizer';
3+
import { ioredisChannels } from './config/ioredis';
4+
import { pgChannels } from './config/pg';
5+
import { openaiChannels } from './config/openai';
6+
import { anthropicAiChannels } from './config/anthropic-ai';
7+
import { vercelAiChannels } from './config/vercel-ai';
8+
19
/**
210
* Fully-qualified `diagnostics_channel` names that orchestrion publishes to.
311
*
@@ -12,33 +20,13 @@
1220
* they don't drift apart and silently stop firing.
1321
*/
1422
export const CHANNELS = {
15-
MYSQL_QUERY: 'orchestrion:mysql:query',
16-
LRU_MEMOIZER_LOAD: 'orchestrion:lru-memoizer:load',
17-
IOREDIS_COMMAND: 'orchestrion:ioredis:command',
18-
IOREDIS_CONNECT: 'orchestrion:ioredis:connect',
19-
PG_QUERY: 'orchestrion:pg:query',
20-
PG_CONNECT: 'orchestrion:pg:connect',
21-
PGPOOL_CONNECT: 'orchestrion:pg-pool:connect',
22-
OPENAI_CHAT: 'orchestrion:openai:chat',
23-
OPENAI_RESPONSES: 'orchestrion:openai:responses',
24-
OPENAI_EMBEDDINGS: 'orchestrion:openai:embeddings',
25-
OPENAI_CONVERSATIONS: 'orchestrion:openai:conversations',
26-
ANTHROPIC_CHAT: 'orchestrion:@anthropic-ai/sdk:chat',
27-
ANTHROPIC_MODELS: 'orchestrion:@anthropic-ai/sdk:models',
28-
ANTHROPIC_MESSAGES_STREAM: 'orchestrion:@anthropic-ai/sdk:messages-stream',
29-
// Vercel AI (`ai`) v6: orchestrion injects these so the same channel-based
30-
// integration that consumes `ai`'s native `ai:telemetry` channel (v7) can
31-
// also instrument v6. Each maps to a top-level function in `ai`'s bundle.
32-
VERCEL_AI_GENERATE_TEXT: 'orchestrion:ai:generateText',
33-
VERCEL_AI_STREAM_TEXT: 'orchestrion:ai:streamText',
34-
VERCEL_AI_EMBED: 'orchestrion:ai:embed',
35-
VERCEL_AI_EMBED_MANY: 'orchestrion:ai:embedMany',
36-
VERCEL_AI_EXECUTE_TOOL_CALL: 'orchestrion:ai:executeToolCall',
37-
// `resolveLanguageModel` is the single chokepoint every model call flows
38-
// through; we wrap it to monkey-patch `doGenerate`/`doStream` on the returned
39-
// model (the model-call site itself is an inline call with no injectable
40-
// definition).
41-
VERCEL_AI_RESOLVE_LANGUAGE_MODEL: 'orchestrion:ai:resolveLanguageModel',
23+
...mysqlChannels,
24+
...lruMemoizerChannels,
25+
...ioredisChannels,
26+
...pgChannels,
27+
...openaiChannels,
28+
...anthropicAiChannels,
29+
...vercelAiChannels,
4230
} as const;
4331

4432
export type ChannelName = (typeof CHANNELS)[keyof typeof CHANNELS];

packages/server-utils/src/orchestrion/config.ts

Lines changed: 0 additions & 215 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { InstrumentationConfig } from '@apm-js-collab/code-transformer';
2+
3+
export const anthropicAiConfig = [
4+
// One entry each for CJS/ESM
5+
...['resources/messages/messages.js', 'resources/messages/messages.mjs'].flatMap(filePath =>
6+
(['create', 'countTokens'] as const).map(methodName => ({
7+
channelName: 'chat',
8+
module: { name: '@anthropic-ai/sdk', versionRange: '>=0.19.2 <1', filePath },
9+
functionQuery: { className: 'Messages', methodName, kind: 'Auto' as const },
10+
})),
11+
),
12+
...['resources/completions.js', 'resources/completions.mjs'].map(filePath => ({
13+
channelName: 'chat',
14+
module: { name: '@anthropic-ai/sdk', versionRange: '>=0.19.2 <1', filePath },
15+
functionQuery: { className: 'Completions', methodName: 'create', kind: 'Auto' as const },
16+
})),
17+
...['resources/beta/messages/messages.js', 'resources/beta/messages/messages.mjs'].map(filePath => ({
18+
channelName: 'chat',
19+
module: { name: '@anthropic-ai/sdk', versionRange: '>=0.19.2 <1', filePath },
20+
functionQuery: { className: 'Messages', methodName: 'create', kind: 'Auto' as const },
21+
})),
22+
...['resources/models.js', 'resources/models.mjs'].map(filePath => ({
23+
channelName: 'models',
24+
module: { name: '@anthropic-ai/sdk', versionRange: '>=0.19.2 <1', filePath },
25+
functionQuery: { className: 'Models', methodName: 'retrieve', kind: 'Auto' as const },
26+
})),
27+
// `messages.stream()` returns a synchronous emitter, not a promise, so `kind: 'Sync'` is required:
28+
// `Auto`'s promise wrapper never publishes `end` for a non-thenable return, so the span would never end.
29+
...['resources/messages/messages.js', 'resources/messages/messages.mjs'].map(filePath => ({
30+
channelName: 'messages-stream',
31+
module: { name: '@anthropic-ai/sdk', versionRange: '>=0.19.2 <1', filePath },
32+
functionQuery: { className: 'Messages', methodName: 'stream', kind: 'Sync' as const },
33+
})),
34+
] satisfies InstrumentationConfig[];
35+
36+
export const anthropicAiChannels = {
37+
ANTHROPIC_CHAT: 'orchestrion:@anthropic-ai/sdk:chat',
38+
ANTHROPIC_MODELS: 'orchestrion:@anthropic-ai/sdk:models',
39+
ANTHROPIC_MESSAGES_STREAM: 'orchestrion:@anthropic-ai/sdk:messages-stream',
40+
} as const;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { InstrumentationConfig } from '@apm-js-collab/code-transformer';
2+
import { mysqlConfig } from './mysql';
3+
import { lruMemoizerConfig } from './lru-memoizer';
4+
import { ioredisConfig } from './ioredis';
5+
import { openaiConfig } from './openai';
6+
import { pgConfig } from './pg';
7+
import { anthropicAiConfig } from './anthropic-ai';
8+
import { vercelAiConfig } from './vercel-ai';
9+
10+
export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [
11+
...mysqlConfig,
12+
...lruMemoizerConfig,
13+
...ioredisConfig,
14+
...openaiConfig,
15+
...pgConfig,
16+
...anthropicAiConfig,
17+
...vercelAiConfig,
18+
];
19+
20+
/**
21+
* The unique set of package names instrumented by `SENTRY_INSTRUMENTATIONS`
22+
* (e.g. `['mysql']`).
23+
*
24+
* Bundler plugins MUST ensure these are actually bundled rather than
25+
* externalized: an externalized dependency is resolved from `node_modules` at
26+
* runtime and never passes through the code transform's `onLoad`, so its
27+
* diagnostics_channel calls are silently never injected.
28+
*/
29+
export const INSTRUMENTED_MODULE_NAMES: string[] = Array.from(new Set(SENTRY_INSTRUMENTATIONS.map(i => i.module.name)));
30+
31+
/**
32+
* Returns `external` with any instrumented packages removed, so a bundler that
33+
* uses an "external" denylist (esbuild, Bun, Rollup) still bundles — and thus
34+
* transforms — them. Matches an exact package name (`'mysql'`) or a subpath
35+
* (`'mysql/lib/...'`); wildcard/other patterns are left untouched. `undefined`
36+
* is returned unchanged.
37+
*
38+
* (Vite uses an `ssr.noExternal` allowlist instead, so it consumes
39+
* `INSTRUMENTED_MODULE_NAMES` directly rather than this helper.)
40+
*/
41+
export function withoutInstrumentedExternals(external: readonly string[] | undefined): string[] | undefined {
42+
if (!external) {
43+
return undefined;
44+
}
45+
return external.filter(
46+
entry => !INSTRUMENTED_MODULE_NAMES.some(name => entry === name || entry.startsWith(`${name}/`)),
47+
);
48+
}

0 commit comments

Comments
 (0)