@tonyclaw/llm-inspector 1.6.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 (286) hide show
  1. package/.output/nitro.json +17 -0
  2. package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
  3. package/.output/public/assets/index-B3RwBPLW.css +1 -0
  4. package/.output/public/assets/index-s4lwsWvq.js +97 -0
  5. package/.output/public/assets/main-Cp8AM0Pa.js +17 -0
  6. package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
  7. package/.output/public/assets/qwen-CONDcHqt.png +0 -0
  8. package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
  9. package/.output/server/_chunks/ssr-renderer.mjs +17 -0
  10. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
  11. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
  12. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +1 -0
  13. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
  14. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -0
  15. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
  16. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
  17. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
  18. package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
  19. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
  20. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
  21. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
  22. package/.output/server/_libs/ajv-formats.mjs +330 -0
  23. package/.output/server/_libs/ajv.mjs +11444 -0
  24. package/.output/server/_libs/aria-hidden.mjs +122 -0
  25. package/.output/server/_libs/atomically.mjs +152 -0
  26. package/.output/server/_libs/bail.mjs +8 -0
  27. package/.output/server/_libs/character-entities.mjs +2130 -0
  28. package/.output/server/_libs/class-variance-authority.mjs +44 -0
  29. package/.output/server/_libs/clsx.mjs +16 -0
  30. package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
  31. package/.output/server/_libs/conf.mjs +635 -0
  32. package/.output/server/_libs/cookie-es.mjs +58 -0
  33. package/.output/server/_libs/core-util-is.mjs +75 -0
  34. package/.output/server/_libs/croner.mjs +1 -0
  35. package/.output/server/_libs/crossws.mjs +1 -0
  36. package/.output/server/_libs/debounce-fn.mjs +69 -0
  37. package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
  38. package/.output/server/_libs/detect-node-es.mjs +1 -0
  39. package/.output/server/_libs/devlop.mjs +8 -0
  40. package/.output/server/_libs/dot-prop.mjs +265 -0
  41. package/.output/server/_libs/env-paths.mjs +57 -0
  42. package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
  43. package/.output/server/_libs/extend.mjs +97 -0
  44. package/.output/server/_libs/fast-deep-equal.mjs +38 -0
  45. package/.output/server/_libs/fast-uri.mjs +812 -0
  46. package/.output/server/_libs/floating-ui__core.mjs +725 -0
  47. package/.output/server/_libs/floating-ui__dom.mjs +622 -0
  48. package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
  49. package/.output/server/_libs/floating-ui__utils.mjs +320 -0
  50. package/.output/server/_libs/get-nonce.mjs +9 -0
  51. package/.output/server/_libs/h3-v2.mjs +276 -0
  52. package/.output/server/_libs/h3.mjs +400 -0
  53. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
  54. package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
  55. package/.output/server/_libs/hookable.mjs +1 -0
  56. package/.output/server/_libs/html-url-attributes.mjs +26 -0
  57. package/.output/server/_libs/immediate.mjs +74 -0
  58. package/.output/server/_libs/inherits.mjs +50 -0
  59. package/.output/server/_libs/inline-style-parser.mjs +142 -0
  60. package/.output/server/_libs/is-plain-obj.mjs +10 -0
  61. package/.output/server/_libs/isarray.mjs +14 -0
  62. package/.output/server/_libs/isbot.mjs +20 -0
  63. package/.output/server/_libs/json-schema-traverse.mjs +180 -0
  64. package/.output/server/_libs/jszip.mjs +3049 -0
  65. package/.output/server/_libs/lie.mjs +273 -0
  66. package/.output/server/_libs/lucide-react.mjs +368 -0
  67. package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
  68. package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
  69. package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
  70. package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
  71. package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
  72. package/.output/server/_libs/micromark-factory-label.mjs +63 -0
  73. package/.output/server/_libs/micromark-factory-space.mjs +24 -0
  74. package/.output/server/_libs/micromark-factory-title.mjs +65 -0
  75. package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
  76. package/.output/server/_libs/micromark-util-character.mjs +44 -0
  77. package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
  78. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
  79. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
  80. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
  81. package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
  82. package/.output/server/_libs/micromark-util-encode.mjs +1 -0
  83. package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
  84. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
  85. package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
  86. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
  87. package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
  88. package/.output/server/_libs/micromark.mjs +906 -0
  89. package/.output/server/_libs/mimic-function.mjs +47 -0
  90. package/.output/server/_libs/ohash.mjs +1 -0
  91. package/.output/server/_libs/pako.mjs +4223 -0
  92. package/.output/server/_libs/process-nextick-args.mjs +48 -0
  93. package/.output/server/_libs/property-information.mjs +1209 -0
  94. package/.output/server/_libs/radix-ui.mjs +1 -0
  95. package/.output/server/_libs/radix-ui__number.mjs +6 -0
  96. package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
  97. package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
  98. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
  99. package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
  100. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
  101. package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
  102. package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
  103. package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
  104. package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
  105. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
  106. package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
  107. package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
  108. package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
  109. package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
  110. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
  111. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
  112. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
  113. package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
  114. package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
  115. package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
  116. package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
  117. package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
  118. package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
  119. package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
  120. package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
  121. package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
  122. package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
  123. package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
  124. package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
  125. package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
  126. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
  127. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
  128. package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
  129. package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
  130. package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
  131. package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
  132. package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
  133. package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
  134. package/.output/server/_libs/radix-ui__react-toast.mjs +1 -0
  135. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
  136. package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
  137. package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
  138. package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
  139. package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
  140. package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
  141. package/.output/server/_libs/react-dom.mjs +9935 -0
  142. package/.output/server/_libs/react-markdown.mjs +147 -0
  143. package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
  144. package/.output/server/_libs/react-remove-scroll.mjs +328 -0
  145. package/.output/server/_libs/react-style-singleton.mjs +69 -0
  146. package/.output/server/_libs/react.mjs +515 -0
  147. package/.output/server/_libs/readable-stream.mjs +1518 -0
  148. package/.output/server/_libs/remark-parse.mjs +19 -0
  149. package/.output/server/_libs/remark-rehype.mjs +21 -0
  150. package/.output/server/_libs/rou3.mjs +8 -0
  151. package/.output/server/_libs/safe-buffer.mjs +64 -0
  152. package/.output/server/_libs/semver.mjs +1984 -0
  153. package/.output/server/_libs/seroval-plugins.mjs +58 -0
  154. package/.output/server/_libs/seroval.mjs +1765 -0
  155. package/.output/server/_libs/setimmediate.mjs +1 -0
  156. package/.output/server/_libs/space-separated-tokens.mjs +6 -0
  157. package/.output/server/_libs/srvx.mjs +334 -0
  158. package/.output/server/_libs/stubborn-fs.mjs +91 -0
  159. package/.output/server/_libs/stubborn-utils.mjs +66 -0
  160. package/.output/server/_libs/style-to-js.mjs +72 -0
  161. package/.output/server/_libs/style-to-object.mjs +38 -0
  162. package/.output/server/_libs/tailwind-merge.mjs +3010 -0
  163. package/.output/server/_libs/tanstack__history.mjs +217 -0
  164. package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
  165. package/.output/server/_libs/tanstack__react-store.mjs +1 -0
  166. package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
  167. package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
  168. package/.output/server/_libs/tanstack__store.mjs +1 -0
  169. package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
  170. package/.output/server/_libs/tiny-invariant.mjs +12 -0
  171. package/.output/server/_libs/tiny-warning.mjs +5 -0
  172. package/.output/server/_libs/trim-lines.mjs +41 -0
  173. package/.output/server/_libs/trough.mjs +85 -0
  174. package/.output/server/_libs/tslib.mjs +576 -0
  175. package/.output/server/_libs/ufo.mjs +54 -0
  176. package/.output/server/_libs/uint8array-extras.mjs +69 -0
  177. package/.output/server/_libs/unctx.mjs +1 -0
  178. package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
  179. package/.output/server/_libs/unified.mjs +661 -0
  180. package/.output/server/_libs/unist-util-is.mjs +100 -0
  181. package/.output/server/_libs/unist-util-position.mjs +27 -0
  182. package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
  183. package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
  184. package/.output/server/_libs/unist-util-visit.mjs +24 -0
  185. package/.output/server/_libs/unstorage.mjs +1 -0
  186. package/.output/server/_libs/use-callback-ref.mjs +66 -0
  187. package/.output/server/_libs/use-sidecar.mjs +106 -0
  188. package/.output/server/_libs/use-sync-external-store.mjs +1 -0
  189. package/.output/server/_libs/util-deprecate.mjs +12 -0
  190. package/.output/server/_libs/vfile-message.mjs +138 -0
  191. package/.output/server/_libs/vfile.mjs +467 -0
  192. package/.output/server/_libs/when-exit.mjs +53 -0
  193. package/.output/server/_libs/zod.mjs +4460 -0
  194. package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
  195. package/.output/server/_ssr/index.mjs +1176 -0
  196. package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
  197. package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  198. package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
  199. package/.output/server/index.mjs +393 -0
  200. package/README.md +196 -0
  201. package/package.json +91 -0
  202. package/src/assets/logos/alibaba.svg +1 -0
  203. package/src/assets/logos/anthropic.svg +1 -0
  204. package/src/assets/logos/deepseek.svg +1 -0
  205. package/src/assets/logos/minimax.jpeg +0 -0
  206. package/src/assets/logos/openai.svg +1 -0
  207. package/src/assets/logos/qwen.png +0 -0
  208. package/src/assets/logos/zhipuai.svg +219 -0
  209. package/src/cli.ts +68 -0
  210. package/src/components/ProxyViewer.tsx +325 -0
  211. package/src/components/ProxyViewerContainer.tsx +211 -0
  212. package/src/components/providers/ProviderCard.tsx +186 -0
  213. package/src/components/providers/ProviderForm.tsx +259 -0
  214. package/src/components/providers/ProviderLogo.tsx +111 -0
  215. package/src/components/providers/ProvidersPanel.tsx +259 -0
  216. package/src/components/providers/SettingsDialog.tsx +39 -0
  217. package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
  218. package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
  219. package/src/components/proxy-viewer/LogEntry.tsx +225 -0
  220. package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
  221. package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
  222. package/src/components/proxy-viewer/ResponseView.tsx +161 -0
  223. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
  224. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
  225. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
  226. package/src/components/proxy-viewer/formats/index.tsx +24 -0
  227. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
  228. package/src/components/proxy-viewer/index.ts +8 -0
  229. package/src/components/ui/badge.tsx +47 -0
  230. package/src/components/ui/button.tsx +47 -0
  231. package/src/components/ui/collapsible.tsx +21 -0
  232. package/src/components/ui/dialog.tsx +129 -0
  233. package/src/components/ui/json-viewer.tsx +464 -0
  234. package/src/components/ui/scroll-area.tsx +54 -0
  235. package/src/components/ui/select.tsx +178 -0
  236. package/src/components/ui/separator.tsx +28 -0
  237. package/src/components/ui/tabs.tsx +88 -0
  238. package/src/components/ui/tooltip.tsx +51 -0
  239. package/src/index.css +11 -0
  240. package/src/lib/export-logs.ts +51 -0
  241. package/src/lib/utils.ts +22 -0
  242. package/src/proxy/chunkStorage.ts +118 -0
  243. package/src/proxy/constants.ts +36 -0
  244. package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
  245. package/src/proxy/formats/anthropic/handler.ts +74 -0
  246. package/src/proxy/formats/anthropic/index.ts +14 -0
  247. package/src/proxy/formats/anthropic/register.ts +4 -0
  248. package/src/proxy/formats/anthropic/schemas.ts +217 -0
  249. package/src/proxy/formats/anthropic/stream.ts +167 -0
  250. package/src/proxy/formats/handler.ts +46 -0
  251. package/src/proxy/formats/index.ts +12 -0
  252. package/src/proxy/formats/jsonSchema.ts +24 -0
  253. package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
  254. package/src/proxy/formats/openai/handler.ts +70 -0
  255. package/src/proxy/formats/openai/index.ts +25 -0
  256. package/src/proxy/formats/openai/provider.ts +50 -0
  257. package/src/proxy/formats/openai/register.ts +4 -0
  258. package/src/proxy/formats/openai/schemas.ts +150 -0
  259. package/src/proxy/formats/openai/stream.ts +153 -0
  260. package/src/proxy/formats/protocol.ts +50 -0
  261. package/src/proxy/formats/providerRegistry.ts +51 -0
  262. package/src/proxy/formats/providers/index.ts +3 -0
  263. package/src/proxy/formats/registry.ts +61 -0
  264. package/src/proxy/handler.ts +389 -0
  265. package/src/proxy/logIndex.ts +187 -0
  266. package/src/proxy/logger.ts +99 -0
  267. package/src/proxy/providers.ts +234 -0
  268. package/src/proxy/schemas.ts +160 -0
  269. package/src/proxy/socketTracker.ts +158 -0
  270. package/src/proxy/store.ts +386 -0
  271. package/src/router.tsx +16 -0
  272. package/src/routes/__root.tsx +38 -0
  273. package/src/routes/api/config.paths.ts +14 -0
  274. package/src/routes/api/health.ts +11 -0
  275. package/src/routes/api/logs.$id.chunks.ts +36 -0
  276. package/src/routes/api/logs.$id.replay.ts +262 -0
  277. package/src/routes/api/logs.$id.ts +22 -0
  278. package/src/routes/api/logs.stream.ts +64 -0
  279. package/src/routes/api/logs.ts +30 -0
  280. package/src/routes/api/models.ts +10 -0
  281. package/src/routes/api/providers.$providerId.ts +45 -0
  282. package/src/routes/api/providers.ts +37 -0
  283. package/src/routes/api/sessions.ts +10 -0
  284. package/src/routes/index.tsx +6 -0
  285. package/src/routes/proxy/$.ts +15 -0
  286. package/styles/globals.css +121 -0
@@ -0,0 +1,75 @@
1
+ import type { CapturedLog } from "../../schemas";
2
+ import type { ProviderProtocol } from "../protocol";
3
+ import { registry } from "../providers";
4
+ import { extractAnthropicStream } from "./stream";
5
+
6
+ const DEFAULT_UPSTREAM = "https://api.anthropic.com";
7
+
8
+ // Default routes - MiniMax is hardcoded here
9
+ const DEFAULT_ROUTES: Record<string, string> = {
10
+ MiniMax: "https://api.minimaxi.com/anthropic",
11
+ };
12
+
13
+ function getUpstreamForModel(model: string): string {
14
+ let bestPrefix = "";
15
+ let bestUrl = DEFAULT_UPSTREAM;
16
+
17
+ for (const [prefix, url] of Object.entries(DEFAULT_ROUTES)) {
18
+ if (model.startsWith(prefix) && prefix.length > bestPrefix.length) {
19
+ bestPrefix = prefix;
20
+ bestUrl = url;
21
+ }
22
+ }
23
+
24
+ return bestUrl;
25
+ }
26
+
27
+ /**
28
+ * Anthropic Provider Implementation
29
+ */
30
+ export const anthropicProvider: ProviderProtocol = {
31
+ name: "anthropic",
32
+
33
+ matches(model: string): boolean {
34
+ const modelLower = model.toLowerCase();
35
+
36
+ // Don't match OpenAI models
37
+ if (modelLower.startsWith("openai-")) {
38
+ return false;
39
+ }
40
+
41
+ // Match Anthropic models
42
+ if (modelLower.includes("claude") || modelLower.includes("anthropic")) {
43
+ return true;
44
+ }
45
+
46
+ // Match known route prefixes (e.g., MiniMax)
47
+ for (const prefix of Object.keys(DEFAULT_ROUTES)) {
48
+ if (modelLower.startsWith(prefix.toLowerCase())) {
49
+ return true;
50
+ }
51
+ }
52
+
53
+ // Default to Anthropic for unknown models (legacy behavior)
54
+ return true;
55
+ },
56
+
57
+ getUpstreamBase(_isChatCompletions: boolean, providerConfig?: { baseUrl?: string }): string {
58
+ if (providerConfig?.baseUrl !== undefined) {
59
+ return providerConfig.baseUrl;
60
+ }
61
+ return DEFAULT_UPSTREAM;
62
+ },
63
+
64
+ extractStream(
65
+ raw: string,
66
+ log: CapturedLog,
67
+ fallbackModel?: string,
68
+ collectChunks?: boolean,
69
+ ): string {
70
+ return extractAnthropicStream(raw, log, fallbackModel, collectChunks);
71
+ },
72
+ };
73
+
74
+ // Register the provider
75
+ registry.register(anthropicProvider);
@@ -0,0 +1,74 @@
1
+ import type { FormatHandler, ParsedRequest } from "../handler";
2
+ import type { CapturedLog, TokenUsage } from "../../schemas";
3
+ import { AnthropicRequestSchema, AnthropicResponseSchema } from "./schemas";
4
+ import { extractAnthropicStream } from "./stream";
5
+
6
+ export const AnthropicFormatHandler: FormatHandler = {
7
+ format: "anthropic",
8
+
9
+ parseRequest(rawBody: string, headers?: Headers): ParsedRequest | null {
10
+ try {
11
+ const json: unknown = JSON.parse(rawBody);
12
+ const result = AnthropicRequestSchema.safeParse(json);
13
+ if (result.success) {
14
+ return {
15
+ model: result.data.model,
16
+ sessionId: result.data.metadata?.user_id ?? headers?.get("x-session-affinity") ?? null,
17
+ };
18
+ }
19
+ return null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ },
24
+
25
+ extractTokens(responseBody: string): TokenUsage {
26
+ try {
27
+ const json: unknown = JSON.parse(responseBody);
28
+ const result = AnthropicResponseSchema.safeParse(json);
29
+ if (result.success) {
30
+ return {
31
+ inputTokens: result.data.usage.input_tokens,
32
+ outputTokens: result.data.usage.output_tokens,
33
+ cacheCreationInputTokens: result.data.usage.cache_creation_input_tokens ?? null,
34
+ cacheReadInputTokens: result.data.usage.cache_read_input_tokens ?? null,
35
+ };
36
+ }
37
+ } catch {
38
+ // Fall through
39
+ }
40
+ return {
41
+ inputTokens: null,
42
+ outputTokens: null,
43
+ cacheCreationInputTokens: null,
44
+ cacheReadInputTokens: null,
45
+ };
46
+ },
47
+
48
+ extractStream(
49
+ raw: string,
50
+ log: CapturedLog,
51
+ fallbackModel?: string,
52
+ collectChunks?: boolean,
53
+ ): string {
54
+ return extractAnthropicStream(raw, log, fallbackModel, collectChunks);
55
+ },
56
+
57
+ detectFormat(rawBody: string | null): boolean {
58
+ if (rawBody === null) return false;
59
+ try {
60
+ const json: unknown = JSON.parse(rawBody);
61
+ if (typeof json === "object" && json !== null && !Array.isArray(json)) {
62
+ const keys = Object.keys(json);
63
+ if (keys.includes("model") && keys.includes("messages")) {
64
+ if (keys.includes("system") || keys.includes("tools")) {
65
+ return true;
66
+ }
67
+ }
68
+ }
69
+ return false;
70
+ } catch {
71
+ return false;
72
+ }
73
+ },
74
+ };
@@ -0,0 +1,14 @@
1
+ // Register format handler
2
+ import "./register";
3
+
4
+ export { AnthropicFormatHandler } from "./handler";
5
+ export {
6
+ AnthropicRequestSchema,
7
+ AnthropicResponseSchema,
8
+ SseEventSchema,
9
+ type AnthropicRequest,
10
+ type AnthropicResponse,
11
+ type ResponseContentBlockType,
12
+ } from "./schemas";
13
+ export { extractAnthropicStream } from "./stream";
14
+ export { anthropicProvider } from "./anthropicProvider";
@@ -0,0 +1,4 @@
1
+ import { formatRegistry } from "../registry";
2
+ import { AnthropicFormatHandler } from "./handler";
3
+
4
+ formatRegistry.register(AnthropicFormatHandler);
@@ -0,0 +1,217 @@
1
+ import { z } from "zod";
2
+ import { JsonValueSchema } from "../jsonSchema";
3
+
4
+ const CacheControl = z.object({
5
+ type: z.string(),
6
+ ttl: z.string().optional(),
7
+ scope: z.string().optional(),
8
+ });
9
+
10
+ const TextContentBlock = z.object({
11
+ type: z.literal("text"),
12
+ text: z.string(),
13
+ cache_control: CacheControl.optional(),
14
+ });
15
+
16
+ const ThinkingContentBlock = z.object({
17
+ type: z.literal("thinking"),
18
+ thinking: z.string(),
19
+ signature: z.string().optional(),
20
+ });
21
+
22
+ const ImageSourceBlock = z.object({
23
+ type: z.literal("base64"),
24
+ media_type: z.string(),
25
+ data: z.string(),
26
+ });
27
+
28
+ const ImageContentBlock = z.object({
29
+ type: z.literal("image"),
30
+ source: ImageSourceBlock,
31
+ });
32
+
33
+ const ToolUseContentBlock = z.object({
34
+ type: z.literal("tool_use"),
35
+ id: z.string(),
36
+ name: z.string(),
37
+ input: z.record(z.string(), JsonValueSchema),
38
+ });
39
+
40
+ const ToolResultContentItem = z.discriminatedUnion("type", [TextContentBlock, ImageContentBlock]);
41
+
42
+ const ToolResultContentBlock = z.object({
43
+ type: z.literal("tool_result"),
44
+ tool_use_id: z.string().optional(),
45
+ content: z.union([z.string(), z.array(ToolResultContentItem)]),
46
+ is_error: z.boolean().optional(),
47
+ });
48
+
49
+ const ContentBlock = z.discriminatedUnion("type", [
50
+ TextContentBlock,
51
+ ThinkingContentBlock,
52
+ ImageContentBlock,
53
+ ToolUseContentBlock,
54
+ ToolResultContentBlock,
55
+ ]);
56
+
57
+ const MessageContent = z.union([z.string(), z.array(ContentBlock)]);
58
+
59
+ const Message = z.object({
60
+ role: z.enum(["user", "assistant"]),
61
+ content: MessageContent,
62
+ });
63
+
64
+ const SystemBlock = z.object({
65
+ type: z.literal("text"),
66
+ text: z.string(),
67
+ cache_control: CacheControl.optional(),
68
+ });
69
+
70
+ const InputSchema = z.object({
71
+ type: z.string(),
72
+ properties: z.record(z.string(), z.record(z.string(), JsonValueSchema)).optional(),
73
+ required: z.array(z.string()).optional(),
74
+ additionalProperties: z.boolean().optional(),
75
+ $schema: z.string().optional(),
76
+ });
77
+
78
+ const ToolDefinition = z.object({
79
+ name: z.string(),
80
+ description: z.string().optional(),
81
+ input_schema: InputSchema.optional(),
82
+ cache_control: CacheControl.optional(),
83
+ });
84
+
85
+ const ThinkingConfig = z.discriminatedUnion("type", [
86
+ z.object({ type: z.literal("enabled"), budget_tokens: z.number() }),
87
+ z.object({ type: z.literal("disabled") }),
88
+ z.object({ type: z.literal("adaptive") }),
89
+ ]);
90
+
91
+ export const AnthropicRequestSchema = z.object({
92
+ model: z.string(),
93
+ messages: z.array(Message),
94
+ system: z.array(SystemBlock).optional(),
95
+ tools: z.array(ToolDefinition).optional(),
96
+ max_tokens: z.number().optional(),
97
+ temperature: z.number().optional(),
98
+ stream: z.boolean().optional(),
99
+ thinking: ThinkingConfig.optional(),
100
+ metadata: z
101
+ .object({
102
+ user_id: z.string().optional(),
103
+ })
104
+ .optional(),
105
+ });
106
+
107
+ const ResponseContentBlock = z.discriminatedUnion("type", [
108
+ TextContentBlock,
109
+ ThinkingContentBlock,
110
+ ToolUseContentBlock,
111
+ ]);
112
+
113
+ const ResponseUsageSchema = z.object({
114
+ input_tokens: z.number(),
115
+ output_tokens: z.number(),
116
+ cache_creation_input_tokens: z.number().optional(),
117
+ cache_read_input_tokens: z.number().optional(),
118
+ });
119
+
120
+ export const AnthropicResponseSchema = z.object({
121
+ id: z.string(),
122
+ type: z.literal("message"),
123
+ model: z.string(),
124
+ role: z.literal("assistant"),
125
+ content: z.array(ResponseContentBlock),
126
+ stop_reason: z.string().nullable(),
127
+ stop_sequence: z.string().nullable(),
128
+ usage: ResponseUsageSchema,
129
+ });
130
+
131
+ const SseMessageStartEvent = z.object({
132
+ type: z.literal("message_start"),
133
+ message: z.object({
134
+ id: z.string(),
135
+ type: z.literal("message"),
136
+ model: z.string(),
137
+ role: z.literal("assistant"),
138
+ content: z.array(ResponseContentBlock),
139
+ stop_reason: z.null(),
140
+ stop_sequence: z.null(),
141
+ usage: z
142
+ .object({
143
+ input_tokens: z.number(),
144
+ cache_creation_input_tokens: z.number().optional(),
145
+ cache_read_input_tokens: z.number().optional(),
146
+ })
147
+ .passthrough(),
148
+ }),
149
+ });
150
+
151
+ const SseContentBlockStartEvent = z.object({
152
+ type: z.literal("content_block_start"),
153
+ index: z.number(),
154
+ content_block: ResponseContentBlock,
155
+ });
156
+
157
+ const SseDeltaBlock = z.discriminatedUnion("type", [
158
+ z.object({ type: z.literal("text_delta"), text: z.string() }),
159
+ z.object({ type: z.literal("input_json_delta"), partial_json: z.string() }),
160
+ z.object({ type: z.literal("thinking_delta"), thinking: z.string() }),
161
+ z.object({ type: z.literal("signature_delta"), signature: z.string() }),
162
+ ]);
163
+
164
+ const SseContentBlockDeltaEvent = z.object({
165
+ type: z.literal("content_block_delta"),
166
+ index: z.number(),
167
+ delta: SseDeltaBlock,
168
+ });
169
+
170
+ const SseContentBlockStopEvent = z.object({
171
+ type: z.literal("content_block_stop"),
172
+ index: z.number(),
173
+ });
174
+
175
+ const SseMessageDeltaEvent = z.object({
176
+ type: z.literal("message_delta"),
177
+ delta: z.object({
178
+ stop_reason: z.string().nullable(),
179
+ stop_sequence: z.string().nullable().optional(),
180
+ }),
181
+ usage: z
182
+ .object({
183
+ output_tokens: z.number(),
184
+ input_tokens: z.number().optional(),
185
+ cache_creation_input_tokens: z.number().optional(),
186
+ cache_read_input_tokens: z.number().optional(),
187
+ })
188
+ .passthrough(),
189
+ });
190
+
191
+ const SseMessageStopEvent = z.object({
192
+ type: z.literal("message_stop"),
193
+ });
194
+
195
+ const SsePingEvent = z.object({
196
+ type: z.literal("ping"),
197
+ });
198
+
199
+ export const SseEventSchema = z.discriminatedUnion("type", [
200
+ SseMessageStartEvent,
201
+ SseContentBlockStartEvent,
202
+ SseContentBlockDeltaEvent,
203
+ SseContentBlockStopEvent,
204
+ SseMessageDeltaEvent,
205
+ SseMessageStopEvent,
206
+ SsePingEvent,
207
+ ]);
208
+
209
+ export type AnthropicRequest = z.infer<typeof AnthropicRequestSchema>;
210
+ export type AnthropicResponse = z.infer<typeof AnthropicResponseSchema>;
211
+ export type ResponseContentBlockType = z.infer<typeof ResponseContentBlock>;
212
+
213
+ // Backward-compatible aliases (originally named Inspector*)
214
+ export const InspectorRequestSchema = AnthropicRequestSchema;
215
+ export const InspectorResponseSchema = AnthropicResponseSchema;
216
+ export type InspectorRequest = AnthropicRequest;
217
+ export type InspectorResponse = AnthropicResponse;
@@ -0,0 +1,167 @@
1
+ import type { CapturedLog, JsonValue } from "../../schemas";
2
+ import { JsonValueSchema } from "../jsonSchema";
3
+ import { SseEventSchema } from "./schemas";
4
+
5
+ type StreamTextBlock = { type: "text"; text: string };
6
+ type StreamThinkingBlock = { type: "thinking"; thinking: string; signature: string };
7
+ type StreamToolUseBlock = { type: "tool_use"; id: string; name: string; inputJson: string };
8
+ type StreamBlock = StreamTextBlock | StreamThinkingBlock | StreamToolUseBlock;
9
+
10
+ function parseInputJson(json: string): Record<string, unknown> {
11
+ if (json === "") return {};
12
+ try {
13
+ const parsed: unknown = JSON.parse(json);
14
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
15
+ return { ...parsed };
16
+ }
17
+ return {};
18
+ } catch {
19
+ return {};
20
+ }
21
+ }
22
+
23
+ function finalizeBlock(block: StreamBlock): Record<string, unknown> {
24
+ switch (block.type) {
25
+ case "text":
26
+ return { type: "text", text: block.text };
27
+ case "thinking": {
28
+ const out: Record<string, unknown> = { type: "thinking", thinking: block.thinking };
29
+ if (block.signature !== "") out.signature = block.signature;
30
+ return out;
31
+ }
32
+ case "tool_use":
33
+ return {
34
+ type: "tool_use",
35
+ id: block.id,
36
+ name: block.name,
37
+ input: parseInputJson(block.inputJson),
38
+ };
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Extract Anthropic streaming response and return normalized JSON.
44
+ * Optionally collects SSE chunks into log.streamingChunks for debugging/display.
45
+ */
46
+ export function extractAnthropicStream(
47
+ raw: string,
48
+ log: CapturedLog,
49
+ fallbackModel?: string,
50
+ collectChunks?: boolean,
51
+ ): string {
52
+ const blocks = new Map<number, StreamBlock>();
53
+ let id = "";
54
+ let model = "";
55
+ let stopReason: string | null = null;
56
+ let stopSequence: string | null = null;
57
+ let inputTokens = 0;
58
+ let outputTokens = 0;
59
+
60
+ const MAX_CHUNKS = 1000;
61
+ let chunkIndex = 0;
62
+ let streamStartMs = 0;
63
+ const chunks: Array<{ index: number; timestamp: number; type: string; data: JsonValue }> = [];
64
+
65
+ for (const line of raw.split("\n")) {
66
+ if (!line.startsWith("data: ")) continue;
67
+ try {
68
+ const json: unknown = JSON.parse(line.slice(6));
69
+ const parsed = SseEventSchema.safeParse(json);
70
+ if (!parsed.success) continue;
71
+ const data = parsed.data;
72
+
73
+ if (chunkIndex === 0) streamStartMs = Date.now();
74
+
75
+ if (collectChunks === true && chunks.length < MAX_CHUNKS) {
76
+ chunks.push({
77
+ index: chunkIndex,
78
+ timestamp: Date.now() - streamStartMs,
79
+ type: data.type,
80
+ data: JsonValueSchema.parse(data),
81
+ });
82
+ }
83
+ chunkIndex++;
84
+
85
+ switch (data.type) {
86
+ case "message_start":
87
+ id = data.message.id;
88
+ model = data.message.model;
89
+ inputTokens = data.message.usage.input_tokens;
90
+ log.inputTokens = inputTokens;
91
+ log.cacheCreationInputTokens = data.message.usage.cache_creation_input_tokens ?? null;
92
+ log.cacheReadInputTokens = data.message.usage.cache_read_input_tokens ?? null;
93
+ if (log.model === null) log.model = model;
94
+ break;
95
+ case "content_block_start": {
96
+ const cb = data.content_block;
97
+ if (cb.type === "text") {
98
+ blocks.set(data.index, { type: "text", text: cb.text ?? "" });
99
+ } else if (cb.type === "thinking") {
100
+ blocks.set(data.index, {
101
+ type: "thinking",
102
+ thinking: cb.thinking ?? "",
103
+ signature: cb.signature ?? "",
104
+ });
105
+ } else {
106
+ blocks.set(data.index, {
107
+ type: "tool_use",
108
+ id: cb.id,
109
+ name: cb.name,
110
+ inputJson: "",
111
+ });
112
+ }
113
+ break;
114
+ }
115
+ case "content_block_delta": {
116
+ const block = blocks.get(data.index);
117
+ if (block === undefined) break;
118
+ const delta = data.delta;
119
+ if (delta.type === "text_delta" && block.type === "text") {
120
+ block.text += delta.text;
121
+ } else if (delta.type === "thinking_delta" && block.type === "thinking") {
122
+ block.thinking += delta.thinking;
123
+ } else if (delta.type === "signature_delta" && block.type === "thinking") {
124
+ block.signature += delta.signature;
125
+ } else if (delta.type === "input_json_delta" && block.type === "tool_use") {
126
+ block.inputJson += delta.partial_json;
127
+ }
128
+ break;
129
+ }
130
+ case "message_delta":
131
+ stopReason = data.delta.stop_reason;
132
+ stopSequence = data.delta.stop_sequence ?? null;
133
+ outputTokens = data.usage.output_tokens;
134
+ log.outputTokens = outputTokens;
135
+ break;
136
+ case "content_block_stop":
137
+ case "message_stop":
138
+ case "ping":
139
+ break;
140
+ }
141
+ } catch {
142
+ // non-JSON SSE line, skip
143
+ }
144
+ }
145
+
146
+ if (collectChunks === true) {
147
+ log.streamingChunks = {
148
+ chunks,
149
+ truncated: chunkIndex > MAX_CHUNKS,
150
+ };
151
+ }
152
+
153
+ const orderedContent = [...blocks.entries()]
154
+ .sort(([a], [b]) => a - b)
155
+ .map(([, block]) => finalizeBlock(block));
156
+
157
+ return JSON.stringify({
158
+ id,
159
+ type: "message",
160
+ model: model !== "" ? model : (fallbackModel ?? ""),
161
+ role: "assistant",
162
+ content: orderedContent,
163
+ stop_reason: stopReason,
164
+ stop_sequence: stopSequence,
165
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens },
166
+ });
167
+ }
@@ -0,0 +1,46 @@
1
+ import type { CapturedLog, RequestFormat, TokenUsage } from "../schemas";
2
+
3
+ /**
4
+ * Structured result of parsing a raw request body.
5
+ */
6
+ export type ParsedRequest = {
7
+ model: string | null;
8
+ sessionId: string | null;
9
+ };
10
+
11
+ /**
12
+ * FormatHandler defines the contract for all API format implementations.
13
+ *
14
+ * Each API format (Anthropic, OpenAI) provides its own handler that encapsulates
15
+ * request parsing, response parsing, SSE stream extraction, and token extraction.
16
+ * This replaces the scattered if/else format branching previously in handler.ts.
17
+ */
18
+ export type FormatHandler = {
19
+ /** The format identifier */
20
+ readonly format: RequestFormat;
21
+
22
+ /**
23
+ * Parse a raw request body to extract model name and metadata.
24
+ */
25
+ parseRequest(rawBody: string, headers?: Headers): ParsedRequest | null;
26
+
27
+ /**
28
+ * Extract token usage from a non-streaming response body.
29
+ */
30
+ extractTokens(responseBody: string): TokenUsage;
31
+
32
+ /**
33
+ * Extract structured content from a streaming SSE response.
34
+ */
35
+ extractStream(
36
+ raw: string,
37
+ log: CapturedLog,
38
+ fallbackModel?: string,
39
+ collectChunks?: boolean,
40
+ ): string;
41
+
42
+ /**
43
+ * Detect if a raw request body uses this format.
44
+ */
45
+ detectFormat(rawBody: string | null): boolean;
46
+ };
@@ -0,0 +1,12 @@
1
+ // Import submodules to trigger handler registration
2
+ import "./anthropic/index";
3
+ import "./openai/index";
4
+
5
+ // Import providers to trigger self-registration
6
+ import "./anthropic/anthropicProvider";
7
+ import "./openai/provider";
8
+ import "./openai/alibabaProvider";
9
+
10
+ export type { FormatHandler, ParsedRequest } from "./handler";
11
+ export { formatRegistry, formatForPath } from "./registry";
12
+ export { registry } from "./providers";
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Shared JSON value type for streaming chunks.
5
+ * Defined in a separate file to avoid circular dependencies.
6
+ */
7
+ export type JsonValue =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | null
12
+ | JsonValue[]
13
+ | { [key: string]: JsonValue };
14
+
15
+ export const JsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>
16
+ z.union([
17
+ z.string(),
18
+ z.number(),
19
+ z.boolean(),
20
+ z.null(),
21
+ z.array(JsonValueSchema),
22
+ z.record(z.string(), JsonValueSchema),
23
+ ]),
24
+ );
@@ -0,0 +1,38 @@
1
+ import type { CapturedLog } from "../../schemas";
2
+ import type { ProviderProtocol } from "../protocol";
3
+ import { registry } from "../providers";
4
+ import { extractOpenAIStream } from "./stream";
5
+
6
+ const DEFAULT_ALIBABA_UPSTREAM = "https://dashscope.aliyuncs.com/compatible-mode/v1";
7
+
8
+ /**
9
+ * Alibaba Provider Implementation (DashScope)
10
+ */
11
+ export const alibabaProvider: ProviderProtocol = {
12
+ name: "alibaba",
13
+
14
+ matches(model: string): boolean {
15
+ // Normalize: lowercase, replace whitespace with hyphens
16
+ const m = model.toLowerCase().replace(/\s+/g, "-");
17
+ // Match glm-5* and glm-5.1* models
18
+ if (m.startsWith("glm-5")) {
19
+ return true;
20
+ }
21
+ // Match qwen models (qwen3.6-plus, qwen3.7-max, Qwen3.6 Plus, etc.)
22
+ if (m.startsWith("qwen")) {
23
+ return true;
24
+ }
25
+ return false;
26
+ },
27
+
28
+ getUpstreamBase(_isChatCompletions: boolean, _providerConfig?: { baseUrl?: string }): string {
29
+ return DEFAULT_ALIBABA_UPSTREAM;
30
+ },
31
+
32
+ extractStream(raw: string, log: CapturedLog, fallbackModel?: string): string {
33
+ return extractOpenAIStream(raw, log, fallbackModel);
34
+ },
35
+ };
36
+
37
+ // Register the provider
38
+ registry.register(alibabaProvider);