diff --git a/apps/rowboat/app/projects/[projectId]/sources/[sourceId]/text-source.tsx b/apps/rowboat/app/projects/[projectId]/sources/[sourceId]/text-source.tsx
new file mode 100644
index 00000000..d16549f6
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/sources/[sourceId]/text-source.tsx
@@ -0,0 +1,128 @@
+"use client";
+import { PageSection } from "../../../../lib/components/page-section";
+import { WithStringId } from "../../../../lib/types/types";
+import { DataSource } from "../../../../lib/types/datasource_types";
+import { z } from "zod";
+import { useState, useEffect } from "react";
+import { Textarea } from "@heroui/react";
+import { FormStatusButton } from "../../../../lib/components/form-status-button";
+import { Spinner } from "@heroui/react";
+import { addDocsToDataSource, deleteDocsFromDataSource, listDocsInDataSource } from "../../../../actions/datasource_actions";
+
+export function TextSource({
+ projectId,
+ dataSource,
+ handleReload,
+}: {
+ projectId: string,
+ dataSource: WithStringId
>,
+ handleReload: () => void;
+}) {
+ const [content, setContent] = useState("");
+ const [docId, setDocId] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isSaving, setIsSaving] = useState(false);
+
+ useEffect(() => {
+ let ignore = false;
+
+ async function fetchContent() {
+ setIsLoading(true);
+ try {
+ const { files } = await listDocsInDataSource({
+ projectId,
+ sourceId: dataSource._id,
+ limit: 1,
+ });
+
+ console.log('got data', files);
+
+ if (!ignore && files.length > 0) {
+ const doc = files[0];
+ if (doc.data.type === 'text') {
+ setContent(doc.data.content);
+ setDocId(doc._id);
+ }
+ }
+ } catch (error) {
+ console.error('Error fetching content:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ fetchContent();
+ return () => {
+ ignore = true;
+ };
+ }, [projectId, dataSource._id]);
+
+ async function handleSubmit(formData: FormData) {
+ setIsSaving(true);
+ try {
+ const newContent = formData.get('content') as string;
+
+ // Delete existing doc if it exists
+ if (docId) {
+ await deleteDocsFromDataSource({
+ projectId,
+ sourceId: dataSource._id,
+ docIds: [docId],
+ });
+ }
+
+ // Add new doc
+ await addDocsToDataSource({
+ projectId,
+ sourceId: dataSource._id,
+ docData: [{
+ name: 'text',
+ data: {
+ type: 'text',
+ content: newContent,
+ },
+ }],
+ });
+
+ handleReload();
+ } finally {
+ setIsSaving(false);
+ }
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/rowboat/app/projects/[projectId]/sources/new/form.tsx b/apps/rowboat/app/projects/[projectId]/sources/new/form.tsx
index 0c12e83b..3b4f1ed0 100644
--- a/apps/rowboat/app/projects/[projectId]/sources/new/form.tsx
+++ b/apps/rowboat/app/projects/[projectId]/sources/new/form.tsx
@@ -60,6 +60,32 @@ export function Form({
router.push(`/projects/${projectId}/sources/${source._id}`);
}
+ async function createTextDataSource(formData: FormData) {
+ const source = await createDataSource({
+ projectId,
+ name: formData.get('name') as string,
+ data: {
+ type: 'text',
+ },
+ status: 'pending',
+ });
+
+ const content = formData.get('content') as string;
+ await addDocsToDataSource({
+ projectId,
+ sourceId: source._id,
+ docData: [{
+ name: 'text',
+ data: {
+ type: 'text',
+ content,
+ },
+ }],
+ });
+
+ router.push(`/projects/${projectId}/sources/${source._id}`);
+ }
+
function handleSourceTypeChange(event: React.ChangeEvent) {
setSourceType(event.target.value);
}
@@ -75,6 +101,12 @@ export function Form({
...(useRagScraping ? [] : ['urls']),
]}
>
+ }
+ >
+ Text
+
}
@@ -87,7 +119,7 @@ export function Form({
>
Upload files
-
+
{sourceType === "urls" &&
}
+
+ {sourceType === "text" &&
+
+
+
+
+
+ }}
+ />
+ }
;
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/sources/sources-list.tsx b/apps/rowboat/app/projects/[projectId]/sources/sources-list.tsx
index 59af3192..d7a51511 100644
--- a/apps/rowboat/app/projects/[projectId]/sources/sources-list.tsx
+++ b/apps/rowboat/app/projects/[projectId]/sources/sources-list.tsx
@@ -86,6 +86,14 @@ export function SourcesList({
List URLs
}
+ {source.data.type == 'text' && }
+ {source.data.type == 'files' && }
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/app.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/app.tsx
index 8538c89a..d8e8537f 100644
--- a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/app.tsx
@@ -6,6 +6,8 @@ import { ProfilesApp } from "./profiles_app";
import { SimulationsApp } from "./simulations_app";
import { usePathname } from "next/navigation";
import { RunsApp } from "./runs_app";
+import { StructuredPanel } from "../../../../lib/components/structured-panel";
+import { ListItem } from "../../../../lib/components/structured-list";
export function App({
projectId,
@@ -43,18 +45,19 @@ export function App({
];
return
-
-
+
+
{menuItems.map((item) => (
-
- {item.label}
-
+ router.push(item.href)}
+ />
))}
-
-
-
+
+
+
{selection === "scenarios" &&
}
{selection === "profiles" &&
}
{selection === "simulations" &&
}
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/item-view.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/item-view.tsx
new file mode 100644
index 00000000..c343e9db
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/item-view.tsx
@@ -0,0 +1,38 @@
+// First, let's create a reusable component for item views
+export function ItemView({
+ items,
+ actions
+}: {
+ items: { label: string; value: string | React.ReactNode }[];
+ actions: React.ReactNode;
+}) {
+ return (
+
+ {/* Content */}
+
+
+ {items.map((item, index) => (
+
+
+ {item.label}
+
+
+ {item.value || "—"}
+
+
+ ))}
+
+
+ {/* Actions */}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/profile-form.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/profile-form.tsx
new file mode 100644
index 00000000..e80f3f5d
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/profile-form.tsx
@@ -0,0 +1,90 @@
+import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { Button, Input, Textarea, Switch } from "@heroui/react"
+import { useRef, useState } from "react";
+
+interface ProfileFormProps {
+ defaultValues?: {
+ name?: string;
+ context?: string;
+ mockTools?: boolean;
+ mockPrompt?: string;
+ };
+ formRef: React.RefObject
;
+ handleSubmit: (formData: FormData) => Promise;
+ onCancel: () => void;
+ submitButtonText: string;
+}
+
+export function ProfileForm({
+ defaultValues = {},
+ formRef,
+ handleSubmit,
+ onCancel,
+ submitButtonText,
+}: ProfileFormProps) {
+ const [mockTools, setMockTools] = useState(Boolean(defaultValues.mockTools));
+ const [showMockPrompt, setShowMockPrompt] = useState(Boolean(defaultValues.mockTools));
+
+ return (
+
+
+
+
+
+ {
+ setMockTools(checked);
+ setShowMockPrompt(checked);
+ }}
+ name="mockTools"
+ value="on"
+ >
+ Mock Tools
+
+
+ {showMockPrompt && (
+
+
Mock Prompt (Optional)
+
+
+ )}
+
+
+
+
+ Cancel
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/scenario-form.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/scenario-form.tsx
new file mode 100644
index 00000000..e281e043
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/scenario-form.tsx
@@ -0,0 +1,68 @@
+import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { Button, Input, Textarea } from "@heroui/react";
+
+interface ScenarioFormProps {
+ formRef: React.RefObject;
+ handleSubmit: (formData: FormData) => Promise;
+ onCancel: () => void;
+ submitButtonText: string;
+ defaultValues?: {
+ name?: string;
+ description?: string;
+ };
+}
+
+export function ScenarioForm({
+ formRef,
+ handleSubmit,
+ onCancel,
+ submitButtonText,
+ defaultValues = {},
+}: ScenarioFormProps) {
+ return (
+
+
+
+
+
+
+ Cancel
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/lib/components/selectors/profile-selector.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector.tsx
similarity index 82%
rename from apps/rowboat/app/lib/components/selectors/profile-selector.tsx
rename to apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector.tsx
index e9882132..de8f0dea 100644
--- a/apps/rowboat/app/lib/components/selectors/profile-selector.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector.tsx
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
import { listProfiles } from "@/app/actions/testing_actions";
import { Button, Pagination, Spinner, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
import { z } from "zod";
+import { useRouter } from "next/navigation";
interface ProfileSelectorProps {
projectId: string;
@@ -19,6 +20,7 @@ export function ProfileSelector({ projectId, isOpen, onOpenChange, onSelect }: P
const [profiles, setProfiles] = useState>[]>([]);
const [totalPages, setTotalPages] = useState(0);
const pageSize = 10;
+ const router = useRouter();
const fetchProfiles = useCallback(async (page: number) => {
setLoading(true);
@@ -58,10 +60,10 @@ export function ProfileSelector({ projectId, isOpen, onOpenChange, onSelect }: P
{!loading && !error && <>
{profiles.length === 0 && No profiles found
}
{profiles.length > 0 &&
-
-
Name
-
Context
-
Mock Tools
+
+
Name
+
Context
+
Mock Tools
{profiles.map((p) => (
@@ -88,9 +90,18 @@ export function ProfileSelector({ projectId, isOpen, onOpenChange, onSelect }: P
>}
-
- Cancel
-
+
+ router.push(`/projects/${projectId}/test/profiles`)}
+ >
+ Manage Profiles
+
+
+ Cancel
+
+
>
)}
diff --git a/apps/rowboat/app/lib/components/selectors/scenario-selector.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/scenario-selector.tsx
similarity index 83%
rename from apps/rowboat/app/lib/components/selectors/scenario-selector.tsx
rename to apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/scenario-selector.tsx
index 07b7d001..3056f9a4 100644
--- a/apps/rowboat/app/lib/components/selectors/scenario-selector.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/scenario-selector.tsx
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
import { listScenarios } from "@/app/actions/testing_actions";
import { Button, Pagination, Spinner, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
import { z } from "zod";
+import { useRouter } from "next/navigation";
interface ScenarioSelectorProps {
projectId: string;
@@ -13,6 +14,7 @@ interface ScenarioSelectorProps {
}
export function ScenarioSelector({ projectId, isOpen, onOpenChange, onSelect }: ScenarioSelectorProps) {
+ const router = useRouter();
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [error, setError] = useState
(null);
@@ -58,9 +60,9 @@ export function ScenarioSelector({ projectId, isOpen, onOpenChange, onSelect }:
{!loading && !error && <>
{scenarios.length === 0 && No scenarios found
}
{scenarios.length > 0 &&
-
-
Name
-
Description
+
{scenarios.map((s) => (
@@ -86,9 +88,18 @@ export function ScenarioSelector({ projectId, isOpen, onOpenChange, onSelect }:
>}
-
- Cancel
-
+
+ router.push(`/projects/${projectId}/test/scenarios`)}
+ >
+ Manage Scenarios
+
+
+ Cancel
+
+
>
)}
diff --git a/apps/rowboat/app/lib/components/selectors/simulation-selector.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/simulation-selector.tsx
similarity index 100%
rename from apps/rowboat/app/lib/components/selectors/simulation-selector.tsx
rename to apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/simulation-selector.tsx
diff --git a/apps/rowboat/app/lib/components/selectors/workflow-selector.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/workflow-selector.tsx
similarity index 98%
rename from apps/rowboat/app/lib/components/selectors/workflow-selector.tsx
rename to apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/workflow-selector.tsx
index b2f81153..2a14252b 100644
--- a/apps/rowboat/app/lib/components/selectors/workflow-selector.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/selectors/workflow-selector.tsx
@@ -5,7 +5,7 @@ import { listWorkflows } from "@/app/actions/workflow_actions";
import { Button, Pagination, Spinner, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
import { z } from "zod";
import { RelativeTime } from "@primer/react";
-import { WorkflowIcon } from "../icons";
+import { WorkflowIcon } from "../../../../../../lib/components/icons";
import { PublishedBadge } from "@/app/projects/[projectId]/workflow/published_badge";
interface WorkflowSelectorProps {
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/simulation-form.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/simulation-form.tsx
new file mode 100644
index 00000000..6aef94e1
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/simulation-form.tsx
@@ -0,0 +1,190 @@
+import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { Button, Input, Textarea } from "@heroui/react";
+import { TestProfile, TestScenario } from "@/app/lib/types/testing_types";
+import { WithStringId } from "@/app/lib/types/types";
+import { ScenarioSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/scenario-selector";
+import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
+import { z } from "zod";
+
+interface SimulationFormProps {
+ formRef: React.RefObject
;
+ handleSubmit: (formData: FormData) => Promise;
+ scenario: WithStringId> | null;
+ setScenario: (scenario: WithStringId> | null) => void;
+ profile: WithStringId> | null;
+ setProfile: (profile: WithStringId> | null) => void;
+ isScenarioModalOpen: boolean;
+ setIsScenarioModalOpen: (isOpen: boolean) => void;
+ isProfileModalOpen: boolean;
+ setIsProfileModalOpen: (isOpen: boolean) => void;
+ projectId: string;
+ submitButtonText: string;
+ defaultValues?: {
+ name?: string;
+ description?: string;
+ passCriteria?: string;
+ };
+ onCancel: () => void;
+}
+
+export function SimulationForm({
+ formRef,
+ handleSubmit,
+ scenario,
+ setScenario,
+ profile,
+ setProfile,
+ isScenarioModalOpen,
+ setIsScenarioModalOpen,
+ isProfileModalOpen,
+ setIsProfileModalOpen,
+ projectId,
+ submitButtonText,
+ defaultValues = {},
+ onCancel,
+}: SimulationFormProps) {
+ return (
+
+ {/* Basic Information */}
+
+
Basic Information
+ Name}
+ placeholder="Enter a name for the simulation, e.g. "Frequent buyer cancelling order""
+ defaultValue={defaultValues.name}
+ isRequired
+ classNames={{
+ input: "bg-white dark:bg-neutral-900",
+ inputWrapper: "bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800"
+ }}
+ />
+ Description}
+ placeholder="Enter an optional description for the simulation, just to help you remember what it's for"
+ defaultValue={defaultValues.description}
+ classNames={{
+ input: "bg-white dark:bg-neutral-900",
+ inputWrapper: "bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800"
+ }}
+ />
+
+
+ {/* Test Configuration */}
+
+
Test Configuration
+
+
+ {/* Scenario Selection */}
+
+
+ Scenario *
+
+
+
+ {scenario ? (
+ {scenario.name}
+ ) : (
+ No scenario selected
+ )}
+
+
setIsScenarioModalOpen(true)}
+ type="button"
+ >
+ {scenario ? "Change" : "Select"} Scenario
+
+
+
+
+ {/* Profile Selection */}
+
+
+ Profile (optional)
+
+
+
+ {profile ? (
+ {profile.name}
+ ) : (
+ "No profile selected"
+ )}
+
+
+ {profile && (
+ setProfile(null)}>
+ Remove
+
+ )}
+ setIsProfileModalOpen(true)}
+ type="button"
+ >
+ {profile ? "Change" : "Select"} Profile
+
+
+
+
+
+ {/* Pass Criteria */}
+
+
+ Pass Criteria *
+
+
+
+
+
+
+ {/* Submit Button */}
+
+
+
+ Cancel
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/table.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/table.tsx
new file mode 100644
index 00000000..2a2bcffc
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/table.tsx
@@ -0,0 +1,321 @@
+import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell, Selection } from "@heroui/react";
+import { Button } from "@heroui/react";
+import { PencilIcon, TrashIcon, EyeIcon, DownloadIcon } from "lucide-react";
+import Link from "next/link";
+import { ReactNode, useState } from "react";
+import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
+
+// Helper function to safely parse dates
+const isValidDate = (date: any): boolean => {
+ const parsed = new Date(date);
+ return parsed instanceof Date && !isNaN(parsed.getTime());
+};
+
+interface Column {
+ key: string;
+ label: string;
+ render?: (item: any) => ReactNode;
+}
+
+interface DataTableProps {
+ items: any[];
+ columns: Column[];
+ selectedKeys?: Selection;
+ onSelectionChange?: (keys: Selection) => void;
+ projectId: string;
+ onDelete?: (id: string) => Promise;
+ onEdit?: (id: string) => void;
+ onView?: (id: string) => void;
+ onDownload?: (id: string) => void;
+ selectionMode?: "multiple" | "none";
+}
+
+export function DataTable({
+ items,
+ columns,
+ selectedKeys,
+ onSelectionChange,
+ projectId,
+ onDelete,
+ onEdit,
+ onView,
+ onDownload,
+ selectionMode = "multiple",
+}: DataTableProps) {
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+ const [itemToDelete, setItemToDelete] = useState(null);
+ const [isDeleteAllModalOpen, setIsDeleteAllModalOpen] = useState(false);
+ const [deleteError, setDeleteError] = useState(null);
+
+ const handleDeleteClick = (id: string) => {
+ setItemToDelete(id);
+ setIsDeleteModalOpen(true);
+ };
+
+ const handleDeleteConfirm = async () => {
+ if (!itemToDelete || !onDelete) return;
+
+ try {
+ await onDelete(itemToDelete);
+ setIsDeleteModalOpen(false);
+ setItemToDelete(null);
+ } catch (error) {
+ setDeleteError(`Failed to delete: ${error}`);
+ }
+ };
+
+ const handleDeleteAll = async () => {
+ if (!onDelete) return;
+
+ try {
+ // Delete all items sequentially
+ for (const item of items) {
+ await onDelete(item._id);
+ }
+ setIsDeleteAllModalOpen(false);
+ // Selection will be cleared automatically when items refresh
+ } catch (error) {
+ setDeleteError(`Failed to delete items: ${error}`);
+ }
+ };
+
+ const isAllSelected = selectedKeys === "all";
+
+ const renderCells = (item: any) => {
+ const cells = columns.map(column => (
+
+ {column.render ? column.render(item) :
+ // Handle date fields specially
+ (column.key.toLowerCase().includes('date') ||
+ column.key === 'createdAt' ||
+ column.key === 'lastUpdatedAt') && isValidDate(item[column.key]) ?
+ new Date(item[column.key]).toLocaleString() :
+ item[column.key]
+ }
+
+ ));
+
+ // Only add actions column if there are any actions
+ const hasActions = onDelete || onEdit || onView || onDownload;
+ if (hasActions) {
+ cells.push(
+
+
+ {onView && (
+
onView(item._id)}
+ aria-label="View item"
+ >
+
+
+ )}
+ {onEdit && (
+
onEdit(item._id)}
+ aria-label="Edit item"
+ >
+
+
+ )}
+ {onDownload && (
+
onDownload(item._id)}
+ aria-label="Download results"
+ >
+
+
+ )}
+ {onDelete && (
+
handleDeleteClick(item._id)}
+ aria-label="Delete item"
+ >
+
+
+ )}
+
+
+ );
+ }
+
+ return cells;
+ };
+
+ return (
+ <>
+
+ {/* Only show Delete All button when selection is enabled and items are selected */}
+ {selectionMode === "multiple" && selectedKeys === "all" && items.length > 0 && (
+
+ setIsDeleteAllModalOpen(true)}
+ startContent={ }
+ >
+ Delete All ({items.length})
+
+
+ )}
+
+
+ ({
+ key: column.key,
+ label: column.label
+ })),
+ ...((onDelete || onEdit || onView || onDownload) ? [{
+ key: 'actions',
+ label: 'ACTIONS',
+ render: (item: any) => (
+
+ ),
+ }] : [])
+ ]}>
+ {(column) => (
+ {column.label}
+ )}
+
+
+ {(item) => (
+
+ {renderCells(item)}
+
+ )}
+
+
+
+
+ {/* Single Delete Confirmation Modal */}
+ {
+ setIsDeleteModalOpen(open);
+ if (!open) setItemToDelete(null);
+ }}
+ size="sm"
+ >
+
+ {(onClose) => (
+ <>
+ Confirm Deletion
+
+ Are you sure you want to delete this item?
+
+
+
+ Cancel
+
+ {
+ handleDeleteConfirm();
+ onClose();
+ }}
+ >
+ Delete
+
+
+ >
+ )}
+
+
+
+ {/* Delete All Confirmation Modal */}
+
+
+ {(onClose) => (
+ <>
+ Confirm Delete All
+
+ Are you sure you want to delete all {items.length} items? This action cannot be undone.
+
+
+
+ Cancel
+
+ {
+ handleDeleteAll();
+ onClose();
+ }}
+ >
+ Delete All
+
+
+ >
+ )}
+
+
+
+ {/* Error Modal */}
+ setDeleteError(null)}
+ size="sm"
+ >
+
+ {(onClose) => (
+ <>
+ Error
+
+ {deleteError}
+
+
+
+ Close
+
+
+ >
+ )}
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/page.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/page.tsx
index f6f323bd..2627562a 100644
--- a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/page.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/page.tsx
@@ -1,8 +1,43 @@
-import { App } from "./app";
+'use client';
-export default function Page({ params }: { params: { projectId: string, slug?: string[] } }) {
- return ;
+import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/components/ui/resizable";
+import { ScenariosApp } from "./scenarios_app";
+import { SimulationsApp } from "./simulations_app";
+import { ProfilesApp } from "./profiles_app";
+import { RunsApp } from "./runs_app";
+import { TestingMenu } from "./testing_menu";
+export default function TestPage({ params }: { params: { projectId: string; slug?: string[] } }) {
+ const { projectId, slug = [] } = params;
+ let app: "scenarios" | "simulations" | "profiles" | "runs" = "runs";
+
+ if (slug[0] === "scenarios") {
+ app = "scenarios";
+ } else if (slug[0] === "simulations") {
+ app = "simulations";
+ } else if (slug[0] === "profiles") {
+ app = "profiles";
+ } else if (slug[0] === "runs") {
+ app = "runs";
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {app === "scenarios" && }
+ {app === "simulations" && }
+ {app === "profiles" && }
+ {app === "runs" && }
+
+
+
+ );
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/profiles_app.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/profiles_app.tsx
index a0e9d14c..25c672db 100644
--- a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/profiles_app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/profiles_app.tsx
@@ -1,15 +1,19 @@
+"use client";
+
import Link from "next/link";
import { WithStringId } from "@/app/lib/types/types";
import { TestProfile } from "@/app/lib/types/testing_types";
import { useEffect, useState, useRef } from "react";
import { createProfile, getProfile, listProfiles, updateProfile, deleteProfile } from "@/app/actions/testing_actions";
-import { Button, Input, Pagination, Spinner, Switch, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Tooltip } from "@heroui/react";
+import { Button, Spinner, Selection } from "@heroui/react";
import { useRouter, useSearchParams } from "next/navigation";
import { z } from "zod";
-import { PlusIcon, ArrowLeftIcon, StarIcon } from "lucide-react";
-import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { PlusIcon } from "lucide-react";
import { RelativeTime } from "@primer/react"
-import { getProjectConfig } from "@/app/actions/project_actions";
+import { StructuredPanel, ActionButton } from "@/app/lib/components/structured-panel";
+import { DataTable } from "./components/table";
+import { isValidDate } from './utils/date';
+import { ProfileForm } from "./components/profile-form";
function EditProfile({
projectId,
@@ -46,324 +50,96 @@ function EditProfile({
try {
const name = formData.get("name") as string;
const context = formData.get("context") as string;
+ const mockTools = formData.get("mockTools") === "on";
const mockPrompt = formData.get("mockPrompt") as string;
- await updateProfile(projectId, profileId, {
- name,
- context,
+
+ await updateProfile(projectId, profileId, {
+ name,
+ context,
mockTools,
- mockPrompt: mockPrompt || undefined
+ mockPrompt: mockTools && mockPrompt ? mockPrompt : undefined
});
- router.push(`/projects/${projectId}/test/profiles/${profileId}`);
+ router.push(`/projects/${projectId}/test/profiles`);
} catch (error) {
setError(`Unable to update profile: ${error}`);
}
}
- return
-
Edit Profile
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- formRef.current?.requestSubmit()}>Retry
-
}
- {!loading && profile && (
-
-
-
- {
- setMockTools(value);
+ return
+
+ {loading && (
+
+
+ Loading profile...
+
+ )}
+
+ {error && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+ {!loading && profile && (
+
router.push(`/projects/${projectId}/test/profiles`)}
+ submitButtonText="Update Profile"
+ defaultValues={{
+ name: profile.name,
+ context: profile.context,
+ mockTools: Boolean(profile.mockTools),
+ mockPrompt: profile.mockPrompt || ""
}}
- className="self-start"
- >
- Mock Tools
-
- {mockTools && }
-
-
-
- Cancel
-
-
-
- )}
- ;
+ />
+ )}
+
+ ;
}
-function ViewProfile({
- projectId,
- profileId,
-}: {
- projectId: string,
- profileId: string,
-}) {
- const router = useRouter();
- const [profile, setProfile] = useState> | null>(null);
- const [loading, setLoading] = useState(true);
- const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
- const [deleteError, setDeleteError] = useState(null);
-
- useEffect(() => {
- async function fetchProfile() {
- const profile = await getProfile(projectId, profileId);
- setProfile(profile);
- setLoading(false);
- }
- fetchProfile();
- }, [projectId, profileId]);
-
- async function handleDelete() {
- try {
- await deleteProfile(projectId, profileId);
- router.push(`/projects/${projectId}/test/profiles`);
- } catch (error) {
- setDeleteError(`Failed to delete profile: ${error}`);
- }
- }
-
- return
-
View Profile
-
}
- >
- All Profiles
-
- {loading &&
-
- Loading...
-
}
- {!loading && !profile &&
Profile not found
}
- {!loading && profile && (
- <>
-
-
-
-
Context
-
{profile.context}
-
-
-
Mock Tools
-
{profile.mockTools ? "Yes" : "No"}
-
- {profile.mockPrompt &&
-
Mock Prompt
-
{profile.mockPrompt}
-
}
-
-
-
-
-
- Edit
-
- setIsDeleteModalOpen(true)}
- >
- Delete
-
-
-
-
-
- {(onClose) => (
- <>
- Confirm Deletion
-
- Are you sure you want to delete this profile?
-
-
-
- Cancel
-
- {
- handleDelete();
- onClose();
- }}
- >
- Delete
-
-
- >
- )}
-
-
-
-
setDeleteError(null)}
- size="sm"
- >
-
- {(onClose) => (
- <>
- Error
-
- {deleteError}
-
-
-
- Close
-
-
- >
- )}
-
-
- >
- )}
-
;
-}
-
-function NewProfile({
- projectId,
-}: {
- projectId: string,
-}) {
+function NewProfile({ projectId }: { projectId: string }) {
+ const formRef = useRef(null);
const router = useRouter();
const [error, setError] = useState(null);
- const [mockTools, setMockTools] = useState(false);
- const formRef = useRef(null);
async function handleSubmit(formData: FormData) {
setError(null);
try {
const name = formData.get("name") as string;
const context = formData.get("context") as string;
- const mockPrompt = formData.get("mockPrompt") as string;
- const profile = await createProfile(projectId, {
- name,
- context,
+ const mockTools = formData.get("mockTools") === "on";
+ const mockPrompt = mockTools ? (formData.get("mockPrompt") as string) : undefined;
+
+ await createProfile(projectId, {
+ name,
+ context,
mockTools,
- mockPrompt: mockPrompt || undefined
+ mockPrompt // This will be undefined if mockTools is false
});
- router.push(`/projects/${projectId}/test/profiles/${profile._id}`);
+ router.push(`/projects/${projectId}/test/profiles`);
} catch (error) {
setError(`Unable to create profile: ${error}`);
}
}
- return
-
New Profile
-
}
- >
- All Profiles
-
- {error &&
- {error}
- formRef.current?.requestSubmit()}>Retry
-
}
-
-
+
+
router.push(`/projects/${projectId}/test/profiles`)}
+ submitButtonText="Create Profile"
/>
-
- {
- setMockTools(value);
- }}
- className="self-start"
- >
- Mock Tools
-
- {mockTools && }
-
-
- ;
+
+ ;
}
function ProfileList({
@@ -379,6 +155,8 @@ function ProfileList({
const [error, setError] = useState(null);
const [profiles, setProfiles] = useState>[]>([]);
const [total, setTotal] = useState(0);
+ const [selectedKeys, setSelectedKeys] = useState(new Set());
+ const [selectedProfiles, setSelectedProfiles] = useState([]);
useEffect(() => {
let ignore = false;
@@ -412,95 +190,146 @@ function ProfileList({
};
}, [page, pageSize, error, projectId]);
- return
-
Profiles
-
router.push(`/projects/${projectId}/test/profiles/new`)}
- className="self-end"
- startContent={ }
- >
- New Profile
-
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- setError(null)}>Retry
-
}
- {!loading && !error && <>
- {profiles.length === 0 &&
No profiles found
}
- {profiles.length > 0 &&
- {/* Header */}
-
-
Name
-
Context
-
Mock Tools
-
Created
-
Updated
-
+ const handleSelectionChange = (selection: Selection) => {
+ if (selection === "all" &&
+ selectedKeys !== "all" &&
+ (selectedKeys as Set
).size > 0) {
+ setSelectedKeys(new Set());
+ setSelectedProfiles([]);
+ } else {
+ setSelectedKeys(selection);
+ if (selection === "all") {
+ setSelectedProfiles(profiles.map(profile => profile._id));
+ } else {
+ setSelectedProfiles(Array.from(selection as Set));
+ }
+ }
+ };
- {/* Rows */}
- {profiles.map((profile) => (
-
-
-
- {profile.name}
-
-
-
{profile.context}
-
{profile.mockTools ? "Yes" : "No"}
-
-
-
-
-
-
-
- ))}
- }
- {total > 1 &&
{
- router.push(`/projects/${projectId}/test/profiles?page=${page}`);
- }}
- className="self-center"
- />}
- >}
- ;
+ const handleDelete = async (profileId: string) => {
+ try {
+ await deleteProfile(projectId, profileId);
+ // Refresh the profiles list after deletion
+ const result = await listProfiles(projectId, page, pageSize);
+ setProfiles(result.profiles);
+ setTotal(result.total);
+ } catch (err) {
+ setError(`Failed to delete profile: ${err}`);
+ }
+ };
+
+ const columns = [
+ {
+ key: 'name',
+ label: 'NAME',
+ render: (profile: any) => profile.name
+ },
+ {
+ key: 'context',
+ label: 'CONTEXT'
+ },
+ {
+ key: 'mockTools',
+ label: 'MOCK TOOLS',
+ render: (profile: any) => profile.mockTools ? "Yes" : "No"
+ },
+ {
+ key: 'createdAt',
+ label: 'CREATED',
+ render: (profile: any) => profile?.createdAt && isValidDate(profile.createdAt) ?
+ :
+ 'Invalid date'
+ },
+ {
+ key: 'lastUpdatedAt',
+ label: 'LAST UPDATED',
+ render: (profile: any) => profile?.lastUpdatedAt && isValidDate(profile.lastUpdatedAt) ?
+ :
+ 'Invalid date'
+ }
+ ];
+
+ return
+
+ {/* Header Section */}
+
+
+
Profiles
+
+ Create and manage test profiles for your simulations
+
+
+
}
+ onPress={() => router.push(`/projects/${projectId}/test/profiles/new`)}
+ >
+ New Profile
+
+
+
+ {/* Error Display */}
+ {error && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+ {/* Profiles Table */}
+ {loading ? (
+
+
+ Loading profiles...
+
+ ) : profiles.length === 0 ? (
+
+
No profiles created yet
+
+ ) : (
+
router.push(`/projects/${projectId}/test/profiles/${id}/edit`)}
+ projectId={projectId}
+ />
+ )}
+
+ ;
}
-export function ProfilesApp({
- projectId,
- slug
-}: {
- projectId: string,
- slug: string[]
-}) {
- let selection: "list" | "view" | "new" | "edit" = "list";
- let profileId: string | null = null;
- if (slug.length > 0) {
+export function ProfilesApp({ projectId, slug }: { projectId: string; slug?: string[] }) {
+ let selection: "list" | "new" | "edit" = "list";
+ let profileId: string | undefined;
+
+ if (slug && slug.length > 0) {
if (slug[0] === "new") {
selection = "new";
- } else if (slug[slug.length - 1] === "edit") {
+ } else if (slug[1] === "edit") {
selection = "edit";
profileId = slug[0];
} else {
- selection = "view";
+ selection = "list";
profileId = slug[0];
}
}
- return <>
- {selection === "list" && }
- {selection === "new" && }
- {selection === "view" && profileId && }
- {selection === "edit" && profileId && }
- >;
-}
\ No newline at end of file
+ return (
+
+ {selection === "list" &&
}
+ {selection === "new" &&
}
+ {selection === "edit" && profileId && (
+
+ )}
+
+ );
+}
+
+export { NewProfile, EditProfile };
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/runs_app.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/runs_app.tsx
index 6207bd51..c7d6b1a1 100644
--- a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/runs_app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/runs_app.tsx
@@ -1,149 +1,20 @@
+"use client";
+
import Link from "next/link";
import { WithStringId } from "@/app/lib/types/types";
import { TestSimulation, TestRun } from "@/app/lib/types/testing_types";
-import { useEffect, useState, useRef } from "react";
-import { createRun, getRun, getSimulation, listRuns } from "@/app/actions/testing_actions";
-import { Button, Input, Pagination, Spinner, Chip } from "@heroui/react";
+import { useEffect, useState } from "react";
+import { getRun, getSimulation, listRuns, cancelRun, deleteRun, getSimulationResult, listRunSimulations } from "@/app/actions/testing_actions";
+import { Button, Spinner, Selection } from "@heroui/react";
import { useRouter, useSearchParams } from "next/navigation";
import { z } from "zod";
-import { ArrowLeftIcon, PlusIcon, WorkflowIcon } from "lucide-react";
-import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { ArrowLeftIcon, PlusIcon, DownloadIcon } from "lucide-react";
import { RelativeTime } from "@primer/react"
-import { SimulationSelector } from "@/app/lib/components/selectors/simulation-selector";
-import { WorkflowSelector } from "@/app/lib/components/selectors/workflow-selector";
import { Workflow } from "@/app/lib/types/workflow_types";
import { fetchWorkflow } from "@/app/actions/workflow_actions";
-
-function NewRun({
- projectId,
-}: {
- projectId: string,
-}) {
- const router = useRouter();
- const [error, setError] = useState(null);
- const formRef = useRef(null);
- const [selectedSimulations, setSelectedSimulations] = useState>[]>([]);
- const [isSimulationSelectorOpen, setIsSimulationSelectorOpen] = useState(false);
- const [selectedWorkflow, setSelectedWorkflow] = useState> | null>(null);
- const [isWorkflowSelectorOpen, setIsWorkflowSelectorOpen] = useState(false);
-
- async function handleSubmit(formData: FormData) {
- setError(null);
- const simulationIds = selectedSimulations.map(sim => sim._id);
-
- if (!selectedWorkflow) {
- setError("Please select a workflow");
- return;
- }
-
- if (simulationIds.length === 0) {
- setError("Please select at least one simulation");
- return;
- }
-
- try {
- const run = await createRun(projectId, {
- workflowId: selectedWorkflow._id,
- simulationIds
- });
- router.push(`/projects/${projectId}/test/runs/${run._id}`);
- } catch (error) {
- setError(`Unable to create run: ${error}`);
- }
- }
-
- return
-
New Run
-
}
- >
- All Runs
-
- {error &&
- {error}
- {
- formRef.current?.requestSubmit();
- }}
- >
- Retry
-
-
}
-
-
-
Workflow
-
- {selectedWorkflow ? (
-
{selectedWorkflow.name}
- ) : (
-
No workflow selected
- )}
-
setIsWorkflowSelectorOpen(true)}
- type="button"
- >
- {selectedWorkflow ? "Change" : "Select"} Workflow
-
-
-
-
-
setIsSimulationSelectorOpen(true)}
- type="button"
- className="self-start"
- >
- Select Simulations
-
- {selectedSimulations.length > 0 && (
-
- {selectedSimulations.map((sim) => (
- setSelectedSimulations(prev => prev.filter(s => s._id !== sim._id))}
- variant="flat"
- className="py-1"
- >
- {sim.name}
-
- ))}
-
- )}
-
-
-
-
-
-
-
-
;
-}
+import { StructuredPanel, ActionButton } from "@/app/lib/components/structured-panel"
+import { DataTable } from "./components/table"
+import { isValidDate } from './utils/date';
function ViewRun({
projectId,
@@ -152,49 +23,124 @@ function ViewRun({
projectId: string,
runId: string,
}) {
+ const router = useRouter();
const [run, setRun] = useState> | null>(null);
- const [loading, setLoading] = useState(true);
- const [workflow, setWorkflow] = useState> | null>(null);
const [simulations, setSimulations] = useState>[]>([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [workflow, setWorkflow] = useState> | null>(null);
useEffect(() => {
- async function fetchRun() {
- const run = await getRun(projectId, runId);
- setRun(run);
- if (run) {
+ async function fetchData() {
+ try {
+ const run = await getRun(projectId, runId);
+ if (!run) {
+ setError("Run not found");
+ return;
+ }
+ setRun(run);
+
+ const enrichedSimulations = await listRunSimulations(projectId, run.simulationIds);
+ setSimulations(enrichedSimulations);
+
// Fetch workflow and simulations in parallel
const [workflowResult, simulationsResult] = await Promise.all([
fetchWorkflow(projectId, run.workflowId),
Promise.all(run.simulationIds.map(id => getSimulation(projectId, id)))
]);
setWorkflow(workflowResult);
- setSimulations(simulationsResult.filter(s => s !== null));
+ } catch (error) {
+ setError(`Error fetching run: ${error}`);
+ } finally {
+ setLoading(false);
}
- setLoading(false);
}
- fetchRun();
- }, [runId, projectId]);
+ fetchData();
+ }, [projectId, runId]);
- return
-
- }
+ const columns = [
+ {
+ key: 'name',
+ label: 'SIMULATION',
+ render: (simulation: any) => simulation.name
+ },
+ {
+ key: 'scenarioId',
+ label: 'SCENARIO',
+ render: (simulation: any) => simulation.scenarioName
+ },
+ {
+ key: 'profileId',
+ label: 'PROFILE',
+ render: (simulation: any) => simulation.profileName
+ }
+ ];
+
+ const handleDownload = async (simulationId: string) => {
+ try {
+ const result = await getSimulationResult(projectId, runId, simulationId);
+ if (!result) {
+ console.error("No result found for simulation");
+ return;
+ }
+
+ // Get simulation name from simulations array
+ const simulation = simulations.find(s => s._id === simulationId);
+ if (!simulation) {
+ console.error("Simulation not found");
+ return;
+ }
+
+ // Create a safe filename
+ const safeName = `${run?.name}_${simulation.name}`
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, '_')
+ .replace(/^_+|_+$/g, ''); // Remove leading/trailing underscores
+
+ // Create the JSON content
+ const content = {
+ run: run?.name,
+ simulation: simulation.name,
+ result: result.result,
+ details: result.details,
+ transcript: result.transcript
+ };
+
+ // Create and trigger download
+ const blob = new Blob([JSON.stringify(content, null, 2)], { type: 'application/json' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${safeName}.json`;
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ } catch (error) {
+ console.error("Failed to download result:", error);
+ }
+ };
+
+ return }
+ onClick={() => router.push(`/projects/${projectId}/test/runs`)}
>
All Runs
-
-
-
+
+ ]}
+ >
{loading &&
Loading...
}
{!loading && !run &&
Run not found
}
{!loading && run && (
- <>
+
{/* Workflow and timing information in a grid */}
{workflow && (
@@ -236,31 +182,22 @@ function ViewRun({
{/* Simulations List */}
-
+
Simulations
-
- {simulations.map(sim => (
-
-
- {sim.name}
-
-
- ))}
-
+
- >
+
)}
-
;
+
}
-function RunList({
- projectId,
-}: {
- projectId: string,
-}) {
+function RunsList({ projectId }: { projectId: string }) {
const router = useRouter();
const searchParams = useSearchParams();
const page = parseInt(searchParams.get("page") || "1");
@@ -270,6 +207,54 @@ function RunList({
const [runs, setRuns] = useState
>[]>([]);
const [workflowMap, setWorkflowMap] = useState>>>({});
const [total, setTotal] = useState(0);
+ const [selectedKeys, setSelectedKeys] = useState(new Set());
+ const [selectedRuns, setSelectedRuns] = useState([]);
+
+ const handleSelectionChange = (selection: Selection) => {
+ if (selection === "all" &&
+ selectedKeys !== "all" &&
+ (selectedKeys as Set).size > 0) {
+ setSelectedKeys(new Set());
+ setSelectedRuns([]);
+ } else {
+ setSelectedKeys(selection);
+ if (selection === "all") {
+ setSelectedRuns(runs.map(run => run._id));
+ } else {
+ setSelectedRuns(Array.from(selection as Set));
+ }
+ }
+ };
+
+ const handleCancel = async (runId: string) => {
+ try {
+ await cancelRun(projectId, runId);
+ // Update the run status locally after successful cancellation
+ setRuns(runs.map(run => {
+ if (run._id === runId) {
+ return {
+ ...run,
+ status: 'cancelled'
+ };
+ }
+ return run;
+ }));
+ } catch (err) {
+ setError(`Failed to cancel run: ${err}`);
+ }
+ };
+
+ const handleDelete = async (runId: string) => {
+ try {
+ await deleteRun(projectId, runId);
+ // Refresh the runs list after deletion
+ const updatedRuns = await listRuns(projectId, page, pageSize);
+ setRuns(updatedRuns.runs);
+ setTotal(updatedRuns.total);
+ } catch (err) {
+ setError(`Failed to delete run: ${err}`);
+ }
+ };
useEffect(() => {
let ignore = false;
@@ -333,101 +318,122 @@ function RunList({
};
}, [runs, error, projectId]);
- return
-
-
Test Runs
-
router.push(`/projects/${projectId}/test/runs/new`)}
- startContent={ }
- >
- New Run
-
-
+ const columns = [
+ {
+ key: 'name',
+ label: 'NAME',
+ render: (run: any) => run.name
+ },
+ {
+ key: 'status',
+ label: 'STATUS',
+ render: (run: any) => (
+
+
+ {run.status.charAt(0).toUpperCase() + run.status.slice(1)}
+
+ )
+ },
+ {
+ key: 'results',
+ label: 'RESULTS',
+ render: (run: any) => (
+
+ {run.passCount || 0} passed
+ {run.failCount || 0} failed
+
+ )
+ },
+ {
+ key: 'createdAt',
+ label: 'STARTED',
+ render: (run: any) => isValidDate(run.startedAt) ?
+
:
+ 'Invalid date'
+ }
+ ];
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- setError(null)}>Retry
-
}
- {!loading && !error && <>
- {runs.length === 0 &&
No test runs found
}
- {runs.length > 0 &&
- {runs.map((run) => (
-
-
-
-
- {run.name}
-
- {workflowMap[run.workflowId] && (
-
-
- {workflowMap[run.workflowId].name}
-
- )}
-
-
-
- {run.status}
-
-
-
-
-
-
- {run.aggregateResults && (
-
-
-
- Total: {run.aggregateResults.total}
-
-
- Passed: {run.aggregateResults.passCount}
-
-
- Failed: {run.aggregateResults.failCount}
-
-
-
- )}
+ return (
+
+
+ {/* Header Section */}
+
+
+
Test Runs
+
+ View and monitor your workflow test runs
+
- ))}
-
}
- {total > 1 &&
{
- router.push(`/projects/${projectId}/test/runs?page=${page}`);
- }}
- className="self-center"
- />}
- >}
- ;
+ }
+ onPress={() => router.push(`/projects/${projectId}/test/simulations`)}
+ >
+ New Run
+
+
+
+ {/* Error Display */}
+ {error && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+ {/* Runs Table */}
+ {loading ? (
+
+
+ Loading test runs...
+
+ ) : runs.length === 0 ? (
+
+
No test runs created yet
+
+ ) : (
+
router.push(`/projects/${projectId}/test/runs/${id}`)}
+ projectId={projectId}
+ />
+ )}
+
+
+ );
}
-// Helper function for status styling
-function getStatusClass(status: string) {
- const baseClass = "px-2 py-1 rounded text-xs uppercase font-medium";
- switch (status) {
- case 'completed':
- return `${baseClass} bg-green-100 text-green-800`;
- case 'failed':
- case 'error':
- return `${baseClass} bg-red-100 text-red-800`;
- case 'cancelled':
- return `${baseClass} bg-gray-100 text-gray-800`;
- case 'running':
- case 'pending':
- default:
- return `${baseClass} bg-yellow-100 text-yellow-800`;
- }
+// Helper functions for status styling
+function getStatusStyles(status: string): string {
+ const styles = {
+ pending: "bg-gray-100 text-gray-700 dark:bg-neutral-800 dark:text-neutral-300",
+ running: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300",
+ completed: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300",
+ cancelled: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300",
+ failed: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300",
+ error: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300"
+ };
+ return styles[status as keyof typeof styles] || styles.pending;
+}
+
+function getStatusDotStyles(status: string): string {
+ const styles = {
+ pending: "bg-gray-500 dark:bg-neutral-400",
+ running: "bg-blue-500 dark:bg-blue-400",
+ completed: "bg-green-500 dark:bg-green-400",
+ cancelled: "bg-yellow-500 dark:bg-yellow-400",
+ failed: "bg-red-500 dark:bg-red-400",
+ error: "bg-red-500 dark:bg-red-400"
+ };
+ return styles[status as keyof typeof styles] || styles.pending;
}
export function RunsApp({
@@ -437,20 +443,15 @@ export function RunsApp({
projectId: string,
slug: string[]
}) {
- let selection: "list" | "view" | "new" = "list";
+ let selection: "list" | "view" = "list";
let runId: string | null = null;
if (slug.length > 0) {
- if (slug[0] === "new") {
- selection = "new";
- } else {
- selection = "view";
- runId = slug[0];
- }
+ selection = "view";
+ runId = slug[0];
}
return <>
- {selection === "list" &&
}
- {selection === "new" &&
}
+ {selection === "list" &&
}
{selection === "view" && runId &&
}
>;
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/scenarios_app.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/scenarios_app.tsx
index 43541df8..6d0281dc 100644
--- a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/scenarios_app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/scenarios_app.tsx
@@ -1,14 +1,20 @@
+"use client";
+
import Link from "next/link";
import { WithStringId } from "@/app/lib/types/types";
import { TestScenario } from "@/app/lib/types/testing_types";
import { useEffect, useState, useRef } from "react";
import { createScenario, getScenario, listScenarios, updateScenario, deleteScenario } from "@/app/actions/testing_actions";
-import { Button, Input, Pagination, Spinner, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
+import { Button, Spinner, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Selection } from "@heroui/react";
import { useRouter, useSearchParams } from "next/navigation";
import { z } from "zod";
-import { ArrowLeftIcon, PlusIcon } from "lucide-react";
-import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { ArrowLeftIcon, PlusIcon, } from "lucide-react";
import { RelativeTime } from "@primer/react"
+import { StructuredPanel, ActionButton } from "@/app/lib/components/structured-panel";
+import { DataTable } from "./components/table";
+import { isValidDate } from './utils/date';
+import { ItemView } from "./components/item-view"
+import { ScenarioForm } from "./components/scenario-form";
function EditScenario({
projectId,
@@ -44,68 +50,45 @@ function EditScenario({
const name = formData.get("name") as string;
const description = formData.get("description") as string;
await updateScenario(projectId, scenarioId, { name, description });
- router.push(`/projects/${projectId}/test/scenarios/${scenarioId}`);
+ router.push(`/projects/${projectId}/test/scenarios`);
} catch (error) {
setError(`Unable to update scenario: ${error}`);
}
}
- return
-
Edit Scenario
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- {
- formRef.current?.requestSubmit();
- }}
- >
- Retry
-
-
}
- {!loading && scenario && (
-
-
-
-
-
-
- Cancel
-
+ return
+
+ {loading && (
+
+
+ Loading scenario...
-
- )}
-
;
+ )}
+
+ {error && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+ {!loading && scenario && (
+ router.push(`/projects/${projectId}/test/scenarios`)}
+ submitButtonText="Update Scenario"
+ defaultValues={{
+ name: scenario.name,
+ description: scenario.description
+ }}
+ />
+ )}
+
+ ;
}
function ViewScenario({
@@ -139,120 +122,98 @@ function ViewScenario({
}
}
- return
-
View Scenario
-
}
+ return (
+
}
+ onClick={() => router.push(`/projects/${projectId}/test/scenarios`)}
+ >
+ All Scenarios
+
+ ]}
>
- All Scenarios
-
- {loading &&
-
- Loading...
-
}
- {!loading && !scenario &&
Scenario not found
}
- {!loading && scenario && (
- <>
-
-
-
Name
-
{scenario.name}
-
-
-
Description
-
{scenario.description}
-
-
-
-
-
-
- Edit
-
- setIsDeleteModalOpen(true)}
- >
- Delete
-
-
-
-
-
- {(onClose) => (
- <>
- Confirm Deletion
-
- Are you sure you want to delete this scenario?
-
-
-
- Cancel
-
- {
- handleDelete();
- onClose();
- }}
- >
- Delete
-
-
- >
- )}
-
-
-
-
setDeleteError(null)}
- size="sm"
- >
-
- {(onClose) => (
- <>
- Error
-
- {deleteError}
-
-
-
- Close
-
-
- >
- )}
-
-
- >
- )}
-
;
+
+ : 'Invalid date'
+ },
+ {
+ label: "Last Updated",
+ value: scenario?.lastUpdatedAt && isValidDate(scenario.lastUpdatedAt)
+ ?
+ : 'Invalid date'
+ }
+ ]}
+ actions={
+ <>
+ router.push(`/projects/${projectId}/test/scenarios/${scenarioId}/edit`)}>Edit
+ setIsDeleteModalOpen(true)}>Delete
+ >
+ }
+ />
+
+
+ {(onClose) => (
+ <>
+ Confirm Deletion
+
+ Are you sure you want to delete this scenario?
+
+
+
+ Cancel
+
+ {
+ handleDelete();
+ onClose();
+ }}
+ >
+ Delete
+
+
+ >
+ )}
+
+
+ setDeleteError(null)}
+ size="sm"
+ >
+
+ {(onClose) => (
+ <>
+ Error
+
+ {deleteError}
+
+
+
+ Close
+
+
+ >
+ )}
+
+
+
+ );
}
function NewScenario({
@@ -266,63 +227,36 @@ function NewScenario({
async function handleSubmit(formData: FormData) {
setError(null);
- const name = formData.get("name") as string;
- const description = formData.get("description") as string;
try {
- const scenario = await createScenario(projectId, { name, description });
- router.push(`/projects/${projectId}/test/scenarios/${scenario._id}`);
+ const name = formData.get("name") as string;
+ const description = formData.get("description") as string;
+ await createScenario(projectId, { name, description });
+ router.push(`/projects/${projectId}/test/scenarios`);
} catch (error) {
setError(`Unable to create scenario: ${error}`);
}
}
- return
-
New Scenario
-
}
- >
- All Scenarios
-
- {error &&
- {error}
- {
- formRef.current?.requestSubmit();
- }}
- >
- Retry
-
-
}
-
-
+
+ {error && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+
router.push(`/projects/${projectId}/test/scenarios`)}
+ submitButtonText="Create Scenario"
/>
-
-
-
- ;
+
+ ;
}
function ScenarioList({
@@ -338,6 +272,8 @@ function ScenarioList({
const [error, setError] = useState(null);
const [scenarios, setScenarios] = useState>[]>([]);
const [total, setTotal] = useState(0);
+ const [selectedKeys, setSelectedKeys] = useState(new Set());
+ const [selectedScenarios, setSelectedScenarios] = useState([]);
useEffect(() => {
let ignore = false;
@@ -371,93 +307,132 @@ function ScenarioList({
};
}, [page, pageSize, error, projectId]);
- return
-
Scenarios
-
router.push(`/projects/${projectId}/test/scenarios/new`)}
- className="self-end"
- startContent={ }
- >
- New Scenario
-
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- setError(null)}>Retry
-
}
- {!loading && !error && <>
- {scenarios.length === 0 &&
No scenarios found
}
- {scenarios.length > 0 &&
- {/* Header */}
-
-
Name
-
Description
-
Created
-
Updated
-
+ const handleSelectionChange = (selection: Selection) => {
+ if (selection === "all" &&
+ selectedKeys !== "all" &&
+ (selectedKeys as Set
).size > 0) {
+ setSelectedKeys(new Set());
+ setSelectedScenarios([]);
+ } else {
+ setSelectedKeys(selection);
+ if (selection === "all") {
+ setSelectedScenarios(scenarios.map(scenario => scenario._id));
+ } else {
+ setSelectedScenarios(Array.from(selection as Set));
+ }
+ }
+ };
- {/* Rows */}
- {scenarios.map((scenario) => (
-
-
-
- {scenario.name}
-
-
-
{scenario.description}
-
-
-
-
-
-
-
- ))}
- }
- {total > 1 &&
{
- router.push(`/projects/${projectId}/test/scenarios?page=${page}`);
- }}
- className="self-center"
- />}
- >}
- ;
+ const handleDelete = async (scenarioId: string) => {
+ try {
+ await deleteScenario(projectId, scenarioId);
+ // Refresh the scenarios list after deletion
+ const result = await listScenarios(projectId, page, pageSize);
+ setScenarios(result.scenarios);
+ setTotal(result.total);
+ } catch (err) {
+ setError(`Failed to delete scenario: ${err}`);
+ }
+ };
+
+ const columns = [
+ {
+ key: 'name',
+ label: 'NAME',
+ render: (scenario: any) => scenario.name
+ },
+ {
+ key: 'description',
+ label: 'DESCRIPTION'
+ },
+ {
+ key: 'createdAt',
+ label: 'CREATED',
+ render: (scenario: any) => isValidDate(scenario.createdAt) ?
+ :
+ 'Invalid date'
+ }
+ ];
+
+ return
+
+ {/* Header Section */}
+
+
+
Scenarios
+
+ Create and manage test scenarios for your simulations
+
+
+
}
+ onPress={() => router.push(`/projects/${projectId}/test/scenarios/new`)}
+ >
+ New Scenario
+
+
+
+ {/* Error Display */}
+ {error && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+ {/* Scenarios Table */}
+ {loading ? (
+
+
+ Loading scenarios...
+
+ ) : scenarios.length === 0 ? (
+
+
No scenarios created yet
+
+ ) : (
+
router.push(`/projects/${projectId}/test/scenarios/${id}/edit`)}
+ projectId={projectId}
+ />
+ )}
+
+ ;
}
-export function ScenariosApp({
- projectId,
- slug
-}: {
- projectId: string,
- slug: string[]
-}) {
- let selection: "list" | "view" | "new" | "edit" = "list";
- let scenarioId: string | null = null;
- if (slug.length > 0) {
+export function ScenariosApp({ projectId, slug }: { projectId: string; slug?: string[] }) {
+ let selection: "list" | "new" | "edit" = "list";
+ let scenarioId: string | undefined;
+
+ if (slug && slug.length > 0) {
if (slug[0] === "new") {
selection = "new";
- } else if (slug[slug.length - 1] === "edit") {
+ } else if (slug[1] === "edit") {
selection = "edit";
scenarioId = slug[0];
} else {
- selection = "view";
+ selection = "list";
scenarioId = slug[0];
}
}
- return <>
- {selection === "list" && }
- {selection === "new" && }
- {selection === "view" && scenarioId && }
- {selection === "edit" && scenarioId && }
- >;
+ return (
+
+ {selection === "list" && }
+ {selection === "new" && }
+ {selection === "edit" && scenarioId && (
+
+ )}
+
+ );
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/simulations_app.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/simulations_app.tsx
index b02d4bae..edbcbd3c 100644
--- a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/simulations_app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/simulations_app.tsx
@@ -1,16 +1,23 @@
+"use client";
+
import Link from "next/link";
import { WithStringId } from "@/app/lib/types/types";
-import { TestProfile, TestScenario, TestSimulation } from "@/app/lib/types/testing_types";
+import { TestProfile, TestScenario, TestSimulation, TestRun } from "@/app/lib/types/testing_types";
+import { Workflow } from "@/app/lib/types/workflow_types";
import { useEffect, useState, useRef } from "react";
-import { createSimulation, getSimulation, listSimulations, updateSimulation, deleteSimulation, listScenarios, getScenario, getProfile } from "@/app/actions/testing_actions";
-import { Button, Input, Pagination, Spinner, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
+import { createSimulation, getSimulation, listSimulations, updateSimulation, deleteSimulation, getScenario, getProfile, createRun } from "@/app/actions/testing_actions";
+import { Button, Spinner, Tooltip, Selection } from "@heroui/react";
import { useRouter, useSearchParams } from "next/navigation";
import { z } from "zod";
-import { PlusIcon, ArrowLeftIcon } from "lucide-react";
-import { FormStatusButton } from "@/app/lib/components/form-status-button";
+import { PlusIcon, ArrowLeftIcon, AlertTriangleIcon } from "lucide-react";
import { RelativeTime } from "@primer/react"
-import { ScenarioSelector } from "@/app/lib/components/selectors/scenario-selector";
-import { ProfileSelector } from "@/app/lib/components/selectors/profile-selector";
+import { ScenarioSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/scenario-selector";
+import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
+import { StructuredPanel, ActionButton } from "@/app/lib/components/structured-panel";
+import { WorkflowSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/workflow-selector";
+import { DataTable } from "./components/table"
+import { isValidDate } from './utils/date';
+import { SimulationForm } from "./components/simulation-form";
function EditSimulation({
projectId,
@@ -23,11 +30,11 @@ function EditSimulation({
const [simulation, setSimulation] = useState> | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const formRef = useRef(null);
const [scenario, setScenario] = useState> | null>(null);
const [profile, setProfile] = useState> | null>(null);
const [isScenarioModalOpen, setIsScenarioModalOpen] = useState(false);
const [isProfileModalOpen, setIsProfileModalOpen] = useState(false);
+ const formRef = useRef(null);
useEffect(() => {
async function fetchSimulation() {
@@ -56,307 +63,91 @@ function EditSimulation({
setError(null);
try {
const name = formData.get("name") as string;
+ const description = formData.get("description") as string;
const passCriteria = formData.get("passCriteria") as string;
- if (!name || !passCriteria) {
- throw new Error("Name and Pass Criteria are required");
- }
-
if (!scenario) {
throw new Error("Please select a scenario");
}
await updateSimulation(projectId, simulationId, {
name,
+ description,
scenarioId: scenario._id,
profileId: profile?._id || null,
passCriteria
});
- router.push(`/projects/${projectId}/test/simulations/${simulationId}`);
+ router.push(`/projects/${projectId}/test/simulations`);
} catch (error) {
setError(`Unable to update simulation: ${error}`);
}
}
- return
-
Edit Simulation
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- formRef.current?.requestSubmit()}>Retry
-
}
- {!loading && simulation && (
-
-
-
-
-
Scenario
-
- {scenario ? (
-
{scenario.name}
- ) : (
-
No scenario selected
- )}
-
setIsScenarioModalOpen(true)}
- type="button"
- >
- {scenario ? "Change" : "Select"} Scenario
-
-
+ return
+
+ {loading && (
+
+
+ Loading simulation...
-
-
Profile (optional)
-
- {profile ? (
-
{profile.name}
- ) : (
-
No profile selected
- )}
- {profile &&
setProfile(null)}>Remove }
-
setIsProfileModalOpen(true)}
- type="button"
- >
- {profile ? "Change" : "Select"} Profile
-
-
+ )}
+
+ {error && (
+
+ {error}
+ setError(null)}>Retry
-
-
+
+
Edit Simulation
+
+ Define a test simulation by selecting a scenario and optionally a profile
+
+
+
+ router.push(`/projects/${projectId}/test/simulations`)}
/>
-
- Cancel
-
-
-
-
-
- )}
-
;
-}
-
-function ViewSimulation({
- projectId,
- simulationId,
-}: {
- projectId: string,
- simulationId: string,
-}) {
- const router = useRouter();
- const [simulation, setSimulation] = useState
> | null>(null);
- const [loading, setLoading] = useState(true);
- const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
- const [deleteError, setDeleteError] = useState(null);
- const [scenario, setScenario] = useState> | null>(null);
- const [profile, setProfile] = useState> | null>(null);
-
- useEffect(() => {
- async function fetchSimulation() {
- const simulation = await getSimulation(projectId, simulationId);
- setSimulation(simulation);
- if (simulation) {
- const [scenarioResult, profileResult] = await Promise.all([
- getScenario(projectId, simulation.scenarioId),
- simulation.profileId ? getProfile(projectId, simulation.profileId) : Promise.resolve(null),
- ]);
- setScenario(scenarioResult);
- setProfile(profileResult);
- }
- setLoading(false);
- }
- fetchSimulation();
- }, [simulationId, projectId]);
-
- async function handleDelete() {
- try {
- await deleteSimulation(projectId, simulationId);
- router.push(`/projects/${projectId}/test/simulations`);
- } catch (error) {
- setDeleteError(`Failed to delete simulation: ${error}`);
- }
- }
-
- return
-
View Simulation
-
}
- >
- All Simulations
-
- {loading &&
-
- Loading...
-
}
- {!loading && !simulation &&
Simulation not found
}
- {!loading && simulation && (
- <>
-
-
-
Name
-
{simulation.name}
-
-
-
Scenario
-
- {scenario ? (
-
- {scenario.name}
-
- ) : (
-
No scenario selected
- )}
-
-
-
-
Profile
-
- {profile ? (
-
- {profile.name}
-
- ) : (
-
No profile selected
- )}
-
-
-
-
Pass Criteria
-
{simulation.passCriteria}
-
-
-
-
-
-
- Edit
-
- setIsDeleteModalOpen(true)}
- >
- Delete
-
-
-
-
-
- {(onClose) => (
- <>
- Confirm Deletion
-
- Are you sure you want to delete this simulation?
-
-
-
- Cancel
-
- {
- handleDelete();
- onClose();
- }}
- >
- Delete
-
-
- >
- )}
-
-
-
-
setDeleteError(null)}
- size="sm"
- >
-
- {(onClose) => (
- <>
- Error
-
- {deleteError}
-
-
-
- Close
-
-
- >
- )}
-
-
- >
- )}
-
;
+
+
+ >
+ )}
+
+ ;
}
function NewSimulation({
@@ -376,6 +167,7 @@ function NewSimulation({
setError(null);
try {
const name = formData.get("name") as string;
+ const description = formData.get("description") as string;
const passCriteria = formData.get("passCriteria") as string;
if (!name || !passCriteria) {
@@ -388,6 +180,7 @@ function NewSimulation({
const result = await createSimulation(projectId, {
name,
+ description,
scenarioId: scenario._id,
profileId: profile?._id || null,
passCriteria,
@@ -398,128 +191,102 @@ function NewSimulation({
}
}
- return
-
New Simulation
-
}
- >
- All Simulations
-
- {error &&
- {error}
- formRef.current?.requestSubmit()}>Retry
-
}
-
-
-
-
-
Scenario
-
- {scenario ? (
-
{scenario.name}
- ) : (
-
No scenario selected
- )}
-
setIsScenarioModalOpen(true)}
- type="button"
- >
- {scenario ? "Change" : "Select"} Scenario
-
-
+ return
}
+ onClick={() => router.push(`/projects/${projectId}/test/simulations`)}
+ >
+ All Simulations
+
+ ]}
+ >
+
+
+
Create New Simulation
+
+ Define a new test simulation by selecting a scenario and optionally a profile
+
-
-
Profile (optional)
-
- {profile ? (
-
{profile.name}
- ) : (
-
No profile selected
- )}
- {profile &&
setProfile(null)}>Remove }
-
setIsProfileModalOpen(true)}
- type="button"
- >
- {profile ? "Change" : "Select"} Profile
-
-
-
-
-
-
-
-
;
+ {error &&
+ {error}
+ formRef.current?.requestSubmit()}>Retry
+
}
+
+
router.push(`/projects/${projectId}/test/simulations`)}
+ />
+
+ ;
}
-function SimulationList({
- projectId,
-}: {
- projectId: string,
-}) {
+function SimulationList({ projectId }: { projectId: string }) {
const router = useRouter();
const searchParams = useSearchParams();
const page = parseInt(searchParams.get("page") || "1");
const pageSize = 10;
+ const [simulations, setSimulations] = useState>[]>([]);
+ const [selectedSimulations, setSelectedSimulations] = useState([]);
+ const [selectedWorkflow, setSelectedWorkflow] = useState> | null>(null);
+ const [isWorkflowSelectorOpen, setIsWorkflowSelectorOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const [simulationList, setSimulationList] = useState>[]>([]);
- const [scenarioMap, setScenarioMap] = useState>>>({});
- const [profileMap, setProfileMap] = useState>>>({});
const [total, setTotal] = useState(0);
+ const [simulationDetails, setSimulationDetails] = useState>,
+ profile?: WithStringId>
+ }>>({});
+ const [selectedKeys, setSelectedKeys] = useState(new Set());
useEffect(() => {
let ignore = false;
- async function fetchSimulation() {
+ async function fetchSimulations() {
setLoading(true);
setError(null);
try {
const result = await listSimulations(projectId, page, pageSize);
if (!ignore) {
- setSimulationList(result.simulations);
- setTotal(Math.ceil(result.total / pageSize));
+ setSimulations(result.simulations);
+ setTotal(result.total);
+
+ // Fetch scenario and profile details for each simulation
+ const details: Record = {};
+ await Promise.all(result.simulations.map(async (simulation) => {
+ const [scenarioResult, profileResult] = await Promise.all([
+ getScenario(projectId, simulation.scenarioId),
+ simulation.profileId ? getProfile(projectId, simulation.profileId) : Promise.resolve(null),
+ ]);
+ if (!ignore) {
+ details[simulation._id] = {
+ scenario: scenarioResult,
+ profile: profileResult
+ };
+ }
+ }));
+ if (!ignore) {
+ setSimulationDetails(details);
+ }
}
} catch (error) {
if (!ignore) {
- setError(`Unable to fetch simulation: ${error}`);
+ setError(`Unable to fetch simulations: ${error}`);
}
} finally {
if (!ignore) {
@@ -528,166 +295,301 @@ function SimulationList({
}
}
- if (error == null) {
- fetchSimulation();
- }
+ fetchSimulations();
return () => {
ignore = true;
};
- }, [page, pageSize, error, projectId]);
+ }, [projectId, page, pageSize]);
- useEffect(() => {
- let ignore = false;
-
- async function resolveScenarios() {
- const scenarioIds = simulationList.reduce((acc, simulation) => {
- if (!acc.includes(simulation.scenarioId)) {
- acc.push(simulation.scenarioId);
- }
- return acc;
- }, [] as string[]);
- const scenarios = await Promise.all(scenarioIds.map((scenarioId) => getScenario(projectId, scenarioId)));
- if (ignore) {
- return;
- }
- setScenarioMap(scenarios.filter((scenario) => scenario !== null).reduce((acc, scenario) => {
- acc[scenario._id] = scenario;
- return acc;
- }, {} as Record>>));
+ const handleSelectionChange = (selection: Selection) => {
+ setSelectedKeys(selection);
+ if (selection === "all") {
+ setSelectedSimulations(simulations.map(sim => sim._id));
+ } else {
+ setSelectedSimulations(Array.from(selection as Set));
}
- async function resolveProfiles() {
- const profileIds = simulationList.reduce((acc, simulation) => {
- if (simulation.profileId && !acc.includes(simulation.profileId)) {
- acc.push(simulation.profileId);
- }
- return acc;
- }, [] as string[]);
- const profiles = await Promise.all(profileIds.map((profileId) => getProfile(projectId, profileId)));
- if (ignore) {
- return;
- }
- setProfileMap(profiles.filter((profile) => profile !== null).reduce((acc, profile) => {
- acc[profile._id] = profile;
- return acc;
- }, {} as Record>>));
+ };
+
+ async function handleCreateRun() {
+ if (!selectedWorkflow || selectedSimulations.length === 0) {
+ return; // Just return without setting error
}
- if (error == null) {
- resolveScenarios();
- resolveProfiles();
+ try {
+ const run = await createRun(projectId, {
+ workflowId: selectedWorkflow._id,
+ simulationIds: selectedSimulations
+ });
+
+ setSelectedSimulations([]);
+ setSelectedWorkflow(null);
+
+ router.push(`/projects/${projectId}/test/runs/${run._id}`);
+ } catch (err) {
+ setError(`Failed to create test run: ${err}`);
}
+ }
- return () => {
- ignore = true;
- };
- }, [simulationList, error, projectId]);
+ const handleDelete = async (simulationId: string) => {
+ try {
+ await deleteSimulation(projectId, simulationId);
+ // Refresh the simulations list after deletion
+ const result = await listSimulations(projectId, page, pageSize);
+ setSimulations(result.simulations);
+ setTotal(result.total);
+ } catch (err) {
+ setError(`Failed to delete simulation: ${err}`);
+ }
+ };
- return
-
Simulations
-
router.push(`/projects/${projectId}/test/simulations/new`)}
- className="self-end"
- startContent={ }
- >
- New Simulation
-
- {loading &&
-
- Loading...
-
}
- {error &&
- {error}
- setError(null)}>Retry
-
}
- {!loading && !error && <>
- {simulationList.length === 0 &&
No simulation found
}
- {simulationList.length > 0 &&
- {/* Header */}
-
-
Name
-
Scenario
-
Profile
-
Criteria
-
Created
-
Updated
+ const handleLaunchClick = () => {
+ if (!selectedWorkflow || selectedSimulations.length === 0) {
+ alert("Please select a workflow version and at least one simulation.");
+ } else {
+ handleCreateRun();
+ }
+ };
+
+ const columns = [
+ {
+ key: 'name',
+ label: 'NAME',
+ render: (simulation: any) => (
+
+
{simulation.name}
+ {(!simulationDetails[simulation._id]?.scenario ||
+ (simulation.profileId && !simulationDetails[simulation._id]?.profile)) && (
+
+
+
+ )}
+ )
+ },
+ {
+ key: 'scenarioId',
+ label: 'SCENARIO',
+ render: (simulation: any) => {
+ const details = simulationDetails[simulation._id];
+ if (!details?.scenario) {
+ return (
+
+ );
+ }
+ return details.scenario.name;
+ }
+ },
+ {
+ key: 'profileId',
+ label: 'PROFILE',
+ render: (simulation: any) => {
+ const details = simulationDetails[simulation._id];
+ if (simulation.profileId && !details?.profile) {
+ return (
+
+ );
+ }
+ return details?.profile?.name || 'None';
+ }
+ },
+ {
+ key: 'createdAt',
+ label: 'CREATED',
+ render: (simulation: any) => isValidDate(simulation.createdAt) ?
+
:
+ 'Invalid date'
+ }
+ ];
- {/* Rows */}
- {simulationList.map((simulation) => (
-
-
-
- {simulation.name}
-
+ return (
+
+
+ {/* Combined Guidance and Run Creation Section */}
+
+
+ Create a Test Run
+
+
+
+ {/* Step 1: Create New Simulation */}
+
+
+ 1
+
+
+
+ Create a New Simulation (Optional)
+
+
+ Define a new test simulation if needed
+
+
+ }
+ onPress={() => router.push(`/projects/${projectId}/test/simulations/new`)}
+ >
+ New Simulation
+
+
+
-
- {scenarioMap[simulation.scenarioId]?.name || (
-
{simulation.scenarioId}
- )}
+
+ {/* Step 2: Select Workflow Version */}
+
+
+ 2
+
+
+
+
+ Select workflow version
+
+ setIsWorkflowSelectorOpen(true)}
+ >
+ {selectedWorkflow?.name || 'Select Version'}
+
+
+
-
- {simulation.profileId ? (
- profileMap[simulation.profileId]?.name || (
-
{simulation.profileId}
- )
- ) : (
-
None
- )}
+
+ {/* Step 3: Select Simulations */}
+
+
+ 3
+
+
+
+ Select Simulations for the Test Run
+
+
+ Choose one or more simulations from the table below
+
+
-
- {simulation.passCriteria}
-
-
-
-
-
-
+
+ {/* Step 4: Create Test Run */}
+
+
+ 4
+
+
+
+
+ Create test run
+
+ 0)}
+ >
+
+ Launch Test Run {selectedSimulations.length > 0 ? `(${selectedSimulations.length})` : ''}
+
+
+
+
- ))}
-
}
- {total > 1 &&
{
- router.push(`/projects/${projectId}/test/simulations?page=${page}`);
- }}
- className="self-center"
- />}
- >}
- ;
+
+
+ {/* Error Display - Only for API/system errors */}
+ {error && error.startsWith('Failed to') && (
+
+ {error}
+ setError(null)}>Retry
+
+ )}
+
+ {/* Simulations Table */}
+ {loading ? (
+
+
+ Loading simulations...
+
+ ) : simulations.length === 0 ? (
+
+
No simulations created yet
+
+ ) : (
+
router.push(`/projects/${projectId}/test/simulations/${id}/edit`)}
+ projectId={projectId}
+ />
+ )}
+
+
+
+
+ );
}
-export function SimulationsApp({
- projectId,
- slug
-}: {
- projectId: string,
- slug: string[]
-}) {
- let selection: "list" | "view" | "new" | "edit" = "list";
- let simulationId: string | null = null;
- if (slug.length > 0) {
+export function SimulationsApp({ projectId, slug }: { projectId: string; slug?: string[] }) {
+ let selection: "list" | "new" | "edit" = "list";
+ let simulationId: string | undefined;
+
+ if (slug && slug.length > 0) {
if (slug[0] === "new") {
selection = "new";
- } else if (slug[slug.length - 1] === "edit") {
+ } else if (slug[1] === "edit") {
selection = "edit";
simulationId = slug[0];
} else {
- selection = "view";
+ selection = "list";
simulationId = slug[0];
}
}
- return <>
- {selection === "list" &&
}
- {selection === "new" &&
}
- {selection === "view" && simulationId &&
}
- {selection === "edit" && simulationId &&
}
- >;
+ return (
+
+ {selection === "list" && }
+ {selection === "new" && }
+ {selection === "edit" && simulationId && (
+
+ )}
+
+ );
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/testing_menu.tsx b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/testing_menu.tsx
new file mode 100644
index 00000000..c288fcdd
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/testing_menu.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import { useRouter } from "next/navigation";
+import { StructuredPanel } from "../../../../lib/components/structured-panel";
+import { ListItem } from "../../../../lib/components/structured-list";
+
+export function TestingMenu({
+ projectId,
+ app,
+}: {
+ projectId: string;
+ app: "scenarios" | "simulations" | "profiles" | "runs";
+}) {
+ const router = useRouter();
+
+ const menuItems = [
+ {
+ label: "Scenarios",
+ href: `/projects/${projectId}/test/scenarios`,
+ isSelected: app === "scenarios"
+ },
+ {
+ label: "Profiles",
+ href: `/projects/${projectId}/test/profiles`,
+ isSelected: app === "profiles"
+ },
+ {
+ label: "Simulations",
+ href: `/projects/${projectId}/test/simulations`,
+ isSelected: app === "simulations"
+ },
+ {
+ label: "Test Runs",
+ href: `/projects/${projectId}/test/runs`,
+ isSelected: app === "runs"
+ },
+ ];
+
+ return (
+
+
+ {menuItems.map((item) => (
+ router.push(item.href)}
+ />
+ ))}
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/utils/date.ts b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/utils/date.ts
new file mode 100644
index 00000000..d05d9177
--- /dev/null
+++ b/apps/rowboat/app/projects/[projectId]/test/[[...slug]]/utils/date.ts
@@ -0,0 +1,4 @@
+export const isValidDate = (date: any): boolean => {
+ const parsed = new Date(date);
+ return parsed instanceof Date && !isNaN(parsed.getTime());
+};
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx
index 47dae09e..20b94273 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx
@@ -1,5 +1,5 @@
"use client";
-import { WithStringId } from "../../../lib/types/types";
+import { MCPServer, WithStringId } from "../../../lib/types/types";
import { Workflow } from "../../../lib/types/workflow_types";
import { DataSource } from "../../../lib/types/datasource_types";
import { z } from "zod";
@@ -9,6 +9,8 @@ import { WorkflowSelector } from "./workflow_selector";
import { Spinner } from "@heroui/react";
import { cloneWorkflow, createWorkflow, fetchPublishedWorkflowId, fetchWorkflow } from "../../../actions/workflow_actions";
import { listDataSources } from "../../../actions/datasource_actions";
+import { listMcpServers } from "@/app/actions/mcp_actions";
+import { getProjectConfig } from "@/app/actions/project_actions";
export function App({
projectId,
@@ -23,17 +25,23 @@ export function App({
const [dataSources, setDataSources] = useState
>[] | null>(null);
const [loading, setLoading] = useState(false);
const [autoSelectIfOnlyOneWorkflow, setAutoSelectIfOnlyOneWorkflow] = useState(true);
+ const [mcpServerUrls, setMcpServerUrls] = useState>>([]);
+ const [toolWebhookUrl, setToolWebhookUrl] = useState('');
const handleSelect = useCallback(async (workflowId: string) => {
setLoading(true);
const workflow = await fetchWorkflow(projectId, workflowId);
const publishedWorkflowId = await fetchPublishedWorkflowId(projectId);
const dataSources = await listDataSources(projectId);
+ const mcpServers = await listMcpServers(projectId);
+ const projectConfig = await getProjectConfig(projectId);
// Store the selected workflow ID in local storage
localStorage.setItem(`lastWorkflowId_${projectId}`, workflowId);
setWorkflow(workflow);
setPublishedWorkflowId(publishedWorkflowId);
setDataSources(dataSources);
+ setMcpServerUrls(mcpServers);
+ setToolWebhookUrl(projectConfig.webhookUrl ?? '');
setLoading(false);
}, [projectId]);
@@ -108,6 +116,8 @@ export function App({
handleShowSelector={handleShowSelector}
handleCloneVersion={handleCloneVersion}
useRag={useRag}
+ mcpServerUrls={mcpServerUrls}
+ toolWebhookUrl={toolWebhookUrl}
/>}
>
}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx b/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx
index c2d267c1..70d61bc5 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx
@@ -15,14 +15,15 @@ import clsx from "clsx";
import { Action as WorkflowDispatch } from "./workflow_editor";
import MarkdownContent from "../../../lib/components/markdown-content";
import { CopyAsJsonButton } from "../playground/copy-as-json-button";
-import { CornerDownLeftIcon, SendIcon } from "lucide-react";
+import { CornerDownLeftIcon, PlusIcon, SendIcon } from "lucide-react";
+import { useSearchParams } from 'next/navigation';
const CopilotContext = createContext<{
workflow: z.infer | null;
handleApplyChange: (messageIndex: number, actionIndex: number, field?: string) => void;
appliedChanges: Record;
-}>({ workflow: null, handleApplyChange: () => {}, appliedChanges: {} });
+}>({ workflow: null, handleApplyChange: () => { }, appliedChanges: {} });
export function getAppliedChangeKey(messageIndex: number, actionIndex: number, field: string) {
return `${messageIndex}-${actionIndex}-${field}`;
@@ -173,35 +174,35 @@ function App({
projectId,
workflow,
dispatch,
- chatContext=undefined,
- messages,
- setMessages,
- loadingResponse,
- setLoadingResponse,
- loadingMessage,
- setLoadingMessage,
- responseError,
- setResponseError,
+ chatContext = undefined,
}: {
projectId: string;
workflow: z.infer;
dispatch: (action: WorkflowDispatch) => void;
chatContext?: z.infer;
- messages: z.infer[];
- setMessages: (messages: z.infer[]) => void;
- loadingResponse: boolean;
- setLoadingResponse: (loading: boolean) => void;
- loadingMessage: string;
- setLoadingMessage: (message: string) => void;
- responseError: string | null;
- setResponseError: (error: string | null) => void;
}) {
const messagesEndRef = useRef(null);
+ const [messages, setMessages] = useState[]>([]);
+ const [loadingResponse, setLoadingResponse] = useState(false);
+ const [loadingMessage, setLoadingMessage] = useState("Thinking");
+ const [responseError, setResponseError] = useState(null);
const [appliedChanges, setAppliedChanges] = useState>({});
const [discardContext, setDiscardContext] = useState(false);
const [lastRequest, setLastRequest] = useState(null);
const [lastResponse, setLastResponse] = useState(null);
+ // Check for initial prompt in local storage and send it
+ useEffect(() => {
+ const prompt = localStorage.getItem(`project_prompt_${projectId}`);
+ if (prompt && messages.length === 0) {
+ localStorage.removeItem(`project_prompt_${projectId}`);
+ setMessages([{
+ role: 'user',
+ content: prompt
+ }]);
+ }
+ }, [projectId, messages.length, setMessages]);
+
// First useEffect for loading messages
useEffect(() => {
setLoadingMessage("Thinking");
@@ -480,8 +481,8 @@ function App({
{effectiveContext.type === 'tool' && `Tool: ${effectiveContext.name}`}
{effectiveContext.type === 'prompt' && `Prompt: ${effectiveContext.name}`}
- setDiscardContext(true)}
>
@@ -502,65 +503,42 @@ function App({
export function Copilot({
projectId,
workflow,
- chatContext=undefined,
+ chatContext = undefined,
dispatch,
- onNewChat,
- messages,
- setMessages,
- loadingResponse,
- setLoadingResponse,
- loadingMessage,
- setLoadingMessage,
- responseError,
- setResponseError,
}: {
projectId: string;
workflow: z.infer;
chatContext?: z.infer;
dispatch: (action: WorkflowDispatch) => void;
- onNewChat: () => void;
- messages: z.infer[];
- setMessages: (messages: z.infer[]) => void;
- loadingResponse: boolean;
- setLoadingResponse: (loading: boolean) => void;
- loadingMessage: string;
- setLoadingMessage: (message: string) => void;
- responseError: string | null;
- setResponseError: (error: string | null) => void;
}) {
+ const [copilotKey, setCopilotKey] = useState(0);
+
+ function handleNewChat() {
+ setCopilotKey(prev => prev + 1);
+ }
+
return (
-
-
-
- }
- onClick={onNewChat}
+ icon={ }
+ onClick={handleNewChat}
>
New
]}
>
);
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/entity_list.tsx b/apps/rowboat/app/projects/[projectId]/workflow/entity_list.tsx
index 0a243951..9dca2490 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/entity_list.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/entity_list.tsx
@@ -6,7 +6,7 @@ import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/r
import { useRef, useEffect } from "react";
import { ActionButton, StructuredPanel } from "../../../lib/components/structured-panel";
import clsx from "clsx";
-import { EllipsisVerticalIcon } from "lucide-react";
+import { EllipsisVerticalIcon, ImportIcon, PlusIcon } from "lucide-react";
import { SectionHeader, ListItem } from "../../../lib/components/structured-list";
interface EntityListProps {
@@ -29,6 +29,7 @@ interface EntityListProps {
onDeleteAgent: (name: string) => void;
onDeleteTool: (name: string) => void;
onDeletePrompt: (name: string) => void;
+ triggerMcpImport: () => void;
}
export function EntityList({
@@ -48,6 +49,7 @@ export function EntityList({
onDeleteAgent,
onDeleteTool,
onDeletePrompt,
+ triggerMcpImport,
}: EntityListProps) {
const selectedRef = useRef
(null);
@@ -58,13 +60,20 @@ export function EntityList({
}, [selectedEntity]);
return (
-
{/* Agents Section */}
-
onAddAgent({})} />
+
+ }
+ onClick={() => onAddAgent({})}
+ >
+ Add
+
+
{agents.map((agent, index) => (
onAddTool({})} />
+
+ }
+ onClick={() => onAddTool({})}
+ >
+ Add
+
+ }
+ onClick={triggerMcpImport}
+ >
+ MCP
+
+
+
{tools.map((tool, index) => (
onSelectTool(tool.name)}
selectedRef={selectedEntity?.type === "tool" && selectedEntity.name === tool.name ? selectedRef : undefined}
rightElement={ }
+ icon={tool.isMcp ? : undefined}
/>
))}
{/* Prompts Section */}
- onAddPrompt({})} />
+
+ }
+ onClick={() => onAddPrompt({})}
+ >
+ Add
+
+
{prompts.map((prompt, index) => (
void;
+ onImport: (tools: z.infer[]) => void;
+}
+
+export function McpImportTools({ projectId, isOpen, onOpenChange, onImport }: McpImportToolsProps) {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [tools, setTools] = useState[]>([]);
+ const [selectedTools, setSelectedTools] = useState>(new Set());
+
+ const process = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+ setSelectedTools(new Set());
+ try {
+ const result = await fetchMcpTools(projectId);
+ setTools(result);
+ // Select all tools by default
+ setSelectedTools(new Set(result.map((_, index) => index)));
+ } catch (error) {
+ setError(`Unable to fetch tools: ${error}`);
+ } finally {
+ setLoading(false);
+ }
+ }, [projectId]);
+
+ useEffect(() => {
+ console.log("mcp import tools useEffect", isOpen);
+ if (isOpen) {
+ process();
+ }
+ }, [isOpen, process]);
+
+ return (
+
+
+ {(onClose) => (
+ <>
+ Import from MCP servers
+
+ {loading &&
+
+ Fetching tools...
+
}
+ {error &&
+ {error}
+ process()}>Retry
+
}
+ {!loading && !error && <>
+
+
+ {tools.length === 0 ? "No tools found" : `Found ${tools.length} tools:`}
+
+
{
+ setTools([]);
+ process();
+ }}
+ startContent={ }
+ >
+ Refresh
+
+
+ {tools.length > 0 &&
+
+
+ 0 && selectedTools.size < tools.length}
+ onValueChange={(checked) => {
+ if (checked) {
+ setSelectedTools(new Set(tools.map((_, i) => i)));
+ } else {
+ setSelectedTools(new Set());
+ }
+ }}
+ />
+
+
Server
+
Tool Name
+
+
+ {tools.map((t, index) => (
+
+
+ {
+ const newSelected = new Set(selectedTools);
+ if (checked) {
+ newSelected.add(index);
+ } else {
+ newSelected.delete(index);
+ }
+ setSelectedTools(newSelected);
+ }}
+ />
+
+
+
+ {t.mcpServerName}
+
+
+
{t.name}
+
+ ))}
+
+
}
+ {tools.length > 0 && (
+
+ {selectedTools.size} of {tools.length} tools selected
+
+ )}
+ >}
+
+
+
+ Cancel
+
+ {tools.length > 0 && {
+ const selectedToolsList = tools.filter((_, index) => selectedTools.has(index));
+ onImport(selectedToolsList);
+ onClose();
+ }}>
+ Import
+ }
+
+ >
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx
index e4d99289..09ec0573 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx
@@ -1,8 +1,8 @@
import { Metadata } from "next";
import { App } from "./app";
import { USE_RAG } from "@/app/lib/feature_flags";
-
-export const dynamic = 'force-dynamic';
+import { projectsCollection } from "@/app/lib/mongodb";
+import { notFound } from "next/navigation";
export const metadata: Metadata = {
title: "Workflow"
@@ -13,6 +13,14 @@ export default async function Page({
}: {
params: { projectId: string };
}) {
+ console.log('->>> workflow page being rendered');
+ const project = await projectsCollection.findOne({
+ _id: params.projectId,
+ });
+ if (!project) {
+ notFound();
+ }
+
return void,
handleDelete: (name: string) => void,
- handleRename: (oldName: string, newName: string) => void
+ handleRename: (oldName: string, newName: string) => void,
+ readOnly?: boolean
}) {
return handleDelete(param.name)}
@@ -41,7 +41,7 @@ export function ParameterConfig({
>
Remove
- ]}
+ ] : []}
>
@@ -69,6 +70,7 @@ export function ParameterConfig({
type: Array.from(keys)[0] as string
});
}}
+ isDisabled={readOnly}
>
{['string', 'number', 'boolean', 'array', 'object'].map(type => (
@@ -89,6 +91,7 @@ export function ParameterConfig({
description: desc
});
}}
+ locked={readOnly}
/>
@@ -102,6 +105,7 @@ export function ParameterConfig({
required: !param.required
});
}}
+ isDisabled={readOnly}
>
Required
@@ -121,6 +125,7 @@ export function ToolConfig({
handleClose: () => void
}) {
const [selectedParams, setSelectedParams] = useState(new Set([]));
+ const isReadOnly = tool.isMcp;
function handleParamRename(oldName: string, newName: string) {
const newProperties = { ...tool.parameters!.properties };
@@ -193,6 +198,13 @@ export function ToolConfig({
]}>
+ {tool.isMcp &&
+
+
+
Imported from MCP server: {tool.mcpServerName}
+
+
}
+
@@ -221,89 +234,92 @@ export function ToolConfig({
description: value
})}
placeholder="Describe what this tool does..."
+ locked={isReadOnly}
/>
-
+ {!isReadOnly && <>
+
-
-
handleUpdate({
- ...tool,
- mockTool: value === "mock",
- autoSubmitMockedResponse: value === "mock" ? true : undefined
- })}
- orientation="horizontal"
- classNames={{
- wrapper: "gap-8",
- label: "text-sm"
- }}
- >
-
+ handleUpdate({
+ ...tool,
+ mockTool: value === "mock",
+ autoSubmitMockedResponse: value === "mock" ? true : undefined
+ })}
+ orientation="horizontal"
classNames={{
- base: "max-w-[50%]",
- label: "text-sm font-normal"
+ wrapper: "gap-8",
+ label: "text-sm"
}}
>
- Mock tool responses
-
-
- Connect tool to your API
-
-
-
- {tool.mockTool && <>
-
- handleUpdate({
- ...tool,
- autoSubmitMockedResponse: value
- })}
>
- Auto-submit mocked response in playground
-
-
+ Mock tool responses
+
+
+ Connect tool to your API
+
+
-
+ {tool.mockTool && <>
+
+ handleUpdate({
+ ...tool,
+ autoSubmitMockedResponse: value
+ })}
+ >
+ Auto-submit mocked response in playground
+
+
-
handleUpdate({
- ...tool,
- mockInstructions: value
- })}
- placeholder="Enter mock instructions..."
- multiline
- />
- >}
+
- {!tool.mockTool && (
-
- Please configure your webhook in the Integrate page if you haven't already.
-
- )}
-
+
handleUpdate({
+ ...tool,
+ mockInstructions: value
+ })}
+ placeholder="Enter mock instructions..."
+ multiline
+ />
+ >}
-
+ {!tool.mockTool && (
+
+ Please configure your webhook in the Integrate page if you haven't already.
+
+ )}
+
+
+
+ >}
@@ -320,11 +336,12 @@ export function ToolConfig({
handleUpdate={handleParamUpdate}
handleDelete={handleParamDelete}
handleRename={handleParamRename}
+ readOnly={isReadOnly}
/>
))}
-
Add Parameter
-
+ }
);
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
index 4923e067..55f79470 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
@@ -1,5 +1,5 @@
"use client";
-import { WithStringId } from "../../../lib/types/types";
+import { MCPServer, WithStringId } from "../../../lib/types/types";
import { Workflow } from "../../../lib/types/workflow_types";
import { WorkflowTool } from "../../../lib/types/workflow_types";
import { WorkflowPrompt } from "../../../lib/types/workflow_types";
@@ -26,10 +26,9 @@ import { apiV1 } from "rowboat-shared";
import { publishWorkflow, renameWorkflow, saveWorkflow } from "../../../actions/workflow_actions";
import { PublishedBadge } from "./published_badge";
import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/icons";
-import { CopyIcon, Layers2Icon, RadioIcon, RedoIcon, Sparkles, UndoIcon } from "lucide-react";
+import { CopyIcon, ImportIcon, Layers2Icon, RadioIcon, RedoIcon, ServerIcon, Sparkles, UndoIcon } from "lucide-react";
import { EntityList } from "./entity_list";
-import { CopilotMessage } from "../../../lib/types/copilot_types";
-import { TestProfile } from "@/app/lib/types/testing_types";
+import { McpImportTools } from "./mcp_imports";
enablePatches();
@@ -132,6 +131,9 @@ export type Action = {
} | {
type: "restore_state";
state: StateItem;
+} | {
+ type: "import_mcp_tools";
+ tools: z.infer[];
};
function reducer(state: State, action: Action): State {
@@ -273,10 +275,10 @@ function reducer(state: State, action: Action): State {
if (isLive) {
break;
}
- let newToolName = "New tool";
+ let newToolName = "new_tool";
if (draft.workflow?.tools.some((tool) => tool.name === newToolName)) {
- newToolName = `New tool ${draft.workflow.tools.filter((tool) =>
- tool.name.startsWith("New tool")).length + 1}`;
+ newToolName = `new_tool_${draft.workflow.tools.filter((tool) =>
+ tool.name.startsWith("new_tool")).length + 1}`;
}
draft.workflow?.tools.push({
name: newToolName,
@@ -509,6 +511,26 @@ function reducer(state: State, action: Action): State {
draft.workflow.startAgent = action.name;
draft.chatKey++;
break;
+ case "import_mcp_tools":
+ if (isLive) {
+ break;
+ }
+ // Process each tool one by one
+ action.tools.forEach(newTool => {
+ const existingToolIndex = draft.workflow.tools.findIndex(
+ tool => tool.name === newTool.name
+ );
+
+ if (existingToolIndex !== -1) {
+ // Replace existing tool
+ draft.workflow.tools[existingToolIndex] = newTool;
+ } else {
+ // Add new tool
+ draft.workflow.tools.push(newTool);
+ }
+ });
+ draft.chatKey++;
+ break;
}
}
);
@@ -535,6 +557,8 @@ export function WorkflowEditor({
handleShowSelector,
handleCloneVersion,
useRag,
+ mcpServerUrls,
+ toolWebhookUrl,
}: {
dataSources: WithStringId>[];
workflow: WithStringId>;
@@ -542,6 +566,8 @@ export function WorkflowEditor({
handleShowSelector: () => void;
handleCloneVersion: (workflowId: string) => void;
useRag: boolean;
+ mcpServerUrls: Array>;
+ toolWebhookUrl: string;
}) {
const [state, dispatch] = useReducer>(reducer, {
patches: [],
@@ -570,14 +596,19 @@ export function WorkflowEditor({
const [showCopySuccess, setShowCopySuccess] = useState(false);
const [showCopilot, setShowCopilot] = useState(false);
const [copilotWidth, setCopilotWidth] = useState(25);
- const [copilotKey, setCopilotKey] = useState(0);
- const [copilotMessages, setCopilotMessages] = useState[]>([]);
- const [loadingResponse, setLoadingResponse] = useState(false);
- const [loadingMessage, setLoadingMessage] = useState("Thinking...");
- const [responseError, setResponseError] = useState(null);
+ const [isMcpImportModalOpen, setIsMcpImportModalOpen] = useState(false);
console.log(`workflow editor chat key: ${state.present.chatKey}`);
+ // Auto-show copilot and increment key when prompt is present
+ useEffect(() => {
+ const prompt = localStorage.getItem(`project_prompt_${state.present.workflow.projectId}`);
+ console.log('init project prompt', prompt);
+ if (prompt) {
+ setShowCopilot(true);
+ }
+ }, [state.present.workflow.projectId]);
+
function handleSelectAgent(name: string) {
dispatch({ type: "select_agent", name });
}
@@ -674,6 +705,10 @@ export function WorkflowEditor({
}, 1500);
}
+ function triggerMcpImport() {
+ setIsMcpImportModalOpen(true);
+ }
+
const processQueue = useCallback(async (state: State, dispatch: React.Dispatch) => {
if (saving.current || saveQueue.current.length === 0) return;
@@ -697,6 +732,10 @@ export function WorkflowEditor({
}
}, [isLive]);
+ function handleImportMcpTools(tools: z.infer[]) {
+ dispatch({ type: "import_mcp_tools", tools });
+ }
+
useEffect(() => {
if (state.present.pendingChanges && state.present.workflow) {
saveQueue.current.push(state.present.workflow);
@@ -732,7 +771,7 @@ export function WorkflowEditor({
{
if (key === 'switch') {
@@ -848,6 +887,7 @@ export function WorkflowEditor({
onDeleteAgent={handleDeleteAgent}
onDeleteTool={handleDeleteTool}
onDeletePrompt={handleDeletePrompt}
+ triggerMcpImport={triggerMcpImport}
/>
@@ -862,6 +902,8 @@ export function WorkflowEditor({
projectId={state.present.workflow.projectId}
workflow={state.present.workflow}
messageSubscriber={updateChatMessages}
+ mcpServerUrls={mcpServerUrls}
+ toolWebhookUrl={toolWebhookUrl}
/>
{state.present.selection?.type === "agent" && setCopilotWidth(size)}
>
{
- setCopilotKey(prev => prev + 1);
- setCopilotMessages([]);
- setLoadingResponse(false);
- setLoadingMessage("Thinking...");
- setResponseError(null);
- }}
- messages={copilotMessages}
- setMessages={setCopilotMessages}
- loadingResponse={loadingResponse}
- setLoadingResponse={setLoadingResponse}
- loadingMessage={loadingMessage}
- setLoadingMessage={setLoadingMessage}
- responseError={responseError}
- setResponseError={setResponseError}
/>
>}
+
;
}
diff --git a/apps/rowboat/app/projects/layout.tsx b/apps/rowboat/app/projects/layout.tsx
index d5ec6c00..3ac4b885 100644
--- a/apps/rowboat/app/projects/layout.tsx
+++ b/apps/rowboat/app/projects/layout.tsx
@@ -4,6 +4,9 @@ import Image from "next/image";
import Link from "next/link";
import { UserButton } from "../lib/components/user_button";
import { ThemeToggle } from "../lib/components/theme-toggle";
+import { USE_AUTH } from "../lib/feature_flags";
+
+export const dynamic = 'force-dynamic';
export default function Layout({
children,
@@ -30,7 +33,7 @@ export default function Layout({
-
+ {USE_AUTH && }
diff --git a/apps/rowboat/app/projects/new/app.tsx b/apps/rowboat/app/projects/new/app.tsx
index eb2de1b5..7777093e 100644
--- a/apps/rowboat/app/projects/new/app.tsx
+++ b/apps/rowboat/app/projects/new/app.tsx
@@ -1,42 +1,155 @@
'use client';
-import { cn, Input } from "@heroui/react";
-import { createProject } from "../../actions/project_actions";
-import { templates } from "../../lib/project_templates";
+import { cn, Input, Textarea } from "@heroui/react";
+import { createProject, createProjectFromPrompt } from "../../actions/project_actions";
+import { templates, starting_copilot_prompts } from "../../lib/project_templates";
import { WorkflowTemplate } from "../../lib/types/workflow_types";
import { FormStatusButton } from "../../lib/components/form-status-button";
import { useFormStatus } from "react-dom";
import { z } from "zod";
import { useState } from "react";
-import { CheckIcon, PlusIcon } from "lucide-react";
+import { CheckIcon, PlusIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
+import { useRouter } from 'next/navigation';
+import React from "react";
-function TemplateCard({
- templateKey,
- template,
+function CustomPromptCard({
onSelect,
- selected
+ selected,
+ onPromptChange,
+ customPrompt
}: {
- templateKey: string,
- template: z.infer,
- onSelect: (templateKey: string) => void,
- selected: boolean
+ onSelect: () => void,
+ selected: boolean,
+ onPromptChange: (prompt: string) => void,
+ customPrompt: string
}) {
return
+ {selected &&
+
+
}
+ Custom Prompt
+ {selected ? (
+ {
+ e.stopPropagation();
+ onPromptChange(e.target.value);
+ }}
+ onClick={(e) => e.stopPropagation()}
+ className="min-h-[100px] text-sm w-full"
+ />
+ ) : (
+
+ “Create an assistant for a food delivery app that can take new orders, cancel existing orders and answer questions about refund policies”
+
+ )}
+
+}
+
+function TemplateCard({
+ templateKey,
+ template,
+ onSelect,
+ selected,
+ type = "template"
+}: {
+ templateKey: string,
+ template: z.infer | string,
+ onSelect: (templateKey: string) => void,
+ selected: boolean,
+ type?: "template" | "prompt"
+}) {
+ const [isExpanded, setIsExpanded] = useState(false);
+ const name = typeof template === "string" ? templateKey : template.name;
+ const description = typeof template === "string"
+ ? `"${template}"`
+ : template.description;
+
+ // Check if text needs expansion button
+ const textRef = React.useRef(null);
+ const [needsExpansion, setNeedsExpansion] = useState(false);
+
+ React.useEffect(() => {
+ if (textRef.current) {
+ const needsButton = textRef.current.scrollHeight > textRef.current.clientHeight;
+ setNeedsExpansion(needsButton);
+ }
+ }, [description]);
+
+ return onSelect(templateKey)}
>
{selected &&
}
-
{template.name}
-
{template.description}
-
+
+
+
{name}
+
+
+ {description}
+
+ {needsExpansion && (
+
{
+ e.stopPropagation();
+ setIsExpanded(!isExpanded);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsExpanded(!isExpanded);
+ }
+ }}
+ className={cn(
+ "absolute right-0 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 cursor-pointer",
+ isExpanded ? "relative mt-1" : "bottom-0"
+ )}
+ aria-label={isExpanded ? "Show less" : "Show more"}
+ >
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
}
function Submit() {
@@ -57,46 +170,119 @@ function Submit() {
export default function App() {
const [selectedTemplate, setSelectedTemplate] = useState('default');
+ const [selectedType, setSelectedType] = useState<"template" | "prompt">("template");
+ const [customPrompt, setCustomPrompt] = useState('');
const { default: defaultTemplate, ...otherTemplates } = templates;
+ const router = useRouter();
- function handleTemplateClick(templateKey: string) {
+ function handleTemplateClick(templateKey: string, type: "template" | "prompt" = "template") {
setSelectedTemplate(templateKey);
+ setSelectedType(type);
+ }
+
+ async function handleSubmit(formData: FormData) {
+ if (selectedType === "template") {
+ console.log('Creating template project');
+ return await createProject(formData);
+ }
+
+ if (selectedType === "prompt") {
+ console.log('Starting prompt-based project creation');
+ try {
+ const newFormData = new FormData();
+ const projectName = formData.get('name') as string;
+ const promptText = selectedTemplate === 'custom'
+ ? customPrompt
+ : starting_copilot_prompts[selectedTemplate];
+
+ newFormData.append('name', projectName);
+ newFormData.append('prompt', promptText);
+
+ console.log('Creating project...');
+ const response = await createProjectFromPrompt(newFormData);
+ console.log('Create project response:', response);
+
+ if (!response?.id) {
+ throw new Error('Project creation failed - no project ID returned');
+ }
+
+ // write prompt to local storage
+ localStorage.setItem(`project_prompt_${response.id}`, promptText);
+ router.push(`/projects/${response.id}/workflow`);
+ } catch (error) {
+ console.error('Error creating project:', error);
+ }
+ }
}
return
-
Create a new project
-
-
-
- Select a template
-
- Create a new project
+
+
+
Name your assistant
+
- {Object.entries(otherTemplates).map(([key, template]) => (
-
- ))}
+
+
+
+
+
+
Tell us what you would like to build
+
handleTemplateClick('custom', 'prompt')}
+ selected={selectedTemplate === 'custom' && selectedType === "prompt"}
+ onPromptChange={setCustomPrompt}
+ customPrompt={customPrompt}
+ />
+
+
+
+
Or start with an example starting prompt
+
+ {Object.entries(starting_copilot_prompts).map(([key, prompt]) => (
+ handleTemplateClick(key, "prompt")}
+ selected={selectedTemplate === key && selectedType === "prompt"}
+ type="prompt"
+ />
+ ))}
+
+
+
+
+
Or choose a pre-built example assistant
+
+ handleTemplateClick(key, "template")}
+ selected={selectedTemplate === 'default' && selectedType === "template"}
+ />
+ {Object.entries(otherTemplates).map(([key, template]) => (
+ handleTemplateClick(key, "template")}
+ selected={selectedTemplate === key && selectedType === "template"}
+ />
+ ))}
+
+
+
+
;
-}
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/scripts/rag_text_worker.ts b/apps/rowboat/app/scripts/rag_text_worker.ts
new file mode 100644
index 00000000..513d8db0
--- /dev/null
+++ b/apps/rowboat/app/scripts/rag_text_worker.ts
@@ -0,0 +1,285 @@
+import '../lib/loadenv';
+import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
+import { z } from 'zod';
+import { dataSourceDocsCollection, dataSourcesCollection } from '../lib/mongodb';
+import { EmbeddingRecord, DataSourceDoc, DataSource } from "../lib/types/datasource_types";
+import { WithId } from 'mongodb';
+import { embedMany } from 'ai';
+import { embeddingModel } from '../lib/embedding';
+import { qdrantClient } from '../lib/qdrant';
+import { PrefixLogger } from "../lib/utils";
+import crypto from 'crypto';
+
+const splitter = new RecursiveCharacterTextSplitter({
+ separators: ['\n\n', '\n', '. ', '.', ''],
+ chunkSize: 1024,
+ chunkOverlap: 20,
+});
+
+const second = 1000;
+const minute = 60 * second;
+const hour = 60 * minute;
+
+async function runProcessPipeline(_logger: PrefixLogger, job: WithId>, doc: WithId>): Promise {
+ const logger = _logger
+ .child(doc._id.toString())
+ .child(doc.name);
+
+ if (doc.data.type !== 'text') {
+ throw new Error("Invalid data source type");
+ }
+
+ // split into chunks
+ logger.log("Splitting into chunks");
+ const splits = await splitter.createDocuments([doc.data.content]);
+
+ // generate embeddings
+ logger.log("Generating embeddings");
+ const { embeddings } = await embedMany({
+ model: embeddingModel,
+ values: splits.map((split) => split.pageContent)
+ });
+
+ // store embeddings in qdrant
+ logger.log("Storing embeddings in Qdrant");
+ const points: z.infer[] = embeddings.map((embedding, i) => ({
+ id: crypto.randomUUID(),
+ vector: embedding,
+ payload: {
+ projectId: job.projectId,
+ sourceId: job._id.toString(),
+ docId: doc._id.toString(),
+ content: splits[i].pageContent,
+ title: doc.name,
+ name: doc.name,
+ },
+ }));
+ await qdrantClient.upsert("embeddings", {
+ points,
+ });
+
+ // store content in doc record
+ logger.log("Storing content in doc record");
+ await dataSourceDocsCollection.updateOne({
+ _id: doc._id,
+ version: doc.version,
+ }, {
+ $set: {
+ content: doc.data.content,
+ status: "ready",
+ lastUpdatedAt: new Date().toISOString(),
+ }
+ });
+}
+
+async function runDeletionPipeline(_logger: PrefixLogger, job: WithId>, doc: WithId>): Promise {
+ const logger = _logger
+ .child(doc._id.toString())
+ .child(doc.name);
+
+ // Delete embeddings from qdrant
+ logger.log("Deleting embeddings from Qdrant");
+ await qdrantClient.delete("embeddings", {
+ filter: {
+ must: [
+ {
+ key: "projectId",
+ match: {
+ value: job.projectId,
+ }
+ },
+ {
+ key: "sourceId",
+ match: {
+ value: job._id.toString(),
+ }
+ },
+ {
+ key: "docId",
+ match: {
+ value: doc._id.toString(),
+ }
+ }
+ ],
+ },
+ });
+
+ // Delete docs from db
+ logger.log("Deleting doc from db");
+ await dataSourceDocsCollection.deleteOne({ _id: doc._id });
+}
+
+// fetch next job from mongodb
+(async () => {
+ while (true) {
+ console.log("Polling for job...")
+ const now = Date.now();
+ let job: WithId> | null = null;
+
+ // first try to find a job that needs deleting
+ job = await dataSourcesCollection.findOneAndUpdate({
+ status: "deleted",
+ $or: [
+ { attempts: { $exists: false } },
+ { attempts: { $lte: 3 } }
+ ]
+ }, { $set: { lastAttemptAt: new Date().toISOString() }, $inc: { attempts: 1 } }, { returnDocument: "after", sort: { createdAt: 1 } });
+
+ if (job === null) {
+ job = await dataSourcesCollection.findOneAndUpdate(
+ {
+ $and: [
+ { 'data.type': { $eq: "text" } },
+ {
+ $or: [
+ // if the job has never been attempted
+ {
+ status: "pending",
+ attempts: 0,
+ },
+ // if the job was attempted but wasn't completed in the last hour
+ {
+ status: "pending",
+ lastAttemptAt: { $lt: new Date(now - 1 * hour).toISOString() },
+ },
+ // if the job errored out but hasn't been retried 3 times yet
+ {
+ status: "error",
+ attempts: { $lt: 3 },
+ },
+ // if the job errored out but hasn't been retried in the last 5 minutes
+ {
+ status: "error",
+ lastAttemptAt: { $lt: new Date(now - 1 * hour).toISOString() },
+ },
+ ]
+ }
+ ]
+ },
+ {
+ $set: {
+ status: "pending",
+ lastAttemptAt: new Date().toISOString(),
+ },
+ $inc: {
+ attempts: 1
+ },
+ },
+ { returnDocument: "after", sort: { createdAt: 1 } }
+ );
+ }
+
+ if (job === null) {
+ // if no doc found, sleep for a bit and start again
+ await new Promise(resolve => setTimeout(resolve, 5 * second));
+ continue;
+ }
+
+ const logger = new PrefixLogger(`${job._id.toString()}-${job.version}`);
+ logger.log(`Starting job ${job._id}. Type: ${job.data.type}. Status: ${job.status}`);
+ let errors = false;
+
+ try {
+ if (job.data.type !== 'text') {
+ throw new Error("Invalid data source type");
+ }
+
+ if (job.status === "deleted") {
+ // delete all embeddings for this source
+ logger.log("Deleting embeddings from Qdrant");
+ await qdrantClient.delete("embeddings", {
+ filter: {
+ must: [
+ { key: "projectId", match: { value: job.projectId } },
+ { key: "sourceId", match: { value: job._id.toString() } },
+ ],
+ },
+ });
+
+ // delete all docs for this source
+ logger.log("Deleting docs from db");
+ await dataSourceDocsCollection.deleteMany({
+ sourceId: job._id.toString(),
+ });
+
+ // delete the source record from db
+ logger.log("Deleting source record from db");
+ await dataSourcesCollection.deleteOne({
+ _id: job._id,
+ });
+
+ logger.log("Job deleted");
+ continue;
+ }
+
+ // fetch docs that need updating
+ const pendingDocs = await dataSourceDocsCollection.find({
+ sourceId: job._id.toString(),
+ status: { $in: ["pending", "error"] },
+ }).toArray();
+
+ logger.log(`Found ${pendingDocs.length} docs to process`);
+
+ // for each doc
+ for (const doc of pendingDocs) {
+ try {
+ await runProcessPipeline(logger, job, doc);
+ } catch (e: any) {
+ errors = true;
+ logger.log("Error processing doc:", e);
+ await dataSourceDocsCollection.updateOne({
+ _id: doc._id,
+ version: doc.version,
+ }, {
+ $set: {
+ status: "error",
+ error: e.message,
+ }
+ });
+ }
+ }
+
+ // fetch docs that need to be deleted
+ const deletedDocs = await dataSourceDocsCollection.find({
+ sourceId: job._id.toString(),
+ status: "deleted",
+ }).toArray();
+
+ logger.log(`Found ${deletedDocs.length} docs to delete`);
+
+ for (const doc of deletedDocs) {
+ try {
+ await runDeletionPipeline(logger, job, doc);
+ } catch (e: any) {
+ errors = true;
+ logger.log("Error deleting doc:", e);
+ await dataSourceDocsCollection.updateOne({
+ _id: doc._id,
+ version: doc.version,
+ }, {
+ $set: {
+ status: "error",
+ error: e.message,
+ }
+ });
+ }
+ }
+ } catch (e) {
+ logger.log("Error processing job; will retry:", e);
+ await dataSourcesCollection.updateOne({ _id: job._id, version: job.version }, { $set: { status: "error" } });
+ continue;
+ }
+
+ // mark job as complete
+ logger.log("Marking job as completed...");
+ await dataSourcesCollection.updateOne({
+ _id: job._id,
+ version: job.version,
+ }, {
+ $set: {
+ status: errors ? "error" : "ready",
+ ...(errors ? { error: "There were some errors processing this job" } : {}),
+ }
+ });
+ }
+})();
diff --git a/apps/rowboat/middleware.ts b/apps/rowboat/middleware.ts
index f13b9340..d92821d8 100644
--- a/apps/rowboat/middleware.ts
+++ b/apps/rowboat/middleware.ts
@@ -34,6 +34,10 @@ export async function middleware(request: NextRequest, event: NextFetchEvent) {
}
if (request.nextUrl.pathname.startsWith('/projects')) {
+ // Skip auth check if USE_AUTH is not enabled
+ if (process.env.USE_AUTH !== 'true') {
+ return NextResponse.next();
+ }
return auth0MiddlewareHandler(request, event);
}
diff --git a/apps/rowboat/package-lock.json b/apps/rowboat/package-lock.json
index ad30d0bd..66470973 100644
--- a/apps/rowboat/package-lock.json
+++ b/apps/rowboat/package-lock.json
@@ -20,6 +20,7 @@
"@langchain/core": "^0.3.7",
"@langchain/textsplitters": "^0.1.0",
"@mendable/firecrawl-js": "^1.0.3",
+ "@modelcontextprotocol/sdk": "^1.7.0",
"@primer/react": "^36.27.0",
"@qdrant/js-client-rest": "^1.13.0",
"ai": "^3.3.28",
@@ -51,6 +52,7 @@
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"tiktoken": "^1.0.17",
+ "twilio": "^5.4.5",
"typewriter-effect": "^2.21.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.5"
@@ -10270,6 +10272,25 @@
"zod-to-json-schema": "^3.23.0"
}
},
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz",
+ "integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "eventsource": "^3.0.2",
+ "express": "^5.0.1",
+ "express-rate-limit": "^7.5.0",
+ "pkce-challenge": "^4.1.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz",
@@ -13920,6 +13941,37 @@
"node": ">=6.5"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.53.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
+ "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
+ "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
+ "dependencies": {
+ "mime-db": "^1.53.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -13940,6 +13992,17 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/agentkeepalive": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
@@ -14347,9 +14410,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
- "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
+ "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@@ -14438,6 +14501,50 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/body-parser": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz",
+ "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.5.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
+ "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -14509,6 +14616,11 @@
"node": ">=16.20.1"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -14520,6 +14632,14 @@
"node": ">=10.16.0"
}
},
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@@ -14539,6 +14659,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -14848,6 +14995,25 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -14862,6 +15028,26 @@
"node": ">= 0.6"
}
},
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -15028,12 +15214,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+ },
"node_modules/debug": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
- "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dependencies": {
- "ms": "2.1.2"
+ "ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -15044,6 +15235,11 @@
}
}
},
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@@ -15152,6 +15348,14 @@
"node": ">=0.4.0"
}
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -15160,6 +15364,15 @@
"node": ">=6"
}
},
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -15289,11 +15502,37 @@
"url": "https://dotenvx.com"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.74",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz",
@@ -15305,6 +15544,14 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/encoding-sniffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
@@ -15415,13 +15662,9 @@
}
},
"node_modules/es-define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
- "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
- "dev": true,
- "dependencies": {
- "get-intrinsic": "^1.2.4"
- },
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
@@ -15430,7 +15673,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -15481,10 +15723,9 @@
}
},
"node_modules/es-object-atoms": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
- "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
- "dev": true,
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dependencies": {
"es-errors": "^1.3.0"
},
@@ -15580,6 +15821,11 @@
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -16022,6 +16268,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
@@ -16035,6 +16289,17 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
+ "node_modules/eventsource": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.5.tgz",
+ "integrity": "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==",
+ "dependencies": {
+ "eventsource-parser": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/eventsource-parser": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
@@ -16043,6 +16308,127 @@
"node": ">=14.18"
}
},
+ "node_modules/eventsource/node_modules/eventsource-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz",
+ "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
+ "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.0.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "4.3.6",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "^2.0.0",
+ "fresh": "2.0.0",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "methods": "~1.1.2",
+ "mime-types": "^3.0.0",
+ "on-finished": "2.4.1",
+ "once": "1.4.0",
+ "parseurl": "~1.3.3",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "router": "^2.0.0",
+ "safe-buffer": "5.2.1",
+ "send": "^1.1.0",
+ "serve-static": "^2.1.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "^2.0.0",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
+ "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": "^4.11 || 5 || ^5.0.0-beta.1"
+ }
+ },
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+ "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.53.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
+ "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
+ "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
+ "dependencies": {
+ "mime-db": "^1.53.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -16160,6 +16546,22 @@
"node": ">=8"
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -16287,6 +16689,14 @@
"node": ">= 12.20"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/framer-motion": {
"version": "11.18.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
@@ -16313,6 +16723,14 @@
}
}
},
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -16390,16 +16808,20 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
- "dev": true,
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
@@ -16408,6 +16830,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
@@ -16543,12 +16977,11 @@
}
},
"node_modules/gopd": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dev": true,
- "dependencies": {
- "get-intrinsic": "^1.1.3"
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -16608,10 +17041,9 @@
}
},
"node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true,
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
@@ -16726,6 +17158,33 @@
"entities": "^4.5.0"
}
},
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@@ -16801,8 +17260,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/inline-style-parser": {
"version": "0.2.3",
@@ -16843,6 +17301,14 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-alphabetical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -17173,6 +17639,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
+ },
"node_modules/is-reference": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
@@ -17496,6 +17967,27 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -17511,6 +18003,25 @@
"node": ">=4.0"
}
},
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -17649,6 +18160,16 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+ },
"node_modules/lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
@@ -17660,17 +18181,42 @@
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
},
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+ },
"node_modules/lodash.isobject": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
"integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA=="
},
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+ },
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@@ -17722,6 +18268,14 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@@ -18060,6 +18614,14 @@
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"peer": true
},
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
@@ -18070,6 +18632,17 @@
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
},
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -18078,6 +18651,14 @@
"node": ">= 8"
}
},
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/micromark": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
@@ -18794,6 +19375,14 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "14.2.25",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.25.tgz",
@@ -18976,10 +19565,9 @@
}
},
"node_modules/object-inspect": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
- "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
- "dev": true,
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"engines": {
"node": ">= 0.4"
},
@@ -19101,11 +19689,21 @@
"node": "^10.13.0 || >=12.0.0"
}
},
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -19373,6 +19971,14 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -19419,6 +20025,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/path-to-regexp": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -19475,6 +20089,14 @@
"node": ">= 6"
}
},
+ "node_modules/pkce-challenge": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
+ "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@@ -19653,6 +20275,18 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -19666,6 +20300,20 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -19733,6 +20381,28 @@
"performance-now": "^2.1.0"
}
},
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -20088,6 +20758,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/router": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz",
+ "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==",
+ "dependencies": {
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/rowboat-shared": {
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/rowboatlabs/shared.git#1c8e722b8c5d644672a2db92f4ba5f25d8560352",
@@ -20146,6 +20829,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/safe-regex-test": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
@@ -20176,6 +20878,11 @@
"loose-envify": "^1.1.0"
}
},
+ "node_modules/scmp": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
+ "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="
+ },
"node_modules/scroll-into-view-if-needed": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz",
@@ -20200,6 +20907,55 @@
"node": ">=10"
}
},
+ "node_modules/send": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz",
+ "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "destroy": "^1.2.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^0.5.2",
+ "http-errors": "^2.0.0",
+ "mime-types": "^2.1.35",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/send/node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/serve-static": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz",
+ "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -20232,6 +20988,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
@@ -20296,15 +21057,65 @@
}
},
"node_modules/side-channel": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
- "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
- "dev": true,
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dependencies": {
- "call-bind": "^1.0.7",
"es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.4",
- "object-inspect": "^1.13.1"
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -20385,6 +21196,14 @@
"svelte": "^4.0.0 || ^5.0.0-next.0"
}
},
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -20940,6 +21759,14 @@
"node": ">=8.0"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/tr46": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
@@ -21022,6 +21849,23 @@
"fsevents": "~2.3.3"
}
},
+ "node_modules/twilio": {
+ "version": "5.4.5",
+ "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.4.5.tgz",
+ "integrity": "sha512-PIteif0CBOrA42SWZiT8IwUuqTNakAFgvXYWsrjEPGaDSczu/GvBs3vUock4S+UguXj7cV4qBswWgXs5ySjGNg==",
+ "dependencies": {
+ "axios": "^1.7.8",
+ "dayjs": "^1.11.9",
+ "https-proxy-agent": "^5.0.0",
+ "jsonwebtoken": "^9.0.2",
+ "qs": "^6.9.4",
+ "scmp": "^2.1.0",
+ "xmlbuilder": "^13.0.2"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -21046,6 +21890,38 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-is": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz",
+ "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.53.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
+ "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
+ "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
+ "dependencies": {
+ "mime-db": "^1.53.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/typed-array-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
@@ -21280,6 +22156,14 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
@@ -21374,6 +22258,14 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@@ -21403,6 +22295,14 @@
"node": ">=8"
}
},
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vfile": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz",
@@ -21690,8 +22590,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.0",
@@ -21714,6 +22613,14 @@
}
}
},
+ "node_modules/xmlbuilder": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz",
+ "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -21744,19 +22651,19 @@
}
},
"node_modules/zod": {
- "version": "3.23.8",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
- "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "version": "3.24.2",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
+ "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
- "version": "3.23.5",
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz",
- "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==",
+ "version": "3.24.3",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz",
+ "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==",
"peerDependencies": {
- "zod": "^3.23.3"
+ "zod": "^3.24.1"
}
},
"node_modules/zwitch": {
diff --git a/apps/rowboat/package.json b/apps/rowboat/package.json
index 175e854d..58f3621a 100644
--- a/apps/rowboat/package.json
+++ b/apps/rowboat/package.json
@@ -2,6 +2,7 @@
"name": "demo.rowboatlabs.com",
"version": "0.1.0",
"private": true,
+ "type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
@@ -10,7 +11,8 @@
"setupQdrant": "tsx app/scripts/setup_qdrant.ts",
"deleteQdrant": "tsx app/scripts/delete_qdrant.ts",
"ragUrlsWorker": "tsx app/scripts/rag_urls_worker.ts",
- "ragFilesWorker": "tsx app/scripts/rag_files_worker.ts"
+ "ragFilesWorker": "tsx app/scripts/rag_files_worker.ts",
+ "ragTextWorker": "tsx app/scripts/rag_text_worker.ts"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.37",
@@ -19,12 +21,13 @@
"@aws-sdk/s3-request-presigner": "^3.743.0",
"@google/generative-ai": "^0.21.0",
"@heroicons/react": "^2.2.0",
- "@langchain/core": "^0.3.7",
- "@langchain/textsplitters": "^0.1.0",
- "@mendable/firecrawl-js": "^1.0.3",
"@heroui/react": "2.7.4",
"@heroui/system": "2.4.11",
"@heroui/theme": "2.4.11",
+ "@langchain/core": "^0.3.7",
+ "@langchain/textsplitters": "^0.1.0",
+ "@mendable/firecrawl-js": "^1.0.3",
+ "@modelcontextprotocol/sdk": "^1.7.0",
"@primer/react": "^36.27.0",
"@qdrant/js-client-rest": "^1.13.0",
"ai": "^3.3.28",
@@ -56,6 +59,7 @@
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"tiktoken": "^1.0.17",
+ "twilio": "^5.4.5",
"typewriter-effect": "^2.21.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.5"
@@ -72,4 +76,4 @@
"tsx": "^4.19.1",
"typescript": "^5"
}
-}
\ No newline at end of file
+}
diff --git a/apps/agents/.dockerignore b/apps/rowboat_agents/.dockerignore
similarity index 83%
rename from apps/agents/.dockerignore
rename to apps/rowboat_agents/.dockerignore
index 4a45bc02..8b13b832 100644
--- a/apps/agents/.dockerignore
+++ b/apps/rowboat_agents/.dockerignore
@@ -2,3 +2,4 @@
.env*
__pycache__/
venv/
+.venv/
\ No newline at end of file
diff --git a/apps/agents/.env.example b/apps/rowboat_agents/.env.example
similarity index 100%
rename from apps/agents/.env.example
rename to apps/rowboat_agents/.env.example
diff --git a/apps/agents/.gitignore b/apps/rowboat_agents/.gitignore
similarity index 100%
rename from apps/agents/.gitignore
rename to apps/rowboat_agents/.gitignore
diff --git a/apps/agents/Dockerfile b/apps/rowboat_agents/Dockerfile
similarity index 86%
rename from apps/agents/Dockerfile
rename to apps/rowboat_agents/Dockerfile
index 592a5444..019c6c2d 100644
--- a/apps/agents/Dockerfile
+++ b/apps/rowboat_agents/Dockerfile
@@ -20,9 +20,9 @@ RUN poetry install --no-interaction --no-ansi
COPY . .
# Set environment variables
-ENV FLASK_APP=src.app.main
+ENV QUART_APP=src.app.main
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app
# Command to run Flask development server
-CMD ["flask", "run", "--host=0.0.0.0", "--port=3001"]
+CMD ["quart", "run", "--host=0.0.0.0", "--port=3001"]
diff --git a/apps/agents/NOTICE.md b/apps/rowboat_agents/NOTICE.md
similarity index 100%
rename from apps/agents/NOTICE.md
rename to apps/rowboat_agents/NOTICE.md
diff --git a/apps/agents/README.md b/apps/rowboat_agents/README.md
similarity index 100%
rename from apps/agents/README.md
rename to apps/rowboat_agents/README.md
diff --git a/apps/agents/__init__.py b/apps/rowboat_agents/__init__.py
similarity index 100%
rename from apps/agents/__init__.py
rename to apps/rowboat_agents/__init__.py
diff --git a/apps/agents/configs/default_config.json b/apps/rowboat_agents/configs/default_config.json
similarity index 100%
rename from apps/agents/configs/default_config.json
rename to apps/rowboat_agents/configs/default_config.json
diff --git a/apps/rowboat_agents/poetry.lock b/apps/rowboat_agents/poetry.lock
new file mode 100644
index 00000000..4332d3d6
--- /dev/null
+++ b/apps/rowboat_agents/poetry.lock
@@ -0,0 +1,4153 @@
+# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
+
+[[package]]
+name = "aiofiles"
+version = "24.1.0"
+description = "File support for asyncio."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"},
+ {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
+]
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.6.1"
+description = "Happy Eyeballs for asyncio"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
+ {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.11.14"
+description = "Async http client/server framework (asyncio)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d"},
+ {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa"},
+ {file = "aiohttp-3.11.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f"},
+ {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0"},
+ {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc"},
+ {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7"},
+ {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628"},
+ {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0"},
+ {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9"},
+ {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff"},
+ {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914"},
+ {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d"},
+ {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3"},
+ {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b"},
+ {file = "aiohttp-3.11.14-cp310-cp310-win32.whl", hash = "sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990"},
+ {file = "aiohttp-3.11.14-cp310-cp310-win_amd64.whl", hash = "sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186"},
+ {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325"},
+ {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b"},
+ {file = "aiohttp-3.11.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f"},
+ {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a"},
+ {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3"},
+ {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b"},
+ {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1"},
+ {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77"},
+ {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c"},
+ {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06"},
+ {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1"},
+ {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3"},
+ {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e"},
+ {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881"},
+ {file = "aiohttp-3.11.14-cp311-cp311-win32.whl", hash = "sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e"},
+ {file = "aiohttp-3.11.14-cp311-cp311-win_amd64.whl", hash = "sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654"},
+ {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc"},
+ {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a"},
+ {file = "aiohttp-3.11.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948"},
+ {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534"},
+ {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f"},
+ {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307"},
+ {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3"},
+ {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0"},
+ {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6"},
+ {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f"},
+ {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09"},
+ {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce"},
+ {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b"},
+ {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be"},
+ {file = "aiohttp-3.11.14-cp312-cp312-win32.whl", hash = "sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f"},
+ {file = "aiohttp-3.11.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c"},
+ {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50"},
+ {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965"},
+ {file = "aiohttp-3.11.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228"},
+ {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5"},
+ {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db"},
+ {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0"},
+ {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91"},
+ {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d"},
+ {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734"},
+ {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058"},
+ {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73"},
+ {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33"},
+ {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49"},
+ {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647"},
+ {file = "aiohttp-3.11.14-cp313-cp313-win32.whl", hash = "sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6"},
+ {file = "aiohttp-3.11.14-cp313-cp313-win_amd64.whl", hash = "sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3"},
+ {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de"},
+ {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda"},
+ {file = "aiohttp-3.11.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f"},
+ {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1"},
+ {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a"},
+ {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1"},
+ {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3"},
+ {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28"},
+ {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618"},
+ {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831"},
+ {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa"},
+ {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"},
+ {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a"},
+ {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff"},
+ {file = "aiohttp-3.11.14-cp39-cp39-win32.whl", hash = "sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db"},
+ {file = "aiohttp-3.11.14-cp39-cp39-win_amd64.whl", hash = "sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45"},
+ {file = "aiohttp-3.11.14.tar.gz", hash = "sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c"},
+]
+
+[package.dependencies]
+aiohappyeyeballs = ">=2.3.0"
+aiosignal = ">=1.1.2"
+async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
+attrs = ">=17.3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+propcache = ">=0.2.0"
+yarl = ">=1.17.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"]
+
+[[package]]
+name = "aiosignal"
+version = "1.3.2"
+description = "aiosignal: a list of registered asynchronous callbacks"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"},
+ {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"},
+]
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[[package]]
+name = "anyio"
+version = "4.8.0"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"},
+ {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
+
+[package.extras]
+doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
+trio = ["trio (>=0.26.1)"]
+
+[[package]]
+name = "asgiref"
+version = "3.8.1"
+description = "ASGI specs, helper code, and adapters"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
+ {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""}
+
+[package.extras]
+tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+
+[[package]]
+name = "async-timeout"
+version = "5.0.1"
+description = "Timeout context manager for asyncio programs"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_full_version < \"3.11.3\""
+files = [
+ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
+ {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
+]
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
+ {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
+tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "backports-tarfile"
+version = "1.2.0"
+description = "Backport of CPython tarfile module"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\""
+files = [
+ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"},
+ {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.12.3"
+description = "Screen-scraping library"
+optional = false
+python-versions = ">=3.6.0"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"},
+ {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"},
+]
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+cchardet = ["cchardet"]
+chardet = ["chardet"]
+charset-normalizer = ["charset-normalizer"]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+name = "blinker"
+version = "1.9.0"
+description = "Fast, simple object-to-object and broadcast signaling"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"},
+ {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"},
+]
+
+[[package]]
+name = "build"
+version = "1.2.2.post1"
+description = "A simple, correct Python build frontend"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"},
+ {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "os_name == \"nt\""}
+importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""}
+packaging = ">=19.1"
+pyproject_hooks = "*"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"]
+test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
+typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
+uv = ["uv (>=0.1.18)"]
+virtualenv = ["virtualenv (>=20.0.35)"]
+
+[[package]]
+name = "cachecontrol"
+version = "0.14.2"
+description = "httplib2 caching for requests"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"},
+ {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"},
+]
+
+[package.dependencies]
+filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""}
+msgpack = ">=0.5.2,<2.0.0"
+requests = ">=2.16.0"
+
+[package.extras]
+dev = ["CacheControl[filecache,redis]", "build", "cherrypy", "codespell[tomli]", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"]
+filecache = ["filelock (>=3.8.0)"]
+redis = ["redis (>=2.10.5)"]
+
+[[package]]
+name = "certifi"
+version = "2024.12.14"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
+ {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
+]
+
+[[package]]
+name = "cffi"
+version = "1.17.1"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
+ {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
+ {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
+ {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
+ {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
+ {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
+ {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
+ {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
+ {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
+ {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
+ {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
+ {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
+ {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
+ {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
+ {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
+ {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
+ {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
+ {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
+ {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
+ {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
+ {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
+ {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
+ {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
+ {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
+ {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
+ {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
+ {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
+ {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
+ {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
+ {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
+ {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
+ {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
+ {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
+ {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
+ {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
+ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
+ {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.1"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
+ {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
+ {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
+ {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
+ {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
+ {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
+ {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
+ {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
+ {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
+ {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
+]
+
+[[package]]
+name = "cleo"
+version = "2.1.0"
+description = "Cleo allows you to create beautiful and testable command-line interfaces."
+optional = false
+python-versions = ">=3.7,<4.0"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"},
+ {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"},
+]
+
+[package.dependencies]
+crashtest = ">=0.4.1,<0.5.0"
+rapidfuzz = ">=3.0.0,<4.0.0"
+
+[[package]]
+name = "click"
+version = "8.1.8"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
+ {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "crashtest"
+version = "0.4.1"
+description = "Manage Python errors with ease"
+optional = false
+python-versions = ">=3.7,<4.0"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"},
+ {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"},
+]
+
+[[package]]
+name = "cryptography"
+version = "44.0.2"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+optional = false
+python-versions = "!=3.9.0,!=3.9.1,>=3.7"
+groups = ["main"]
+markers = "(python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\") and sys_platform == \"linux\""
+files = [
+ {file = "cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a"},
+ {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308"},
+ {file = "cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688"},
+ {file = "cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7"},
+ {file = "cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79"},
+ {file = "cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa"},
+ {file = "cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9"},
+ {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23"},
+ {file = "cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922"},
+ {file = "cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4"},
+ {file = "cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5"},
+ {file = "cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6"},
+ {file = "cryptography-44.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb"},
+ {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41"},
+ {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562"},
+ {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5"},
+ {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa"},
+ {file = "cryptography-44.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d"},
+ {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d"},
+ {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471"},
+ {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615"},
+ {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390"},
+ {file = "cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0"},
+]
+
+[package.dependencies]
+cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
+docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
+nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
+pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
+sdist = ["build (>=1.0.0)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
+test-randomorder = ["pytest-randomly"]
+
+[[package]]
+name = "distlib"
+version = "0.3.9"
+description = "Distribution utilities"
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
+ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+description = "Distro - an OS platform information API"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"},
+ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
+]
+
+[[package]]
+name = "dnspython"
+version = "2.7.0"
+description = "DNS toolkit"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"},
+ {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"},
+]
+
+[package.extras]
+dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
+dnssec = ["cryptography (>=43)"]
+doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
+doq = ["aioquic (>=1.0.0)"]
+idna = ["idna (>=3.7)"]
+trio = ["trio (>=0.23)"]
+wmi = ["wmi (>=1.5.1)"]
+
+[[package]]
+name = "dulwich"
+version = "0.22.8"
+description = "Python Git Library"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"},
+ {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"},
+ {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8318bc0921d42e3e69f03716f983a301b5ee4c8dc23c7f2c5bbb28581257a9"},
+ {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7a0f96a2a87f3b4f7feae79d2ac6b94107d6b7d827ac08f2f331b88c8f597a1"},
+ {file = "dulwich-0.22.8-cp310-cp310-win32.whl", hash = "sha256:432a37b25733202897b8d67cdd641688444d980167c356ef4e4dd15a17a39a24"},
+ {file = "dulwich-0.22.8-cp310-cp310-win_amd64.whl", hash = "sha256:f3a15e58dac8b8a76073ddca34e014f66f3672a5540a99d49ef6a9c09ab21285"},
+ {file = "dulwich-0.22.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0852edc51cff4f4f62976bdaa1d82f6ef248356c681c764c0feb699bc17d5782"},
+ {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:826aae8b64ac1a12321d6b272fc13934d8f62804fda2bc6ae46f93f4380798eb"},
+ {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ae726f923057d36cdbb9f4fb7da0d0903751435934648b13f1b851f0e38ea1"},
+ {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6987d753227f55cf75ba29a8dab69d1d83308ce483d7a8c6d223086f7a42e125"},
+ {file = "dulwich-0.22.8-cp311-cp311-win32.whl", hash = "sha256:7757b4a2aad64c6f1920082fc1fccf4da25c3923a0ae7b242c08d06861dae6e1"},
+ {file = "dulwich-0.22.8-cp311-cp311-win_amd64.whl", hash = "sha256:12b243b7e912011c7225dc67480c313ac8d2990744789b876016fb593f6f3e19"},
+ {file = "dulwich-0.22.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d81697f74f50f008bb221ab5045595f8a3b87c0de2c86aa55be42ba97421f3cd"},
+ {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bff1da8e2e6a607c3cb45f5c2e652739589fe891245e1d5b770330cdecbde41"},
+ {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9969099e15b939d3936f8bee8459eaef7ef5a86cd6173393a17fe28ca3d38aff"},
+ {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:017152c51b9a613f0698db28c67cf3e0a89392d28050dbf4f4ac3f657ea4c0dc"},
+ {file = "dulwich-0.22.8-cp312-cp312-win32.whl", hash = "sha256:ee70e8bb8798b503f81b53f7a103cb869c8e89141db9005909f79ab1506e26e9"},
+ {file = "dulwich-0.22.8-cp312-cp312-win_amd64.whl", hash = "sha256:dc89c6f14dcdcbfee200b0557c59ae243835e42720be143526d834d0e53ed3af"},
+ {file = "dulwich-0.22.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbade3342376be1cd2409539fe1b901d2d57a531106bbae204da921ef4456a74"},
+ {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71420ffb6deebc59b2ce875e63d814509f9c1dc89c76db962d547aebf15670c7"},
+ {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a626adbfac44646a125618266a24133763bdc992bf8bd0702910d67e6b994443"},
+ {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f1476c9c4e4ede95714d06c4831883a26680e37b040b8b6230f506e5ba39f51"},
+ {file = "dulwich-0.22.8-cp313-cp313-win32.whl", hash = "sha256:b2b31913932bb5bd41658dd398b33b1a2d4d34825123ad54e40912cfdfe60003"},
+ {file = "dulwich-0.22.8-cp313-cp313-win_amd64.whl", hash = "sha256:7a44e5a61a7989aca1e301d39cfb62ad2f8853368682f524d6e878b4115d823d"},
+ {file = "dulwich-0.22.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9cd0c67fb44a38358b9fcabee948bf11044ef6ce7a129e50962f54c176d084e"},
+ {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b79b94726c3f4a9e5a830c649376fd0963236e73142a4290bac6bc9fc9cb120"},
+ {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16bbe483d663944972e22d64e1f191201123c3b5580fbdaac6a4f66bfaa4fc11"},
+ {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e02d403af23d93dc1f96eb2408e25efd50046e38590a88c86fa4002adc9849b0"},
+ {file = "dulwich-0.22.8-cp39-cp39-win32.whl", hash = "sha256:8bdd9543a77fb01be704377f5e634b71f955fec64caa4a493dc3bfb98e3a986e"},
+ {file = "dulwich-0.22.8-cp39-cp39-win_amd64.whl", hash = "sha256:3b6757c6b3ba98212b854a766a4157b9cb79a06f4e1b06b46dec4bd834945b8e"},
+ {file = "dulwich-0.22.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7bb18fa09daa1586c1040b3e2777d38d4212a5cdbe47d384ba66a1ac336fcc4c"},
+ {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2fda8e87907ed304d4a5962aea0338366144df0df60f950b8f7f125871707f"},
+ {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1748cd573a0aee4d530bc223a23ccb8bb5b319645931a37bd1cfb68933b720c1"},
+ {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a631b2309feb9a9631eabd896612ba36532e3ffedccace57f183bb868d7afc06"},
+ {file = "dulwich-0.22.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:00e7d9a3d324f9e0a1b27880eec0e8e276ff76519621b66c1a429ca9eb3f5a8d"},
+ {file = "dulwich-0.22.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f8aa3de93201f9e3e40198725389aa9554a4ee3318a865f96a8e9bc9080f0b25"},
+ {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e8da9dd8135884975f5be0563ede02179240250e11f11942801ae31ac293f37"},
+ {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc5ce2435fb3abdf76f1acabe48f2e4b3f7428232cadaef9daaf50ea7fa30ee"},
+ {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982b21cc3100d959232cadb3da0a478bd549814dd937104ea50f43694ec27153"},
+ {file = "dulwich-0.22.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6bde2b13a05cc0ec2ecd4597a99896663544c40af1466121f4d046119b874ce3"},
+ {file = "dulwich-0.22.8-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6d446cb7d272a151934ad4b48ba691f32486d5267cf2de04ee3b5e05fc865326"},
+ {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f6338e6cf95cd76a0191b3637dc3caed1f988ae84d8e75f876d5cd75a8dd81a"},
+ {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e004fc532ea262f2d5f375068101ca4792becb9d4aa663b050f5ac31fda0bb5c"},
+ {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bfdbc6fa477dee00d04e22d43a51571cd820cfaaaa886f0f155b8e29b3e3d45"},
+ {file = "dulwich-0.22.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ae900c8e573f79d714c1d22b02cdadd50b64286dd7203028f0200f82089e4950"},
+ {file = "dulwich-0.22.8-py3-none-any.whl", hash = "sha256:ffc7a02e62b72884de58baaa3b898b7f6427893e79b1289ffa075092efe59181"},
+ {file = "dulwich-0.22.8.tar.gz", hash = "sha256:701547310415de300269331abe29cb5717aa1ea377af826bf513d0adfb1c209b"},
+]
+
+[package.dependencies]
+urllib3 = ">=1.25"
+
+[package.extras]
+dev = ["mypy (==1.15.0)", "ruff (==0.9.7)"]
+fastimport = ["fastimport"]
+https = ["urllib3 (>=1.24.1)"]
+paramiko = ["paramiko"]
+pgp = ["gpg"]
+
+[[package]]
+name = "et-xmlfile"
+version = "2.0.0"
+description = "An implementation of lxml.xmlfile for the standard library"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"},
+ {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"},
+]
+
+[[package]]
+name = "eval-type-backport"
+version = "0.2.2"
+description = "Like `typing._eval_type`, but lets older Python versions use newer typing features."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a"},
+ {file = "eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1"},
+]
+
+[package.extras]
+tests = ["pytest"]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.2.2"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
+ {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.21.1"
+description = "Fastest Python implementation of JSON schema"
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"},
+ {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"},
+]
+
+[package.extras]
+devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"]
+
+[[package]]
+name = "filelock"
+version = "3.18.0"
+description = "A platform independent file lock."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
+ {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
+typing = ["typing-extensions (>=4.12.2)"]
+
+[[package]]
+name = "findpython"
+version = "0.6.3"
+description = "A utility to find python versions on your system"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"},
+ {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"},
+]
+
+[package.dependencies]
+packaging = ">=20"
+
+[[package]]
+name = "firecrawl"
+version = "1.9.0"
+description = "Python SDK for Firecrawl API"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "firecrawl-1.9.0-py3-none-any.whl", hash = "sha256:9c0931242048ddd86e85631db439011fa0545cbcca21f7c41c89d13116bb2187"},
+ {file = "firecrawl-1.9.0.tar.gz", hash = "sha256:8e82d3b288d57bf7c93b9118fb226a84b7c8b84699861e1d36b4791dc87a7435"},
+]
+
+[package.dependencies]
+nest-asyncio = "*"
+pydantic = ">=2.10.3"
+python-dotenv = "*"
+requests = "*"
+websockets = "*"
+
+[[package]]
+name = "flask"
+version = "3.1.0"
+description = "A simple framework for building complex web applications."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"},
+ {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"},
+]
+
+[package.dependencies]
+blinker = ">=1.9"
+click = ">=8.1.3"
+itsdangerous = ">=2.2"
+Jinja2 = ">=3.1.2"
+Werkzeug = ">=3.1"
+
+[package.extras]
+async = ["asgiref (>=3.2)"]
+dotenv = ["python-dotenv"]
+
+[[package]]
+name = "frozenlist"
+version = "1.5.0"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"},
+ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"},
+ {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"},
+ {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"},
+ {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"},
+ {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"},
+ {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"},
+ {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"},
+ {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"},
+ {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"},
+ {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"},
+ {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"},
+ {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"},
+ {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"},
+ {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"},
+ {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"},
+ {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"},
+ {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"},
+ {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"},
+ {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"},
+ {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"},
+ {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"},
+ {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"},
+ {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"},
+ {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"},
+ {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"},
+ {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"},
+ {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"},
+ {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"},
+ {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"},
+ {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"},
+ {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"},
+ {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"},
+ {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"},
+ {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"},
+ {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"},
+ {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"},
+ {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"},
+ {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"},
+ {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"},
+ {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"},
+ {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"},
+ {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"},
+ {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"},
+ {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"},
+ {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"},
+ {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"},
+ {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"},
+ {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"},
+ {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"},
+ {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"},
+ {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"},
+ {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"},
+ {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"},
+ {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"},
+ {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"},
+ {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"},
+ {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"},
+ {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"},
+ {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"},
+ {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"},
+ {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"},
+ {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"},
+ {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"},
+ {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"},
+ {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"},
+ {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"},
+ {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"},
+ {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"},
+ {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"},
+ {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"},
+ {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"},
+ {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"},
+ {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"},
+ {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"},
+ {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"},
+ {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"},
+ {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"},
+ {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"},
+ {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"},
+ {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"},
+ {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"},
+ {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"},
+ {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"},
+ {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"},
+ {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"},
+ {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"},
+ {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"},
+ {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"},
+ {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"},
+ {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"},
+ {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"},
+]
+
+[[package]]
+name = "griffe"
+version = "1.6.2"
+description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "griffe-1.6.2-py3-none-any.whl", hash = "sha256:6399f7e663150e4278a312a8e8a14d2f3d7bd86e2ef2f8056a1058e38579c2ee"},
+ {file = "griffe-1.6.2.tar.gz", hash = "sha256:3a46fa7bd83280909b63c12b9a975732a927dd97809efe5b7972290b606c5d91"},
+]
+
+[package.dependencies]
+colorama = ">=0.4"
+
+[[package]]
+name = "grpcio"
+version = "1.71.0"
+description = "HTTP/2-based RPC framework"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"},
+ {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"},
+ {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"},
+ {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"},
+ {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"},
+ {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"},
+ {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"},
+ {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"},
+ {file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"},
+ {file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"},
+ {file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"},
+ {file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"},
+ {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"},
+ {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"},
+ {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"},
+ {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"},
+ {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"},
+ {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"},
+ {file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"},
+ {file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"},
+ {file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"},
+ {file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"},
+ {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"},
+ {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"},
+ {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"},
+ {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"},
+ {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"},
+ {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"},
+ {file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"},
+ {file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"},
+ {file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"},
+ {file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"},
+ {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"},
+ {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"},
+ {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"},
+ {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"},
+ {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"},
+ {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"},
+ {file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"},
+ {file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"},
+ {file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"},
+ {file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"},
+ {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"},
+ {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"},
+ {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"},
+ {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"},
+ {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"},
+ {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"},
+ {file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"},
+ {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"},
+ {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.71.0)"]
+
+[[package]]
+name = "grpcio-tools"
+version = "1.71.0"
+description = "Protobuf code generator for gRPC"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "grpcio_tools-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:f4ad7f0d756546902597053d70b3af2606fbd70d7972876cd75c1e241d22ae00"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:64bdb291df61cf570b5256777ad5fe2b1db6d67bc46e55dc56a0a862722ae329"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:8dd9795e982d77a4b496f7278b943c2563d9afde2069cdee78c111a40cc4d675"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1b5860c41a36b26fec4f52998f1a451d0525a5c9a4fb06b6ea3e9211abdb925"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3059c14035e5dc03d462f261e5900b9a077fd1a36976c3865b8507474520bad4"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f360981b215b1d5aff9235b37e7e1826246e35bbac32a53e41d4e990a37b8f4c"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bfe3888c3bbe16a5aa39409bc38744a31c0c3d2daa2b0095978c56e106c85b42"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:145985c0bf12131f0a1503e65763e0f060473f7f3928ed1ff3fb0e8aad5bc8ac"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-win32.whl", hash = "sha256:82c430edd939bb863550ee0fecf067d78feff828908a1b529bbe33cc57f2419c"},
+ {file = "grpcio_tools-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:83e90724e3f02415c628e4ead1d6ffe063820aaaa078d9a39176793df958cd5a"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1f19b16b49afa5d21473f49c0966dd430c88d089cd52ac02404d8cef67134efb"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:459c8f5e00e390aecd5b89de67deb3ec7188a274bc6cb50e43cef35ab3a3f45d"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:edab7e6518de01196be37f96cb1e138c3819986bf5e2a6c9e1519b4d716b2f5a"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b93b9f6adc7491d4c10144c0643409db298e5e63c997106a804f6f0248dbaf4"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ae5f2efa9e644c10bf1021600bfc099dfbd8e02b184d2d25dc31fcd6c2bc59e"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:65aa082f4435571d65d5ce07fc444f23c3eff4f3e34abef599ef8c9e1f6f360f"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1331e726e08b7bdcbf2075fcf4b47dff07842b04845e6e220a08a4663e232d7f"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6693a7d3ba138b0e693b3d1f687cdd9db9e68976c3fa2b951c17a072fea8b583"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-win32.whl", hash = "sha256:6d11ed3ff7b6023b5c72a8654975324bb98c1092426ba5b481af406ff559df00"},
+ {file = "grpcio_tools-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:072b2a5805ac97e4623b3aa8f7818275f3fb087f4aa131b0fce00471065f6eaa"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:61c0409d5bdac57a7bd0ce0ab01c1c916728fe4c8a03d77a25135ad481eb505c"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:28784f39921d061d2164a9dcda5164a69d07bf29f91f0ea50b505958292312c9"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:192808cf553cedca73f0479cc61d5684ad61f24db7a5f3c4dfe1500342425866"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:989ee9da61098230d3d4c8f8f8e27c2de796f1ff21b1c90110e636d9acd9432b"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:541a756276c8a55dec991f6c0106ae20c8c8f5ce8d0bdbfcb01e2338d1a8192b"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:870c0097700d13c403e5517cb7750ab5b4a791ce3e71791c411a38c5468b64bd"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:abd57f615e88bf93c3c6fd31f923106e3beb12f8cd2df95b0d256fa07a7a0a57"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:753270e2d06d37e6d7af8967d1d059ec635ad215882041a36294f4e2fd502b2e"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-win32.whl", hash = "sha256:0e647794bd7138b8c215e86277a9711a95cf6a03ff6f9e555d54fdf7378b9f9d"},
+ {file = "grpcio_tools-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:48debc879570972d28bfe98e4970eff25bb26da3f383e0e49829b2d2cd35ad87"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:9a78d07d6c301a25ef5ede962920a522556a1dfee1ccc05795994ceb867f766c"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:580ac88141c9815557e63c9c04f5b1cdb19b4db8d0cb792b573354bde1ee8b12"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f7c678e68ece0ae908ecae1c4314a0c2c7f83e26e281738b9609860cc2c82d96"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56ecd6cc89b5e5eed1de5eb9cafce86c9c9043ee3840888cc464d16200290b53"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a041afc20ab2431d756b6295d727bd7adee813b21b06a3483f4a7a15ea15f"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a1712f12102b60c8d92779b89d0504e0d6f3a59f2b933e5622b8583f5c02992"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:41878cb7a75477e62fdd45e7e9155b3af1b7a5332844021e2511deaf99ac9e6c"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:682e958b476049ccc14c71bedf3f979bced01f6e0c04852efc5887841a32ad6b"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-win32.whl", hash = "sha256:0ccfb837152b7b858b9f26bb110b3ae8c46675d56130f6c2f03605c4f129be13"},
+ {file = "grpcio_tools-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:ffff9bc5eacb34dd26b487194f7d44a3e64e752fc2cf049d798021bf25053b87"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:834959b6eceb85de5217a411aba1643b5f782798680c122202d6a06177226644"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:e3ae9556e2a1cd70e7d7b0e0459c35af71d51a7dae4cf36075068011a69f13ec"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:77fe6db1334e0ce318b2cb4e70afa94e0c173ed1a533d37aea69ad9f61ae8ea9"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57e3e2544c306b60ef2d76570bac4e977be1ad548641c9eec130c3bc47e80141"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af39e245fa56f7f5c2fe86b7d6c1b78f395c07e54d5613cbdbb3c24769a92b6e"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f987d0053351217954543b174b0bddbf51d45b3cfcf8d6de97b0a43d264d753"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8e6cdbba4dae7b37b0d25d074614be9936fb720144420f03d9f142a80be69ba2"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3adc8b229e60c77bab5a5d62b415667133bd5ced7d59b5f71d6317c9143631e"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f68334d28a267fabec6e70cb5986e9999cfbfd14db654094ddf9aedd804a293a"},
+ {file = "grpcio_tools-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:1291a6136c07a86c3bb09f6c33f5cf227cc14956edd1b85cb572327a36e0aef8"},
+ {file = "grpcio_tools-1.71.0.tar.gz", hash = "sha256:38dba8e0d5e0fb23a034e09644fdc6ed862be2371887eee54901999e8f6792a8"},
+]
+
+[package.dependencies]
+grpcio = ">=1.71.0"
+protobuf = ">=5.26.1,<6.0dev"
+setuptools = "*"
+
+[[package]]
+name = "gunicorn"
+version = "23.0.0"
+description = "WSGI HTTP Server for UNIX"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
+ {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
+]
+
+[package.dependencies]
+packaging = "*"
+
+[package.extras]
+eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
+gevent = ["gevent (>=1.4.0)"]
+setproctitle = ["setproctitle"]
+testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
+tornado = ["tornado (>=0.2)"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "h2"
+version = "4.2.0"
+description = "Pure-Python HTTP/2 protocol implementation"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"},
+ {file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"},
+]
+
+[package.dependencies]
+hpack = ">=4.1,<5"
+hyperframe = ">=6.1,<7"
+
+[[package]]
+name = "hpack"
+version = "4.1.0"
+description = "Pure-Python HPACK header encoding"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"},
+ {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"},
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.7"
+description = "A minimal low-level HTTP client."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
+ {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
+]
+
+[package.dependencies]
+certifi = "*"
+h11 = ">=0.13,<0.15"
+
+[package.extras]
+asyncio = ["anyio (>=4.0,<5.0)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+trio = ["trio (>=0.22.0,<1.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.27.2"
+description = "The next generation HTTP client."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
+ {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
+]
+
+[package.dependencies]
+anyio = "*"
+certifi = "*"
+h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""}
+httpcore = "==1.*"
+idna = "*"
+sniffio = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "httpx-sse"
+version = "0.4.0"
+description = "Consume Server-Sent Event (SSE) messages with HTTPX."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"},
+ {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"},
+]
+
+[[package]]
+name = "hypercorn"
+version = "0.17.3"
+description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "hypercorn-0.17.3-py3-none-any.whl", hash = "sha256:059215dec34537f9d40a69258d323f56344805efb462959e727152b0aa504547"},
+ {file = "hypercorn-0.17.3.tar.gz", hash = "sha256:1b37802ee3ac52d2d85270700d565787ab16cf19e1462ccfa9f089ca17574165"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+h11 = "*"
+h2 = ">=3.1.0"
+priority = "*"
+taskgroup = {version = "*", markers = "python_version < \"3.11\""}
+tomli = {version = "*", markers = "python_version < \"3.11\""}
+typing_extensions = {version = "*", markers = "python_version < \"3.11\""}
+wsproto = ">=0.14.0"
+
+[package.extras]
+docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"]
+h3 = ["aioquic (>=0.9.0,<1.0)"]
+trio = ["trio (>=0.22.0)"]
+uvloop = ["uvloop (>=0.18)"]
+
+[[package]]
+name = "hyperframe"
+version = "6.1.0"
+description = "Pure-Python HTTP/2 framing"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"},
+ {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"},
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+ {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
+]
+
+[package.extras]
+all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.6.1"
+description = "Read metadata from Python packages"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\""
+files = [
+ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
+ {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
+]
+
+[package.dependencies]
+zipp = ">=3.20"
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+perf = ["ipython"]
+test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "installer"
+version = "0.7.0"
+description = "A library for installing Python wheels."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"},
+ {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"},
+]
+
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+description = "Safely pass data to untrusted environments and back."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
+ {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
+]
+
+[[package]]
+name = "jaraco-classes"
+version = "3.4.0"
+description = "Utility functions for Python class constructs"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"},
+ {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"},
+]
+
+[package.dependencies]
+more-itertools = "*"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
+
+[[package]]
+name = "jaraco-context"
+version = "6.0.1"
+description = "Useful decorators and context managers"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"},
+ {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"},
+]
+
+[package.dependencies]
+"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""}
+
+[package.extras]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
+
+[[package]]
+name = "jaraco-functools"
+version = "4.1.0"
+description = "Functools like those found in stdlib"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"},
+ {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"},
+]
+
+[package.dependencies]
+more-itertools = "*"
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "jeepney"
+version = "0.9.0"
+description = "Low-level, pure Python DBus protocol wrapper."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "(python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\") and sys_platform == \"linux\""
+files = [
+ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"},
+ {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"},
+]
+
+[package.extras]
+test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"]
+trio = ["trio"]
+
+[[package]]
+name = "jinja2"
+version = "3.1.5"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
+ {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jiter"
+version = "0.6.1"
+description = "Fast iterable JSON parser."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "jiter-0.6.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d08510593cb57296851080018006dfc394070178d238b767b1879dc1013b106c"},
+ {file = "jiter-0.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adef59d5e2394ebbad13b7ed5e0306cceb1df92e2de688824232a91588e77aa7"},
+ {file = "jiter-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e02f7a27f2bcc15b7d455c9df05df8ffffcc596a2a541eeda9a3110326e7a3"},
+ {file = "jiter-0.6.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed69a7971d67b08f152c17c638f0e8c2aa207e9dd3a5fcd3cba294d39b5a8d2d"},
+ {file = "jiter-0.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2019d966e98f7c6df24b3b8363998575f47d26471bfb14aade37630fae836a1"},
+ {file = "jiter-0.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36c0b51a285b68311e207a76c385650322734c8717d16c2eb8af75c9d69506e7"},
+ {file = "jiter-0.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:220e0963b4fb507c525c8f58cde3da6b1be0bfddb7ffd6798fb8f2531226cdb1"},
+ {file = "jiter-0.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa25c7a9bf7875a141182b9c95aed487add635da01942ef7ca726e42a0c09058"},
+ {file = "jiter-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e90552109ca8ccd07f47ca99c8a1509ced93920d271bb81780a973279974c5ab"},
+ {file = "jiter-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:67723a011964971864e0b484b0ecfee6a14de1533cff7ffd71189e92103b38a8"},
+ {file = "jiter-0.6.1-cp310-none-win32.whl", hash = "sha256:33af2b7d2bf310fdfec2da0177eab2fedab8679d1538d5b86a633ebfbbac4edd"},
+ {file = "jiter-0.6.1-cp310-none-win_amd64.whl", hash = "sha256:7cea41c4c673353799906d940eee8f2d8fd1d9561d734aa921ae0f75cb9732f4"},
+ {file = "jiter-0.6.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b03c24e7da7e75b170c7b2b172d9c5e463aa4b5c95696a368d52c295b3f6847f"},
+ {file = "jiter-0.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:47fee1be677b25d0ef79d687e238dc6ac91a8e553e1a68d0839f38c69e0ee491"},
+ {file = "jiter-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0d2f6e01a8a0fb0eab6d0e469058dab2be46ff3139ed2d1543475b5a1d8e7"},
+ {file = "jiter-0.6.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b809e39e342c346df454b29bfcc7bca3d957f5d7b60e33dae42b0e5ec13e027"},
+ {file = "jiter-0.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e9ac7c2f092f231f5620bef23ce2e530bd218fc046098747cc390b21b8738a7a"},
+ {file = "jiter-0.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e51a2d80d5fe0ffb10ed2c82b6004458be4a3f2b9c7d09ed85baa2fbf033f54b"},
+ {file = "jiter-0.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3343d4706a2b7140e8bd49b6c8b0a82abf9194b3f0f5925a78fc69359f8fc33c"},
+ {file = "jiter-0.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82521000d18c71e41c96960cb36e915a357bc83d63a8bed63154b89d95d05ad1"},
+ {file = "jiter-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c843e7c1633470708a3987e8ce617ee2979ee18542d6eb25ae92861af3f1d62"},
+ {file = "jiter-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a2e861658c3fe849efc39b06ebb98d042e4a4c51a8d7d1c3ddc3b1ea091d0784"},
+ {file = "jiter-0.6.1-cp311-none-win32.whl", hash = "sha256:7d72fc86474862c9c6d1f87b921b70c362f2b7e8b2e3c798bb7d58e419a6bc0f"},
+ {file = "jiter-0.6.1-cp311-none-win_amd64.whl", hash = "sha256:3e36a320634f33a07794bb15b8da995dccb94f944d298c8cfe2bd99b1b8a574a"},
+ {file = "jiter-0.6.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1fad93654d5a7dcce0809aff66e883c98e2618b86656aeb2129db2cd6f26f867"},
+ {file = "jiter-0.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4e6e340e8cd92edab7f6a3a904dbbc8137e7f4b347c49a27da9814015cc0420c"},
+ {file = "jiter-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:691352e5653af84ed71763c3c427cff05e4d658c508172e01e9c956dfe004aba"},
+ {file = "jiter-0.6.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:defee3949313c1f5b55e18be45089970cdb936eb2a0063f5020c4185db1b63c9"},
+ {file = "jiter-0.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26d2bdd5da097e624081c6b5d416d3ee73e5b13f1703bcdadbb1881f0caa1933"},
+ {file = "jiter-0.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18aa9d1626b61c0734b973ed7088f8a3d690d0b7f5384a5270cd04f4d9f26c86"},
+ {file = "jiter-0.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3567c8228afa5ddcce950631c6b17397ed178003dc9ee7e567c4c4dcae9fa0"},
+ {file = "jiter-0.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5c0507131c922defe3f04c527d6838932fcdfd69facebafd7d3574fa3395314"},
+ {file = "jiter-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:540fcb224d7dc1bcf82f90f2ffb652df96f2851c031adca3c8741cb91877143b"},
+ {file = "jiter-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e7b75436d4fa2032b2530ad989e4cb0ca74c655975e3ff49f91a1a3d7f4e1df2"},
+ {file = "jiter-0.6.1-cp312-none-win32.whl", hash = "sha256:883d2ced7c21bf06874fdeecab15014c1c6d82216765ca6deef08e335fa719e0"},
+ {file = "jiter-0.6.1-cp312-none-win_amd64.whl", hash = "sha256:91e63273563401aadc6c52cca64a7921c50b29372441adc104127b910e98a5b6"},
+ {file = "jiter-0.6.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:852508a54fe3228432e56019da8b69208ea622a3069458252f725d634e955b31"},
+ {file = "jiter-0.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f491cc69ff44e5a1e8bc6bf2b94c1f98d179e1aaf4a554493c171a5b2316b701"},
+ {file = "jiter-0.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc56c8f0b2a28ad4d8047f3ae62d25d0e9ae01b99940ec0283263a04724de1f3"},
+ {file = "jiter-0.6.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51b58f7a0d9e084a43b28b23da2b09fc5e8df6aa2b6a27de43f991293cab85fd"},
+ {file = "jiter-0.6.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f79ce15099154c90ef900d69c6b4c686b64dfe23b0114e0971f2fecd306ec6c"},
+ {file = "jiter-0.6.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03a025b52009f47e53ea619175d17e4ded7c035c6fbd44935cb3ada11e1fd592"},
+ {file = "jiter-0.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74a8d93718137c021d9295248a87c2f9fdc0dcafead12d2930bc459ad40f885"},
+ {file = "jiter-0.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40b03b75f903975f68199fc4ec73d546150919cb7e534f3b51e727c4d6ccca5a"},
+ {file = "jiter-0.6.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:825651a3f04cf92a661d22cad61fc913400e33aa89b3e3ad9a6aa9dc8a1f5a71"},
+ {file = "jiter-0.6.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:928bf25eb69ddb292ab8177fe69d3fbf76c7feab5fce1c09265a7dccf25d3991"},
+ {file = "jiter-0.6.1-cp313-none-win32.whl", hash = "sha256:352cd24121e80d3d053fab1cc9806258cad27c53cad99b7a3cac57cf934b12e4"},
+ {file = "jiter-0.6.1-cp313-none-win_amd64.whl", hash = "sha256:be7503dd6f4bf02c2a9bacb5cc9335bc59132e7eee9d3e931b13d76fd80d7fda"},
+ {file = "jiter-0.6.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:31d8e00e1fb4c277df8ab6f31a671f509ebc791a80e5c61fdc6bc8696aaa297c"},
+ {file = "jiter-0.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77c296d65003cd7ee5d7b0965f6acbe6cffaf9d1fa420ea751f60ef24e85fed5"},
+ {file = "jiter-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeeb0c0325ef96c12a48ea7e23e2e86fe4838e6e0a995f464cf4c79fa791ceeb"},
+ {file = "jiter-0.6.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a31c6fcbe7d6c25d6f1cc6bb1cba576251d32795d09c09961174fe461a1fb5bd"},
+ {file = "jiter-0.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59e2b37f3b9401fc9e619f4d4badcab2e8643a721838bcf695c2318a0475ae42"},
+ {file = "jiter-0.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bae5ae4853cb9644144e9d0755854ce5108d470d31541d83f70ca7ecdc2d1637"},
+ {file = "jiter-0.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9df588e9c830b72d8db1dd7d0175af6706b0904f682ea9b1ca8b46028e54d6e9"},
+ {file = "jiter-0.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15f8395e835cf561c85c1adee72d899abf2733d9df72e9798e6d667c9b5c1f30"},
+ {file = "jiter-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a99d4e0b5fc3b05ea732d67eb2092fe894e95a90e6e413f2ea91387e228a307"},
+ {file = "jiter-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a311df1fa6be0ccd64c12abcd85458383d96e542531bafbfc0a16ff6feda588f"},
+ {file = "jiter-0.6.1-cp38-none-win32.whl", hash = "sha256:81116a6c272a11347b199f0e16b6bd63f4c9d9b52bc108991397dd80d3c78aba"},
+ {file = "jiter-0.6.1-cp38-none-win_amd64.whl", hash = "sha256:13f9084e3e871a7c0b6e710db54444088b1dd9fbefa54d449b630d5e73bb95d0"},
+ {file = "jiter-0.6.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f1c53615fcfec3b11527c08d19cff6bc870da567ce4e57676c059a3102d3a082"},
+ {file = "jiter-0.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f791b6a4da23238c17a81f44f5b55d08a420c5692c1fda84e301a4b036744eb1"},
+ {file = "jiter-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c97e90fec2da1d5f68ef121444c2c4fa72eabf3240829ad95cf6bbeca42a301"},
+ {file = "jiter-0.6.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cbc1a66b4e41511209e97a2866898733c0110b7245791ac604117b7fb3fedb7"},
+ {file = "jiter-0.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e85f9e12cd8418ab10e1fcf0e335ae5bb3da26c4d13a0fd9e6a17a674783b6"},
+ {file = "jiter-0.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08be33db6dcc374c9cc19d3633af5e47961a7b10d4c61710bd39e48d52a35824"},
+ {file = "jiter-0.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:677be9550004f5e010d673d3b2a2b815a8ea07a71484a57d3f85dde7f14cf132"},
+ {file = "jiter-0.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e8bd065be46c2eecc328e419d6557bbc37844c88bb07b7a8d2d6c91c7c4dedc9"},
+ {file = "jiter-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bd95375ce3609ec079a97c5d165afdd25693302c071ca60c7ae1cf826eb32022"},
+ {file = "jiter-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db459ed22d0208940d87f614e1f0ea5a946d29a3cfef71f7e1aab59b6c6b2afb"},
+ {file = "jiter-0.6.1-cp39-none-win32.whl", hash = "sha256:d71c962f0971347bd552940ab96aa42ceefcd51b88c4ced8a27398182efa8d80"},
+ {file = "jiter-0.6.1-cp39-none-win_amd64.whl", hash = "sha256:d465db62d2d10b489b7e7a33027c4ae3a64374425d757e963f86df5b5f2e7fc5"},
+ {file = "jiter-0.6.1.tar.gz", hash = "sha256:e19cd21221fc139fb032e4112986656cb2739e9fe6d84c13956ab30ccc7d4449"},
+]
+
+[[package]]
+name = "jsonpath-python"
+version = "1.0.6"
+description = "A more powerful JSONPath implementation in modern python"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666"},
+ {file = "jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575"},
+]
+
+[[package]]
+name = "keyring"
+version = "25.6.0"
+description = "Store and access your passwords safely."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"},
+ {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"},
+]
+
+[package.dependencies]
+importlib_metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""}
+"jaraco.classes" = "*"
+"jaraco.context" = "*"
+"jaraco.functools" = "*"
+jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""}
+pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""}
+SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""}
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+completion = ["shtab (>=1.1.0)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["pyfakefs", "pytest (>=6,!=8.1.*)"]
+type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"]
+
+[[package]]
+name = "lxml"
+version = "5.3.0"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"},
+ {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"},
+ {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"},
+ {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"},
+ {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"},
+ {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"},
+ {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"},
+ {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"},
+ {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"},
+ {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"},
+ {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"},
+ {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"},
+ {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"},
+ {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"},
+ {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"},
+ {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"},
+ {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"},
+ {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"},
+ {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"},
+ {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"},
+ {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"},
+ {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"},
+ {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"},
+ {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"},
+ {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"},
+ {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"},
+ {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"},
+ {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"},
+ {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"},
+ {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"},
+ {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"},
+ {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"},
+ {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"},
+ {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"},
+ {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"},
+ {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"},
+ {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"},
+ {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"},
+ {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"},
+ {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"},
+ {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"},
+ {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"},
+ {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"},
+ {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"},
+ {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"},
+ {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"},
+ {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"},
+ {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"},
+ {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"},
+ {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"},
+ {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"},
+ {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"},
+ {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"},
+ {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"},
+ {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"},
+ {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"},
+ {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"},
+ {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"},
+ {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"},
+ {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"},
+ {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"},
+ {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"},
+ {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"},
+ {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"},
+ {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"},
+ {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"},
+ {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"},
+ {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"},
+ {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"},
+ {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"},
+ {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"},
+ {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"},
+ {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"},
+ {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"},
+ {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"},
+ {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"},
+ {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"},
+ {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"},
+ {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"},
+ {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"},
+ {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"},
+ {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"},
+ {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"},
+ {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"},
+ {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"},
+ {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"},
+ {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"},
+ {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"},
+ {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"},
+ {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"},
+ {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"},
+ {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"},
+ {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"},
+ {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"},
+ {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"},
+ {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"},
+ {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"},
+ {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"},
+]
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html-clean = ["lxml-html-clean"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=3.0.11)"]
+
+[[package]]
+name = "markdownify"
+version = "0.13.1"
+description = "Convert HTML to markdown."
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "markdownify-0.13.1-py3-none-any.whl", hash = "sha256:1d181d43d20902bcc69d7be85b5316ed174d0dda72ff56e14ae4c95a4a407d22"},
+ {file = "markdownify-0.13.1.tar.gz", hash = "sha256:ab257f9e6bd4075118828a28c9d02f8a4bfeb7421f558834aa79b2dfeb32a098"},
+]
+
+[package.dependencies]
+beautifulsoup4 = ">=4.9,<5"
+six = ">=1.15,<2"
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
+ {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
+ {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
+ {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
+ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
+]
+
+[[package]]
+name = "mcp"
+version = "1.5.0"
+description = "Model Context Protocol SDK"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "mcp-1.5.0-py3-none-any.whl", hash = "sha256:51c3f35ce93cb702f7513c12406bbea9665ef75a08db909200b07da9db641527"},
+ {file = "mcp-1.5.0.tar.gz", hash = "sha256:5b2766c05e68e01a2034875e250139839498c61792163a7b221fc170c12f5aa9"},
+]
+
+[package.dependencies]
+anyio = ">=4.5"
+httpx = ">=0.27"
+httpx-sse = ">=0.4"
+pydantic = ">=2.7.2,<3.0.0"
+pydantic-settings = ">=2.5.2"
+sse-starlette = ">=1.6.1"
+starlette = ">=0.27"
+uvicorn = ">=0.23.1"
+
+[package.extras]
+cli = ["python-dotenv (>=1.0.0)", "typer (>=0.12.4)"]
+rich = ["rich (>=13.9.4)"]
+ws = ["websockets (>=15.0.1)"]
+
+[[package]]
+name = "more-itertools"
+version = "10.6.0"
+description = "More routines for operating on iterables, beyond itertools"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"},
+ {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"},
+]
+
+[[package]]
+name = "motor"
+version = "3.7.0"
+description = "Non-blocking MongoDB driver for Tornado or asyncio"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "motor-3.7.0-py3-none-any.whl", hash = "sha256:61bdf1afded179f008d423f98066348157686f25a90776ea155db5f47f57d605"},
+ {file = "motor-3.7.0.tar.gz", hash = "sha256:0dfa1f12c812bd90819c519b78bed626b5a9dbb29bba079ccff2bfa8627e0fec"},
+]
+
+[package.dependencies]
+pymongo = ">=4.9,<5.0"
+
+[package.extras]
+aws = ["pymongo[aws] (>=4.5,<5)"]
+docs = ["aiohttp", "furo (==2024.8.6)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<8)", "sphinx-rtd-theme (>=2,<3)", "tornado"]
+encryption = ["pymongo[encryption] (>=4.5,<5)"]
+gssapi = ["pymongo[gssapi] (>=4.5,<5)"]
+ocsp = ["pymongo[ocsp] (>=4.5,<5)"]
+snappy = ["pymongo[snappy] (>=4.5,<5)"]
+test = ["aiohttp (>=3.8.7)", "cffi (>=1.17.0rc1)", "mockupdb", "pymongo[encryption] (>=4.5,<5)", "pytest (>=7)", "pytest-asyncio", "tornado (>=5)"]
+zstd = ["pymongo[zstd] (>=4.5,<5)"]
+
+[[package]]
+name = "msgpack"
+version = "1.1.0"
+description = "MessagePack serializer"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"},
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"},
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"},
+ {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"},
+ {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"},
+ {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"},
+ {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"},
+ {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"},
+ {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"},
+ {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"},
+ {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"},
+ {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"},
+ {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"},
+ {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"},
+ {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"},
+ {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"},
+]
+
+[[package]]
+name = "multidict"
+version = "6.2.0"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b9f6392d98c0bd70676ae41474e2eecf4c7150cb419237a41f8f96043fcb81d1"},
+ {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3501621d5e86f1a88521ea65d5cad0a0834c77b26f193747615b7c911e5422d2"},
+ {file = "multidict-6.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32ed748ff9ac682eae7859790d3044b50e3076c7d80e17a44239683769ff485e"},
+ {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc826b9a8176e686b67aa60fd6c6a7047b0461cae5591ea1dc73d28f72332a8a"},
+ {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:214207dcc7a6221d9942f23797fe89144128a71c03632bf713d918db99bd36de"},
+ {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05fefbc3cddc4e36da209a5e49f1094bbece9a581faa7f3589201fd95df40e5d"},
+ {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e851e6363d0dbe515d8de81fd544a2c956fdec6f8a049739562286727d4a00c3"},
+ {file = "multidict-6.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32c9b4878f48be3e75808ea7e499d6223b1eea6d54c487a66bc10a1871e3dc6a"},
+ {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7243c5a6523c5cfeca76e063efa5f6a656d1d74c8b1fc64b2cd1e84e507f7e2a"},
+ {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0e5a644e50ef9fb87878d4d57907f03a12410d2aa3b93b3acdf90a741df52c49"},
+ {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0dc25a3293c50744796e87048de5e68996104d86d940bb24bc3ec31df281b191"},
+ {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a49994481b99cd7dedde07f2e7e93b1d86c01c0fca1c32aded18f10695ae17eb"},
+ {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641cf2e3447c9ecff2f7aa6e9eee9eaa286ea65d57b014543a4911ff2799d08a"},
+ {file = "multidict-6.2.0-cp310-cp310-win32.whl", hash = "sha256:0c383d28857f66f5aebe3e91d6cf498da73af75fbd51cedbe1adfb85e90c0460"},
+ {file = "multidict-6.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:a33273a541f1e1a8219b2a4ed2de355848ecc0254264915b9290c8d2de1c74e1"},
+ {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46"},
+ {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932"},
+ {file = "multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf"},
+ {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf"},
+ {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc"},
+ {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1"},
+ {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081"},
+ {file = "multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98"},
+ {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633"},
+ {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e"},
+ {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d"},
+ {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4"},
+ {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2"},
+ {file = "multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d"},
+ {file = "multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86"},
+ {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b"},
+ {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4"},
+ {file = "multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44"},
+ {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd"},
+ {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e"},
+ {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c"},
+ {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87"},
+ {file = "multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29"},
+ {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd"},
+ {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8"},
+ {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df"},
+ {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d"},
+ {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b"},
+ {file = "multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626"},
+ {file = "multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c"},
+ {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80"},
+ {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16"},
+ {file = "multidict-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e"},
+ {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817"},
+ {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc"},
+ {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1"},
+ {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844"},
+ {file = "multidict-6.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48"},
+ {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0"},
+ {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f"},
+ {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de"},
+ {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02"},
+ {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d"},
+ {file = "multidict-6.2.0-cp313-cp313-win32.whl", hash = "sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e"},
+ {file = "multidict-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2"},
+ {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7"},
+ {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b"},
+ {file = "multidict-6.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e"},
+ {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025"},
+ {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd"},
+ {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7"},
+ {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af"},
+ {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331"},
+ {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c"},
+ {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b"},
+ {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151"},
+ {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019"},
+ {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547"},
+ {file = "multidict-6.2.0-cp313-cp313t-win32.whl", hash = "sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc"},
+ {file = "multidict-6.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44"},
+ {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b4f3d66dd0354b79761481fc15bdafaba0b9d9076f1f42cc9ce10d7fcbda205a"},
+ {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e2a2d6749e1ff2c9c76a72c6530d5baa601205b14e441e6d98011000f47a7ac"},
+ {file = "multidict-6.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cca83a629f77402cfadd58352e394d79a61c8015f1694b83ab72237ec3941f88"},
+ {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781b5dd1db18c9e9eacc419027b0acb5073bdec9de1675c0be25ceb10e2ad133"},
+ {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf8d370b2fea27fb300825ec3984334f7dd54a581bde6456799ba3776915a656"},
+ {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25bb96338512e2f46f615a2bb7c6012fe92a4a5ebd353e5020836a7e33120349"},
+ {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e2819b0b468174de25c0ceed766606a07cedeab132383f1e83b9a4e96ccb4f"},
+ {file = "multidict-6.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aed763b6a1b28c46c055692836879328f0b334a6d61572ee4113a5d0c859872"},
+ {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1133414b771619aa3c3000701c11b2e4624a7f492f12f256aedde97c28331a2"},
+ {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:639556758c36093b35e2e368ca485dada6afc2bd6a1b1207d85ea6dfc3deab27"},
+ {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:163f4604e76639f728d127293d24c3e208b445b463168af3d031b92b0998bb90"},
+ {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2325105e16d434749e1be8022f942876a936f9bece4ec41ae244e3d7fae42aaf"},
+ {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e4371591e621579cb6da8401e4ea405b33ff25a755874a3567c4075ca63d56e2"},
+ {file = "multidict-6.2.0-cp39-cp39-win32.whl", hash = "sha256:d1175b0e0d6037fab207f05774a176d71210ebd40b1c51f480a04b65ec5c786d"},
+ {file = "multidict-6.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad81012b24b88aad4c70b2cbc2dad84018783221b7f923e926f4690ff8569da3"},
+ {file = "multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530"},
+ {file = "multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+description = "Patch asyncio to allow nested event loops"
+optional = false
+python-versions = ">=3.5"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"},
+ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
+]
+
+[[package]]
+name = "numpy"
+version = "2.2.1"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440"},
+ {file = "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab"},
+ {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675"},
+ {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308"},
+ {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957"},
+ {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf"},
+ {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2"},
+ {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528"},
+ {file = "numpy-2.2.1-cp310-cp310-win32.whl", hash = "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95"},
+ {file = "numpy-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf"},
+ {file = "numpy-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484"},
+ {file = "numpy-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7"},
+ {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb"},
+ {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5"},
+ {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73"},
+ {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591"},
+ {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8"},
+ {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0"},
+ {file = "numpy-2.2.1-cp311-cp311-win32.whl", hash = "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd"},
+ {file = "numpy-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16"},
+ {file = "numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab"},
+ {file = "numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa"},
+ {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315"},
+ {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355"},
+ {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7"},
+ {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d"},
+ {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51"},
+ {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046"},
+ {file = "numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2"},
+ {file = "numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8"},
+ {file = "numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780"},
+ {file = "numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821"},
+ {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e"},
+ {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348"},
+ {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59"},
+ {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af"},
+ {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51"},
+ {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716"},
+ {file = "numpy-2.2.1-cp313-cp313-win32.whl", hash = "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e"},
+ {file = "numpy-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60"},
+ {file = "numpy-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e"},
+ {file = "numpy-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712"},
+ {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008"},
+ {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84"},
+ {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631"},
+ {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d"},
+ {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5"},
+ {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71"},
+ {file = "numpy-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2"},
+ {file = "numpy-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268"},
+ {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3"},
+ {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964"},
+ {file = "numpy-2.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800"},
+ {file = "numpy-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e"},
+ {file = "numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918"},
+]
+
+[[package]]
+name = "openai"
+version = "1.68.0"
+description = "The official Python library for the openai API"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "openai-1.68.0-py3-none-any.whl", hash = "sha256:20e279b0f3a78cb4a95f3eab2a180f3ee30c6a196aeebd6bf642a4f88ab85ee1"},
+ {file = "openai-1.68.0.tar.gz", hash = "sha256:c570c06c9ba10f98b891ac30a3dd7b5c89ed48094c711c7a3f35fb5ade6c0757"},
+]
+
+[package.dependencies]
+anyio = ">=3.5.0,<5"
+distro = ">=1.7.0,<2"
+httpx = ">=0.23.0,<1"
+jiter = ">=0.4.0,<1"
+numpy = ">=2.0.2"
+pydantic = ">=1.9.0,<3"
+sniffio = "*"
+sounddevice = ">=0.5.1"
+tqdm = ">4"
+typing-extensions = ">=4.11,<5"
+
+[package.extras]
+datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
+realtime = ["websockets (>=13,<15)"]
+
+[[package]]
+name = "openai-agents"
+version = "0.0.4"
+description = "OpenAI Agents SDK"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "openai_agents-0.0.4-py3-none-any.whl", hash = "sha256:5577c3ee994fe0bd200d7283e4f7a614b3af19afeebcfb07b6ca6039a8a50a5c"},
+ {file = "openai_agents-0.0.4.tar.gz", hash = "sha256:297e8d5faeca753e1b303d860b7ac94d03a7e10382be738163dc6a10a3b7cc1c"},
+]
+
+[package.dependencies]
+griffe = ">=1.5.6,<2"
+openai = ">=1.66.2"
+pydantic = ">=2.10,<3"
+requests = ">=2.0,<3"
+types-requests = ">=2.0,<3"
+typing-extensions = ">=4.12.2,<5"
+
+[[package]]
+name = "openpyxl"
+version = "3.1.5"
+description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"},
+ {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"},
+]
+
+[package.dependencies]
+et-xmlfile = "*"
+
+[[package]]
+name = "packaging"
+version = "24.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
+ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
+]
+
+[[package]]
+name = "pandas"
+version = "2.2.3"
+description = "Powerful data structures for data analysis, time series, and statistics"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"},
+ {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"},
+ {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"},
+ {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"},
+ {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"},
+ {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"},
+ {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"},
+ {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"},
+ {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"},
+ {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"},
+ {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"},
+ {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"},
+ {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"},
+ {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"},
+ {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"},
+ {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"},
+ {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"},
+ {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"},
+ {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"},
+ {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"},
+ {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"},
+ {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"},
+ {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"},
+ {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"},
+ {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"},
+ {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"},
+ {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"},
+ {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"},
+ {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"},
+ {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"},
+ {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"},
+ {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"},
+ {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"},
+ {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"},
+ {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"},
+ {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"},
+ {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"},
+ {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"},
+ {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"},
+ {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"},
+ {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"},
+ {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"},
+]
+
+[package.dependencies]
+numpy = [
+ {version = ">=1.22.4", markers = "python_version < \"3.11\""},
+ {version = ">=1.23.2", markers = "python_version == \"3.11\""},
+ {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
+]
+python-dateutil = ">=2.8.2"
+pytz = ">=2020.1"
+tzdata = ">=2022.7"
+
+[package.extras]
+all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
+aws = ["s3fs (>=2022.11.0)"]
+clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
+compression = ["zstandard (>=0.19.0)"]
+computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
+consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
+excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
+feather = ["pyarrow (>=10.0.1)"]
+fss = ["fsspec (>=2022.11.0)"]
+gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
+hdf5 = ["tables (>=3.8.0)"]
+html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
+mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
+output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
+parquet = ["pyarrow (>=10.0.1)"]
+performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
+plot = ["matplotlib (>=3.6.3)"]
+postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
+pyarrow = ["pyarrow (>=10.0.1)"]
+spss = ["pyreadstat (>=1.2.0)"]
+sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
+test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
+xml = ["lxml (>=4.9.2)"]
+
+[[package]]
+name = "pbs-installer"
+version = "2025.3.17"
+description = "Installer for Python Build Standalone"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pbs_installer-2025.3.17-py3-none-any.whl", hash = "sha256:d2b0563b1d5d814e479f3c43d7aee019250f68a0a113d754714fa9a721f83b47"},
+ {file = "pbs_installer-2025.3.17.tar.gz", hash = "sha256:dde058f925b989c1d3bd90739c16ffd0e68732f7716e4d1e01ca480d00a67560"},
+]
+
+[package.dependencies]
+httpx = {version = ">=0.27.0,<1", optional = true, markers = "extra == \"download\""}
+zstandard = {version = ">=0.21.0", optional = true, markers = "extra == \"install\""}
+
+[package.extras]
+all = ["pbs-installer[download,install]"]
+download = ["httpx (>=0.27.0,<1)"]
+install = ["zstandard (>=0.21.0)"]
+
+[[package]]
+name = "pkginfo"
+version = "1.12.1.2"
+description = "Query metadata from sdists / bdists / installed packages."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"},
+ {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"},
+]
+
+[package.extras]
+testing = ["pytest", "pytest-cov", "wheel"]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.7"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"},
+ {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.14.1)"]
+
+[[package]]
+name = "poetry"
+version = "2.1.1"
+description = "Python dependency management and packaging made easy."
+optional = false
+python-versions = "<4.0,>=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "poetry-2.1.1-py3-none-any.whl", hash = "sha256:1d433880bd5b401327ddee789ccfe9ff197bf3b0cd240f0bc7cc99c84d14b16c"},
+ {file = "poetry-2.1.1.tar.gz", hash = "sha256:d82673865bf13d6cd0dacf28c69a89670456d8df2f9e5da82bfb5f833ba00efc"},
+]
+
+[package.dependencies]
+build = ">=1.2.1,<2.0.0"
+cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]}
+cleo = ">=2.1.0,<3.0.0"
+dulwich = ">=0.22.6,<0.23.0"
+fastjsonschema = ">=2.18.0,<3.0.0"
+findpython = ">=0.6.2,<0.7.0"
+installer = ">=0.7.0,<0.8.0"
+keyring = ">=25.1.0,<26.0.0"
+packaging = ">=24.0"
+pbs-installer = {version = ">=2025.1.6,<2026.0.0", extras = ["download", "install"]}
+pkginfo = ">=1.12,<2.0"
+platformdirs = ">=3.0.0,<5"
+poetry-core = "2.1.1"
+pyproject-hooks = ">=1.0.0,<2.0.0"
+requests = ">=2.26,<3.0"
+requests-toolbelt = ">=1.0.0,<2.0.0"
+shellingham = ">=1.5,<2.0"
+tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
+tomlkit = ">=0.11.4,<1.0.0"
+trove-classifiers = ">=2022.5.19"
+virtualenv = ">=20.26.6,<21.0.0"
+xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""}
+
+[[package]]
+name = "poetry-core"
+version = "2.1.1"
+description = "Poetry PEP 517 Build Backend"
+optional = false
+python-versions = "<4.0,>=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "poetry_core-2.1.1-py3-none-any.whl", hash = "sha256:bc3b0382ab4d00d5d780277fd0aad1580eb4403613b37fc60fec407b5bee1fe6"},
+ {file = "poetry_core-2.1.1.tar.gz", hash = "sha256:c1a1f6f00e4254742f40988a8caf665549101cf9991122cd5de1198897768b1a"},
+]
+
+[[package]]
+name = "portalocker"
+version = "2.10.1"
+description = "Wraps the portalocker recipe for easy usage"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf"},
+ {file = "portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f"},
+]
+
+[package.dependencies]
+pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+docs = ["sphinx (>=1.7.1)"]
+redis = ["redis"]
+tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"]
+
+[[package]]
+name = "priority"
+version = "2.0.0"
+description = "A pure-Python implementation of the HTTP/2 priority tree"
+optional = false
+python-versions = ">=3.6.1"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"},
+ {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"},
+]
+
+[[package]]
+name = "propcache"
+version = "0.3.0"
+description = "Accelerated property cache"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"},
+ {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"},
+ {file = "propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc"},
+ {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d"},
+ {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f"},
+ {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf"},
+ {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9"},
+ {file = "propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc"},
+ {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0"},
+ {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b"},
+ {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f"},
+ {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a"},
+ {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25"},
+ {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f"},
+ {file = "propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c"},
+ {file = "propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340"},
+ {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51"},
+ {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e"},
+ {file = "propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa"},
+ {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf"},
+ {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b"},
+ {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9"},
+ {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6"},
+ {file = "propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c"},
+ {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075"},
+ {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c"},
+ {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810"},
+ {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3"},
+ {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7"},
+ {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c"},
+ {file = "propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d"},
+ {file = "propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32"},
+ {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e"},
+ {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af"},
+ {file = "propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5"},
+ {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b"},
+ {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667"},
+ {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7"},
+ {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7"},
+ {file = "propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf"},
+ {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138"},
+ {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86"},
+ {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d"},
+ {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e"},
+ {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64"},
+ {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c"},
+ {file = "propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d"},
+ {file = "propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57"},
+ {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568"},
+ {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9"},
+ {file = "propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767"},
+ {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8"},
+ {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0"},
+ {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d"},
+ {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05"},
+ {file = "propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe"},
+ {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1"},
+ {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92"},
+ {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787"},
+ {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545"},
+ {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e"},
+ {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626"},
+ {file = "propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374"},
+ {file = "propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a"},
+ {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf"},
+ {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0"},
+ {file = "propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829"},
+ {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa"},
+ {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6"},
+ {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db"},
+ {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"},
+ {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121"},
+ {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e"},
+ {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e"},
+ {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a"},
+ {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac"},
+ {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e"},
+ {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf"},
+ {file = "propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863"},
+ {file = "propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46"},
+ {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc"},
+ {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b"},
+ {file = "propcache-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649"},
+ {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce"},
+ {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe"},
+ {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14"},
+ {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe"},
+ {file = "propcache-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e"},
+ {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07"},
+ {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90"},
+ {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641"},
+ {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f"},
+ {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7"},
+ {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f"},
+ {file = "propcache-0.3.0-cp39-cp39-win32.whl", hash = "sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663"},
+ {file = "propcache-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929"},
+ {file = "propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043"},
+ {file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"},
+]
+
+[[package]]
+name = "protobuf"
+version = "5.29.4"
+description = ""
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"},
+ {file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"},
+ {file = "protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0"},
+ {file = "protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e"},
+ {file = "protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922"},
+ {file = "protobuf-5.29.4-cp38-cp38-win32.whl", hash = "sha256:1832f0515b62d12d8e6ffc078d7e9eb06969aa6dc13c13e1036e39d73bebc2de"},
+ {file = "protobuf-5.29.4-cp38-cp38-win_amd64.whl", hash = "sha256:476cb7b14914c780605a8cf62e38c2a85f8caff2e28a6a0bad827ec7d6c85d68"},
+ {file = "protobuf-5.29.4-cp39-cp39-win32.whl", hash = "sha256:fd32223020cb25a2cc100366f1dedc904e2d71d9322403224cdde5fdced0dabe"},
+ {file = "protobuf-5.29.4-cp39-cp39-win_amd64.whl", hash = "sha256:678974e1e3a9b975b8bc2447fca458db5f93a2fb6b0c8db46b6675b5b5346812"},
+ {file = "protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862"},
+ {file = "protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99"},
+]
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+description = "C parser in Python"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.10.5"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"},
+ {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.6.0"
+pydantic-core = "2.27.2"
+typing-extensions = ">=4.12.2"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+timezone = ["tzdata"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.27.2"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
+ {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
+ {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
+ {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"},
+ {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"},
+ {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
+ {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
+ {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
+ {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
+ {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+
+[[package]]
+name = "pydantic-settings"
+version = "2.8.1"
+description = "Settings management using Pydantic"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"},
+ {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"},
+]
+
+[package.dependencies]
+pydantic = ">=2.7.0"
+python-dotenv = ">=0.21.0"
+
+[package.extras]
+azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"]
+toml = ["tomli (>=2.0.1)"]
+yaml = ["pyyaml (>=6.0.1)"]
+
+[[package]]
+name = "pyjwt"
+version = "2.10.1"
+description = "JSON Web Token implementation in Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"},
+ {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
+]
+
+[package.extras]
+crypto = ["cryptography (>=3.4.0)"]
+dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"]
+docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
+tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
+
+[[package]]
+name = "pymongo"
+version = "4.10.1"
+description = "Python driver for MongoDB "
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pymongo-4.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e699aa68c4a7dea2ab5a27067f7d3e08555f8d2c0dc6a0c8c60cfd9ff2e6a4b1"},
+ {file = "pymongo-4.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70645abc714f06b4ad6b72d5bf73792eaad14e3a2cfe29c62a9c81ada69d9e4b"},
+ {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2fd94c9fe048c94838badcc6e992d033cb9473eb31e5710b3707cba5e8aee2"},
+ {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ded27a4a5374dae03a92e084a60cdbcecd595306555bda553b833baf3fc4868"},
+ {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ecc2455e3974a6c429687b395a0bc59636f2d6aedf5785098cf4e1f180f1c71"},
+ {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920fee41f7d0259f5f72c1f1eb331bc26ffbdc952846f9bd8c3b119013bb52c"},
+ {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0a15665b2d6cf364f4cd114d62452ce01d71abfbd9c564ba8c74dcd7bbd6822"},
+ {file = "pymongo-4.10.1-cp310-cp310-win32.whl", hash = "sha256:29e1c323c28a4584b7095378ff046815e39ff82cdb8dc4cc6dfe3acf6f9ad1f8"},
+ {file = "pymongo-4.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:88dc4aa45f8744ccfb45164aedb9a4179c93567bbd98a33109d7dc400b00eb08"},
+ {file = "pymongo-4.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57ee6becae534e6d47848c97f6a6dff69e3cce7c70648d6049bd586764febe59"},
+ {file = "pymongo-4.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f437a612f4d4f7aca1812311b1e84477145e950fdafe3285b687ab8c52541f3"},
+ {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a970fd3117ab40a4001c3dad333bbf3c43687d90f35287a6237149b5ccae61d"},
+ {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c4d0e7cd08ef9f8fbf2d15ba281ed55604368a32752e476250724c3ce36c72e"},
+ {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca6f700cff6833de4872a4e738f43123db34400173558b558ae079b5535857a4"},
+ {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec237c305fcbeef75c0bcbe9d223d1e22a6e3ba1b53b2f0b79d3d29c742b45b"},
+ {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3337804ea0394a06e916add4e5fac1c89902f1b6f33936074a12505cab4ff05"},
+ {file = "pymongo-4.10.1-cp311-cp311-win32.whl", hash = "sha256:778ac646ce6ac1e469664062dfe9ae1f5c9961f7790682809f5ec3b8fda29d65"},
+ {file = "pymongo-4.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:9df4ab5594fdd208dcba81be815fa8a8a5d8dedaf3b346cbf8b61c7296246a7a"},
+ {file = "pymongo-4.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fbedc4617faa0edf423621bb0b3b8707836687161210d470e69a4184be9ca011"},
+ {file = "pymongo-4.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7bd26b2aec8ceeb95a5d948d5cc0f62b0eb6d66f3f4230705c1e3d3d2c04ec76"},
+ {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb104c3c2a78d9d85571c8ac90ec4f95bca9b297c6eee5ada71fabf1129e1674"},
+ {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4924355245a9c79f77b5cda2db36e0f75ece5faf9f84d16014c0a297f6d66786"},
+ {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11280809e5dacaef4971113f0b4ff4696ee94cfdb720019ff4fa4f9635138252"},
+ {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5d55f2a82e5eb23795f724991cac2bffbb1c0f219c0ba3bf73a835f97f1bb2e"},
+ {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e974ab16a60be71a8dfad4e5afccf8dd05d41c758060f5d5bda9a758605d9a5d"},
+ {file = "pymongo-4.10.1-cp312-cp312-win32.whl", hash = "sha256:544890085d9641f271d4f7a47684450ed4a7344d6b72d5968bfae32203b1bb7c"},
+ {file = "pymongo-4.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:dcc07b1277e8b4bf4d7382ca133850e323b7ab048b8353af496d050671c7ac52"},
+ {file = "pymongo-4.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:90bc6912948dfc8c363f4ead54d54a02a15a7fee6cfafb36dc450fc8962d2cb7"},
+ {file = "pymongo-4.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:594dd721b81f301f33e843453638e02d92f63c198358e5a0fa8b8d0b1218dabc"},
+ {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0783e0c8e95397c84e9cf8ab092ab1e5dd7c769aec0ef3a5838ae7173b98dea0"},
+ {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fb6a72e88df46d1c1040fd32cd2d2c5e58722e5d3e31060a0393f04ad3283de"},
+ {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e3a593333e20c87415420a4fb76c00b7aae49b6361d2e2205b6fece0563bf40"},
+ {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72e2ace7456167c71cfeca7dcb47bd5dceda7db2231265b80fc625c5e8073186"},
+ {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad05eb9c97e4f589ed9e74a00fcaac0d443ccd14f38d1258eb4c39a35dd722b"},
+ {file = "pymongo-4.10.1-cp313-cp313-win32.whl", hash = "sha256:ee4c86d8e6872a61f7888fc96577b0ea165eb3bdb0d841962b444fa36001e2bb"},
+ {file = "pymongo-4.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:45ee87a4e12337353242bc758accc7fb47a2f2d9ecc0382a61e64c8f01e86708"},
+ {file = "pymongo-4.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:442ca247f53ad24870a01e80a71cd81b3f2318655fd9d66748ee2bd1b1569d9e"},
+ {file = "pymongo-4.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23e1d62df5592518204943b507be7b457fb8a4ad95a349440406fd42db5d0923"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6131bc6568b26e7495a9f3ef2b1700566b76bbecd919f4472bfe90038a61f425"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdeba88c540c9ed0338c0b2062d9f81af42b18d6646b3e6dda05cf6edd46ada9"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a624d752dd3c89d10deb0ef6431559b6d074703cab90a70bb849ece02adc6b"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba164e73fdade9b4614a2497321c5b7512ddf749ed508950bdecc28d8d76a2d9"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9235fa319993405ae5505bf1333366388add2e06848db7b3deee8f990b69808e"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4a65567bd17d19f03157c7ec992c6530eafd8191a4e5ede25566792c4fe3fa2"},
+ {file = "pymongo-4.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f1945d48fb9b8a87d515da07f37e5b2c35b364a435f534c122e92747881f4a7c"},
+ {file = "pymongo-4.10.1-cp38-cp38-win32.whl", hash = "sha256:345f8d340802ebce509f49d5833cc913da40c82f2e0daf9f60149cacc9ca680f"},
+ {file = "pymongo-4.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:3a70d5efdc0387ac8cd50f9a5f379648ecfc322d14ec9e1ba8ec957e5d08c372"},
+ {file = "pymongo-4.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15b1492cc5c7cd260229590be7218261e81684b8da6d6de2660cf743445500ce"},
+ {file = "pymongo-4.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95207503c41b97e7ecc7e596d84a61f441b4935f11aa8332828a754e7ada8c82"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb99f003c720c6d83be02c8f1a7787c22384a8ca9a4181e406174db47a048619"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2bc1ee4b1ca2c4e7e6b7a5e892126335ec8d9215bcd3ac2fe075870fefc3358"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93a0833c10a967effcd823b4e7445ec491f0bf6da5de0ca33629c0528f42b748"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f56707497323150bd2ed5d63067f4ffce940d0549d4ea2dfae180deec7f9363"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:409ab7d6c4223e5c85881697f365239dd3ed1b58f28e4124b846d9d488c86880"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dac78a650dc0637d610905fd06b5fa6419ae9028cf4d04d6a2657bc18a66bbce"},
+ {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ec3fa88b541e0481aff3c35194c9fac96e4d57ec5d1c122376000eb28c01431"},
+ {file = "pymongo-4.10.1-cp39-cp39-win32.whl", hash = "sha256:e0e961923a7b8a1c801c43552dcb8153e45afa41749d9efbd3a6d33f45489f7a"},
+ {file = "pymongo-4.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:dabe8bf1ad644e6b93f3acf90ff18536d94538ca4d27e583c6db49889e98e48f"},
+ {file = "pymongo-4.10.1.tar.gz", hash = "sha256:a9de02be53b6bb98efe0b9eda84ffa1ec027fcb23a2de62c4f941d9a2f2f3330"},
+]
+
+[package.dependencies]
+dnspython = ">=1.16.0,<3.0.0"
+
+[package.extras]
+aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"]
+docs = ["furo (==2023.9.10)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<8)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<3)", "sphinxcontrib-shellcheck (>=1,<2)"]
+encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.10.0,<2.0.0)"]
+gssapi = ["pykerberos", "winkerberos (>=0.5.0)"]
+ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"]
+snappy = ["python-snappy"]
+test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"]
+zstd = ["zstandard"]
+
+[[package]]
+name = "pyproject-hooks"
+version = "1.2.0"
+description = "Wrappers to call pyproject.toml-based build backend hooks."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"},
+ {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"},
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "python-docx"
+version = "1.1.2"
+description = "Create, read, and update Microsoft Word .docx files."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe"},
+ {file = "python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd"},
+]
+
+[package.dependencies]
+lxml = ">=3.1.0"
+typing-extensions = ">=4.9.0"
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "pytz"
+version = "2024.2"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
+ {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
+]
+
+[[package]]
+name = "pywin32"
+version = "310"
+description = "Python for Window Extensions"
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "(python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\") and platform_system == \"Windows\""
+files = [
+ {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"},
+ {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"},
+ {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"},
+ {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"},
+ {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"},
+ {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"},
+ {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"},
+ {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"},
+ {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"},
+ {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"},
+ {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"},
+ {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"},
+ {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"},
+ {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"},
+ {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"},
+ {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"},
+]
+
+[[package]]
+name = "pywin32-ctypes"
+version = "0.2.3"
+description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "(python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\") and sys_platform == \"win32\""
+files = [
+ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"},
+ {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"},
+]
+
+[[package]]
+name = "qdrant-client"
+version = "1.13.3"
+description = "Client library for the Qdrant vector search engine"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "qdrant_client-1.13.3-py3-none-any.whl", hash = "sha256:f52cacbb936e547d3fceb1aaed3e3c56be0ebfd48e8ea495ea3dbc89c671d1d2"},
+ {file = "qdrant_client-1.13.3.tar.gz", hash = "sha256:61ca09e07c6d7ac0dfbdeb13dca4fe5f3e08fa430cb0d74d66ef5d023a70adfc"},
+]
+
+[package.dependencies]
+grpcio = ">=1.41.0"
+grpcio-tools = ">=1.41.0"
+httpx = {version = ">=0.20.0", extras = ["http2"]}
+numpy = [
+ {version = ">=1.21", markers = "python_version >= \"3.10\" and python_version < \"3.12\""},
+ {version = ">=1.26", markers = "python_version == \"3.12\""},
+ {version = ">=2.1.0", markers = "python_version >= \"3.13\""},
+]
+portalocker = ">=2.7.0,<3.0.0"
+pydantic = ">=1.10.8"
+urllib3 = ">=1.26.14,<3"
+
+[package.extras]
+fastembed = ["fastembed (==0.5.1)"]
+fastembed-gpu = ["fastembed-gpu (==0.5.1)"]
+
+[[package]]
+name = "quart"
+version = "0.20.0"
+description = "A Python ASGI web framework with the same API as Flask"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "quart-0.20.0-py3-none-any.whl", hash = "sha256:003c08f551746710acb757de49d9b768986fd431517d0eb127380b656b98b8f1"},
+ {file = "quart-0.20.0.tar.gz", hash = "sha256:08793c206ff832483586f5ae47018c7e40bdd75d886fee3fabbdaa70c2cf505d"},
+]
+
+[package.dependencies]
+aiofiles = "*"
+blinker = ">=1.6"
+click = ">=8.0"
+flask = ">=3.0"
+hypercorn = ">=0.11.2"
+itsdangerous = "*"
+jinja2 = "*"
+markupsafe = "*"
+werkzeug = ">=3.0"
+
+[package.extras]
+dotenv = ["python-dotenv"]
+
+[[package]]
+name = "rapidfuzz"
+version = "3.12.2"
+description = "rapid fuzzy string matching"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "rapidfuzz-3.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b9a75e0385a861178adf59e86d6616cbd0d5adca7228dc9eeabf6f62cf5b0b1"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6906a7eb458731e3dd2495af1d0410e23a21a2a2b7ced535e6d5cd15cb69afc5"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4b3334a8958b689f292d5ce8a928140ac98919b51e084f04bf0c14276e4c6ba"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85a54ce30345cff2c79cbcffa063f270ad1daedd0d0c3ff6e541d3c3ba4288cf"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb63c5072c08058f8995404201a52fc4e1ecac105548a4d03c6c6934bda45a3"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5385398d390c6571f0f2a7837e6ddde0c8b912dac096dc8c87208ce9aaaa7570"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5032cbffa245b4beba0067f8ed17392ef2501b346ae3c1f1d14b950edf4b6115"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:195adbb384d89d6c55e2fd71e7fb262010f3196e459aa2f3f45f31dd7185fe72"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f43b773a4d4950606fb25568ecde5f25280daf8f97b87eb323e16ecd8177b328"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:55a43be0e0fa956a919043c19d19bd988991d15c59f179d413fe5145ed9deb43"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:71cf1ea16acdebe9e2fb62ee7a77f8f70e877bebcbb33b34e660af2eb6d341d9"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a3692d4ab36d44685f61326dca539975a4eda49b2a76f0a3df177d8a2c0de9d2"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-win32.whl", hash = "sha256:09227bd402caa4397ba1d6e239deea635703b042dd266a4092548661fb22b9c6"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:0f05b7b95f9f87254b53fa92048367a8232c26cee7fc8665e4337268c3919def"},
+ {file = "rapidfuzz-3.12.2-cp310-cp310-win_arm64.whl", hash = "sha256:6938738e00d9eb6e04097b3f565097e20b0c398f9c58959a2bc64f7f6be3d9da"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9c4d984621ae17404c58f8d06ed8b025e167e52c0e6a511dfec83c37e9220cd"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f9132c55d330f0a1d34ce6730a76805323a6250d97468a1ca766a883d6a9a25"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b343b6cb4b2c3dbc8d2d4c5ee915b6088e3b144ddf8305a57eaab16cf9fc74"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24081077b571ec4ee6d5d7ea0e49bc6830bf05b50c1005028523b9cd356209f3"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c988a4fc91856260355773bf9d32bebab2083d4c6df33fafeddf4330e5ae9139"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:780b4469ee21cf62b1b2e8ada042941fd2525e45d5fb6a6901a9798a0e41153c"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edd84b0a323885493c893bad16098c5e3b3005d7caa995ae653da07373665d97"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efa22059c765b3d8778083805b199deaaf643db070f65426f87d274565ddf36a"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:095776b11bb45daf7c2973dd61cc472d7ea7f2eecfa454aef940b4675659b92f"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7e2574cf4aa86065600b664a1ac7b8b8499107d102ecde836aaaa403fc4f1784"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d5a3425a6c50fd8fbd991d8f085ddb504791dae6ef9cc3ab299fea2cb5374bef"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fb05e1ddb7b71a054040af588b0634214ee87cea87900d309fafc16fd272a4"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-win32.whl", hash = "sha256:b4c5a0413589aef936892fbfa94b7ff6f7dd09edf19b5a7b83896cc9d4e8c184"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:58d9ae5cf9246d102db2a2558b67fe7e73c533e5d769099747921232d88b9be2"},
+ {file = "rapidfuzz-3.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:7635fe34246cd241c8e35eb83084e978b01b83d5ef7e5bf72a704c637f270017"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1d982a651253ffe8434d9934ff0c1089111d60502228464721a2a4587435e159"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02e6466caa0222d5233b1f05640873671cd99549a5c5ba4c29151634a1e56080"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e956b3f053e474abae69ac693a52742109d860ac2375fe88e9387d3277f4c96c"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dee7d740a2d5418d4f964f39ab8d89923e6b945850db833e798a1969b19542a"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a057cdb0401e42c84b6516c9b1635f7aedd5e430c6e388bd5f6bcd1d6a0686bb"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dccf8d4fb5b86d39c581a59463c596b1d09df976da26ff04ae219604223d502f"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21d5b3793c6f5aecca595cd24164bf9d3c559e315ec684f912146fc4e769e367"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:46a616c0e13cff2de1761b011e0b14bb73b110182f009223f1453d505c9a975c"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19fa5bc4301a1ee55400d4a38a8ecf9522b0391fc31e6da5f4d68513fe5c0026"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:544a47190a0d25971658a9365dba7095397b4ce3e897f7dd0a77ca2cf6fa984e"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f21af27c5e001f0ba1b88c36a0936437dfe034c452548d998891c21125eb640f"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b63170d9db00629b5b3f2862114d8d6ee19127eaba0eee43762d62a25817dbe0"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-win32.whl", hash = "sha256:6c7152d77b2eb6bfac7baa11f2a9c45fd5a2d848dbb310acd0953b3b789d95c9"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:1a314d170ee272ac87579f25a6cf8d16a031e1f7a7b07663434b41a1473bc501"},
+ {file = "rapidfuzz-3.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:d41e8231326e94fd07c4d8f424f6bed08fead6f5e6688d1e6e787f1443ae7631"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941f31038dba5d3dedcfcceba81d61570ad457c873a24ceb13f4f44fcb574260"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fe2dfc454ee51ba168a67b1e92b72aad251e45a074972cef13340bbad2fd9438"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78fafaf7f5a48ee35ccd7928339080a0136e27cf97396de45259eca1d331b714"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0c7989ff32c077bb8fd53253fd6ca569d1bfebc80b17557e60750e6909ba4fe"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96fa00bc105caa34b6cd93dca14a29243a3a7f0c336e4dcd36348d38511e15ac"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bccfb30c668620c5bc3490f2dc7d7da1cca0ead5a9da8b755e2e02e2ef0dff14"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f9b0adc3d894beb51f5022f64717b6114a6fabaca83d77e93ac7675911c8cc5"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32691aa59577f42864d5535cb6225d0f47e2c7bff59cf4556e5171e96af68cc1"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:758b10380ad34c1f51753a070d7bb278001b5e6fcf544121c6df93170952d705"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:50a9c54c0147b468363119132d514c5024fbad1ed8af12bd8bd411b0119f9208"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e3ceb87c11d2d0fbe8559bb795b0c0604b84cfc8bb7b8720b5c16e9e31e00f41"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f7c9a003002434889255ff5676ca0f8934a478065ab5e702f75dc42639505bba"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-win32.whl", hash = "sha256:cf165a76870cd875567941cf861dfd361a0a6e6a56b936c5d30042ddc9def090"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:55bcc003541f5f16ec0a73bf6de758161973f9e8d75161954380738dd147f9f2"},
+ {file = "rapidfuzz-3.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:69f6ecdf1452139f2b947d0c169a605de578efdb72cbb2373cb0a94edca1fd34"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c852cd8bed1516a64fd6e2d4c6f270d4356196ee03fda2af1e5a9e13c34643"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42e7f747b55529a6d0d1588695d71025e884ab48664dca54b840413dea4588d8"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a749fd2690f24ef256b264a781487746bbb95344364fe8fe356f0eef7ef206ba"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a11e1d036170bbafa43a9e63d8c309273564ec5bdfc5439062f439d1a16965a"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfb337f1832c1231e3d5621bd0ebebb854e46036aedae3e6a49c1fc08f16f249"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e88c6e68fca301722fa3ab7fd3ca46998012c14ada577bc1e2c2fc04f2067ca6"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e1a3a8b4b5125cfb63a6990459b25b87ea769bdaf90d05bb143f8febef076a"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9f8177b24ccc0a843e85932b1088c5e467a7dd7a181c13f84c684b796bea815"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6c506bdc2f304051592c0d3b0e82eed309248ec10cdf802f13220251358375ea"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:30bf15c1ecec2798b713d551df17f23401a3e3653ad9ed4e83ad1c2b06e86100"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bd9a67cfc83e8453ef17ddd1c2c4ce4a74d448a197764efb54c29f29fb41f611"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a6eaec2ef658dd650c6eb9b36dff7a361ebd7d8bea990ce9d639b911673b2cb"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-win32.whl", hash = "sha256:d7701769f110332cde45c41759cb2a497de8d2dca55e4c519a46aed5fbb19d1a"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:296bf0fd4f678488670e262c87a3e4f91900b942d73ae38caa42a417e53643b1"},
+ {file = "rapidfuzz-3.12.2-cp39-cp39-win_arm64.whl", hash = "sha256:7957f5d768de14f6b2715303ccdf224b78416738ee95a028a2965c95f73afbfb"},
+ {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5fd3ce849b27d063755829cda27a9dab6dbd63be3801f2a40c60ec563a4c90f"},
+ {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:54e53662d71ed660c83c5109127c8e30b9e607884b7c45d2aff7929bbbd00589"},
+ {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b9e43cf2213e524f3309d329f1ad8dbf658db004ed44f6ae1cd2919aa997da5"},
+ {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29ca445e320e5a8df3bd1d75b4fa4ecfa7c681942b9ac65b55168070a1a1960e"},
+ {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83eb7ef732c2f8533c6b5fbe69858a722c218acc3e1fc190ab6924a8af7e7e0e"},
+ {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:648adc2dd2cf873efc23befcc6e75754e204a409dfa77efd0fea30d08f22ef9d"},
+ {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b1e6f48e1ffa0749261ee23a1c6462bdd0be5eac83093f4711de17a42ae78ad"},
+ {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ae9ded463f2ca4ba1eb762913c5f14c23d2e120739a62b7f4cc102eab32dc90"},
+ {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dda45f47b559be72ecbce45c7f71dc7c97b9772630ab0f3286d97d2c3025ab71"},
+ {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3745c6443890265513a3c8777f2de4cb897aeb906a406f97741019be8ad5bcc"},
+ {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36d3ef4f047ed1bc96fa29289f9e67a637ddca5e4f4d3dc7cb7f50eb33ec1664"},
+ {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:54bb69ebe5ca0bd7527357e348f16a4c0c52fe0c2fcc8a041010467dcb8385f7"},
+ {file = "rapidfuzz-3.12.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3f2ddd5b99b254039a8c82be5749d4d75943f62eb2c2918acf6ffd586852834f"},
+ {file = "rapidfuzz-3.12.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8117dab9b26a1aaffab59b4e30f80ac4d55e61ad4139a637c149365960933bee"},
+ {file = "rapidfuzz-3.12.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40c0f16d62d6553527de3dab2fb69709c4383430ea44bce8fb4711ed4cbc6ae3"},
+ {file = "rapidfuzz-3.12.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f177e1eb6e4f5261a89c475e21bce7a99064a8f217d2336fb897408f46f0ceaf"},
+ {file = "rapidfuzz-3.12.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df0cecc2852fcb078ed1b4482fac4fc2c2e7787f3edda8920d9a4c0f51b1c95"},
+ {file = "rapidfuzz-3.12.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b3c4df0321df6f8f0b61afbaa2ced9622750ee1e619128db57a18533d139820"},
+ {file = "rapidfuzz-3.12.2.tar.gz", hash = "sha256:b0ba1ccc22fff782e7152a3d3d0caca44ec4e32dc48ba01c560b8593965b5aa3"},
+]
+
+[package.extras]
+all = ["numpy"]
+
+[[package]]
+name = "redis"
+version = "5.2.1"
+description = "Python client for Redis database and key-value store"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"},
+ {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"},
+]
+
+[package.dependencies]
+async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""}
+
+[package.extras]
+hiredis = ["hiredis (>=3.0.0)"]
+ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"]
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
+ {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "requests-toolbelt"
+version = "1.0.0"
+description = "A utility belt for advanced users of python-requests"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
+ {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
+]
+
+[package.dependencies]
+requests = ">=2.0.1,<3.0.0"
+
+[[package]]
+name = "secretstorage"
+version = "3.3.3"
+description = "Python bindings to FreeDesktop.org Secret Service API"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+markers = "(python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\") and sys_platform == \"linux\""
+files = [
+ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"},
+ {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"},
+]
+
+[package.dependencies]
+cryptography = ">=2.0"
+jeepney = ">=0.6"
+
+[[package]]
+name = "setuptools"
+version = "75.8.0"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
+ {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"]
+core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
+type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+description = "Tool to Detect Surrounding Shell"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
+ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+description = "Sniff out which async library your code is running under"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
+]
+
+[[package]]
+name = "sounddevice"
+version = "0.5.1"
+description = "Play and Record Sound with Python"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "sounddevice-0.5.1-py3-none-any.whl", hash = "sha256:e2017f182888c3f3c280d9fbac92e5dbddac024a7e3442f6e6116bd79dab8a9c"},
+ {file = "sounddevice-0.5.1-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d16cb23d92322526a86a9490c427bf8d49e273d9ccc0bd096feecd229cde6031"},
+ {file = "sounddevice-0.5.1-py3-none-win32.whl", hash = "sha256:d84cc6231526e7a08e89beff229c37f762baefe5e0cc2747cbe8e3a565470055"},
+ {file = "sounddevice-0.5.1-py3-none-win_amd64.whl", hash = "sha256:4313b63f2076552b23ac3e0abd3bcfc0c1c6a696fc356759a13bd113c9df90f1"},
+ {file = "sounddevice-0.5.1.tar.gz", hash = "sha256:09ca991daeda8ce4be9ac91e15a9a81c8f81efa6b695a348c9171ea0c16cb041"},
+]
+
+[package.dependencies]
+CFFI = ">=1.0"
+
+[package.extras]
+numpy = ["NumPy"]
+
+[[package]]
+name = "soupsieve"
+version = "2.6"
+description = "A modern CSS selector implementation for Beautiful Soup."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"},
+ {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"},
+]
+
+[[package]]
+name = "sse-starlette"
+version = "2.2.1"
+description = "SSE plugin for Starlette"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99"},
+ {file = "sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419"},
+]
+
+[package.dependencies]
+anyio = ">=4.7.0"
+starlette = ">=0.41.3"
+
+[package.extras]
+examples = ["fastapi"]
+uvicorn = ["uvicorn (>=0.34.0)"]
+
+[[package]]
+name = "starlette"
+version = "0.46.1"
+description = "The little ASGI library that shines."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227"},
+ {file = "starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230"},
+]
+
+[package.dependencies]
+anyio = ">=3.6.2,<5"
+
+[package.extras]
+full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
+
+[[package]]
+name = "tabulate"
+version = "0.9.0"
+description = "Pretty-print tabular data"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
+ {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
+]
+
+[package.extras]
+widechars = ["wcwidth"]
+
+[[package]]
+name = "taskgroup"
+version = "0.2.2"
+description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout"
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb"},
+ {file = "taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d"},
+]
+
+[package.dependencies]
+exceptiongroup = "*"
+typing_extensions = ">=4.12.2,<5"
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
+ {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
+ {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
+ {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
+ {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
+ {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
+ {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
+ {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
+ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
+ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
+]
+
+[[package]]
+name = "tomlkit"
+version = "0.13.2"
+description = "Style preserving TOML library"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
+ {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
+]
+
+[[package]]
+name = "tqdm"
+version = "4.67.1"
+description = "Fast, Extensible Progress Meter"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
+ {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"]
+discord = ["requests"]
+notebook = ["ipywidgets (>=6)"]
+slack = ["slack-sdk"]
+telegram = ["requests"]
+
+[[package]]
+name = "trove-classifiers"
+version = "2025.3.19.19"
+description = "Canonical source for classifiers on PyPI (pypi.org)."
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "trove_classifiers-2025.3.19.19-py3-none-any.whl", hash = "sha256:5fc02770ecd81588a605ac98b9d85d50a5a3f9daa30af2a6b1361a1999d75d07"},
+ {file = "trove_classifiers-2025.3.19.19.tar.gz", hash = "sha256:98e9d396fe908d5f43b7454fa4c43d17cd0fdadf046f45fb38a5e3af8d959ecd"},
+]
+
+[[package]]
+name = "types-requests"
+version = "2.32.0.20250306"
+description = "Typing stubs for requests"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b"},
+ {file = "types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1"},
+]
+
+[package.dependencies]
+urllib3 = ">=2"
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+]
+
+[[package]]
+name = "typing-inspect"
+version = "0.9.0"
+description = "Runtime inspection utilities for typing module."
+optional = false
+python-versions = "*"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
+ {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=0.3.0"
+typing-extensions = ">=3.7.4"
+
+[[package]]
+name = "tzdata"
+version = "2024.2"
+description = "Provider of IANA time zone data"
+optional = false
+python-versions = ">=2"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
+ {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.3.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
+ {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "uvicorn"
+version = "0.34.0"
+description = "The lightning-fast ASGI server."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"},
+ {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+h11 = ">=0.8"
+typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[[package]]
+name = "virtualenv"
+version = "20.29.3"
+description = "Virtual Python Environment builder"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170"},
+ {file = "virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac"},
+]
+
+[package.dependencies]
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
+
+[package.extras]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+
+[[package]]
+name = "waitress"
+version = "2.1.2"
+description = "Waitress WSGI server"
+optional = false
+python-versions = ">=3.7.0"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "waitress-2.1.2-py3-none-any.whl", hash = "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a"},
+ {file = "waitress-2.1.2.tar.gz", hash = "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"},
+]
+
+[package.extras]
+docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"]
+testing = ["coverage (>=5.0)", "pytest", "pytest-cover"]
+
+[[package]]
+name = "websockets"
+version = "13.1"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"},
+ {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"},
+ {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"},
+ {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"},
+ {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"},
+ {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"},
+ {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"},
+ {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"},
+ {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"},
+ {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"},
+ {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"},
+ {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"},
+ {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"},
+ {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"},
+ {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"},
+ {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"},
+ {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"},
+ {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"},
+ {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"},
+ {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"},
+ {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"},
+ {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"},
+ {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"},
+ {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"},
+ {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"},
+ {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"},
+ {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"},
+ {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"},
+ {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"},
+ {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"},
+ {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"},
+ {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"},
+ {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"},
+ {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"},
+ {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"},
+ {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"},
+ {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"},
+ {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"},
+ {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"},
+ {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"},
+ {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"},
+ {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"},
+ {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"},
+ {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"},
+ {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"},
+ {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"},
+ {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"},
+ {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"},
+ {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"},
+ {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"},
+ {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"},
+ {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"},
+ {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"},
+ {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"},
+ {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"},
+ {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"},
+ {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"},
+ {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"},
+ {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"},
+ {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"},
+ {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"},
+ {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"},
+ {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"},
+ {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"},
+ {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"},
+ {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"},
+ {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"},
+ {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"},
+ {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"},
+ {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"},
+ {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"},
+ {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"},
+ {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"},
+ {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"},
+ {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"},
+ {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"},
+ {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"},
+ {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"},
+ {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"},
+ {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"},
+ {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"},
+ {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"},
+ {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"},
+ {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"},
+ {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"},
+ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"},
+]
+
+[[package]]
+name = "werkzeug"
+version = "3.1.3"
+description = "The comprehensive WSGI web application library."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
+ {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.1.1"
+
+[package.extras]
+watchdog = ["watchdog (>=2.3)"]
+
+[[package]]
+name = "wheel"
+version = "0.44.0"
+description = "A built-package format for Python"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"},
+ {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"},
+]
+
+[package.extras]
+test = ["pytest (>=6.0.0)", "setuptools (>=65)"]
+
+[[package]]
+name = "wsproto"
+version = "1.2.0"
+description = "WebSockets state-machine based protocol implementation"
+optional = false
+python-versions = ">=3.7.0"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
+ {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
+]
+
+[package.dependencies]
+h11 = ">=0.9.0,<1"
+
+[[package]]
+name = "xattr"
+version = "1.1.4"
+description = "Python wrapper for extended filesystem attributes"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"},
+ {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"},
+ {file = "xattr-1.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:467ee77471d26ae5187ee7081b82175b5ca56ead4b71467ec2e6119d1b08beed"},
+ {file = "xattr-1.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd35f46cb0154f7033f9d5d0960f226857acb0d1e0d71fd7af18ed84663007c"},
+ {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d956478e9bb98a1efd20ebc6e5703497c1d2d690d5a13c4df4abf59881eed50"},
+ {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f25dfdcd974b700fb04a40e14a664a80227ee58e02ea062ac241f0d7dc54b4e"},
+ {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33b63365c1fcbc80a79f601575bac0d6921732e0245b776876f3db3fcfefe22d"},
+ {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:544542be95c9b49e211f0a463758f200de88ba6d5a94d3c4f42855a484341acd"},
+ {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac14c9893f3ea046784b7702be30889b200d31adcd2e6781a8a190b6423f9f2d"},
+ {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bb4bbe37ba95542081890dd34fa5347bef4651e276647adaa802d5d0d7d86452"},
+ {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3da489ecef798705f9a39ea8cea4ead0d1eeed55f92c345add89740bd930bab6"},
+ {file = "xattr-1.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:798dd0cbe696635a6f74b06fc430818bf9c3b24314e1502eadf67027ab60c9b0"},
+ {file = "xattr-1.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2b6361626efad5eb5a6bf8172c6c67339e09397ee8140ec41258737bea9681"},
+ {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7fa20a0c9ce022d19123b1c5b848d00a68b837251835a7929fe041ee81dcd0"},
+ {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20eeb08e2c57fc7e71f050b1cfae35cbb46105449853a582bf53fd23c5379e"},
+ {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:477370e75821bded901487e5e752cffe554d1bd3bd4839b627d4d1ee8c95a093"},
+ {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a8682091cd34a9f4a93c8aaea4101aae99f1506e24da00a3cc3dd2eca9566f21"},
+ {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e079b3b1a274ba2121cf0da38bbe5c8d2fb1cc49ecbceb395ce20eb7d69556d"},
+ {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ae6579dea05bf9f335a082f711d5924a98da563cac72a2d550f5b940c401c0e9"},
+ {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd6038ec9df2e67af23c212693751481d5f7e858156924f14340376c48ed9ac7"},
+ {file = "xattr-1.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:608b2877526674eb15df4150ef4b70b7b292ae00e65aecaae2f192af224be200"},
+ {file = "xattr-1.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54dad1a6a998c6a23edfd25e99f4d38e9b942d54e518570044edf8c767687ea"},
+ {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0dab6ff72bb2b508f3850c368f8e53bd706585012676e1f71debba3310acde8"},
+ {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3c54c6af7cf09432b2c461af257d5f4b1cb2d59eee045f91bacef44421a46d"},
+ {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e346e05a158d554639fbf7a0db169dc693c2d2260c7acb3239448f1ff4a9d67f"},
+ {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3ff6d9e2103d0d6e5fcd65b85a2005b66ea81c0720a37036445faadc5bbfa424"},
+ {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a2ee4563c6414dfec0d1ac610f59d39d5220531ae06373eeb1a06ee37cd193f"},
+ {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878df1b38cfdadf3184ad8c7b0f516311128d5597b60ac0b3486948953658a83"},
+ {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c9b8350244a1c5454f93a8d572628ff71d7e2fc2f7480dcf4c4f0e8af3150fe"},
+ {file = "xattr-1.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a46bf48fb662b8bd745b78bef1074a1e08f41a531168de62b5d7bd331dadb11a"},
+ {file = "xattr-1.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83fc3c07b583777b1dda6355329f75ca6b7179fe0d1002f1afe0ef96f7e3b5de"},
+ {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6308b19cff71441513258699f0538394fad5d66e1d324635207a97cb076fd439"},
+ {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c00ddc15ddadc9c729cd9504dabf50adb3d9c28f647d4ac9a3df45a046b1a0"},
+ {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a06136196f26293758e1b244200b73156a0274af9a7349fa201c71c7af3bb9e8"},
+ {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8fc2631a3c6cfcdc71f7f0f847461839963754e76a2015de71e7e71e3304abc0"},
+ {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6e1e835f9c938d129dd45e7eb52ebf7d2d6816323dab93ce311bf331f7d2328"},
+ {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60dea2d369a6484e8b7136224fc2971e10e2c46340d83ab780924afe78c90066"},
+ {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85c2b778b09d919523f80f244d799a142302582d76da18903dc693207c4020b0"},
+ {file = "xattr-1.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee0abba9e1b890d39141714ff43e9666864ca635ea8a5a2194d989e6b17fe862"},
+ {file = "xattr-1.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4174ba7f51f46b95ea7918d907c91cd579575d59e6a2f22ca36a0551026737"},
+ {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2b05e52e99d82d87528c54c2c5c8c5fb0ba435f85ac6545511aeea136e49925"},
+ {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a3696fad746be37de34eb73c60ea67144162bd08106a5308a90ce9dea9a3287"},
+ {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3a7149439a26b68904c14fdc4587cde4ac7d80303e9ff0fefcfd893b698c976"},
+ {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:507b36a126ce900dbfa35d4e2c2db92570c933294cba5d161ecd6a89f7b52f43"},
+ {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9392b417b54923e031041940d396b1d709df1d3779c6744454e1f1c1f4dad4f5"},
+ {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e9f00315e6c02943893b77f544776b49c756ac76960bea7cb8d7e1b96aefc284"},
+ {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8f98775065260140efb348b1ff8d50fd66ddcbf0c685b76eb1e87b380aaffb3"},
+ {file = "xattr-1.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b471c6a515f434a167ca16c5c15ff34ee42d11956baa749173a8a4e385ff23e7"},
+ {file = "xattr-1.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0763a1b7ceb78ba2f78bee5f30d1551dc26daafcce4ac125115fa1def20519"},
+ {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:099e6e9ce7999b403d36d9cf943105a3d25d8233486b54ec9d1b78623b050433"},
+ {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e56faef9dde8d969f0d646fb6171883693f88ae39163ecd919ec707fbafa85"},
+ {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:328156d4e594c9ae63e1072503c168849e601a153ad37f0290743544332d6b6f"},
+ {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a57a55a27c7864d6916344c9a91776afda6c3b8b2209f8a69b79cdba93fbe128"},
+ {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c19cdde08b040df1e99d2500bf8a9cff775ab0e6fa162bf8afe6d84aa93ed04"},
+ {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c72667f19d3a9acf324aed97f58861d398d87e42314731e7c6ab3ac7850c971"},
+ {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:67ae934d75ea2563fc48a27c5945749575c74a6de19fdd38390917ddcb0e4f24"},
+ {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1b0c348dd8523554dc535540d2046c0c8a535bb086561d8359f3667967b6ca"},
+ {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22284255d2a8e8f3da195bd8e8d43ce674dbc7c38d38cb6ecfb37fae7755d31f"},
+ {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aac5ef4381c26d3ce147ca98fba5a78b1e5bcd6be6755b4908659f2705c6d"},
+ {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:803f864af528f6f763a5be1e7b1ccab418e55ae0e4abc8bda961d162f850c991"},
+ {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:40354ebfb5cecd60a5fbb9833a8a452d147486b0ffec547823658556625d98b5"},
+ {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2abaf5d06be3361bfa8e0db2ee123ba8e92beab5bceed5e9d7847f2145a32e04"},
+ {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e638e5ffedc3565242b5fa3296899d35161bad771f88d66277b58f03a1ba9fe"},
+ {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0597e919d116ec39997804288d77bec3777228368efc0f2294b84a527fc4f9c2"},
+ {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee9455c501d19f065527afda974418b3ef7c61e85d9519d122cd6eb3cb7a00"},
+ {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:89ed62ce430f5789e15cfc1ccabc172fd8b349c3a17c52d9e6c64ecedf08c265"},
+ {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b824f4b9259cd8bb6e83c4873cf8bf080f6e4fa034a02fe778e07aba8d345"},
+ {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fba66faa0016dfc0af3dd7ac5782b5786a1dfb851f9f3455e266f94c2a05a04"},
+ {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec4b0c3e0a7bcd103f3cf31dd40c349940b2d4223ce43d384a3548992138ef1"},
+ {file = "xattr-1.1.4.tar.gz", hash = "sha256:b7b02ecb2270da5b7e7deaeea8f8b528c17368401c2b9d5f63e91f545b45d372"},
+]
+
+[package.dependencies]
+cffi = ">=1.16.0"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "yarl"
+version = "1.18.3"
+description = "Yet another URL library"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"},
+ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"},
+ {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"},
+ {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"},
+ {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"},
+ {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"},
+ {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"},
+ {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"},
+ {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"},
+ {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"},
+ {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"},
+ {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"},
+ {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"},
+ {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"},
+ {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"},
+ {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"},
+ {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"},
+ {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"},
+ {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"},
+ {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"},
+ {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"},
+ {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"},
+ {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"},
+ {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"},
+ {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"},
+ {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"},
+ {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"},
+ {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"},
+ {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"},
+ {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"},
+ {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"},
+ {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"},
+ {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"},
+ {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"},
+ {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"},
+ {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"},
+ {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"},
+ {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"},
+ {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"},
+ {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"},
+ {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"},
+ {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"},
+ {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"},
+ {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"},
+ {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"},
+ {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"},
+ {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"},
+ {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"},
+ {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"},
+ {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"},
+ {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"},
+ {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"},
+ {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"},
+ {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"},
+ {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"},
+ {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"},
+ {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"},
+ {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"},
+ {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"},
+ {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"},
+ {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"},
+ {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"},
+ {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"},
+ {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"},
+ {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"},
+ {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"},
+ {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"},
+ {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"},
+ {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"},
+ {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"},
+ {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"},
+ {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"},
+ {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"},
+ {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"},
+ {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"},
+ {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"},
+ {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"},
+ {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"},
+ {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"},
+ {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"},
+ {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"},
+ {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"},
+]
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+propcache = ">=0.2.0"
+
+[[package]]
+name = "zipp"
+version = "3.21.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "python_version <= \"3.11\""
+files = [
+ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
+ {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "zstandard"
+version = "0.23.0"
+description = "Zstandard bindings for Python"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\""
+files = [
+ {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"},
+ {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"},
+ {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"},
+ {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"},
+ {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"},
+ {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"},
+ {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"},
+ {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"},
+ {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"},
+ {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"},
+ {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"},
+ {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"},
+ {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"},
+ {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"},
+ {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"},
+ {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"},
+ {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"},
+ {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"},
+ {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"},
+ {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"},
+ {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"},
+ {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"},
+ {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"},
+ {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"},
+ {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"},
+ {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"},
+ {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"},
+ {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"},
+ {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"},
+ {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"},
+ {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"},
+ {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"},
+ {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"},
+ {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"},
+ {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"},
+ {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"},
+ {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"},
+ {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"},
+ {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"},
+ {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"},
+ {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"},
+ {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"},
+ {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"},
+ {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"},
+ {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"},
+ {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"},
+ {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"},
+ {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"},
+ {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"},
+ {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"},
+ {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"},
+ {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"},
+ {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"},
+ {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"},
+ {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"},
+ {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"},
+ {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"},
+ {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"},
+ {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"},
+ {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"},
+ {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"},
+]
+
+[package.dependencies]
+cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""}
+
+[package.extras]
+cffi = ["cffi (>=1.11)"]
+
+[metadata]
+lock-version = "2.1"
+python-versions = ">=3.10,<4.0"
+content-hash = "e384375098923696153ae702a6ac3eae30c2aeaf56844a2de66072194420d042"
diff --git a/apps/rowboat_agents/pyproject.toml b/apps/rowboat_agents/pyproject.toml
new file mode 100644
index 00000000..2cb85ee6
--- /dev/null
+++ b/apps/rowboat_agents/pyproject.toml
@@ -0,0 +1,111 @@
+[tool.poetry]
+name = "agents"
+version = "0.1.0"
+description = "RowBoat Labs Agent OS"
+authors = ["Akhilesh "]
+license = "MIT"
+readme = "README.md"
+homepage = "https://github.com/rowboatlabs/agents"
+package-mode = false
+
+[tool.poetry.dependencies]
+python = ">=3.10,<4.0"
+
+# Dependencies
+aiohttp = "^3.9.3"
+annotated-types = "^0.7.0"
+anyio = "^4.8.0"
+asgiref = "*"
+beautifulsoup4 = "^4.12.3"
+blinker = "^1.9.0"
+build = "1.2.2.post1"
+CacheControl = "^0.14.2"
+certifi = "^2024.12.14"
+cffi = "^1.17.1"
+charset-normalizer = "^3.4.1"
+cleo = "^2.1.0"
+click = "^8.1.8"
+crashtest = "^0.4.1"
+distlib = "^0.3.9"
+distro = "^1.9.0"
+dnspython = "^2.7.0"
+dulwich = "^0.22.7"
+et_xmlfile = "^2.0.0"
+eval_type_backport = "^0.2.2"
+fastjsonschema = "^2.21.1"
+filelock = "^3.17.0"
+firecrawl = "^1.9.0"
+Flask = "^3.1.0"
+gunicorn = "^23.0.0"
+h11 = "^0.14.0"
+httpcore = "^1.0.7"
+httpx = "^0.27.2"
+hypercorn = "*"
+idna = "^3.10"
+installer = "^0.7.0"
+itsdangerous = "^2.2.0"
+"jaraco.classes" = "^3.4.0"
+"jaraco.context" = "^6.0.1"
+"jaraco.functools" = "^4.1.0"
+Jinja2 = "^3.1.5"
+jiter = "^0.6.1"
+jsonpath-python = "^1.0.6"
+keyring = "^25.6.0"
+lxml = "^5.3.0"
+markdownify = "^0.13.1"
+MarkupSafe = "^3.0.2"
+mcp = "*"
+more-itertools = "^10.6.0"
+motor = "*"
+msgpack = "^1.1.0"
+mypy-extensions = "^1.0.0"
+nest-asyncio = "^1.6.0"
+numpy = "^2.2.1"
+openai = "*"
+openai-agents = "*"
+openpyxl = "^3.1.5"
+packaging = "^24.2"
+pandas = "^2.2.3"
+pkginfo = "^1.12.0"
+platformdirs = "^4.3.6"
+poetry = "^2.0.1"
+poetry-core = "^2.0.1"
+pycparser = "^2.22"
+pydantic = "^2.10.5"
+pydantic_core = "^2.27.2"
+PyJWT = "^2.10.1"
+pymongo = "^4.10.1"
+pyproject_hooks = "^1.2.0"
+python-dateutil = "^2.9.0.post0"
+python-docx = "^1.1.2"
+python-dotenv = "^1.0.1"
+pytz = "^2024.2"
+qdrant-client = "*"
+Quart = "^0.20.0"
+RapidFuzz = "^3.11.0"
+redis = "^5.2.1"
+requests = "^2.32.3"
+requests-toolbelt = "^1.0.0"
+setuptools = "^75.8.0"
+shellingham = "^1.5.4"
+six = "^1.17.0"
+sniffio = "^1.3.1"
+soupsieve = "^2.6"
+tabulate = "^0.9.0"
+tomlkit = "^0.13.2"
+tqdm = "^4.67.1"
+trove-classifiers = "^2025.1.15.22"
+typing-inspect = "^0.9.0"
+typing_extensions = "^4.12.2"
+tzdata = "^2024.2"
+urllib3 = "^2.3.0"
+virtualenv = "^20.29.1"
+waitress = "^2.1.2"
+websockets = "^13.1"
+Werkzeug = "^3.1.3"
+wheel = "^0.44.0"
+xattr = "^1.1.4"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/apps/agents/requirements.txt b/apps/rowboat_agents/requirements.txt
similarity index 90%
rename from apps/agents/requirements.txt
rename to apps/rowboat_agents/requirements.txt
index 54a25f05..aaa4caa4 100644
--- a/apps/agents/requirements.txt
+++ b/apps/rowboat_agents/requirements.txt
@@ -1,5 +1,7 @@
+aiohttp==3.9.3
annotated-types==0.7.0
anyio==4.8.0
+asgiref
beautifulsoup4==4.12.3
blinker==1.9.0
build==1.2.2.post1
@@ -24,6 +26,7 @@ gunicorn==23.0.0
h11==0.14.0
httpcore==1.0.7
httpx==0.27.2
+hypercorn
idna==3.10
installer==0.7.0
itsdangerous==2.2.0
@@ -37,12 +40,15 @@ keyring==25.6.0
lxml==5.3.0
markdownify==0.13.1
MarkupSafe==3.0.2
+mcp
more-itertools==10.6.0
+motor
msgpack==1.1.0
mypy-extensions==1.0.0
nest-asyncio==1.6.0
numpy==2.2.1
-openai==1.59.7
+openai
+openai-agents
openpyxl==3.1.5
packaging==24.2
pandas==2.2.3
@@ -53,13 +59,17 @@ poetry-core==2.0.1
pycparser==2.22
pydantic==2.10.5
pydantic_core==2.27.2
+PyJWT==2.10.1
pymongo==4.10.1
pyproject_hooks==1.2.0
python-dateutil==2.9.0.post0
python-docx==1.1.2
python-dotenv==1.0.1
pytz==2024.2
+qdrant-client
+Quart==0.20.0
RapidFuzz==3.11.0
+redis==5.2.1
requests==2.32.3
requests-toolbelt==1.0.0
setuptools==75.8.0
@@ -76,7 +86,8 @@ typing_extensions==4.12.2
tzdata==2024.2
urllib3==2.3.0
virtualenv==20.29.1
+waitress==2.1.2
websockets==13.1
Werkzeug==3.1.3
wheel==0.44.0
-xattr==1.1.4
+xattr==1.1.4
\ No newline at end of file
diff --git a/apps/agents/src/__init__.py b/apps/rowboat_agents/src/__init__.py
similarity index 100%
rename from apps/agents/src/__init__.py
rename to apps/rowboat_agents/src/__init__.py
diff --git a/apps/agents/src/app/__init__.py b/apps/rowboat_agents/src/app/__init__.py
similarity index 100%
rename from apps/agents/src/app/__init__.py
rename to apps/rowboat_agents/src/app/__init__.py
diff --git a/apps/rowboat_agents/src/app/main.py b/apps/rowboat_agents/src/app/main.py
new file mode 100644
index 00000000..6d6d501d
--- /dev/null
+++ b/apps/rowboat_agents/src/app/main.py
@@ -0,0 +1,198 @@
+from quart import Quart, request, jsonify, Response
+from datetime import datetime
+from functools import wraps
+import os
+import redis
+import uuid
+import json
+from hypercorn.config import Config
+from hypercorn.asyncio import serve
+import asyncio
+
+from src.graph.core import run_turn, run_turn_streamed
+from src.graph.tools import RAG_TOOL, CLOSE_CHAT_TOOL
+from src.utils.common import common_logger, read_json_from_file
+
+from pprint import pprint
+
+logger = common_logger
+redis_client = redis.from_url(os.environ.get('REDIS_URL', 'redis://localhost:6379'))
+app = Quart(__name__)
+
+# filter out agent transfer messages using a function
+def is_agent_transfer_message(msg):
+ if (msg.get("role") == "assistant" and
+ msg.get("content") is None and
+ msg.get("tool_calls") is not None and
+ len(msg.get("tool_calls")) > 0 and
+ msg.get("tool_calls")[0].get("function").get("name") == "transfer_to_agent"):
+ return True
+ if (msg.get("role") == "tool" and
+ msg.get("tool_calls") is None and
+ msg.get("tool_call_id") is not None and
+ msg.get("tool_name") == "transfer_to_agent"):
+ return True
+ return False
+
+@app.route("/health", methods=["GET"])
+async def health():
+ return jsonify({"status": "ok"})
+
+@app.route("/")
+async def home():
+ return "Hello, World!"
+
+def require_api_key(f):
+ @wraps(f)
+ async def decorated(*args, **kwargs):
+ auth_header = request.headers.get('Authorization')
+ if not auth_header or not auth_header.startswith('Bearer '):
+ return jsonify({'error': 'Missing or invalid authorization header'}), 401
+
+ token = auth_header.split('Bearer ')[1]
+ actual = os.environ.get('API_KEY', '').strip()
+ if actual and token != actual:
+ return jsonify({'error': 'Invalid API key'}), 403
+
+ return await f(*args, **kwargs)
+ return decorated
+
+@app.route("/chat", methods=["POST"])
+@require_api_key
+async def chat():
+ logger.info('='*100)
+ logger.info(f"{'*'*100}Running server mode{'*'*100}")
+ try:
+ request_data = await request.get_json()
+ config = read_json_from_file("./configs/default_config.json")
+
+ # filter out agent transfer messages
+ input_messages = [msg for msg in request_data["messages"] if not is_agent_transfer_message(msg)]
+
+ # Preprocess messages to handle null content and role issues
+ for msg in input_messages:
+ if (msg.get("role") == "assistant" and
+ msg.get("content") is None and
+ msg.get("tool_calls") is not None and
+ len(msg.get("tool_calls")) > 0):
+ msg["content"] = "Calling tool"
+
+ if msg.get("role") == "tool":
+ msg["role"] = "developer"
+ elif not msg.get("role"):
+ msg["role"] = "user"
+
+ print("Request:")
+ pprint(request_data)
+
+ data = request_data
+ resp_messages, resp_tokens_used, resp_state = await run_turn(
+ messages=input_messages,
+ start_agent_name=data.get("startAgent", ""),
+ agent_configs=data.get("agents", []),
+ tool_configs=data.get("tools", []),
+ start_turn_with_start_agent=config.get("start_turn_with_start_agent", False),
+ state=data.get("state", {}),
+ additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL],
+ complete_request=data
+ )
+
+ logger.info('-'*100)
+ logger.info('Raw output:')
+ logger.info((resp_messages, resp_tokens_used, resp_state))
+
+ out = {
+ "messages": resp_messages,
+ "tokens_used": resp_tokens_used,
+ "state": resp_state,
+ }
+
+ logger.info("Output:")
+ for k, v in out.items():
+ logger.info(f"{k}: {v}")
+ logger.info('*'*100)
+
+ return jsonify(out)
+
+ except Exception as e:
+ logger.error(f"Error: {e}")
+ return jsonify({"error": str(e)}), 500
+
+@app.route("/chat_stream_init", methods=["POST"])
+@require_api_key
+async def chat_stream_init():
+ # create a uuid for the stream
+ stream_id = str(uuid.uuid4())
+
+ # store the request data in redis with 10 minute TTL
+ data = await request.get_json()
+ redis_client.setex(f"stream_request_{stream_id}", 600, json.dumps(data))
+
+ return jsonify({"streamId": stream_id})
+
+def format_sse(data: dict, event: str = None) -> str:
+ msg = f"data: {json.dumps(data)}\n\n"
+ if event is not None:
+ msg = f"event: {event}\n{msg}"
+ return msg
+
+@app.route("/chat_stream/", methods=["GET"])
+@require_api_key
+async def chat_stream(stream_id):
+ # get the request data from redis
+ request_data = redis_client.get(f"stream_request_{stream_id}")
+ if not request_data:
+ return jsonify({"error": "Stream not found"}), 404
+
+ request_data = json.loads(request_data)
+ config = read_json_from_file("./configs/default_config.json")
+
+ # filter out agent transfer messages
+ input_messages = [msg for msg in request_data["messages"] if not is_agent_transfer_message(msg)]
+
+ # Preprocess messages to handle null content and role issues
+ for msg in input_messages:
+ if (msg.get("role") == "assistant" and
+ msg.get("content") is None and
+ msg.get("tool_calls") is not None and
+ len(msg.get("tool_calls")) > 0):
+ msg["content"] = "Calling tool"
+
+ if msg.get("role") == "tool":
+ msg["role"] = "developer"
+ elif not msg.get("role"):
+ msg["role"] = "user"
+
+ print("Request:")
+ pprint(request_data)
+
+ async def generate():
+ try:
+ async for event_type, event_data in run_turn_streamed(
+ messages=input_messages,
+ start_agent_name=request_data.get("startAgent", ""),
+ agent_configs=request_data.get("agents", []),
+ tool_configs=request_data.get("tools", []),
+ start_turn_with_start_agent=config.get("start_turn_with_start_agent", False),
+ state=request_data.get("state", {}),
+ additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL],
+ complete_request=request_data
+ ):
+ if event_type == 'message':
+ print("Yielding message:")
+ yield format_sse(event_data, "message")
+ elif event_type == 'done':
+ print("Yielding done:")
+ yield format_sse(event_data, "done")
+
+ except Exception as e:
+ logger.error(f"Streaming error: {str(e)}")
+ yield format_sse({"error": str(e)}, "error")
+
+ return Response(generate(), mimetype='text/event-stream')
+
+if __name__ == "__main__":
+ print("Starting async server...")
+ config = Config()
+ config.bind = ["0.0.0.0:4040"]
+ asyncio.run(serve(app, config))
\ No newline at end of file
diff --git a/apps/agents/src/graph/__init__.py b/apps/rowboat_agents/src/graph/__init__.py
similarity index 100%
rename from apps/agents/src/graph/__init__.py
rename to apps/rowboat_agents/src/graph/__init__.py
diff --git a/apps/rowboat_agents/src/graph/core.py b/apps/rowboat_agents/src/graph/core.py
new file mode 100644
index 00000000..1cb8171a
--- /dev/null
+++ b/apps/rowboat_agents/src/graph/core.py
@@ -0,0 +1,400 @@
+from copy import deepcopy
+from datetime import datetime
+import json
+import uuid
+import logging
+from .helpers.access import (
+ get_agent_by_name,
+ get_external_tools,
+)
+from .helpers.state import (
+ construct_state_from_response
+)
+from .helpers.control import get_latest_assistant_msg, get_latest_non_assistant_messages, get_last_agent_name
+from .swarm_wrapper import run as swarm_run, run_streamed as swarm_run_streamed, create_response, get_agents
+from src.utils.common import common_logger as logger
+import asyncio
+
+# Create a dedicated logger for swarm wrapper
+logger.setLevel(logging.INFO)
+print("Logger level set to INFO")
+
+def order_messages(messages):
+ """
+ Sorts each message's keys in a specified order and returns a new list of ordered messages.
+ """
+ ordered_messages = []
+ for msg in messages:
+ # Filter out None values
+ msg = {k: v for k, v in msg.items() if v is not None}
+
+ # Specify the exact order
+ ordered = {}
+ for key in ['role', 'sender', 'content', 'created_at', 'timestamp']:
+ if key in msg:
+ ordered[key] = msg[key]
+
+ # Add remaining keys in alphabetical order
+ remaining_keys = sorted(k for k in msg if k not in ordered)
+ for key in remaining_keys:
+ ordered[key] = msg[key]
+
+ ordered_messages.append(ordered)
+ return ordered_messages
+
+
+def clean_up_history(agent_data):
+ """
+ Ensures each agent's history is sorted using order_messages.
+ """
+ for data in agent_data:
+ data["history"] = order_messages(data["history"])
+ return agent_data
+
+def create_final_response(response, turn_messages, tokens_used, all_agents):
+ """
+ Constructs the final response data (messages, tokens_used, updated state) that a caller would need.
+ """
+ # Ensure response has a messages attribute
+ if not hasattr(response, 'messages'):
+ response.messages = []
+
+ # Assign the appropriate messages to the response
+ response.messages = turn_messages
+
+ # Ensure tokens_used is a valid dictionary
+ if not isinstance(tokens_used, dict):
+ tokens_used = {"total": 100, "prompt": 50, "completion": 50} # Default values if not a dictionary
+
+ # Ensure response has a tokens_used attribute that's a dictionary
+ if not hasattr(response, 'tokens_used') or not isinstance(response.tokens_used, dict):
+ response.tokens_used = {}
+
+ response.tokens_used = tokens_used
+
+ # Ensure response has an agent attribute for state construction
+ if not hasattr(response, 'agent'):
+ if all_agents and len(all_agents) > 0:
+ response.agent = all_agents[0] # Set default agent if missing
+
+ new_state = construct_state_from_response(response, all_agents)
+ return response.messages, response.tokens_used, new_state
+
+
+async def run_turn(
+ messages, start_agent_name, agent_configs, tool_configs, start_turn_with_start_agent, state={}, additional_tool_configs=[], complete_request={}
+):
+ """
+ Coordinates a single 'turn' of conversation or processing among agents.
+ Includes validation, agent setup, optional greeting logic, error handling, and post-processing steps.
+ """
+ logger.info("Running stateless turn")
+ print("Running stateless turn")
+
+ # Sort messages by the specified ordering
+ #messages = order_messages(messages)
+
+ # Merge any additional tool configs
+ tool_configs = tool_configs + additional_tool_configs
+
+ # Determine if this is a greeting turn
+ greeting_turn = not any(msg.get("role") != "system" for msg in messages)
+ turn_messages = []
+ # Initialize tokens_used as a dictionary
+ tokens_used = {"total": 0, "prompt": 0, "completion": 0}
+
+ agent_data = state.get("agent_data", [])
+
+ # If not a greeting turn, localize the last user or system messages
+ if not greeting_turn:
+ latest_assistant_msg = get_latest_assistant_msg(messages)
+ latest_non_assistant_msgs = get_latest_non_assistant_messages(messages)
+ msg_type = latest_non_assistant_msgs[-1]["role"]
+
+ # Determine the last agent from state/config
+ last_agent_name = get_last_agent_name(
+ state=state,
+ agent_configs=agent_configs,
+ start_agent_name=start_agent_name,
+ msg_type=msg_type,
+ latest_assistant_msg=latest_assistant_msg,
+ start_turn_with_start_agent=start_turn_with_start_agent
+ )
+ else:
+ # For a greeting turn, we assume the last agent is the start_agent_name
+ last_agent_name = start_agent_name
+
+ state["agent_data"] = agent_data
+
+ # Initialize all agents
+ logger.info("Initializing agents")
+ print("Initializing agents")
+ new_agents = get_agents(
+ agent_configs=agent_configs,
+ tool_configs=tool_configs,
+ complete_request=complete_request
+ )
+ # Prepare escalation agent
+ last_new_agent = get_agent_by_name(last_agent_name, new_agents)
+
+ # Gather external tools for Swarm
+ external_tools = get_external_tools(tool_configs)
+ logger.info(f"Found {len(external_tools)} external tools")
+ print(f"Found {len(external_tools)} external tools")
+
+ # If no validation error yet, proceed with the main run
+
+ logger.info("Running swarm run")
+ print("Running swarm run")
+
+ response = await swarm_run(
+ agent=last_new_agent,
+ messages=messages,
+ external_tools=external_tools,
+ tokens_used=tokens_used
+ )
+
+ logger.info("Swarm run completed")
+ print("Swarm run completed")
+
+ # Initialize response.messages if it doesn't exist
+ if not hasattr(response, 'messages'):
+ response.messages = []
+
+ # Convert the ResponseOutputMessage to a standard message format
+ if hasattr(response, 'new_items') and response.new_items and hasattr(response.new_items[-1], 'raw_item'):
+ raw_item = response.new_items[-1].raw_item
+ # Extract text content from ResponseOutputText objects
+ content = ""
+ if hasattr(raw_item, 'content') and raw_item.content:
+ for content_item in raw_item.content:
+ if hasattr(content_item, 'text'):
+ content += content_item.text
+
+ # Create a standard message dictionary
+ standard_message = {
+ "role": raw_item.role if hasattr(raw_item, 'role') else "assistant",
+ "content": content,
+ "sender": last_new_agent.name,
+ "created_at": None,
+ "response_type": "internal"
+ }
+
+ # Add the converted message to response messages
+ response.messages.append(standard_message)
+
+ logger.info("Converted message added to response messages")
+ print("Converted message added to response messages")
+
+ # Use a dictionary for tokens_used instead of a hard-coded integer
+ tokens_used = {"total": 100, "prompt": 50, "completion": 50} # Dummy values as placeholders
+
+ # Ensure turn_messages can be extended with response.messages
+ if hasattr(response, 'messages') and isinstance(response.messages, list):
+ turn_messages.extend(response.messages)
+
+ logger.info(f"Completed run of agent: {last_new_agent.name}")
+ print(f"Completed run of agent: {last_new_agent.name}")
+
+
+ # Otherwise, duplicate the last response as external
+ logger.info("No post-processing agent found. Duplicating last response and setting to external.")
+ print("No post-processing agent found. Duplicating last response and setting to external.")
+ if turn_messages:
+ duplicate_msg = deepcopy(turn_messages[-1])
+ duplicate_msg["response_type"] = "external"
+ duplicate_msg["sender"] += " >> External"
+
+ # Ensure tokens_used remains a proper dictionary
+ if not isinstance(tokens_used, dict):
+ tokens_used = {"total": 100, "prompt": 50, "completion": 50} # Default values if not a dictionary
+
+ response = create_response(
+ messages=[duplicate_msg],
+ tokens_used=tokens_used,
+ agent=last_new_agent,
+ error_msg=''
+ )
+
+ # Ensure response has messages attribute
+ if hasattr(response, 'messages') and isinstance(response.messages, list):
+ turn_messages.extend(response.messages)
+
+ # Finalize the response
+ logger.info("Finalizing response")
+ print("Finalizing response")
+ return create_final_response(
+ response=response,
+ turn_messages=turn_messages,
+ tokens_used=tokens_used,
+ all_agents=new_agents
+ )
+
+async def run_turn_streamed(
+ messages,
+ start_agent_name,
+ agent_configs,
+ tool_configs,
+ start_turn_with_start_agent,
+ state={},
+ additional_tool_configs=[],
+ complete_request={}
+):
+ final_state = None # Initialize outside try block
+ try:
+ # Initialize agents and get external tools
+ new_agents = get_agents(agent_configs=agent_configs, tool_configs=tool_configs, complete_request=complete_request)
+ last_agent_name = get_last_agent_name(
+ state=state,
+ agent_configs=agent_configs,
+ start_agent_name=start_agent_name,
+ msg_type="user",
+ latest_assistant_msg=None,
+ start_turn_with_start_agent=start_turn_with_start_agent
+ )
+ last_new_agent = get_agent_by_name(last_agent_name, new_agents)
+ external_tools = get_external_tools(tool_configs)
+
+ current_agent = last_new_agent
+ tokens_used = {"total": 0, "prompt": 0, "completion": 0}
+
+ stream_result = await swarm_run_streamed(
+ agent=last_new_agent,
+ messages=messages,
+ external_tools=external_tools,
+ tokens_used=tokens_used
+ )
+
+ # Process streaming events
+ async for event in stream_result.stream_events():
+ print('='*50)
+ print("Received event: ", event)
+ print('-'*50)
+
+ # Handle raw response events and accumulate tokens
+ if event.type == "raw_response_event":
+ if hasattr(event.data, 'type') and event.data.type == "response.completed":
+ if hasattr(event.data.response, 'usage'):
+ tokens_used["total"] += event.data.response.usage.total_tokens
+ tokens_used["prompt"] += event.data.response.usage.input_tokens
+ tokens_used["completion"] += event.data.response.usage.output_tokens
+ print('-'*50)
+ print(f"Found usage information. Updated cumulative tokens: {tokens_used}")
+ print('-'*50)
+ continue
+
+ # Update current agent when it changes
+ elif event.type == "agent_updated_stream_event":
+ if current_agent.name == event.new_agent.name:
+ continue
+
+ tool_call_id = str(uuid.uuid4())
+
+ # yield the transfer invocation
+ message = {
+ 'content': None,
+ 'role': 'assistant',
+ 'sender': current_agent.name,
+ 'tool_calls': [{
+ 'function': {
+ 'name': 'transfer_to_agent',
+ 'arguments': json.dumps({
+ 'assistant': event.new_agent.name
+ })
+ },
+ 'id': tool_call_id,
+ 'type': 'function'
+ }],
+ 'tool_call_id': None,
+ 'tool_name': None,
+ 'response_type': 'internal'
+ }
+ print("Yielding message: ", message)
+ yield ('message', message)
+
+ # yield the transfer result
+ message = {
+ 'content': json.dumps({
+ 'assistant': event.new_agent.name
+ }),
+ 'role': 'tool',
+ 'sender': None,
+ 'tool_calls': None,
+ 'tool_call_id': tool_call_id,
+ 'tool_name': 'transfer_to_agent',
+ }
+ print("Yielding message: ", message)
+ yield ('message', message)
+
+ current_agent = event.new_agent
+ continue
+
+ # Handle run items (tools, messages, etc)
+ elif event.type == "run_item_stream_event":
+ current_agent = event.item.agent
+ if event.item.type == "tool_call_item":
+ message = {
+ 'content': None,
+ 'role': 'assistant',
+ 'sender': current_agent.name if current_agent else None,
+ 'tool_calls': [{
+ 'function': {
+ 'name': event.item.raw_item.name,
+ 'arguments': event.item.raw_item.arguments
+ },
+ 'id': event.item.raw_item.call_id,
+ 'type': 'function'
+ }],
+ 'tool_call_id': None,
+ 'tool_name': None,
+ 'response_type': 'internal'
+ }
+ print("Yielding message: ", message)
+ yield ('message', message)
+
+ elif event.item.type == "tool_call_output_item":
+ message = {
+ 'content': str(event.item.output),
+ 'role': 'tool',
+ 'sender': None,
+ 'tool_calls': None,
+ 'tool_call_id': event.item.raw_item['call_id'],
+ 'tool_name': event.item.raw_item.get('name', None),
+ 'response_type': 'internal'
+ }
+ print("Yielding message: ", message)
+ yield ('message', message)
+
+ elif event.item.type == "message_output_item":
+ content = ""
+ if hasattr(event.item.raw_item, 'content'):
+ for content_item in event.item.raw_item.content:
+ if hasattr(content_item, 'text'):
+ content += content_item.text
+
+ message = {
+ 'content': content,
+ 'role': 'assistant',
+ 'sender': current_agent.name,
+ 'tool_calls': None,
+ 'tool_call_id': None,
+ 'tool_name': None,
+ 'response_type': 'external'
+ }
+ print("Yielding message: ", message)
+ yield ('message', message)
+
+ print(f"\n{'='*50}\n")
+
+ # After all events are processed, set final state
+ final_state = {
+ "last_agent_name": current_agent.name if current_agent else None,
+ "tokens": tokens_used
+ }
+ yield ('done', {'state': final_state})
+
+ except Exception as e:
+ import traceback
+ print(traceback.format_exc())
+ print(f"Error in stream processing: {str(e)}")
+ yield ('error', {'error': str(e), 'state': final_state}) # Include final_state in error response
\ No newline at end of file
diff --git a/apps/agents/src/graph/guardrails.py b/apps/rowboat_agents/src/graph/guardrails.py
similarity index 95%
rename from apps/agents/src/graph/guardrails.py
rename to apps/rowboat_agents/src/graph/guardrails.py
index 483aee79..f5b3f660 100644
--- a/apps/agents/src/graph/guardrails.py
+++ b/apps/rowboat_agents/src/graph/guardrails.py
@@ -3,7 +3,7 @@ from src.utils.common import generate_llm_output
import os
import copy
-from src.swarm.types import Response, Agent
+from .swarm_wrapper import Agent, Response, create_response
from src.utils.common import common_logger, generate_openai_output, update_tokens_used
logger = common_logger
@@ -20,12 +20,12 @@ def classify_hallucination(context: str, assistant_response: str, chat_history:
Returns:
str: Verdict indicating level of hallucination:
'yes-absolute' - completely supported by context
- 'yes-common-sensical' - supported with common sense interpretation
+ 'yes-common-sensical' - supported with common sense interpretation
'no-absolute' - not supported by context
'no-subtle' - not supported but difference is subtle
"""
chat_history_str = "\n".join([f"{message['role']}: {message['content']}" for message in chat_history])
-
+
prompt = f"""
You are a guardrail agent. Your job is to check if the response is hallucinating.
@@ -51,40 +51,40 @@ def classify_hallucination(context: str, assistant_response: str, chat_history:
no-absolute: not supported by the context
no-subtle: not supported by the context but the difference is subtle
- Output of of the classes:
- verdict : yes-absolute/yes-common-sensical/no-absolute/no-subtle
+ Output of of the classes:
+ verdict : yes-absolute/yes-common-sensical/no-absolute/no-subtle
Example 1: The response is completely supported by the context.
- User Input:
+ User Input:
Context: "Our airline provides complimentary meals and beverages on all international flights. Passengers are allowed one carry-on bag and one personal item."
- Chat History:
+ Chat History:
User: "Do international flights with your airline offer free meals?"
Response: "Yes, all international flights with our airline offer free meals and beverages."
Output: verdict: yes-absolute
Example 2: The response is generally true and could be deduced with common sense interpretation, though not explicitly stated in the context.
- User Input:
+ User Input:
Context: "Flights may experience delays due to weather conditions. In such cases, the airline staff will provide updates at the airport."
- Chat History:
+ Chat History:
User: "Will there be announcements if my flight is delayed?"
Response: "Yes, if your flight is delayed, there will be announcements at the airport."
- Output: verdict: yes-common-sensical
+ Output: verdict: yes-common-sensical
Example 3: The response is not supported by the context and contains glaring inaccuracies.
- User Input:
+ User Input:
Context: "You can cancel your ticket online up to 24 hours before the flight's departure time and receive a full refund."
- Chat History:
+ Chat History:
User: "Can I get a refund if I cancel 12 hours before the flight?"
Response: "Yes, you can get a refund if you cancel 12 hours before the flight."
Output: verdict: no-absolute
Example 4: The response is not supported by the context but the difference is subtle.
- User Input:
+ User Input:
Context: "Our frequent flyer program offers discounts on checked bags for members who have achieved Gold status."
- Chat History:
+ Chat History:
User: "As a member, do I get discounts on checked bags?"
Response: "Yes, members of our frequent flyer program get discounts on checked bags."
- Output: verdict: no-subtle
+ Output: verdict: no-subtle
"""
messages = [
{
@@ -105,7 +105,7 @@ def post_process_response(messages: list, post_processing_agent_name: str, post_
logger.debug(f"Pending message keys: {pending_msg.keys()}")
skip = False
-
+
if pending_msg.get("tool_calls"):
logger.info("Last message is a tool call, skipping post processing and setting last message to external")
skip = True
@@ -113,11 +113,11 @@ def post_process_response(messages: list, post_processing_agent_name: str, post_
elif not pending_msg['response_type'] == "internal":
logger.info("Last message is not internal, skipping post processing and setting last message to external")
skip = True
-
+
elif not pending_msg['content']:
logger.info("Last message has no content, skipping post processing and setting last message to external")
skip = True
-
+
elif not post_process_instructions:
logger.info("No post process instructions, skipping post processing and setting last message to external")
skip = True
@@ -131,7 +131,7 @@ def post_process_response(messages: list, post_processing_agent_name: str, post_
error_msg=''
)
return response
-
+
agent_history_str = f"\n{'*'*100}\n".join([f"Role: {message['role']} | Content: {message.get('content', 'None')} | Tool Calls: {message.get('tool_calls', 'None')}" for message in agent_history[:-1]])
logger.debug(f"Agent history: {agent_history_str}")
@@ -147,7 +147,7 @@ def post_process_response(messages: list, post_processing_agent_name: str, post_
{post_process_instructions}
------------------------------------------------------------------------
-
+
# CHAT HISTORY
Here is the chat history:
@@ -186,7 +186,7 @@ def post_process_response(messages: list, post_processing_agent_name: str, post_
Here is the response that the agent has generated:
{pending_msg['content']}
-
+
"""
prompt += agent_response_and_instructions
diff --git a/apps/agents/src/graph/helpers/access.py b/apps/rowboat_agents/src/graph/helpers/access.py
similarity index 100%
rename from apps/agents/src/graph/helpers/access.py
rename to apps/rowboat_agents/src/graph/helpers/access.py
diff --git a/apps/agents/src/graph/helpers/control.py b/apps/rowboat_agents/src/graph/helpers/control.py
similarity index 100%
rename from apps/agents/src/graph/helpers/control.py
rename to apps/rowboat_agents/src/graph/helpers/control.py
diff --git a/apps/agents/src/graph/helpers/instructions.py b/apps/rowboat_agents/src/graph/helpers/instructions.py
similarity index 100%
rename from apps/agents/src/graph/helpers/instructions.py
rename to apps/rowboat_agents/src/graph/helpers/instructions.py
diff --git a/apps/agents/src/graph/helpers/state.py b/apps/rowboat_agents/src/graph/helpers/state.py
similarity index 75%
rename from apps/agents/src/graph/helpers/state.py
rename to apps/rowboat_agents/src/graph/helpers/state.py
index c98c0462..c1b2427c 100644
--- a/apps/agents/src/graph/helpers/state.py
+++ b/apps/rowboat_agents/src/graph/helpers/state.py
@@ -49,13 +49,7 @@ def construct_state_from_response(response, agents):
for agent in agents:
agent_data.append({
"name": agent.name,
- "instructions": agent.instructions,
- "parent_function": agent.parent_function.__name__ if agent.parent_function else None,
- "child_functions": [f.__name__ for f in agent.child_functions.values()] if agent.child_functions else [],
- "internal_tools": [t.get("function").get("name") for t in agent.internal_tools] if agent.internal_tools else [],
- "external_tools": [t.get("function").get("name") for t in agent.external_tools] if agent.external_tools else [],
- "history": agent.history,
- "most_recent_parent_name": agent.most_recent_parent.name if agent.most_recent_parent else ""
+ "instructions": agent.instructions
})
state = {
diff --git a/apps/agents/src/graph/helpers/transfer.py b/apps/rowboat_agents/src/graph/helpers/transfer.py
similarity index 100%
rename from apps/agents/src/graph/helpers/transfer.py
rename to apps/rowboat_agents/src/graph/helpers/transfer.py
diff --git a/apps/agents/src/graph/instructions.py b/apps/rowboat_agents/src/graph/instructions.py
similarity index 100%
rename from apps/agents/src/graph/instructions.py
rename to apps/rowboat_agents/src/graph/instructions.py
diff --git a/apps/rowboat_agents/src/graph/swarm_wrapper.py b/apps/rowboat_agents/src/graph/swarm_wrapper.py
new file mode 100644
index 00000000..759a5a83
--- /dev/null
+++ b/apps/rowboat_agents/src/graph/swarm_wrapper.py
@@ -0,0 +1,406 @@
+import logging
+import json
+import aiohttp
+import jwt
+import hashlib
+
+# Import helper functions needed for get_agents
+from .helpers.access import (
+ get_tool_config_by_name,
+ get_tool_config_by_type
+)
+from .helpers.instructions import (
+ add_rag_instructions_to_agent
+)
+
+from agents import Agent as NewAgent, Runner, FunctionTool, RunContextWrapper, ModelSettings
+# Add import for OpenAI functionality
+from src.utils.common import common_logger as logger, generate_openai_output
+from typing import Any
+from dataclasses import asdict
+import asyncio
+from mcp import ClientSession
+from mcp.client.sse import sse_client
+
+from pydantic import BaseModel
+from typing import List, Optional, Dict
+from .tool_calling import call_rag_tool
+from pymongo import MongoClient
+import os
+MONGO_URI = os.environ.get("MONGODB_URI", "mongodb://localhost:27017/rowboat").strip()
+mongo_client = MongoClient(MONGO_URI)
+db = mongo_client["rowboat"]
+
+class NewResponse(BaseModel):
+ messages: List[Dict]
+ agent: Optional[Any] = None
+ tokens_used: Optional[dict] = {}
+ error_msg: Optional[str] = ""
+
+async def mock_tool(tool_name: str, args: str, description: str, mock_instructions: str) -> str:
+ """
+ Handles tool execution by either using mock instructions or generating a response.
+
+ Args:
+ tool_name: The name of the tool
+ args: The arguments passed to the tool
+ tool_config: The configuration of the tool
+
+ Returns:
+ The response from the tool
+ """
+ print(f"Mock tool called for: {tool_name}")
+
+
+ messages = [
+ {"role": "system", "content": f"You are simulating the execution of a tool called '{tool_name}'.Here is the description of the tool: {description}. Here are the instructions for the mock tool: {mock_instructions}. Generate a realistic response as if the tool was actually executed with the given parameters."},
+ {"role": "user", "content": f"Generate a realistic response for the tool '{tool_name}' with these parameters: {args}. The response should be concise and focused on what the tool would actually return."}
+ ]
+
+ print(f"Generating simulated response for tool: {tool_name}")
+ response_content = generate_openai_output(messages, output_type='text', model="gpt-4o")
+ return response_content
+
+async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secret: str) -> str:
+ """
+ Calls the webhook with the given tool name and arguments.
+
+ Args:
+ tool_name (str): The name of the tool to call.
+ args (str): The arguments for the tool as a JSON string.
+
+ Returns:
+ str: The response from the webhook, or an error message if the call fails.
+ """
+ content_dict = {
+ "toolCall": {
+ "function": {
+ "name": tool_name,
+ "arguments": args
+ }
+ }
+ }
+ request_body = {
+ "content": json.dumps(content_dict)
+ }
+
+ # Prepare headers
+ headers = {}
+ if signing_secret:
+ content_str = request_body["content"]
+ body_hash = hashlib.sha256(content_str.encode('utf-8')).hexdigest()
+ payload = {"bodyHash": body_hash}
+ signature_jwt = jwt.encode(payload, signing_secret, algorithm="HS256")
+ headers["X-Signature-Jwt"] = signature_jwt
+
+ try:
+ async with aiohttp.ClientSession() as session:
+ async with session.post(webhook_url, json=request_body, headers=headers) as response:
+ if response.status == 200:
+ response_json = await response.json()
+ return response_json.get("result", "")
+ else:
+ error_msg = await response.text()
+ print(f"Webhook error: {error_msg}")
+ return f"Error: {error_msg}"
+ except Exception as e:
+ print(f"Exception in call_webhook: {str(e)}")
+ return f"Error: Failed to call webhook - {str(e)}"
+
+async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str:
+ """
+ Calls the MCP with the given tool name and arguments.
+ """
+
+ async with sse_client(url=mcp_server_url) as streams:
+ async with ClientSession(*streams) as session:
+ await session.initialize()
+ jargs = json.loads(args)
+ response = await session.call_tool(tool_name, arguments=jargs)
+ json_output = json.dumps([item.__dict__ for item in response.content], indent=2)
+
+ return json_output
+
+async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool_config: dict, complete_request: dict) -> str:
+ """
+ Handles all tool calls by dispatching to appropriate functions.
+ """
+ print(f"Catch all called for tool: {tool_name}")
+ print(f"Args: {args}")
+ print(f"Tool config: {tool_config}")
+
+ # Create event loop for async operations
+ try:
+ loop = asyncio.get_event_loop()
+ except RuntimeError:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ response_content = None
+ if tool_config.get("mockTool", False) or complete_request.get("testProfile", {}).get("mockTools", False):
+ # Call mock_tool to handle the response (it will decide whether to use mock instructions or generate a response)
+ if complete_request.get("testProfile", {}).get("mockPrompt", ""):
+ response_content = await mock_tool(tool_name, args, tool_config.get("description", ""), complete_request.get("testProfile", {}).get("mockPrompt", ""))
+ else:
+ response_content = await mock_tool(tool_name, args, tool_config.get("description", ""), tool_config.get("mockInstructions", ""))
+ print(response_content)
+ elif tool_config.get("isMcp", False):
+ mcp_server_name = tool_config.get("mcpServerName", "")
+ mcp_servers = complete_request.get("mcpServers", {})
+ mcp_server_url = next((server.get("url", "") for server in mcp_servers if server.get("name") == mcp_server_name), "")
+ response_content = await call_mcp(tool_name, args, mcp_server_url)
+ else:
+ collection = db["projects"]
+ doc = collection.find_one({"_id": complete_request.get("projectId", "")})
+ signing_secret = doc.get("secret", "")
+ webhook_url = complete_request.get("toolWebhookUrl", "")
+ response_content = await call_webhook(tool_name, args, webhook_url, signing_secret)
+ return response_content
+
+
+def get_rag_tool(config: dict, complete_request: dict) -> FunctionTool:
+ """
+ Creates a RAG tool based on the provided configuration.
+ """
+ project_id = complete_request.get("projectId", "")
+ if config.get("ragDataSources", None):
+ print("getArticleInfo")
+ params = {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "The query to search for"
+ }
+ },
+ "additionalProperties": False,
+ "required": [
+ "query"
+ ]
+ }
+ tool = FunctionTool(
+ name="getArticleInfo",
+ description="Get information about an article",
+ params_json_schema=params,
+ on_invoke_tool=lambda ctx, args: call_rag_tool(project_id, json.loads(args)['query'], config.get("ragDataSources", []), "chunks", 3)
+ )
+ return tool
+ else:
+ return None
+
+
+
+def get_agents(agent_configs, tool_configs, complete_request):
+ """
+ Creates and initializes Agent objects based on their configurations and connections.
+ """
+ if not isinstance(agent_configs, list):
+ raise ValueError("Agents config is not a list in get_agents")
+ if not isinstance(tool_configs, list):
+ raise ValueError("Tools config is not a list in get_agents")
+
+ new_agents = []
+ new_agent_to_children = {}
+ new_agent_name_to_index = {}
+ # Create Agent objects from config
+ for agent_config in agent_configs:
+ logger.debug(f"Processing config for agent: {agent_config['name']}")
+ print("="*100)
+ print(f"Processing config for agent: {agent_config['name']}")
+
+ # If hasRagSources, append the RAG tool to the agent's tools
+ if agent_config.get("hasRagSources", False):
+ rag_tool_name = get_tool_config_by_type(tool_configs, "rag").get("name", "")
+ agent_config["tools"].append(rag_tool_name)
+ agent_config = add_rag_instructions_to_agent(agent_config, rag_tool_name)
+
+ # Prepare tool lists for this agent
+ external_tools = []
+
+ logger.debug(f"Agent {agent_config['name']} has {len(agent_config['tools'])} configured tools")
+ print(f"Agent {agent_config['name']} has {len(agent_config['tools'])} configured tools")
+
+ new_tools = []
+ rag_tool = get_rag_tool(agent_config, complete_request)
+ if rag_tool:
+ new_tools.append(rag_tool)
+ logger.debug(f"Added rag tool to agent {agent_config['name']}")
+ print(f"Added rag tool to agent {agent_config['name']}")
+
+ for tool_name in agent_config["tools"]:
+
+ tool_config = get_tool_config_by_name(tool_configs, tool_name)
+
+ if tool_config:
+ external_tools.append({
+ "type": "function",
+ "function": tool_config
+ })
+ #TODO: Remove this once we have a way to handle the additionalProperties
+ tool_config['parameters']['additionalProperties'] = False
+ tool = FunctionTool(
+ name=tool_name,
+ description=tool_config["description"],
+ params_json_schema=tool_config["parameters"],
+ on_invoke_tool=lambda ctx, args, _tool_name=tool_name, _tool_config=tool_config, _complete_request=complete_request:
+ catch_all(ctx, args, _tool_name, _tool_config, _complete_request)
+ )
+ new_tools.append(tool)
+ logger.debug(f"Added tool {tool_name} to agent {agent_config['name']}")
+ print(f"Added tool {tool_name} to agent {agent_config['name']}")
+ else:
+ logger.warning(f"Tool {tool_name} not found in tool_configs")
+ print(f"WARNING: Tool {tool_name} not found in tool_configs")
+
+ # Create the agent object
+ logger.debug(f"Creating Agent object for {agent_config['name']}")
+ print(f"Creating Agent object for {agent_config['name']}")
+ try:
+ new_agent = NewAgent(
+ name=agent_config["name"],
+ instructions=agent_config["instructions"],
+ handoff_description=agent_config["description"],
+ tools=new_tools,
+ model=agent_config["model"],
+ model_settings=ModelSettings(temperature=0.0)
+ )
+
+ new_agent_to_children[agent_config["name"]] = agent_config.get("connectedAgents", [])
+ new_agent_name_to_index[agent_config["name"]] = len(new_agents)
+ new_agents.append(new_agent)
+ logger.debug(f"Successfully created agent: {agent_config['name']}")
+ print(f"Successfully created agent: {agent_config['name']}")
+ except Exception as e:
+ logger.error(f"Failed to create agent {agent_config['name']}: {str(e)}")
+ print(f"ERROR: Failed to create agent {agent_config['name']}: {str(e)}")
+ raise
+
+ for new_agent in new_agents:
+ # Initialize the handoffs attribute if it doesn't exist
+ if not hasattr(new_agent, 'handoffs'):
+ new_agent.handoffs = []
+ # Look up the agent's children from the old agent and create a list called handoffs in new_agent with pointers to the children in new_agents
+ new_agent.handoffs = [new_agents[new_agent_name_to_index[child]] for child in new_agent_to_children[new_agent.name]]
+
+ print("Returning created agents")
+ print("="*100)
+ return new_agents
+
+
+def create_response(messages=None, tokens_used=None, agent=None, error_msg=''):
+ """
+ Create a Response object with the given parameters.
+
+ Args:
+ messages: List of messages
+ tokens_used: Dictionary tracking token usage
+ agent: The agent that generated the response
+ error_msg: Error message if any
+
+ Returns:
+ Response object
+ """
+ if messages is None:
+ messages = []
+ if tokens_used is None:
+ tokens_used = {}
+
+ return NewResponse(
+ messages=messages,
+ agent=agent,
+ tokens_used=tokens_used,
+ error_msg=error_msg
+ )
+
+
+async def run(
+ agent,
+ messages,
+ external_tools=None,
+ tokens_used=None
+):
+ """
+ Wrapper function for initializing and running the Swarm client.
+ """
+ logger.info(f"Initializing Swarm client for agent: {agent.name}")
+ print(f"Initializing Swarm client for agent: {agent.name}")
+
+ # Initialize default parameters
+ if external_tools is None:
+ external_tools = []
+ if tokens_used is None:
+ tokens_used = {}
+
+ # Format messages to ensure they're compatible with the OpenAI API
+ formatted_messages = []
+ for msg in messages:
+ if isinstance(msg, dict) and "content" in msg:
+ formatted_msg = {
+ "role": msg.get("role", "user"),
+ "content": msg["content"]
+ }
+ formatted_messages.append(formatted_msg)
+ else:
+ formatted_messages.append({
+ "role": "user",
+ "content": str(msg)
+ })
+
+ logger.info("Beginning Swarm run")
+ print("Beginning Swarm run")
+
+ try:
+ response = await Runner.run(agent, formatted_messages)
+ except Exception as e:
+ logger.error(f"Error during run: {str(e)}")
+ print(f"Error during run: {str(e)}")
+ raise
+
+ logger.info(f"Completed Swarm run for agent: {agent.name}")
+ print(f"Completed Swarm run for agent: {agent.name}")
+ return response
+
+async def run_streamed(
+ agent,
+ messages,
+ external_tools=None,
+ tokens_used=None
+):
+ """
+ Wrapper function for initializing and running the Swarm client in streaming mode.
+ """
+ logger.info(f"Initializing Swarm streaming client for agent: {agent.name}")
+ print(f"Initializing Swarm streaming client for agent: {agent.name}")
+
+ # Initialize default parameters
+ if external_tools is None:
+ external_tools = []
+ if tokens_used is None:
+ tokens_used = {}
+
+ # Format messages to ensure they're compatible with the OpenAI API
+ formatted_messages = []
+ for msg in messages:
+ if isinstance(msg, dict) and "content" in msg:
+ formatted_msg = {
+ "role": msg.get("role", "user"),
+ "content": msg["content"]
+ }
+ formatted_messages.append(formatted_msg)
+ else:
+ formatted_messages.append({
+ "role": "user",
+ "content": str(msg)
+ })
+
+ logger.info("Beginning Swarm streaming run")
+ print("Beginning Swarm streaming run")
+
+ try:
+ # Use the Runner.run_streamed method
+ stream_result = Runner.run_streamed(agent, formatted_messages)
+ return stream_result
+ except Exception as e:
+ logger.error(f"Error during streaming run: {str(e)}")
+ print(f"Error during streaming run: {str(e)}")
+ raise
\ No newline at end of file
diff --git a/apps/rowboat_agents/src/graph/tool_calling.py b/apps/rowboat_agents/src/graph/tool_calling.py
new file mode 100644
index 00000000..cc3ae012
--- /dev/null
+++ b/apps/rowboat_agents/src/graph/tool_calling.py
@@ -0,0 +1,146 @@
+from bson.objectid import ObjectId
+from openai import OpenAI
+import os
+from motor.motor_asyncio import AsyncIOMotorClient
+import asyncio
+from dataclasses import dataclass
+from typing import Dict, List, Any
+from qdrant_client import QdrantClient
+import json
+# Initialize MongoDB client
+mongo_uri = os.environ.get("MONGODB_URI", "mongodb://localhost:27017")
+mongo_client = AsyncIOMotorClient(mongo_uri)
+db = mongo_client.rowboat
+data_sources_collection = db['sources']
+data_source_docs_collection = db['source_docs']
+
+
+qdrant_client = QdrantClient(
+ url=os.environ.get("QDRANT_URL"),
+ api_key=os.environ.get("QDRANT_API_KEY") or None
+)
+# Initialize OpenAI client
+client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
+
+# Define embedding model
+embedding_model = "text-embedding-3-small"
+
+async def embed(model: str, value: str) -> dict:
+ """
+ Generate embeddings using OpenAI's embedding models.
+
+ Args:
+ model (str): The embedding model to use (e.g., "text-embedding-3-small").
+ value (str): The text to embed.
+
+ Returns:
+ dict: A dictionary containing the embedding.
+ """
+ response = client.embeddings.create(
+ model=model,
+ input=value
+ )
+ return {"embedding": response.data[0].embedding}
+
+async def call_rag_tool(
+ project_id: str,
+ query: str,
+ source_ids: list[str],
+ return_type: str,
+ k: int,
+) -> dict:
+ """
+ Runs the RAG tool call to retrieve information based on the query and source IDs.
+
+ Args:
+ project_id (str): The ID of the project.
+ query (str): The query string to search for.
+ source_ids (list[str]): List of source IDs to filter the search.
+ return_type (str): The type of return, e.g., 'chunks' or other.
+ k (int): The number of results to return.
+
+ Returns:
+ dict: A dictionary containing the results of the search.
+ """
+
+ print("\n\n calling rag tool \n\n")
+ print(query)
+ # Create embedding for the query
+ embed_result = await embed(model=embedding_model, value=query)
+
+ # print(embed_result)
+ # Fetch all active data sources for this project
+ sources = await data_sources_collection.find({
+ "projectId": project_id,
+ "active": True
+ }).to_list(length=None)
+
+ print(sources)
+ # Filter sources to those in source_ids
+ valid_source_ids = [
+ str(s["_id"]) for s in sources if str(s["_id"]) in source_ids
+ ]
+
+ print(valid_source_ids)
+ # If no valid sources are found, return empty results
+ if not valid_source_ids:
+ return ''
+
+ # Perform Qdrant vector search
+ qdrant_results = qdrant_client.search(
+ collection_name="embeddings",
+ query_vector=embed_result["embedding"],
+ query_filter={
+ "must": [
+ {"key": "projectId", "match": {"value": project_id}},
+ {"key": "sourceId", "match": {"any": valid_source_ids}},
+ ]
+ },
+ limit=k,
+ with_payload=True
+ )
+
+ # Map the Qdrant results to the desired format
+ results = [
+ {
+ "title": point.payload["title"],
+ "name": point.payload["name"],
+ "content": point.payload["content"],
+ "docId": point.payload["docId"],
+ "sourceId": point.payload["sourceId"],
+ }
+ for point in qdrant_results
+ ]
+
+ print(return_type)
+ print(results)
+ # If return_type is 'chunks', return the results directly
+ if return_type == "chunks":
+ return json.dumps({"Information": results}, indent=2)
+
+ # Otherwise, fetch the full document contents from MongoDB
+ doc_ids = [ObjectId(r["docId"]) for r in results]
+ docs = await data_source_docs_collection.find({"_id": {"$in": doc_ids}}).to_list(length=None)
+
+ # Create a dictionary for quick lookup of documents by their string ID
+ doc_dict = {str(doc["_id"]): doc for doc in docs}
+
+ # Update the results with the full document content
+ results = [
+ {**r, "content": doc_dict.get(r["docId"], {}).get("content", "")}
+ for r in results
+ ]
+
+ # Convert results to a JSON string
+ formatted_string = json.dumps({"Information": results}, indent=2)
+ print(formatted_string)
+ return formatted_string
+
+
+if __name__ == "__main__":
+ asyncio.run(call_rag_tool(
+ project_id="faf2bfb3-41d4-4299-b0d2-048581ea9bd8",
+ query="What is the range on your scooter",
+ source_ids=["67e102c9fab4514d7aaeb5a4"],
+ return_type="docs",
+ k=3))
\ No newline at end of file
diff --git a/apps/agents/src/graph/tools.py b/apps/rowboat_agents/src/graph/tools.py
similarity index 100%
rename from apps/agents/src/graph/tools.py
rename to apps/rowboat_agents/src/graph/tools.py
diff --git a/apps/agents/src/graph/types.py b/apps/rowboat_agents/src/graph/types.py
similarity index 87%
rename from apps/agents/src/graph/types.py
rename to apps/rowboat_agents/src/graph/types.py
index 3730be11..32c827fb 100644
--- a/apps/agents/src/graph/types.py
+++ b/apps/rowboat_agents/src/graph/types.py
@@ -11,8 +11,8 @@ class ControlType(Enum):
class PromptType(Enum):
STYLE = "style_prompt"
+ GREETING = "greeting"
class ErrorType(Enum):
FATAL = "fatal"
- ESCALATE = "escalate"
-
+ ESCALATE = "escalate"
\ No newline at end of file
diff --git a/apps/agents/src/utils/__init__.py b/apps/rowboat_agents/src/utils/__init__.py
similarity index 100%
rename from apps/agents/src/utils/__init__.py
rename to apps/rowboat_agents/src/utils/__init__.py
diff --git a/apps/agents/src/utils/common.py b/apps/rowboat_agents/src/utils/common.py
similarity index 99%
rename from apps/agents/src/utils/common.py
rename to apps/rowboat_agents/src/utils/common.py
index 83a0cf7f..57c1c58c 100644
--- a/apps/agents/src/utils/common.py
+++ b/apps/rowboat_agents/src/utils/common.py
@@ -4,7 +4,6 @@ import os
import subprocess
import sys
import time
-from collections import defaultdict
from dotenv import load_dotenv
from openai import OpenAI
diff --git a/apps/agents/tests/__init__.py b/apps/rowboat_agents/tests/__init__.py
similarity index 100%
rename from apps/agents/tests/__init__.py
rename to apps/rowboat_agents/tests/__init__.py
diff --git a/apps/agents/tests/app_client.py b/apps/rowboat_agents/tests/app_client.py
similarity index 100%
rename from apps/agents/tests/app_client.py
rename to apps/rowboat_agents/tests/app_client.py
diff --git a/apps/rowboat_agents/tests/app_client_streaming.py b/apps/rowboat_agents/tests/app_client_streaming.py
new file mode 100644
index 00000000..8759fff7
--- /dev/null
+++ b/apps/rowboat_agents/tests/app_client_streaming.py
@@ -0,0 +1,174 @@
+from src.utils.common import common_logger, read_json_from_file
+import requests
+import json
+import argparse
+from datetime import datetime
+
+logger = common_logger
+logger.info("Running app_client_streaming.py")
+
+def preprocess_messages(messages):
+ # Preprocess messages to handle null content and role issues
+ for msg in messages:
+ # Handle null content in assistant messages with tool calls
+ if (msg.get("role") == "assistant" and
+ msg.get("content") is None and
+ msg.get("tool_calls") is not None and
+ len(msg.get("tool_calls")) > 0):
+ msg["content"] = "Calling tool"
+
+ # Handle role issues
+ if msg.get("role") == "tool":
+ msg["role"] = "developer"
+ elif not msg.get("role"):
+ msg["role"] = "user"
+
+ return messages
+
+def stream_chat(host, request_data, api_key):
+ start_time = datetime.now()
+ print("\n" + "="*80)
+ print(f"Starting streaming chat at {start_time}")
+ print(f"Host: {host}")
+ print("="*80 + "\n")
+
+ # First, initialize the stream
+ try:
+ print("\n" + "-"*80)
+ print("Initializing stream...")
+ init_response = requests.post(
+ f"{host}/chat_stream_init",
+ json=request_data,
+ headers={'Authorization': f'Bearer {api_key}'}
+ )
+ print(f"Init Response Status: {init_response.status_code}")
+ print(f"Init Response Text: {init_response.text}")
+ print("-"*80 + "\n")
+
+ if init_response.status_code != 200:
+ logger.error(f"Error initializing stream. Status code: {init_response.status_code}")
+ logger.error(f"Response: {init_response.text}")
+ return
+
+ init_data = init_response.json()
+
+ if 'error' in init_data:
+ logger.error(f"Error initializing stream: {init_data['error']}")
+ return
+
+ stream_id = init_data['stream_id']
+ print(f"Stream initialized successfully with ID: {stream_id}")
+
+ except requests.exceptions.RequestException as e:
+ logger.error(f"Request error during stream initialization: {e}")
+ return
+ except json.JSONDecodeError as e:
+ logger.error(f"Failed to decode JSON response: {e}")
+ logger.error(f"Raw response: {init_response.text}")
+ return
+
+ # Now connect to the stream
+ try:
+ print("\n" + "-"*80)
+ print(f"Connecting to stream {stream_id}...")
+ stream_response = requests.get(
+ f"{host}/chat_stream/{stream_id}",
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'Accept': 'text/event-stream'
+ },
+ stream=True
+ )
+
+ if stream_response.status_code != 200:
+ logger.error(f"Error connecting to stream. Status code: {stream_response.status_code}")
+ logger.error(f"Response: {stream_response.text}")
+ return
+
+ print(f"Successfully connected to stream")
+ print("-"*80 + "\n")
+
+ event_count = 0
+ current_data = []
+
+ try:
+ print("\n" + "-"*80)
+ print("Starting to process events...")
+ print("-"*80 + "\n")
+
+ for line in stream_response.iter_lines(decode_unicode=True):
+ if line:
+ if line.startswith('data: '):
+ data = line[6:] # Remove 'data: ' prefix
+ try:
+ event_data = json.loads(data)
+ event_count += 1
+ print("\n" + "*"*80)
+ print(f"Event #{event_count}")
+
+ if isinstance(event_data, dict):
+ # Pretty print the event data
+ print("Event Data:")
+ print(json.dumps(event_data, indent=2))
+
+ # Special handling for message events
+ if 'content' in event_data:
+ print("\nMessage Content:", event_data['content'])
+ if event_data.get('tool_calls'):
+ print("Tool Calls:", json.dumps(event_data['tool_calls'], indent=2))
+ else:
+ print("Event Data:", event_data)
+ print("*"*80 + "\n")
+
+ except json.JSONDecodeError as e:
+ print(f"Error decoding event data: {e}")
+ print(f"Raw data: {data}")
+
+ except Exception as e:
+ print(f"Error processing stream: {e}")
+ import traceback
+ traceback.print_exc()
+ finally:
+ print("\n" + "-"*80)
+ print(f"Closing stream after processing {event_count} events")
+ print("-"*80 + "\n")
+ stream_response.close()
+
+ except requests.exceptions.RequestException as e:
+ print(f"Request error during streaming: {e}")
+ import traceback
+ traceback.print_exc()
+
+ end_time = datetime.now()
+ duration = end_time - start_time
+ print("\n" + "="*80)
+ print(f"Streaming session completed at {end_time}")
+ print(f"Total duration: {duration}")
+ print("="*80 + "\n")
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--sample_request', type=str, required=False, default='tmp.json',
+ help='Sample request JSON file name under tests/sample_requests/')
+ parser.add_argument('--api_key', type=str, required=False, default='test',
+ help='API key to use for authentication')
+ parser.add_argument('--host', type=str, default='http://localhost:4040',
+ help='Host to use for the request')
+ args = parser.parse_args()
+
+ try:
+ print("\n" + "="*80)
+ print("Loading request data...")
+ request = read_json_from_file(f"./tests/sample_requests/{args.sample_request}").get("lastRequest", {})
+ print("Request data:")
+ print(json.dumps(request, indent=2))
+ print("Starting streaming request...")
+ print("="*80 + "\n")
+
+ stream_chat(args.host, request, args.api_key)
+ except Exception as e:
+ print("\n" + "!"*80)
+ print(f"Error in main: {e}")
+ import traceback
+ traceback.print_exc()
+ print("!"*80 + "\n")
diff --git a/apps/agents/tests/interactive.py b/apps/rowboat_agents/tests/interactive.py
similarity index 57%
rename from apps/agents/tests/interactive.py
rename to apps/rowboat_agents/tests/interactive.py
index 1cf6dcdc..ace2c608 100644
--- a/apps/agents/tests/interactive.py
+++ b/apps/rowboat_agents/tests/interactive.py
@@ -2,12 +2,76 @@ import copy
from datetime import datetime
import json
import sys
+import asyncio
-from src.graph.core import run_turn, order_messages
+from src.graph.core import order_messages, run_turn_streamed
from src.graph.tools import respond_to_tool_raise_error, respond_to_tool_close_chat, RAG_TOOL, CLOSE_CHAT_TOOL
from src.utils.common import common_logger, read_json_from_file
logger = common_logger
+def preprocess_messages(messages):
+ # Preprocess messages to handle null content and role issues
+ for msg in messages:
+ # Handle null content in assistant messages with tool calls
+ if (msg.get("role") == "assistant" and
+ msg.get("content") is None and
+ msg.get("tool_calls") is not None and
+ len(msg.get("tool_calls")) > 0):
+ msg["content"] = "Calling tool"
+
+ # Handle role issues
+ if msg.get("role") == "tool":
+ msg["role"] = "developer"
+ elif not msg.get("role"):
+ msg["role"] = "user"
+
+ return messages
+
+async def process_turn(messages, agent_configs, tool_configs, prompt_configs, start_agent_name, state, config, complete_request):
+ """Processes a single turn using streaming API"""
+ print(f"\n{'*'*50}\nLatest Request:\n{'*'*50}")
+ request_json = {
+ "messages": [{k: v for k, v in msg.items() if k != 'current_turn'} for msg in messages],
+ "state": state,
+ "agents": agent_configs,
+ "tools": tool_configs,
+ "prompts": prompt_configs,
+ "startAgent": start_agent_name
+ }
+ print(json.dumps(request_json, indent=2))
+
+ collected_messages = []
+
+ async for event_type, event_data in run_turn_streamed(
+ messages=messages,
+ start_agent_name=start_agent_name,
+ agent_configs=agent_configs,
+ tool_configs=tool_configs,
+ start_turn_with_start_agent=config.get("start_turn_with_start_agent", False),
+ state=state,
+ additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL],
+ complete_request=complete_request
+ ):
+ if event_type == "message":
+ # Add each message to collected_messages
+ collected_messages.append(event_data)
+
+ elif event_type == "done":
+ print(f"\n\n{'*'*50}\nLatest Response:\n{'*'*50}")
+ response_json = {
+ "messages": collected_messages,
+ "state": event_data.get('state', {}),
+ }
+ print("Turn completed. Here are the streamed messages and final state:")
+ print(json.dumps(response_json, indent=2))
+ print('='*50)
+
+ return collected_messages, event_data.get('state', {})
+
+ elif event_type == "error":
+ print(f"\nError: {event_data.get('error', 'Unknown error')}")
+ return [], state
+
if __name__ == "__main__":
logger.info(f"{'*'*50}Running interactive mode{'*'*50}")
@@ -27,6 +91,9 @@ if __name__ == "__main__":
config_file = sys.argv[sys.argv.index("--config") + 1] if "--config" in sys.argv else "default_config.json"
sample_request_file = sys.argv[sys.argv.index("--sample_request") + 1] if "--sample_request" in sys.argv else "default_example.json"
+ print(f"Config file: {config_file}")
+ print(f"Sample request file: {sample_request_file}")
+
config = read_json_from_file(f"./configs/{config_file}")
example_request = read_json_from_file(f"./tests/sample_requests/{sample_request_file}").get("lastRequest", {})
@@ -71,48 +138,25 @@ if __name__ == "__main__":
break
logger.info("Added user message to conversation")
- print(f"\n{'*'*50}\nLatest Request:\n{'*'*50}")
- request_json = {
- "messages": [{k: v for k, v in msg.items() if k != 'current_turn'} for msg in messages],
- "state": state,
- "agents": agent_configs,
- "tools": tool_configs,
- "prompts": prompt_configs,
- "startAgent": start_agent_name
- }
- print(json.dumps(request_json, indent=2))
+ # Preprocess messages to replace role tool with role developer and add role user to empty roles
+ print("Preprocessing messages to replace role tool with role developer and add role user to empty roles")
+ messages = preprocess_messages(messages)
+ complete_request["messages"] = preprocess_messages(complete_request["messages"])
- resp_messages, resp_tokens_used, resp_state = run_turn(
+ # Run the streaming turn
+ resp_messages, resp_state = asyncio.run(process_turn(
messages=messages,
- start_agent_name=start_agent_name,
agent_configs=agent_configs,
tool_configs=tool_configs,
- return_diff_messages=config.get("return_diff_messages", True),
prompt_configs=prompt_configs,
- start_turn_with_start_agent=config.get("start_turn_with_start_agent", False),
- children_aware_of_parent=config.get("children_aware_of_parent", False),
- parent_has_child_history=config.get("parent_has_child_history", True),
+ start_agent_name=start_agent_name,
state=state,
- additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL],
- error_tool_call=config.get("error_tool_call", True),
- max_messages_per_turn=config.get("max_messages_per_turn", 10),
- max_messages_per_error_escalation_turn=config.get("max_messages_per_error_escalation_turn", 4),
- escalate_errors=config.get("escalate_errors", True),
- max_overall_turns=config.get("max_overall_turns", 10)
- )
- state = resp_state
- resp_messages = order_messages(resp_messages)
-
- print(f"\n{'*'*50}\nLatest Response:\n{'*'*50}")
- response_json = {
- "messages": resp_messages,
- "state": state,
- "tokens_used": resp_tokens_used
- }
- print(json.dumps(response_json, indent=2))
+ config=config,
+ complete_request=complete_request
+ ))
- last_msg = resp_messages[-1]
- print(f"\nBOT: {last_msg}\n")
+ state = resp_state
+ last_msg = resp_messages[-1] if resp_messages else {}
tool_calls = last_msg.get("tool_calls", [])
sender = last_msg.get("sender", "")
@@ -153,7 +197,9 @@ if __name__ == "__main__":
else:
user_input_needed = True
- print(f"Turn Duration: {round((datetime.now() - turn_start_time).total_seconds() * 10) / 10:.1f}s\n")
- print(f"Tool Response Duration: {round(tool_duration * 10) / 10:.1f}s\n")
+ print("Quick stats")
+ print(f"Turn Duration: {round((datetime.now() - turn_start_time).total_seconds() * 10) / 10:.1f}s")
+ print(f"Tool Response Duration: {round(tool_duration * 10) / 10:.1f}s")
+ print('='*50)
print("\n" + "-" * 80)
\ No newline at end of file
diff --git a/apps/rowboat_agents/tests/sample_requests/default_example.json b/apps/rowboat_agents/tests/sample_requests/default_example.json
new file mode 100644
index 00000000..8fc308c6
--- /dev/null
+++ b/apps/rowboat_agents/tests/sample_requests/default_example.json
@@ -0,0 +1,235 @@
+{
+ "lastRequest": {
+ "messages": [
+ {
+ "content": "",
+ "role": "system",
+ "sender": null,
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null
+ },
+ {
+ "content": "hi",
+ "role": "user",
+ "sender": null,
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null
+ }
+ ],
+ "state": {
+ "last_agent_name": "Door Dash Hub"
+ },
+ "agents": [
+ {
+ "name": "Door Dash Hub",
+ "type": "conversation",
+ "description": "Hub agent to manage Door Dash-related queries.",
+ "instructions": "## 🧑💼 Role:\nYou are responsible for directing Door Dash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which Door Dash-related query they need help with (e.g., 'Are you facing issues with your order items or delivery timing?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Order Issue → [@agent:Order Issue]\n - Delayed Delivery → [@agent:Delayed Delivery]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Issues with order items\n- Delayed delivery issues\n\n❌ Out of Scope:\n- Issues unrelated to Door Dash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Direct queries to specific Door Dash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond Door Dash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with my order items.\n - **Agent actions**: [@agent:Order Issue](#mention)\n\n- **User** : My delivery is delayed.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : I'm not sure where my order is.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : Can you reset my order settings?\n - **Agent actions**: [@agent:Escalation](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": [
+ "Order Issue",
+ "Delayed Delivery",
+ "Escalation"
+ ]
+ },
+ {
+ "name": "Post process",
+ "type": "post_process",
+ "description": "",
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Escalation",
+ "type": "escalation",
+ "description": "",
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Order Issue",
+ "type": "conversation",
+ "description": "Agent to assist users with missing or incorrect order items.",
+ "instructions": "## 🧑💼 Role:\nAssist users with issues related to missing or incorrect order items.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the issue with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Handling missing or incorrect order items\n\n❌ Out of Scope:\n- Delayed delivery issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the user is aware of the order details before proceeding.\n\n🚫 Don'ts:\n- Extend the conversation beyond order issues.\n\n# Examples\n- **User** : I received the wrong item in my order.\n - **Agent response**: I can help with that. Let me fetch your order details first.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing an item.\n - **Agent response**: Let's check your order details and resolve this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I got someone else's order.\n - **Agent response**: I apologize for the mix-up. I'll fetch your order details to sort this out.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : Can you help me with a missing item?\n - **Agent response**: Certainly, I'll look into your order details right away.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : There's an issue with my order items.\n - **Agent response**: Let's verify your order details to address this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "model": "gpt-4o",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [
+ "get_order_details"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Delayed Delivery",
+ "type": "conversation",
+ "description": "Agent to assist users with delayed delivery issues.",
+ "instructions": "## 🧑💼 Role:\nAssist users with issues related to delayed delivery.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Confirm the delay with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Handling delayed delivery issues\n\n❌ Out of Scope:\n- Missing or incorrect order items\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the user is aware of the delivery status before proceeding.\n\n🚫 Don'ts:\n- Extend the conversation beyond delivery issues.\n\n# Examples\n- **User** : My delivery is late.\n - **Agent response**: I can help with that. Let me fetch your delivery status first.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Where is my order? It's delayed.\n - **Agent response**: Let's check your delivery status and resolve this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : My order hasn't arrived yet.\n - **Agent response**: I apologize for the delay. I'll fetch your delivery status to sort this out.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Can you help me with a delayed delivery?\n - **Agent response**: Certainly, I'll look into your delivery status right away.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : There's an issue with my delivery timing.\n - **Agent response**: Let's verify your delivery status to address this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "model": "gpt-4o",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [
+ "get_delivery_status"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_order_details",
+ "description": "Tool to fetch details about the user's order.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ },
+ "mockTool": true,
+ "mockInstructions": "Return a mock response for Door Dash order details."
+ },
+ {
+ "name": "get_delivery_status",
+ "description": "Tool to fetch the current status of the delivery.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ }
+ }
+ ],
+ "prompts": [
+ {
+ "name": "Style prompt",
+ "type": "style_prompt",
+ "prompt": "You should be empathetic and helpful."
+ }
+ ],
+ "startAgent": "Door Dash Hub"
+ },
+ "lastResponse": {
+ "messages": [
+ {
+ "annotations": [],
+ "content": "Hello! How can I assist you today? Are you facing issues with your order items or delivery timing?",
+ "created_at": "2025-03-19T12:29:06.547196",
+ "current_turn": true,
+ "response_type": "internal",
+ "role": "assistant",
+ "sender": "Door Dash Hub"
+ },
+ {
+ "annotations": [],
+ "content": "Hi! How can I help you today? Are you having issues with your order items or delivery timing?",
+ "created_at": "2025-03-19T12:29:06.547196",
+ "current_turn": true,
+ "response_type": "external",
+ "role": "assistant",
+ "sender": "Door Dash Hub >> Post process"
+ }
+ ],
+ "state": {
+ "agent_data": [
+ {
+ "child_functions": [
+ "transfer_to_escalation",
+ "transfer_to_order_issue",
+ "transfer_to_delayed_delivery"
+ ],
+ "external_tools": [],
+ "history": [
+ {
+ "content": "hi",
+ "current_turn": true,
+ "role": "user"
+ },
+ {
+ "annotations": [],
+ "content": "Hello! How can I assist you today? Are you facing issues with your order items or delivery timing?",
+ "created_at": "2025-03-19T12:29:06.547196",
+ "current_turn": true,
+ "response_type": "internal",
+ "role": "assistant",
+ "sender": "Door Dash Hub"
+ }
+ ],
+ "instructions": "## 🧑💼 Role:\nYou are responsible for directing Door Dash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which Door Dash-related query they need help with (e.g., 'Are you facing issues with your order items or delivery timing?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Order Issue → [@agent:Order Issue]\n - Delayed Delivery → [@agent:Delayed Delivery]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Issues with order items\n- Delayed delivery issues\n\n❌ Out of Scope:\n- Issues unrelated to Door Dash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Direct queries to specific Door Dash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond Door Dash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with my order items.\n - **Agent actions**: [@agent:Order Issue](#mention)\n\n- **User** : My delivery is delayed.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : I'm not sure where my order is.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : Can you reset my order settings?\n - **Agent actions**: [@agent:Escalation](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?\n\n----------------------------------------------------------------------------------------------------\n\n\n# Instructions about using other specialized agents\nYou have the following specialized agents that you can transfer the chat to, using the appropriate tool calls for the transfer: \nName: Escalation\nDescription: \nTool for transfer: transfer_to_escalation\n----------------------------------------------------------------------------------------------------\nName: Order Issue\nDescription: Agent to assist users with missing or incorrect order items.\nTool for transfer: transfer_to_order_issue\n----------------------------------------------------------------------------------------------------\nName: Delayed Delivery\nDescription: Agent to assist users with delayed delivery issues.\nTool for transfer: transfer_to_delayed_delivery\n\n## Notes:\n- Transfer the chat to the appropriate agent, based on the chat history and / or the user's request.\n- When you transfer the chat to another agent, you should not provide any response to the user. For example, do not say 'Transferring chat to X agent' or anything like that. Just invoke the tool call to transfer to the other agent.\n- Do NOT ever mention the existence of other agents. For example, do not say 'Please check with X agent for details regarding processing times.' or anything like that.\n- If any other agent transfers the chat to you without responding to the user, it means that they don't know how to help. Do not transfer the chat to back to the same agent in this case. In such cases, you should transfer to the escalation agent using the appropriate tool call. Never ask the user to contact support.\n\n\n----------------------------------------------------------------------------------------------------\n\n\n# Additional System-Wide Context or Instructions:\n\n",
+ "internal_tools": [],
+ "most_recent_parent_name": "",
+ "name": "Door Dash Hub",
+ "parent_function": null
+ },
+ {
+ "child_functions": [],
+ "external_tools": [],
+ "history": [],
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.\n\n----------------------------------------------------------------------------------------------------\n\n\n# Additional System-Wide Context or Instructions:\n\n",
+ "internal_tools": [],
+ "most_recent_parent_name": "",
+ "name": "Escalation",
+ "parent_function": null
+ },
+ {
+ "child_functions": [],
+ "external_tools": [
+ "get_order_details"
+ ],
+ "history": [],
+ "instructions": "## 🧑💼 Role:\nAssist users with issues related to missing or incorrect order items.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the issue with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Handling missing or incorrect order items\n\n❌ Out of Scope:\n- Delayed delivery issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the user is aware of the order details before proceeding.\n\n🚫 Don'ts:\n- Extend the conversation beyond order issues.\n\n# Examples\n- **User** : I received the wrong item in my order.\n - **Agent response**: I can help with that. Let me fetch your order details first.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing an item.\n - **Agent response**: Let's check your order details and resolve this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I got someone else's order.\n - **Agent response**: I apologize for the mix-up. I'll fetch your order details to sort this out.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : Can you help me with a missing item?\n - **Agent response**: Certainly, I'll look into your order details right away.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : There's an issue with my order items.\n - **Agent response**: Let's verify your order details to address this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n----------------------------------------------------------------------------------------------------\n\n\n# Instructions about giving up chat control\nIf you are unable to handle the chat (e.g. if it is not in your scope of instructions), you should use the tool call provided to give up control of the chat.\nTool for transfer: give_up_chat_control\n\n## Notes:\n- When you give up control of the chat, you should not provide any response to the user. Just invoke the tool call to give up control.\n\n\n----------------------------------------------------------------------------------------------------\n\n\n# Additional System-Wide Context or Instructions:\n\n",
+ "internal_tools": [],
+ "most_recent_parent_name": "",
+ "name": "Order Issue",
+ "parent_function": null
+ },
+ {
+ "child_functions": [],
+ "external_tools": [
+ "get_delivery_status"
+ ],
+ "history": [],
+ "instructions": "## 🧑💼 Role:\nAssist users with issues related to delayed delivery.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Confirm the delay with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Handling delayed delivery issues\n\n❌ Out of Scope:\n- Missing or incorrect order items\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the user is aware of the delivery status before proceeding.\n\n🚫 Don'ts:\n- Extend the conversation beyond delivery issues.\n\n# Examples\n- **User** : My delivery is late.\n - **Agent response**: I can help with that. Let me fetch your delivery status first.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Where is my order? It's delayed.\n - **Agent response**: Let's check your delivery status and resolve this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : My order hasn't arrived yet.\n - **Agent response**: I apologize for the delay. I'll fetch your delivery status to sort this out.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Can you help me with a delayed delivery?\n - **Agent response**: Certainly, I'll look into your delivery status right away.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : There's an issue with my delivery timing.\n - **Agent response**: Let's verify your delivery status to address this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n----------------------------------------------------------------------------------------------------\n\n\n# Instructions about giving up chat control\nIf you are unable to handle the chat (e.g. if it is not in your scope of instructions), you should use the tool call provided to give up control of the chat.\nTool for transfer: give_up_chat_control\n\n## Notes:\n- When you give up control of the chat, you should not provide any response to the user. Just invoke the tool call to give up control.\n\n\n----------------------------------------------------------------------------------------------------\n\n\n# Additional System-Wide Context or Instructions:\n\n",
+ "internal_tools": [],
+ "most_recent_parent_name": "",
+ "name": "Delayed Delivery",
+ "parent_function": null
+ }
+ ],
+ "last_agent_name": "Door Dash Hub"
+ },
+ "tokens_used": {
+ "openai/gpt-4o-mini": {
+ "input_tokens": 1731,
+ "output_tokens": 45
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/agents/tests/sample_requests/example1.json b/apps/rowboat_agents/tests/sample_requests/example1.json
similarity index 100%
rename from apps/agents/tests/sample_requests/example1.json
rename to apps/rowboat_agents/tests/sample_requests/example1.json
diff --git a/apps/agents/tests/sample_requests/example2.json b/apps/rowboat_agents/tests/sample_requests/example2.json
similarity index 100%
rename from apps/agents/tests/sample_requests/example2.json
rename to apps/rowboat_agents/tests/sample_requests/example2.json
diff --git a/apps/agents/tests/sample_requests/example3.json b/apps/rowboat_agents/tests/sample_requests/example3.json
similarity index 100%
rename from apps/agents/tests/sample_requests/example3.json
rename to apps/rowboat_agents/tests/sample_requests/example3.json
diff --git a/apps/rowboat_agents/tests/sample_requests/tmp1.json b/apps/rowboat_agents/tests/sample_requests/tmp1.json
new file mode 100644
index 00000000..9bae6e74
--- /dev/null
+++ b/apps/rowboat_agents/tests/sample_requests/tmp1.json
@@ -0,0 +1,176 @@
+{
+ "lastRequest": {
+ "messages": [
+ {
+ "role": "system",
+ "content": ""
+ },
+ {
+ "role": "user",
+ "content": "hi"
+ },
+ {
+ "role": "assistant",
+ "sender": "Door Dash Hub",
+ "content": "Hello! Are you facing issues with your order items or delivery timing? How can I assist you today?",
+ "created_at": "2025-03-24T17:33:27.564940"
+ },
+ {
+ "role": "user",
+ "content": "my order is missing fries"
+ },
+ {
+ "content": "Agent changed to Door Dash Hub",
+ "role": "assistant",
+ "sender": "Door Dash Hub",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "Agent changed to Order Issue",
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "I can help with that. Could you please provide your order ID so I can check the details?",
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null,
+ "response_type": "external"
+ },
+ {
+ "role": "user",
+ "content": "12312"
+ }
+ ],
+ "state": {
+ "last_agent_name": "Order Issue",
+ "tokens": {
+ "total": 1521,
+ "prompt": 1486,
+ "completion": 35
+ }
+ },
+ "agents": [
+ {
+ "name": "Door Dash Hub",
+ "type": "conversation",
+ "description": "Hub agent to manage Door Dash-related queries.",
+ "instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nYou are responsible for directing Door Dash-related queries to appropriate agents.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Greet the user and ask which Door Dash-related query they need help with (e.g., 'Are you facing issues with your order items or delivery timing?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Order Issue \u2192 [@agent:Order Issue]\n - Delayed Delivery \u2192 [@agent:Delayed Delivery]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Issues with order items\n- Delayed delivery issues\n\n\u274c Out of Scope:\n- Issues unrelated to Door Dash\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Direct queries to specific Door Dash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n\ud83d\udeab Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond Door Dash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with my order items.\n - **Agent actions**: [@agent:Order Issue](#mention)\n\n- **User** : My delivery is delayed.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : I'm not sure where my order is.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : Can you reset my order settings?\n - **Agent actions**: [@agent:Escalation](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": [
+ "Order Issue",
+ "Delayed Delivery",
+ "Escalation"
+ ]
+ },
+ {
+ "name": "Post process",
+ "type": "post_process",
+ "description": "",
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Escalation",
+ "type": "escalation",
+ "description": "",
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Order Issue",
+ "type": "conversation",
+ "description": "Agent to assist users with missing or incorrect order items.",
+ "instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to missing or incorrect order items.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the issue with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling missing or incorrect order items\n\n\u274c Out of Scope:\n- Delayed delivery issues\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the order details before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond order issues.\n\n# Examples\n- **User** : I received the wrong item in my order.\n - **Agent response**: I can help with that. Let me fetch your order details first.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing an item.\n - **Agent response**: Let's check your order details and resolve this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I got someone else's order.\n - **Agent response**: I apologize for the mix-up. I'll fetch your order details to sort this out.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : Can you help me with a missing item?\n - **Agent response**: Certainly, I'll look into your order details right away.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : There's an issue with my order items.\n - **Agent response**: Let's verify your order details to address this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "model": "gpt-4o",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [
+ "get_order_details"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Delayed Delivery",
+ "type": "conversation",
+ "description": "Agent to assist users with delayed delivery issues.",
+ "instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to delayed delivery.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Confirm the delay with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling delayed delivery issues\n\n\u274c Out of Scope:\n- Missing or incorrect order items\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the delivery status before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond delivery issues.\n\n# Examples\n- **User** : My delivery is late.\n - **Agent response**: I can help with that. Let me fetch your delivery status first.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Where is my order? It's delayed.\n - **Agent response**: Let's check your delivery status and resolve this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : My order hasn't arrived yet.\n - **Agent response**: I apologize for the delay. I'll fetch your delivery status to sort this out.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Can you help me with a delayed delivery?\n - **Agent response**: Certainly, I'll look into your delivery status right away.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : There's an issue with my delivery timing.\n - **Agent response**: Let's verify your delivery status to address this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "model": "gpt-4o",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [
+ "get_delivery_status"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_order_details",
+ "description": "Tool to fetch details about the user's order.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ },
+ "mockTool": true,
+ "mockInstructions": "Return a mock response for Door Dash order details."
+ },
+ {
+ "name": "get_delivery_status",
+ "description": "Tool to fetch the current status of the delivery.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ }
+ }
+ ],
+ "prompts": [
+ {
+ "name": "Style prompt",
+ "type": "style_prompt",
+ "prompt": "You should be empathetic and helpful."
+ }
+ ],
+ "startAgent": "Door Dash Hub"
+ }
+
+}
\ No newline at end of file
diff --git a/apps/rowboat_agents/tests/sample_requests/tmp2.json b/apps/rowboat_agents/tests/sample_requests/tmp2.json
new file mode 100644
index 00000000..c0055e7d
--- /dev/null
+++ b/apps/rowboat_agents/tests/sample_requests/tmp2.json
@@ -0,0 +1,223 @@
+{
+ "lastRequest": {
+ "messages": [
+ {
+ "role": "system",
+ "content": ""
+ },
+ {
+ "role": "user",
+ "content": "hi"
+ },
+ {
+ "role": "assistant",
+ "sender": "Door Dash Hub",
+ "content": "Hello! Are you facing issues with your order items or delivery timing? How can I assist you today?",
+ "created_at": "2025-03-24T17:33:27.564940"
+ },
+ {
+ "role": "user",
+ "content": "my order is missing fries"
+ },
+ {
+ "content": "Agent changed to Door Dash Hub",
+ "role": "assistant",
+ "sender": "Door Dash Hub",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "Agent changed to Order Issue",
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "Let's resolve this issue by checking your order details. Could you please provide the order ID?",
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null,
+ "response_type": "external"
+ },
+ {
+ "role": "user",
+ "content": "123412"
+ },
+ {
+ "content": "Agent changed to Order Issue",
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "response_type": "internal"
+ },
+ {
+ "content": null,
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": [
+ {
+ "function": {
+ "name": "get_order_details",
+ "arguments": "{\"order_id\":\"123412\"}"
+ },
+ "id": "fc_67e37c622f208192aceec557fbd3125609b4eca638eb3571",
+ "type": "function"
+ }
+ ],
+ "tool_call_id": null,
+ "tool_name": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "Order Details for Order ID 123412:\n\n- **Restaurant**: Luigi's Pizzeria \n- **Items Ordered**: \n - 2x Margherita Pizza \n - 1x Garlic Bread \n - 1x Caesar Salad \n- **Estimated Delivery Time**: 7:45 PM \n- **Delivery Address**: 123 Elm Street, Apt 4B \n- **Order Status**: Out for delivery \n- **Total Amount**: $32.75 \n- **Payment Method**: Credit Card (**** **** **** 5678) \n- **Contact**: (123) 456-7890 \n- **Special Instructions**: Leave at the door.",
+ "role": "tool",
+ "sender": null,
+ "tool_calls": null,
+ "tool_call_id": "call_dqCMC5oreOoS9znDDJ7PqWha",
+ "tool_name": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "I checked your order details, and it seems the fries were not included. Could you confirm if there was an oversight in placing the order, or were they supposed to be included?",
+ "role": "assistant",
+ "sender": "Order Issue",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null,
+ "response_type": "external"
+ },
+ {
+ "role": "user",
+ "content": "Fries were supposed to be in"
+ }
+ ],
+ "state": {
+ "last_agent_name": "Order Issue",
+ "tokens": {
+ "total": 1699,
+ "prompt": 1643,
+ "completion": 56
+ }
+ },
+ "agents": [
+ {
+ "name": "Door Dash Hub",
+ "type": "conversation",
+ "description": "Hub agent to manage Door Dash-related queries.",
+ "instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nYou are responsible for directing Door Dash-related queries to appropriate agents.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Greet the user and ask which Door Dash-related query they need help with (e.g., 'Are you facing issues with your order items or delivery timing?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Order Issue \u2192 [@agent:Order Issue]\n - Delayed Delivery \u2192 [@agent:Delayed Delivery]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Issues with order items\n- Delayed delivery issues\n\n\u274c Out of Scope:\n- Issues unrelated to Door Dash\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Direct queries to specific Door Dash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n\ud83d\udeab Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond Door Dash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with my order items.\n - **Agent actions**: [@agent:Order Issue](#mention)\n\n- **User** : My delivery is delayed.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : I'm not sure where my order is.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : Can you reset my order settings?\n - **Agent actions**: [@agent:Escalation](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": [
+ "Order Issue",
+ "Delayed Delivery",
+ "Escalation"
+ ]
+ },
+ {
+ "name": "Post process",
+ "type": "post_process",
+ "description": "",
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Escalation",
+ "type": "escalation",
+ "description": "",
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "model": "gpt-4o-mini",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Order Issue",
+ "type": "conversation",
+ "description": "Agent to assist users with missing or incorrect order items.",
+ "instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to missing or incorrect order items.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the issue with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling missing or incorrect order items\n\n\u274c Out of Scope:\n- Delayed delivery issues\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the order details before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond order issues.\n\n# Examples\n- **User** : I received the wrong item in my order.\n - **Agent response**: I can help with that. Let me fetch your order details first.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing an item.\n - **Agent response**: Let's check your order details and resolve this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I got someone else's order.\n - **Agent response**: I apologize for the mix-up. I'll fetch your order details to sort this out.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : Can you help me with a missing item?\n - **Agent response**: Certainly, I'll look into your order details right away.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : There's an issue with my order items.\n - **Agent response**: Let's verify your order details to address this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "model": "gpt-4o",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [
+ "get_order_details"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Delayed Delivery",
+ "type": "conversation",
+ "description": "Agent to assist users with delayed delivery issues.",
+ "instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to delayed delivery.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Confirm the delay with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling delayed delivery issues\n\n\u274c Out of Scope:\n- Missing or incorrect order items\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the delivery status before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond delivery issues.\n\n# Examples\n- **User** : My delivery is late.\n - **Agent response**: I can help with that. Let me fetch your delivery status first.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Where is my order? It's delayed.\n - **Agent response**: Let's check your delivery status and resolve this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : My order hasn't arrived yet.\n - **Agent response**: I apologize for the delay. I'll fetch your delivery status to sort this out.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Can you help me with a delayed delivery?\n - **Agent response**: Certainly, I'll look into your delivery status right away.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : There's an issue with my delivery timing.\n - **Agent response**: Let's verify your delivery status to address this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "model": "gpt-4o",
+ "hasRagSources": false,
+ "controlType": "retain",
+ "tools": [
+ "get_delivery_status"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_order_details",
+ "description": "Tool to fetch details about the user's order.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ },
+ "mockTool": true,
+ "mockInstructions": "Return a mock response for Door Dash order details."
+ },
+ {
+ "name": "get_delivery_status",
+ "description": "Tool to fetch the current status of the delivery.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ }
+ }
+ ],
+ "prompts": [
+ {
+ "name": "Style prompt",
+ "type": "style_prompt",
+ "prompt": "You should be empathetic and helpful."
+ }
+ ],
+ "startAgent": "Door Dash Hub"
+ }
+}
\ No newline at end of file
diff --git a/apps/rowboat_agents/tests/sample_requests/tmp3.json b/apps/rowboat_agents/tests/sample_requests/tmp3.json
new file mode 100644
index 00000000..f215dc21
--- /dev/null
+++ b/apps/rowboat_agents/tests/sample_requests/tmp3.json
@@ -0,0 +1,270 @@
+{
+ "lastRequest": {
+ "projectId": "4ebd5e81-010a-4bc6-91e0-0aa98173dbac",
+ "messages": [
+ {
+ "content": "",
+ "role": "system",
+ "sender": null,
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null
+ },
+ {
+ "content": "Hello! How can I assist you with your DoorDash query today? Are you inquiring about a missing item, delivery status, or subscription details?",
+ "role": "assistant",
+ "sender": "DoorDash Support Hub",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null,
+ "response_type": "internal"
+ },
+ {
+ "content": "Hello! How can I assist you with your DoorDash query today? Are you inquiring about a missing item, delivery status, or subscription details?",
+ "role": "assistant",
+ "sender": "DoorDash Support Hub >> External",
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null,
+ "response_type": "external"
+ },
+ {
+ "content": "hi",
+ "role": "user",
+ "sender": null,
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null
+ }
+ ],
+ "state": {
+ "agent_data": [
+ {
+ "instructions": "## 🧑💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)",
+ "name": "DoorDash Support Hub"
+ },
+ {
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "name": "Post process"
+ },
+ {
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "name": "Escalation"
+ },
+ {
+ "instructions": "## 🧑💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "name": "Missing Items"
+ },
+ {
+ "instructions": "## 🧑💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "name": "Delivery Status"
+ },
+ {
+ "instructions": "## 🧑💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
+ "name": "Subscription Info"
+ }
+ ],
+ "last_agent_name": "DoorDash Support Hub"
+ },
+ "agents": [
+ {
+ "name": "DoorDash Support Hub",
+ "type": "conversation",
+ "description": "Hub agent to manage DoorDash-related queries.",
+ "instructions": "## 🧑💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)",
+ "model": "gpt-4o",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": [
+ "Missing Items",
+ "Delivery Status",
+ "Subscription Info",
+ "Escalation"
+ ]
+ },
+ {
+ "name": "Post process",
+ "type": "post_process",
+ "description": "",
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "model": "gpt-4o-mini",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Escalation",
+ "type": "escalation",
+ "description": "",
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "model": "gpt-4o-mini",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Missing Items",
+ "type": "conversation",
+ "description": "Agent to assist users with missing items in their orders.",
+ "instructions": "## 🧑💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "model": "gpt-4o",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [
+ "get_order_details"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Delivery Status",
+ "type": "conversation",
+ "description": "Agent to assist users with delivery status queries.",
+ "instructions": "## 🧑💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "model": "gpt-4o",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [
+ "get_delivery_status"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Subscription Info",
+ "type": "conversation",
+ "description": "Agent to assist users with subscription-related queries.",
+ "instructions": "## 🧑💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
+ "model": "gpt-4o",
+ "controlType": "relinquish_to_parent",
+ "ragDataSources": [
+ "67e1612510540d9027909e10"
+ ],
+ "ragK": 3,
+ "ragReturnType": "content",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_order_details",
+ "description": "Tool to fetch the user's order details.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ }
+ },
+ {
+ "name": "get_delivery_status",
+ "description": "Tool to fetch the delivery status of an order.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ },
+ "mockTool": true,
+ "mockInstructions": "Give a mock response for a door dash order delivery status."
+ }
+ ],
+ "prompts": [
+ {
+ "name": "Style prompt",
+ "type": "style_prompt",
+ "prompt": "You should be empathetic and helpful."
+ },
+ {
+ "name": "Greeting",
+ "type": "greeting",
+ "prompt": "Hello! How can I help you?"
+ }
+ ],
+ "startAgent": "DoorDash Support Hub",
+ "mcpServers": [
+ {
+ "name": "delivery",
+ "url": "http://localhost:8000/sse"
+ }
+ ],
+ "toolWebhookUrl": "http://127.0.0.1:4020/tool_call"
+ },
+ "lastResponse": {
+ "messages": [
+ {
+ "content": "Hi there! How can I help you with your DoorDash query? Are you inquiring about a missing item, delivery status, or subscription details?",
+ "created_at": null,
+ "response_type": "internal",
+ "role": "assistant",
+ "sender": "DoorDash Support Hub"
+ },
+ {
+ "content": "Hi there! How can I help you with your DoorDash query? Are you inquiring about a missing item, delivery status, or subscription details?",
+ "created_at": null,
+ "response_type": "external",
+ "role": "assistant",
+ "sender": "DoorDash Support Hub >> External"
+ }
+ ],
+ "state": {
+ "agent_data": [
+ {
+ "instructions": "## 🧑💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)",
+ "name": "DoorDash Support Hub"
+ },
+ {
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "name": "Post process"
+ },
+ {
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "name": "Escalation"
+ },
+ {
+ "instructions": "## 🧑💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "name": "Missing Items"
+ },
+ {
+ "instructions": "## 🧑💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "name": "Delivery Status"
+ },
+ {
+ "instructions": "## 🧑💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
+ "name": "Subscription Info"
+ }
+ ],
+ "last_agent_name": "DoorDash Support Hub"
+ },
+ "tokens_used": {
+ "completion": 50,
+ "prompt": 50,
+ "total": 100
+ }
+ }
+ }
\ No newline at end of file
diff --git a/apps/rowboat_agents/tests/sample_requests/tmp4.json b/apps/rowboat_agents/tests/sample_requests/tmp4.json
new file mode 100644
index 00000000..9a9ac2e0
--- /dev/null
+++ b/apps/rowboat_agents/tests/sample_requests/tmp4.json
@@ -0,0 +1,166 @@
+{
+ "lastRequest": {
+ "projectId": "4ebd5e81-010a-4bc6-91e0-0aa98173dbac",
+ "messages": [
+ {
+ "content": "",
+ "role": "system",
+ "sender": null,
+ "tool_calls": null,
+ "tool_call_id": null,
+ "tool_name": null
+ }
+ ],
+ "state": {
+ "last_agent_name": "DoorDash Support Hub"
+ },
+ "agents": [
+ {
+ "name": "DoorDash Support Hub",
+ "type": "conversation",
+ "description": "Hub agent to manage DoorDash-related queries.",
+ "instructions": "## 🧑💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)",
+ "model": "gpt-4o",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": [
+ "Missing Items",
+ "Delivery Status",
+ "Subscription Info",
+ "Escalation"
+ ]
+ },
+ {
+ "name": "Post process",
+ "type": "post_process",
+ "description": "",
+ "instructions": "Ensure that the agent response is terse and to the point.",
+ "model": "gpt-4o-mini",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Escalation",
+ "type": "escalation",
+ "description": "",
+ "instructions": "Get the user's contact information and let them know that their request has been escalated.",
+ "model": "gpt-4o-mini",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Missing Items",
+ "type": "conversation",
+ "description": "Agent to assist users with missing items in their orders.",
+ "instructions": "## 🧑💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
+ "model": "gpt-4o",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [
+ "get_order_details"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Delivery Status",
+ "type": "conversation",
+ "description": "Agent to assist users with delivery status queries.",
+ "instructions": "## 🧑💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
+ "model": "gpt-4o",
+ "controlType": "retain",
+ "ragK": 3,
+ "ragReturnType": "chunks",
+ "tools": [
+ "get_delivery_status"
+ ],
+ "prompts": [],
+ "connectedAgents": []
+ },
+ {
+ "name": "Subscription Info",
+ "type": "conversation",
+ "description": "Agent to assist users with subscription-related queries.",
+ "instructions": "## 🧑💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
+ "model": "gpt-4o",
+ "controlType": "relinquish_to_parent",
+ "ragDataSources": [
+ "67e1612510540d9027909e10"
+ ],
+ "ragK": 3,
+ "ragReturnType": "content",
+ "tools": [],
+ "prompts": [],
+ "connectedAgents": []
+ }
+ ],
+ "tools": [
+ {
+ "name": "get_order_details",
+ "description": "Tool to fetch the user's order details.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ }
+ },
+ {
+ "name": "get_delivery_status",
+ "description": "Tool to fetch the delivery status of an order.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string",
+ "description": "The unique identifier for the order."
+ }
+ },
+ "required": [
+ "order_id"
+ ]
+ },
+ "mockTool": true,
+ "mockInstructions": "Give a mock response for a door dash order delivery status."
+ }
+ ],
+ "prompts": [
+ {
+ "name": "Style prompt",
+ "type": "style_prompt",
+ "prompt": "You should be empathetic and helpful."
+ },
+ {
+ "name": "Greeting",
+ "type": "greeting",
+ "prompt": "Hello! How can I help you?"
+ }
+ ],
+ "startAgent": "DoorDash Support Hub",
+ "mcpServers": [
+ {
+ "name": "delivery",
+ "url": "http://localhost:8000/sse"
+ }
+ ],
+ "toolWebhookUrl": "http://127.0.0.1:4020/tool_call"
+ }
+}
\ No newline at end of file
diff --git a/apps/agents/tests/sample_responses/default_example.json b/apps/rowboat_agents/tests/sample_responses/default_example.json
similarity index 100%
rename from apps/agents/tests/sample_responses/default_example.json
rename to apps/rowboat_agents/tests/sample_responses/default_example.json
diff --git a/apps/simulation_runner/db.py b/apps/simulation_runner/db.py
index c8a79937..06713720 100644
--- a/apps/simulation_runner/db.py
+++ b/apps/simulation_runner/db.py
@@ -6,7 +6,6 @@ from typing import Optional
from scenario_types import (
TestRun,
TestScenario,
- TestProfile,
TestSimulation,
TestResult,
AggregateResults
@@ -14,13 +13,11 @@ from scenario_types import (
MONGO_URI = os.environ.get("MONGODB_URI", "mongodb://localhost:27017/rowboat").strip()
-# New collection names
TEST_SCENARIOS_COLLECTION = "test_scenarios"
-TEST_PROFILES_COLLECTION = "test_profiles"
TEST_SIMULATIONS_COLLECTION = "test_simulations"
TEST_RUNS_COLLECTION = "test_runs"
TEST_RESULTS_COLLECTION = "test_results"
-API_KEYS_COLLECTION = "api_keys" # If still needed
+API_KEYS_COLLECTION = "api_keys"
def get_db():
client = MongoClient(MONGO_URI)
@@ -145,6 +142,23 @@ def get_simulations_for_run(test_run: TestRun) -> list[TestSimulation]:
)
return simulations
+def get_scenario_by_id(scenario_id: str) -> TestScenario:
+ """
+ Returns a TestScenario by its ID.
+ """
+ collection = get_collection(TEST_SCENARIOS_COLLECTION)
+ doc = collection.find_one({"_id": ObjectId(scenario_id)})
+ if doc:
+ return TestScenario(
+ id=str(doc["_id"]),
+ projectId=doc["projectId"],
+ name=doc["name"],
+ description=doc["description"],
+ createdAt=doc["createdAt"],
+ lastUpdatedAt=doc["lastUpdatedAt"]
+ )
+ return None
+
#
# TestResult helpers
#
diff --git a/apps/simulation_runner/requirements.txt b/apps/simulation_runner/requirements.txt
index 8b501db2..bd0f4f9d 100644
--- a/apps/simulation_runner/requirements.txt
+++ b/apps/simulation_runner/requirements.txt
@@ -21,7 +21,7 @@ pytest==8.3.4
pytest-asyncio==0.25.3
python-dateutil==2.9.0.post0
requests==2.32.3
-rowboat==1.0.4
+rowboat==2.1.0
six==1.17.0
sniffio==1.3.1
tqdm==4.67.1
diff --git a/apps/simulation_runner/scenario_types.py b/apps/simulation_runner/scenario_types.py
index 981342eb..3e61d894 100644
--- a/apps/simulation_runner/scenario_types.py
+++ b/apps/simulation_runner/scenario_types.py
@@ -14,16 +14,6 @@ class TestScenario(BaseModel):
createdAt: datetime
lastUpdatedAt: datetime
-class TestProfile(BaseModel):
- id: str
- projectId: str
- name: str
- context: str
- createdAt: datetime
- lastUpdatedAt: datetime
- mockTools: bool
- mockPrompt: Optional[str] = None
-
class TestSimulation(BaseModel):
id: str
projectId: str
@@ -48,11 +38,7 @@ class TestRun(BaseModel):
status: RunStatus
startedAt: datetime
completedAt: Optional[datetime] = None
- # By default, store aggregate results as a dict or the typed AggregateResults
aggregateResults: Optional[AggregateResults] = None
-
- # The new schema does not mention lastHeartbeat,
- # but you can keep it if you still want to track stale runs
lastHeartbeat: Optional[datetime] = None
class TestResult(BaseModel):
@@ -61,3 +47,4 @@ class TestResult(BaseModel):
simulationId: str
result: Literal["pass", "fail"]
details: str
+ transcript: str
diff --git a/apps/simulation_runner/simulation.py b/apps/simulation_runner/simulation.py
index 67e6dfd2..39618897 100644
--- a/apps/simulation_runner/simulation.py
+++ b/apps/simulation_runner/simulation.py
@@ -5,12 +5,9 @@ import json
import os
from openai import OpenAI
-# Updated imports from your new schema/types
-from scenario_types import TestSimulation, TestResult, AggregateResults
-
-# If your DB functions changed names, adapt here:
-from db import write_test_result # replaced write_simulation_result
+from scenario_types import TestSimulation, TestResult, AggregateResults, TestScenario
+from db import write_test_result, get_scenario_by_id
from rowboat import Client, StatefulChat
openai_client = OpenAI()
@@ -18,7 +15,9 @@ MODEL_NAME = "gpt-4o"
ROWBOAT_API_HOST = os.environ.get("ROWBOAT_API_HOST", "http://127.0.0.1:3000").strip()
async def simulate_simulation(
- simulation: TestSimulation,
+ scenario: TestScenario,
+ profile_id: str,
+ pass_criteria: str,
rowboat_client: Client,
workflow_id: str,
max_iterations: int = 5
@@ -30,25 +29,20 @@ async def simulate_simulation(
"""
loop = asyncio.get_running_loop()
+ pass_criteria = pass_criteria
- # Optionally embed passCriteria in the system prompt, if it’s relevant to context:
- pass_criteria = simulation.passCriteria or ""
- # Or place it separately below if you prefer.
-
- # Prepare a Rowboat chat
+ # Todo: add profile_id
support_chat = StatefulChat(
rowboat_client,
- system_prompt=f"Context: {pass_criteria}" if pass_criteria else "",
- workflow_id=workflow_id
+ workflow_id=workflow_id,
+ test_profile_id=profile_id
)
- # You might want to describe the simulation or scenario more thoroughly.
- # Here, we just embed simulation.name in the system message:
messages = [
{
"role": "system",
"content": (
- f"Simulate the user based on this simulation:\n{simulation.name}"
+ f"You are role playing a customer talking to a chatbot (the user is role playing the chatbot). Have the following chat with the chatbot. Scenario:\n{scenario.description}. You are provided no other information. If the chatbot asks you for information that is not in context, go ahead and provide one unless stated otherwise in the scenario. Directly have the chat with the chatbot. Start now with your first message."
)
}
]
@@ -70,31 +64,39 @@ async def simulate_simulation(
)
simulated_content = simulated_user_response.choices[0].message.content.strip()
-
+ messages.append({"role": "assistant", "content": simulated_content})
# Run Rowboat chat in a thread if it's synchronous
rowboat_response = await loop.run_in_executor(
None,
lambda: support_chat.run(simulated_content)
)
- messages.append({"role": "assistant", "content": rowboat_response})
+ messages.append({"role": "user", "content": rowboat_response})
# -------------------------
# (2) EVALUATION STEP
# -------------------------
+ # swap the roles of the assistant and the user
transcript_str = ""
for m in messages:
+ if m.get("role") == "assistant":
+ m["role"] = "user"
+ elif m.get("role") == "user":
+ m["role"] = "assistant"
role = m.get("role", "unknown")
content = m.get("content", "")
transcript_str += f"{role.upper()}: {content}\n"
- # We use passCriteria as the evaluation “criteria.”
+ # Store the transcript as a JSON string
+ transcript = json.dumps(messages)
+
+ # We use passCriteria as the evaluation "criteria."
evaluation_prompt = [
{
"role": "system",
"content": (
f"You are a neutral evaluator. Evaluate based on these criteria:\n"
- f"{simulation.passCriteria}\n\n"
+ f"{pass_criteria}\n\n"
"Return ONLY a JSON object in this format:\n"
'{"verdict": "pass", "details": } or '
'{"verdict": "fail", "details": }.'
@@ -117,8 +119,6 @@ async def simulate_simulation(
model=MODEL_NAME,
messages=evaluation_prompt,
temperature=0.0,
- # If your LLM supports a structured response format, you can specify it.
- # Otherwise, remove or adapt 'response_format':
response_format={"type": "json_object"}
)
)
@@ -135,7 +135,7 @@ async def simulate_simulation(
if evaluation_result is None:
raise Exception("No 'verdict' field found in evaluation response")
- return (evaluation_result, details, transcript_str)
+ return (evaluation_result, details, transcript)
async def simulate_simulations(
simulations: List[TestSimulation],
@@ -151,10 +151,8 @@ async def simulate_simulations(
# Return an empty result if there's nothing to simulate
return AggregateResults(total=0, pass_=0, fail=0)
- # We assume all simulations belong to the same project
project_id = simulations[0].projectId
- # Create a Rowboat client instance
client = Client(
host=ROWBOAT_API_HOST,
project_id=project_id,
@@ -165,9 +163,10 @@ async def simulate_simulations(
results: List[TestResult] = []
for simulation in simulations:
- # Run each simulation
verdict, details, transcript = await simulate_simulation(
- simulation=simulation,
+ scenario=get_scenario_by_id(simulation.scenarioId),
+ profile_id=simulation.profileId,
+ pass_criteria=simulation.passCriteria,
rowboat_client=client,
workflow_id=workflow_id,
max_iterations=max_iterations
@@ -179,7 +178,8 @@ async def simulate_simulations(
runId=run_id,
simulationId=simulation.id,
result=verdict,
- details=details
+ details=details,
+ transcript=transcript
)
results.append(test_result)
diff --git a/apps/twilio_handler/.dockerignore b/apps/twilio_handler/.dockerignore
new file mode 100644
index 00000000..75f5f8c0
--- /dev/null
+++ b/apps/twilio_handler/.dockerignore
@@ -0,0 +1,2 @@
+__pycache__
+.venv/
\ No newline at end of file
diff --git a/apps/twilio_handler/.env.example b/apps/twilio_handler/.env.example
new file mode 100644
index 00000000..fdafbe37
--- /dev/null
+++ b/apps/twilio_handler/.env.example
@@ -0,0 +1,24 @@
+# Environment variables for the Voice API application
+
+# Twilio configuration
+TWILIO_ACCOUNT_SID=your_account_sid_here
+TWILIO_AUTH_TOKEN=your_auth_token_here
+BASE_URL=https://your-public-url-here.ngrok.io
+
+# RowBoat API configuration
+ROWBOAT_API_HOST=http://localhost:3000
+ROWBOAT_PROJECT_ID=your_project_id_here
+ROWBOAT_API_KEY=your_api_key_here
+
+# Speech processing APIs
+DEEPGRAM_API_KEY=your_deepgram_api_key_here
+ELEVENLABS_API_KEY=your_elevenlabs_api_key_here
+
+# Server configuration
+PORT=3009
+WHATSAPP_PORT=3010
+
+# Redis configuration for persistent state
+REDIS_URL=redis://localhost:6379/0
+REDIS_EXPIRY_SECONDS=86400
+SERVICE_NAME=rowboat-voice
\ No newline at end of file
diff --git a/apps/twilio_handler/.gitignore b/apps/twilio_handler/.gitignore
new file mode 100644
index 00000000..9f7550b1
--- /dev/null
+++ b/apps/twilio_handler/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+.venv
diff --git a/apps/twilio_handler/Dockerfile b/apps/twilio_handler/Dockerfile
new file mode 100644
index 00000000..7b5b3017
--- /dev/null
+++ b/apps/twilio_handler/Dockerfile
@@ -0,0 +1,18 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+# Copy requirements first to leverage Docker cache
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy application code
+COPY . .
+
+# Set environment variables
+ENV FLASK_APP=app
+ENV PYTHONUNBUFFERED=1
+ENV PYTHONPATH=/app
+
+# Command to run Flask development server
+CMD ["flask", "run", "--host=0.0.0.0", "--port=4010"]
\ No newline at end of file
diff --git a/apps/twilio_handler/app.py b/apps/twilio_handler/app.py
new file mode 100644
index 00000000..c0cf7f39
--- /dev/null
+++ b/apps/twilio_handler/app.py
@@ -0,0 +1,633 @@
+from flask import Flask, request, jsonify, Response
+from twilio.twiml.voice_response import VoiceResponse, Gather
+import os
+import logging
+import uuid
+from typing import Dict, Any, Optional
+import json
+from time import time
+from rowboat.schema import SystemMessage, UserMessage, ApiMessage
+import elevenlabs
+# Load environment variables
+from load_env import load_environment
+load_environment()
+
+from twilio_api import process_conversation_turn
+
+
+# Import MongoDB utility functions
+from util import (
+ get_call_state,
+ save_call_state,
+ delete_call_state,
+ get_mongodb_status,
+ get_twilio_config,
+ CallState
+)
+
+Message = SystemMessage | UserMessage
+
+ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY")
+elevenlabs_client = elevenlabs.ElevenLabs(api_key=ELEVENLABS_API_KEY)
+
+app = Flask(__name__)
+
+# Configure logging to stdout for Docker compatibility
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[logging.StreamHandler()] # Send logs to stdout
+)
+logger = logging.getLogger(__name__)
+
+# Local in-memory cache of call state (temporary cache only - not primary storage)
+# MongoDB is the primary storage for state across multiple instances
+active_calls = {}
+
+# TTS configuration
+TTS_VOICE = "Markus - Mature and Chill"
+TTS_MODEL = "eleven_flash_v2_5"
+
+@app.route('/inbound', methods=['POST'])
+def handle_inbound_call():
+ """Handle incoming calls to Twilio numbers configured for RowBoat"""
+ try:
+ # Log the entire request for debugging
+ logger.info(f"Received inbound call request: {request.values}")
+
+ # Get the Twilio phone number that received the call
+ to_number = request.values.get('To')
+ call_sid = request.values.get('CallSid')
+ from_number = request.values.get('From')
+
+ logger.info(f"Inbound call from {from_number} to {to_number}, CallSid: {call_sid}")
+ logger.info(f"Raw To number value: '{to_number}', Type: {type(to_number)}")
+
+ # Get configuration ONLY from MongoDB
+ system_prompt = "You are a helpful assistant. Provide concise and clear answers."
+ workflow_id = None
+ project_id = None
+
+ # Look up configuration in MongoDB
+ twilio_config = get_twilio_config(to_number)
+ if twilio_config:
+ workflow_id = twilio_config['workflow_id']
+ project_id = twilio_config['project_id']
+ system_prompt = twilio_config.get('system_prompt', system_prompt)
+ logger.info(f"Found MongoDB configuration for {to_number}: project_id={project_id}, workflow_id={workflow_id}")
+ else:
+ logger.warning(f"No active configuration found in MongoDB for phone number {to_number}")
+
+ if not workflow_id:
+ # No workflow found - provide error message
+ logger.error(f"No workflow_id found for inbound call to {to_number}")
+ response = VoiceResponse()
+ response.say("I'm sorry, this phone number is not properly configured in our system. Please contact support.", voice='alice')
+ # Include additional information in TwiML for debugging
+ response.say(f"Received call to number {to_number}", voice='alice')
+ response.hangup()
+ return str(response)
+
+ # Initialize call state with stateless API fields
+ call_state = CallState(
+ workflow_id=workflow_id,
+ project_id=project_id,
+ system_prompt=system_prompt,
+ conversation_history=[],
+ messages=[], # For stateless API
+ state=None, # For stateless API state
+ turn_count=0,
+ inbound=True,
+ to_number=to_number,
+ created_at=int(time()) # Add timestamp for expiration tracking
+ )
+
+ # Save to MongoDB (primary source of truth)
+ try:
+ save_call_state(call_sid, call_state)
+ logger.info(f"Saved initial call state to MongoDB for inbound call {call_sid}")
+ except Exception as e:
+ logger.error(f"Error saving inbound call state to MongoDB: {str(e)}")
+ raise RuntimeError(f"Failed to save call state to MongoDB: {str(e)}")
+
+ # Only use memory storage as a temporary cache
+ # The service that handles the next request might be different
+ active_calls[call_sid] = call_state
+
+ logger.info(f"Initialized call state for {call_sid}, proceeding to handle_call")
+
+ # Create a direct response instead of redirecting
+ return handle_call(call_sid, workflow_id, project_id)
+
+ except Exception as e:
+ # Log the full error with traceback
+ import traceback
+ logger.error(f"Error in handle_inbound_call: {str(e)}")
+ logger.error(traceback.format_exc())
+
+ # Return a basic TwiML response so Twilio doesn't get a 500 error
+ response = VoiceResponse()
+ response.say("I'm sorry, we encountered an error processing your call. Please try again later.", voice='alice')
+ response.hangup()
+ return str(response)
+
+@app.route('/twiml', methods=['POST'])
+def handle_twiml_call():
+ """TwiML endpoint for outbound call handling"""
+ call_sid = request.values.get('CallSid')
+
+ # Get call state to retrieve workflow_id and project_id
+ call_state = get_call_state(call_sid)
+ if call_state:
+ workflow_id = call_state.get('workflow_id')
+ project_id = call_state.get('project_id')
+ return handle_call(call_sid, workflow_id, project_id)
+ else:
+ # No call state found - error response
+ response = VoiceResponse()
+ response.say("I'm sorry, your call session has expired. Please try again.", voice='alice')
+ response.hangup()
+ return str(response)
+
+def handle_call(call_sid, workflow_id, project_id=None):
+ """Common handler for both inbound and outbound calls"""
+ try:
+ logger.info(f"handle_call: processing call {call_sid} with workflow {workflow_id}, project_id {project_id}")
+
+ # Get or initialize call state, first from MongoDB
+ call_state = None
+
+ try:
+ # Query MongoDB for the call state
+ call_state = get_call_state(call_sid)
+ if call_state:
+ logger.info(f"Loaded and restored call state from MongoDB for {call_sid}")
+ except Exception as e:
+ logger.error(f"Error retrieving MongoDB state for {call_sid}: {str(e)}")
+ call_state = None
+
+ # Try in-memory cache as fallback (temporary local cache)
+ if call_state is None and call_sid in active_calls:
+ call_state = active_calls.get(call_sid)
+ logger.info(f"Using in-memory cache for call state of {call_sid}")
+
+ # Initialize new state if needed
+ if call_state is None and workflow_id:
+ call_state = CallState(
+ workflow_id=workflow_id,
+ project_id=project_id,
+ system_prompt="You are a helpful assistant. Provide concise and clear answers.",
+ conversation_history=[],
+ messages=[], # For stateless API
+ state=None, # For stateless API state
+ turn_count=0,
+ inbound=False, # Default for outbound calls
+ to_number="", # This will be set properly for inbound calls
+ created_at=int(time()), # Add timestamp for expiration tracking
+ last_transcription=""
+ )
+
+ # Save to MongoDB (primary source of truth)
+ try:
+ save_call_state(call_sid, call_state)
+ logger.info(f"Initialized and saved new call state to MongoDB for {call_sid}")
+ except Exception as e:
+ logger.error(f"Error saving new call state to MongoDB: {str(e)}")
+ raise RuntimeError(f"Failed to save call state to MongoDB: {str(e)}")
+
+ # Only use memory as temporary cache for this request
+ active_calls[call_sid] = call_state
+ logger.info(f"Initialized new call state for {call_sid}")
+
+ logger.info(f"Using call state: {call_state}")
+
+ # Create TwiML response
+ response = VoiceResponse()
+
+# Check if this is a new call (no turns yet)
+ if call_state.get('turn_count', 0) == 0:
+ logger.info("First turn: generating AI greeting using an empty user input...")
+
+ # Generate greeting by calling process_conversation_turn with empty user input
+ try:
+ ai_greeting, updated_messages, updated_state = process_conversation_turn(
+ user_input="", # empty to signal "give me your greeting"
+ workflow_id=call_state['workflow_id'],
+ system_prompt=call_state['system_prompt'],
+ previous_messages=[],
+ previous_state=None,
+ project_id=call_state.get('project_id')
+ )
+ except Exception as e:
+ logger.error(f"Error generating AI greeting: {str(e)}")
+ ai_greeting = "Hello, I encountered an issue creating a greeting. How can I help you?"
+
+ # Fallback: no changes to updated_messages/updated_state
+ updated_messages = []
+ updated_state = None
+
+ # Update call_state with AI greeting
+ call_state['messages'] = updated_messages
+ call_state['state'] = updated_state
+ call_state['conversation_history'].append({
+ 'user': "", # empty user
+ 'assistant': ai_greeting
+ })
+ call_state['turn_count'] = 1
+
+ # Save changes to MongoDB
+ try:
+ save_call_state(call_sid, call_state)
+ logger.info(f"Saved greeting state to MongoDB for {call_sid}")
+ except Exception as e:
+ logger.error(f"Error saving greeting state to MongoDB: {str(e)}")
+ raise RuntimeError(f"Failed to save greeting state to MongoDB: {str(e)}")
+
+ active_calls[call_sid] = call_state
+
+ # Play the greeting via streaming audio
+ unique_id = str(uuid.uuid4())
+ audio_url = f"/stream-audio/{call_sid}/greeting/{unique_id}"
+ logger.info(f"Will stream greeting from {audio_url}")
+ response.play(audio_url)
+
+ # Gather user input next
+ gather = Gather(
+ input='speech',
+ action=f'/process_speech?call_sid={call_sid}',
+ speech_timeout='auto',
+ language='en-US',
+ enhanced=True,
+ speechModel='phone_call'
+ )
+ response.append(gather)
+ response.redirect('/twiml')
+
+ logger.info(f"Returning response: {str(response)}")
+ return str(response)
+
+ except Exception as e:
+ # Log the full error with traceback
+ import traceback
+ logger.error(f"Error in handle_call: {str(e)}")
+ logger.error(traceback.format_exc())
+
+ # Return a basic TwiML response
+ response = VoiceResponse()
+ response.say("I'm sorry, we encountered an error processing your call. Please try again later.", voice='alice')
+ response.hangup()
+ return str(response)
+
+@app.route('/process_speech', methods=['POST'])
+def process_speech():
+ """Process user speech input and generate AI response"""
+ try:
+ logger.info(f"Processing speech: {request.values}")
+
+ call_sid = request.args.get('call_sid')
+
+ # Log all request values for debugging
+ logger.info(f"FULL REQUEST VALUES: {dict(request.values)}")
+ logger.info(f"FULL REQUEST ARGS: {dict(request.args)}")
+
+ # Get the speech result directly from Twilio
+ # We're now relying on Twilio's enhanced speech recognition instead of Deepgram
+ speech_result = request.values.get('SpeechResult')
+ confidence = request.values.get('Confidence')
+
+ logger.info(f"Twilio SpeechResult: {speech_result}")
+ logger.info(f"Twilio Confidence: {confidence}")
+
+ if not call_sid:
+ logger.warning(f"Missing call_sid: {call_sid}")
+ response = VoiceResponse()
+ response.say("I'm sorry, I couldn't process that request.", voice='alice')
+ response.hangup()
+ return str(response)
+
+ if not speech_result:
+ logger.warning("No speech result after transcription attempts")
+ response = VoiceResponse()
+ response.say("I'm sorry, I didn't catch what you said. Could you please try again?", voice='alice')
+
+ # Gather user input again
+ gather = Gather(
+ input='speech',
+ action=f'/process_speech?call_sid={call_sid}',
+ speech_timeout='auto',
+ language='en-US',
+ enhanced=True,
+ speechModel='phone_call'
+ )
+ response.append(gather)
+
+ # Redirect to twiml endpoint which will get call state from MongoDB
+ response.redirect('/twiml')
+
+ return str(response)
+
+ # Load call state from MongoDB (primary source of truth)
+ call_state = None
+
+ try:
+ call_state = get_call_state(call_sid)
+ if call_state:
+ logger.info(f"Loaded call state from MongoDB for speech processing: {call_sid}")
+ except Exception as e:
+ logger.error(f"Error retrieving MongoDB state for speech processing: {str(e)}")
+ call_state = None
+
+ # Try memory cache as fallback
+ if call_state is None and call_sid in active_calls:
+ call_state = active_calls[call_sid]
+ logger.info(f"Using in-memory state for speech processing: {call_sid}")
+
+ # Check if we have valid state
+ if not call_state:
+ logger.warning(f"No call state found for speech processing: {call_sid}")
+ response = VoiceResponse()
+ response.say("I'm sorry, your call session has expired. Please call back.", voice='alice')
+ response.hangup()
+ return str(response)
+
+ # Extract key information
+ workflow_id = call_state.get('workflow_id')
+ project_id = call_state.get('project_id')
+ system_prompt = call_state.get('system_prompt', "You are a helpful assistant.")
+
+ # Check if we have a Deepgram transcription stored in the call state
+ if 'last_transcription' in call_state and call_state['last_transcription']:
+ deepgram_transcription = call_state['last_transcription']
+ logger.info(f"Found stored Deepgram transcription: {deepgram_transcription}")
+ logger.info(f"Comparing with Twilio transcription: {speech_result}")
+
+ # Use the Deepgram transcription instead of Twilio's
+ speech_result = deepgram_transcription
+ # Remove it so we don't use it again
+ del call_state['last_transcription']
+ logger.info(f"Using Deepgram transcription instead")
+
+ # Log final user input that will be used
+ logger.info(f"Final user input: {speech_result}")
+
+ # Process with RowBoat agent
+ try:
+ # Clean up the speech result if needed
+ if speech_result:
+ # Remove any common filler words or fix typical transcription issues
+ import re
+ # Convert to lowercase for easier pattern matching
+ cleaned_input = speech_result.lower()
+ # Remove filler words that might be at the beginning
+ cleaned_input = re.sub(r'^(um|uh|like|so|okay|well)\s+', '', cleaned_input)
+ # Capitalize first letter for better appearance
+ if cleaned_input:
+ speech_result = cleaned_input[0].upper() + cleaned_input[1:]
+
+ logger.info(f"Sending to RowBoat: '{speech_result}'")
+
+ # Get previous messages and state from call state
+ previous_messages = call_state.get('messages', [])
+ previous_state = call_state.get('state')
+
+ # Process with stateless API
+ ai_response, updated_messages, updated_state = process_conversation_turn(
+ user_input=speech_result,
+ workflow_id=workflow_id,
+ system_prompt=system_prompt,
+ previous_messages=previous_messages,
+ previous_state=previous_state,
+ project_id=project_id
+ )
+
+ # Update the messages and state in call state
+ call_state['messages'] = updated_messages
+ call_state['state'] = updated_state
+
+ logger.info(f"RowBoat response: {ai_response}")
+ except Exception as e:
+ logger.error(f"Error processing with RowBoat: {str(e)}")
+ ai_response = "I'm sorry, I encountered an issue processing your request. Could you please try again?"
+
+ # Conversation history is updated in the streaming response section below
+
+ # Create TwiML response
+ response = VoiceResponse()
+
+ # Use streaming audio for the response
+ logger.info("Setting up response streaming with ElevenLabs")
+
+ try:
+ # Store the AI response in conversation history first
+ # (The stream-audio endpoint will read it from here)
+
+ # Update conversation history (do this before streaming so the endpoint can access it)
+ call_state['conversation_history'].append({
+ 'user': speech_result,
+ 'assistant': ai_response
+ })
+ call_state['turn_count'] += 1
+
+ # Save to MongoDB (primary source of truth)
+ try:
+ save_call_state(call_sid, call_state)
+ logger.info(f"Saved response state to MongoDB for {call_sid}")
+ except Exception as e:
+ logger.error(f"Error saving response state to MongoDB: {str(e)}")
+ raise RuntimeError(f"Failed to save response state to MongoDB: {str(e)}")
+
+ # Update local memory cache
+ active_calls[call_sid] = call_state
+
+ # Generate a unique ID to prevent caching
+ unique_id = str(uuid.uuid4())
+ # Use a relative URL - Twilio will use the same host as the webhook
+ audio_url = f"/stream-audio/{call_sid}/response/{unique_id}"
+ logger.info(f"Streaming response from relative URL: {audio_url}")
+
+ # Play the response via streaming
+ response.play(audio_url)
+ except Exception as e:
+ logger.error(f"Error with audio streaming for response: {str(e)}")
+ import traceback
+ logger.error(traceback.format_exc())
+ # Fallback to Twilio TTS
+ response.say(ai_response, voice='alice')
+
+ # Gather next user input with enhanced speech recognition
+ gather = Gather(
+ input='speech',
+ action=f'/process_speech?call_sid={call_sid}',
+ speech_timeout='auto',
+ language='en-US',
+ enhanced=True, # Enable enhanced speech recognition
+ speechModel='phone_call' # Optimize for phone calls
+ )
+ response.append(gather)
+
+ # If no input detected, redirect to twiml endpoint
+ # Call state will be retrieved from MongoDB
+ response.redirect('/twiml')
+
+ logger.info(f"Returning TwiML response for speech processing")
+ return str(response)
+
+ except Exception as e:
+ # Log the full error with traceback
+ import traceback
+ logger.error(f"Error in process_speech: {str(e)}")
+ logger.error(traceback.format_exc())
+
+ # Return a basic TwiML response
+ response = VoiceResponse()
+ response.say("I'm sorry, we encountered an error processing your speech. Please try again.", voice='alice')
+ response.gather(
+ input='speech',
+ action=f'/process_speech?call_sid={request.args.get("call_sid")}',
+ speech_timeout='auto'
+ )
+ return str(response)
+
+@app.route('/stream-audio///', methods=['GET'])
+def stream_audio(call_sid, text_type, unique_id):
+ """Stream audio directly from ElevenLabs to Twilio without saving to disk"""
+ try:
+ logger.info(f"Audio streaming requested for call {call_sid}, type {text_type}")
+
+ # Determine what text to synthesize
+ text_to_speak = ""
+
+ if text_type == "greeting" or text_type == "response":
+ # Get the text from call state (try MongoDB first, then memory)
+ call_state = None
+
+ # Try MongoDB first
+ try:
+ call_state = get_call_state(call_sid)
+ if call_state:
+ logger.info(f"Loaded call state from MongoDB for streaming: {call_sid}")
+ except Exception as e:
+ logger.error(f"Error retrieving MongoDB state for streaming: {str(e)}")
+ call_state = None
+
+ # Fall back to memory if needed
+ if call_state is None:
+ if call_sid not in active_calls:
+ logger.error(f"Call SID not found for streaming: {call_sid}")
+ return "Call not found", 404
+
+ call_state = active_calls[call_sid]
+ logger.info(f"Using in-memory state for streaming: {call_sid}")
+ if call_state.get('conversation_history') and len(call_state['conversation_history']) > 0:
+ # Get the most recent AI response
+ text_to_speak = call_state['conversation_history'][-1]['assistant']
+ else:
+ logger.warning(f"No conversation history found for call {call_sid}")
+ text_to_speak = "I'm sorry, I don't have a response ready. Could you please repeat?"
+ else:
+ # Direct text may be passed as the text_type (for testing)
+ text_to_speak = text_type
+
+ if not text_to_speak:
+ logger.error("No text to synthesize")
+ return "No text to synthesize", 400
+
+ logger.info(f"Streaming audio for text: {text_to_speak[:50]}...")
+
+
+ def generate():
+ try:
+ # Generate and stream the audio directly
+ audio_stream = elevenlabs_client.generate(
+ text=text_to_speak,
+ voice=TTS_VOICE,
+ model=TTS_MODEL,
+ output_format="mp3_44100_128"
+ )
+
+ # Stream chunks directly to the response
+ for chunk in audio_stream:
+ yield chunk
+
+ logger.info(f"Finished streaming audio for call {call_sid}")
+ except Exception as e:
+ logger.error(f"Error in audio stream generator: {str(e)}")
+ import traceback
+ logger.error(traceback.format_exc())
+
+ # Return a streaming response
+ response = Response(generate(), mimetype='audio/mpeg')
+ return response
+
+ except Exception as e:
+ logger.error(f"Error setting up audio stream: {str(e)}")
+ import traceback
+ logger.error(traceback.format_exc())
+ return "Error streaming audio", 500
+
+@app.route('/call-status', methods=['POST'])
+def call_status_callback():
+ """Handle call status callbacks from Twilio"""
+ call_sid = request.values.get('CallSid')
+ call_status = request.values.get('CallStatus')
+
+ logger.info(f"Call {call_sid} status: {call_status}")
+
+ # Clean up resources when call completes
+ if call_status in ['completed', 'failed', 'busy', 'no-answer', 'canceled']:
+ # Get call state from MongoDB or memory
+ call_state = None
+
+ # Try to load from MongoDB first
+ try:
+ call_state = get_call_state(call_sid)
+ if call_state:
+ logger.info(f"Loaded final state from MongoDB for {call_sid}")
+ except Exception as e:
+ logger.error(f"Error retrieving final state from MongoDB: {str(e)}")
+ call_state = None
+
+ # Fall back to memory if needed
+ if call_state is None and call_sid in active_calls:
+ call_state = active_calls[call_sid]
+ logger.info(f"Using in-memory state for final call state of {call_sid}")
+
+ if call_state:
+ # Remove from active calls in both memory and MongoDB
+ if call_sid in active_calls:
+ del active_calls[call_sid]
+ logger.info(f"Removed call {call_sid} from active calls memory")
+
+ try:
+ # Remove the document from MongoDB
+ delete_call_state(call_sid)
+ logger.info(f"Removed call {call_sid} from MongoDB")
+ except Exception as e:
+ logger.error(f"Error removing call state from MongoDB: {str(e)}")
+ return '', 204
+
+
+@app.route('/health', methods=['GET'])
+def health_check():
+ """Simple health check endpoint"""
+ health_data = {
+ "status": "healthy",
+ "active_calls_memory": len(active_calls)
+ }
+
+ # Get MongoDB status
+ try:
+ mongodb_status = get_mongodb_status()
+ health_data["mongodb"] = mongodb_status
+ health_data["active_calls_mongodb"] = mongodb_status.get("active_calls", 0)
+ except Exception as e:
+ health_data["mongodb_error"] = str(e)
+ health_data["status"] = "degraded"
+
+ return jsonify(health_data)
+
+if __name__ == '__main__':
+ # Log startup information
+ logger.info(f"Starting Twilio-RowBoat server")
+ # Remove the explicit run configuration since Flask CLI will handle it
+ app.run()
\ No newline at end of file
diff --git a/apps/twilio_handler/load_env.py b/apps/twilio_handler/load_env.py
new file mode 100644
index 00000000..309c2390
--- /dev/null
+++ b/apps/twilio_handler/load_env.py
@@ -0,0 +1,6 @@
+from dotenv import load_dotenv
+import os
+
+def load_environment():
+ """Load environment variables from .env file"""
+ load_dotenv()
\ No newline at end of file
diff --git a/apps/twilio_handler/requirements.txt b/apps/twilio_handler/requirements.txt
new file mode 100644
index 00000000..a3986fa6
--- /dev/null
+++ b/apps/twilio_handler/requirements.txt
@@ -0,0 +1,39 @@
+aiohappyeyeballs==2.5.0
+aiohttp==3.11.13
+aiohttp-retry==2.9.1
+aiosignal==1.3.2
+annotated-types==0.7.0
+anyio==4.8.0
+attrs==25.1.0
+blinker==1.9.0
+certifi==2025.1.31
+charset-normalizer==3.4.1
+click==8.1.8
+dnspython==2.7.0
+dotenv==0.9.9
+elevenlabs==1.52.0
+Flask==3.1.0
+frozenlist==1.5.0
+h11==0.14.0
+httpcore==1.0.7
+httpx==0.28.1
+idna==3.10
+itsdangerous==2.2.0
+Jinja2==3.1.6
+MarkupSafe==3.0.2
+multidict==6.1.0
+propcache==0.3.0
+pydantic==2.10.6
+pydantic_core==2.27.2
+PyJWT==2.10.1
+pymongo==4.11.2
+python-dotenv==1.0.1
+requests==2.32.3
+rowboat==2.1.0
+sniffio==1.3.1
+twilio==9.4.6
+typing_extensions==4.12.2
+urllib3==2.3.0
+websockets==15.0.1
+Werkzeug==3.1.3
+yarl==1.18.3
diff --git a/apps/twilio_handler/twilio_api.py b/apps/twilio_handler/twilio_api.py
new file mode 100644
index 00000000..b233fb65
--- /dev/null
+++ b/apps/twilio_handler/twilio_api.py
@@ -0,0 +1,96 @@
+from twilio.rest import Client as TwilioClient
+from rowboat.client import Client
+from rowboat.schema import UserMessage, SystemMessage
+import os
+from typing import Dict, List, Optional, Tuple, Any
+import logging
+from util import get_api_key
+import time
+import json
+
+# Load environment variables
+from load_env import load_environment
+load_environment()
+
+# Configure logging to stdout for Docker compatibility
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[logging.StreamHandler()] # Send logs to stdout
+)
+logger = logging.getLogger(__name__)
+
+# Environment variables and configuration
+ROWBOAT_API_HOST = os.environ.get("ROWBOAT_API_HOST").strip()
+
+Message = UserMessage | SystemMessage
+
+def process_conversation_turn(
+ user_input: str,
+ workflow_id: str,
+ system_prompt: str = "You are a helpful assistant. Provide concise and clear answers.",
+ previous_messages: List[Message] = None,
+ previous_state: Any = None,
+ project_id: str = None
+) -> Tuple[str, List[Message], Any]:
+ """
+ Process a single conversation turn with the RowBoat agent using the stateless API.
+
+ Args:
+ user_input: User's transcribed input
+ workflow_id: RowBoat workflow ID
+ system_prompt: System prompt for the agent
+ previous_messages: Previous messages in the conversation
+ previous_state: Previous state from RowBoat
+ project_id: RowBoat project ID (if different from default)
+
+ Returns:
+ A tuple of (response_text, updated_messages, updated_state)
+ """
+ try:
+ # Initialize messages list if not provided
+ messages = [] if previous_messages is None else previous_messages.copy()
+
+ # If we're starting a new conversation, add the system message
+ if not messages or not any(msg.role == 'system' for msg in messages):
+ messages.append(SystemMessage(role='system', content=system_prompt))
+
+ # Add the user's new
+ if user_input:
+ messages.append(UserMessage(role='user', content=user_input))
+
+ # Process the conversation using the stateless API
+ logger.info(f"Sending to RowBoat API with {len(messages)} messages")
+
+ # Create client with custom project_id if provided
+
+ client = Client(
+ host=ROWBOAT_API_HOST,
+ project_id=project_id,
+ api_key=get_api_key(project_id)
+ )
+
+ response_messages, new_state = client.chat(
+ messages=messages,
+ workflow_id=workflow_id,
+ state=previous_state
+ )
+
+ # Extract the assistant's response (last message)
+ if response_messages and len(response_messages) > 0:
+ assistant_response = response_messages[-1].content
+ else:
+ assistant_response = "I'm sorry, I didn't receive a proper response."
+
+ # Update messages list with the new responses
+ final_messages = messages + response_messages
+
+
+ logger.info(f"Got response from RowBoat API: {assistant_response[:100]}...")
+ return assistant_response, final_messages, new_state
+
+ except Exception as e:
+ logger.error(f"Error processing conversation turn: {str(e)}")
+ import traceback
+ logger.error(traceback.format_exc())
+ return "I'm sorry, I encountered an error processing your request.", previous_messages, previous_state
\ No newline at end of file
diff --git a/apps/twilio_handler/util.py b/apps/twilio_handler/util.py
new file mode 100644
index 00000000..013809a8
--- /dev/null
+++ b/apps/twilio_handler/util.py
@@ -0,0 +1,423 @@
+import os
+import logging
+import datetime
+from typing import Dict, Any, Optional, List, Union
+import copy
+from pymongo import MongoClient
+from pymongo.errors import ConnectionFailure, PyMongoError
+from pymongo.collection import Collection
+from bson import json_util
+from pydantic import BaseModel
+from rowboat.schema import ApiMessage
+
+# Configure logging to stdout for Docker compatibility
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[logging.StreamHandler()] # Send logs to stdout
+)
+logger = logging.getLogger(__name__)
+
+# MongoDB Configuration
+MONGODB_URI = os.environ.get('MONGODB_URI')
+MONGODB_DB = 'rowboat'
+
+CALL_STATE_COLLECTION = 'call-state'
+MONGODB_EXPIRY_SECONDS = 86400 # Default 24 hours
+API_KEYS_COLLECTION = "api_keys"
+# MongoDB client singleton
+_mongo_client = None
+_db = None
+_call_state_collection = None
+_api_keys_collection = None
+
+# Define chat state pydantic model
+class CallState(BaseModel):
+ messages: List[ApiMessage] = []
+ workflow_id: str
+ project_id: str
+ system_prompt: str
+ turn_count: int = 0
+ inbound: bool = False
+ conversation_history: List[Dict[str, str]] = [] # Using Dict instead of ApiMessage for chat history
+ to_number: str = ""
+ created_at: int
+ state: Any = None # Allow any type since the API might return a complex state object
+ last_transcription: Optional[str] = None
+
+ # Enable dictionary-style access for compatibility with existing code
+ def __getitem__(self, key):
+ return getattr(self, key)
+
+ def __setitem__(self, key, value):
+ setattr(self, key, value)
+
+ def get(self, key, default=None):
+ return getattr(self, key, default)
+
+ model_config = {
+ # Allow extra fields for flexibility
+ "extra": "allow",
+ # More lenient type validation
+ "arbitrary_types_allowed": True,
+ # Allow population by field name
+ "populate_by_name": True
+ }
+
+def init_mongodb():
+ """Initialize MongoDB connection and set up indexes."""
+ global _mongo_client, _db, _call_state_collection, _api_keys_collection
+
+ try:
+ _mongo_client = MongoClient(MONGODB_URI)
+ # Force a command to check the connection
+ _mongo_client.admin.command('ping')
+
+ # Set up database and collection
+ _db = _mongo_client[MONGODB_DB]
+ _call_state_collection = _db[CALL_STATE_COLLECTION]
+ _api_keys_collection = _db[API_KEYS_COLLECTION]
+ # Create TTL index if it doesn't exist
+ if 'expires_at_1' not in _call_state_collection.index_information():
+ _call_state_collection.create_index('expires_at', expireAfterSeconds=0)
+
+ logger.info(f"Connected to MongoDB at {MONGODB_URI}")
+ return True
+ except ConnectionFailure as e:
+ logger.error(f"Failed to connect to MongoDB: {str(e)}")
+ raise RuntimeError(f"Could not connect to MongoDB: {str(e)}")
+
+def get_collection() -> Collection:
+ """Get the MongoDB collection, initializing if needed."""
+ global _call_state_collection
+
+ if _call_state_collection is None:
+ init_mongodb()
+
+ return _call_state_collection
+
+def get_api_keys_collection() -> Collection:
+ """Get the MongoDB collection, initializing if needed."""
+ global _api_keys_collection
+
+ if _api_keys_collection is None:
+ init_mongodb()
+
+ return _api_keys_collection
+
+def get_api_key(project_id: str) -> Optional[str]:
+ """Get the API key for a given project ID."""
+ collection = get_api_keys_collection()
+ doc = collection.find_one({"projectId": project_id})
+ return doc["key"] if doc else None
+
+def save_call_state(call_sid: str, call_state: CallState) -> bool:
+ """
+ Save call state to MongoDB.
+
+ Args:
+ call_sid: The call SID to use as document ID
+ call_state: The call state dictionary to save
+
+ Returns:
+ True if successful, False otherwise
+ """
+ try:
+ # Validate call_state is a CallState object
+ if not isinstance(call_state, CallState):
+ raise ValueError(f"call_state must be a CallState object, got {type(call_state)}")
+
+ collection = get_collection()
+ # Use call_sid as document ID
+ collection.update_one(
+ {'_id': call_sid},
+ {'$set': call_state.model_dump()},
+ upsert=True
+ )
+ logger.info(f"Saved call state to MongoDB for call {call_sid}")
+ return True
+ except PyMongoError as e:
+ logger.error(f"Error saving call state to MongoDB for call {call_sid}: {str(e)}")
+ raise RuntimeError(f"Failed to save call state to MongoDB: {str(e)}")
+ except Exception as e:
+ logger.error(f"Unexpected error in save_call_state: {str(e)}")
+ raise RuntimeError(f"Failed to save call state: {str(e)}")
+
+def get_call_state(call_sid: str) -> Optional[CallState]:
+ """
+ Retrieve call state from MongoDB.
+
+ Args:
+ call_sid: The call SID to retrieve
+
+ Returns:
+ Call state dictionary or None if not found
+ """
+ try:
+ collection = get_collection()
+
+ # Query MongoDB for the call state
+ state_doc = collection.find_one({'_id': call_sid})
+ if not state_doc:
+ logger.info(f"No call state found in MongoDB for call {call_sid}")
+ return None
+
+ call_state = CallState.model_validate(state_doc)
+
+ logger.info(f"Retrieved call state from MongoDB for call {call_sid}")
+ return call_state
+ except PyMongoError as e:
+ logger.error(f"Error retrieving call state from MongoDB for call {call_sid}: {str(e)}")
+ raise RuntimeError(f"Failed to retrieve call state from MongoDB: {str(e)}")
+ except Exception as e:
+ logger.error(f"Unexpected error in get_call_state: {str(e)}")
+ raise RuntimeError(f"Failed to retrieve call state: {str(e)}")
+
+def delete_call_state(call_sid: str) -> bool:
+ """
+ Delete call state from MongoDB.
+
+ Args:
+ call_sid: The call SID to delete
+
+ Returns:
+ True if successful, False if not found
+ """
+ try:
+ collection = get_collection()
+
+ # Delete the document from MongoDB
+ result = collection.delete_one({'_id': call_sid})
+ if result.deleted_count > 0:
+ logger.info(f"Deleted call state from MongoDB for call {call_sid}")
+ return True
+ else:
+ logger.info(f"No call state found to delete in MongoDB for call {call_sid}")
+ return False
+ except PyMongoError as e:
+ logger.error(f"Error deleting call state from MongoDB for call {call_sid}: {str(e)}")
+ raise RuntimeError(f"Failed to delete call state from MongoDB: {str(e)}")
+ except Exception as e:
+ logger.error(f"Unexpected error in delete_call_state: {str(e)}")
+ raise RuntimeError(f"Failed to delete call state: {str(e)}")
+
+def count_active_calls() -> int:
+ """
+ Count active call documents in MongoDB.
+
+ Returns:
+ Number of active call documents
+ """
+ try:
+ collection = get_collection()
+ return collection.count_documents({})
+ except PyMongoError as e:
+ logger.error(f"Error counting active calls in MongoDB: {str(e)}")
+ raise RuntimeError(f"Failed to count active calls in MongoDB: {str(e)}")
+ except Exception as e:
+ logger.error(f"Unexpected error in count_active_calls: {str(e)}")
+ raise RuntimeError(f"Failed to count active calls: {str(e)}")
+
+def get_mongodb_status() -> Dict[str, Any]:
+ """
+ Get MongoDB connection status information.
+
+ Returns:
+ Dictionary with status information
+ """
+ status = {
+ "status": "connected",
+ "uri": MONGODB_URI,
+ "database": MONGODB_DB,
+ "collection": CALL_STATE_COLLECTION
+ }
+
+ try:
+ # First check connection with a simple command
+ collection = get_collection()
+ db = collection.database
+ db.command('ping')
+ status["connection"] = "ok"
+
+ # Count active calls
+ count = count_active_calls()
+ status["active_calls"] = count
+
+ # Get collection stats
+ try:
+ stats = db.command("collStats", CALL_STATE_COLLECTION)
+ status["size_bytes"] = stats.get("size", 0)
+ status["document_count"] = stats.get("count", 0)
+ status["index_count"] = len(stats.get("indexSizes", {}))
+ except Exception as stats_error:
+ status["stats_error"] = str(stats_error)
+
+ except Exception as e:
+ status["status"] = "error"
+ status["error"] = str(e)
+ status["timestamp"] = datetime.datetime.utcnow().isoformat()
+
+ return status
+
+# Twilio configuration functions
+def get_twilio_config(phone_number: str) -> Optional[Dict[str, Any]]:
+ """
+ Get Twilio configuration for a specific phone number from MongoDB.
+
+ Args:
+ phone_number: The phone number to look up configuration for
+
+ Returns:
+ Configuration dictionary or None if not found/active
+ """
+ try:
+ # Get MongoDB client and database
+ client = get_collection().database.client
+ db = client[MONGODB_DB]
+
+ # Use the twilio_configs collection
+ config_collection = db['twilio_configs']
+
+ # Enhanced logging for phone number format
+ logger.info(f"Looking up configuration for phone number: '{phone_number}'")
+
+ # Try different formats of the phone number
+ cleaned_number = phone_number.strip().replace(' ', '').replace('-', '').replace('(', '').replace(')', '')
+
+ possible_formats = [
+ phone_number, # Original format from Twilio
+ cleaned_number, # Thoroughly cleaned number
+ '+' + cleaned_number if not cleaned_number.startswith('+') else cleaned_number, # Ensure + prefix
+
+ # Try with different country code formats
+ '+1' + cleaned_number[-10:] if len(cleaned_number) >= 10 else cleaned_number, # US format with +1
+ '1' + cleaned_number[-10:] if len(cleaned_number) >= 10 else cleaned_number, # US format with 1
+ cleaned_number[-10:] if len(cleaned_number) >= 10 else cleaned_number, # US format without country code
+ ]
+
+ # Remove duplicates while preserving order
+ unique_formats = []
+ for fmt in possible_formats:
+ if fmt not in unique_formats:
+ unique_formats.append(fmt)
+ possible_formats = unique_formats
+
+ # Log the formats we're trying
+ logger.info(f"Trying phone number formats: {possible_formats}")
+
+ # Try each format
+ for phone_format in possible_formats:
+ # Look up the configuration for this phone number format with status=active
+ config = config_collection.find_one({'phone_number': phone_format, 'status': 'active'})
+ if config:
+ logger.info(f"Found active configuration for '{phone_format}': project_id={config.get('project_id')}, workflow_id={config.get('workflow_id')}")
+ break # Found a match, exit the loop
+
+ # If we didn't find any match
+ if not config:
+ # Try a more generic query to see what configurations exist
+ try:
+ all_configs = list(config_collection.find({'phone_number': {'$regex': phone_number[-10:] if len(phone_number) >= 10 else phone_number}}))
+ if all_configs:
+ logger.warning(f"Found {len(all_configs)} configurations that match phone number {phone_number}, but none are active:")
+ for cfg in all_configs:
+ logger.warning(f" - Phone: {cfg.get('phone_number')}, Status: {cfg.get('status')}, Workflow: {cfg.get('workflow_id')}")
+ else:
+ logger.warning(f"No configurations found at all for phone number {phone_number} or related formats")
+ except Exception as e:
+ logger.error(f"Error running regex query: {str(e)}")
+
+ logger.warning(f"No active configuration found for any format of phone number {phone_number}")
+ return None
+
+ # Make sure required fields are present
+ if 'project_id' not in config or 'workflow_id' not in config:
+ logger.error(f"Configuration for {phone_number} is missing required fields")
+ return None
+
+ logger.info(f"Found active configuration for {phone_number}: project_id={config['project_id']}, workflow_id={config['workflow_id']}")
+ return config
+ except Exception as e:
+ logger.error(f"Error retrieving Twilio configuration for {phone_number}: {str(e)}")
+ # Return None instead of raising an exception to allow fallback to default behavior
+ return None
+
+def list_active_twilio_configs() -> List[Dict[str, Any]]:
+ """
+ List all active Twilio configurations from MongoDB.
+
+ Returns:
+ List of active configuration dictionaries
+ """
+ try:
+ # Get MongoDB client and database
+ client = get_collection().database.client
+ db = client[MONGODB_DB]
+
+ # Use the twilio_configs collection
+ config_collection = db['twilio_configs']
+
+ # Find all active configurations
+ configs = list(config_collection.find({'status': 'active'}))
+
+ logger.info(f"Found {len(configs)} active Twilio configurations")
+ return configs
+ except Exception as e:
+ logger.error(f"Error retrieving active Twilio configurations: {str(e)}")
+ return []
+
+def save_twilio_config(config: Dict[str, Any]) -> bool:
+ """
+ Save a Twilio configuration to MongoDB.
+
+ Args:
+ config: Configuration dictionary with at least phone_number, project_id, and workflow_id
+
+ Returns:
+ True if successful, False otherwise
+ """
+ required_fields = ['phone_number', 'project_id', 'workflow_id']
+ for field in required_fields:
+ if field not in config:
+ logger.error(f"Missing required field '{field}' in Twilio configuration")
+ return False
+
+ try:
+ # Get MongoDB client and database
+ client = get_collection().database.client
+ db = client[MONGODB_DB]
+
+ # Use the twilio_configs collection
+ config_collection = db['twilio_configs']
+
+ # Ensure status is set to active
+ if 'status' not in config:
+ config['status'] = 'active'
+
+ # Add timestamp
+ config['updated_at'] = datetime.datetime.utcnow()
+ if 'created_at' not in config:
+ config['created_at'] = config['updated_at']
+
+ # Use phone_number as the ID
+ phone_number = config['phone_number']
+
+ # Update or insert the configuration
+ result = config_collection.update_one(
+ {'phone_number': phone_number},
+ {'$set': config},
+ upsert=True
+ )
+
+ if result.matched_count > 0:
+ logger.info(f"Updated Twilio configuration for {phone_number}")
+ else:
+ logger.info(f"Created new Twilio configuration for {phone_number}")
+
+ return True
+ except Exception as e:
+ logger.error(f"Error saving Twilio configuration: {str(e)}")
+ return False
+
+# Initialize MongoDB on module import
+init_mongodb()
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 558b5f82..3681512d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -10,12 +10,13 @@ services:
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING}
+ - USE_AUTH=${USE_AUTH}
- AUTH0_SECRET=${AUTH0_SECRET}
- AUTH0_BASE_URL=${AUTH0_BASE_URL}
- AUTH0_ISSUER_BASE_URL=${AUTH0_ISSUER_BASE_URL}
- AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}
- AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET}
- - AGENTS_API_URL=http://agents:3001
+ - AGENTS_API_URL=http://rowboat_agents:3001
- AGENTS_API_KEY=${AGENTS_API_KEY}
- COPILOT_API_URL=http://copilot:3002
- COPILOT_API_KEY=${COPILOT_API_KEY}
@@ -35,17 +36,22 @@ services:
- CHAT_WIDGET_SESSION_JWT_SECRET=${CHAT_WIDGET_SESSION_JWT_SECRET}
- MAX_QUERIES_PER_MINUTE=${MAX_QUERIES_PER_MINUTE}
- MAX_PROJECTS_PER_USER=${MAX_PROJECTS_PER_USER}
+ - VOICE_API_URL=${VOICE_API_URL}
restart: unless-stopped
- agents:
+ rowboat_agents:
build:
- context: ./apps/agents
+ context: ./apps/rowboat_agents
dockerfile: Dockerfile
ports:
- "3001:3001"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- API_KEY=${AGENTS_API_KEY}
+ - REDIS_URL=redis://redis:6379
+ - MONGODB_URI=${MONGODB_CONNECTION_STRING}
+ - QDRANT_URL=${QDRANT_URL}
+ - QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped
copilot:
@@ -59,6 +65,16 @@ services:
- API_KEY=${COPILOT_API_KEY}
restart: unless-stopped
+ tools_webhook:
+ build:
+ context: ./apps/tools_webhook
+ dockerfile: Dockerfile
+ ports:
+ - "3005:3005"
+ environment:
+ - SIGNING_SECRET=${SIGNING_SECRET}
+ restart: unless-stopped
+
simulation_runner:
build:
context: ./apps/simulation_runner
@@ -123,15 +139,17 @@ services:
- QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped
- tools_webhook:
+ rag_text_worker:
build:
- context: ./apps/tools_webhook
- dockerfile: Dockerfile
- profiles: [ "tools_webhook" ]
- ports:
- - "3005:3005"
+ context: ./apps/rowboat
+ dockerfile: scripts.Dockerfile
+ command: ["npm", "run", "ragTextWorker"]
+ profiles: [ "rag_text_worker" ]
environment:
- - SIGNING_SECRET=${SIGNING_SECRET}
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
+ - MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING}
+ - QDRANT_URL=${QDRANT_URL}
+ - QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped
chat_widget:
@@ -160,4 +178,15 @@ services:
profiles: [ "docs" ]
ports:
- "8000:8000"
- restart: unless-stopped
+
+ # twilio_handler:
+ # build:
+ # context: ./apps/twilio_handler
+ # dockerfile: Dockerfile
+ # ports:
+ # - "4010:4010"
+ # environment:
+ # - ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY}
+ # - ROWBOAT_API_HOST=http://rowboat:3000
+ # - MONGODB_URI=${MONGODB_CONNECTION_STRING}
+ # restart: unless-stopped