@tonyclaw/llm-inspector 1.11.2 → 1.11.4

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.
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, R as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, R as RuntimeConfigSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-e-RbjELX.mjs";
2
+ import { C as CapturedLogSchema, R as RuntimeConfigSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-DRA0j7Zv.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
5
5
  import { J as JSZip } from "../_libs/jszip.mjs";
@@ -255,7 +255,7 @@ async function exportLogsAsZip(logs) {
255
255
  document.body.removeChild(anchor);
256
256
  URL.revokeObjectURL(url);
257
257
  }
258
- const version = "1.11.2";
258
+ const version = "1.11.4";
259
259
  const packageJson = {
260
260
  version
261
261
  };
@@ -1778,6 +1778,7 @@ function ResponseContentBlockRenderer({
1778
1778
  case "text":
1779
1779
  return /* @__PURE__ */ jsxRuntimeExports.jsx(TextBlock, { text: block.text });
1780
1780
  case "thinking":
1781
+ case "think":
1781
1782
  return /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingBlock, { thinking: block.thinking });
1782
1783
  case "tool_use":
1783
1784
  return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseBlock, { name: block.name, input: block.input });
@@ -1907,7 +1908,10 @@ function getStatusClasses(category) {
1907
1908
  }
1908
1909
  function parseResponse(text, apiFormat) {
1909
1910
  try {
1910
- const json = JSON.parse(text);
1911
+ let json = JSON.parse(text);
1912
+ if (typeof json === "string") {
1913
+ json = JSON.parse(json);
1914
+ }
1911
1915
  if (apiFormat === "openai") {
1912
1916
  const result2 = parseOpenAIResponse(text);
1913
1917
  if (result2) return result2;
@@ -2937,7 +2941,8 @@ const KNOWN_PROVIDER_PRESETS = {
2937
2941
  },
2938
2942
  minimax: {
2939
2943
  format: "anthropic",
2940
- baseUrl: "https://api.minimaxi.com/anthropic"
2944
+ baseUrl: "https://api.minimaxi.com/anthropic",
2945
+ apiDocsUrl: "https://platform.minimaxi.com/docs/api-reference/api-overview"
2941
2946
  },
2942
2947
  alibaba: {
2943
2948
  format: "openai",
@@ -2945,6 +2950,7 @@ const KNOWN_PROVIDER_PRESETS = {
2945
2950
  }
2946
2951
  };
2947
2952
  const MINIMAX_MODELS = [
2953
+ "MiniMax M3",
2948
2954
  "MiniMax M2.7",
2949
2955
  "MiniMax M2.7-highspeed",
2950
2956
  "MiniMax M2.5",
@@ -2985,6 +2991,9 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2985
2991
  setFormat(preset.format);
2986
2992
  setBaseUrl(preset.baseUrl);
2987
2993
  }
2994
+ if (preset.apiDocsUrl !== void 0 && !apiDocsUrl) {
2995
+ setApiDocsUrl(preset.apiDocsUrl);
2996
+ }
2988
2997
  if (keyword === "minimax" && !model) {
2989
2998
  setModel(MINIMAX_MODELS[0] ?? "");
2990
2999
  }
@@ -3331,11 +3340,17 @@ function ProvidersPanel({
3331
3340
  anthropicBaseUrl: data.format === "anthropic" ? data.baseUrl : void 0,
3332
3341
  openaiBaseUrl: data.format === "openai" ? data.baseUrl : void 0
3333
3342
  };
3334
- const res = await fetch("/api/providers", {
3335
- method: "POST",
3336
- headers: { "Content-Type": "application/json" },
3337
- body: JSON.stringify(payload)
3338
- });
3343
+ let res;
3344
+ try {
3345
+ res = await fetch("/api/providers", {
3346
+ method: "POST",
3347
+ headers: { "Content-Type": "application/json" },
3348
+ body: JSON.stringify(payload)
3349
+ });
3350
+ } catch (err) {
3351
+ setError("Network error: could not reach the server. Is the proxy running?");
3352
+ return;
3353
+ }
3339
3354
  if (!res.ok) {
3340
3355
  const err = await res.json();
3341
3356
  setError(err.error ?? "Failed to add provider");
@@ -3358,11 +3373,17 @@ function ProvidersPanel({
3358
3373
  anthropicBaseUrl: data.format === "anthropic" ? data.baseUrl : void 0,
3359
3374
  openaiBaseUrl: data.format === "openai" ? data.baseUrl : void 0
3360
3375
  };
3361
- const res = await fetch(`/api/providers/${editingProvider.id}`, {
3362
- method: "PUT",
3363
- headers: { "Content-Type": "application/json" },
3364
- body: JSON.stringify(payload)
3365
- });
3376
+ let res;
3377
+ try {
3378
+ res = await fetch(`/api/providers/${editingProvider.id}`, {
3379
+ method: "PUT",
3380
+ headers: { "Content-Type": "application/json" },
3381
+ body: JSON.stringify(payload)
3382
+ });
3383
+ } catch (err) {
3384
+ setError("Network error: could not reach the server. Is the proxy running?");
3385
+ return;
3386
+ }
3366
3387
  if (!res.ok) {
3367
3388
  const err = await res.json();
3368
3389
  setError(err.error ?? "Failed to update provider");
@@ -3377,9 +3398,15 @@ function ProvidersPanel({
3377
3398
  function handleDeleteProvider(providerId) {
3378
3399
  if (!window.confirm("Are you sure you want to delete this provider?")) return;
3379
3400
  void (async () => {
3380
- const res = await fetch(`/api/providers/${providerId}`, {
3381
- method: "DELETE"
3382
- });
3401
+ let res;
3402
+ try {
3403
+ res = await fetch(`/api/providers/${providerId}`, {
3404
+ method: "DELETE"
3405
+ });
3406
+ } catch (err) {
3407
+ setError("Network error: could not reach the server. Is the proxy running?");
3408
+ return;
3409
+ }
3383
3410
  if (!res.ok) {
3384
3411
  const err = await res.json();
3385
3412
  setError(err.error ?? "Failed to delete provider");
@@ -85,7 +85,7 @@ function getResponse() {
85
85
  }
86
86
  var HEADERS = { TSS_SHELL: "X-TSS_SHELL" };
87
87
  async function getStartManifest(matchedRoutes) {
88
- const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-C5P5lsG5.mjs");
88
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-DKfW4gRz.mjs");
89
89
  const startManifest = tsrStartManifest();
90
90
  let routes = startManifest.routes;
91
91
  routes[rootRouteId];
@@ -1157,7 +1157,7 @@ var getBaseManifest = getProdBaseManifest;
1157
1157
  var createEarlyHintsForRequest = createEarlyHintsCollector;
1158
1158
  async function loadEntries() {
1159
1159
  const [routerEntry, startEntry, pluginAdapters] = await Promise.all([
1160
- import("./router-e-RbjELX.mjs").then((n) => n.r),
1160
+ import("./router-DRA0j7Zv.mjs").then((n) => n.r),
1161
1161
  import("./start-HYkvq4Ni.mjs"),
1162
1162
  import("./empty-plugin-adapters-BFgPZ6_d.mjs")
1163
1163
  ]);
@@ -66,7 +66,7 @@ function RootDocument({ children }) {
66
66
  ] })
67
67
  ] });
68
68
  }
69
- const $$splitComponentImporter = () => import("./index-CYuV1ljD.mjs");
69
+ const $$splitComponentImporter = () => import("./index-BWMVJy33.mjs");
70
70
  const Route$g = createFileRoute("/")({
71
71
  component: lazyRouteComponent($$splitComponentImporter, "component")
72
72
  });
@@ -346,6 +346,11 @@ const ThinkingContentBlock = object({
346
346
  thinking: string(),
347
347
  signature: string().optional()
348
348
  });
349
+ const ThinkContentBlock = object({
350
+ type: literal("think"),
351
+ thinking: string(),
352
+ signature: string().optional()
353
+ });
349
354
  const ImageSourceBlock = object({
350
355
  type: literal("base64"),
351
356
  media_type: string(),
@@ -419,6 +424,7 @@ const AnthropicRequestSchema = object({
419
424
  const ResponseContentBlock = discriminatedUnion("type", [
420
425
  TextContentBlock,
421
426
  ThinkingContentBlock,
427
+ ThinkContentBlock,
422
428
  ToolUseContentBlock
423
429
  ]);
424
430
  const ResponseUsageSchema = object({
@@ -434,7 +440,7 @@ const AnthropicResponseSchema$1 = object({
434
440
  role: literal("assistant"),
435
441
  content: array(ResponseContentBlock),
436
442
  stop_reason: string().nullable(),
437
- stop_sequence: string().nullable(),
443
+ stop_sequence: string().nullable().optional(),
438
444
  usage: ResponseUsageSchema
439
445
  }).passthrough();
440
446
  const SseMessageStartEvent = object({
@@ -1068,6 +1074,11 @@ function finalizeBlock(block) {
1068
1074
  if (block.signature !== "") out.signature = block.signature;
1069
1075
  return out;
1070
1076
  }
1077
+ case "think": {
1078
+ const out = { type: "think", thinking: block.thinking };
1079
+ if (block.signature !== "") out.signature = block.signature;
1080
+ return out;
1081
+ }
1071
1082
  case "tool_use":
1072
1083
  return {
1073
1084
  type: "tool_use",
@@ -1122,21 +1133,32 @@ function extractAnthropicStream(raw, log, fallbackModel, collectChunks) {
1122
1133
  break;
1123
1134
  case "content_block_start": {
1124
1135
  const cb = data.content_block;
1125
- if (cb.type === "text") {
1126
- blocks.set(data.index, { type: "text", text: cb.text ?? "" });
1127
- } else if (cb.type === "thinking") {
1128
- blocks.set(data.index, {
1129
- type: "thinking",
1130
- thinking: cb.thinking ?? "",
1131
- signature: cb.signature ?? ""
1132
- });
1133
- } else {
1134
- blocks.set(data.index, {
1135
- type: "tool_use",
1136
- id: cb.id,
1137
- name: cb.name,
1138
- inputJson: ""
1139
- });
1136
+ switch (cb.type) {
1137
+ case "text":
1138
+ blocks.set(data.index, { type: "text", text: cb.text ?? "" });
1139
+ break;
1140
+ case "thinking":
1141
+ blocks.set(data.index, {
1142
+ type: "thinking",
1143
+ thinking: cb.thinking ?? "",
1144
+ signature: cb.signature ?? ""
1145
+ });
1146
+ break;
1147
+ case "think":
1148
+ blocks.set(data.index, {
1149
+ type: "think",
1150
+ thinking: cb.thinking ?? "",
1151
+ signature: cb.signature ?? ""
1152
+ });
1153
+ break;
1154
+ case "tool_use":
1155
+ blocks.set(data.index, {
1156
+ type: "tool_use",
1157
+ id: cb.id,
1158
+ name: cb.name,
1159
+ inputJson: ""
1160
+ });
1161
+ break;
1140
1162
  }
1141
1163
  break;
1142
1164
  }
@@ -1146,9 +1168,9 @@ function extractAnthropicStream(raw, log, fallbackModel, collectChunks) {
1146
1168
  const delta = data.delta;
1147
1169
  if (delta.type === "text_delta" && block.type === "text") {
1148
1170
  block.text += delta.text;
1149
- } else if (delta.type === "thinking_delta" && block.type === "thinking") {
1171
+ } else if (delta.type === "thinking_delta" && (block.type === "thinking" || block.type === "think")) {
1150
1172
  block.thinking += delta.thinking;
1151
- } else if (delta.type === "signature_delta" && block.type === "thinking") {
1173
+ } else if (delta.type === "signature_delta" && (block.type === "thinking" || block.type === "think")) {
1152
1174
  block.signature += delta.signature;
1153
1175
  } else if (delta.type === "input_json_delta" && block.type === "tool_use") {
1154
1176
  block.inputJson += delta.partial_json;
@@ -2408,7 +2430,8 @@ const ProviderInputSchema = object({
2408
2430
  name: string().min(1, "Name is required"),
2409
2431
  apiKey: string().min(1, "API key is required"),
2410
2432
  format: _enum(["anthropic", "openai"]),
2411
- baseUrl: string().min(1, "Base URL is required"),
2433
+ anthropicBaseUrl: string().optional(),
2434
+ openaiBaseUrl: string().optional(),
2412
2435
  model: string().min(1, "Model is required"),
2413
2436
  authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer"),
2414
2437
  apiDocsUrl: string().optional()
@@ -2428,7 +2451,7 @@ const Route$d = createFileRoute("/api/providers")({
2428
2451
  parsed.data.name,
2429
2452
  parsed.data.apiKey,
2430
2453
  parsed.data.format,
2431
- parsed.data.baseUrl,
2454
+ parsed.data.format === "anthropic" ? parsed.data.anthropicBaseUrl : parsed.data.openaiBaseUrl,
2432
2455
  parsed.data.model,
2433
2456
  parsed.data.authHeader,
2434
2457
  parsed.data.apiDocsUrl
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ routes: { __root__: { filePath: "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", children: ["/", "/api/config", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], preloads: ["/assets/index-Chp_30CP.js"], scripts: [{ attrs: { type: "module", async: true, src: "/assets/index-Chp_30CP.js" } }] }, "/": { filePath: "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", children: void 0, preloads: ["/assets/index-DLvqlFS9.js"] } } });
1
+ const tsrStartManifest = () => ({ routes: { __root__: { filePath: "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", children: ["/", "/api/config", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], preloads: ["/assets/index-Do7wdaYZ.js"], scripts: [{ attrs: { type: "module", async: true, src: "/assets/index-Do7wdaYZ.js" } }] }, "/": { filePath: "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", children: void 0, preloads: ["/assets/index-CjjXIYIt.js"] } } });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -38,51 +38,51 @@ const assets = {
38
38
  "/assets/alibaba-TTwafVwX.svg": {
39
39
  "type": "image/svg+xml",
40
40
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
41
- "mtime": "2026-06-06T09:13:20.378Z",
41
+ "mtime": "2026-06-08T00:49:07.051Z",
42
42
  "size": 5915,
43
43
  "path": "../public/assets/alibaba-TTwafVwX.svg"
44
44
  },
45
45
  "/assets/minimax-BPMzvuL-.jpeg": {
46
46
  "type": "image/jpeg",
47
47
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
48
- "mtime": "2026-06-06T09:13:20.380Z",
48
+ "mtime": "2026-06-08T00:49:07.048Z",
49
49
  "size": 6918,
50
50
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
51
51
  },
52
52
  "/assets/index-BpKPXEcb.css": {
53
53
  "type": "text/css; charset=utf-8",
54
54
  "etag": '"11628-5GjLelHqc7BEfGB/7qUFPt5/gyM"',
55
- "mtime": "2026-06-06T09:13:20.380Z",
55
+ "mtime": "2026-06-08T00:49:07.051Z",
56
56
  "size": 71208,
57
57
  "path": "../public/assets/index-BpKPXEcb.css"
58
58
  },
59
59
  "/assets/zhipuai-BPNAnxo-.svg": {
60
60
  "type": "image/svg+xml",
61
61
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
62
- "mtime": "2026-06-06T09:13:20.378Z",
62
+ "mtime": "2026-06-08T00:49:07.051Z",
63
63
  "size": 11256,
64
64
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
65
65
  },
66
+ "/assets/index-Do7wdaYZ.js": {
67
+ "type": "text/javascript; charset=utf-8",
68
+ "etag": '"5103c-aaFcAP7Accy+WRst7gdyeAfO198"',
69
+ "mtime": "2026-06-08T00:49:07.051Z",
70
+ "size": 331836,
71
+ "path": "../public/assets/index-Do7wdaYZ.js"
72
+ },
66
73
  "/assets/qwen-CONDcHqt.png": {
67
74
  "type": "image/png",
68
75
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
69
- "mtime": "2026-06-06T09:13:20.378Z",
76
+ "mtime": "2026-06-08T00:49:07.051Z",
70
77
  "size": 357059,
71
78
  "path": "../public/assets/qwen-CONDcHqt.png"
72
79
  },
73
- "/assets/index-Chp_30CP.js": {
74
- "type": "text/javascript; charset=utf-8",
75
- "etag": '"5103c-5HK4V8mRGKw17pJ5x54GVb2N5bU"',
76
- "mtime": "2026-06-06T09:13:20.380Z",
77
- "size": 331836,
78
- "path": "../public/assets/index-Chp_30CP.js"
79
- },
80
- "/assets/index-DLvqlFS9.js": {
80
+ "/assets/index-CjjXIYIt.js": {
81
81
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"8b6f3-NmcxU8MkXNUN+0T4A/2Ajs2dUI4"',
83
- "mtime": "2026-06-06T09:13:20.380Z",
84
- "size": 571123,
85
- "path": "../public/assets/index-DLvqlFS9.js"
82
+ "etag": '"8b905-rdaof1vYelA3ENck4MON8W7NPww"',
83
+ "mtime": "2026-06-08T00:49:07.051Z",
84
+ "size": 571653,
85
+ "path": "../public/assets/index-CjjXIYIt.js"
86
86
  }
87
87
  };
88
88
  function readAsset(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.11.2",
3
+ "version": "1.11.4",
4
4
  "type": "module",
5
5
  "description": "LLM API proxy inspector — captures and displays requests/responses from AI coding tools in a web UI",
6
6
  "license": "MIT",
@@ -3,24 +3,28 @@ import { Button } from "../ui/button";
3
3
  import type { ProviderConfig } from "../../proxy/providers";
4
4
 
5
5
  // Known provider presets - maps provider name keywords to their API URLs
6
- const KNOWN_PROVIDER_PRESETS: Record<string, { format: "anthropic" | "openai"; baseUrl: string }> =
7
- {
8
- deepseek: {
9
- format: "openai",
10
- baseUrl: "https://api.deepseek.com",
11
- },
12
- minimax: {
13
- format: "anthropic",
14
- baseUrl: "https://api.minimaxi.com/anthropic",
15
- },
16
- alibaba: {
17
- format: "openai",
18
- baseUrl: "https://dashscope.aliyuncs.com/compatible-mode",
19
- },
20
- };
6
+ const KNOWN_PROVIDER_PRESETS: Record<
7
+ string,
8
+ { format: "anthropic" | "openai"; baseUrl: string; apiDocsUrl?: string }
9
+ > = {
10
+ deepseek: {
11
+ format: "openai",
12
+ baseUrl: "https://api.deepseek.com",
13
+ },
14
+ minimax: {
15
+ format: "anthropic",
16
+ baseUrl: "https://api.minimaxi.com/anthropic",
17
+ apiDocsUrl: "https://platform.minimaxi.com/docs/api-reference/api-overview",
18
+ },
19
+ alibaba: {
20
+ format: "openai",
21
+ baseUrl: "https://dashscope.aliyuncs.com/compatible-mode",
22
+ },
23
+ };
21
24
 
22
25
  // MiniMax model options
23
26
  const MINIMAX_MODELS = [
27
+ "MiniMax M3",
24
28
  "MiniMax M2.7",
25
29
  "MiniMax M2.7-highspeed",
26
30
  "MiniMax M2.5",
@@ -85,6 +89,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
85
89
  setFormat(preset.format);
86
90
  setBaseUrl(preset.baseUrl);
87
91
  }
92
+ if (preset.apiDocsUrl !== undefined && !apiDocsUrl) {
93
+ setApiDocsUrl(preset.apiDocsUrl);
94
+ }
88
95
  // For MiniMax, auto-select the first model if not already set
89
96
  if (keyword === "minimax" && !model) {
90
97
  setModel(MINIMAX_MODELS[0] ?? "");
@@ -261,11 +261,17 @@ export function ProvidersPanel({
261
261
  anthropicBaseUrl: data.format === "anthropic" ? data.baseUrl : undefined,
262
262
  openaiBaseUrl: data.format === "openai" ? data.baseUrl : undefined,
263
263
  };
264
- const res = await fetch("/api/providers", {
265
- method: "POST",
266
- headers: { "Content-Type": "application/json" },
267
- body: JSON.stringify(payload),
268
- });
264
+ let res: Response;
265
+ try {
266
+ res = await fetch("/api/providers", {
267
+ method: "POST",
268
+ headers: { "Content-Type": "application/json" },
269
+ body: JSON.stringify(payload),
270
+ });
271
+ } catch (err) {
272
+ setError("Network error: could not reach the server. Is the proxy running?");
273
+ return;
274
+ }
269
275
  if (!res.ok) {
270
276
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
271
277
  const err = (await res.json()) as { error?: string };
@@ -299,11 +305,17 @@ export function ProvidersPanel({
299
305
  anthropicBaseUrl: data.format === "anthropic" ? data.baseUrl : undefined,
300
306
  openaiBaseUrl: data.format === "openai" ? data.baseUrl : undefined,
301
307
  };
302
- const res = await fetch(`/api/providers/${editingProvider.id}`, {
303
- method: "PUT",
304
- headers: { "Content-Type": "application/json" },
305
- body: JSON.stringify(payload),
306
- });
308
+ let res: Response;
309
+ try {
310
+ res = await fetch(`/api/providers/${editingProvider.id}`, {
311
+ method: "PUT",
312
+ headers: { "Content-Type": "application/json" },
313
+ body: JSON.stringify(payload),
314
+ });
315
+ } catch (err) {
316
+ setError("Network error: could not reach the server. Is the proxy running?");
317
+ return;
318
+ }
307
319
  if (!res.ok) {
308
320
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
309
321
  const err = (await res.json()) as { error?: string };
@@ -323,9 +335,15 @@ export function ProvidersPanel({
323
335
  // eslint-disable-next-line no-alert
324
336
  if (!window.confirm("Are you sure you want to delete this provider?")) return;
325
337
  void (async () => {
326
- const res = await fetch(`/api/providers/${providerId}`, {
327
- method: "DELETE",
328
- });
338
+ let res: Response;
339
+ try {
340
+ res = await fetch(`/api/providers/${providerId}`, {
341
+ method: "DELETE",
342
+ });
343
+ } catch (err) {
344
+ setError("Network error: could not reach the server. Is the proxy running?");
345
+ return;
346
+ }
329
347
  if (!res.ok) {
330
348
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
331
349
  const err = (await res.json()) as { error?: string };
@@ -41,7 +41,11 @@ function parseResponse(
41
41
  apiFormat: "anthropic" | "openai" | "unknown" | undefined,
42
42
  ): InspectorResponse | OpenAIResponse | null {
43
43
  try {
44
- const json: unknown = JSON.parse(text);
44
+ let json: unknown = JSON.parse(text);
45
+ // Handle double-encoded JSON (some providers send response body as JSON-encoded string)
46
+ if (typeof json === "string") {
47
+ json = JSON.parse(json);
48
+ }
45
49
  if (apiFormat === "openai") {
46
50
  const result = parseOpenAIResponse(text);
47
51
  if (result) return result;
@@ -163,6 +163,7 @@ export function ResponseContentBlockRenderer({
163
163
  case "text":
164
164
  return <TextBlock text={block.text} />;
165
165
  case "thinking":
166
+ case "think":
166
167
  return <ThinkingBlock thinking={block.thinking} />;
167
168
  case "tool_use":
168
169
  return <ToolUseBlock name={block.name} input={block.input} />;
@@ -19,6 +19,12 @@ const ThinkingContentBlock = z.object({
19
19
  signature: z.string().optional(),
20
20
  });
21
21
 
22
+ const ThinkContentBlock = z.object({
23
+ type: z.literal("think"),
24
+ thinking: z.string(),
25
+ signature: z.string().optional(),
26
+ });
27
+
22
28
  const ImageSourceBlock = z.object({
23
29
  type: z.literal("base64"),
24
30
  media_type: z.string(),
@@ -107,6 +113,7 @@ export const AnthropicRequestSchema = z.object({
107
113
  const ResponseContentBlock = z.discriminatedUnion("type", [
108
114
  TextContentBlock,
109
115
  ThinkingContentBlock,
116
+ ThinkContentBlock,
110
117
  ToolUseContentBlock,
111
118
  ]);
112
119
 
@@ -127,7 +134,7 @@ export const AnthropicResponseSchema = z
127
134
  role: z.literal("assistant"),
128
135
  content: z.array(ResponseContentBlock),
129
136
  stop_reason: z.string().nullable(),
130
- stop_sequence: z.string().nullable(),
137
+ stop_sequence: z.string().nullable().optional(),
131
138
  usage: ResponseUsageSchema,
132
139
  })
133
140
  .passthrough(); // Allow extra unknown fields
@@ -4,8 +4,9 @@ import { SseEventSchema } from "./schemas";
4
4
 
5
5
  type StreamTextBlock = { type: "text"; text: string };
6
6
  type StreamThinkingBlock = { type: "thinking"; thinking: string; signature: string };
7
+ type StreamThinkBlock = { type: "think"; thinking: string; signature: string };
7
8
  type StreamToolUseBlock = { type: "tool_use"; id: string; name: string; inputJson: string };
8
- type StreamBlock = StreamTextBlock | StreamThinkingBlock | StreamToolUseBlock;
9
+ type StreamBlock = StreamTextBlock | StreamThinkingBlock | StreamThinkBlock | StreamToolUseBlock;
9
10
 
10
11
  function parseInputJson(json: string): Record<string, unknown> {
11
12
  if (json === "") return {};
@@ -29,6 +30,11 @@ function finalizeBlock(block: StreamBlock): Record<string, unknown> {
29
30
  if (block.signature !== "") out.signature = block.signature;
30
31
  return out;
31
32
  }
33
+ case "think": {
34
+ const out: Record<string, unknown> = { type: "think", thinking: block.thinking };
35
+ if (block.signature !== "") out.signature = block.signature;
36
+ return out;
37
+ }
32
38
  case "tool_use":
33
39
  return {
34
40
  type: "tool_use",
@@ -98,21 +104,32 @@ export function extractAnthropicStream(
98
104
  break;
99
105
  case "content_block_start": {
100
106
  const cb = data.content_block;
101
- if (cb.type === "text") {
102
- blocks.set(data.index, { type: "text", text: cb.text ?? "" });
103
- } else if (cb.type === "thinking") {
104
- blocks.set(data.index, {
105
- type: "thinking",
106
- thinking: cb.thinking ?? "",
107
- signature: cb.signature ?? "",
108
- });
109
- } else {
110
- blocks.set(data.index, {
111
- type: "tool_use",
112
- id: cb.id,
113
- name: cb.name,
114
- inputJson: "",
115
- });
107
+ switch (cb.type) {
108
+ case "text":
109
+ blocks.set(data.index, { type: "text", text: cb.text ?? "" });
110
+ break;
111
+ case "thinking":
112
+ blocks.set(data.index, {
113
+ type: "thinking",
114
+ thinking: cb.thinking ?? "",
115
+ signature: cb.signature ?? "",
116
+ });
117
+ break;
118
+ case "think":
119
+ blocks.set(data.index, {
120
+ type: "think",
121
+ thinking: cb.thinking ?? "",
122
+ signature: cb.signature ?? "",
123
+ });
124
+ break;
125
+ case "tool_use":
126
+ blocks.set(data.index, {
127
+ type: "tool_use",
128
+ id: cb.id,
129
+ name: cb.name,
130
+ inputJson: "",
131
+ });
132
+ break;
116
133
  }
117
134
  break;
118
135
  }
@@ -122,9 +139,15 @@ export function extractAnthropicStream(
122
139
  const delta = data.delta;
123
140
  if (delta.type === "text_delta" && block.type === "text") {
124
141
  block.text += delta.text;
125
- } else if (delta.type === "thinking_delta" && block.type === "thinking") {
142
+ } else if (
143
+ delta.type === "thinking_delta" &&
144
+ (block.type === "thinking" || block.type === "think")
145
+ ) {
126
146
  block.thinking += delta.thinking;
127
- } else if (delta.type === "signature_delta" && block.type === "thinking") {
147
+ } else if (
148
+ delta.type === "signature_delta" &&
149
+ (block.type === "thinking" || block.type === "think")
150
+ ) {
128
151
  block.signature += delta.signature;
129
152
  } else if (delta.type === "input_json_delta" && block.type === "tool_use") {
130
153
  block.inputJson += delta.partial_json;
@@ -6,7 +6,8 @@ const ProviderInputSchema = z.object({
6
6
  name: z.string().min(1, "Name is required"),
7
7
  apiKey: z.string().min(1, "API key is required"),
8
8
  format: z.enum(["anthropic", "openai"]),
9
- baseUrl: z.string().min(1, "Base URL is required"),
9
+ anthropicBaseUrl: z.string().optional(),
10
+ openaiBaseUrl: z.string().optional(),
10
11
  model: z.string().min(1, "Model is required"),
11
12
  authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
12
13
  apiDocsUrl: z.string().optional(),
@@ -27,7 +28,9 @@ export const Route = createFileRoute("/api/providers")({
27
28
  parsed.data.name,
28
29
  parsed.data.apiKey,
29
30
  parsed.data.format,
30
- parsed.data.baseUrl,
31
+ parsed.data.format === "anthropic"
32
+ ? parsed.data.anthropicBaseUrl
33
+ : parsed.data.openaiBaseUrl,
31
34
  parsed.data.model,
32
35
  parsed.data.authHeader,
33
36
  parsed.data.apiDocsUrl,