@tonyclaw/agent-inspector 2.1.16 → 2.1.17

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 (51) hide show
  1. package/.output/cli.js +57 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-Ccrrcx1j.js → CompareDrawer-B9sLBHw5.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
  5. package/.output/public/assets/{ReplayDialog-C7axhr-l.js → ReplayDialog-D9I9W9n3.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-WOptg8j_.js → RequestAnatomy-DGXK_Rii.js} +1 -1
  7. package/.output/public/assets/{ResponseView-CSAcxh_M.js → ResponseView-df_JzwsS.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-DJS5KhPx.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
  9. package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
  10. package/.output/public/assets/index-Blqvndcn.js +1 -0
  11. package/.output/public/assets/index-Cs79WRQj.css +1 -0
  12. package/.output/public/assets/{index-DMPNh46t.js → index-Dqc2r1ea.js} +1 -1
  13. package/.output/public/assets/{json-viewer-inX5QSa3.js → json-viewer-D3QWQlSB.js} +1 -1
  14. package/.output/public/assets/{main-aCAKYGSD.js → main-ZhxhOCmF.js} +2 -2
  15. package/.output/server/_libs/lucide-react.mjs +220 -202
  16. package/.output/server/{_sessionId-C0fhvQnn.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
  17. package/.output/server/_ssr/{CompareDrawer-CO-Ti5sg.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-N4J7uBCX.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +1858 -1396
  19. package/.output/server/_ssr/{ReplayDialog-J1VxC0In.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
  20. package/.output/server/_ssr/{RequestAnatomy-DBH-F3rq.mjs → RequestAnatomy-HQfrxjO1.mjs} +2 -2
  21. package/.output/server/_ssr/{ResponseView-DLFwV7Hd.mjs → ResponseView-QSlSD11e.mjs} +3 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-BywmuND0.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +2 -2
  23. package/.output/server/_ssr/{index-CStymIc0.mjs → index-D_EzfXaN.mjs} +2 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-DlQu0N1x.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
  26. package/.output/server/_ssr/{router-CQ4Zxhmr.mjs → router-__o2wrfO.mjs} +398 -95
  27. package/.output/server/{_tanstack-start-manifest_v-BHeJBGfo.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
  28. package/.output/server/index.mjs +73 -73
  29. package/README.md +57 -2
  30. package/package.json +1 -1
  31. package/src/cli/startupOutput.ts +16 -0
  32. package/src/cli.ts +61 -15
  33. package/src/components/ProxyViewer.tsx +395 -64
  34. package/src/components/providers/ProviderCard.tsx +132 -31
  35. package/src/components/providers/ProviderForm.tsx +43 -11
  36. package/src/components/providers/ProvidersPanel.tsx +1 -1
  37. package/src/components/proxy-viewer/ConversationHeader.tsx +133 -112
  38. package/src/components/proxy-viewer/LogEntry.tsx +109 -26
  39. package/src/components/proxy-viewer/LogEntryHeader.tsx +339 -329
  40. package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
  41. package/src/components/proxy-viewer/TurnGroup.tsx +22 -63
  42. package/src/components/proxy-viewer/bodyHydration.ts +56 -0
  43. package/src/lib/providerTestContract.ts +56 -10
  44. package/src/lib/providerTestPrompt.ts +23 -1
  45. package/src/lib/upstreamUrl.ts +105 -1
  46. package/src/mcp/toolHandlers.ts +1 -1
  47. package/src/routes/api/providers.$providerId.test.log.ts +85 -10
  48. package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +0 -106
  49. package/.output/public/assets/_sessionId-DUSalzKH.js +0 -1
  50. package/.output/public/assets/index-BPpA21dY.css +0 -1
  51. package/.output/public/assets/index-Bt0Az2I2.js +0 -1
@@ -1,9 +1,24 @@
1
1
  export const PATH_V1_CHAT_COMPLETIONS = "/v1/chat/completions";
2
2
  export const PATH_V1_RESPONSES = "/v1/responses";
3
+ export const PATH_V1_MESSAGES = "/v1/messages";
4
+
5
+ export type UpstreamUrlDiagnostic = {
6
+ severity: "info" | "warning";
7
+ message: string;
8
+ };
9
+
10
+ export type UpstreamUrlPreviewOptions = {
11
+ providerName?: string;
12
+ };
13
+
14
+ export type UpstreamUrlPreview = {
15
+ finalUrl: string;
16
+ diagnostics: UpstreamUrlDiagnostic[];
17
+ };
3
18
 
4
19
  export function buildUpstreamUrl(upstreamBase: string, normalizedPath: string): string {
5
20
  const base = upstreamBase.endsWith("/") ? upstreamBase.slice(0, -1) : upstreamBase;
6
- const endpointPath = normalizedPath.split("?")[0] ?? normalizedPath;
21
+ const endpointPath = endpointPathFor(normalizedPath);
7
22
  const queryIndex = normalizedPath.indexOf("?");
8
23
  const search = queryIndex >= 0 ? normalizedPath.slice(queryIndex) : "";
9
24
 
@@ -24,6 +39,62 @@ export function buildUpstreamUrl(upstreamBase: string, normalizedPath: string):
24
39
  return base + normalizedPath;
25
40
  }
26
41
 
42
+ export function previewUpstreamUrl(
43
+ upstreamBase: string,
44
+ normalizedPath: string,
45
+ options: UpstreamUrlPreviewOptions = {},
46
+ ): UpstreamUrlPreview {
47
+ const base = upstreamBase.trim().replace(/\/+$/, "");
48
+ const endpointPath = endpointPathFor(normalizedPath);
49
+ const finalUrl = buildUpstreamUrl(base, normalizedPath);
50
+ const diagnostics: UpstreamUrlDiagnostic[] = [];
51
+ const exactEndpoint = endpointPath !== "" && base.endsWith(endpointPath);
52
+
53
+ if (exactEndpoint) {
54
+ diagnostics.push({
55
+ severity: "info",
56
+ message: "This already includes the exact endpoint; no duplicate suffix will be appended.",
57
+ });
58
+ } else if (isZhipuVersionedOpenAIBase(base) && isVersionedChatCompletionsPath(normalizedPath)) {
59
+ diagnostics.push({
60
+ severity: "info",
61
+ message: "Zhipu bases already include the API version; only /chat/completions is appended.",
62
+ });
63
+ } else if (base.endsWith("/v1") && normalizedPath.startsWith("/v1/")) {
64
+ diagnostics.push({
65
+ severity: "info",
66
+ message: "Base URL already ends with /v1; the suffix will not repeat /v1.",
67
+ });
68
+ }
69
+
70
+ if (isResponsesPath(endpointPath) && baseEndsWithChatEndpoint(base)) {
71
+ diagnostics.push({
72
+ severity: "warning",
73
+ message: "This looks like a Chat Completions endpoint, so Responses would append after it.",
74
+ });
75
+ }
76
+
77
+ if (isChatCompletionsPath(endpointPath) && baseEndsWithResponsesEndpoint(base)) {
78
+ diagnostics.push({
79
+ severity: "warning",
80
+ message: "This looks like a Responses endpoint, so Chat Completions would append after it.",
81
+ });
82
+ }
83
+
84
+ if (isResponsesPath(endpointPath) && isLikelyDeepSeekBase(base, options.providerName)) {
85
+ diagnostics.push({
86
+ severity: "warning",
87
+ message: "DeepSeek's official API does not currently expose OpenAI Responses.",
88
+ });
89
+ }
90
+
91
+ return { finalUrl, diagnostics };
92
+ }
93
+
94
+ function endpointPathFor(normalizedPath: string): string {
95
+ return normalizedPath.split("?")[0] ?? normalizedPath;
96
+ }
97
+
27
98
  function isVersionedChatCompletionsPath(normalizedPath: string): boolean {
28
99
  return (
29
100
  normalizedPath === PATH_V1_CHAT_COMPLETIONS ||
@@ -32,6 +103,39 @@ function isVersionedChatCompletionsPath(normalizedPath: string): boolean {
32
103
  );
33
104
  }
34
105
 
106
+ function isChatCompletionsPath(endpointPath: string): boolean {
107
+ return (
108
+ endpointPath === PATH_V1_CHAT_COMPLETIONS ||
109
+ endpointPath.startsWith(`${PATH_V1_CHAT_COMPLETIONS}/`)
110
+ );
111
+ }
112
+
113
+ function isResponsesPath(endpointPath: string): boolean {
114
+ return endpointPath === PATH_V1_RESPONSES || endpointPath.startsWith(`${PATH_V1_RESPONSES}/`);
115
+ }
116
+
117
+ function baseEndsWithChatEndpoint(base: string): boolean {
118
+ const normalized = base.toLowerCase();
119
+ return normalized.endsWith("/chat/completions");
120
+ }
121
+
122
+ function baseEndsWithResponsesEndpoint(base: string): boolean {
123
+ const normalized = base.toLowerCase();
124
+ return normalized.endsWith("/responses");
125
+ }
126
+
127
+ function isLikelyDeepSeekBase(base: string, providerName: string | undefined): boolean {
128
+ const provider = providerName?.toLowerCase();
129
+ if (provider !== undefined && provider.includes("deepseek")) return true;
130
+
131
+ try {
132
+ const parsed = new URL(base);
133
+ return parsed.hostname.toLowerCase().includes("deepseek");
134
+ } catch {
135
+ return base.toLowerCase().includes("deepseek");
136
+ }
137
+ }
138
+
35
139
  function isZhipuVersionedOpenAIBase(base: string): boolean {
36
140
  try {
37
141
  const parsed = new URL(base);
@@ -907,7 +907,7 @@ function providerDiagnosticHints(result: unknown): string[] {
907
907
  }
908
908
  if (text.includes("nonstream") || text.includes("non-stream")) {
909
909
  hints.push(
910
- "The failure mentions non-streaming. Compare the OpenAI/Anthropic non-streaming path and payload with the streaming request.",
910
+ "The failure mentions non-streaming. Compare the Anthropic, OpenAI Chat, and OpenAI Responses non-streaming path and payload with the streaming request.",
911
911
  );
912
912
  }
913
913
  return [...new Set(hints)];
@@ -6,6 +6,7 @@ import { buildProviderTestSessionId } from "../../proxy/sessionSupervisor";
6
6
  import { buildProviderTestRequestBody } from "../../lib/providerTestPrompt";
7
7
  import {
8
8
  ProviderTestResultsSchema,
9
+ type ProviderModelTestResults,
9
10
  type ProviderTestResult as TestResult,
10
11
  type ProviderTestState,
11
12
  } from "../../lib/providerTestContract";
@@ -55,11 +56,9 @@ async function logModelResults(
55
56
  providerName: string,
56
57
  providerSessionId: string,
57
58
  anthropicUrl: string | undefined,
58
- openaiUrl: string | undefined,
59
- modelResults: {
60
- anthropic: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
61
- openai: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
62
- },
59
+ openaiChatUrl: string | undefined,
60
+ openaiResponsesUrl: string | undefined,
61
+ modelResults: ProviderModelTestResults,
63
62
  captureFullDetails: boolean,
64
63
  ): Promise<void> {
65
64
  const usageModel = getModelUsageName(displayName, providerName);
@@ -132,9 +131,9 @@ async function logModelResults(
132
131
  }
133
132
  }
134
133
 
135
- if (openaiUrl !== undefined) {
136
- const nsResult = modelResults.openai.nonStreaming;
137
- const sResult = modelResults.openai.streaming;
134
+ if (openaiChatUrl !== undefined) {
135
+ const nsResult = modelResults.openaiChat.nonStreaming;
136
+ const sResult = modelResults.openaiChat.streaming;
138
137
  if (hasSuccessField(nsResult) && hasSuccessField(sResult)) {
139
138
  const nonStreamingResult = nsResult;
140
139
  const streamingResult = sResult;
@@ -199,6 +198,76 @@ async function logModelResults(
199
198
  });
200
199
  }
201
200
  }
201
+
202
+ if (openaiResponsesUrl !== undefined) {
203
+ const nsResult = modelResults.openaiResponses.nonStreaming;
204
+ const sResult = modelResults.openaiResponses.streaming;
205
+ if (hasSuccessField(nsResult) && hasSuccessField(sResult)) {
206
+ const nonStreamingResult = nsResult;
207
+ const streamingResult = sResult;
208
+
209
+ const requestBody = JSON.stringify(
210
+ buildProviderTestRequestBody(usageModel, "non-streaming", "responses"),
211
+ );
212
+
213
+ await addTestLogEntry({
214
+ timestamp: new Date().toISOString(),
215
+ method: "POST",
216
+ path: "/v1/responses",
217
+ model: nonStreamingResult.model ?? displayName,
218
+ sessionId: providerSessionId,
219
+ rawRequestBody: requestBody,
220
+ responseStatus: getResultResponseStatus(nonStreamingResult),
221
+ responseText: getResultResponseText(nonStreamingResult),
222
+ inputTokens: nonStreamingResult.inputTokens ?? null,
223
+ outputTokens: nonStreamingResult.outputTokens ?? null,
224
+ cacheCreationInputTokens: null,
225
+ cacheReadInputTokens: nonStreamingResult.cacheReadInputTokens ?? null,
226
+ elapsedMs: nonStreamingResult.latencyMs ?? 0,
227
+ firstChunkMs: null,
228
+ totalStreamMs: null,
229
+ tokensPerSecond: null,
230
+ streaming: false,
231
+ userAgent: "provider-test",
232
+ origin: null,
233
+ apiFormat: "openai",
234
+ isTest: true,
235
+ providerName,
236
+ headers: captureFullDetails ? getResultHeaders(nonStreamingResult) : undefined,
237
+ });
238
+
239
+ const streamingRequestBody = JSON.stringify(
240
+ buildProviderTestRequestBody(usageModel, "streaming", "responses"),
241
+ );
242
+
243
+ await addTestLogEntry({
244
+ timestamp: new Date().toISOString(),
245
+ method: "POST",
246
+ path: "/v1/responses",
247
+ model: streamingResult.model ?? displayName,
248
+ sessionId: providerSessionId,
249
+ rawRequestBody: streamingRequestBody,
250
+ responseStatus: getResultResponseStatus(streamingResult),
251
+ responseText: getResultResponseText(streamingResult),
252
+ inputTokens: streamingResult.inputTokens ?? null,
253
+ outputTokens: streamingResult.outputTokens ?? null,
254
+ cacheCreationInputTokens: null,
255
+ cacheReadInputTokens: streamingResult.cacheReadInputTokens ?? null,
256
+ elapsedMs: getResultTotalStreamMs(streamingResult) ?? 0,
257
+ firstChunkMs: getResultFirstChunkMs(streamingResult),
258
+ totalStreamMs: getResultTotalStreamMs(streamingResult),
259
+ tokensPerSecond: getResultTokensPerSecond(streamingResult),
260
+ streaming: true,
261
+ streamingChunks: captureFullDetails ? streamingResult.streamingChunks : undefined,
262
+ userAgent: "provider-test",
263
+ origin: null,
264
+ apiFormat: "openai",
265
+ isTest: true,
266
+ providerName,
267
+ headers: captureFullDetails ? getResultHeaders(streamingResult) : undefined,
268
+ });
269
+ }
270
+ }
202
271
  }
203
272
 
204
273
  export const Route = createFileRoute("/api/providers/$providerId/test/log")({
@@ -232,10 +301,15 @@ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
232
301
  provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl.length > 0
233
302
  ? provider.anthropicBaseUrl
234
303
  : undefined;
235
- const openaiUrl =
304
+ const openaiChatUrl =
236
305
  provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl.length > 0
237
306
  ? provider.openaiBaseUrl
238
307
  : undefined;
308
+ const openaiResponsesUrl =
309
+ provider.openaiResponsesBaseUrl !== undefined &&
310
+ provider.openaiResponsesBaseUrl.length > 0
311
+ ? provider.openaiResponsesBaseUrl
312
+ : undefined;
239
313
 
240
314
  const entries: Promise<void>[] = [];
241
315
  if (results.models !== undefined) {
@@ -246,7 +320,8 @@ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
246
320
  provider.name,
247
321
  providerSessionId,
248
322
  anthropicUrl,
249
- openaiUrl,
323
+ openaiChatUrl,
324
+ openaiResponsesUrl,
250
325
  modelResult,
251
326
  captureFullDetails,
252
327
  ),