@cephalization/phoenix-insight 0.1.0

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 (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +620 -0
  3. package/dist/agent/index.js +230 -0
  4. package/dist/cli.js +640 -0
  5. package/dist/commands/index.js +2 -0
  6. package/dist/commands/px-fetch-more-spans.js +98 -0
  7. package/dist/commands/px-fetch-more-trace.js +110 -0
  8. package/dist/config/index.js +165 -0
  9. package/dist/config/loader.js +141 -0
  10. package/dist/config/schema.js +53 -0
  11. package/dist/index.js +1 -0
  12. package/dist/modes/index.js +17 -0
  13. package/dist/modes/local.js +134 -0
  14. package/dist/modes/sandbox.js +121 -0
  15. package/dist/modes/types.js +1 -0
  16. package/dist/observability/index.js +65 -0
  17. package/dist/progress.js +209 -0
  18. package/dist/prompts/index.js +1 -0
  19. package/dist/prompts/system.js +30 -0
  20. package/dist/snapshot/client.js +74 -0
  21. package/dist/snapshot/context.js +332 -0
  22. package/dist/snapshot/datasets.js +68 -0
  23. package/dist/snapshot/experiments.js +135 -0
  24. package/dist/snapshot/index.js +262 -0
  25. package/dist/snapshot/projects.js +44 -0
  26. package/dist/snapshot/prompts.js +199 -0
  27. package/dist/snapshot/spans.js +80 -0
  28. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  29. package/package.json +75 -0
  30. package/src/agent/index.ts +323 -0
  31. package/src/cli.ts +782 -0
  32. package/src/commands/index.ts +8 -0
  33. package/src/commands/px-fetch-more-spans.ts +174 -0
  34. package/src/commands/px-fetch-more-trace.ts +183 -0
  35. package/src/config/index.ts +225 -0
  36. package/src/config/loader.ts +173 -0
  37. package/src/config/schema.ts +66 -0
  38. package/src/index.ts +1 -0
  39. package/src/modes/index.ts +21 -0
  40. package/src/modes/local.ts +163 -0
  41. package/src/modes/sandbox.ts +144 -0
  42. package/src/modes/types.ts +31 -0
  43. package/src/observability/index.ts +90 -0
  44. package/src/progress.ts +239 -0
  45. package/src/prompts/index.ts +1 -0
  46. package/src/prompts/system.ts +31 -0
  47. package/src/snapshot/client.ts +129 -0
  48. package/src/snapshot/context.ts +462 -0
  49. package/src/snapshot/datasets.ts +132 -0
  50. package/src/snapshot/experiments.ts +246 -0
  51. package/src/snapshot/index.ts +403 -0
  52. package/src/snapshot/projects.ts +58 -0
  53. package/src/snapshot/prompts.ts +267 -0
  54. package/src/snapshot/spans.ts +142 -0
@@ -0,0 +1,173 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import { configSchema, getDefaultConfig, type Config } from "./schema.js";
5
+
6
+ /**
7
+ * Default config directory and file path
8
+ */
9
+ const DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".phoenix-insight");
10
+ const DEFAULT_CONFIG_FILE = path.join(DEFAULT_CONFIG_DIR, "config.json");
11
+
12
+ /**
13
+ * Module-level storage for CLI args passed from commander
14
+ * This is set externally before getConfigPath is called
15
+ */
16
+ let cliConfigPath: string | undefined;
17
+
18
+ /**
19
+ * Set the CLI config path (called from CLI parsing)
20
+ */
21
+ export function setCliConfigPath(configPath: string | undefined): void {
22
+ cliConfigPath = configPath;
23
+ }
24
+
25
+ /**
26
+ * Get the config file path based on priority:
27
+ * 1. CLI argument (--config)
28
+ * 2. Environment variable (PHOENIX_INSIGHT_CONFIG)
29
+ * 3. Default path (~/.phoenix-insight/config.json)
30
+ *
31
+ * @returns The path to the config file and whether it's the default path
32
+ */
33
+ export function getConfigPath(): { path: string; isDefault: boolean } {
34
+ // Priority 1: CLI argument
35
+ if (cliConfigPath) {
36
+ return { path: cliConfigPath, isDefault: false };
37
+ }
38
+
39
+ // Priority 2: Environment variable
40
+ const envConfigPath = process.env.PHOENIX_INSIGHT_CONFIG;
41
+ if (envConfigPath) {
42
+ return { path: envConfigPath, isDefault: false };
43
+ }
44
+
45
+ // Priority 3: Default path
46
+ return { path: DEFAULT_CONFIG_FILE, isDefault: true };
47
+ }
48
+
49
+ /**
50
+ * Load and parse a config file from disk
51
+ *
52
+ * @param configPath - Path to the config file
53
+ * @returns Parsed JSON object or null if file not found
54
+ * @throws Error if file exists but cannot be parsed as JSON
55
+ */
56
+ export async function loadConfigFile(
57
+ configPath: string
58
+ ): Promise<Record<string, unknown> | null> {
59
+ try {
60
+ const content = await fs.readFile(configPath, "utf-8");
61
+ return JSON.parse(content) as Record<string, unknown>;
62
+ } catch (error) {
63
+ // File not found is expected - return null
64
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
65
+ return null;
66
+ }
67
+
68
+ // JSON parse errors should be reported
69
+ if (error instanceof SyntaxError) {
70
+ console.warn(
71
+ `Warning: Config file at ${configPath} contains invalid JSON: ${error.message}`
72
+ );
73
+ return null;
74
+ }
75
+
76
+ // Other errors (permissions, etc.) - warn and return null
77
+ console.warn(
78
+ `Warning: Could not read config file at ${configPath}: ${error instanceof Error ? error.message : String(error)}`
79
+ );
80
+ return null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Validate a raw config object against the schema
86
+ * Returns validated config or defaults if validation fails
87
+ *
88
+ * @param raw - Raw config object (or null/undefined)
89
+ * @returns Validated config with defaults applied
90
+ */
91
+ export function validateConfig(
92
+ raw: Record<string, unknown> | null | undefined
93
+ ): Config {
94
+ // If no raw config, return defaults
95
+ if (!raw) {
96
+ return getDefaultConfig();
97
+ }
98
+
99
+ try {
100
+ // Parse with Zod schema - this applies defaults for missing fields
101
+ return configSchema.parse(raw);
102
+ } catch (error) {
103
+ // Log validation errors as warnings
104
+ if (error instanceof Error && "issues" in error) {
105
+ // Zod error with issues array
106
+ const zodError = error as {
107
+ issues: Array<{ path: string[]; message: string }>;
108
+ };
109
+ zodError.issues.forEach((issue) => {
110
+ console.warn(
111
+ `Warning: Config validation error at '${issue.path.join(".")}': ${issue.message}`
112
+ );
113
+ });
114
+ } else {
115
+ console.warn(
116
+ `Warning: Config validation failed: ${error instanceof Error ? error.message : String(error)}`
117
+ );
118
+ }
119
+
120
+ // Return defaults on validation failure
121
+ return getDefaultConfig();
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Create a default config file at the given path
127
+ * Only creates the file if it doesn't already exist
128
+ * Only triggers for the default path, not custom paths
129
+ *
130
+ * @param configPath - Path where to create the config file
131
+ * @param isDefault - Whether this is the default path (only create if true)
132
+ * @returns true if file was created, false otherwise
133
+ */
134
+ export async function createDefaultConfig(
135
+ configPath: string,
136
+ isDefault: boolean
137
+ ): Promise<boolean> {
138
+ // Only create default config for the default path
139
+ if (!isDefault) {
140
+ return false;
141
+ }
142
+
143
+ try {
144
+ // Check if file already exists
145
+ await fs.access(configPath);
146
+ // File exists, don't overwrite
147
+ return false;
148
+ } catch {
149
+ // File doesn't exist, create it
150
+ }
151
+
152
+ try {
153
+ // Create directory if needed
154
+ const configDir = path.dirname(configPath);
155
+ await fs.mkdir(configDir, { recursive: true });
156
+
157
+ // Get default config and write it
158
+ const defaultConfig = getDefaultConfig();
159
+ const content = JSON.stringify(defaultConfig, null, 2);
160
+ await fs.writeFile(configPath, content, "utf-8");
161
+
162
+ // Log informational message to stderr
163
+ console.error(`Created default config at ${configPath}`);
164
+
165
+ return true;
166
+ } catch (error) {
167
+ // Log warning but don't fail - config will use defaults
168
+ console.warn(
169
+ `Warning: Could not create default config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`
170
+ );
171
+ return false;
172
+ }
173
+ }
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Zod schema for Phoenix Insight CLI configuration
5
+ *
6
+ * Configuration values can be set via:
7
+ * 1. Config file (~/.phoenix-insight/config.json or custom path)
8
+ * 2. Environment variables (PHOENIX_BASE_URL, PHOENIX_API_KEY, etc.)
9
+ * 3. CLI arguments (--base-url, --api-key, etc.)
10
+ *
11
+ * Priority: config file < env vars < CLI args
12
+ */
13
+ export const configSchema = z.object({
14
+ /**
15
+ * Phoenix server base URL
16
+ * @default "http://localhost:6006"
17
+ */
18
+ baseUrl: z.string().default("http://localhost:6006"),
19
+
20
+ /**
21
+ * Phoenix API key for authentication (optional)
22
+ */
23
+ apiKey: z.string().optional(),
24
+
25
+ /**
26
+ * Maximum number of spans to fetch per project
27
+ * @default 1000
28
+ */
29
+ limit: z.number().int().positive().default(1000),
30
+
31
+ /**
32
+ * Enable streaming responses from the agent
33
+ * @default true
34
+ */
35
+ stream: z.boolean().default(true),
36
+
37
+ /**
38
+ * Execution mode: "sandbox" for in-memory filesystem, "local" for real filesystem
39
+ * @default "sandbox"
40
+ */
41
+ mode: z.enum(["sandbox", "local"]).default("sandbox"),
42
+
43
+ /**
44
+ * Force refresh of snapshot data
45
+ * @default false
46
+ */
47
+ refresh: z.boolean().default(false),
48
+
49
+ /**
50
+ * Enable tracing of the agent to Phoenix
51
+ * @default false
52
+ */
53
+ trace: z.boolean().default(false),
54
+ });
55
+
56
+ /**
57
+ * Inferred TypeScript type from the config schema
58
+ */
59
+ export type Config = z.infer<typeof configSchema>;
60
+
61
+ /**
62
+ * Get default configuration values
63
+ */
64
+ export function getDefaultConfig(): Config {
65
+ return configSchema.parse({});
66
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./modes/index.js";
@@ -0,0 +1,21 @@
1
+ export * from "./types.js";
2
+ export * from "./sandbox.js";
3
+ export * from "./local.js";
4
+
5
+ import { SandboxMode } from "./sandbox.js";
6
+ import { LocalMode } from "./local.js";
7
+ import type { ExecutionMode } from "./types.js";
8
+
9
+ /**
10
+ * Creates a new sandbox execution mode
11
+ */
12
+ export function createSandboxMode(): ExecutionMode {
13
+ return new SandboxMode();
14
+ }
15
+
16
+ /**
17
+ * Creates a new local execution mode
18
+ */
19
+ export async function createLocalMode(): Promise<ExecutionMode> {
20
+ return new LocalMode();
21
+ }
@@ -0,0 +1,163 @@
1
+ import type { ExecutionMode } from "./types.js";
2
+ import { exec as execCallback } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import * as fs from "node:fs/promises";
5
+ import * as path from "node:path";
6
+ import * as os from "node:os";
7
+ import { tool } from "ai";
8
+ import { z } from "zod";
9
+
10
+ const execAsync = promisify(execCallback);
11
+
12
+ /**
13
+ * Local execution mode using real bash and persistent filesystem
14
+ * - Real bash execution via child_process
15
+ * - Persistent storage in ~/.phoenix-insight/
16
+ * - Full system access
17
+ */
18
+ export class LocalMode implements ExecutionMode {
19
+ private workDir: string;
20
+ private toolCreated = false;
21
+ private bashToolPromise: Promise<any> | null = null;
22
+
23
+ constructor() {
24
+ // Create a timestamped directory for this snapshot
25
+ // Add a small random component to ensure uniqueness even if created at the same millisecond
26
+ const timestamp =
27
+ Date.now().toString() + "-" + Math.random().toString(36).substring(7);
28
+ this.workDir = path.join(
29
+ os.homedir(),
30
+ ".phoenix-insight",
31
+ "snapshots",
32
+ timestamp,
33
+ "phoenix"
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Initialize the working directory
39
+ */
40
+ private async init() {
41
+ try {
42
+ // Create the directory structure if it doesn't exist
43
+ await fs.mkdir(this.workDir, { recursive: true });
44
+ } catch (error) {
45
+ throw new Error(
46
+ `Failed to initialize local mode directory at ${this.workDir}: ${error instanceof Error ? error.message : String(error)}`
47
+ );
48
+ }
49
+ }
50
+
51
+ async writeFile(filePath: string, content: string): Promise<void> {
52
+ await this.init();
53
+
54
+ // Ensure the path is relative to phoenix root
55
+ const cleanPath = filePath.startsWith("/phoenix")
56
+ ? filePath.substring(8) // Remove /phoenix prefix
57
+ : filePath.startsWith("/")
58
+ ? filePath.substring(1) // Remove leading slash
59
+ : filePath;
60
+
61
+ const fullPath = path.join(this.workDir, cleanPath);
62
+
63
+ // Create parent directories if they don't exist
64
+ const dirname = path.dirname(fullPath);
65
+ await fs.mkdir(dirname, { recursive: true });
66
+
67
+ // Write the file
68
+ await fs.writeFile(fullPath, content, "utf-8");
69
+ }
70
+
71
+ async exec(
72
+ command: string
73
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
74
+ await this.init();
75
+
76
+ try {
77
+ // Execute the command in the phoenix directory with a timeout
78
+ const { stdout, stderr } = await execAsync(command, {
79
+ cwd: this.workDir,
80
+ shell: "/bin/bash",
81
+ encoding: "utf-8",
82
+ timeout: 60000, // 60 second timeout for bash commands
83
+ });
84
+
85
+ return {
86
+ stdout: stdout || "",
87
+ stderr: stderr || "",
88
+ exitCode: 0,
89
+ };
90
+ } catch (error: any) {
91
+ // Handle command execution errors
92
+ if (error.code !== undefined) {
93
+ return {
94
+ stdout: error.stdout || "",
95
+ stderr: error.stderr || error.message || "Command failed",
96
+ exitCode: error.code || 1,
97
+ };
98
+ }
99
+
100
+ // Handle other errors
101
+ return {
102
+ stdout: "",
103
+ stderr: error.message || "Unknown error",
104
+ exitCode: 1,
105
+ };
106
+ }
107
+ }
108
+
109
+ async getBashTool(): Promise<any> {
110
+ // Only create the tool once and cache it
111
+ if (!this.bashToolPromise) {
112
+ this.bashToolPromise = this.createBashTool();
113
+ }
114
+ return this.bashToolPromise;
115
+ }
116
+
117
+ private async createBashTool(): Promise<any> {
118
+ // We can't use bash-tool directly for local mode since it's designed for just-bash
119
+ // Instead, we'll create a tool using the AI SDK's tool function
120
+ // This ensures compatibility with the AI SDK
121
+
122
+ // Return a bash tool that executes real bash commands
123
+ return tool({
124
+ description: "Execute bash commands in the local filesystem",
125
+ inputSchema: z.object({
126
+ command: z.string().describe("The bash command to execute"),
127
+ }),
128
+ execute: async ({ command }) => {
129
+ const result = await this.exec(command);
130
+
131
+ // Return result in a format similar to bash-tool
132
+ if (result.exitCode !== 0) {
133
+ // Include error details in the response
134
+ return {
135
+ success: false,
136
+ stdout: result.stdout,
137
+ stderr: result.stderr,
138
+ exitCode: result.exitCode,
139
+ error: `Command failed with exit code ${result.exitCode}`,
140
+ };
141
+ }
142
+
143
+ return {
144
+ success: true,
145
+ stdout: result.stdout,
146
+ stderr: result.stderr,
147
+ exitCode: result.exitCode,
148
+ };
149
+ },
150
+ });
151
+ }
152
+
153
+ async cleanup(): Promise<void> {
154
+ // Optional: Clean up old snapshots
155
+ // For now, we'll keep all snapshots for user reference
156
+ // Users can manually clean ~/.phoenix-insight/ if needed
157
+ // We could implement logic to:
158
+ // 1. Keep only the last N snapshots
159
+ // 2. Delete snapshots older than X days
160
+ // 3. Provide a separate cleanup command
161
+ // For this implementation, we do nothing
162
+ }
163
+ }
@@ -0,0 +1,144 @@
1
+ import type { ExecutionMode } from "./types.js";
2
+ import { tool } from "ai";
3
+ import { z } from "zod";
4
+
5
+ /**
6
+ * Sandbox execution mode using just-bash for isolated execution
7
+ * - In-memory filesystem
8
+ * - Simulated bash commands
9
+ * - No disk or network access
10
+ */
11
+ export class SandboxMode implements ExecutionMode {
12
+ private bash: any; // Will be typed as Bash from just-bash
13
+ private initialized = false;
14
+ private bashToolPromise: Promise<any> | null = null;
15
+
16
+ constructor() {
17
+ // We'll initialize in the init method since we need async imports
18
+ }
19
+
20
+ private async init() {
21
+ if (this.initialized) return;
22
+
23
+ try {
24
+ // Dynamic imports for ESM modules
25
+ const { Bash } = await import("just-bash");
26
+
27
+ // Initialize just-bash with /phoenix as the working directory
28
+ this.bash = new Bash({ cwd: "/phoenix" });
29
+
30
+ this.initialized = true;
31
+ } catch (error) {
32
+ throw new Error(
33
+ `Failed to initialize sandbox mode: ${error instanceof Error ? error.message : String(error)}`
34
+ );
35
+ }
36
+ }
37
+
38
+ async writeFile(path: string, content: string): Promise<void> {
39
+ await this.init();
40
+
41
+ try {
42
+ // Ensure the path starts with /phoenix
43
+ const fullPath = path.startsWith("/phoenix")
44
+ ? path
45
+ : `/phoenix${path.startsWith("/") ? "" : "/"}${path}`;
46
+
47
+ // Create parent directories if they don't exist
48
+ const dirname = fullPath.substring(0, fullPath.lastIndexOf("/"));
49
+ if (dirname) {
50
+ await this.bash.exec(`mkdir -p ${dirname}`);
51
+ }
52
+
53
+ // Write the file using just-bash's filesystem
54
+ // We'll use the InMemoryFs directly for better performance
55
+ this.bash.fs.writeFileSync(fullPath, content);
56
+ } catch (error) {
57
+ throw new Error(
58
+ `Failed to write file ${path}: ${error instanceof Error ? error.message : String(error)}`
59
+ );
60
+ }
61
+ }
62
+
63
+ async exec(
64
+ command: string
65
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
66
+ await this.init();
67
+
68
+ try {
69
+ const result = await this.bash.exec(command);
70
+
71
+ // just-bash returns a different structure, so we need to normalize it
72
+ return {
73
+ stdout: result.stdout || "",
74
+ stderr: result.stderr || "",
75
+ exitCode: result.exitCode || 0,
76
+ };
77
+ } catch (error) {
78
+ // If the command fails, just-bash throws an error
79
+ // Extract what we can from the error
80
+ if (error && typeof error === "object" && "exitCode" in error) {
81
+ return {
82
+ stdout: (error as any).stdout || "",
83
+ stderr: (error as any).stderr || error.toString(),
84
+ exitCode: (error as any).exitCode || 1,
85
+ };
86
+ }
87
+
88
+ // Fallback for unexpected errors
89
+ return {
90
+ stdout: "",
91
+ stderr: error?.toString() || "Unknown error",
92
+ exitCode: 1,
93
+ };
94
+ }
95
+ }
96
+
97
+ async getBashTool(): Promise<any> {
98
+ // Only create the tool once and cache it
99
+ if (!this.bashToolPromise) {
100
+ this.bashToolPromise = this.createBashTool();
101
+ }
102
+ return this.bashToolPromise;
103
+ }
104
+
105
+ private async createBashTool(): Promise<any> {
106
+ await this.init();
107
+
108
+ // Create a bash tool compatible with the AI SDK
109
+ // Similar to local mode, we'll create it directly using the tool function
110
+ return tool({
111
+ description: "Execute bash commands in the sandbox filesystem",
112
+ inputSchema: z.object({
113
+ command: z.string().describe("The bash command to execute"),
114
+ }),
115
+ execute: async ({ command }) => {
116
+ const result = await this.exec(command);
117
+
118
+ // Return result in a format similar to bash-tool
119
+ if (result.exitCode !== 0) {
120
+ // Include error details in the response
121
+ return {
122
+ success: false,
123
+ stdout: result.stdout,
124
+ stderr: result.stderr,
125
+ exitCode: result.exitCode,
126
+ error: `Command failed with exit code ${result.exitCode}`,
127
+ };
128
+ }
129
+
130
+ return {
131
+ success: true,
132
+ stdout: result.stdout,
133
+ stderr: result.stderr,
134
+ exitCode: result.exitCode,
135
+ };
136
+ },
137
+ });
138
+ }
139
+
140
+ async cleanup(): Promise<void> {
141
+ // No-op for in-memory mode - garbage collection will handle cleanup
142
+ // We could optionally clear the filesystem here if needed
143
+ }
144
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Common interface for execution modes (sandbox vs local)
3
+ */
4
+ export interface ExecutionMode {
5
+ /**
6
+ * Write Phoenix data to the filesystem
7
+ * @param path - The file path relative to the Phoenix root
8
+ * @param content - The content to write
9
+ */
10
+ writeFile(path: string, content: string): Promise<void>;
11
+
12
+ /**
13
+ * Execute a bash command and return output
14
+ * @param command - The bash command to execute
15
+ * @returns The command output with stdout, stderr, and exit code
16
+ */
17
+ exec(
18
+ command: string
19
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }>;
20
+
21
+ /**
22
+ * Get the bash tool for the AI SDK agent
23
+ * @returns A tool that can be used by the AI SDK
24
+ */
25
+ getBashTool(): Promise<any>; // Tool type from AI SDK
26
+
27
+ /**
28
+ * Clean up resources
29
+ */
30
+ cleanup(): Promise<void>;
31
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Phoenix Insight observability configuration
3
+ */
4
+
5
+ import { register, type NodeTracerProvider } from "@arizeai/phoenix-otel";
6
+ import { DiagLogLevel } from "@opentelemetry/api";
7
+
8
+ /**
9
+ * Options for configuring observability
10
+ */
11
+ export interface ObservabilityOptions {
12
+ /** Whether to enable tracing */
13
+ enabled: boolean;
14
+ /** Phoenix base URL for sending traces */
15
+ baseUrl?: string;
16
+ /** Phoenix API key for authentication */
17
+ apiKey?: string;
18
+ /** Phoenix project name for organizing traces */
19
+ projectName?: string;
20
+ /** Whether to enable debug logging */
21
+ debug?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Global tracer provider instance
26
+ */
27
+ let tracerProvider: NodeTracerProvider | null = null;
28
+
29
+ /**
30
+ * Check if observability is enabled
31
+ */
32
+ export function isObservabilityEnabled(): boolean {
33
+ return tracerProvider !== null;
34
+ }
35
+
36
+ /**
37
+ * Initialize observability for the Phoenix Insight agent
38
+ */
39
+ export function initializeObservability(options: ObservabilityOptions): void {
40
+ if (!options.enabled) {
41
+ return;
42
+ }
43
+
44
+ // If already initialized, skip
45
+ if (tracerProvider) {
46
+ return;
47
+ }
48
+
49
+ try {
50
+ // Configure the tracer provider
51
+ tracerProvider = register({
52
+ projectName: options.projectName || "phoenix-insight",
53
+ url: options.baseUrl,
54
+ apiKey: options.apiKey,
55
+ batch: true,
56
+ global: true,
57
+ diagLogLevel: options.debug ? DiagLogLevel.DEBUG : undefined,
58
+ });
59
+
60
+ if (options.debug) {
61
+ console.error(
62
+ "🔭 Observability enabled - traces will be sent to Phoenix"
63
+ );
64
+ }
65
+ } catch (error) {
66
+ console.error("⚠️ Failed to initialize observability:", error);
67
+ // Don't throw - observability should not break the main functionality
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Shutdown observability and cleanup resources
73
+ */
74
+ export async function shutdownObservability(): Promise<void> {
75
+ if (tracerProvider) {
76
+ try {
77
+ await tracerProvider.shutdown();
78
+ tracerProvider = null;
79
+ } catch (error) {
80
+ console.error("⚠️ Error shutting down observability:", error);
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get the current tracer provider
87
+ */
88
+ export function getTracerProvider(): NodeTracerProvider | null {
89
+ return tracerProvider;
90
+ }