diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/docs/docs/education_popes.md b/backend/docs/docs/education_popes.md new file mode 100644 index 000000000..4b6353c05 --- /dev/null +++ b/backend/docs/docs/education_popes.md @@ -0,0 +1,44 @@ +\# Popes Education and Secular Jobs + + + +\## Pope Francis (Jorge Mario Bergoglio) + +\- Education: Philosophy and Theology + +\- Secular jobs: Chemistry lab technician, literature teacher + + + +\## Pope Benedict XVI (Joseph Ratzinger) + +\- Education: Philosophy and Theology + +\- Secular jobs: None (mostly academic) + + + +\## Pope John Paul II (Karol Wojtyła) + +\- Education: Philosophy, Theology, Literature + +\- Secular jobs: Actor, poet + + + +\## Pope John Paul I (Albino Luciani) + +\- Education: Theology + +\- Secular jobs: None + + + +\## Pope Paul VI (Giovanni Battista Montini) + +\- Education: Theology, Canon Law + +\- Secular jobs: Journalist (early career) + + + diff --git a/backend/docs/docs/interrupt_example.md b/backend/docs/docs/interrupt_example.md new file mode 100644 index 000000000..7c23352ff --- /dev/null +++ b/backend/docs/docs/interrupt_example.md @@ -0,0 +1,22 @@ +# LangGraph Interrupt Example + +LangGraph is a Python library for building stateful LLM applications +using graphs or functional workflows. + +This document shows how interrupts can be implemented +using both the Functional API and the Graph API. + +## Functional API + +The Functional API allows defining agent logic as a Python function. +Interrupts are typically implemented using control flow statements. + +```python +from langgraph.functional import agent + +def my_agent(): + while True: + inp = input(">>> ") + if inp == "STOP": + return "Interrupted" + print(f"Echo: {inp}") diff --git a/backend/examples/cli_research.py b/backend/examples/cli_research.py index a086496be..be87efcfe 100644 --- a/backend/examples/cli_research.py +++ b/backend/examples/cli_research.py @@ -1,42 +1,28 @@ +import os import argparse -from langchain_core.messages import HumanMessage from agent.graph import graph +from agent.state import OverallState +from langchain_core.messages import HumanMessage - -def main() -> None: - """Run the research agent from the command line.""" - parser = argparse.ArgumentParser(description="Run the LangGraph research agent") - parser.add_argument("question", help="Research question") - parser.add_argument( - "--initial-queries", - type=int, - default=3, - help="Number of initial search queries", - ) - parser.add_argument( - "--max-loops", - type=int, - default=2, - help="Maximum number of research loops", - ) - parser.add_argument( - "--reasoning-model", - default="gemini-2.5-pro-preview-05-06", - help="Model for the final answer", - ) +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("question", nargs="?", default=None, help="Question to ask") + parser.add_argument("--dir", required=True, help="Directory for local Markdown sources") + parser.add_argument("--loops", type=int, default=3, help="Max research loops") args = parser.parse_args() - state = { - "messages": [HumanMessage(content=args.question)], - "initial_search_query_count": args.initial_queries, - "max_research_loops": args.max_loops, - "reasoning_model": args.reasoning_model, - } + state = OverallState( + messages=[HumanMessage(content=args.question or "")], + search_dir=args.dir, + max_research_loops=args.loops, + research_loop_count=0, + is_sufficient=False, + ) result = graph.invoke(state) - messages = result.get("messages", []) - if messages: - print(messages[-1].content) + + for msg in result["messages"]: + print("\n" + msg.content) if __name__ == "__main__": diff --git a/backend/src/__init__.py b/backend/src/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/agent/graph.py b/backend/src/agent/graph.py index 0f19c3f2f..ffeaa019d 100644 --- a/backend/src/agent/graph.py +++ b/backend/src/agent/graph.py @@ -1,293 +1,98 @@ -import os - -from agent.tools_and_schemas import SearchQueryList, Reflection -from dotenv import load_dotenv +from langgraph.graph import StateGraph, START, END from langchain_core.messages import AIMessage -from langgraph.types import Send -from langgraph.graph import StateGraph -from langgraph.graph import START, END -from langchain_core.runnables import RunnableConfig -from google.genai import Client - -from agent.state import ( - OverallState, - QueryGenerationState, - ReflectionState, - WebSearchState, -) -from agent.configuration import Configuration -from agent.prompts import ( - get_current_date, - query_writer_instructions, - web_searcher_instructions, - reflection_instructions, - answer_instructions, -) -from langchain_google_genai import ChatGoogleGenerativeAI -from agent.utils import ( - get_citations, - get_research_topic, - insert_citation_markers, - resolve_urls, -) - -load_dotenv() - -if os.getenv("GEMINI_API_KEY") is None: - raise ValueError("GEMINI_API_KEY is not set") - -# Used for Google Search API -genai_client = Client(api_key=os.getenv("GEMINI_API_KEY")) +from agent.state import OverallState +from agent.tools_and_schemas import SearchQueryList, Reflection +from agent.llm.groq import GroqLLM -# Nodes -def generate_query(state: OverallState, config: RunnableConfig) -> QueryGenerationState: - """LangGraph node that generates search queries based on the User's question. +from .search.local_markdown import search_markdown - Uses Gemini 2.0 Flash to create an optimized search queries for web research based on - the User's question. +llm = GroqLLM() - Args: - state: Current graph state containing the User's question - config: Configuration for the runnable, including LLM provider settings - Returns: - Dictionary with state update, including search_query key containing the generated queries - """ - configurable = Configuration.from_runnable_config(config) +# ---------- NODES ---------- - # check for custom initial search query count - if state.get("initial_search_query_count") is None: - state["initial_search_query_count"] = configurable.number_of_initial_queries +def generate_search_queries(state: OverallState): + question = state["messages"][-1].content - # init Gemini 2.0 Flash - llm = ChatGoogleGenerativeAI( - model=configurable.query_generator_model, - temperature=1.0, - max_retries=2, - api_key=os.getenv("GEMINI_API_KEY"), - ) - structured_llm = llm.with_structured_output(SearchQueryList) - - # Format the prompt - current_date = get_current_date() - formatted_prompt = query_writer_instructions.format( - current_date=current_date, - research_topic=get_research_topic(state["messages"]), - number_queries=state["initial_search_query_count"], - ) - # Generate the search queries - result = structured_llm.invoke(formatted_prompt) - return {"search_query": result.query} + prompt = f"Generate search queries for: {question}" + queries = llm.invoke_structured(prompt, SearchQueryList) + return { + "search_query": [q.model_dump() for q in queries.query] + } -def continue_to_web_research(state: QueryGenerationState): - """LangGraph node that sends the search queries to the web research node. - This is used to spawn n number of web research nodes, one for each search query. - """ - return [ - Send("web_research", {"search_query": search_query, "id": int(idx)}) - for idx, search_query in enumerate(state["search_query"]) - ] +def web_research(state: OverallState): + all_results = [] + search_dir = state.get("search_dir") + if not search_dir: + return {"web_research_result": [], "sources_gathered": []} -def web_research(state: WebSearchState, config: RunnableConfig) -> OverallState: - """LangGraph node that performs web research using the native Google Search API tool. + for q in state["search_query"]: + query_text = q["query"] + matches = search_markdown(search_dir, query_text) + all_results.extend(matches) - Executes a web search using the native Google Search API tool in combination with Gemini 2.0 Flash. + return { + "web_research_result": all_results, + "sources_gathered": [] + } - Args: - state: Current graph state containing the search query and research loop count - config: Configuration for the runnable, including search API settings - Returns: - Dictionary with state update, including sources_gathered, research_loop_count, and web_research_results - """ - # Configure - configurable = Configuration.from_runnable_config(config) - formatted_prompt = web_searcher_instructions.format( - current_date=get_current_date(), - research_topic=state["search_query"], +def reflect(state: OverallState): + prompt = ( + f"Question:\n{state['messages'][-1].content}\n\n" + f"Research:\n" + "\n".join(state["web_research_result"]) ) - # Uses the google genai client as the langchain client doesn't return grounding metadata - response = genai_client.models.generate_content( - model=configurable.query_generator_model, - contents=formatted_prompt, - config={ - "tools": [{"google_search": {}}], - "temperature": 0, - }, - ) - # resolve the urls to short urls for saving tokens and time - resolved_urls = resolve_urls( - response.candidates[0].grounding_metadata.grounding_chunks, state["id"] - ) - # Gets the citations and adds them to the generated text - citations = get_citations(response, resolved_urls) - modified_text = insert_citation_markers(response.text, citations) - sources_gathered = [item for citation in citations for item in citation["segments"]] + reflection = llm.invoke_structured(prompt, Reflection) return { - "sources_gathered": sources_gathered, - "search_query": [state["search_query"]], - "web_research_result": [modified_text], + "is_sufficient": reflection.is_sufficient, + "follow_up_queries": [ + q.model_dump() for q in reflection.follow_up_queries + ] } -def reflection(state: OverallState, config: RunnableConfig) -> ReflectionState: - """LangGraph node that identifies knowledge gaps and generates potential follow-up queries. - - Analyzes the current summary to identify areas for further research and generates - potential follow-up queries. Uses structured output to extract - the follow-up query in JSON format. - - Args: - state: Current graph state containing the running summary and research topic - config: Configuration for the runnable, including LLM provider settings - - Returns: - Dictionary with state update, including search_query key containing the generated follow-up query - """ - configurable = Configuration.from_runnable_config(config) - # Increment the research loop count and get the reasoning model - state["research_loop_count"] = state.get("research_loop_count", 0) + 1 - reasoning_model = state.get("reasoning_model", configurable.reflection_model) - - # Format the prompt - current_date = get_current_date() - formatted_prompt = reflection_instructions.format( - current_date=current_date, - research_topic=get_research_topic(state["messages"]), - summaries="\n\n---\n\n".join(state["web_research_result"]), - ) - # init Reasoning Model - llm = ChatGoogleGenerativeAI( - model=reasoning_model, - temperature=1.0, - max_retries=2, - api_key=os.getenv("GEMINI_API_KEY"), +def finalize(state: OverallState): + prompt = ( + f"Answer the question:\n" + f"{state['messages'][-1].content}\n\n" + f"Using:\n" + "\n".join(state["web_research_result"]) ) - result = llm.with_structured_output(Reflection).invoke(formatted_prompt) + + answer = llm.invoke(prompt) return { - "is_sufficient": result.is_sufficient, - "knowledge_gap": result.knowledge_gap, - "follow_up_queries": result.follow_up_queries, - "research_loop_count": state["research_loop_count"], - "number_of_ran_queries": len(state["search_query"]), + "messages": [AIMessage(content=answer)] } -def evaluate_research( - state: ReflectionState, - config: RunnableConfig, -) -> OverallState: - """LangGraph routing function that determines the next step in the research flow. - - Controls the research loop by deciding whether to continue gathering information - or to finalize the summary based on the configured maximum number of research loops. - - Args: - state: Current graph state containing the research loop count - config: Configuration for the runnable, including max_research_loops setting - - Returns: - String literal indicating the next node to visit ("web_research" or "finalize_summary") - """ - configurable = Configuration.from_runnable_config(config) - max_research_loops = ( - state.get("max_research_loops") - if state.get("max_research_loops") is not None - else configurable.max_research_loops - ) - if state["is_sufficient"] or state["research_loop_count"] >= max_research_loops: - return "finalize_answer" - else: - return [ - Send( - "web_research", - { - "search_query": follow_up_query, - "id": state["number_of_ran_queries"] + int(idx), - }, - ) - for idx, follow_up_query in enumerate(state["follow_up_queries"]) - ] - - -def finalize_answer(state: OverallState, config: RunnableConfig): - """LangGraph node that finalizes the research summary. +# ---------- GRAPH ---------- - Prepares the final output by deduplicating and formatting sources, then - combining them with the running summary to create a well-structured - research report with proper citations. +def build_graph(): + graph = StateGraph(OverallState) - Args: - state: Current graph state containing the running summary and sources gathered + graph.add_node("generate_queries", generate_search_queries) + graph.add_node("research", web_research) + graph.add_node("reflect", reflect) + graph.add_node("finalize", finalize) - Returns: - Dictionary with state update, including running_summary key containing the formatted final summary with sources - """ - configurable = Configuration.from_runnable_config(config) - reasoning_model = state.get("reasoning_model") or configurable.answer_model + graph.add_edge(START, "generate_queries") + graph.add_edge("generate_queries", "research") + graph.add_edge("research", "reflect") - # Format the prompt - current_date = get_current_date() - formatted_prompt = answer_instructions.format( - current_date=current_date, - research_topic=get_research_topic(state["messages"]), - summaries="\n---\n\n".join(state["web_research_result"]), + graph.add_conditional_edges( + "reflect", + lambda state: "finalize" ) - # init Reasoning Model, default to Gemini 2.5 Flash - llm = ChatGoogleGenerativeAI( - model=reasoning_model, - temperature=0, - max_retries=2, - api_key=os.getenv("GEMINI_API_KEY"), - ) - result = llm.invoke(formatted_prompt) - - # Replace the short urls with the original urls and add all used urls to the sources_gathered - unique_sources = [] - for source in state["sources_gathered"]: - if source["short_url"] in result.content: - result.content = result.content.replace( - source["short_url"], source["value"] - ) - unique_sources.append(source) - - return { - "messages": [AIMessage(content=result.content)], - "sources_gathered": unique_sources, - } - - -# Create our Agent Graph -builder = StateGraph(OverallState, config_schema=Configuration) + graph.add_edge("finalize", END) -# Define the nodes we will cycle between -builder.add_node("generate_query", generate_query) -builder.add_node("web_research", web_research) -builder.add_node("reflection", reflection) -builder.add_node("finalize_answer", finalize_answer) + return graph.compile() -# Set the entrypoint as `generate_query` -# This means that this node is the first one called -builder.add_edge(START, "generate_query") -# Add conditional edge to continue with search queries in a parallel branch -builder.add_conditional_edges( - "generate_query", continue_to_web_research, ["web_research"] -) -# Reflect on the web research -builder.add_edge("web_research", "reflection") -# Evaluate the research -builder.add_conditional_edges( - "reflection", evaluate_research, ["web_research", "finalize_answer"] -) -# Finalize the answer -builder.add_edge("finalize_answer", END) -graph = builder.compile(name="pro-search-agent") +graph = build_graph() diff --git a/backend/src/agent/llm/__init__.py b/backend/src/agent/llm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/agent/llm/groq.py b/backend/src/agent/llm/groq.py new file mode 100644 index 000000000..55c7a1a20 --- /dev/null +++ b/backend/src/agent/llm/groq.py @@ -0,0 +1,58 @@ +import json +import re +from groq import Groq +from dotenv import load_dotenv +import os + +load_dotenv() + + +class GroqLLM: + def __init__(self, model="llama-3.3-70b-versatile"): + self.client = Groq(api_key=os.getenv("GROQ_API_KEY")) + self.model = model + + def invoke(self, prompt: str) -> str: + response = self.client.chat.completions.create( + model=self.model, + messages=[{"role": "user", "content": prompt}], + temperature=0, + ) + return response.choices[0].message.content + + def invoke_structured(self, prompt: str, schema): + structured_prompt = f""" +You MUST return ONLY valid JSON. +Do NOT include explanations, markdown, or extra text. + +JSON schema: +{schema.model_json_schema()} + +User request: +{prompt} +""" + + response = self.client.chat.completions.create( + model=self.model, + messages=[{"role": "user", "content": structured_prompt}], + temperature=0, + ) + + content = response.choices[0].message.content.strip() + + match = re.search(r"\{.*\}", content, re.DOTALL) + if not match: + raise ValueError( + f"Groq did not return JSON.\nRaw output:\n{content}" + ) + + json_str = match.group(0) + + try: + data = json.loads(json_str) + except json.JSONDecodeError as e: + raise ValueError( + f"Invalid JSON from Groq:\n{json_str}" + ) from e + + return schema.model_validate(data) diff --git a/backend/src/agent/search/__init__.py b/backend/src/agent/search/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/agent/search/local_markdown.py b/backend/src/agent/search/local_markdown.py new file mode 100644 index 000000000..546eb74e9 --- /dev/null +++ b/backend/src/agent/search/local_markdown.py @@ -0,0 +1,45 @@ +from pathlib import Path +import re +from typing import List + + +def text_to_paragraphs(text: str): + + return [p.strip() for p in text.split("\n\n") if p.strip()] + +def score_paragraph(query_tokens, paragraph): + + p = paragraph.lower() + return sum(1 for token in query_tokens if token in p) + +def search_markdown(search_dir: str, query: str) -> List[str]: + """ + Very simple local markdown search: + - scans all .md files + - returns paragraphs containing query keywords + """ + + results: List[str] = [] + path = Path(search_dir) + + if not path.exists(): + return results + + keywords = [w.lower() for w in query.split() if len(w) > 3] + + for md_file in path.rglob("*.md"): + text = md_file.read_text(encoding="utf-8", errors="ignore") + paragraphs = text.split("\n\n") + + for p in paragraphs: + content = p.strip() + if not content: + continue + + score = sum(k in content.lower() for k in keywords) + if score >= 1: + results.append( + f"[{md_file.name}]\n{content}" + ) + + return results \ No newline at end of file diff --git a/backend/src/agent/state.py b/backend/src/agent/state.py index d5ad4dcd8..03be9ab21 100644 --- a/backend/src/agent/state.py +++ b/backend/src/agent/state.py @@ -1,48 +1,21 @@ from __future__ import annotations -from dataclasses import dataclass, field -from typing import TypedDict - -from langgraph.graph import add_messages +from typing import TypedDict, List from typing_extensions import Annotated - - +from langgraph.graph import add_messages import operator class OverallState(TypedDict): messages: Annotated[list, add_messages] - search_query: Annotated[list, operator.add] - web_research_result: Annotated[list, operator.add] - sources_gathered: Annotated[list, operator.add] - initial_search_query_count: int - max_research_loops: int - research_loop_count: int - reasoning_model: str + search_query: Annotated[List[dict], operator.add] + web_research_result: Annotated[List[str], operator.add] + sources_gathered: Annotated[List[str], operator.add] -class ReflectionState(TypedDict): - is_sufficient: bool - knowledge_gap: str - follow_up_queries: Annotated[list, operator.add] + initial_search_query_count: int + max_research_loops: int research_loop_count: int - number_of_ran_queries: int - - -class Query(TypedDict): - query: str - rationale: str - - -class QueryGenerationState(TypedDict): - search_query: list[Query] - -class WebSearchState(TypedDict): - search_query: str - id: str - - -@dataclass(kw_only=True) -class SearchStateOutput: - running_summary: str = field(default=None) # Final report + reasoning_model: str + search_dir: str diff --git a/backend/src/agent/tools_and_schemas.py b/backend/src/agent/tools_and_schemas.py index 5e683c341..47780d64a 100644 --- a/backend/src/agent/tools_and_schemas.py +++ b/backend/src/agent/tools_and_schemas.py @@ -2,22 +2,16 @@ from pydantic import BaseModel, Field +class SearchQuery(BaseModel): + query: str + rationale: str + + class SearchQueryList(BaseModel): - query: List[str] = Field( - description="A list of search queries to be used for web research." - ) - rationale: str = Field( - description="A brief explanation of why these queries are relevant to the research topic." - ) + query: List[SearchQuery] class Reflection(BaseModel): - is_sufficient: bool = Field( - description="Whether the provided summaries are sufficient to answer the user's question." - ) - knowledge_gap: str = Field( - description="A description of what information is missing or needs clarification." - ) - follow_up_queries: List[str] = Field( - description="A list of follow-up queries to address the knowledge gap." - ) + is_sufficient: bool = Field(..., description="Is research sufficient?") + knowledge_gap: str = Field(..., description="What is missing?") + follow_up_queries: List[SearchQuery] = Field(default_factory=list) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 48599091d..d7c25984a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -449,9 +449,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -491,13 +491,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -506,19 +506,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -566,19 +569,22 @@ } }, "node_modules/@eslint/js": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", - "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -586,13 +592,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -704,9 +710,9 @@ } }, "node_modules/@langchain/core": { - "version": "0.3.55", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.55.tgz", - "integrity": "sha512-SojY2ugpT6t9eYfFB9Ysvyhhyh+KJTGXs50hdHUE9tAEQWp3WAwoxe4djwJnOZ6fSpWYdpFt2UT2ksHVDy2vXA==", + "version": "0.3.80", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.80.tgz", + "integrity": "sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA==", "license": "MIT", "dependencies": { "@cfworker/json-schema": "^4.0.2", @@ -714,12 +720,12 @@ "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", - "langsmith": "^0.3.16", + "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", - "zod": "^3.22.4", + "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" }, "engines": { @@ -2259,6 +2265,7 @@ "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2268,6 +2275,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2278,6 +2286,7 @@ "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -2336,6 +2345,7 @@ "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/types": "8.31.1", @@ -2439,9 +2449,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2526,11 +2536,12 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2637,9 +2648,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2805,21 +2816,25 @@ "license": "MIT" }, "node_modules/console-table-printer": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz", - "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz", + "integrity": "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==", "license": "MIT", "dependencies": { - "simple-wcswidth": "^1.0.1" + "simple-wcswidth": "^1.1.2" } }, "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cross-spawn": { @@ -2993,33 +3008,33 @@ } }, "node_modules/eslint": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", - "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.25.1", - "@eslint/plugin-kit": "^0.2.8", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3077,9 +3092,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3094,9 +3109,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3107,15 +3122,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3596,9 +3611,9 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -3640,23 +3655,34 @@ } }, "node_modules/langsmith": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.25.tgz", - "integrity": "sha512-KuJu89VY3DmCdFvlVxQG4owQl546Z6pQc6TbhsyP77MkVJgZr8yvevZvvcXDWIpT2o2s52c9Aww2XVOH6GmHxQ==", + "version": "0.3.87", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.87.tgz", + "integrity": "sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==", "license": "MIT", "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", - "p-retry": "4", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, "openai": { "optional": true } @@ -4045,9 +4071,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4879,6 +4905,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4888,6 +4915,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -4970,14 +4998,13 @@ } }, "node_modules/react-router": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz", - "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0", - "turbo-stream": "2.4.0" + "set-cookie-parser": "^2.6.0" }, "engines": { "node": ">=20.0.0" @@ -4993,12 +5020,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz", - "integrity": "sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", + "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", "license": "MIT", "dependencies": { - "react-router": "7.5.3" + "react-router": "7.12.0" }, "engines": { "node": ">=20.0.0" @@ -5175,9 +5202,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, "node_modules/shebang-command": { @@ -5204,9 +5231,9 @@ } }, "node_modules/simple-wcswidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", - "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", + "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", "license": "MIT" }, "node_modules/source-map-js": { @@ -5345,6 +5372,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5404,12 +5432,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/turbo-stream": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", - "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", - "license": "ISC" - }, "node_modules/tw-animate-css": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.2.9.tgz", @@ -5439,6 +5461,7 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5659,10 +5682,11 @@ } }, "node_modules/vite": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", - "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -5751,6 +5775,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5798,10 +5823,11 @@ } }, "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }