@boostecom/provider 0.0.1

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 (70) hide show
  1. package/README.md +90 -0
  2. package/dist/index.cjs +2522 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +848 -0
  5. package/dist/index.d.ts +848 -0
  6. package/dist/index.js +2484 -0
  7. package/dist/index.js.map +1 -0
  8. package/docs/content/README.md +337 -0
  9. package/docs/content/agent-teams.mdx +324 -0
  10. package/docs/content/api.mdx +757 -0
  11. package/docs/content/best-practices.mdx +624 -0
  12. package/docs/content/examples.mdx +675 -0
  13. package/docs/content/guide.mdx +516 -0
  14. package/docs/content/index.mdx +99 -0
  15. package/docs/content/installation.mdx +246 -0
  16. package/docs/content/skills.mdx +548 -0
  17. package/docs/content/troubleshooting.mdx +588 -0
  18. package/docs/examples/README.md +499 -0
  19. package/docs/examples/abort-signal.ts +125 -0
  20. package/docs/examples/agent-teams.ts +122 -0
  21. package/docs/examples/basic-usage.ts +73 -0
  22. package/docs/examples/check-cli.ts +51 -0
  23. package/docs/examples/conversation-history.ts +69 -0
  24. package/docs/examples/custom-config.ts +90 -0
  25. package/docs/examples/generate-object-constraints.ts +209 -0
  26. package/docs/examples/generate-object.ts +211 -0
  27. package/docs/examples/hooks-callbacks.ts +63 -0
  28. package/docs/examples/images.ts +76 -0
  29. package/docs/examples/integration-test.ts +241 -0
  30. package/docs/examples/limitations.ts +150 -0
  31. package/docs/examples/logging-custom-logger.ts +99 -0
  32. package/docs/examples/logging-default.ts +55 -0
  33. package/docs/examples/logging-disabled.ts +74 -0
  34. package/docs/examples/logging-verbose.ts +64 -0
  35. package/docs/examples/long-running-tasks.ts +179 -0
  36. package/docs/examples/message-injection.ts +210 -0
  37. package/docs/examples/mid-stream-injection.ts +126 -0
  38. package/docs/examples/run-all-examples.sh +48 -0
  39. package/docs/examples/sdk-tools-callbacks.ts +49 -0
  40. package/docs/examples/skills-discovery.ts +144 -0
  41. package/docs/examples/skills-management.ts +140 -0
  42. package/docs/examples/stream-object.ts +80 -0
  43. package/docs/examples/streaming.ts +52 -0
  44. package/docs/examples/structured-output-repro.ts +227 -0
  45. package/docs/examples/tool-management.ts +215 -0
  46. package/docs/examples/tool-streaming.ts +132 -0
  47. package/docs/examples/zod4-compatibility-test.ts +290 -0
  48. package/docs/src/claude-code-language-model.test.ts +3883 -0
  49. package/docs/src/claude-code-language-model.ts +2586 -0
  50. package/docs/src/claude-code-provider.test.ts +97 -0
  51. package/docs/src/claude-code-provider.ts +179 -0
  52. package/docs/src/convert-to-claude-code-messages.images.test.ts +104 -0
  53. package/docs/src/convert-to-claude-code-messages.test.ts +193 -0
  54. package/docs/src/convert-to-claude-code-messages.ts +419 -0
  55. package/docs/src/errors.test.ts +213 -0
  56. package/docs/src/errors.ts +216 -0
  57. package/docs/src/index.test.ts +49 -0
  58. package/docs/src/index.ts +98 -0
  59. package/docs/src/logger.integration.test.ts +164 -0
  60. package/docs/src/logger.test.ts +184 -0
  61. package/docs/src/logger.ts +65 -0
  62. package/docs/src/map-claude-code-finish-reason.test.ts +120 -0
  63. package/docs/src/map-claude-code-finish-reason.ts +60 -0
  64. package/docs/src/mcp-helpers.test.ts +71 -0
  65. package/docs/src/mcp-helpers.ts +123 -0
  66. package/docs/src/message-injection.test.ts +460 -0
  67. package/docs/src/types.ts +447 -0
  68. package/docs/src/validation.test.ts +558 -0
  69. package/docs/src/validation.ts +360 -0
  70. package/package.json +124 -0
@@ -0,0 +1,216 @@
1
+ import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
2
+
3
+ /**
4
+ * Metadata associated with Claude Code SDK errors.
5
+ * Provides additional context about command execution failures.
6
+ */
7
+ export interface ClaudeCodeErrorMetadata {
8
+ /**
9
+ * Error code from the CLI process (e.g., 'ENOENT', 'ETIMEDOUT').
10
+ */
11
+ code?: string;
12
+
13
+ /**
14
+ * Exit code from the Claude Code SDK process.
15
+ * Common codes:
16
+ * - 401: Authentication error
17
+ * - 1: General error
18
+ */
19
+ exitCode?: number;
20
+
21
+ /**
22
+ * Standard error output from the CLI process.
23
+ */
24
+ stderr?: string;
25
+
26
+ /**
27
+ * Excerpt from the prompt that caused the error.
28
+ * Limited to first 200 characters for debugging.
29
+ */
30
+ promptExcerpt?: string;
31
+ }
32
+
33
+ /**
34
+ * Creates an APICallError with Claude Code specific metadata.
35
+ * Used for general CLI execution errors.
36
+ *
37
+ * @param options - Error details and metadata
38
+ * @param options.message - Human-readable error message
39
+ * @param options.code - Error code from the CLI process
40
+ * @param options.exitCode - Exit code from the CLI
41
+ * @param options.stderr - Standard error output
42
+ * @param options.promptExcerpt - Excerpt of the prompt that caused the error
43
+ * @param options.isRetryable - Whether the error is potentially retryable
44
+ * @returns An APICallError instance with Claude Code metadata
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * throw createAPICallError({
49
+ * message: 'Claude Code SDK failed',
50
+ * code: 'ENOENT',
51
+ * isRetryable: true
52
+ * });
53
+ * ```
54
+ */
55
+ export function createAPICallError({
56
+ message,
57
+ code,
58
+ exitCode,
59
+ stderr,
60
+ promptExcerpt,
61
+ isRetryable = false,
62
+ }: ClaudeCodeErrorMetadata & {
63
+ message: string;
64
+ isRetryable?: boolean;
65
+ }): APICallError {
66
+ const metadata: ClaudeCodeErrorMetadata = {
67
+ code,
68
+ exitCode,
69
+ stderr,
70
+ promptExcerpt,
71
+ };
72
+
73
+ return new APICallError({
74
+ message,
75
+ isRetryable,
76
+ url: 'claude-code-cli://command',
77
+ requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
78
+ data: metadata,
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Creates an authentication error for Claude Code SDK login failures.
84
+ *
85
+ * @param options - Error configuration
86
+ * @param options.message - Error message describing the authentication failure
87
+ * @returns A LoadAPIKeyError instance
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * throw createAuthenticationError({
92
+ * message: 'Please run "claude login" to authenticate'
93
+ * });
94
+ * ```
95
+ */
96
+ export function createAuthenticationError({ message }: { message: string }): LoadAPIKeyError {
97
+ return new LoadAPIKeyError({
98
+ message:
99
+ message || 'Authentication failed. Please ensure Claude Code SDK is properly authenticated.',
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Creates a timeout error for Claude Code SDK operations.
105
+ *
106
+ * @param options - Timeout error details
107
+ * @param options.message - Error message describing the timeout
108
+ * @param options.promptExcerpt - Excerpt of the prompt that timed out
109
+ * @param options.timeoutMs - Timeout duration in milliseconds
110
+ * @returns An APICallError instance configured as a timeout error
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * throw createTimeoutError({
115
+ * message: 'Request timed out after 2 minutes',
116
+ * timeoutMs: 120000
117
+ * });
118
+ * ```
119
+ */
120
+ export function createTimeoutError({
121
+ message,
122
+ promptExcerpt,
123
+ timeoutMs,
124
+ }: {
125
+ message: string;
126
+ promptExcerpt?: string;
127
+ timeoutMs?: number;
128
+ }): APICallError {
129
+ // Store timeoutMs in metadata for potential use by error handlers
130
+ const metadata: ClaudeCodeErrorMetadata = {
131
+ code: 'TIMEOUT',
132
+ promptExcerpt,
133
+ };
134
+
135
+ return new APICallError({
136
+ message,
137
+ isRetryable: true,
138
+ url: 'claude-code-cli://command',
139
+ requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
140
+ data: timeoutMs !== undefined ? { ...metadata, timeoutMs } : metadata,
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Checks if an error is an authentication error.
146
+ * Returns true for LoadAPIKeyError instances or APICallError with exit code 401.
147
+ *
148
+ * @param error - The error to check
149
+ * @returns True if the error is an authentication error
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * try {
154
+ * await model.generate(...);
155
+ * } catch (error) {
156
+ * if (isAuthenticationError(error)) {
157
+ * console.log('Please authenticate with Claude Code SDK');
158
+ * }
159
+ * }
160
+ * ```
161
+ */
162
+ export function isAuthenticationError(error: unknown): boolean {
163
+ if (error instanceof LoadAPIKeyError) return true;
164
+ if (error instanceof APICallError && (error.data as ClaudeCodeErrorMetadata)?.exitCode === 401)
165
+ return true;
166
+ return false;
167
+ }
168
+
169
+ /**
170
+ * Checks if an error is a timeout error.
171
+ * Returns true for APICallError instances with code 'TIMEOUT'.
172
+ *
173
+ * @param error - The error to check
174
+ * @returns True if the error is a timeout error
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * try {
179
+ * await model.generate(...);
180
+ * } catch (error) {
181
+ * if (isTimeoutError(error)) {
182
+ * console.log('Request timed out, consider retrying');
183
+ * }
184
+ * }
185
+ * ```
186
+ */
187
+ export function isTimeoutError(error: unknown): boolean {
188
+ if (error instanceof APICallError && (error.data as ClaudeCodeErrorMetadata)?.code === 'TIMEOUT')
189
+ return true;
190
+ return false;
191
+ }
192
+
193
+ /**
194
+ * Extracts Claude Code error metadata from an error object.
195
+ *
196
+ * @param error - The error to extract metadata from
197
+ * @returns The error metadata if available, undefined otherwise
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * try {
202
+ * await model.generate(...);
203
+ * } catch (error) {
204
+ * const metadata = getErrorMetadata(error);
205
+ * if (metadata?.exitCode === 401) {
206
+ * console.log('Authentication required');
207
+ * }
208
+ * }
209
+ * ```
210
+ */
211
+ export function getErrorMetadata(error: unknown): ClaudeCodeErrorMetadata | undefined {
212
+ if (error instanceof APICallError && error.data) {
213
+ return error.data as ClaudeCodeErrorMetadata;
214
+ }
215
+ return undefined;
216
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('index exports', () => {
4
+ it('should export all expected functions and types', async () => {
5
+ const exports = await import('./index.js');
6
+
7
+ // Provider exports
8
+ expect(exports.createClaudeCode).toBeDefined();
9
+ expect(typeof exports.createClaudeCode).toBe('function');
10
+ expect(exports.claudeCode).toBeDefined();
11
+ expect(typeof exports.claudeCode).toBe('function');
12
+
13
+ // Language model exports
14
+ expect(exports.ClaudeCodeLanguageModel).toBeDefined();
15
+ expect(typeof exports.ClaudeCodeLanguageModel).toBe('function');
16
+
17
+ // Error handling exports
18
+ expect(exports.isAuthenticationError).toBeDefined();
19
+ expect(typeof exports.isAuthenticationError).toBe('function');
20
+ expect(exports.isTimeoutError).toBeDefined();
21
+ expect(typeof exports.isTimeoutError).toBe('function');
22
+ expect(exports.getErrorMetadata).toBeDefined();
23
+ expect(typeof exports.getErrorMetadata).toBe('function');
24
+ expect(exports.createAPICallError).toBeDefined();
25
+ expect(typeof exports.createAPICallError).toBe('function');
26
+ expect(exports.createAuthenticationError).toBeDefined();
27
+ expect(typeof exports.createAuthenticationError).toBe('function');
28
+ expect(exports.createTimeoutError).toBeDefined();
29
+ expect(typeof exports.createTimeoutError).toBe('function');
30
+
31
+ // SDK passthroughs
32
+ expect(exports.createSdkMcpServer).toBeDefined();
33
+ expect(typeof exports.createSdkMcpServer).toBe('function');
34
+ expect(exports.tool).toBeDefined();
35
+ expect(typeof exports.tool).toBe('function');
36
+ });
37
+
38
+ it('should export correct modules', async () => {
39
+ const indexExports = await import('./index.js');
40
+ const providerExports = await import('./claude-code-provider.js');
41
+ const errorExports = await import('./errors.js');
42
+
43
+ // Check that exported functions are the same references
44
+ expect(indexExports.createClaudeCode).toBe(providerExports.createClaudeCode);
45
+ expect(indexExports.claudeCode).toBe(providerExports.claudeCode);
46
+ expect(indexExports.isAuthenticationError).toBe(errorExports.isAuthenticationError);
47
+ expect(indexExports.isTimeoutError).toBe(errorExports.isTimeoutError);
48
+ });
49
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Provider exports for creating and configuring Claude Code instances.
3
+ * @module claude-code
4
+ */
5
+
6
+ /**
7
+ * Creates a new Claude Code provider instance and the default provider instance.
8
+ * @see {@link createClaudeCode} for creating custom provider instances
9
+ * @see {@link claudeCode} for the default provider instance
10
+ */
11
+ export { createClaudeCode, claudeCode } from './claude-code-provider.js';
12
+
13
+ /**
14
+ * Type definitions for the Claude Code provider.
15
+ * @see {@link ClaudeCodeProvider} for the provider interface
16
+ * @see {@link ClaudeCodeProviderSettings} for provider configuration options
17
+ */
18
+ export type { ClaudeCodeProvider, ClaudeCodeProviderSettings } from './claude-code-provider.js';
19
+
20
+ /**
21
+ * Language model implementation for Claude Code.
22
+ * This class implements the AI SDK's LanguageModelV3 interface.
23
+ */
24
+ export { ClaudeCodeLanguageModel } from './claude-code-language-model.js';
25
+
26
+ /**
27
+ * Type definitions for Claude Code language models.
28
+ * @see {@link ClaudeCodeModelId} for supported model identifiers
29
+ * @see {@link ClaudeCodeLanguageModelOptions} for model configuration options
30
+ */
31
+ export type {
32
+ ClaudeCodeModelId,
33
+ ClaudeCodeLanguageModelOptions,
34
+ } from './claude-code-language-model.js';
35
+
36
+ /**
37
+ * Settings for configuring Claude Code behavior.
38
+ * Includes options for customizing the CLI execution, permissions, and tool usage.
39
+ */
40
+ export type { ClaudeCodeSettings, Logger, MessageInjector } from './types.js';
41
+
42
+ // Convenience re-exports from the SDK for custom tools and hooks
43
+ export { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
44
+ export { createCustomMcpServer } from './mcp-helpers.js';
45
+ export type { ToolAnnotations, MinimalCallToolResult } from './mcp-helpers.js';
46
+ export type {
47
+ HookEvent,
48
+ HookCallback,
49
+ HookCallbackMatcher,
50
+ HookInput,
51
+ HookJSONOutput,
52
+ PreToolUseHookInput,
53
+ PostToolUseHookInput,
54
+ UserPromptSubmitHookInput,
55
+ SessionStartHookInput,
56
+ SessionEndHookInput,
57
+ TeammateIdleHookInput,
58
+ TaskCompletedHookInput,
59
+ CanUseTool,
60
+ PermissionResult,
61
+ PermissionUpdate,
62
+ PermissionBehavior,
63
+ PermissionRuleValue,
64
+ McpServerConfig,
65
+ McpSdkServerConfigWithInstance,
66
+ OutputFormat,
67
+ SpawnedProcess,
68
+ SpawnOptions,
69
+ AgentMcpServerSpec,
70
+ // Query interface for mid-stream message injection via streamInput()
71
+ Query,
72
+ } from '@anthropic-ai/claude-agent-sdk';
73
+
74
+ /**
75
+ * Error handling utilities for Claude Code.
76
+ * These functions help create and identify specific error types.
77
+ *
78
+ * @see {@link isAuthenticationError} to check for authentication failures
79
+ * @see {@link isTimeoutError} to check for timeout errors
80
+ * @see {@link getErrorMetadata} to extract error metadata
81
+ * @see {@link createAPICallError} to create general API errors
82
+ * @see {@link createAuthenticationError} to create authentication errors
83
+ * @see {@link createTimeoutError} to create timeout errors
84
+ */
85
+ export {
86
+ isAuthenticationError,
87
+ isTimeoutError,
88
+ getErrorMetadata,
89
+ createAPICallError,
90
+ createAuthenticationError,
91
+ createTimeoutError,
92
+ } from './errors.js';
93
+
94
+ /**
95
+ * Metadata associated with Claude Code errors.
96
+ * Contains additional context about CLI execution failures.
97
+ */
98
+ export type { ClaudeCodeErrorMetadata } from './errors.js';
@@ -0,0 +1,164 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createClaudeCode } from '../src/index.js';
3
+ import type { Logger } from '../src/types.js';
4
+
5
+ describe('logger integration', () => {
6
+ let originalConsoleWarn: typeof console.warn;
7
+ let originalConsoleError: typeof console.error;
8
+ let consoleWarnSpy: any;
9
+ let consoleErrorSpy: any;
10
+
11
+ beforeEach(() => {
12
+ originalConsoleWarn = console.warn;
13
+ originalConsoleError = console.error;
14
+ consoleWarnSpy = vi.fn();
15
+ consoleErrorSpy = vi.fn();
16
+ console.warn = consoleWarnSpy;
17
+ console.error = consoleErrorSpy;
18
+ });
19
+
20
+ afterEach(() => {
21
+ console.warn = originalConsoleWarn;
22
+ console.error = originalConsoleError;
23
+ });
24
+
25
+ describe('createClaudeCode with logger settings', () => {
26
+ it('should use console logger by default', () => {
27
+ createClaudeCode({
28
+ defaultSettings: {
29
+ maxTurns: 99, // High value to trigger warning
30
+ },
31
+ });
32
+
33
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('High maxTurns value'));
34
+ });
35
+
36
+ it('should respect logger: false setting', () => {
37
+ createClaudeCode({
38
+ defaultSettings: {
39
+ logger: false,
40
+ maxTurns: 99, // High value that would normally trigger warning
41
+ },
42
+ });
43
+
44
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
45
+ });
46
+
47
+ it('should use custom logger when provided', () => {
48
+ const customLogger: Logger = {
49
+ debug: vi.fn(),
50
+ info: vi.fn(),
51
+ warn: vi.fn(),
52
+ error: vi.fn(),
53
+ };
54
+
55
+ createClaudeCode({
56
+ defaultSettings: {
57
+ logger: customLogger,
58
+ maxTurns: 99, // High value to trigger warning
59
+ },
60
+ });
61
+
62
+ expect(customLogger.warn).toHaveBeenCalledWith(
63
+ expect.stringContaining('High maxTurns value')
64
+ );
65
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
66
+ });
67
+
68
+ it('should pass logger to created models', () => {
69
+ const customLogger: Logger = {
70
+ debug: vi.fn(),
71
+ info: vi.fn(),
72
+ warn: vi.fn(),
73
+ error: vi.fn(),
74
+ };
75
+
76
+ const provider = createClaudeCode({
77
+ defaultSettings: {
78
+ logger: customLogger,
79
+ },
80
+ });
81
+
82
+ // Create a model with an unknown ID to trigger warning
83
+ provider('unknown-model-id');
84
+
85
+ expect(customLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Unknown model ID'));
86
+ });
87
+
88
+ it('should allow model-specific logger override', () => {
89
+ const providerLogger: Logger = {
90
+ debug: vi.fn(),
91
+ info: vi.fn(),
92
+ warn: vi.fn(),
93
+ error: vi.fn(),
94
+ };
95
+
96
+ const modelLogger: Logger = {
97
+ debug: vi.fn(),
98
+ info: vi.fn(),
99
+ warn: vi.fn(),
100
+ error: vi.fn(),
101
+ };
102
+
103
+ const provider = createClaudeCode({
104
+ defaultSettings: {
105
+ logger: providerLogger,
106
+ },
107
+ });
108
+
109
+ // Create a model with its own logger
110
+ provider('unknown-model', {
111
+ logger: modelLogger,
112
+ });
113
+
114
+ expect(modelLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Unknown model ID'));
115
+ expect(providerLogger.warn).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it('should handle image input warnings', async () => {
119
+ const customLogger: Logger = {
120
+ debug: vi.fn(),
121
+ info: vi.fn(),
122
+ warn: vi.fn(),
123
+ error: vi.fn(),
124
+ };
125
+
126
+ const provider = createClaudeCode({
127
+ defaultSettings: {
128
+ logger: customLogger,
129
+ },
130
+ });
131
+
132
+ provider('opus');
133
+
134
+ // Mock the query function to prevent actual API calls
135
+ const { ClaudeCodeLanguageModel } = await import('../src/claude-code-language-model.js');
136
+ const proto = ClaudeCodeLanguageModel.prototype as any;
137
+
138
+ // Access the private method through prototype
139
+ const result = proto.generateAllWarnings.call(
140
+ {
141
+ modelValidationWarning: undefined,
142
+ settingsValidationWarnings: [],
143
+ logger: customLogger,
144
+ },
145
+ {
146
+ prompt: [
147
+ {
148
+ role: 'user',
149
+ content: [
150
+ { type: 'text', text: 'Hello' },
151
+ { type: 'image', image: new Uint8Array([]) },
152
+ ],
153
+ },
154
+ ],
155
+ },
156
+ 'test prompt'
157
+ );
158
+
159
+ // The warning should be in the warnings array, not logged directly
160
+ expect(customLogger.warn).not.toHaveBeenCalled();
161
+ expect(result.some((w: any) => w.message?.includes('image inputs'))).toBe(false);
162
+ });
163
+ });
164
+ });
@@ -0,0 +1,184 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { getLogger, createVerboseLogger } from './logger.js';
3
+ import type { Logger } from './types.js';
4
+
5
+ describe('logger', () => {
6
+ describe('getLogger', () => {
7
+ it('should return default logger when undefined', () => {
8
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
9
+ const logger = getLogger(undefined);
10
+
11
+ logger.warn('test warning');
12
+
13
+ expect(consoleSpy).toHaveBeenCalledWith('[WARN] test warning');
14
+ consoleSpy.mockRestore();
15
+ });
16
+
17
+ it('should support all log levels with default logger', () => {
18
+ const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
19
+ const infoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
20
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
21
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
22
+
23
+ const logger = getLogger(undefined);
24
+
25
+ logger.debug('test debug');
26
+ logger.info('test info');
27
+ logger.warn('test warning');
28
+ logger.error('test error');
29
+
30
+ expect(debugSpy).toHaveBeenCalledWith('[DEBUG] test debug');
31
+ expect(infoSpy).toHaveBeenCalledWith('[INFO] test info');
32
+ expect(warnSpy).toHaveBeenCalledWith('[WARN] test warning');
33
+ expect(errorSpy).toHaveBeenCalledWith('[ERROR] test error');
34
+
35
+ debugSpy.mockRestore();
36
+ infoSpy.mockRestore();
37
+ warnSpy.mockRestore();
38
+ errorSpy.mockRestore();
39
+ });
40
+
41
+ it('should return noop logger when false', () => {
42
+ const logger = getLogger(false);
43
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
44
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
45
+
46
+ logger.debug('test debug');
47
+ logger.info('test info');
48
+ logger.warn('test warning');
49
+ logger.error('test error');
50
+
51
+ expect(warnSpy).not.toHaveBeenCalled();
52
+ expect(errorSpy).not.toHaveBeenCalled();
53
+ warnSpy.mockRestore();
54
+ errorSpy.mockRestore();
55
+ });
56
+
57
+ it('should return custom logger when provided', () => {
58
+ const customLogger: Logger = {
59
+ debug: vi.fn(),
60
+ info: vi.fn(),
61
+ warn: vi.fn(),
62
+ error: vi.fn(),
63
+ };
64
+
65
+ const logger = getLogger(customLogger);
66
+
67
+ expect(logger).toBe(customLogger);
68
+
69
+ logger.debug('custom debug');
70
+ logger.info('custom info');
71
+ logger.warn('custom warning');
72
+ logger.error('custom error');
73
+
74
+ expect(customLogger.debug).toHaveBeenCalledWith('custom debug');
75
+ expect(customLogger.info).toHaveBeenCalledWith('custom info');
76
+ expect(customLogger.warn).toHaveBeenCalledWith('custom warning');
77
+ expect(customLogger.error).toHaveBeenCalledWith('custom error');
78
+ });
79
+
80
+ it('should handle error logging with default logger', () => {
81
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
82
+ const logger = getLogger(undefined);
83
+
84
+ logger.error('test error');
85
+
86
+ expect(consoleSpy).toHaveBeenCalledWith('[ERROR] test error');
87
+ consoleSpy.mockRestore();
88
+ });
89
+ });
90
+
91
+ describe('createVerboseLogger', () => {
92
+ it('should allow all log levels when verbose is true', () => {
93
+ const mockLogger: Logger = {
94
+ debug: vi.fn(),
95
+ info: vi.fn(),
96
+ warn: vi.fn(),
97
+ error: vi.fn(),
98
+ };
99
+
100
+ const logger = createVerboseLogger(mockLogger, true);
101
+
102
+ logger.debug('test debug');
103
+ logger.info('test info');
104
+ logger.warn('test warning');
105
+ logger.error('test error');
106
+
107
+ expect(mockLogger.debug).toHaveBeenCalledWith('test debug');
108
+ expect(mockLogger.info).toHaveBeenCalledWith('test info');
109
+ expect(mockLogger.warn).toHaveBeenCalledWith('test warning');
110
+ expect(mockLogger.error).toHaveBeenCalledWith('test error');
111
+ });
112
+
113
+ it('should suppress debug and info when verbose is false', () => {
114
+ const mockLogger: Logger = {
115
+ debug: vi.fn(),
116
+ info: vi.fn(),
117
+ warn: vi.fn(),
118
+ error: vi.fn(),
119
+ };
120
+
121
+ const logger = createVerboseLogger(mockLogger, false);
122
+
123
+ logger.debug('test debug');
124
+ logger.info('test info');
125
+ logger.warn('test warning');
126
+ logger.error('test error');
127
+
128
+ expect(mockLogger.debug).not.toHaveBeenCalled();
129
+ expect(mockLogger.info).not.toHaveBeenCalled();
130
+ expect(mockLogger.warn).toHaveBeenCalledWith('test warning');
131
+ expect(mockLogger.error).toHaveBeenCalledWith('test error');
132
+ });
133
+
134
+ it('should default to verbose false when not specified', () => {
135
+ const mockLogger: Logger = {
136
+ debug: vi.fn(),
137
+ info: vi.fn(),
138
+ warn: vi.fn(),
139
+ error: vi.fn(),
140
+ };
141
+
142
+ const logger = createVerboseLogger(mockLogger);
143
+
144
+ logger.debug('test debug');
145
+ logger.info('test info');
146
+
147
+ expect(mockLogger.debug).not.toHaveBeenCalled();
148
+ expect(mockLogger.info).not.toHaveBeenCalled();
149
+ });
150
+
151
+ it('should preserve this binding for custom logger instances', () => {
152
+ // Create a logger that relies on instance state
153
+ class CustomLogger implements Logger {
154
+ private prefix = '[CUSTOM]';
155
+
156
+ debug(message: string) {
157
+ return `${this.prefix} DEBUG: ${message}`;
158
+ }
159
+
160
+ info(message: string) {
161
+ return `${this.prefix} INFO: ${message}`;
162
+ }
163
+
164
+ warn(message: string) {
165
+ return `${this.prefix} WARN: ${message}`;
166
+ }
167
+
168
+ error(message: string) {
169
+ return `${this.prefix} ERROR: ${message}`;
170
+ }
171
+ }
172
+
173
+ const customLogger = new CustomLogger();
174
+ const logger = createVerboseLogger(customLogger, false);
175
+
176
+ // These should not throw and should preserve 'this' binding
177
+ const warnResult = logger.warn('test warning');
178
+ const errorResult = logger.error('test error');
179
+
180
+ expect(warnResult).toBe('[CUSTOM] WARN: test warning');
181
+ expect(errorResult).toBe('[CUSTOM] ERROR: test error');
182
+ });
183
+ });
184
+ });