@revenium/openai 1.0.13 → 1.0.15

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 (240) hide show
  1. package/.env.example +10 -15
  2. package/CHANGELOG.md +65 -11
  3. package/CODE_OF_CONDUCT.md +57 -0
  4. package/CONTRIBUTING.md +38 -0
  5. package/README.md +104 -216
  6. package/SECURITY.md +34 -0
  7. package/dist/cjs/core/client/index.js +14 -0
  8. package/dist/cjs/core/client/index.js.map +1 -0
  9. package/dist/cjs/core/client/manager.js +109 -0
  10. package/dist/cjs/core/client/manager.js.map +1 -0
  11. package/dist/cjs/core/config/azure-config.js +5 -17
  12. package/dist/cjs/core/config/azure-config.js.map +1 -1
  13. package/dist/cjs/core/config/index.js +2 -2
  14. package/dist/cjs/core/config/index.js.map +1 -1
  15. package/dist/cjs/core/config/loader.js +34 -14
  16. package/dist/cjs/core/config/loader.js.map +1 -1
  17. package/dist/cjs/core/config/manager.js +11 -5
  18. package/dist/cjs/core/config/manager.js.map +1 -1
  19. package/dist/cjs/core/config/validator.js +3 -45
  20. package/dist/cjs/core/config/validator.js.map +1 -1
  21. package/dist/cjs/core/middleware/index.js +21 -0
  22. package/dist/cjs/core/middleware/index.js.map +1 -0
  23. package/dist/cjs/core/middleware/interfaces.js +454 -0
  24. package/dist/cjs/core/middleware/interfaces.js.map +1 -0
  25. package/dist/cjs/core/middleware/revenium-client.js +152 -0
  26. package/dist/cjs/core/middleware/revenium-client.js.map +1 -0
  27. package/dist/cjs/core/providers/detector.js +45 -23
  28. package/dist/cjs/core/providers/detector.js.map +1 -1
  29. package/dist/cjs/core/providers/index.js +2 -1
  30. package/dist/cjs/core/providers/index.js.map +1 -1
  31. package/dist/cjs/core/tracking/api-client.js +21 -14
  32. package/dist/cjs/core/tracking/api-client.js.map +1 -1
  33. package/dist/cjs/core/tracking/index.js +5 -1
  34. package/dist/cjs/core/tracking/index.js.map +1 -1
  35. package/dist/cjs/core/tracking/payload-builder.js +143 -25
  36. package/dist/cjs/core/tracking/payload-builder.js.map +1 -1
  37. package/dist/cjs/core/tracking/usage-tracker.js +111 -18
  38. package/dist/cjs/core/tracking/usage-tracker.js.map +1 -1
  39. package/dist/cjs/index.js +39 -202
  40. package/dist/cjs/index.js.map +1 -1
  41. package/dist/cjs/types/index.js +0 -8
  42. package/dist/cjs/types/index.js.map +1 -1
  43. package/dist/cjs/types/openai-augmentation.js +0 -49
  44. package/dist/cjs/types/openai-augmentation.js.map +1 -1
  45. package/dist/cjs/utils/constants.js +17 -20
  46. package/dist/cjs/utils/constants.js.map +1 -1
  47. package/dist/cjs/utils/error-handler.js +18 -14
  48. package/dist/cjs/utils/error-handler.js.map +1 -1
  49. package/dist/cjs/utils/metadata-builder.js +17 -16
  50. package/dist/cjs/utils/metadata-builder.js.map +1 -1
  51. package/dist/cjs/utils/provider-detection.js +25 -28
  52. package/dist/cjs/utils/provider-detection.js.map +1 -1
  53. package/dist/cjs/utils/trace-fields.js +115 -0
  54. package/dist/cjs/utils/trace-fields.js.map +1 -0
  55. package/dist/esm/core/client/index.js +6 -0
  56. package/dist/esm/core/client/index.js.map +1 -0
  57. package/dist/esm/core/client/manager.js +102 -0
  58. package/dist/esm/core/client/manager.js.map +1 -0
  59. package/dist/esm/core/config/azure-config.js +6 -18
  60. package/dist/esm/core/config/azure-config.js.map +1 -1
  61. package/dist/esm/core/config/index.js +5 -4
  62. package/dist/esm/core/config/index.js.map +1 -1
  63. package/dist/esm/core/config/loader.js +33 -13
  64. package/dist/esm/core/config/loader.js.map +1 -1
  65. package/dist/esm/core/config/manager.js +13 -7
  66. package/dist/esm/core/config/manager.js.map +1 -1
  67. package/dist/esm/core/config/validator.js +3 -44
  68. package/dist/esm/core/config/validator.js.map +1 -1
  69. package/dist/esm/core/middleware/index.js +8 -0
  70. package/dist/esm/core/middleware/index.js.map +1 -0
  71. package/dist/esm/core/middleware/interfaces.js +442 -0
  72. package/dist/esm/core/middleware/interfaces.js.map +1 -0
  73. package/dist/esm/core/middleware/revenium-client.js +115 -0
  74. package/dist/esm/core/middleware/revenium-client.js.map +1 -0
  75. package/dist/esm/core/providers/detector.js +43 -22
  76. package/dist/esm/core/providers/detector.js.map +1 -1
  77. package/dist/esm/core/providers/index.js +2 -2
  78. package/dist/esm/core/providers/index.js.map +1 -1
  79. package/dist/esm/core/tracking/api-client.js +20 -13
  80. package/dist/esm/core/tracking/api-client.js.map +1 -1
  81. package/dist/esm/core/tracking/index.js +4 -4
  82. package/dist/esm/core/tracking/index.js.map +1 -1
  83. package/dist/esm/core/tracking/payload-builder.js +142 -26
  84. package/dist/esm/core/tracking/payload-builder.js.map +1 -1
  85. package/dist/esm/core/tracking/usage-tracker.js +78 -20
  86. package/dist/esm/core/tracking/usage-tracker.js.map +1 -1
  87. package/dist/esm/index.js +9 -177
  88. package/dist/esm/index.js.map +1 -1
  89. package/dist/esm/types/index.js +2 -10
  90. package/dist/esm/types/index.js.map +1 -1
  91. package/dist/esm/types/openai-augmentation.js +0 -49
  92. package/dist/esm/types/openai-augmentation.js.map +1 -1
  93. package/dist/esm/utils/constants.js +16 -19
  94. package/dist/esm/utils/constants.js.map +1 -1
  95. package/dist/esm/utils/error-handler.js +19 -15
  96. package/dist/esm/utils/error-handler.js.map +1 -1
  97. package/dist/esm/utils/metadata-builder.js +17 -16
  98. package/dist/esm/utils/metadata-builder.js.map +1 -1
  99. package/dist/esm/utils/provider-detection.js +26 -29
  100. package/dist/esm/utils/provider-detection.js.map +1 -1
  101. package/dist/esm/utils/trace-fields.js +100 -0
  102. package/dist/esm/utils/trace-fields.js.map +1 -0
  103. package/dist/types/core/client/index.d.ts +6 -0
  104. package/dist/types/core/client/index.d.ts.map +1 -0
  105. package/dist/types/core/client/manager.d.ts +32 -0
  106. package/dist/types/core/client/manager.d.ts.map +1 -0
  107. package/dist/types/core/config/azure-config.d.ts +2 -2
  108. package/dist/types/core/config/azure-config.d.ts.map +1 -1
  109. package/dist/types/core/config/index.d.ts +4 -4
  110. package/dist/types/core/config/index.d.ts.map +1 -1
  111. package/dist/types/core/config/loader.d.ts +3 -1
  112. package/dist/types/core/config/loader.d.ts.map +1 -1
  113. package/dist/types/core/config/manager.d.ts +1 -1
  114. package/dist/types/core/config/manager.d.ts.map +1 -1
  115. package/dist/types/core/config/validator.d.ts +1 -12
  116. package/dist/types/core/config/validator.d.ts.map +1 -1
  117. package/dist/types/core/middleware/index.d.ts +8 -0
  118. package/dist/types/core/middleware/index.d.ts.map +1 -0
  119. package/dist/types/core/middleware/interfaces.d.ts +104 -0
  120. package/dist/types/core/middleware/interfaces.d.ts.map +1 -0
  121. package/dist/types/core/middleware/revenium-client.d.ts +64 -0
  122. package/dist/types/core/middleware/revenium-client.d.ts.map +1 -0
  123. package/dist/types/core/providers/detector.d.ts +9 -2
  124. package/dist/types/core/providers/detector.d.ts.map +1 -1
  125. package/dist/types/core/providers/index.d.ts +2 -2
  126. package/dist/types/core/providers/index.d.ts.map +1 -1
  127. package/dist/types/core/tracking/api-client.d.ts +1 -1
  128. package/dist/types/core/tracking/api-client.d.ts.map +1 -1
  129. package/dist/types/core/tracking/index.d.ts +4 -4
  130. package/dist/types/core/tracking/index.d.ts.map +1 -1
  131. package/dist/types/core/tracking/payload-builder.d.ts +5 -3
  132. package/dist/types/core/tracking/payload-builder.d.ts.map +1 -1
  133. package/dist/types/core/tracking/usage-tracker.d.ts +4 -2
  134. package/dist/types/core/tracking/usage-tracker.d.ts.map +1 -1
  135. package/dist/types/index.d.ts +11 -135
  136. package/dist/types/index.d.ts.map +1 -1
  137. package/dist/types/types/function-parameters.d.ts +91 -23
  138. package/dist/types/types/function-parameters.d.ts.map +1 -1
  139. package/dist/types/types/index.d.ts +53 -108
  140. package/dist/types/types/index.d.ts.map +1 -1
  141. package/dist/types/types/openai-augmentation.d.ts +4 -138
  142. package/dist/types/types/openai-augmentation.d.ts.map +1 -1
  143. package/dist/types/utils/constants.d.ts +7 -1
  144. package/dist/types/utils/constants.d.ts.map +1 -1
  145. package/dist/types/utils/error-handler.d.ts +2 -2
  146. package/dist/types/utils/error-handler.d.ts.map +1 -1
  147. package/dist/types/utils/metadata-builder.d.ts +2 -2
  148. package/dist/types/utils/metadata-builder.d.ts.map +1 -1
  149. package/dist/types/utils/provider-detection.d.ts +3 -3
  150. package/dist/types/utils/provider-detection.d.ts.map +1 -1
  151. package/dist/types/utils/trace-fields.d.ts +11 -0
  152. package/dist/types/utils/trace-fields.d.ts.map +1 -0
  153. package/examples/README.md +282 -198
  154. package/examples/azure/basic.ts +62 -0
  155. package/examples/azure/responses-basic.ts +45 -0
  156. package/examples/azure/responses-stream.ts +61 -0
  157. package/examples/azure/stream.ts +56 -0
  158. package/examples/getting_started.ts +31 -43
  159. package/examples/openai/basic.ts +45 -0
  160. package/examples/openai/metadata.ts +67 -0
  161. package/examples/openai/responses-basic.ts +44 -0
  162. package/examples/openai/responses-embed.ts +34 -0
  163. package/examples/openai/responses-streaming.ts +63 -0
  164. package/examples/openai/streaming.ts +59 -0
  165. package/package.json +23 -13
  166. package/dist/cjs/core/wrapper/index.js +0 -15
  167. package/dist/cjs/core/wrapper/index.js.map +0 -1
  168. package/dist/cjs/core/wrapper/instance-patcher.js +0 -202
  169. package/dist/cjs/core/wrapper/instance-patcher.js.map +0 -1
  170. package/dist/cjs/core/wrapper/request-handler.js +0 -317
  171. package/dist/cjs/core/wrapper/request-handler.js.map +0 -1
  172. package/dist/cjs/core/wrapper/stream-wrapper.js +0 -82
  173. package/dist/cjs/core/wrapper/stream-wrapper.js.map +0 -1
  174. package/dist/cjs/utils/azure-model-resolver.js +0 -211
  175. package/dist/cjs/utils/azure-model-resolver.js.map +0 -1
  176. package/dist/cjs/utils/request-handler-factory.js +0 -185
  177. package/dist/cjs/utils/request-handler-factory.js.map +0 -1
  178. package/dist/esm/core/wrapper/index.js +0 -9
  179. package/dist/esm/core/wrapper/index.js.map +0 -1
  180. package/dist/esm/core/wrapper/instance-patcher.js +0 -199
  181. package/dist/esm/core/wrapper/instance-patcher.js.map +0 -1
  182. package/dist/esm/core/wrapper/request-handler.js +0 -310
  183. package/dist/esm/core/wrapper/request-handler.js.map +0 -1
  184. package/dist/esm/core/wrapper/stream-wrapper.js +0 -79
  185. package/dist/esm/core/wrapper/stream-wrapper.js.map +0 -1
  186. package/dist/esm/utils/azure-model-resolver.js +0 -204
  187. package/dist/esm/utils/azure-model-resolver.js.map +0 -1
  188. package/dist/esm/utils/request-handler-factory.js +0 -146
  189. package/dist/esm/utils/request-handler-factory.js.map +0 -1
  190. package/dist/types/core/wrapper/index.d.ts +0 -8
  191. package/dist/types/core/wrapper/index.d.ts.map +0 -1
  192. package/dist/types/core/wrapper/instance-patcher.d.ts +0 -33
  193. package/dist/types/core/wrapper/instance-patcher.d.ts.map +0 -1
  194. package/dist/types/core/wrapper/request-handler.d.ts +0 -29
  195. package/dist/types/core/wrapper/request-handler.d.ts.map +0 -1
  196. package/dist/types/core/wrapper/stream-wrapper.d.ts +0 -13
  197. package/dist/types/core/wrapper/stream-wrapper.d.ts.map +0 -1
  198. package/dist/types/utils/azure-model-resolver.d.ts +0 -41
  199. package/dist/types/utils/azure-model-resolver.d.ts.map +0 -1
  200. package/dist/types/utils/request-handler-factory.d.ts +0 -81
  201. package/dist/types/utils/request-handler-factory.d.ts.map +0 -1
  202. package/examples/azure-basic.ts +0 -206
  203. package/examples/azure-responses-basic.ts +0 -233
  204. package/examples/azure-responses-streaming.ts +0 -255
  205. package/examples/azure-streaming.ts +0 -209
  206. package/examples/openai-basic.ts +0 -147
  207. package/examples/openai-function-calling.ts +0 -259
  208. package/examples/openai-responses-basic.ts +0 -212
  209. package/examples/openai-responses-streaming.ts +0 -232
  210. package/examples/openai-streaming.ts +0 -172
  211. package/examples/openai-vision.ts +0 -289
  212. package/src/core/config/azure-config.ts +0 -72
  213. package/src/core/config/index.ts +0 -23
  214. package/src/core/config/loader.ts +0 -66
  215. package/src/core/config/manager.ts +0 -95
  216. package/src/core/config/validator.ts +0 -89
  217. package/src/core/providers/detector.ts +0 -159
  218. package/src/core/providers/index.ts +0 -16
  219. package/src/core/tracking/api-client.ts +0 -78
  220. package/src/core/tracking/index.ts +0 -21
  221. package/src/core/tracking/payload-builder.ts +0 -137
  222. package/src/core/tracking/usage-tracker.ts +0 -189
  223. package/src/core/wrapper/index.ts +0 -9
  224. package/src/core/wrapper/instance-patcher.ts +0 -288
  225. package/src/core/wrapper/request-handler.ts +0 -423
  226. package/src/core/wrapper/stream-wrapper.ts +0 -100
  227. package/src/index.ts +0 -360
  228. package/src/types/function-parameters.ts +0 -251
  229. package/src/types/index.ts +0 -310
  230. package/src/types/openai-augmentation.ts +0 -232
  231. package/src/types/responses-api.ts +0 -308
  232. package/src/utils/azure-model-resolver.ts +0 -220
  233. package/src/utils/constants.ts +0 -21
  234. package/src/utils/error-handler.ts +0 -251
  235. package/src/utils/metadata-builder.ts +0 -228
  236. package/src/utils/provider-detection.ts +0 -257
  237. package/src/utils/request-handler-factory.ts +0 -285
  238. package/src/utils/stop-reason-mapper.ts +0 -78
  239. package/src/utils/type-guards.ts +0 -202
  240. package/src/utils/url-builder.ts +0 -68
@@ -1,202 +0,0 @@
1
- /**
2
- * Type Guards Module
3
- *
4
- * Runtime type checking utilities to ensure type safety.
5
- * These functions validate that objects match expected interfaces.
6
- */
7
-
8
- import {
9
- OpenAIChatResponse,
10
- OpenAIEmbeddingResponse,
11
- OpenAIChatRequest,
12
- OpenAIEmbeddingRequest,
13
- OpenAIClientInstance,
14
- StreamChunk,
15
- } from '../types/function-parameters.js';
16
-
17
- /**
18
- * Type guard for OpenAI chat response
19
- */
20
- export function isOpenAIChatResponse(obj: unknown): obj is OpenAIChatResponse {
21
- if (!obj || typeof obj !== 'object') return false;
22
-
23
- const response = obj as Record<string, unknown>;
24
- return (
25
- typeof response.id === 'string' &&
26
- typeof response.model === 'string' &&
27
- typeof response.usage === 'object' &&
28
- response.usage !== null &&
29
- typeof (response.usage as Record<string, unknown>).prompt_tokens === 'number' &&
30
- typeof (response.usage as Record<string, unknown>).completion_tokens === 'number' &&
31
- typeof (response.usage as Record<string, unknown>).total_tokens === 'number' &&
32
- Array.isArray(response.choices)
33
- );
34
- }
35
-
36
- /**
37
- * Type guard for OpenAI embedding response
38
- */
39
- export function isOpenAIEmbeddingResponse(obj: unknown): obj is OpenAIEmbeddingResponse {
40
- if (!obj || typeof obj !== 'object') return false;
41
-
42
- const response = obj as Record<string, unknown>;
43
- return (
44
- typeof response.model === 'string' &&
45
- typeof response.usage === 'object' &&
46
- response.usage !== null &&
47
- typeof (response.usage as Record<string, unknown>).prompt_tokens === 'number' &&
48
- typeof (response.usage as Record<string, unknown>).total_tokens === 'number' &&
49
- Array.isArray(response.data) &&
50
- typeof response.object === 'string'
51
- );
52
- }
53
-
54
- /**
55
- * Type guard for OpenAI chat request
56
- */
57
- export function isOpenAIChatRequest(obj: unknown): obj is OpenAIChatRequest {
58
- if (!obj || typeof obj !== 'object') return false;
59
-
60
- const request = obj as Record<string, unknown>;
61
- return (
62
- typeof request.model === 'string' &&
63
- Array.isArray(request.messages) &&
64
- request.messages.length > 0 &&
65
- request.messages.every((msg: unknown) => {
66
- if (!msg || typeof msg !== 'object') return false;
67
- const message = msg as Record<string, unknown>;
68
- return typeof message.role === 'string' && typeof message.content === 'string';
69
- })
70
- );
71
- }
72
-
73
- /**
74
- * Type guard for OpenAI embedding request
75
- */
76
- export function isOpenAIEmbeddingRequest(obj: unknown): obj is OpenAIEmbeddingRequest {
77
- if (!obj || typeof obj !== 'object') return false;
78
-
79
- const request = obj as Record<string, unknown>;
80
- return (
81
- typeof request.model === 'string' &&
82
- (typeof request.input === 'string' || Array.isArray(request.input))
83
- );
84
- }
85
-
86
- /**
87
- * Type guard for OpenAI client instance
88
- */
89
- export function isOpenAIClientInstance(obj: unknown): obj is OpenAIClientInstance {
90
- if (!obj || typeof obj !== 'object') return false;
91
-
92
- const client = obj as Record<string, unknown>;
93
-
94
- // Must have at least one of the expected OpenAI client properties
95
- const hasBaseURL = typeof client.baseURL === 'string' || client.baseURL instanceof URL;
96
- const hasChat = typeof client.chat === 'object' && client.chat !== null;
97
- const hasEmbeddings = typeof client.embeddings === 'object' && client.embeddings !== null;
98
-
99
- // A valid OpenAI client should have at least one of these properties
100
- return hasBaseURL || hasChat || hasEmbeddings;
101
- }
102
-
103
- /**
104
- * Type guard for stream chunk
105
- */
106
- export function isStreamChunk(obj: unknown): obj is StreamChunk {
107
- if (!obj || typeof obj !== 'object') return false;
108
- const chunk = obj as Record<string, unknown>;
109
- return typeof chunk.id === 'string' && typeof chunk.model === 'string';
110
- }
111
-
112
- /**
113
- * Type guard for usage object
114
- */
115
- export function hasValidUsage(
116
- obj: unknown
117
- ): obj is { usage: { prompt_tokens: number; total_tokens: number } } {
118
- if (!obj || typeof obj !== 'object') return false;
119
-
120
- const response = obj as Record<string, unknown>;
121
- return (
122
- typeof response.usage === 'object' &&
123
- response.usage !== null &&
124
- typeof (response.usage as Record<string, unknown>).prompt_tokens === 'number' &&
125
- typeof (response.usage as Record<string, unknown>).total_tokens === 'number'
126
- );
127
- }
128
-
129
- /**
130
- * Type guard for function
131
- */
132
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
- export function isFunction(obj: unknown): obj is any {
134
- return typeof obj === 'function';
135
- }
136
-
137
- /**
138
- * Type guard for string
139
- */
140
- export function isString(obj: unknown): obj is string {
141
- return typeof obj === 'string';
142
- }
143
-
144
- /**
145
- * Type guard for number
146
- */
147
- export function isNumber(obj: unknown): obj is number {
148
- return typeof obj === 'number' && !isNaN(obj);
149
- }
150
-
151
- /**
152
- * Type guard for boolean
153
- */
154
- export function isBoolean(obj: unknown): obj is boolean {
155
- return typeof obj === 'boolean';
156
- }
157
-
158
- /**
159
- * Type guard for object
160
- */
161
- export function isObject(obj: unknown): obj is Record<string, unknown> {
162
- return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
163
- }
164
-
165
- /**
166
- * Type guard for array
167
- */
168
- export function isArray(obj: unknown): obj is unknown[] {
169
- return Array.isArray(obj);
170
- }
171
-
172
- /**
173
- * Safe property access with type checking
174
- */
175
- export function safeGetProperty<T>(
176
- obj: unknown,
177
- property: string,
178
- typeGuard: (value: unknown) => value is T
179
- ): T | undefined {
180
- if (!isObject(obj)) return undefined;
181
-
182
- const value = obj[property];
183
- return typeGuard(value) ? value : undefined;
184
- }
185
-
186
- /**
187
- * Safe nested property access
188
- */
189
- export function safeGetNestedProperty<T>(
190
- obj: unknown,
191
- path: string[],
192
- typeGuard: (value: unknown) => value is T
193
- ): T | undefined {
194
- let current = obj;
195
-
196
- for (const property of path) {
197
- if (!isObject(current)) return undefined;
198
- current = current[property];
199
- }
200
-
201
- return typeGuard(current) ? current : undefined;
202
- }
@@ -1,68 +0,0 @@
1
- /**
2
- * URL Builder Utilities
3
- *
4
- * Centralized URL construction logic to eliminate nested conditionals.
5
- * Replaces the complex URL building logic from tracking.ts.
6
- */
7
-
8
- /**
9
- * Build Revenium API URL with proper path handling
10
- *
11
- * This function intelligently handles base URLs and ensures the correct path structure.
12
- * Logic:
13
- * - If URL has /meter/v2 → append endpoint as-is
14
- * - If URL has /meter only → append /v2 and endpoint
15
- * - If URL has neither → append /meter/v2 and endpoint
16
- *
17
- * Examples:
18
- * - 'https://api.revenium.ai' + '/ai/completions' → 'https://api.revenium.ai/meter/v2/ai/completions'
19
- * - 'https://api.revenium.ai/meter' + '/ai/completions' → 'https://api.revenium.ai/meter/v2/ai/completions'
20
- * - 'https://api.revenium.ai/meter/v2' + '/ai/completions' → 'https://api.revenium.ai/meter/v2/ai/completions'
21
- *
22
- * @param baseUrl - The base URL from configuration (may include /meter or /meter/v2)
23
- * @param endpoint - The API endpoint to append (e.g., '/ai/completions')
24
- * @returns Complete URL for the API call
25
- */
26
- export function buildReveniumUrl(baseUrl: string, endpoint: string): string {
27
- // Normalize the base URL by removing trailing slashes
28
- let normalizedBase = baseUrl.replace(/\/+$/, '');
29
-
30
- // Check if /meter/v2 is already at the end
31
- const hasMeterV2AtEnd = /\/meter\/v2$/i.test(normalizedBase);
32
- if (hasMeterV2AtEnd) {
33
- // Already has /meter/v2, just append endpoint
34
- return `${normalizedBase}${endpoint}`;
35
- }
36
-
37
- // Check if /meter is at the end (but not /meter/v2)
38
- const hasMeterAtEnd = /\/meter$/i.test(normalizedBase);
39
- if (hasMeterAtEnd) {
40
- // Has /meter but not /v2, append /v2 and endpoint
41
- return `${normalizedBase}/v2${endpoint}`;
42
- }
43
-
44
- // Check if /v2 is at the end (without /meter)
45
- const hasV2AtEnd = /\/v2$/i.test(normalizedBase);
46
- if (hasV2AtEnd) {
47
- // Has /v2 but not /meter, append endpoint as-is
48
- return `${normalizedBase}${endpoint}`;
49
- }
50
-
51
- // Has neither /meter nor /v2, append /meter/v2 and endpoint
52
- return `${normalizedBase}/meter/v2${endpoint}`;
53
- }
54
-
55
- /**
56
- * Validate URL format
57
- *
58
- * @param url - URL to validate
59
- * @returns true if valid, false otherwise
60
- */
61
- export function isValidUrl(url: string): boolean {
62
- try {
63
- new URL(url);
64
- return true;
65
- } catch {
66
- return false;
67
- }
68
- }