chore(llm): fix type issues

This commit is contained in:
Elian Doran
2026-03-29 12:24:13 +03:00
parent 9ddcaf4552
commit 4c4a29f9cf
5 changed files with 89 additions and 36 deletions

View File

@@ -19,7 +19,8 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
search: null,
text: null,
webView: null,
spreadsheet: null
spreadsheet: null,
llmChat: null
};
export const byBookType: Record<ViewTypeOptions, string | null> = {

View File

@@ -16,8 +16,9 @@ export interface ChatConfig {
}
export interface Citation {
url: string;
url?: string;
title?: string;
citedText?: string;
}
export interface StreamCallbacks {
@@ -91,7 +92,9 @@ export async function streamChatCompletion(
callbacks.onToolResult?.(data.toolName, data.result);
break;
case "citation":
callbacks.onCitation?.({ url: data.url, title: data.title });
if (data.citation) {
callbacks.onCitation?.(data.citation);
}
break;
case "error":
callbacks.onError(data.error);

View File

@@ -88,18 +88,39 @@ export default function ChatMessage({ message, isStreaming }: Props) {
{t("llm_chat.sources")}
</div>
<ul className="llm-chat-citations-list">
{message.citations.map((citation, idx) => (
<li key={idx}>
<a
href={citation.url}
target="_blank"
rel="noopener noreferrer"
title={citation.url}
>
{citation.title || new URL(citation.url).hostname}
</a>
</li>
))}
{message.citations.map((citation, idx) => {
// Determine display text: title, URL hostname, or cited text
let displayText = citation.title;
if (!displayText && citation.url) {
try {
displayText = new URL(citation.url).hostname;
} catch {
displayText = citation.url;
}
}
if (!displayText) {
displayText = citation.citedText?.slice(0, 50) || `Source ${idx + 1}`;
}
return (
<li key={idx}>
{citation.url ? (
<a
href={citation.url}
target="_blank"
rel="noopener noreferrer"
title={citation.citedText || citation.url}
>
{displayText}
</a>
) : (
<span title={citation.citedText}>
{displayText}
</span>
)}
</li>
);
})}
</ul>
</div>
)}

View File

@@ -1,9 +1,20 @@
import Anthropic from "@anthropic-ai/sdk";
import type { LlmProvider, LlmMessage, LlmStreamChunk, LlmProviderConfig } from "../types.js";
import type { LlmMessage, LlmProvider, LlmProviderConfig,LlmStreamChunk } from "../types.js";
const DEFAULT_MODEL = "claude-sonnet-4-20250514";
const DEFAULT_MAX_TOKENS = 8096;
/**
* Server-side web search tool type.
* Not yet in SDK types as of @anthropic-ai/sdk.
*/
interface WebSearchTool {
type: "web_search_20250305";
name: "web_search";
max_uses?: number;
}
export class AnthropicProvider implements LlmProvider {
name = "anthropic";
private client: Anthropic;
@@ -23,19 +34,18 @@ export class AnthropicProvider implements LlmProvider {
const systemPrompt = config.systemPrompt || messages.find(m => m.role === "system")?.content;
const chatMessages = messages.filter(m => m.role !== "system");
// Build tools array - using 'unknown' assertion for server-side tools
// that may not be in the SDK types yet
const tools: unknown[] = [];
// Build tools array
// Using union with WebSearchTool since it's not in SDK types yet
const tools: (Anthropic.ToolUnion | WebSearchTool)[] = [];
if (config.enableWebSearch) {
tools.push({
type: "web_search_20250305",
name: "web_search",
max_uses: 5 // Limit searches per request
});
} satisfies WebSearchTool);
}
try {
// Cast tools to any since server-side tools may not be in SDK types yet
const streamParams: Anthropic.Messages.MessageStreamParams = {
model: config.model || DEFAULT_MODEL,
max_tokens: config.maxTokens || DEFAULT_MAX_TOKENS,
@@ -47,7 +57,8 @@ export class AnthropicProvider implements LlmProvider {
};
if (tools.length > 0) {
(streamParams as any).tools = tools;
// Cast needed until SDK adds WebSearchTool type
streamParams.tools = tools as Anthropic.ToolUnion[];
}
// Enable extended thinking for deeper reasoning
@@ -55,7 +66,7 @@ export class AnthropicProvider implements LlmProvider {
const thinkingBudget = config.thinkingBudget || 10000;
// max_tokens must be greater than thinking budget
streamParams.max_tokens = Math.max(streamParams.max_tokens, thinkingBudget + 4000);
(streamParams as any).thinking = {
streamParams.thinking = {
type: "enabled",
budget_tokens: thinkingBudget
};
@@ -82,7 +93,7 @@ export class AnthropicProvider implements LlmProvider {
if (delta.type === "text_delta") {
yield { type: "text", content: delta.text };
} else if (delta.type === "thinking_delta") {
yield { type: "thinking", content: (delta as any).thinking };
yield { type: "thinking", content: delta.thinking };
} else if (delta.type === "input_json_delta") {
// Tool input is being streamed - we could accumulate it
// For now, we already emitted tool_use at start
@@ -102,17 +113,21 @@ export class AnthropicProvider implements LlmProvider {
// Get the final message to extract any citations
const finalMessage = await stream.finalMessage();
for (const block of finalMessage.content) {
if (block.type === "text") {
// Check for citations in the text block
// Anthropic returns citations as part of the content
if ("citations" in block && Array.isArray((block as any).citations)) {
for (const citation of (block as any).citations) {
yield {
type: "citation",
url: citation.url || citation.source,
title: citation.title
};
}
if (block.type === "text" && block.citations) {
for (const citation of block.citations) {
// Extract citation info from SDK types (CitationCharLocation, etc.)
// These have: cited_text, document_index, document_title
// Web search citations may have additional properties at runtime
const citationData = citation as unknown as Record<string, unknown>;
yield {
type: "citation",
citation: {
title: citation.document_title ?? undefined,
citedText: citation.cited_text,
// URL may be present for web search results (not in SDK types yet)
url: typeof citationData.url === "string" ? citationData.url : undefined
}
};
}
}
}

View File

@@ -8,6 +8,19 @@ export interface LlmMessage {
content: string;
}
/**
* Citation information extracted from LLM responses.
* May include URL (for web search) or document metadata (for document citations).
*/
export interface LlmCitation {
/** Source URL (typically from web search) */
url?: string;
/** Document or page title */
title?: string;
/** The text that was cited */
citedText?: string;
}
/**
* Stream chunk types for real-time updates.
*/
@@ -16,7 +29,7 @@ export type LlmStreamChunk =
| { type: "thinking"; content: string }
| { type: "tool_use"; toolName: string; toolInput: Record<string, unknown> }
| { type: "tool_result"; toolName: string; result: string }
| { type: "citation"; url: string; title?: string }
| { type: "citation"; citation: LlmCitation }
| { type: "error"; error: string }
| { type: "done" };