diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx
index 34c4773f4b..26386cb090 100644
--- a/frontend/app/aipanel/aimessage.tsx
+++ b/frontend/app/aipanel/aimessage.tsx
@@ -1,11 +1,13 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
+import { WaveStreamdown } from "@/app/element/streamdown";
import { cn } from "@/util/util";
+import { useAtomValue } from "jotai";
import { memo } from "react";
-import { Streamdown } from "streamdown";
import { getFileIcon } from "./ai-utils";
import { WaveUIMessage, WaveUIMessagePart } from "./aitypes";
+import { WaveAIModel } from "./waveai-model";
const AIThinking = memo(() => (
@@ -72,6 +74,8 @@ interface AIMessagePartProps {
}
const AIMessagePart = memo(({ part, role, isStreaming }: AIMessagePartProps) => {
+ const model = WaveAIModel.getInstance();
+
if (part.type === "text") {
const content = part.text ?? "";
@@ -79,23 +83,12 @@ const AIMessagePart = memo(({ part, role, isStreaming }: AIMessagePartProps) =>
return
{content}
;
} else {
return (
-
- {content}
-
+ className="text-gray-100"
+ codeBlockMaxWidthAtom={model.codeBlockMaxWidth}
+ />
);
}
}
@@ -139,9 +132,7 @@ export const AIMessage = memo(({ message, isStreaming }: AIMessageProps) => {
{showThinkingOnly ? (
diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx
index a5d30d8440..d92c029130 100644
--- a/frontend/app/aipanel/aipanel.tsx
+++ b/frontend/app/aipanel/aipanel.tsx
@@ -34,6 +34,7 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
const [isDragOver, setIsDragOver] = useState(false);
const [isLoadingChat, setIsLoadingChat] = useState(true);
const model = WaveAIModel.getInstance();
+ const containerRef = useRef
(null);
const errorMessage = jotai.useAtomValue(model.errorMessage);
const realMessageRef = useRef(null);
const inputRef = useRef(null);
@@ -104,10 +105,32 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
const messages = await model.loadChat();
setMessages(messages as any);
setIsLoadingChat(false);
+ setTimeout(() => {
+ model.scrollToBottom();
+ }, 100);
};
loadMessages();
}, [model, setMessages]);
+ useEffect(() => {
+ const updateWidth = () => {
+ if (containerRef.current) {
+ globalStore.set(model.containerWidth, containerRef.current.offsetWidth);
+ }
+ };
+
+ updateWidth();
+
+ const resizeObserver = new ResizeObserver(updateWidth);
+ if (containerRef.current) {
+ resizeObserver.observe(containerRef.current);
+ }
+
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, [model]);
+
useEffect(() => {
model.ensureRateLimitSet();
}, [model]);
@@ -279,6 +302,7 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
return (
{
return (
@@ -25,6 +26,7 @@ interface AIPanelMessagesProps {
}
export const AIPanelMessages = memo(({ messages, status, isLoadingChat }: AIPanelMessagesProps) => {
+ const model = WaveAIModel.getInstance();
const isPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
const messagesEndRef = useRef
(null);
const messagesContainerRef = useRef(null);
@@ -37,6 +39,10 @@ export const AIPanelMessages = memo(({ messages, status, isLoadingChat }: AIPane
}
};
+ useEffect(() => {
+ model.registerScrollToBottom(scrollToBottom);
+ }, [model]);
+
useEffect(() => {
scrollToBottom();
}, [messages]);
diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx
index 36f0ecd9eb..852d0a11ae 100644
--- a/frontend/app/aipanel/waveai-model.tsx
+++ b/frontend/app/aipanel/waveai-model.tsx
@@ -24,12 +24,15 @@ export interface DroppedFile {
export class WaveAIModel {
private static instance: WaveAIModel | null = null;
private inputRef: React.RefObject | null = null;
+ private scrollToBottomCallback: (() => void) | null = null;
widgetAccessAtom!: jotai.Atom;
droppedFiles: jotai.PrimitiveAtom = jotai.atom([]);
chatId!: jotai.PrimitiveAtom;
errorMessage: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom;
modelAtom!: jotai.Atom;
+ containerWidth: jotai.PrimitiveAtom = jotai.atom(0);
+ codeBlockMaxWidth!: jotai.Atom;
private constructor() {
const tabId = globalStore.get(atoms.staticTabId);
@@ -58,6 +61,11 @@ export class WaveAIModel {
const value = get(widgetAccessMetaAtom);
return value ?? true;
});
+
+ this.codeBlockMaxWidth = jotai.atom((get) => {
+ const width = get(this.containerWidth);
+ return width > 0 ? width - 35 : 0;
+ });
}
static getInstance(): WaveAIModel {
@@ -140,6 +148,14 @@ export class WaveAIModel {
this.inputRef = ref;
}
+ registerScrollToBottom(callback: () => void) {
+ this.scrollToBottomCallback = callback;
+ }
+
+ scrollToBottom() {
+ this.scrollToBottomCallback?.();
+ }
+
focusInput() {
if (!WorkspaceLayoutModel.getInstance().getAIPanelVisible()) {
WorkspaceLayoutModel.getInstance().setAIPanelVisible(true);
diff --git a/frontend/app/element/streamdown.tsx b/frontend/app/element/streamdown.tsx
new file mode 100644
index 0000000000..ba848ee904
--- /dev/null
+++ b/frontend/app/element/streamdown.tsx
@@ -0,0 +1,317 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { CopyButton } from "@/app/element/copybutton";
+import { IconButton } from "@/app/element/iconbutton";
+import { cn, useAtomValueSafe } from "@/util/util";
+import type { Atom } from "jotai";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { bundledLanguages, codeToHtml } from "shiki/bundle/web";
+import { Streamdown } from "streamdown";
+import { throttle } from "throttle-debounce";
+
+const ShikiTheme = "github-dark-high-contrast";
+
+function extractText(node: React.ReactNode): string {
+ if (node == null || typeof node === "boolean") return "";
+ if (typeof node === "string" || typeof node === "number") return String(node);
+ if (Array.isArray(node)) return node.map(extractText).join("");
+ // @ts-expect-error props exists on ReactElement
+ if (typeof node === "object" && node.props) return extractText(node.props.children);
+ return "";
+}
+
+function CodePlain({ className = "", isCodeBlock, text }: { className?: string; isCodeBlock: boolean; text: string }) {
+ if (isCodeBlock) {
+ return {text};
+ }
+
+ return (
+
+ {text}
+
+ );
+}
+
+function CodeHighlight({ className = "", lang, text }: { className?: string; lang: string; text: string }) {
+ const [html, setHtml] = useState("");
+ const [hasError, setHasError] = useState(false);
+ const codeRef = useRef(null);
+ const seqRef = useRef(0);
+
+ const highlightCode = useCallback(
+ async (textToHighlight: string, language: string, disposedRef: { current: boolean }, seq: number) => {
+ try {
+ const full = await codeToHtml(textToHighlight, { lang: language, theme: ShikiTheme });
+ const start = full.indexOf("", start);
+ const end = full.lastIndexOf("");
+ const inner = start !== -1 && open !== -1 && end !== -1 ? full.slice(open + 1, end) : "";
+ if (!disposedRef.current && seq === seqRef.current) {
+ setHtml(inner);
+ setHasError(false);
+ }
+ } catch (e) {
+ if (!disposedRef.current && seq === seqRef.current) {
+ setHasError(true);
+ }
+ console.warn(`Shiki highlight failed for ${language}`, e);
+ }
+ },
+ []
+ );
+
+ const throttledHighlight = useMemo(() => throttle(300, highlightCode, { noLeading: false }), [highlightCode]);
+
+ useEffect(() => {
+ const disposedRef = { current: false };
+
+ if (!text) {
+ setHtml("");
+ return;
+ }
+
+ seqRef.current++;
+ const currentSeq = seqRef.current;
+ throttledHighlight(text, lang, disposedRef, currentSeq);
+
+ return () => {
+ disposedRef.current = true;
+ };
+ }, [text, lang, throttledHighlight]);
+
+ if (hasError) {
+ return (
+
+ {text}
+
+ );
+ }
+
+ if (!html && text) {
+ return (
+
+ {text}
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+export function Code({ className = "", children }: { className?: string; children: React.ReactNode }) {
+ const m = className?.match(/language-([\w+-]+)/i);
+ const isCodeBlock = !!m;
+ const lang = m?.[1] || "text";
+ const text = extractText(children);
+
+ if (isCodeBlock && lang in bundledLanguages) {
+ return ;
+ }
+
+ return ;
+}
+
+type CodeBlockProps = {
+ children: React.ReactNode;
+ onClickExecute?: (cmd: string) => void;
+ codeBlockMaxWidthAtom?: Atom;
+};
+
+const CodeBlock = ({ children, onClickExecute, codeBlockMaxWidthAtom }: CodeBlockProps) => {
+ const codeBlockMaxWidth = useAtomValueSafe(codeBlockMaxWidthAtom);
+ const getLanguage = (children: any): string => {
+ if (children?.props?.className) {
+ const match = children.props.className.match(/language-([\w+-]+)/i);
+ if (match) return match[1];
+ }
+ return "text";
+ };
+
+ const handleCopy = async (e: React.MouseEvent) => {
+ const textToCopy = extractText(children).replace(/\n$/, "");
+ await navigator.clipboard.writeText(textToCopy);
+ };
+
+ const handleExecute = (e: React.MouseEvent) => {
+ const cmd = extractText(children).replace(/\n$/, "");
+ if (onClickExecute) {
+ onClickExecute(cmd);
+ return;
+ }
+ };
+
+ const language = getLanguage(children);
+
+ return (
+
+
+
{language}
+
+
+ {onClickExecute && (
+
+ )}
+
+
+
{children}
+
+ );
+};
+
+function Collapsible({ title, children, defaultOpen = false }) {
+ const [isOpen, setIsOpen] = useState(defaultOpen);
+
+ return (
+
+
+ {isOpen &&
{children}
}
+
+ );
+}
+
+interface WaveStreamdownProps {
+ text: string;
+ parseIncompleteMarkdown?: boolean;
+ className?: string;
+ onClickExecute?: (cmd: string) => void;
+ codeBlockMaxWidthAtom?: Atom;
+}
+
+export const WaveStreamdown = ({
+ text,
+ parseIncompleteMarkdown,
+ className,
+ onClickExecute,
+ codeBlockMaxWidthAtom,
+}: WaveStreamdownProps) => {
+ const components = useMemo(
+ () => ({
+ code: Code,
+ pre: (props: React.HTMLAttributes) => (
+
+ ),
+ p: (props: React.HTMLAttributes) => ,
+ h1: (props: React.HTMLAttributes) => (
+
+ ),
+ h2: (props: React.HTMLAttributes) => (
+
+ ),
+ h3: (props: React.HTMLAttributes) => (
+
+ ),
+ h4: (props: React.HTMLAttributes) => (
+
+ ),
+ h5: (props: React.HTMLAttributes) => (
+
+ ),
+ h6: (props: React.HTMLAttributes) => (
+
+ ),
+ table: (props: React.HTMLAttributes) => (
+
+ ),
+ thead: (props: React.HTMLAttributes) => (
+
+ ),
+ tbody: (props: React.HTMLAttributes) => ,
+ tr: (props: React.HTMLAttributes) => (
+
+ ),
+ th: (props: React.HTMLAttributes) => (
+ |
+ ),
+ td: (props: React.HTMLAttributes) => (
+ |
+ ),
+ ul: (props: React.HTMLAttributes) => (
+
+ ),
+ ol: (props: React.HTMLAttributes) => (
+
+ ),
+ li: (props: React.HTMLAttributes) => (
+
+ ),
+ blockquote: (props: React.HTMLAttributes) => (
+
+ ),
+ details: ({ children, ...props }) => {
+ const childArray = Array.isArray(children) ? children : [children];
+
+ // Extract summary text and content
+ const summary = childArray.find((c) => c?.props?.node?.tagName === "summary");
+ const summaryText = summary?.props?.children || "Details";
+ const content = childArray.filter((c) => c?.props?.node?.tagName !== "summary");
+
+ return (
+
+ {content}
+
+ );
+ },
+ summary: () => null, // Don't render summary separately
+ a: (props: React.AnchorHTMLAttributes) => (
+
+ ),
+ strong: (props: React.HTMLAttributes) => (
+
+ ),
+ em: (props: React.HTMLAttributes) => ,
+ }),
+ [onClickExecute, codeBlockMaxWidthAtom]
+ );
+
+ return (
+ *:first-child]:mt-0 [&>*:first-child>*:first-child]:mt-0",
+ className
+ )}
+ shikiTheme={[ShikiTheme, ShikiTheme]}
+ controls={{
+ code: false,
+ table: false,
+ mermaid: true,
+ }}
+ mermaidConfig={{
+ theme: "dark",
+ darkMode: true,
+ }}
+ defaultOrigin="http://localhost"
+ components={components}
+ >
+ {text}
+
+ );
+};
diff --git a/frontend/tailwindsetup.css b/frontend/tailwindsetup.css
index 686c9c1fab..7f43b55ec7 100644
--- a/frontend/tailwindsetup.css
+++ b/frontend/tailwindsetup.css
@@ -2,6 +2,7 @@
SPDX-License-Identifier: Apache-2.0 */
@import "tailwindcss";
+@source "../node_modules/streamdown/dist/index.js";
@theme {
--color-background: rgb(34, 34, 34);
diff --git a/package-lock.json b/package-lock.json
index 399e74aee6..e26ff08a02 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -75,6 +75,7 @@
"remark-github-blockquote-alert": "^1.3.1",
"rxjs": "^7.8.2",
"shell-quote": "^1.8.3",
+ "shiki": "^3.13.0",
"sprintf-js": "^1.1.3",
"streamdown": "^1.3.0",
"tailwind-merge": "^3.3.1",
@@ -8464,60 +8465,60 @@
]
},
"node_modules/@shikijs/core": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.12.2.tgz",
- "integrity": "sha512-L1Safnhra3tX/oJK5kYHaWmLEBJi1irASwewzY3taX5ibyXyMkkSDZlq01qigjryOBwrXSdFgTiZ3ryzSNeu7Q==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.13.0.tgz",
+ "integrity": "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.12.2",
+ "@shikijs/types": "3.13.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
}
},
"node_modules/@shikijs/engine-javascript": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.12.2.tgz",
- "integrity": "sha512-Nm3/azSsaVS7hk6EwtHEnTythjQfwvrO5tKqMlaH9TwG1P+PNaR8M0EAKZ+GaH2DFwvcr4iSfTveyxMIvXEHMw==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.13.0.tgz",
+ "integrity": "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.12.2",
+ "@shikijs/types": "3.13.0",
"@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.3"
}
},
"node_modules/@shikijs/engine-oniguruma": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.12.2.tgz",
- "integrity": "sha512-hozwnFHsLvujK4/CPVHNo3Bcg2EsnG8krI/ZQ2FlBlCRpPZW4XAEQmEwqegJsypsTAN9ehu2tEYe30lYKSZW/w==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz",
+ "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.12.2",
+ "@shikijs/types": "3.13.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/langs": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.12.2.tgz",
- "integrity": "sha512-bVx5PfuZHDSHoBal+KzJZGheFuyH4qwwcwG/n+MsWno5cTlKmaNtTsGzJpHYQ8YPbB5BdEdKU1rga5/6JGY8ww==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz",
+ "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.12.2"
+ "@shikijs/types": "3.13.0"
}
},
"node_modules/@shikijs/themes": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.12.2.tgz",
- "integrity": "sha512-fTR3QAgnwYpfGczpIbzPjlRnxyONJOerguQv1iwpyQZ9QXX4qy/XFQqXlf17XTsorxnHoJGbH/LXBvwtqDsF5A==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz",
+ "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.12.2"
+ "@shikijs/types": "3.13.0"
}
},
"node_modules/@shikijs/types": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.12.2.tgz",
- "integrity": "sha512-K5UIBzxCyv0YoxN3LMrKB9zuhp1bV+LgewxuVwHdl4Gz5oePoUFrr9EfgJlGlDeXCU1b/yhdnXeuRvAnz8HN8Q==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz",
+ "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
@@ -30548,17 +30549,17 @@
}
},
"node_modules/shiki": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.12.2.tgz",
- "integrity": "sha512-uIrKI+f9IPz1zDT+GMz+0RjzKJiijVr6WDWm9Pe3NNY6QigKCfifCEv9v9R2mDASKKjzjQ2QpFLcxaR3iHSnMA==",
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.13.0.tgz",
+ "integrity": "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==",
"license": "MIT",
"dependencies": {
- "@shikijs/core": "3.12.2",
- "@shikijs/engine-javascript": "3.12.2",
- "@shikijs/engine-oniguruma": "3.12.2",
- "@shikijs/langs": "3.12.2",
- "@shikijs/themes": "3.12.2",
- "@shikijs/types": "3.12.2",
+ "@shikijs/core": "3.13.0",
+ "@shikijs/engine-javascript": "3.13.0",
+ "@shikijs/engine-oniguruma": "3.13.0",
+ "@shikijs/langs": "3.13.0",
+ "@shikijs/themes": "3.13.0",
+ "@shikijs/types": "3.13.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
}
diff --git a/package.json b/package.json
index 66c5ec476a..637eef3941 100644
--- a/package.json
+++ b/package.json
@@ -151,6 +151,7 @@
"remark-github-blockquote-alert": "^1.3.1",
"rxjs": "^7.8.2",
"shell-quote": "^1.8.3",
+ "shiki": "^3.13.0",
"sprintf-js": "^1.1.3",
"streamdown": "^1.3.0",
"tailwind-merge": "^3.3.1",
diff --git a/pkg/aiusechat/usechat.go b/pkg/aiusechat/usechat.go
index b0300f8f65..18f73c89d1 100644
--- a/pkg/aiusechat/usechat.go
+++ b/pkg/aiusechat/usechat.go
@@ -68,6 +68,7 @@ var SystemPromptText_OpenAI = strings.Join([]string{
"Reserve inline code (single backticks) for short references like command names (`grep`, `less`), flags, env vars, file paths, or tiny snippets not meant to be executed.",
`You may use Markdown (lists, tables, bold/italics) to improve readability.`,
`Never comment on or justify your formatting choices; just follow these rules.`,
+ `When generating code or command blocks, try to keep lines under ~100 characters wide where practical (soft wrap; do not break tokens mid-word). Favor indentation and short variable names to stay compact, but correctness always takes priority.`,
// Safety & limits
`If a request would execute dangerous or destructive actions, warn briefly and provide a safer alternative.`,
@@ -206,7 +207,7 @@ func processToolResults(stopReason *uctypes.WaveStopReason, chatOpts uctypes.Wav
log.Printf("TOOLUSE name=%s id=%s input=%s\n", toolCall.Name, toolCall.ID, utilfn.TruncateString(string(inputJSON), 40))
result := ResolveToolCall(toolCall, chatOpts)
toolResults = append(toolResults, result)
-
+
// Track tool usage by ToolLogName
toolDef := getToolDefinition(toolCall.Name, chatOpts)
if toolDef != nil && toolDef.ToolLogName != "" {
@@ -434,7 +435,7 @@ func WaveAIPostMessageWrap(ctx context.Context, sseHandler *sse.SSEHandlerCh, me
log.Printf("metrics: requests=%d tools=%d premium=%d proxy=%d images=%d pdfs=%d textdocs=%d textlen=%d duration=%dms error=%v\n",
metrics.RequestCount, metrics.ToolUseCount, metrics.PremiumReqCount, metrics.ProxyReqCount,
metrics.ImageCount, metrics.PDFCount, metrics.TextDocCount, metrics.TextLen, metrics.RequestDuration, metrics.HadError)
-
+
sendAIMetricsTelemetry(ctx, metrics)
}
return err
diff --git a/tsconfig.json b/tsconfig.json
index 92c7ae5b98..950b6fecb6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,7 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
- "moduleResolution": "node",
+ "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true,