@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.
- package/.output/nitro.json +17 -0
- package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
- package/.output/public/assets/index-B3RwBPLW.css +1 -0
- package/.output/public/assets/index-s4lwsWvq.js +97 -0
- package/.output/public/assets/main-Cp8AM0Pa.js +17 -0
- package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
- package/.output/server/_chunks/ssr-renderer.mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
- package/.output/server/_libs/ajv-formats.mjs +330 -0
- package/.output/server/_libs/ajv.mjs +11444 -0
- package/.output/server/_libs/aria-hidden.mjs +122 -0
- package/.output/server/_libs/atomically.mjs +152 -0
- package/.output/server/_libs/bail.mjs +8 -0
- package/.output/server/_libs/character-entities.mjs +2130 -0
- package/.output/server/_libs/class-variance-authority.mjs +44 -0
- package/.output/server/_libs/clsx.mjs +16 -0
- package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
- package/.output/server/_libs/conf.mjs +635 -0
- package/.output/server/_libs/cookie-es.mjs +58 -0
- package/.output/server/_libs/core-util-is.mjs +75 -0
- package/.output/server/_libs/croner.mjs +1 -0
- package/.output/server/_libs/crossws.mjs +1 -0
- package/.output/server/_libs/debounce-fn.mjs +69 -0
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
- package/.output/server/_libs/detect-node-es.mjs +1 -0
- package/.output/server/_libs/devlop.mjs +8 -0
- package/.output/server/_libs/dot-prop.mjs +265 -0
- package/.output/server/_libs/env-paths.mjs +57 -0
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
- package/.output/server/_libs/extend.mjs +97 -0
- package/.output/server/_libs/fast-deep-equal.mjs +38 -0
- package/.output/server/_libs/fast-uri.mjs +812 -0
- package/.output/server/_libs/floating-ui__core.mjs +725 -0
- package/.output/server/_libs/floating-ui__dom.mjs +622 -0
- package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
- package/.output/server/_libs/floating-ui__utils.mjs +320 -0
- package/.output/server/_libs/get-nonce.mjs +9 -0
- package/.output/server/_libs/h3-v2.mjs +276 -0
- package/.output/server/_libs/h3.mjs +400 -0
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
- package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
- package/.output/server/_libs/hookable.mjs +1 -0
- package/.output/server/_libs/html-url-attributes.mjs +26 -0
- package/.output/server/_libs/immediate.mjs +74 -0
- package/.output/server/_libs/inherits.mjs +50 -0
- package/.output/server/_libs/inline-style-parser.mjs +142 -0
- package/.output/server/_libs/is-plain-obj.mjs +10 -0
- package/.output/server/_libs/isarray.mjs +14 -0
- package/.output/server/_libs/isbot.mjs +20 -0
- package/.output/server/_libs/json-schema-traverse.mjs +180 -0
- package/.output/server/_libs/jszip.mjs +3049 -0
- package/.output/server/_libs/lie.mjs +273 -0
- package/.output/server/_libs/lucide-react.mjs +368 -0
- package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
- package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
- package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
- package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
- package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
- package/.output/server/_libs/micromark-factory-label.mjs +63 -0
- package/.output/server/_libs/micromark-factory-space.mjs +24 -0
- package/.output/server/_libs/micromark-factory-title.mjs +65 -0
- package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
- package/.output/server/_libs/micromark-util-character.mjs +44 -0
- package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
- package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
- package/.output/server/_libs/micromark-util-encode.mjs +1 -0
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
- package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
- package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
- package/.output/server/_libs/micromark.mjs +906 -0
- package/.output/server/_libs/mimic-function.mjs +47 -0
- package/.output/server/_libs/ohash.mjs +1 -0
- package/.output/server/_libs/pako.mjs +4223 -0
- package/.output/server/_libs/process-nextick-args.mjs +48 -0
- package/.output/server/_libs/property-information.mjs +1209 -0
- package/.output/server/_libs/radix-ui.mjs +1 -0
- package/.output/server/_libs/radix-ui__number.mjs +6 -0
- package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
- package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
- package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
- package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
- package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
- package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
- package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
- package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
- package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
- package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
- package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
- package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
- package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
- package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
- package/.output/server/_libs/radix-ui__react-toast.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
- package/.output/server/_libs/react-dom.mjs +9935 -0
- package/.output/server/_libs/react-markdown.mjs +147 -0
- package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
- package/.output/server/_libs/react-remove-scroll.mjs +328 -0
- package/.output/server/_libs/react-style-singleton.mjs +69 -0
- package/.output/server/_libs/react.mjs +515 -0
- package/.output/server/_libs/readable-stream.mjs +1518 -0
- package/.output/server/_libs/remark-parse.mjs +19 -0
- package/.output/server/_libs/remark-rehype.mjs +21 -0
- package/.output/server/_libs/rou3.mjs +8 -0
- package/.output/server/_libs/safe-buffer.mjs +64 -0
- package/.output/server/_libs/semver.mjs +1984 -0
- package/.output/server/_libs/seroval-plugins.mjs +58 -0
- package/.output/server/_libs/seroval.mjs +1765 -0
- package/.output/server/_libs/setimmediate.mjs +1 -0
- package/.output/server/_libs/space-separated-tokens.mjs +6 -0
- package/.output/server/_libs/srvx.mjs +334 -0
- package/.output/server/_libs/stubborn-fs.mjs +91 -0
- package/.output/server/_libs/stubborn-utils.mjs +66 -0
- package/.output/server/_libs/style-to-js.mjs +72 -0
- package/.output/server/_libs/style-to-object.mjs +38 -0
- package/.output/server/_libs/tailwind-merge.mjs +3010 -0
- package/.output/server/_libs/tanstack__history.mjs +217 -0
- package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
- package/.output/server/_libs/tanstack__react-store.mjs +1 -0
- package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
- package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
- package/.output/server/_libs/tanstack__store.mjs +1 -0
- package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
- package/.output/server/_libs/tiny-invariant.mjs +12 -0
- package/.output/server/_libs/tiny-warning.mjs +5 -0
- package/.output/server/_libs/trim-lines.mjs +41 -0
- package/.output/server/_libs/trough.mjs +85 -0
- package/.output/server/_libs/tslib.mjs +576 -0
- package/.output/server/_libs/ufo.mjs +54 -0
- package/.output/server/_libs/uint8array-extras.mjs +69 -0
- package/.output/server/_libs/unctx.mjs +1 -0
- package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
- package/.output/server/_libs/unified.mjs +661 -0
- package/.output/server/_libs/unist-util-is.mjs +100 -0
- package/.output/server/_libs/unist-util-position.mjs +27 -0
- package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
- package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
- package/.output/server/_libs/unist-util-visit.mjs +24 -0
- package/.output/server/_libs/unstorage.mjs +1 -0
- package/.output/server/_libs/use-callback-ref.mjs +66 -0
- package/.output/server/_libs/use-sidecar.mjs +106 -0
- package/.output/server/_libs/use-sync-external-store.mjs +1 -0
- package/.output/server/_libs/util-deprecate.mjs +12 -0
- package/.output/server/_libs/vfile-message.mjs +138 -0
- package/.output/server/_libs/vfile.mjs +467 -0
- package/.output/server/_libs/when-exit.mjs +53 -0
- package/.output/server/_libs/zod.mjs +4460 -0
- package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
- package/.output/server/_ssr/index.mjs +1176 -0
- package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
- package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
- package/.output/server/index.mjs +393 -0
- package/README.md +196 -0
- package/package.json +91 -0
- package/src/assets/logos/alibaba.svg +1 -0
- package/src/assets/logos/anthropic.svg +1 -0
- package/src/assets/logos/deepseek.svg +1 -0
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +1 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/assets/logos/zhipuai.svg +219 -0
- package/src/cli.ts +68 -0
- package/src/components/ProxyViewer.tsx +325 -0
- package/src/components/ProxyViewerContainer.tsx +211 -0
- package/src/components/providers/ProviderCard.tsx +186 -0
- package/src/components/providers/ProviderForm.tsx +259 -0
- package/src/components/providers/ProviderLogo.tsx +111 -0
- package/src/components/providers/ProvidersPanel.tsx +259 -0
- package/src/components/providers/SettingsDialog.tsx +39 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
- package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
- package/src/components/proxy-viewer/LogEntry.tsx +225 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
- package/src/components/proxy-viewer/ResponseView.tsx +161 -0
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
- package/src/components/proxy-viewer/formats/index.tsx +24 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
- package/src/components/proxy-viewer/index.ts +8 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/collapsible.tsx +21 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/json-viewer.tsx +464 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +178 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/tabs.tsx +88 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/index.css +11 -0
- package/src/lib/export-logs.ts +51 -0
- package/src/lib/utils.ts +22 -0
- package/src/proxy/chunkStorage.ts +118 -0
- package/src/proxy/constants.ts +36 -0
- package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
- package/src/proxy/formats/anthropic/handler.ts +74 -0
- package/src/proxy/formats/anthropic/index.ts +14 -0
- package/src/proxy/formats/anthropic/register.ts +4 -0
- package/src/proxy/formats/anthropic/schemas.ts +217 -0
- package/src/proxy/formats/anthropic/stream.ts +167 -0
- package/src/proxy/formats/handler.ts +46 -0
- package/src/proxy/formats/index.ts +12 -0
- package/src/proxy/formats/jsonSchema.ts +24 -0
- package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
- package/src/proxy/formats/openai/handler.ts +70 -0
- package/src/proxy/formats/openai/index.ts +25 -0
- package/src/proxy/formats/openai/provider.ts +50 -0
- package/src/proxy/formats/openai/register.ts +4 -0
- package/src/proxy/formats/openai/schemas.ts +150 -0
- package/src/proxy/formats/openai/stream.ts +153 -0
- package/src/proxy/formats/protocol.ts +50 -0
- package/src/proxy/formats/providerRegistry.ts +51 -0
- package/src/proxy/formats/providers/index.ts +3 -0
- package/src/proxy/formats/registry.ts +61 -0
- package/src/proxy/handler.ts +389 -0
- package/src/proxy/logIndex.ts +187 -0
- package/src/proxy/logger.ts +99 -0
- package/src/proxy/providers.ts +234 -0
- package/src/proxy/schemas.ts +160 -0
- package/src/proxy/socketTracker.ts +158 -0
- package/src/proxy/store.ts +386 -0
- package/src/router.tsx +16 -0
- package/src/routes/__root.tsx +38 -0
- package/src/routes/api/config.paths.ts +14 -0
- package/src/routes/api/health.ts +11 -0
- package/src/routes/api/logs.$id.chunks.ts +36 -0
- package/src/routes/api/logs.$id.replay.ts +262 -0
- package/src/routes/api/logs.$id.ts +22 -0
- package/src/routes/api/logs.stream.ts +64 -0
- package/src/routes/api/logs.ts +30 -0
- package/src/routes/api/models.ts +10 -0
- package/src/routes/api/providers.$providerId.ts +45 -0
- package/src/routes/api/providers.ts +37 -0
- package/src/routes/api/sessions.ts +10 -0
- package/src/routes/index.tsx +6 -0
- package/src/routes/proxy/$.ts +15 -0
- package/styles/globals.css +121 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import Conf from "conf";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
|
|
5
|
+
export const ProviderConfigSchema = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
name: z.string(),
|
|
8
|
+
apiKey: z.string(),
|
|
9
|
+
model: z.string().optional(),
|
|
10
|
+
/** API format: "anthropic" or "openai" */
|
|
11
|
+
format: z.enum(["anthropic", "openai"]).optional(),
|
|
12
|
+
/** Base URL for the provider (deprecated - use anthropicBaseUrl/openaiBaseUrl instead) */
|
|
13
|
+
baseUrl: z.string().optional(),
|
|
14
|
+
/** Anthropic API base URL */
|
|
15
|
+
anthropicBaseUrl: z.string().optional(),
|
|
16
|
+
/** OpenAI API base URL */
|
|
17
|
+
openaiBaseUrl: z.string().optional(),
|
|
18
|
+
/** Auth header to use: "bearer" (default) or "x-api-key" */
|
|
19
|
+
authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
20
|
+
createdAt: z.string(),
|
|
21
|
+
updatedAt: z.string(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type ProviderConfig = z.infer<typeof ProviderConfigSchema>;
|
|
25
|
+
|
|
26
|
+
const ProvidersStoreSchema = z.object({
|
|
27
|
+
providers: z.array(ProviderConfigSchema),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
type ProvidersStore = z.infer<typeof ProvidersStoreSchema>;
|
|
31
|
+
|
|
32
|
+
// Using conf for storage - works in any Node.js environment without Electron
|
|
33
|
+
// Note: conf stores data in plain JSON. For production, consider additional encryption.
|
|
34
|
+
export const store = new Conf<ProvidersStore>({
|
|
35
|
+
projectName: "llm-inspector",
|
|
36
|
+
defaults: {
|
|
37
|
+
providers: [],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
|
|
43
|
+
* Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
|
|
44
|
+
*/
|
|
45
|
+
function migrateProviders(): void {
|
|
46
|
+
const providers = store.get("providers", []);
|
|
47
|
+
let migrated = false;
|
|
48
|
+
|
|
49
|
+
const updated = providers.map((p) => {
|
|
50
|
+
// Helper to safely get old URL properties using Object.getOwnPropertyDescriptor
|
|
51
|
+
function getOldUrl(obj: unknown, key: string): string {
|
|
52
|
+
if (obj !== null && typeof obj === "object") {
|
|
53
|
+
const desc = Object.getOwnPropertyDescriptor(obj, key);
|
|
54
|
+
if (desc !== undefined && typeof desc.value === "string") {
|
|
55
|
+
return desc.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const oldAnthropicBaseUrl = getOldUrl(p, "anthropicBaseUrl");
|
|
62
|
+
const oldOpenaiBaseUrl = getOldUrl(p, "openaiBaseUrl");
|
|
63
|
+
|
|
64
|
+
// If already migrated (has format field and both URLs), skip
|
|
65
|
+
if (p.format !== undefined && oldAnthropicBaseUrl === "" && oldOpenaiBaseUrl === "") {
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Preserve both URLs
|
|
70
|
+
const newAnthropicUrl =
|
|
71
|
+
oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : (p.anthropicBaseUrl ?? "");
|
|
72
|
+
const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : (p.openaiBaseUrl ?? "");
|
|
73
|
+
|
|
74
|
+
// Determine primary format based on which URL is set
|
|
75
|
+
let format: "anthropic" | "openai" | undefined;
|
|
76
|
+
let baseUrl: string | undefined;
|
|
77
|
+
|
|
78
|
+
if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
|
|
79
|
+
// Both URLs set - prefer anthropic as default, use baseUrl for backward compat
|
|
80
|
+
format = p.format ?? "anthropic";
|
|
81
|
+
baseUrl = p.baseUrl !== undefined && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
|
|
82
|
+
} else if (newOpenaiUrl !== "") {
|
|
83
|
+
format = "openai";
|
|
84
|
+
baseUrl = newOpenaiUrl;
|
|
85
|
+
} else if (newAnthropicUrl !== "") {
|
|
86
|
+
format = "anthropic";
|
|
87
|
+
baseUrl = newAnthropicUrl;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
migrated = true;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...p,
|
|
94
|
+
format,
|
|
95
|
+
baseUrl,
|
|
96
|
+
anthropicBaseUrl: newAnthropicUrl,
|
|
97
|
+
openaiBaseUrl: newOpenaiUrl,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (migrated) {
|
|
102
|
+
store.set("providers", updated);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Run migration on module load
|
|
107
|
+
migrateProviders();
|
|
108
|
+
|
|
109
|
+
export function getProviders(): ProviderConfig[] {
|
|
110
|
+
return store.get("providers", []);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getProvider(id: string): ProviderConfig | undefined {
|
|
114
|
+
const providers = getProviders();
|
|
115
|
+
return providers.find((p) => p.id === id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Normalizes an API key by stripping "Bearer " prefix if present.
|
|
120
|
+
*/
|
|
121
|
+
function normalizeApiKey(apiKey: string): string {
|
|
122
|
+
return apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function addProvider(
|
|
126
|
+
name: string,
|
|
127
|
+
apiKey: string,
|
|
128
|
+
format: "anthropic" | "openai",
|
|
129
|
+
baseUrl?: string,
|
|
130
|
+
model?: string,
|
|
131
|
+
authHeader?: "bearer" | "x-api-key",
|
|
132
|
+
): ProviderConfig {
|
|
133
|
+
const providers = getProviders();
|
|
134
|
+
const now = new Date().toISOString();
|
|
135
|
+
const newProvider: ProviderConfig = {
|
|
136
|
+
id: randomUUID(),
|
|
137
|
+
name,
|
|
138
|
+
apiKey: normalizeApiKey(apiKey),
|
|
139
|
+
format,
|
|
140
|
+
baseUrl,
|
|
141
|
+
model,
|
|
142
|
+
authHeader: authHeader ?? "bearer",
|
|
143
|
+
createdAt: now,
|
|
144
|
+
updatedAt: now,
|
|
145
|
+
};
|
|
146
|
+
providers.push(newProvider);
|
|
147
|
+
store.set("providers", providers);
|
|
148
|
+
return newProvider;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function updateProvider(
|
|
152
|
+
id: string,
|
|
153
|
+
updates: Partial<Omit<ProviderConfig, "id" | "createdAt">>,
|
|
154
|
+
): ProviderConfig | null {
|
|
155
|
+
const providers = getProviders();
|
|
156
|
+
const existing = providers.find((p) => p.id === id);
|
|
157
|
+
if (!existing) return null;
|
|
158
|
+
|
|
159
|
+
const updated: ProviderConfig = {
|
|
160
|
+
id: existing.id,
|
|
161
|
+
name: updates.name ?? existing.name,
|
|
162
|
+
apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
|
|
163
|
+
model: updates.model !== undefined ? updates.model : existing.model,
|
|
164
|
+
format: updates.format ?? existing.format,
|
|
165
|
+
baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
|
|
166
|
+
authHeader: updates.authHeader ?? existing.authHeader,
|
|
167
|
+
createdAt: existing.createdAt,
|
|
168
|
+
updatedAt: new Date().toISOString(),
|
|
169
|
+
};
|
|
170
|
+
const index = providers.findIndex((p) => p.id === id);
|
|
171
|
+
providers[index] = updated;
|
|
172
|
+
store.set("providers", providers);
|
|
173
|
+
return updated;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function deleteProvider(id: string): boolean {
|
|
177
|
+
const providers = getProviders();
|
|
178
|
+
const filtered = providers.filter((p) => p.id !== id);
|
|
179
|
+
if (filtered.length === providers.length) return false;
|
|
180
|
+
|
|
181
|
+
store.set("providers", filtered);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Converts display model name to API usage name based on provider-specific rules.
|
|
187
|
+
* Default behavior: returns model as-is.
|
|
188
|
+
* MiniMax: replaces spaces with hyphens.
|
|
189
|
+
*/
|
|
190
|
+
export function getModelUsageName(model: string, providerName?: string): string {
|
|
191
|
+
if (
|
|
192
|
+
providerName !== undefined &&
|
|
193
|
+
providerName !== "" &&
|
|
194
|
+
providerName.toLowerCase().includes("minimax")
|
|
195
|
+
) {
|
|
196
|
+
return model.replace(/ /g, "-");
|
|
197
|
+
}
|
|
198
|
+
return model;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Normalize a model name for comparison: lowercase, replace whitespace with hyphens.
|
|
203
|
+
*/
|
|
204
|
+
function normalizeModelName(name: string): string {
|
|
205
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Finds a provider by model name using two strategies:
|
|
210
|
+
* 1. Case-insensitive prefix match against "{provider.name}-" (e.g., "deepseek-" matches "deepseek-*")
|
|
211
|
+
* 2. Case-insensitive match against provider.model field (with whitespace/hyphen normalization)
|
|
212
|
+
*/
|
|
213
|
+
export function findProviderByModel(model: string): ProviderConfig | null {
|
|
214
|
+
const providers = getProviders();
|
|
215
|
+
const modelLower = model.toLowerCase();
|
|
216
|
+
const modelNormalized = normalizeModelName(model);
|
|
217
|
+
|
|
218
|
+
for (const provider of providers) {
|
|
219
|
+
// Strategy 1: provider name prefix match
|
|
220
|
+
const providerPrefix = (provider.name + "-").toLowerCase();
|
|
221
|
+
if (modelLower.startsWith(providerPrefix)) {
|
|
222
|
+
return provider;
|
|
223
|
+
}
|
|
224
|
+
// Strategy 2: match against provider.model field with normalization
|
|
225
|
+
if (
|
|
226
|
+
provider.model !== undefined &&
|
|
227
|
+
provider.model !== "" &&
|
|
228
|
+
modelNormalized === normalizeModelName(provider.model)
|
|
229
|
+
) {
|
|
230
|
+
return provider;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Import JsonValueSchema from shared location to avoid circular dependency
|
|
4
|
+
import { JsonValueSchema, type JsonValue } from "./formats/jsonSchema";
|
|
5
|
+
|
|
6
|
+
export { JsonValueSchema };
|
|
7
|
+
export type { JsonValue };
|
|
8
|
+
|
|
9
|
+
export type RequestFormat = "anthropic" | "openai" | "unknown";
|
|
10
|
+
|
|
11
|
+
function safeGetProperty(obj: unknown, key: string): unknown {
|
|
12
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return undefined;
|
|
13
|
+
const desc = Object.getOwnPropertyDescriptor(obj, key);
|
|
14
|
+
return desc?.value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A single SSE event from a streaming response.
|
|
19
|
+
*/
|
|
20
|
+
export const StreamingChunkSchema = z.object({
|
|
21
|
+
index: z.number(),
|
|
22
|
+
timestamp: z.number(),
|
|
23
|
+
type: z.string(),
|
|
24
|
+
data: JsonValueSchema,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type StreamingChunk = z.infer<typeof StreamingChunkSchema>;
|
|
28
|
+
|
|
29
|
+
const StreamingChunksArraySchema = z.object({
|
|
30
|
+
chunks: z.array(StreamingChunkSchema),
|
|
31
|
+
truncated: z.boolean().optional().default(false),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const CapturedLogSchema = z.object({
|
|
35
|
+
id: z.number(),
|
|
36
|
+
timestamp: z.string(),
|
|
37
|
+
method: z.string(),
|
|
38
|
+
path: z.string(),
|
|
39
|
+
model: z.string().nullable(),
|
|
40
|
+
sessionId: z.string().nullable(),
|
|
41
|
+
rawRequestBody: z.string().nullable(),
|
|
42
|
+
responseStatus: z.number().nullable(),
|
|
43
|
+
responseText: z.string().nullable(),
|
|
44
|
+
inputTokens: z.number().nullable(),
|
|
45
|
+
outputTokens: z.number().nullable(),
|
|
46
|
+
cacheCreationInputTokens: z.number().nullable(),
|
|
47
|
+
cacheReadInputTokens: z.number().nullable(),
|
|
48
|
+
elapsedMs: z.number().nullable(),
|
|
49
|
+
streaming: z.boolean(),
|
|
50
|
+
userAgent: z.string().nullable(),
|
|
51
|
+
origin: z.string().nullable(),
|
|
52
|
+
rawHeaders: z.record(z.string(), z.string()).optional(),
|
|
53
|
+
/** Headers sent to the upstream LLM */
|
|
54
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
55
|
+
apiFormat: z.enum(["anthropic", "openai", "unknown"]).default("unknown"),
|
|
56
|
+
isTest: z.boolean().optional().default(false),
|
|
57
|
+
providerName: z.string().nullable().optional(),
|
|
58
|
+
clientPort: z.number().nullable().optional(),
|
|
59
|
+
clientPid: z.number().nullable().optional(),
|
|
60
|
+
clientCwd: z.string().nullable().optional(),
|
|
61
|
+
clientProjectFolder: z.string().nullable().optional(),
|
|
62
|
+
streamingChunks: StreamingChunksArraySchema.optional(),
|
|
63
|
+
streamingChunksPath: z.string().nullable().optional(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export type CapturedLog = z.infer<typeof CapturedLogSchema>;
|
|
67
|
+
|
|
68
|
+
export type TokenUsage = {
|
|
69
|
+
inputTokens: number | null;
|
|
70
|
+
outputTokens: number | null;
|
|
71
|
+
cacheCreationInputTokens: number | null;
|
|
72
|
+
cacheReadInputTokens: number | null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ============================================================
|
|
76
|
+
// Import types and schemas from formats
|
|
77
|
+
// ============================================================
|
|
78
|
+
|
|
79
|
+
// Import types and schemas from formats for internal use
|
|
80
|
+
import { InspectorRequestSchema, type InspectorRequest } from "./formats/anthropic/schemas";
|
|
81
|
+
|
|
82
|
+
// Re-export types and schemas from formats for backward compatibility
|
|
83
|
+
export {
|
|
84
|
+
InspectorRequestSchema,
|
|
85
|
+
InspectorResponseSchema,
|
|
86
|
+
SseEventSchema,
|
|
87
|
+
} from "./formats/anthropic/schemas";
|
|
88
|
+
export type {
|
|
89
|
+
InspectorRequest,
|
|
90
|
+
InspectorResponse,
|
|
91
|
+
ResponseContentBlockType,
|
|
92
|
+
} from "./formats/anthropic/schemas";
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
OpenAIRequestSchema,
|
|
96
|
+
OpenAIResponseSchema,
|
|
97
|
+
OpenAISSERawChunkSchema,
|
|
98
|
+
type OpenAIResponse,
|
|
99
|
+
parseOpenAIResponse,
|
|
100
|
+
} from "./formats/openai/schemas";
|
|
101
|
+
|
|
102
|
+
// ============================================================
|
|
103
|
+
// Utility functions
|
|
104
|
+
// ============================================================
|
|
105
|
+
|
|
106
|
+
function detectFormat(rawBody: string | null): RequestFormat {
|
|
107
|
+
if (rawBody === null) return "unknown";
|
|
108
|
+
try {
|
|
109
|
+
const json: unknown = JSON.parse(rawBody);
|
|
110
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
|
|
111
|
+
const keys = Object.keys(json);
|
|
112
|
+
if (keys.includes("model") && keys.includes("messages")) {
|
|
113
|
+
if (keys.includes("system") || keys.includes("tools")) {
|
|
114
|
+
return "anthropic";
|
|
115
|
+
}
|
|
116
|
+
const msgVal = safeGetProperty(json, "messages");
|
|
117
|
+
if (Array.isArray(msgVal)) {
|
|
118
|
+
return "openai";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return "unknown";
|
|
123
|
+
} catch {
|
|
124
|
+
return "unknown";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Schema for model name in request body (used for routing)
|
|
129
|
+
const RequestModelSchema = z.object({
|
|
130
|
+
model: z.string(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export function extractModelFromBody(body: string): string | null {
|
|
134
|
+
try {
|
|
135
|
+
const json: unknown = JSON.parse(body);
|
|
136
|
+
const parsed = RequestModelSchema.safeParse(json);
|
|
137
|
+
if (parsed.success) {
|
|
138
|
+
return parsed.data.model;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Parse a raw request body into an InspectorRequest.
|
|
148
|
+
* @deprecated Use FormatHandler.parseRequest() instead
|
|
149
|
+
*/
|
|
150
|
+
export function parseRequest(rawBody: string | null): InspectorRequest | null {
|
|
151
|
+
if (rawBody === null) return null;
|
|
152
|
+
try {
|
|
153
|
+
const json: unknown = JSON.parse(rawBody);
|
|
154
|
+
const result = InspectorRequestSchema.safeParse(json);
|
|
155
|
+
if (result.success) return result.data;
|
|
156
|
+
return null;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
type ClientInfo = {
|
|
7
|
+
port: number | null;
|
|
8
|
+
pid: number | null;
|
|
9
|
+
cwd: string | null;
|
|
10
|
+
projectFolder: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type CacheEntry = ClientInfo & {
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Cache entries: port -> { pid, cwd, projectFolder, expiresAt }
|
|
18
|
+
const cache = new Map<number, CacheEntry>();
|
|
19
|
+
|
|
20
|
+
// Cache TTL in milliseconds (5 minutes)
|
|
21
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
function getFromCache(port: number): ClientInfo | null {
|
|
24
|
+
const entry = cache.get(port);
|
|
25
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
26
|
+
return { port: entry.port, pid: entry.pid, cwd: entry.cwd, projectFolder: entry.projectFolder };
|
|
27
|
+
}
|
|
28
|
+
// Expired or not found
|
|
29
|
+
if (entry) cache.delete(port);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setCache(port: number, info: ClientInfo): void {
|
|
34
|
+
cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the remote port from a Request's underlying socket.
|
|
39
|
+
* Works with Bun, Node.js, and Nitro's event.node.req.
|
|
40
|
+
*/
|
|
41
|
+
function extractRemotePort(request: Request): number | null {
|
|
42
|
+
// Try Bun's socket property by accessing the underlying socket
|
|
43
|
+
// Bun's Request has a socket property with remotePort that standard Request doesn't have
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
45
|
+
const socket = (request as any).socket;
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
47
|
+
const remotePort = (socket as any)?.remotePort;
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
49
|
+
if (remotePort !== undefined && remotePort !== null) return remotePort;
|
|
50
|
+
|
|
51
|
+
// Try to get from headers (some proxies add x-forwarded-port)
|
|
52
|
+
// This is a fallback - not all requests will have this
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Look up PID from a local port using netstat.
|
|
59
|
+
* Returns PID as number, or null if not found.
|
|
60
|
+
*/
|
|
61
|
+
async function lookupPidByPort(port: number): Promise<number | null> {
|
|
62
|
+
try {
|
|
63
|
+
// netstat -aon finds all connections with port, -n avoids DNS resolution
|
|
64
|
+
// Filter by ESTABLISHED connections on the specific port
|
|
65
|
+
const { stdout } = await execAsync(`netstat -aon | findstr :${port} | findstr ESTABLISHED`, {
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
// Format: TCP 127.0.0.1:12345 127.0.0.1:54321 ESTABLISHED 12345
|
|
72
|
+
// The last space-separated token is the PID
|
|
73
|
+
const parts = line.trim().split(/\s+/);
|
|
74
|
+
const lastPart = parts[parts.length - 1];
|
|
75
|
+
if (lastPart !== undefined) {
|
|
76
|
+
const pid = parseInt(lastPart, 10);
|
|
77
|
+
if (!isNaN(pid) && pid > 0) return pid;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the working directory and command line for a process by PID.
|
|
88
|
+
* Returns { cwd, projectFolder } or { cwd: null, projectFolder: null }.
|
|
89
|
+
*/
|
|
90
|
+
async function lookupProcessInfo(
|
|
91
|
+
pid: number,
|
|
92
|
+
): Promise<{ cwd: string | null; projectFolder: string | null }> {
|
|
93
|
+
try {
|
|
94
|
+
// Use wmic to get the full command line
|
|
95
|
+
const { stdout } = await execAsync(
|
|
96
|
+
`wmic process where processid=${pid} get commandline /value`,
|
|
97
|
+
{ windowsHide: true },
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
if (line.includes("=")) {
|
|
103
|
+
const commandLine = (line.split("=")[1] ?? "").trim();
|
|
104
|
+
if (commandLine) {
|
|
105
|
+
// Extract CWD from command line
|
|
106
|
+
// The command typically looks like: "C:\path\to\ai-tool\resources\app\bin\cli\win32-x64\tool.exe" ...
|
|
107
|
+
// or "C:\Users\user\AppData\Local\Programs\Claude\Claude.exe" --some-flag
|
|
108
|
+
// We need to find the working directory - it's usually the directory containing the exe
|
|
109
|
+
// but we also need to check if a --parent-dir flag was passed
|
|
110
|
+
|
|
111
|
+
// Parse the command line to find the executable path
|
|
112
|
+
const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
|
|
113
|
+
if (exeMatch && exeMatch[1] !== undefined) {
|
|
114
|
+
const exePath = exeMatch[1];
|
|
115
|
+
const exeDir = exePath.substring(0, exePath.lastIndexOf("\\"));
|
|
116
|
+
return {
|
|
117
|
+
cwd: exeDir,
|
|
118
|
+
projectFolder: exeDir.split("\\").pop() ?? null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { cwd: null, projectFolder: null };
|
|
125
|
+
} catch {
|
|
126
|
+
return { cwd: null, projectFolder: null };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get client info (PID, CWD, project folder) from a request's source port.
|
|
132
|
+
* Uses caching to avoid repeated netstat/wmic calls.
|
|
133
|
+
*/
|
|
134
|
+
export async function getClientInfo(request: Request): Promise<ClientInfo> {
|
|
135
|
+
const port = extractRemotePort(request);
|
|
136
|
+
if (port === null) {
|
|
137
|
+
return { port: null, pid: null, cwd: null, projectFolder: null };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check cache first
|
|
141
|
+
const cached = getFromCache(port);
|
|
142
|
+
if (cached) return cached;
|
|
143
|
+
|
|
144
|
+
// Lookup PID from port
|
|
145
|
+
const pid = await lookupPidByPort(port);
|
|
146
|
+
if (pid === null) {
|
|
147
|
+
const info = { port, pid: null, cwd: null, projectFolder: null };
|
|
148
|
+
setCache(port, info);
|
|
149
|
+
return info;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Lookup process info from PID
|
|
153
|
+
const { cwd, projectFolder } = await lookupProcessInfo(pid);
|
|
154
|
+
|
|
155
|
+
const info = { port, pid, cwd, projectFolder };
|
|
156
|
+
setCache(port, info);
|
|
157
|
+
return info;
|
|
158
|
+
}
|