@introspection-sdk/introspection-node 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/otel/init.js +1 -1
  3. package/dist/otel/init.js.map +1 -1
  4. package/dist/otel/integrations/anthropic.d.ts +1 -1
  5. package/dist/otel/integrations/anthropic.d.ts.map +1 -1
  6. package/dist/otel/integrations/anthropic.js +8 -4
  7. package/dist/otel/integrations/anthropic.js.map +1 -1
  8. package/dist/otel/integrations/base.d.ts +30 -1
  9. package/dist/otel/integrations/base.d.ts.map +1 -1
  10. package/dist/otel/integrations/base.js +50 -0
  11. package/dist/otel/integrations/base.js.map +1 -1
  12. package/dist/otel/integrations/claude-agent.d.ts +1 -2
  13. package/dist/otel/integrations/claude-agent.d.ts.map +1 -1
  14. package/dist/otel/integrations/claude-agent.js +4 -3
  15. package/dist/otel/integrations/claude-agent.js.map +1 -1
  16. package/dist/otel/integrations/gemini.d.ts +1 -1
  17. package/dist/otel/integrations/gemini.d.ts.map +1 -1
  18. package/dist/otel/integrations/gemini.js +4 -2
  19. package/dist/otel/integrations/gemini.js.map +1 -1
  20. package/dist/otel/integrations/index.d.ts +6 -12
  21. package/dist/otel/integrations/index.d.ts.map +1 -1
  22. package/dist/otel/integrations/index.js +20 -24
  23. package/dist/otel/integrations/index.js.map +1 -1
  24. package/dist/otel/integrations/langchain.d.ts +1 -2
  25. package/dist/otel/integrations/langchain.d.ts.map +1 -1
  26. package/dist/otel/integrations/langchain.js +6 -4
  27. package/dist/otel/integrations/langchain.js.map +1 -1
  28. package/dist/otel/integrations/mastra.d.ts +1 -1
  29. package/dist/otel/integrations/mastra.d.ts.map +1 -1
  30. package/dist/otel/integrations/mastra.js +6 -2
  31. package/dist/otel/integrations/mastra.js.map +1 -1
  32. package/dist/otel/integrations/openai-agents.d.ts +1 -1
  33. package/dist/otel/integrations/openai-agents.d.ts.map +1 -1
  34. package/dist/otel/integrations/openai-agents.js +7 -2
  35. package/dist/otel/integrations/openai-agents.js.map +1 -1
  36. package/dist/otel/integrations/pi.d.ts +1 -2
  37. package/dist/otel/integrations/pi.d.ts.map +1 -1
  38. package/dist/otel/integrations/pi.js +4 -3
  39. package/dist/otel/integrations/pi.js.map +1 -1
  40. package/dist/otel/integrations/vercel.d.ts +1 -2
  41. package/dist/otel/integrations/vercel.d.ts.map +1 -1
  42. package/dist/otel/integrations/vercel.js +2 -2
  43. package/dist/otel/integrations/vercel.js.map +1 -1
  44. package/package.json +16 -16
  45. package/src/otel/init.ts +1 -1
  46. package/src/otel/integrations/anthropic.ts +16 -6
  47. package/src/otel/integrations/base.ts +64 -1
  48. package/src/otel/integrations/claude-agent.ts +9 -5
  49. package/src/otel/integrations/gemini.ts +9 -4
  50. package/src/otel/integrations/index.ts +25 -24
  51. package/src/otel/integrations/langchain.ts +20 -5
  52. package/src/otel/integrations/mastra.ts +21 -3
  53. package/src/otel/integrations/openai-agents.ts +20 -4
  54. package/src/otel/integrations/pi.ts +9 -5
  55. package/src/otel/integrations/vercel.ts +6 -4
@@ -5,8 +5,7 @@
5
5
  * builds the instrumentor — bound to the shared provider's global tracer — and
6
6
  * publishes it via `introspection.instrumentPi(agent, meta)`.
7
7
  */
8
- import "@earendil-works/pi-agent-core";
9
- import type { Integration } from "./base.js";
8
+ import { type Integration } from "./base.js";
10
9
  declare const integration: Integration;
11
10
  export default integration;
12
11
  //# sourceMappingURL=pi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/otel/integrations/pi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,QAAA,MAAM,WAAW,EAAE,WAOlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/otel/integrations/pi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AAEnB,QAAA,MAAM,WAAW,EAAE,WASlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -5,12 +5,13 @@
5
5
  * builds the instrumentor — bound to the shared provider's global tracer — and
6
6
  * publishes it via `introspection.instrumentPi(agent, meta)`.
7
7
  */
8
- // Presence gate.
9
- import "@earendil-works/pi-agent-core";
10
8
  import { IntrospectionPiInstrumentor } from "../pi.js";
9
+ import { importOptionalPeer, isOptionalPeerInstalled, OPTIONAL_PEERS, } from "./base.js";
11
10
  const integration = {
12
11
  identifier: "pi",
13
- setupOnce({ handles }) {
12
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.piAgentCore),
13
+ async setupOnce({ handles }) {
14
+ await importOptionalPeer(OPTIONAL_PEERS.piAgentCore);
14
15
  // The instrumentor emits onto the global tracer, which `init()` has just
15
16
  // registered with the shared IntrospectionSpanProcessor.
16
17
  handles.piInstrumentor = new IntrospectionPiInstrumentor();
@@ -1 +1 @@
1
- {"version":3,"file":"pi.js","sourceRoot":"","sources":["../../../src/otel/integrations/pi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,iBAAiB;AACjB,OAAO,+BAA+B,CAAC;AAEvC,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAGvD,MAAM,WAAW,GAAgB;IAC/B,UAAU,EAAE,IAAI;IAChB,SAAS,CAAC,EAAE,OAAO,EAAE;QACnB,yEAAyE;QACzE,yDAAyD;QACzD,OAAO,CAAC,cAAc,GAAG,IAAI,2BAA2B,EAAE,CAAC;IAC7D,CAAC;CACF,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"pi.js","sourceRoot":"","sources":["../../../src/otel/integrations/pi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,GAEf,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,GAAgB;IAC/B,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,cAAc,CAAC,WAAW,CAAC;IACtE,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE;QACzB,MAAM,kBAAkB,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACrD,yEAAyE;QACzE,yDAAyD;QACzD,OAAO,CAAC,cAAc,GAAG,IAAI,2BAA2B,EAAE,CAAC;IAC7D,CAAC;CACF,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -8,8 +8,7 @@
8
8
  * This integration exists so auto-discovery reports the AI SDK as detected and
9
9
  * (via its top-level import) is skipped when `ai` is not installed.
10
10
  */
11
- import "ai";
12
- import type { Integration } from "./base.js";
11
+ import { type Integration } from "./base.js";
13
12
  declare const integration: Integration;
14
13
  export default integration;
15
14
  //# sourceMappingURL=vercel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../../src/otel/integrations/vercel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,IAAI,CAAC;AAEZ,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,QAAA,MAAM,WAAW,EAAE,WAKlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../../src/otel/integrations/vercel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AAEnB,QAAA,MAAM,WAAW,EAAE,WAMlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -8,10 +8,10 @@
8
8
  * This integration exists so auto-discovery reports the AI SDK as detected and
9
9
  * (via its top-level import) is skipped when `ai` is not installed.
10
10
  */
11
- // Presence gate: a missing `ai` package makes this module's import reject.
12
- import "ai";
11
+ import { isOptionalPeerInstalled, OPTIONAL_PEERS, } from "./base.js";
13
12
  const integration = {
14
13
  identifier: "vercel",
14
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.vercelAi),
15
15
  setupOnce() {
16
16
  // No-op: the shared IntrospectionSpanProcessor converts AI SDK spans.
17
17
  },
@@ -1 +1 @@
1
- {"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../../src/otel/integrations/vercel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2EAA2E;AAC3E,OAAO,IAAI,CAAC;AAIZ,MAAM,WAAW,GAAgB;IAC/B,UAAU,EAAE,QAAQ;IACpB,SAAS;QACP,sEAAsE;IACxE,CAAC;CACF,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../../src/otel/integrations/vercel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,uBAAuB,EACvB,cAAc,GAEf,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,GAAgB;IAC/B,UAAU,EAAE,QAAQ;IACpB,WAAW,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,cAAc,CAAC,QAAQ,CAAC;IACnE,SAAS;QACP,sEAAsE;IACxE,CAAC;CACF,CAAC;AAEF,eAAe,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-sdk/introspection-node",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Introspection observability SDK for Node.js",
6
6
  "engines": {
@@ -43,21 +43,21 @@
43
43
  "@opentelemetry/sdk-trace-base": "^2.7.1",
44
44
  "@opentelemetry/sdk-trace-node": "^2.7.1",
45
45
  "@opentelemetry/semantic-conventions": "^1.41.1",
46
- "@types/node": "^22.19.19",
46
+ "@types/node": "^22.19.20",
47
47
  "eslint": "^9.39.4",
48
48
  "@langchain/core": "^1.1.48",
49
- "@mastra/core": "^1.37.1",
50
- "@mastra/observability": "^1.14.0",
51
- "@earendil-works/pi-agent-core": "^0.75.4",
52
- "@earendil-works/pi-ai": "^0.75.4",
53
- "@anthropic-ai/sdk": "^0.100.1",
54
- "@anthropic-ai/claude-agent-sdk": "^0.3.159",
55
- "@google/genai": "^2.6.0",
56
- "ai": "^6.0.194",
57
- "openai": "^6.40.0",
49
+ "@mastra/core": "^1.41.0",
50
+ "@mastra/observability": "^1.14.1",
51
+ "@earendil-works/pi-agent-core": "^0.79.0",
52
+ "@earendil-works/pi-ai": "^0.79.0",
53
+ "@anthropic-ai/sdk": "^0.102.0",
54
+ "@anthropic-ai/claude-agent-sdk": "^0.3.168",
55
+ "@google/genai": "^2.8.0",
56
+ "ai": "^6.0.197",
57
+ "openai": "^6.42.0",
58
58
  "typescript": "5.9.3",
59
- "@repo/typescript-config": "0.0.0",
60
- "@repo/eslint-config": "0.0.0"
59
+ "@repo/eslint-config": "0.0.0",
60
+ "@repo/typescript-config": "0.0.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@openai/agents": ">=0.0.1",
@@ -138,9 +138,9 @@
138
138
  "dependencies": {
139
139
  "@opentelemetry/api": "^1.9.1",
140
140
  "https-proxy-agent": "^7.0.6",
141
- "@introspection-sdk/introspection-pi": "0.4.7",
142
- "@introspection-sdk/introspection-proxy": "0.4.7",
143
- "@introspection-sdk/types": "0.4.7"
141
+ "@introspection-sdk/types": "0.4.9",
142
+ "@introspection-sdk/introspection-pi": "0.4.9",
143
+ "@introspection-sdk/introspection-proxy": "0.4.9"
144
144
  },
145
145
  "scripts": {
146
146
  "lint": "eslint . --max-warnings 0",
package/src/otel/init.ts CHANGED
@@ -184,7 +184,7 @@ export async function init(options: InitOptions = {}): Promise<TracerProvider> {
184
184
  toInstall.push(...(await discoverIntegrations()));
185
185
  }
186
186
  if (options.integrations) toInstall.push(...options.integrations);
187
- setupIntegrations(toInstall, ctx);
187
+ await setupIntegrations(toInstall, ctx);
188
188
  } catch (e) {
189
189
  state.handles = {};
190
190
  throw e;
@@ -5,16 +5,26 @@
5
5
  * client is traced against the shared provider — no per-client wiring.
6
6
  */
7
7
 
8
- // Top-level import gates availability: if `@anthropic-ai/sdk` is not installed
9
- // the dynamic import of this module rejects and discovery skips the shim.
10
- import Anthropic from "@anthropic-ai/sdk";
11
-
12
8
  import { AnthropicInstrumentor } from "../anthropic.js";
13
- import type { Integration } from "./base.js";
9
+ import {
10
+ importOptionalPeer,
11
+ isOptionalPeerInstalled,
12
+ OPTIONAL_PEERS,
13
+ type Integration,
14
+ } from "./base.js";
15
+
16
+ async function loadAnthropicSdk(): Promise<unknown> {
17
+ const mod = await importOptionalPeer<{ default?: unknown }>(
18
+ OPTIONAL_PEERS.anthropic,
19
+ );
20
+ return mod.default ?? mod;
21
+ }
14
22
 
15
23
  const integration: Integration = {
16
24
  identifier: "anthropic",
17
- setupOnce({ tracerProvider }) {
25
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.anthropic),
26
+ async setupOnce({ tracerProvider }) {
27
+ const Anthropic = await loadAnthropicSdk();
18
28
  const instrumentor = new AnthropicInstrumentor();
19
29
  instrumentor.instrumentClass({ anthropic: Anthropic, tracerProvider });
20
30
  // Restore the original prototype on shutdown so a later init() re-patches
@@ -15,6 +15,17 @@ import type { InstrumentedClaudeAgentSDK } from "../claude-wrapper.js";
15
15
  import type { IntrospectionCallbackHandler } from "../langchain-handler.js";
16
16
  import type { IntrospectionPiInstrumentor } from "../pi.js";
17
17
 
18
+ export const OPTIONAL_PEERS = {
19
+ anthropic: "@anthropic-ai/sdk",
20
+ claudeAgent: "@anthropic-ai/claude-agent-sdk",
21
+ gemini: "@google/genai",
22
+ langchainCallbacks: "@langchain/core/callbacks/base",
23
+ mastraObservability: "@mastra/observability",
24
+ openaiAgents: "@openai/agents",
25
+ piAgentCore: "@earendil-works/pi-agent-core",
26
+ vercelAi: "ai",
27
+ } as const;
28
+
18
29
  /**
19
30
  * Bound framework handles published by instance/config-based integrations.
20
31
  *
@@ -50,6 +61,50 @@ export class DidNotEnable extends Error {
50
61
  }
51
62
  }
52
63
 
64
+ /**
65
+ * Distinguish "framework package not installed" (expected — skip quietly) from
66
+ * a real error inside an installed integration (a bug we shouldn't hide).
67
+ */
68
+ export function isModuleNotFound(e: unknown): boolean {
69
+ const code = (e as { code?: string } | null)?.code;
70
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
71
+ return true;
72
+ }
73
+ const msg = e instanceof Error ? e.message : String(e);
74
+ return /Cannot find (module|package)|Failed to resolve/i.test(msg);
75
+ }
76
+
77
+ /**
78
+ * Import an optional peer without leaving a static import edge for bundlers.
79
+ * Keep the `import()` argument variable-based: built-in framework integrations
80
+ * use this so importing `@introspection-sdk/introspection-node/otel` does not
81
+ * require every optional framework SDK to be installed.
82
+ */
83
+ export async function importOptionalPeer<T = unknown>(
84
+ specifier: string,
85
+ ): Promise<T> {
86
+ try {
87
+ return (await import(specifier)) as T;
88
+ } catch (e) {
89
+ if (isModuleNotFound(e)) {
90
+ throw new DidNotEnable(`Optional peer ${specifier} is not installed`);
91
+ }
92
+ throw e;
93
+ }
94
+ }
95
+
96
+ export async function isOptionalPeerInstalled(
97
+ specifier: (typeof OPTIONAL_PEERS)[keyof typeof OPTIONAL_PEERS],
98
+ ): Promise<boolean> {
99
+ try {
100
+ await importOptionalPeer(specifier);
101
+ return true;
102
+ } catch (e) {
103
+ if (e instanceof DidNotEnable) return false;
104
+ throw e;
105
+ }
106
+ }
107
+
53
108
  /**
54
109
  * Everything an integration needs to wire itself into the shared pipeline.
55
110
  *
@@ -85,11 +140,19 @@ export interface Integration {
85
140
  * wrapping framework (e.g. LangChain) does not double-trace the SDK it wraps.
86
141
  */
87
142
  readonly deactivates?: readonly string[];
143
+ /**
144
+ * Optional availability probe for built-in integrations backed by optional
145
+ * peer packages. Missing peers should return false so their `deactivates`
146
+ * rules do not affect installed integrations.
147
+ */
148
+ isAvailable?(): boolean | Promise<boolean>;
88
149
  /**
89
150
  * Wire the framework into the shared pipeline. Runs once; may throw
90
151
  * {@link DidNotEnable}. May return a teardown callback (e.g. to uninstrument a
91
152
  * prototype patch) that `introspection.shutdown()` runs so a later `init()`
92
153
  * re-installs cleanly against the rebuilt provider.
93
154
  */
94
- setupOnce(ctx: IntegrationSetupContext): void | (() => void);
155
+ setupOnce(
156
+ ctx: IntegrationSetupContext,
157
+ ): void | (() => void) | Promise<void | (() => void)>;
95
158
  }
@@ -7,15 +7,19 @@
7
7
  * `init()` re-exposes as `introspection.instrumentClaudeAgent(sdk)`.
8
8
  */
9
9
 
10
- // Presence gate.
11
- import "@anthropic-ai/claude-agent-sdk";
12
-
13
10
  import { withIntrospection } from "../claude-wrapper.js";
14
- import type { Integration } from "./base.js";
11
+ import {
12
+ importOptionalPeer,
13
+ isOptionalPeerInstalled,
14
+ OPTIONAL_PEERS,
15
+ type Integration,
16
+ } from "./base.js";
15
17
 
16
18
  const integration: Integration = {
17
19
  identifier: "claude_agent",
18
- setupOnce({ token, serviceName, baseUrl, advanced, handles }) {
20
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.claudeAgent),
21
+ async setupOnce({ token, serviceName, baseUrl, advanced, handles }) {
22
+ await importOptionalPeer(OPTIONAL_PEERS.claudeAgent);
19
23
  handles.instrumentClaudeAgent = (sdk) =>
20
24
  withIntrospection(sdk, {
21
25
  token,
@@ -6,14 +6,19 @@
6
6
  * capture the standalone `GeminiInstrumentor` is built for.
7
7
  */
8
8
 
9
- import * as genai from "@google/genai";
10
-
11
9
  import { GeminiInstrumentor } from "../gemini.js";
12
- import type { Integration } from "./base.js";
10
+ import {
11
+ importOptionalPeer,
12
+ isOptionalPeerInstalled,
13
+ OPTIONAL_PEERS,
14
+ type Integration,
15
+ } from "./base.js";
13
16
 
14
17
  const integration: Integration = {
15
18
  identifier: "gemini",
16
- setupOnce({ tracerProvider }) {
19
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.gemini),
20
+ async setupOnce({ tracerProvider }) {
21
+ const genai = await importOptionalPeer(OPTIONAL_PEERS.gemini);
17
22
  const instrumentor = new GeminiInstrumentor();
18
23
  instrumentor.instrumentClass({ genai, tracerProvider });
19
24
  // Restore the original prototype on shutdown so a later init() re-patches
@@ -9,19 +9,19 @@
9
9
  import { logger } from "../../utils.js";
10
10
  import {
11
11
  DidNotEnable,
12
+ isModuleNotFound,
12
13
  type Integration,
13
14
  type IntegrationHandles,
14
15
  type IntegrationSetupContext,
15
16
  } from "./base.js";
16
17
 
17
- export { DidNotEnable };
18
+ export { DidNotEnable, isModuleNotFound };
18
19
  export type { Integration, IntegrationHandles, IntegrationSetupContext };
19
20
 
20
21
  /**
21
- * Built-in integrations, loaded lazily. Each thunk dynamically imports an
22
- * integration module; that module imports its framework at the top level, so
23
- * if the framework is not installed the import rejects and the integration is
24
- * skipped. Order matters only for `deactivates` resolution (handled below).
22
+ * Built-in integrations, loaded lazily. Each integration probes its optional
23
+ * framework package at runtime, so importing the OTel barrel never requires
24
+ * every optional framework SDK to be installed.
25
25
  */
26
26
  const BUILTIN_INTEGRATIONS: ReadonlyArray<
27
27
  () => Promise<{ default: Integration }>
@@ -41,26 +41,27 @@ const installed = new Set<string>();
41
41
  // prototype patch), run by teardownIntegrations() on shutdown.
42
42
  const teardowns: Array<() => void> = [];
43
43
 
44
- /**
45
- * Distinguish "framework package not installed" (expected — skip quietly) from
46
- * a real error inside an installed integration (a bug we shouldn't hide).
47
- */
48
- export function isModuleNotFound(e: unknown): boolean {
49
- const code = (e as { code?: string } | null)?.code;
50
- if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
51
- return true;
44
+ async function availableIntegrations(
45
+ integrations: Integration[],
46
+ ): Promise<Integration[]> {
47
+ const available: Integration[] = [];
48
+
49
+ for (const integration of integrations) {
50
+ if (integration.isAvailable && !(await integration.isAvailable())) {
51
+ continue;
52
+ }
53
+ available.push(integration);
52
54
  }
53
- const msg = e instanceof Error ? e.message : String(e);
54
- return /Cannot find (module|package)|Failed to resolve/i.test(msg);
55
+
56
+ return available;
55
57
  }
56
58
 
57
59
  /**
58
60
  * Return the built-in integrations whose framework is importable.
59
61
  *
60
- * Each module's top-level framework import determines availability: a missing
61
- * package makes the dynamic import reject, which we log at debug and skip. Any
62
- * OTHER failure (a real error inside an installed integration) is surfaced at
63
- * warn level rather than silently swallowed — it's a bug, not an absent peer.
62
+ * Missing optional peer packages are expected and skipped quietly. Any OTHER
63
+ * failure is surfaced at warn level rather than silently swallowed — it's a
64
+ * bug, not an absent peer.
64
65
  */
65
66
  export async function discoverIntegrations(): Promise<Integration[]> {
66
67
  const found: Integration[] = [];
@@ -80,7 +81,7 @@ export async function discoverIntegrations(): Promise<Integration[]> {
80
81
  }
81
82
  }
82
83
  }
83
- return found;
84
+ return availableIntegrations(found);
84
85
  }
85
86
 
86
87
  /**
@@ -88,13 +89,13 @@ export async function discoverIntegrations(): Promise<Integration[]> {
88
89
  *
89
90
  * @returns the set of identifiers installed so far this process.
90
91
  */
91
- export function setupIntegrations(
92
+ export async function setupIntegrations(
92
93
  integrations: Integration[],
93
94
  ctx: IntegrationSetupContext,
94
- ): Set<string> {
95
+ ): Promise<Set<string>> {
95
96
  // First-wins de-dupe by identifier.
96
97
  const byId = new Map<string, Integration>();
97
- for (const integration of integrations) {
98
+ for (const integration of await availableIntegrations(integrations)) {
98
99
  if (!byId.has(integration.identifier)) {
99
100
  byId.set(integration.identifier, integration);
100
101
  }
@@ -114,7 +115,7 @@ export function setupIntegrations(
114
115
  }
115
116
  if (installed.has(identifier)) continue;
116
117
  try {
117
- const teardown = integration.setupOnce(ctx);
118
+ const teardown = await integration.setupOnce(ctx);
118
119
  if (typeof teardown === "function") teardowns.push(teardown);
119
120
  installed.add(identifier);
120
121
  } catch (e) {
@@ -10,14 +10,25 @@
10
10
  * patch would double-trace it.
11
11
  */
12
12
 
13
- // Presence gate.
14
- import "@langchain/core/callbacks/base";
13
+ import {
14
+ importOptionalPeer,
15
+ isOptionalPeerInstalled,
16
+ OPTIONAL_PEERS,
17
+ type Integration,
18
+ } from "./base.js";
19
+ import type { IntrospectionCallbackHandler } from "../langchain-handler.js";
15
20
 
16
- import { IntrospectionCallbackHandler } from "../langchain-handler.js";
17
- import type { Integration } from "./base.js";
21
+ const importLocal = (specifier: string) =>
22
+ import(specifier) as Promise<{
23
+ IntrospectionCallbackHandler: new (
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ options: any,
26
+ ) => IntrospectionCallbackHandler;
27
+ }>;
18
28
 
19
29
  const integration: Integration = {
20
30
  identifier: "langchain",
31
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.langchainCallbacks),
21
32
  // Intentional, and matches the Python SDK: when @langchain/core is present,
22
33
  // auto-discovery skips the always-on raw-Anthropic prototype patch so a
23
34
  // LangChain → ChatAnthropic call isn't traced twice (once by LangChain's
@@ -29,7 +40,11 @@ const integration: Integration = {
29
40
  // `new AnthropicInstrumentor().instrumentClass(...)`, or call
30
41
  // `init({ autoDiscover: false, integrations: [anthropicIntegration] })`.
31
42
  deactivates: ["anthropic"],
32
- setupOnce({ token, serviceName, baseUrl, advanced, handles }) {
43
+ async setupOnce({ token, serviceName, baseUrl, advanced, handles }) {
44
+ await importOptionalPeer(OPTIONAL_PEERS.langchainCallbacks);
45
+ const { IntrospectionCallbackHandler } = await importLocal(
46
+ "../langchain-handler.js",
47
+ );
33
48
  handles.langchainHandler = new IntrospectionCallbackHandler({
34
49
  token,
35
50
  serviceName,
@@ -9,12 +9,30 @@
9
9
  * is the presence gate: a missing Mastra makes discovery skip this shim.
10
10
  */
11
11
 
12
- import { IntrospectionMastraExporter } from "../mastra-exporter.js";
13
- import type { Integration } from "./base.js";
12
+ import {
13
+ importOptionalPeer,
14
+ isOptionalPeerInstalled,
15
+ OPTIONAL_PEERS,
16
+ type Integration,
17
+ } from "./base.js";
18
+
19
+ const importLocal = (specifier: string) =>
20
+ import(specifier) as Promise<{
21
+ IntrospectionMastraExporter: new (
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ options: any,
24
+ ) => unknown;
25
+ }>;
14
26
 
15
27
  const integration: Integration = {
16
28
  identifier: "mastra",
17
- setupOnce({ token, baseUrl, advanced, handles }) {
29
+ isAvailable: () =>
30
+ isOptionalPeerInstalled(OPTIONAL_PEERS.mastraObservability),
31
+ async setupOnce({ token, baseUrl, advanced, handles }) {
32
+ await importOptionalPeer(OPTIONAL_PEERS.mastraObservability);
33
+ const { IntrospectionMastraExporter } = await importLocal(
34
+ "../mastra-exporter.js",
35
+ );
18
36
  handles.mastraExporter = new IntrospectionMastraExporter({
19
37
  token,
20
38
  baseUrl,
@@ -6,14 +6,30 @@
6
6
  * other integrations (e.g. a vendor exporter) already registered.
7
7
  */
8
8
 
9
- import { addTraceProcessor } from "@openai/agents";
10
-
11
9
  import { IntrospectionTracingProcessor } from "../tracing-processor.js";
12
- import type { Integration } from "./base.js";
10
+ import {
11
+ importOptionalPeer,
12
+ isOptionalPeerInstalled,
13
+ OPTIONAL_PEERS,
14
+ type Integration,
15
+ } from "./base.js";
16
+
17
+ interface OpenAIAgentsModule {
18
+ addTraceProcessor?: (processor: IntrospectionTracingProcessor) => void;
19
+ }
13
20
 
14
21
  const integration: Integration = {
15
22
  identifier: "openai_agents",
16
- setupOnce({ token, serviceName, baseUrl, advanced }) {
23
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.openaiAgents),
24
+ async setupOnce({ token, serviceName, baseUrl, advanced }) {
25
+ const { addTraceProcessor } = await importOptionalPeer<OpenAIAgentsModule>(
26
+ OPTIONAL_PEERS.openaiAgents,
27
+ );
28
+ if (!addTraceProcessor) {
29
+ throw new Error(
30
+ "Invalid @openai/agents module: addTraceProcessor missing",
31
+ );
32
+ }
17
33
  addTraceProcessor(
18
34
  new IntrospectionTracingProcessor({
19
35
  token,
@@ -6,15 +6,19 @@
6
6
  * publishes it via `introspection.instrumentPi(agent, meta)`.
7
7
  */
8
8
 
9
- // Presence gate.
10
- import "@earendil-works/pi-agent-core";
11
-
12
9
  import { IntrospectionPiInstrumentor } from "../pi.js";
13
- import type { Integration } from "./base.js";
10
+ import {
11
+ importOptionalPeer,
12
+ isOptionalPeerInstalled,
13
+ OPTIONAL_PEERS,
14
+ type Integration,
15
+ } from "./base.js";
14
16
 
15
17
  const integration: Integration = {
16
18
  identifier: "pi",
17
- setupOnce({ handles }) {
19
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.piAgentCore),
20
+ async setupOnce({ handles }) {
21
+ await importOptionalPeer(OPTIONAL_PEERS.piAgentCore);
18
22
  // The instrumentor emits onto the global tracer, which `init()` has just
19
23
  // registered with the shared IntrospectionSpanProcessor.
20
24
  handles.piInstrumentor = new IntrospectionPiInstrumentor();
@@ -9,13 +9,15 @@
9
9
  * (via its top-level import) is skipped when `ai` is not installed.
10
10
  */
11
11
 
12
- // Presence gate: a missing `ai` package makes this module's import reject.
13
- import "ai";
14
-
15
- import type { Integration } from "./base.js";
12
+ import {
13
+ isOptionalPeerInstalled,
14
+ OPTIONAL_PEERS,
15
+ type Integration,
16
+ } from "./base.js";
16
17
 
17
18
  const integration: Integration = {
18
19
  identifier: "vercel",
20
+ isAvailable: () => isOptionalPeerInstalled(OPTIONAL_PEERS.vercelAi),
19
21
  setupOnce() {
20
22
  // No-op: the shared IntrospectionSpanProcessor converts AI SDK spans.
21
23
  },