From 0052dfbd63d878c897e10b137e713e09367f592c Mon Sep 17 00:00:00 2001 From: rabelenda Date: Mon, 17 Nov 2025 14:12:42 -0300 Subject: [PATCH 01/17] Fix default team name for consistency to avoid confusion --- ...51117-f688dc4a2a9a-fix_global_team_name.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/backend/alembic/versions/20251117-f688dc4a2a9a-fix_global_team_name.py diff --git a/src/backend/alembic/versions/20251117-f688dc4a2a9a-fix_global_team_name.py b/src/backend/alembic/versions/20251117-f688dc4a2a9a-fix_global_team_name.py new file mode 100644 index 0000000..88fb265 --- /dev/null +++ b/src/backend/alembic/versions/20251117-f688dc4a2a9a-fix_global_team_name.py @@ -0,0 +1,32 @@ +"""fix-global-team-name + +Revision ID: f688dc4a2a9a +Revises: 07fd1bcafad1 +Create Date: 2025-11-17 14:10:08.496924 + +""" +from typing import Sequence, Union +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = 'f688dc4a2a9a' +down_revision: Union[str, None] = '07fd1bcafad1' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute(""" + UPDATE team + SET name = 'Global' + WHERE id = 1 + """) + + +def downgrade() -> None: + op.execute(""" + UPDATE team + SET name = 'Default' + WHERE id = 1 + """) From e27810fb93ec8cd594879bc92888cfc5edffdf54 Mon Sep 17 00:00:00 2001 From: rabelenda Date: Mon, 17 Nov 2025 14:35:29 -0300 Subject: [PATCH 02/17] Fix agent import dialog resizing when an unsupported file type is used --- src/frontend/src/components/agent/AgentImportDialog.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/agent/AgentImportDialog.vue b/src/frontend/src/components/agent/AgentImportDialog.vue index d1df2b6..fce051c 100644 --- a/src/frontend/src/components/agent/AgentImportDialog.vue +++ b/src/frontend/src/components/agent/AgentImportDialog.vue @@ -48,7 +48,7 @@ watch(visible, (_) => { -
+
From 1ff4ff61c66197b088e146e34a1436176ea8d7a9 Mon Sep 17 00:00:00 2001 From: rabelenda Date: Mon, 17 Nov 2025 14:42:35 -0300 Subject: [PATCH 03/17] Fix openai and azure environment variables references in readme and user guide quickstart --- README.md | 2 +- docs/guide/quickstart.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1818da2..601410f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ To run Tero locally: 1. Generate an OpenAI API key or an Azure OpenAI endpoint and key. 2. Clone this repository. Make sure you have git-lfs installed to get all the files properly. -3. Copy `src/sample.env` to `.env` in the project root and set `OPENAI_KEY` or `AZURE_OPENAI_KEY` and `AZURE_OPENAI_ENDPOINT`. +3. Copy `src/sample.env` to `.env` in the project root and set `OPENAI_API_KEY` or `AZURE_API_KEYS` and `AZURE_ENDPOINTS`. 4. Start the app and dependencies with `docker compose up -d`. 5. Open `http://localhost:8000` and log in with username `test` and password `test`. diff --git a/docs/guide/quickstart.md b/docs/guide/quickstart.md index c3e1d1a..c809a6d 100644 --- a/docs/guide/quickstart.md +++ b/docs/guide/quickstart.md @@ -10,7 +10,7 @@ The easiest way to try Tero is through the online demo. Request access at `https 1. Clone the [repository](https://github.com/abstracta/tero). Make sure you have git-lfs installed to get all the files properly. 2. Generate an OpenAI API key or an Azure OpenAI endpoint and key. -3. Copy `src/sample.env` to `.env` and set `OPENAI_KEY` or `AZURE_OPENAI_KEY` and `AZURE_OPENAI_ENDPOINT`. +3. Copy `src/sample.env` to `.env` and set `OPENAI_API_KEY` or `AZURE_API_KEYS` and `AZURE_ENDPOINTS`. 4. Start the app and dependencies: ```bash From 2fb157e521085345a79efa4072cf7f9b7c510f25 Mon Sep 17 00:00:00 2001 From: rabelenda Date: Mon, 17 Nov 2025 14:58:50 -0300 Subject: [PATCH 04/17] Fix azure provider embeddings initialization --- src/backend/tero/ai_models/azure_provider.py | 4 ++-- src/backend/tero/ai_models/openai_provider.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/tero/ai_models/azure_provider.py b/src/backend/tero/ai_models/azure_provider.py index d125729..baafc51 100644 --- a/src/backend/tero/ai_models/azure_provider.py +++ b/src/backend/tero/ai_models/azure_provider.py @@ -47,9 +47,9 @@ def build_embedding(self, model: str) -> AzureOpenAIEmbeddings: deployment = env.azure_model_deployments[model] return AzureOpenAIEmbeddings( azure_endpoint=env.azure_endpoints[deployment.endpoint_index], - azure_deployment=deployment, + azure_deployment=deployment.deployment_name, api_version=env.azure_api_version, - api_key=env.azure_endpoints[deployment.endpoint_index]) + api_key=env.azure_api_keys[deployment.endpoint_index]) class ReasoningTokenCountingAzureChatOpenAI(AzureChatOpenAI): diff --git a/src/backend/tero/ai_models/openai_provider.py b/src/backend/tero/ai_models/openai_provider.py index 9019f27..f5eae3c 100644 --- a/src/backend/tero/ai_models/openai_provider.py +++ b/src/backend/tero/ai_models/openai_provider.py @@ -45,7 +45,7 @@ def _get_encoding_model(self) -> tuple[str, tiktoken.Encoding]: return get_encoding_model(self.model_name, lambda: ChatOpenAI._get_encoding_model(self)) -def get_encoding_model(model_name: str, default: Callable[[], tuple[str, tiktoken.Encoding]]) -> tuple[str, tiktoken.Encoding]: +def get_encoding_model(model_name: Optional[str], default: Callable[[], tuple[str, tiktoken.Encoding]]) -> tuple[str, tiktoken.Encoding]: if model_name and model_name.startswith("o"): # we return gpt-4o for o- series since it is supported by existing implementation of get_num_tokens_from_messages return "gpt-4o", tiktoken.get_encoding("o200k_base") From e50a1841c4be500f6d891126d445c5c254c9b1f5 Mon Sep 17 00:00:00 2001 From: rabelenda Date: Mon, 17 Nov 2025 15:27:20 -0300 Subject: [PATCH 05/17] Update github pages for every change in main branch --- .github/workflows/build.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index caf474c..e35a4a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + lfs: true - uses: pnpm/action-setup@v4 with: version: 10 @@ -33,6 +35,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + lfs: true - uses: pnpm/action-setup@v4 with: version: 10 @@ -65,3 +69,22 @@ jobs: poetry run pyright cp ../sample.env ../../.env poetry run alembic revision + + publish-gh-pages: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: 23 + cache: 'pnpm' + cache-dependency-path: 'docs/pnpm-lock.yaml' + - name: Publish + run: .github/gh-pages-deploy.sh + env: + ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b5361750643a7c997b50e9b26dee986f084bf084 Mon Sep 17 00:00:00 2001 From: Lucas del Reguero Martinez Date: Tue, 18 Nov 2025 11:07:51 -0300 Subject: [PATCH 06/17] =?UTF-8?q?Correcci=C3=B3n:=20i18n=20tooltip=20y=20c?= =?UTF-8?q?omportamiento=20desplegar-plegar=20de=20listas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/sidebar/SidebarPanel.vue | 78 ++++++++++++++----- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/src/frontend/src/components/sidebar/SidebarPanel.vue b/src/frontend/src/components/sidebar/SidebarPanel.vue index 9c4d1a0..0184f72 100644 --- a/src/frontend/src/components/sidebar/SidebarPanel.vue +++ b/src/frontend/src/components/sidebar/SidebarPanel.vue @@ -1,5 +1,6 @@ diff --git a/src/browser-extension/components/PageOverlay.vue b/src/browser-extension/components/PageOverlay.vue index fb87fe4..1f16aa3 100644 --- a/src/browser-extension/components/PageOverlay.vue +++ b/src/browser-extension/components/PageOverlay.vue @@ -22,5 +22,6 @@ import headerBg from '../assets/header.webp' +
diff --git a/src/browser-extension/entrypoints/background.ts b/src/browser-extension/entrypoints/background.ts index b80cffd..c79efc0 100644 --- a/src/browser-extension/entrypoints/background.ts +++ b/src/browser-extension/entrypoints/background.ts @@ -1,13 +1,13 @@ import { browser } from 'wxt/browser'; import { type Browser } from 'wxt/browser'; -import { Agent, RequestEvent, RequestEventType} from "~/utils/agent"; -import { findAllAgents, findAgentById, removeAllAgents, } from "~/utils/agent-repository"; +import { Agent, RequestEvent, RequestEventType } from "~/utils/agent"; +import { findAllAgents, findAgentById, removeAllAgents } from "~/utils/agent-repository"; import { AgentSession } from "~/utils/agent-session"; -import { findAgentSession, saveAgentSession, removeAgentSession, } from "~/utils/agent-session-repository"; +import { findAgentSession, saveAgentSession, removeAgentSession } from "~/utils/agent-session-repository"; import { AgentSource } from "~/utils/agent"; -import { BrowserMessage, ToggleSidebar, ActiveTabListener, ActivateAgent, AgentActivation, InteractionSummary, } from "~/utils/browser-message"; +import { BrowserMessage, ToggleSidebar, ActiveTabListener, ActivateAgent, AgentActivation, InteractionSummary } from "~/utils/browser-message"; import { HttpServiceError } from "~/utils/http"; -import { isActiveTabListener, setTabListenerActive, removeTabListenerStatus, } from "~/utils/tab-listener-status-repository"; +import { isActiveTabListener, setTabListenerActive, removeTabListenerStatus } from "~/utils/tab-listener-status-repository"; import { removeTabState } from "~/utils/tab-state-repository"; export default defineBackground(() => { @@ -17,7 +17,6 @@ export default defineBackground(() => { browser.runtime.onInstalled.addListener(async () => { await removeAllAgents() createToggleContextMenu(); - console.log("onInstalled", import.meta.env.DEV) if (import.meta.env.DEV) { try { await AgentSource.loadAgentsFromUrl("http://localhost:8000"); diff --git a/src/browser-extension/entrypoints/iframe/App.vue b/src/browser-extension/entrypoints/iframe/App.vue index 57dbc53..5769b64 100644 --- a/src/browser-extension/entrypoints/iframe/App.vue +++ b/src/browser-extension/entrypoints/iframe/App.vue @@ -326,7 +326,7 @@ const sidebarClasses = computed(() => [ "flowStepMissingElement": "I could not find the element '{selector}'. This might be due to recent changes in the page which I am not aware of. Please try again and if the issue persists contact [support](mailto:{contactEmail}?subject=Navigation%20element).", }, "es": { - "activationError": "No se pudo activar el {agentName}. Puedes intentar de nuevo y si el problema persiste contactar al [soporte de {agentName}](mailto:{contactEmail}?subject=Activation%20issue)", + "activationError": "No se pudo activar {agentName}. Puedes intentar de nuevo y si el problema persiste contactar al [soporte de {agentName}](mailto:{contactEmail}?subject=Activation%20issue)", "interactionSummaryError": "No pude procesar informacion generada por la página actual. Esto puede impactar en la información y respuestas que te puedo dar. Si el problema persiste por favor contacta a [soporte](mailto:{contactEmail})?subject=Interaction%20issue", "agentAnswerError": "Ahora no puedo completar tu pedido. Puedes intentar de nuevo y si el problema persiste contactar a [soporte](mailto:{contactEmail}?subject=Question%20issue)", "flowStepMissingElement": "No pude encontrar el elemento '{selector}'. Esto puede ser debido a cambios recientes en la página de los cuales no tengo conocimiento. Por favor intenta de nuevo y si el problema persiste contacta a [soporte](mailto:{contactEmail}?subject=Navigation%20element).", diff --git a/src/browser-extension/utils/agent-repository.ts b/src/browser-extension/utils/agent-repository.ts index b84992b..ab9ad9f 100644 --- a/src/browser-extension/utils/agent-repository.ts +++ b/src/browser-extension/utils/agent-repository.ts @@ -39,3 +39,14 @@ export const removeAllAgents = async (): Promise => { await updateAgents([]) await browser.storage.local.set({ prompts: [] }) } + +export const findAgentsByHubUrl = async (hubUrl: string): Promise => { + const agents = await findAllAgents() + return agents.filter(a => a.url === hubUrl) +} + +export const removeAgentsByHubUrl = async (hubUrl: string): Promise => { + const agents = await findAllAgents() + const filteredAgents = agents.filter(a => a.url !== hubUrl) + await updateAgents(filteredAgents) +} diff --git a/src/browser-extension/utils/agent.ts b/src/browser-extension/utils/agent.ts index 9720d9e..39fa02b 100644 --- a/src/browser-extension/utils/agent.ts +++ b/src/browser-extension/utils/agent.ts @@ -12,7 +12,6 @@ export abstract class AgentSource { static async loadAgentsFromUrl(url: string): Promise { const agents: Agent[] = [] const manifest = await Agent.findManifest(url) - console.log("Manifest: ", manifest) // comparing with agents-hub for backwards compatibility with environments that haven't fully migrated to tero if (manifest.auth && (manifest.auth.clientId === AgentType.TeroAgent || manifest.auth.clientId === "agents-hub")) { diff --git a/src/common/src/components/chat/ChatInput.vue b/src/common/src/components/chat/ChatInput.vue index 40ba2d6..8d5aedc 100644 --- a/src/common/src/components/chat/ChatInput.vue +++ b/src/common/src/components/chat/ChatInput.vue @@ -473,7 +473,7 @@ defineExpose({
diff --git a/src/common/src/components/chat/ChatMessage.vue b/src/common/src/components/chat/ChatMessage.vue index 187c3a9..a70d684 100644 --- a/src/common/src/components/chat/ChatMessage.vue +++ b/src/common/src/components/chat/ChatMessage.vue @@ -42,7 +42,8 @@ export class ChatUiMessage { minutesSaved?: number, feedbackText?: string, hasPositiveFeedback?: boolean, - stopped?: boolean + stopped?: boolean, + statusUpdates?: StatusUpdate[] ) { this.text = text this.files = files @@ -57,6 +58,8 @@ export class ChatUiMessage { this.feedbackText = feedbackText this.hasPositiveFeedback = hasPositiveFeedback this.stopped = stopped + this.statusUpdates = statusUpdates || [] + this.isStatusComplete = (statusUpdates?.length || 0) > 0 } public addChild(child: ChatUiMessage): void { @@ -257,9 +260,9 @@ const handleNextMessage = () => {
diff --git a/src/common/src/components/chat/ChatStatus.vue b/src/common/src/components/chat/ChatStatus.vue index 27bfecd..84ab2d3 100644 --- a/src/common/src/components/chat/ChatStatus.vue +++ b/src/common/src/components/chat/ChatStatus.vue @@ -1,5 +1,5 @@ -{ + +{ "en": { + "statusProcessing": "Processing", "statusPreModel": "Thinking", "planning": "Planning to run tools", "statusExecutingTool": "Executing {tool}", @@ -115,16 +125,18 @@ const formatStatusAction = (status: StatusUpdate): string => { "analyzed": "Chunks analyzed", "groundingResponse": "Validating response", "groundedResponse": "Response validated", - "withParams": " with params {params}" + "withParams": " with params {params}", + "thoughtProcessMessage": "Thought process" }, "es": { + "statusProcessing": "Procesando", "statusPreModel": "Pensando", "planning": "Planificando ejecutar herramientas", "statusExecutingTool": "Ejecutando {tool}", "statusExecutedTool": "Ejecución de herramienta {tool} finalizada", "documentsRetrieved": "{count} secciones recuperados", "toolError": "Error en la herramienta {tool}", - "endMessage": "Penso durante {time} segundos", + "endMessage": "Pensó durante {time} segundos", "result": "Resultado:", "results": "Resultados {count}:", "retrieving": "Recuperando secciones", @@ -134,7 +146,8 @@ const formatStatusAction = (status: StatusUpdate): string => { "groundingResponse": "Validando respuesta", "groundedResponse": "Respuesta validada", "description": "Descripción", - "withParams": " con los siguientes parametros {params}" + "withParams": " con los siguientes parametros {params}", + "thoughtProcessMessage": "Proceso de pensamiento" } -} - +} + diff --git a/src/common/src/components/common/FileInput.vue b/src/common/src/components/common/FileInput.vue index 4e65f1b..116f2aa 100644 --- a/src/common/src/components/common/FileInput.vue +++ b/src/common/src/components/common/FileInput.vue @@ -39,6 +39,14 @@ const formattedFileAccept = computed(() => { return props.allowedExtensions.map((ext) => `.${ext}`).join(',') }) +const allowMultiple = computed(() => { + return props.maxFiles === -1 || props.maxFiles > 1 +}) + +const isMaxFilesReached = computed(() => { + return props.maxFiles !== -1 && props.attachedFiles.length >= props.maxFiles +}) + const isFileExtensionSupported = (filename: string): boolean => { const extension = filename.split('.').pop()?.toLowerCase() || '' return props.allowedExtensions.includes(extension) @@ -154,22 +162,24 @@ defineExpose({ diff --git a/src/frontend/src/components/agent/AgentEditorPanel.vue b/src/frontend/src/components/agent/AgentEditorPanel.vue index ad36b64..214a85a 100644 --- a/src/frontend/src/components/agent/AgentEditorPanel.vue +++ b/src/frontend/src/components/agent/AgentEditorPanel.vue @@ -2,46 +2,38 @@ import { onMounted, ref, computed, watch } from 'vue' import { useRoute, onBeforeRouteUpdate, useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' -import { Agent, ApiService, LlmModel, LlmTemperature, ReasoningEffort, LlmModelType, AgentToolConfig, AutomaticAgentField, Team, TestCase, TestCaseResult, TestCaseResultStatus, TestSuiteRun, TestSuiteRunStatus, LlmModelVendor } from '@/services/api' -import { IconPlayerPlay, IconPencil, IconTrash, IconDownload, IconUpload } from '@tabler/icons-vue' -import TestCaseStatus from './TestCaseStatus.vue' -import { AnimationEffect } from '../../../../common/src/utils/animations' +import { Agent, ApiService, LlmModel, AgentToolConfig, AutomaticAgentField, Team, TestCase, TestSuiteRun } from '@/services/api' +import { IconPlayerPlay, IconPencil, IconDownload, IconUpload, IconListDetails } from '@tabler/icons-vue' import { useErrorHandler } from '@/composables/useErrorHandler' import { useAgentStore } from '@/composables/useAgentStore' import { useAgentPromptStore } from '@/composables/useAgentPromptStore' +import { useTestCaseStore } from '@/composables/useTestCaseStore' import { loadUserProfile } from '@/composables/useUserProfile' import { AgentPrompt, UploadedFile } from '../../../../common/src/utils/domain' -import moment from 'moment' -import openAiIcon from '@/assets/images/open-ai.svg' -import googleIcon from '@/assets/images/gemini.svg' -import anthropicIcon from '@/assets/images/anthropic.svg' - +import { AgentTestcaseChatUiMessage } from './AgentTestcaseChatMessage.vue' const props = defineProps<{ selectedThreadId: number - selectedTestCaseId?: number loadingTests?: boolean editingTestcase?: boolean - runningTests?: boolean testCases?: TestCase[] - testCaseResults?: TestCaseResult[] - testSuiteRun?: TestSuiteRun + isComparingResultWithTestSpec?: boolean + testSpecMessages?: AgentTestcaseChatUiMessage[] }>() const emit = defineEmits<{ - (e: 'selectTestCase', testCaseId: number | undefined): void (e: 'showTestCaseEditor', show: boolean): void (e: 'editingTestcase', editing: boolean): void - (e: 'deleteTestCase', testCaseThreadId: number): void (e: 'runTests'): void (e: 'runSingleTest', testCaseId: number): void - (e: 'newTestCase', testCase: TestCase): void (e: 'importAgent' ): void + (e: 'selectExecution', execution: TestSuiteRun): void }>() const { t } = useI18n() const { handleError } = useErrorHandler() const { setCurrentAgent } = useAgentStore() +const { testCasesStore } = useTestCaseStore() const api = new ApiService() const route = useRoute() @@ -54,16 +46,6 @@ const backendAgent = ref() const isSaving = ref(false) const models = ref([]) const toolConfigs = ref([]) -const temperatures = [ - { name: t('preciseTemperature'), value: LlmTemperature.PRECISE }, - { name: t('neutralTemperature'), value: LlmTemperature.NEUTRAL }, - { name: t('creativeTemperature'), value: LlmTemperature.CREATIVE } -] -const reasoningEfforts = [ - { name: t('lowEffort'), value: ReasoningEffort.LOW }, - { name: t('mediumEffort'), value: ReasoningEffort.MEDIUM }, - { name: t('highEffort'), value: ReasoningEffort.HIGH } -] const isGenerating = ref({ name: false, description: false, @@ -81,10 +63,8 @@ const teams = ref([]) const defaultTeams = ref([new Team(0, t('private')), new Team(1, t('global'))]) const selectedTeam = ref(null) const activeTab = ref('0') -const activeTestCaseMenuId = ref(null) -const deletingTestCaseId = ref(null) -const renamingTestCaseId = ref(null) const showImportAgent = ref(false) +const showPastExecutions = ref(false) const loadAgentData = async (agentIdStr: string) => { const agentId = parseInt(agentIdStr) @@ -104,10 +84,6 @@ const loadAgentData = async (agentIdStr: string) => { } } -const findSelectedModel = (): LlmModel | undefined => { - return models.value.find((m) => m.id === agent.value?.modelId) -} - const loadPromptStarters = async (agentId: number) => { await loadAgentPrompts(agentId) starters.value = agentsPromptStore.prompts.filter(p => p.starter) || [] @@ -270,74 +246,17 @@ const onUpdateToolConfigs = async () => { toolConfigs.value = await api.findAgentToolConfigs(agent.value!.id) } -const selectTestCase = (testCaseId: number) => { - emit('selectTestCase', testCaseId) -} - -const onEditTests = () => { - if (!props.testCases?.length && props.editingTestcase) { - return +const onTestCaseDeleted = () => { + if (testCasesStore.testCases.length === 0 && !props.editingTestcase) { + emit('editingTestcase', true) } - emit('editingTestcase', !props.editingTestcase) -} - -const toggleTestCaseMenu = (testCaseId: number) => { - activeTestCaseMenuId.value = activeTestCaseMenuId.value === testCaseId ? null : testCaseId -} - -const closeTestCaseMenu = () => { - activeTestCaseMenuId.value = null -} - -const onDeleteTestCase = (testCase: TestCase) => { - deletingTestCaseId.value = testCase.thread.id - closeTestCaseMenu() -} - -const onConfirmDeleteTestCase = async () => { - try { - await api.deleteTestCase(agent.value!.id, deletingTestCaseId.value!) - - emit('deleteTestCase', deletingTestCaseId.value!) - deletingTestCaseId.value = null - } catch (error) { - handleError(error) - } -} - -const onRenameTestCase = (testCase: TestCase) => { - renamingTestCaseId.value = testCase.thread.id - closeTestCaseMenu() -} - -const onSaveTestCaseName = async (newName: string) => { - try { - const testCase = props.testCases?.find(tc => tc.thread.id === renamingTestCaseId.value) - const updatedTestCase = await api.updateTestCase(agent.value!.id, renamingTestCaseId.value!, newName) - testCase!.thread.name = updatedTestCase.thread.name - renamingTestCaseId.value = null - } catch (error) { - handleError(error) - } -} - -const onCancelRenameTestCase = () => { - renamingTestCaseId.value = null -} - -const onCancelDeleteTestCase = () => { - deletingTestCaseId.value = null -} - -const findTestCaseResult = (testCaseId: number) => { - return props.testCaseResults?.find((result) => result.testCaseId === testCaseId) } watch(activeTab, (newVal) => { emit('showTestCaseEditor', newVal === '1') + emit('editingTestcase', newVal === '1') }) -const modelType = computed(() => findSelectedModel()?.modelType) const isSelectedPublicTeam = computed(() => selectedTeam.value != null && selectedTeam.value > 0) const shareDialogTranslationKey = computed(() => { @@ -360,19 +279,6 @@ const shareDialogTranslationParams = computed(() => ({ team: findTeam(selectedTeam.value!)?.name })) -const onNewTestCase = async () => { - try { - const testCase = await api.addTestCase(agent.value!.id) - emit('newTestCase', testCase) - } catch (e) { - handleError(e) - } -} - -const countResultWithStatus = (status: TestCaseResultStatus) => { - return props.testCaseResults?.filter((result) => result.status === status).length || 0 -} - const exportAgent = async () => { try { const response = await api.exportAgent(agent.value!.id) @@ -398,38 +304,21 @@ const onImportAgent = async (file: UploadedFile) => { } } -// Flattens grouped models: showVendor is true only for the first model of each vendor to render logos once per group. -const flatOptions = computed(() => { - const models = groupedModelsByVendor(); - return models.flatMap((vendorModels) => vendorModels.map((m, i) => ({ ...m, vendor: m.modelVendor, showVendor: i === 0 }))); -}); - -const groupedModelsByVendor = () => { - const map = new Map(); - - for (const model of models.value) { - if (!map.has(model.modelVendor)) { - map.set(model.modelVendor, []); - } - map.get(model.modelVendor)!.push(model); - } - - return Array.from(map.values()); +const onSelectExecution = (execution: TestSuiteRun) => { + emit('selectExecution', execution) + showPastExecutions.value = false } - -const vendorLogos: Record = { - [LlmModelVendor.OPENAI]: openAiIcon, - [LlmModelVendor.GOOGLE]: googleIcon, - [LlmModelVendor.ANTHROPIC]: anthropicIcon -} - -{ + +{ "en": { - "shareTooltip": "Share", - "unshareTooltip": "Unshare", - "editAgentTitle": "Edit {name}", "nameLabel": "Name", "descriptionLabel": "Description", - "modelLabel": "Model", "systemPromptLabel": "Instructions", "saving": "Saving...", "namePlaceholder": "Enter a name for the agent", "descriptionPlaceholder": "What does this agent do?", "systemPromptPlaceholder": "Write the instructions for this agent", - "public": "Public", "private": "Private", "shareConfirmationTitle": "Do you want to make this agent public?", "shareInvalidAttrs": "To share the agent you need to specify {invalidAttrs}.", @@ -734,49 +497,20 @@ const vendorLogos: Record = { "changeTeamConfirmationTitle": "Change the team of this agent from {oldTeam} to {newTeam}?", "changeTeamConfirmationMessage": "When you change the team of an agent only the users in the new team will be able to use it.", "changeTeam": "Change team", - "temperatureLabel": "Temperature", - "reasoningEffortLabel": "Reasoning", - "preciseTemperature": "Precise", - "neutralTemperature": "Neutral", - "creativeTemperature": "Creative", - "lowEffort": "Low", - "mediumEffort": "Medium", - "highEffort": "High", "editAgentTabTitle": "Edit", "testsTabTitle": "Tests", - "noTestCasesTitle": "You don't have test cases for this agent yet", - "noTestCasesDescription": "Create your first test case to validate that the agent meets the expected requirements.", - "editTestsButton": "Edit", - "runTestsButton": "Run all", - "runSingleTestMenuItem": "Run", - "editTestCaseTooltip": "Edit", - "renameTestCaseTooltip": "Rename", - "deleteTestCaseTooltip": "Delete", - "deleteTestCaseConfirmation": "Delete {testCaseName}?", - "newTestCaseButton": "Add", - "lastExecution": "Last execution", - "noTestCasesResults": "No test cases results yet", - "running": "Running...", "exportAgent": "Export", "importAgent": "Import", - "success": "Success", - "failure": "Failed", - "error": "Error running", - "skipped": "Skipped" + "testSpec": "Test case specification" }, "es": { - "shareTooltip": "Compartir", - "unshareTooltip": "Dejar de compartir", - "editAgentTitle": "Editar {name}", "nameLabel": "Nombre", "descriptionLabel": "Descripción", - "modelLabel": "Modelo", "systemPromptLabel": "Instrucciones", "saving": "Guardando...", "namePlaceholder": "Ingresa el nombre del agente", "descriptionPlaceholder": "¿Qué hace este agente?", "systemPromptPlaceholder": "Escribe las instrucciones de este agente", - "public": "Público", "private": "Privado", "shareConfirmationTitle": "¿Quieres hacer este agente público?", "shareConfirmationMessageGlobal": "Cuando haces un agente público, este será visible en la página de inicio de Tero para que todos podamos beneficiarnos de lo que has creado.\n\nAdemás, todas las modificaciones futuras al agente estarán disponibles inmediatamente para el resto de sus usuarios.", @@ -801,37 +535,14 @@ const vendorLogos: Record = { "changeTeamConfirmationTitle": "¿Quieres cambiar el equipo de este agente de {oldTeam} a {newTeam}?", "changeTeamConfirmationMessage": "Cuando cambias el equipo de un agente, solo lo podrán usar los usuarios del nuevo equipo.", "changeTeam": "Cambiar equipo", - "temperatureLabel": "Temperatura", - "reasoningEffortLabel": "Razonamiento", - "preciseTemperature": "Preciso", - "neutralTemperature": "Neutro", - "creativeTemperature": "Creativo", - "lowEffort": "Bajo", - "mediumEffort": "Medio", - "highEffort": "Alto", "editAgentTabTitle": "Editar", "testsTabTitle": "Tests", - "noTestCasesTitle": "Aún no tienes test cases para este agente", - "noTestCasesDescription": "Crea tu primer test case para validar que el agente cumple los requisitos esperados.", - "editTestsButton": "Editar", - "runTestsButton": "Ejecutar todos", - "runSingleTestMenuItem": "Ejecutar", - "editTestCaseTooltip": "Editar", - "renameTestCaseTooltip": "Renombrar", - "deleteTestCaseTooltip": "Eliminar", - "deleteTestCaseConfirmation": "¿Eliminar {testCaseName}?", - "newTestCaseButton": "Agregar", - "lastExecution": "Última ejecución", - "noTestCasesResults": "No hay resultados de ejecución aún", - "running": "Ejecutando...", "exportAgent": "Exportar", "importAgent": "Importar", - "success": "Pasó", - "failure": "Falló", - "error": "Error al ejecutar", - "skipped": "Omitido" + "testSpec": "Especificación del test case" } -} +} + diff --git a/src/frontend/src/components/agent/AgentModelSelect.vue b/src/frontend/src/components/agent/AgentModelSelect.vue new file mode 100644 index 0000000..7d56f36 --- /dev/null +++ b/src/frontend/src/components/agent/AgentModelSelect.vue @@ -0,0 +1,198 @@ + + + + + + { + "en": { + "showAllModels": "Show all", + "modelLabel": "Model" + }, + "es": { + "showAllModels": "Mostrar todos", + "modelLabel": "Modelo" + } + } + + diff --git a/src/frontend/src/components/agent/AgentPastExecutionsDialog.vue b/src/frontend/src/components/agent/AgentPastExecutionsDialog.vue new file mode 100644 index 0000000..aaf1765 --- /dev/null +++ b/src/frontend/src/components/agent/AgentPastExecutionsDialog.vue @@ -0,0 +1,151 @@ + + + + + +{ + "en": { + "pastExecutionsTitle": "Past Executions", + "noPastExecutions": "No past executions found", + "deleteConfirmDescription": "Delete execution?" + }, + "es": { + "pastExecutionsTitle": "Ejecuciones Pasadas", + "noPastExecutions": "No se encontraron ejecuciones pasadas", + "deleteConfirmDescription": "¿Eliminar ejecución?" + } +} + diff --git a/src/frontend/src/components/agent/AgentTestcaseChatMessage.vue b/src/frontend/src/components/agent/AgentTestcaseChatMessage.vue index c1d4ee0..748160b 100644 --- a/src/frontend/src/components/agent/AgentTestcaseChatMessage.vue +++ b/src/frontend/src/components/agent/AgentTestcaseChatMessage.vue @@ -21,13 +21,15 @@ export class AgentTestcaseChatUiMessage{ statusUpdates: StatusUpdate[] = [] isStatusComplete: boolean = false - constructor(text: string, isUser: boolean, isPlaceholder: boolean, id?: number){ + constructor(text: string, isUser: boolean, isPlaceholder: boolean, id?: number, statusUpdates?: StatusUpdate[]){ this.uuid = uuidv4() this.text = text this.isUser = isUser this.isPlaceholder = isPlaceholder this.id = id this.isStreaming = false + this.statusUpdates = statusUpdates || [] + this.isStatusComplete = (statusUpdates?.length || 0) > 0 } public addStatusUpdate(statusUpdate: StatusUpdate): void { @@ -99,7 +101,8 @@ const { t } = useI18n() } -{ + +{ "en": { "editMessageButton": "Edit message" }, @@ -107,4 +110,4 @@ const { t } = useI18n() "editMessageButton": "Editar mensaje" } } - \ No newline at end of file + diff --git a/src/frontend/src/components/agent/AgentTestcasePanel.vue b/src/frontend/src/components/agent/AgentTestcasePanel.vue index 6a2c763..29ae3c7 100644 --- a/src/frontend/src/components/agent/AgentTestcasePanel.vue +++ b/src/frontend/src/components/agent/AgentTestcasePanel.vue @@ -1,51 +1,61 @@ @@ -276,30 +370,37 @@ defineExpose({
-
- {{ testCaseId ? t('noTestCaseResultsDescription') : t('noRunResultsDescription') }} -
-
-
+
@@ -334,9 +435,9 @@ defineExpose({
+ class="h-40 min-h-40 flex-shrink-0 border-t-1 border-auxiliar-gray p-4 overflow-y-auto">
- +
{{ t('phaseExecuting') }} @@ -356,7 +457,8 @@ defineExpose({ -{ + +{ "en": { "newTestCaseTitle": "Create a new test case", "editTestCaseTitle": "Editing: {testCaseName}", @@ -368,14 +470,15 @@ defineExpose({ "successDescription": "The agent's response matched the expected output. No formatting or content deviations were detected.", "failureDescription": "The agent's response did not match the expected output. Formatting or content deviations were detected.", "errorDescription": "An error occurred while running the test case", - "noRunResultsDescription": "Run the full suite to validate all defined test cases.", - "noTestCaseResultsDescription": "Run the test to compare the agent's response with the expected output.", - "obtainedResultLabel": "Obtained result", "runningTestCase": "Running: {testCaseName}", "testCaseResult": "Test case result: {testCaseName}", "testRunning": "Test is running...", "phaseExecuting": "Executing test case...", - "phaseEvaluating": "Evaluating response..." + "phaseEvaluating": "Evaluating response...", + "compare": "Compare with specification", + "closeCompare": "Close compare", + "compareDisabledReasonTestModified": "Compare is disabled because the test case was modified after the test execution", + "compareDisabledReasonTestDeleted": "Compare is disabled because the test case was deleted" }, "es": { "newTestCaseTitle": "Crea un nuevo test case", @@ -388,13 +491,15 @@ defineExpose({ "successDescription": "La respuesta del agente coincidió con la salida esperada. No se detectaron desvíos de formato o contenido.", "failureDescription": "La respuesta del agente no coincidió con la salida esperada. Se detectaron desvíos de formato o contenido.", "errorDescription": "Ocurrió un error al ejecutar el test case", - "noRunResultsDescription": "Corre la suite completa para validar todos los test cases definidos.", - "noTestCaseResultsDescription": "Ejecuta el test para comparar la respuesta del agente con la esperada.", - "obtainedResultLabel": "Resultado obtenido", "runningTestCase": "Ejecutando: {testCaseName}", "testCaseResult": "Resultado del test case: {testCaseName}", "testRunning": "El test está ejecutándose...", "phaseExecuting": "Ejecutando test case...", - "phaseEvaluating": "Evaluando respuesta..." + "phaseEvaluating": "Evaluando respuesta...", + "compare": "Comparar con la especificación", + "closeCompare": "Cerrar comparación", + "compareDisabledReasonTestModified": "La comparación está deshabilitada porque el test case fue modificado después de la ejecución del test", + "compareDisabledReasonTestDeleted": "La comparación está deshabilitada porque el test case fue eliminado" } -} +} + diff --git a/src/frontend/src/components/agent/AgentTestcaseResults.vue b/src/frontend/src/components/agent/AgentTestcaseResults.vue new file mode 100644 index 0000000..26f46aa --- /dev/null +++ b/src/frontend/src/components/agent/AgentTestcaseResults.vue @@ -0,0 +1,106 @@ + + + + + +{ + "en": { + "running": "Running...", + "runResultTitle": "Results from {'<'}b>{date}{'<'}/b>", + "stopSuiteRun": "Stop" + }, + "es": { + "running": "Ejecutando...", + "runResultTitle": "Resultados de {'<'}b>{date}{'<'}/b>", + "stopSuiteRun": "Detener" + } +} + diff --git a/src/frontend/src/components/agent/AgentTestcaseResultsSkeleton.vue b/src/frontend/src/components/agent/AgentTestcaseResultsSkeleton.vue new file mode 100644 index 0000000..72c983e --- /dev/null +++ b/src/frontend/src/components/agent/AgentTestcaseResultsSkeleton.vue @@ -0,0 +1,39 @@ + diff --git a/src/frontend/src/components/agent/AgentTestcaseRunStatus.vue b/src/frontend/src/components/agent/AgentTestcaseRunStatus.vue new file mode 100644 index 0000000..f296905 --- /dev/null +++ b/src/frontend/src/components/agent/AgentTestcaseRunStatus.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/frontend/src/components/agent/TestCaseStatus.vue b/src/frontend/src/components/agent/AgentTestcaseStatus.vue similarity index 98% rename from src/frontend/src/components/agent/TestCaseStatus.vue rename to src/frontend/src/components/agent/AgentTestcaseStatus.vue index a4ea1c1..5b2fe34 100644 --- a/src/frontend/src/components/agent/TestCaseStatus.vue +++ b/src/frontend/src/components/agent/AgentTestcaseStatus.vue @@ -67,7 +67,8 @@ const statusConfig = computed(() => {
-{ + +{ "en": { "running": "Running", "success": "Success", @@ -84,5 +85,5 @@ const statusConfig = computed(() => { "pending": "Pendiente", "skipped": "Omitido" } -} - +} + diff --git a/src/frontend/src/components/agent/AgentTestcases.vue b/src/frontend/src/components/agent/AgentTestcases.vue new file mode 100644 index 0000000..6df59db --- /dev/null +++ b/src/frontend/src/components/agent/AgentTestcases.vue @@ -0,0 +1,238 @@ + + + + + +{ + "en": { + "newTestCaseButton": "Add", + "renameTestCaseTooltip": "Rename", + "cloneTestCaseTooltip": "Clone", + "deleteTestCaseTooltip": "Delete", + "deleteTestCaseConfirmation": "Delete {testCaseName}?", + "runTestCaseMenuItem": "Run", + "runTestsButton": "Run all", + "pastExecutions": "Past Executions", + "noTestCasesTitle": "You don't have test cases for this agent yet", + "noTestCasesDescription": "Create your first test case to validate that the agent meets the expected requirements.", + "configureEvaluator": "Configure agent evaluator", + "configureTestEvaluator": "Configure test evaluator" + }, + "es": { + "newTestCaseButton": "Agregar", + "renameTestCaseTooltip": "Renombrar", + "cloneTestCaseTooltip": "Clonar", + "deleteTestCaseTooltip": "Eliminar", + "deleteTestCaseConfirmation": "¿Eliminar {testCaseName}?", + "runTestCaseMenuItem": "Ejecutar", + "runTestsButton": "Ejecutar todos", + "pastExecutions": "Ejecuciones pasadas", + "noTestCasesTitle": "Aún no tienes test cases para este agente", + "noTestCasesDescription": "Crea tu primer test case para validar que el agente cumple los requisitos esperados.", + "configureEvaluator": "Configurar evaluador del agente", + "configureTestEvaluator": "Configurar evaluador del test" + } +} + diff --git a/src/frontend/src/components/agent/AgentToolConfigEditor.vue b/src/frontend/src/components/agent/AgentToolConfigEditor.vue index d70649d..8a68f24 100644 --- a/src/frontend/src/components/agent/AgentToolConfigEditor.vue +++ b/src/frontend/src/components/agent/AgentToolConfigEditor.vue @@ -116,11 +116,15 @@ const toolMessage = computed(() => { return ret != toolMessageKey ? ret : null }) -const isFileProperty = (toolProp: JSONSchema7Definition) : boolean => { +const isFileArrayProperty = (toolProp: JSONSchema7Definition) : boolean => { const toolPropSchema = js7(toolProp) return toolPropSchema?.type === 'array' && (js7(toolPropSchema?.items)?.$ref?.endsWith('/File') ?? false) } +const isFileProperty = (toolProp: JSONSchema7Definition) : boolean => { + return js7(toolProp)?.$ref?.endsWith('/File') ?? false +} + const isEnumProperty = (toolProp: JSONSchema7Definition) : boolean => { const toolPropSchema = js7(toolProp)! return toolPropSchema.type === 'array' && js7(toolPropSchema.items)?.enum !== undefined @@ -259,20 +263,43 @@ class ValidationErrors extends Error {
-
+
- +
- + {{ translateToolPropertyName(toolConfig.tool.id, propName) }}
- +
- +
diff --git a/src/frontend/src/components/agent/AgentToolFilesEditor.vue b/src/frontend/src/components/agent/AgentToolFilesEditor.vue index e0ff6a5..2dfa307 100644 --- a/src/frontend/src/components/agent/AgentToolFilesEditor.vue +++ b/src/frontend/src/components/agent/AgentToolFilesEditor.vue @@ -14,6 +14,8 @@ const props = defineProps<{ configuredTool: boolean contactEmail?: string viewMode?: boolean + allowedExtensions?: string[] + maxFiles?: number onBeforeFileUpload: (filesCount: number) => Promise onAfterFileRemove: (filesCount: number) => Promise }>() @@ -21,6 +23,9 @@ const props = defineProps<{ const { t } = useI18n() const api = new ApiService() const { handleError } = useErrorHandler() + +const defaultAllowedExtensions = ['pdf', 'txt', 'md', 'csv', 'xlsx', 'xls'] +const allowedExtensions = computed(() => props.allowedExtensions ?? defaultAllowedExtensions) const attachedFilesError = ref({ title: '', message: '' @@ -188,12 +193,8 @@ const filteredFiles = computed(() => props.viewMode ? toolFiles.value.filter(f =
-
- -
-
- -
+ +
diff --git a/src/frontend/src/components/agent/EvaluatorConfigurationModal.vue b/src/frontend/src/components/agent/EvaluatorConfigurationModal.vue new file mode 100644 index 0000000..4252dd4 --- /dev/null +++ b/src/frontend/src/components/agent/EvaluatorConfigurationModal.vue @@ -0,0 +1,197 @@ + + + + + +{ + "en": { + "evaluator": "evaluator", + "evaluatorTitle": "Agent evaluator", + "testCaseEvaluatorNote": "This evaluator configuration will only be used for this specific test case.", + "modelLabel": "Model", + "instructionsLabel": "Instructions", + "instructionsPlaceholder": "Write the instructions for this evaluator", + "availableVariablesNote": "You can use these variables in your instructions:\n• {'{'}{'{'}inputs{'}'}{'}'} - user message\n• {'{'}{'{'}reference_outputs{'}'}{'}'} - expected response\n• {'{'}{'{'}outputs{'}'}{'}'} - actual agent response", + "cancel": "Cancel", + "confirm": "Confirm" + + }, + "es": { + "evaluator": "evaluador", + "evaluatorTitle": "Evaluador del agente", + "testCaseEvaluatorNote": "Esta configuración del evaluador solo se usará para este caso de prueba específico.", + "modelLabel": "Modelo", + "instructionsLabel": "Instrucciones", + "instructionsPlaceholder": "Escribe las instrucciones para este evaluador", + "availableVariablesNote": "Puedes usar estas variables en tus instrucciones:\n• {'{'}{'{'}inputs{'}'}{'}'} - mensaje del usuario\n• {'{'}{'{'}reference_outputs{'}'}{'}'} - respuesta esperada\n• {'{'}{'{'}outputs{'}'}{'}'} - respuesta actual del agente", + "cancel": "Cancelar", + "confirm": "Confirmar" + + } +} + diff --git a/src/frontend/src/components/agent/LlmModelSettings.vue b/src/frontend/src/components/agent/LlmModelSettings.vue new file mode 100644 index 0000000..795e5e4 --- /dev/null +++ b/src/frontend/src/components/agent/LlmModelSettings.vue @@ -0,0 +1,92 @@ + + + + + +{ + "en": { + "temperatureLabel": "Temperature", + "reasoningEffortLabel": "Reasoning", + "preciseTemperature": "Precise", + "neutralTemperature": "Neutral", + "creativeTemperature": "Creative", + "lowEffort": "Low", + "mediumEffort": "Medium", + "highEffort": "High" + }, + "es": { + "temperatureLabel": "Temperatura", + "reasoningEffortLabel": "Razonamiento", + "preciseTemperature": "Preciso", + "neutralTemperature": "Neutro", + "creativeTemperature": "Creativo", + "lowEffort": "Bajo", + "mediumEffort": "Medio", + "highEffort": "Alto" + } +} + diff --git a/src/frontend/src/components/chat/ChatPanel.vue b/src/frontend/src/components/chat/ChatPanel.vue index 8278894..c0fa204 100644 --- a/src/frontend/src/components/chat/ChatPanel.vue +++ b/src/frontend/src/components/chat/ChatPanel.vue @@ -75,7 +75,7 @@ function mapThreadMessageToChatUi( parent?: ChatUiMessage ): ChatUiMessage { const files = threadMsg.files || [] as UploadedFile[] - const uiMsg = new ChatUiMessage(threadMsg.text, files, threadMsg.origin === ThreadMessageOrigin.USER, true, true, [], parent, threadMsg.id, threadMsg.minutesSaved, threadMsg.feedbackText, threadMsg.hasPositiveFeedback, threadMsg.stopped); + const uiMsg = new ChatUiMessage(threadMsg.text, files, threadMsg.origin === ThreadMessageOrigin.USER, true, true, [], parent, threadMsg.id, threadMsg.minutesSaved, threadMsg.feedbackText, threadMsg.hasPositiveFeedback, threadMsg.stopped, threadMsg.statusUpdates); for (const childThread of threadMsg.children) { const childUi = mapThreadMessageToChatUi(childThread, uiMsg) uiMsg.children.push(childUi) @@ -154,6 +154,14 @@ const sendUserMessage = async (text:string, files: UploadedFile[] = [], editMess const parentMessageId = appendMessage(userUIMessage, editMessageId) const answerMsg = reactive(ChatUiMessage.agentMessage(undefined)) userUIMessage.addChild(answerMsg) + + const startTime = new Date() + const initialStatusUpdate: StatusUpdate = { + action: 'statusProcessing', + timestamp: startTime + } + answerMsg.addStatusUpdate(initialStatusUpdate) + await updateChat(chat.value!) await updateAgent(chat.value!.agent.id) try { @@ -245,6 +253,8 @@ const processAnswer = async (answer: AsyncIterable, answerMsg userUIMessage.id = part.userMessage.id userUIMessage.files = part.userMessage.files || [] } else if (part.metadata) { + const lastStatus = answerMsg.statusUpdates[answerMsg.statusUpdates.length - 1] + lastStatus.timestamp = new Date() answerMsg.completeStatus() answerMsg.id = part.metadata.answerMessageId answerMsg.minutesSaved = part.metadata.minutesSaved @@ -261,6 +271,7 @@ const processAnswer = async (answer: AsyncIterable, answerMsg timestamp: new Date() } answerMsg.addStatusUpdate(statusUpdate) + scrollToLastChatMessage() } } } @@ -438,17 +449,13 @@ const handleViewFile = (file: UploadedFile) => { - + { "en": { "authenticationWindowClosed": "The authentication window was closed before completing the process. Please, try again and keep the authentication popup window open until it finishes.", "authenticationCancelled": "The authentication was cancelled. Please, try again and complete the authentication to use this agent.", "agentAnswerError": "I am currently unable to complete your request. You can try again and if the issue persists contact [support](mailto:{contactEmail}?subject=Tero%20issue)", "quotaExceeded": "You have reached the monthly usage quota. Contact [support](mailto:{contactEmail}?subject=Tero%20Monthly%20Limit) to increase your monthly quota or wait for the next month.", - "managePromptsTooltip": "Manage Prompts", - "promptVariablesTitle": "Set prompt variables", - "promptVariablePlaceholder": "Set a value for the variable", - "confirm": "Confirm", "starterText": "Hi! 👋 \n How can I help you?" }, "es": { @@ -456,10 +463,6 @@ const handleViewFile = (file: UploadedFile) => { "authenticationCancelled": "La autenticación fue cancelada. Por favor, inténtelo de nuevo y complete la autenticación para usar este agente o esta herramienta.", "agentAnswerError": "Ahora no puedo completar tu pedido. Puedes intentar de nuevo y si el problema persiste contactar a [soporte](mailto:{contactEmail}?subject=Tero%20issue)", "quotaExceeded": "Ha alcanzado la cuota de uso mensual. Contacte a [soporte](mailto:{contactEmail}?subject=Tero%20Monthly%20Limit) para aumentar su cuota mensual o espere al próximo mes.", - "managePromptsTooltip": "Administrar Prompts", - "promptVariablesTitle": "Configura las variables del prompt", - "promptVariablePlaceholder": "Ingresa un valor para la variable", - "confirm": "Confirmar", "starterText": "Hola! 👋 \n ¿Cómo puedo ayudarte?" } } diff --git a/src/frontend/src/components/common/NotificationItem.vue b/src/frontend/src/components/common/NotificationItem.vue index 2ad27fa..555cdb8 100644 --- a/src/frontend/src/components/common/NotificationItem.vue +++ b/src/frontend/src/components/common/NotificationItem.vue @@ -55,7 +55,7 @@ const collapsed = ref(true); "reject": "Reject", "invitationText": "You have been invited to join the team", "acceptText": "By accepting, the leaders of this team will be able to", - "usageMetricsText": "See your usage metrics in AI Console.", + "usageMetricsText": "See your metrics of hours saved with AI and the number of chats.", "editAgentsText": "Edit the agents you publish in this new team.", "acceptButton": "Accept invitation", "rejectButton": "Reject" @@ -65,7 +65,7 @@ const collapsed = ref(true); "reject": "Rechazar", "invitationText": "Has sido invitado para unirte al equipo", "acceptText": "Al aceptar, los lideres de este equipo podrán", - "usageMetricsText": "Ver tus horas IA utilizadas en la consola de IA.", + "usageMetricsText": "Ver las métricas de tus horas ahorradas con IA y el número de chats.", "editAgentsText": "Editar los agentes que publiques en este nuevo equipo.", "acceptButton": "Aceptar invitación", "rejectButton": "Rechazar" diff --git a/src/frontend/src/components/agent/AgentTestcaseMenu.vue b/src/frontend/src/components/common/SimpleMenu.vue similarity index 77% rename from src/frontend/src/components/agent/AgentTestcaseMenu.vue rename to src/frontend/src/components/common/SimpleMenu.vue index 103b8ba..063f87b 100644 --- a/src/frontend/src/components/agent/AgentTestcaseMenu.vue +++ b/src/frontend/src/components/common/SimpleMenu.vue @@ -1,17 +1,12 @@ @@ -418,27 +199,27 @@ onBeforeUnmount(() => { -{ + +{ "en": { "suiteExecutionFailed": "Test suite execution failed", "suiteAlreadyRunning": "Please wait for the test suite to finish running before starting a new execution" @@ -447,4 +228,5 @@ onBeforeUnmount(() => { "suiteExecutionFailed": "Falló la ejecución de la suite de tests", "suiteAlreadyRunning": "Espera a que el test suite termine de correr para lanzar una nueva ejecucion" } -} +} + diff --git a/src/frontend/src/pages/FilePreviewPage.vue b/src/frontend/src/pages/FilePreviewPage.vue index d7aa9e0..268987a 100644 --- a/src/frontend/src/pages/FilePreviewPage.vue +++ b/src/frontend/src/pages/FilePreviewPage.vue @@ -129,7 +129,7 @@ const findFile = async () : Promise<[File, ProcessedContent]> => { })()]) } else { return await Promise.all([api.downloadAgentToolFile(parsedAgentId.value, toolId!, fileId), (async () => { - const docFile = await api.findAgentDocToolFile(parsedAgentId.value, toolId!, fileId) + const docFile = await api.findAgentToolFile(parsedAgentId.value, toolId!, fileId) return new ProcessedContent(docFile.status, docFile.fileProcessor, docFile.processedContent) })()]) } @@ -179,7 +179,7 @@ const reprocess = async () => { const agentId = parsedAgentId.value await api.configureAgentTool(agentId, new AgentToolConfig(toolId!, {advancedFileProcessing: newProcessor === FileProcessor.ENHANCED})) await api.updateAgentToolFile(agentId, toolId!, fileId, new File([], originalFile.value!.name, { type: originalFile.value!.type })) - let toolFile = await api.findAgentDocToolFile(agentId, toolId!, fileId) + let toolFile = await api.findAgentToolFile(agentId, toolId!, fileId) toolFile = await awaitFileProcessingCompletes(toolFile) processedContent.value = toolFile.processedContent const quotaExceeded = await checkQuotaExceeded(toolFile) @@ -194,7 +194,7 @@ const reprocess = async () => { const awaitFileProcessingCompletes = async (toolFile: DocToolFile) => { while (toolFile.status === FileStatus.PENDING) { - toolFile = await api.findAgentDocToolFile(parsedAgentId.value, toolId!, fileId) + toolFile = await api.findAgentToolFile(parsedAgentId.value, toolId!, fileId) if (toolFile.status === FileStatus.PENDING) { await new Promise((resolve) => setTimeout(resolve, 1000)) } diff --git a/src/frontend/src/services/api.ts b/src/frontend/src/services/api.ts index eadec11..1f7fc7e 100644 --- a/src/frontend/src/services/api.ts +++ b/src/frontend/src/services/api.ts @@ -2,6 +2,8 @@ import auth from './auth' import moment from 'moment' import type { JSONSchema7 } from 'json-schema' import { UploadedFile, FileStatus, AgentPrompt } from '../../../common/src/utils/domain' +import type { StatusUpdate } from '../../../common/src/components/chat/ChatMessage.vue' + export class HttpError extends Error { public status: number @@ -90,6 +92,20 @@ export enum ReasoningEffort { HIGH = 'HIGH' } +export class Evaluator { + modelId: string + temperature: LlmTemperature + reasoningEffort: ReasoningEffort + prompt: string + + constructor(modelId: string, temperature: LlmTemperature, reasoningEffort: ReasoningEffort, prompt: string) { + this.modelId = modelId + this.temperature = temperature + this.reasoningEffort = reasoningEffort + this.prompt = prompt + } +} + export enum FileProcessor { BASIC = 'BASIC', ENHANCED = 'ENHANCED' @@ -308,8 +324,9 @@ export class ThreadMessage { hasPositiveFeedback?: boolean files?: UploadedFile[] stopped?: boolean + statusUpdates: StatusUpdate[] = [] - constructor(id: number, text: string, timestamp: Date, origin: ThreadMessageOrigin, children: ThreadMessage[], minutesSaved?: number, feedbackText?: string, hasPositiveFeedback?: boolean, stopped?: boolean) { + constructor(id: number, text: string, timestamp: Date, origin: ThreadMessageOrigin, children: ThreadMessage[], minutesSaved?: number, feedbackText?: string, hasPositiveFeedback?: boolean, stopped?: boolean, statusUpdates?: StatusUpdate[]) { this.id = id this.text = text this.timestamp = timestamp @@ -319,6 +336,7 @@ export class ThreadMessage { this.feedbackText = feedbackText this.hasPositiveFeedback = hasPositiveFeedback this.stopped = stopped + this.statusUpdates = statusUpdates || [] } } @@ -559,13 +577,17 @@ export class TestCaseResult { testSuiteRunId?: number executedAt: Date status: TestCaseResultStatus + testCaseName: string + evaluatorAnalysis?: string - constructor(testCaseId: number, executedAt: Date, status: TestCaseResultStatus, id?: number, testSuiteRunId?: number) { + constructor(testCaseId: number, executedAt: Date, status: TestCaseResultStatus, testCaseName: string, id?: number, testSuiteRunId?: number, evaluatorAnalysis?: string) { this.testCaseId = testCaseId this.executedAt = executedAt this.status = status + this.testCaseName = testCaseName this.testSuiteRunId = testSuiteRunId this.id = id + this.evaluatorAnalysis = evaluatorAnalysis } } @@ -589,7 +611,7 @@ export type TestSuiteExecutionStreamEvent = | { type: 'suite.test.agentMessage.complete'; data: { id: number; text: string } } | { type: 'suite.test.executionStatus'; data: any } | { type: 'suite.test.error'; data: { message: string } } - | { type: 'suite.test.complete'; data: { testCaseId: number; resultId: number; status: string } } + | { type: 'suite.test.complete'; data: { testCaseId: number; resultId: number; status: string; evaluation?: any } } | { type: 'suite.complete'; data: { suiteRunId: number; status: string; totalTests: number; passed: number; failed: number; errors: number; skipped: number } } | { type: 'suite.error'; data: {} } @@ -779,7 +801,7 @@ export class ApiService { return await this.fetchJson(`/agents/${agentId}/tools/${toolId}/files`) } - async findAgentDocToolFile(agentId: number, toolId: string, fileId: number): Promise { + async findAgentToolFile(agentId: number, toolId: string, fileId: number): Promise { return await this.fetchJson(`/agents/${agentId}/tools/${toolId}/files/${fileId}`) } @@ -812,6 +834,14 @@ export class ApiService { await this.delete(`/agents/${agentId}/tools/${toolId}/files/${fileId}`) } + async findAgentEvaluator(agentId: number): Promise { + return await this.fetchJson(`/agents/${agentId}/evaluator`) + } + + async saveAgentEvaluator(agentId: number, evaluator: Evaluator): Promise { + return await this.put(`/agents/${agentId}/evaluator`, evaluator) + } + async findAgentPrompts(agentId: number): Promise { return await this.fetchJson(`/agents/${agentId}/prompts`) } @@ -844,6 +874,10 @@ export class ApiService { return await this.put(`/agents/${agentId}/tests/${testCaseId}`, { name }) } + async cloneTestCase(agentId: number, testCaseId: number): Promise { + return await this.post(`/agents/${agentId}/tests/${testCaseId}/clone`) + } + async deleteTestCase(agentId: number, testCaseId: number) { await this.delete(`/agents/${agentId}/tests/${testCaseId}`) } @@ -852,6 +886,14 @@ export class ApiService { return await this.fetchJson(`/agents/${agentId}/tests/${testCaseId}/messages`) } + async findTestCaseEvaluator(agentId: number, testCaseId: number): Promise { + return await this.fetchJson(`/agents/${agentId}/tests/${testCaseId}/evaluator`) + } + + async saveTestCaseEvaluator(agentId: number, testCaseId: number, config: Evaluator): Promise { + return await this.put(`/agents/${agentId}/tests/${testCaseId}/evaluator`, config) + } + async addTestCaseMessage(agentId: number, testCaseId: number, message: TestCaseNewThreadMessage): Promise { return await this.fetchJson(`/agents/${agentId}/tests/${testCaseId}/messages`, 'POST', message) } @@ -874,6 +916,10 @@ export class ApiService { } } + async stopTestSuiteRun(agentId: number, suiteRunId: number): Promise { + await this.post(`/agents/${agentId}/tests/runs/${suiteRunId}/stop`) + } + async findTestSuiteRuns(agentId: number, limit: number = 20, offset: number = 0): Promise { const searchParams = new URLSearchParams({ limit: limit.toString(), offset: offset.toString() }); const suiteRuns = await this.fetchJson(`/agents/${agentId}/tests/runs?${searchParams.toString()}`) @@ -883,8 +929,8 @@ export class ApiService { private parseTestSuiteRunDates(suiteRun: any): TestSuiteRun { return { ...suiteRun, - executedAt: new Date(suiteRun.executedAt), - completedAt: suiteRun.completedAt ? new Date(suiteRun.completedAt) : undefined + executedAt: moment.utc(suiteRun.executedAt).toDate(), + completedAt: suiteRun.completedAt ? moment.utc(suiteRun.completedAt).toDate() : undefined } } @@ -892,6 +938,10 @@ export class ApiService { return await this.fetchJson(`/agents/${agentId}/tests/runs/${suiteRunId}/results`) } + async deleteTestSuiteRun(agentId: number, suiteRunId: number): Promise { + await this.delete(`/agents/${agentId}/tests/runs/${suiteRunId}`) + } + async findTestSuiteRunResultMessages(agentId: number, suiteRunId: number, resultId: number): Promise { return await this.fetchJson(`/agents/${agentId}/tests/runs/${suiteRunId}/results/${resultId}/messages`) } diff --git a/src/sample.env b/src/sample.env index 12d84e2..ad94169 100644 --- a/src/sample.env +++ b/src/sample.env @@ -8,7 +8,7 @@ OPENID_CLIENT_ID=tero OPENID_SCOPE=openid profile # You can uncomment and set this in case you need to use a different openid url for frontend (this is might be necessary for production deployments) # FRONTEND_OPENID_URL= -# This configuration allows you to only allow to login to the given list of users . +# This configuration allows you to only allow to login to the given list of users. # Specify the users in a comma separated list of usernames, eg: test@test.com,test2@test.com. # This is particularly handy when you use SSO to authenticate users but you want only to give access to some of them (for example in a dev environment). ALLOWED_USERS= @@ -36,9 +36,8 @@ AZURE_MODEL_DEPLOYMENTS=gpt-5-nano:gpt-5-nano,gpt-5-mini:gpt-5-mini,gpt-5:gpt-5, TEMPERATURES=PRECISE:0,NEUTRAL:0.7,CREATIVE:1 # Model for internal generation tasks (e.g., auto-generating agent fields, chat names, cost estimation) INTERNAL_GENERATOR_MODEL=gpt-4o-mini -INTERNAL_GENERATOR_TEMPERATURE=0.7 # Model for internal evaluations (e.g., determining if agent tests pass by comparing expected vs actual results). If not set, will use INTERNAL_GENERATOR_MODEL -INTERNAL_EVALUATOR_MODEL=gpt-4o-mini +INTERNAL_EVALUATOR_MODEL=gpt-5-nano # Temperature for internal evaluations. If not set, will use INTERNAL_GENERATOR_TEMPERATURE INTERNAL_EVALUATOR_TEMPERATURE=0.3 MONTHLY_USD_LIMIT_DEFAULT=10 From 38988c715a0824c55b3d0ebacaa28cff75dad066 Mon Sep 17 00:00:00 2001 From: rabelenda Date: Wed, 3 Dec 2025 16:10:50 -0300 Subject: [PATCH 11/17] Add editor role to allow some users to edit agents but not see team metrics and manage teams Additionally: - Fix issue that prevents users that didn't create an agent configured with jira or mcp tool with oauth, to use the agent - Add environment variable that allows to disable publishing to global team to any user - Update backend dependencies - Fix authentication issue when tabs are pinned - Fix token renewal in chrome extension - Thought process UI improvement --- ...8259088e28cf-remove_client_info_user_id.py | 26 + ...51124-6733defeb310-add_team_editor_role.py | 36 + src/backend/poetry.lock | 741 ++++++++++-------- src/backend/pyproject.toml | 50 +- src/backend/tero/agents/api.py | 9 + src/backend/tero/agents/distribution.py | 2 +- src/backend/tero/agents/domain.py | 5 +- src/backend/tero/agents/prompts/api.py | 10 +- src/backend/tero/ai_models/ai_factory.py | 1 + src/backend/tero/ai_models/openai_provider.py | 1 + src/backend/tero/api.py | 5 +- src/backend/tero/core/env.py | 3 +- src/backend/tero/teams/domain.py | 1 + src/backend/tero/tools/docs/tool.py | 2 +- src/backend/tero/tools/jira/tool.py | 7 +- src/backend/tero/tools/mcp/tool.py | 2 +- src/backend/tero/tools/oauth.py | 114 ++- src/backend/tests/cleanup_testcontainers.py | 14 + src/backend/tests/test_config.py | 1 + .../assets/copilot-title.svg | 18 - .../entrypoints/background.ts | 4 +- .../entrypoints/iframe/App.vue | 10 +- src/browser-extension/utils/agent.ts | 3 +- src/browser-extension/utils/auth.ts | 9 + .../utils/browser-message.ts | 6 +- src/browser-extension/utils/http.ts | 10 +- src/common/src/assets/styles.css | 12 + src/common/src/components/chat/ChatStatus.vue | 61 +- .../src/components/agent/AgentEditorPanel.vue | 15 +- .../components/agent/AgentTestcasePanel.vue | 48 +- .../agent/AgentTestcaseRunStatus.vue | 28 +- .../src/components/agent/AgentTestcases.vue | 20 +- .../src/components/chat/ChatPanel.vue | 12 +- .../src/components/common/UserTeamsSelect.vue | 5 +- .../dashboard/DashboardCardUsers.vue | 5 +- .../src/components/sidebar/SidebarPanel.vue | 33 +- .../src/components/user/UsersForm.vue | 30 +- src/frontend/src/services/api.ts | 13 +- src/frontend/src/services/auth.ts | 7 +- src/sample.env | 6 +- 40 files changed, 862 insertions(+), 523 deletions(-) create mode 100644 src/backend/alembic/versions/20251122-8259088e28cf-remove_client_info_user_id.py create mode 100644 src/backend/alembic/versions/20251124-6733defeb310-add_team_editor_role.py create mode 100644 src/backend/tests/cleanup_testcontainers.py delete mode 100644 src/browser-extension/assets/copilot-title.svg diff --git a/src/backend/alembic/versions/20251122-8259088e28cf-remove_client_info_user_id.py b/src/backend/alembic/versions/20251122-8259088e28cf-remove_client_info_user_id.py new file mode 100644 index 0000000..b39a8f6 --- /dev/null +++ b/src/backend/alembic/versions/20251122-8259088e28cf-remove_client_info_user_id.py @@ -0,0 +1,26 @@ +"""remove_client_info_user_id + +Revision ID: 8259088e28cf +Revises: 897df88a29c9 +Create Date: 2025-11-22 11:43:38.610593 + +""" +import sqlalchemy as sa +import sqlmodel +from typing import Sequence, Union +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = '8259088e28cf' +down_revision: Union[str, None] = '897df88a29c9' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.drop_column('tool_oauth_client_info', 'user_id') + + +def downgrade() -> None: + op.add_column('tool_oauth_client_info', sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False)) diff --git a/src/backend/alembic/versions/20251124-6733defeb310-add_team_editor_role.py b/src/backend/alembic/versions/20251124-6733defeb310-add_team_editor_role.py new file mode 100644 index 0000000..f8bb989 --- /dev/null +++ b/src/backend/alembic/versions/20251124-6733defeb310-add_team_editor_role.py @@ -0,0 +1,36 @@ +"""add_team_editor_role + +Revision ID: 6733defeb310 +Revises: 8259088e28cf +Create Date: 2025-11-24 14:37:28.062059 + +""" +from typing import Sequence, Union +from alembic import op +from alembic_postgresql_enum import TableReference + +# revision identifiers, used by Alembic. +revision: str = '6733defeb310' +down_revision: Union[str, None] = '8259088e28cf' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.sync_enum_values( # type: ignore + enum_schema='public', + enum_name='role', + new_values=['TEAM_OWNER', 'TEAM_MEMBER', 'TEAM_EDITOR'], + affected_columns=[TableReference(table_schema='public', table_name='team_role', column_name='role')], + enum_values_to_rename=[], + ) + + +def downgrade() -> None: + op.sync_enum_values( # type: ignore + enum_schema='public', + enum_name='role', + new_values=['TEAM_OWNER', 'TEAM_MEMBER'], + affected_columns=[TableReference(table_schema='public', table_name='team_role', column_name='role')], + enum_values_to_rename=[], + ) diff --git a/src/backend/poetry.lock b/src/backend/poetry.lock index 23d73f5..9ec97f8 100644 --- a/src/backend/poetry.lock +++ b/src/backend/poetry.lock @@ -2,14 +2,14 @@ [[package]] name = "aiofiles" -version = "24.1.0" +version = "25.1.0" description = "File support for asyncio." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, - {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, + {file = "aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695"}, + {file = "aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2"}, ] [[package]] @@ -184,14 +184,14 @@ typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] name = "aiosmtplib" -version = "4.0.2" +version = "5.0.0" description = "asyncio SMTP client" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "aiosmtplib-4.0.2-py3-none-any.whl", hash = "sha256:72491f96e6de035c28d29870186782eccb2f651db9c5f8a32c9db689327f5742"}, - {file = "aiosmtplib-4.0.2.tar.gz", hash = "sha256:f0b4933e7270a8be2b588761e5b12b7334c11890ee91987c2fb057e72f566da6"}, + {file = "aiosmtplib-5.0.0-py3-none-any.whl", hash = "sha256:95eb0f81189780845363ab0627e7f130bca2d0060d46cd3eeb459f066eb7df32"}, + {file = "aiosmtplib-5.0.0.tar.gz", hash = "sha256:514ac11c31cb767c764077eb3c2eb2ae48df6f63f1e847aeb36119c4fc42b52d"}, ] [package.extras] @@ -219,14 +219,14 @@ docs = ["sphinx (==8.1.3)", "sphinx-mdinclude (==0.6.1)"] [[package]] name = "alembic" -version = "1.17.1" +version = "1.17.2" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "alembic-1.17.1-py3-none-any.whl", hash = "sha256:cbc2386e60f89608bb63f30d2d6cc66c7aaed1fe105bd862828600e5ad167023"}, - {file = "alembic-1.17.1.tar.gz", hash = "sha256:8a289f6778262df31571d29cca4c7fbacd2f0f582ea0816f4c399b6da7528486"}, + {file = "alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6"}, + {file = "alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e"}, ] [package.dependencies] @@ -285,6 +285,76 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.31.0)"] +[[package]] +name = "asyncpg" +version = "0.31.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +files = [ + {file = "asyncpg-0.31.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:831712dd3cf117eec68575a9b50da711893fd63ebe277fc155ecae1c6c9f0f61"}, + {file = "asyncpg-0.31.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0b17c89312c2f4ccea222a3a6571f7df65d4ba2c0e803339bfc7bed46a96d3be"}, + {file = "asyncpg-0.31.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3faa62f997db0c9add34504a68ac2c342cfee4d57a0c3062fcf0d86c7f9cb1e8"}, + {file = "asyncpg-0.31.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ea599d45c361dfbf398cb67da7fd052affa556a401482d3ff1ee99bd68808a1"}, + {file = "asyncpg-0.31.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:795416369c3d284e1837461909f58418ad22b305f955e625a4b3a2521d80a5f3"}, + {file = "asyncpg-0.31.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a8d758dac9d2e723e173d286ef5e574f0b350ec00e9186fce84d0fc5f6a8e6b8"}, + {file = "asyncpg-0.31.0-cp310-cp310-win32.whl", hash = "sha256:2d076d42eb583601179efa246c5d7ae44614b4144bc1c7a683ad1222814ed095"}, + {file = "asyncpg-0.31.0-cp310-cp310-win_amd64.whl", hash = "sha256:9ea33213ac044171f4cac23740bed9a3805abae10e7025314cfbd725ec670540"}, + {file = "asyncpg-0.31.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eee690960e8ab85063ba93af2ce128c0f52fd655fdff9fdb1a28df01329f031d"}, + {file = "asyncpg-0.31.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2657204552b75f8288de08ca60faf4a99a65deef3a71d1467454123205a88fab"}, + {file = "asyncpg-0.31.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a429e842a3a4b4ea240ea52d7fe3f82d5149853249306f7ff166cb9948faa46c"}, + {file = "asyncpg-0.31.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0807be46c32c963ae40d329b3a686356e417f674c976c07fa49f1b30303f109"}, + {file = "asyncpg-0.31.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e5d5098f63beeae93512ee513d4c0c53dc12e9aa2b7a1af5a81cddf93fe4e4da"}, + {file = "asyncpg-0.31.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37fc6c00a814e18eef51833545d1891cac9aa69140598bb076b4cd29b3e010b9"}, + {file = "asyncpg-0.31.0-cp311-cp311-win32.whl", hash = "sha256:5a4af56edf82a701aece93190cc4e094d2df7d33f6e915c222fb09efbb5afc24"}, + {file = "asyncpg-0.31.0-cp311-cp311-win_amd64.whl", hash = "sha256:480c4befbdf079c14c9ca43c8c5e1fe8b6296c96f1f927158d4f1e750aacc047"}, + {file = "asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad"}, + {file = "asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d"}, + {file = "asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a"}, + {file = "asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671"}, + {file = "asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec"}, + {file = "asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20"}, + {file = "asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8"}, + {file = "asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186"}, + {file = "asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b"}, + {file = "asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e"}, + {file = "asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403"}, + {file = "asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4"}, + {file = "asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2"}, + {file = "asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602"}, + {file = "asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696"}, + {file = "asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab"}, + {file = "asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44"}, + {file = "asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5"}, + {file = "asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2"}, + {file = "asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2"}, + {file = "asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218"}, + {file = "asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d"}, + {file = "asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b"}, + {file = "asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be"}, + {file = "asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2"}, + {file = "asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31"}, + {file = "asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7"}, + {file = "asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e"}, + {file = "asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c"}, + {file = "asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a"}, + {file = "asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d"}, + {file = "asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3"}, + {file = "asyncpg-0.31.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb3cde58321a1f89ce41812be3f2a98dddedc1e76d0838aba1d724f1e4e1a95"}, + {file = "asyncpg-0.31.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6974f36eb9a224d8fb428bcf66bd411aa12cf57c2967463178149e73d4de366"}, + {file = "asyncpg-0.31.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc2b685f400ceae428f79f78b58110470d7b4466929a7f78d455964b17ad1008"}, + {file = "asyncpg-0.31.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb223567dea5f47c45d347f2bde5486be8d9f40339f27217adb3fb1c3be51298"}, + {file = "asyncpg-0.31.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:22be6e02381bab3101cd502d9297ac71e2f966c86e20e78caead9934c98a8af6"}, + {file = "asyncpg-0.31.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37a58919cfef2448a920df00d1b2f821762d17194d0dbf355d6dde8d952c04f9"}, + {file = "asyncpg-0.31.0-cp39-cp39-win32.whl", hash = "sha256:c1a9c5b71d2371a2290bc93336cd05ba4ec781683cab292adbddc084f89443c6"}, + {file = "asyncpg-0.31.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1e1ab5bc65373d92dd749d7308c5b26fb2dc0fbe5d3bf68a32b676aa3bcd24a"}, + {file = "asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735"}, +] + +[package.extras] +gssauth = ["gssapi ; platform_system != \"Windows\"", "sspilib ; platform_system == \"Windows\""] + [[package]] name = "attrs" version = "25.4.0" @@ -684,62 +754,79 @@ markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win [[package]] name = "cryptography" -version = "45.0.7" +version = "46.0.3" 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"] -files = [ - {file = "cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3"}, - {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6"}, - {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd"}, - {file = "cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8"}, - {file = "cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443"}, - {file = "cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17"}, - {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b"}, - {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c"}, - {file = "cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5"}, - {file = "cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63"}, - {file = "cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main"] +files = [ + {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e"}, + {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926"}, + {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71"}, + {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac"}, + {file = "cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018"}, + {file = "cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb"}, + {file = "cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c"}, + {file = "cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665"}, + {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3"}, + {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20"}, + {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de"}, + {file = "cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914"}, + {file = "cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db"}, + {file = "cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21"}, + {file = "cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04"}, + {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506"}, + {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963"}, + {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4"}, + {file = "cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df"}, + {file = "cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f"}, + {file = "cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372"}, + {file = "cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32"}, + {file = "cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9"}, + {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}, + {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}, ] [package.dependencies] -cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "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) ; python_full_version >= \"3.8.0\""] -pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==45.0.7)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "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]] @@ -1324,6 +1411,7 @@ files = [ {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, ] +markers = {dev = "python_version == \"3.12\""} [package.extras] docs = ["Sphinx", "furo"] @@ -1849,30 +1937,25 @@ referencing = ">=0.31.0" [[package]] name = "langchain" -version = "0.3.27" +version = "1.1.0" description = "Building applications with LLMs through composability" optional = false -python-versions = "<4.0,>=3.9" +python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, - {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, + {file = "langchain-1.1.0-py3-none-any.whl", hash = "sha256:af080f3a4a779bfa5925de7aacb6dfab83249d4aab9a08f7aa7b9bec3766d8ea"}, + {file = "langchain-1.1.0.tar.gz", hash = "sha256:583c892f59873c0329dbe04169fb3234ac794c50780e7c6fb62a61c7b86a981b"}, ] [package.dependencies] -langchain-core = ">=0.3.72,<1.0.0" -langchain-text-splitters = ">=0.3.9,<1.0.0" -langsmith = ">=0.1.17" +langchain-core = ">=1.1.0,<2.0.0" +langgraph = ">=1.0.2,<1.1.0" pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" [package.extras] anthropic = ["langchain-anthropic"] aws = ["langchain-aws"] azure-ai = ["langchain-azure-ai"] -cohere = ["langchain-cohere"] community = ["langchain-community"] deepseek = ["langchain-deepseek"] fireworks = ["langchain-fireworks"] @@ -1889,43 +1972,79 @@ xai = ["langchain-xai"] [[package]] name = "langchain-aws" -version = "0.2.35" +version = "1.1.0" description = "An integration package connecting AWS and LangChain" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langchain_aws-0.2.35-py3-none-any.whl", hash = "sha256:8ddb10f3c29f6d52bcbaa4d7f4f56462acf01f608adc7c70f41e5a476899a6bc"}, - {file = "langchain_aws-0.2.35.tar.gz", hash = "sha256:45793a34fe45d365f4292cc768db74669ca24601d2c5da1ac6f44403750d70af"}, + {file = "langchain_aws-1.1.0-py3-none-any.whl", hash = "sha256:8ec074615b42839e035354063717374c32c63f5028ef5221ba073fd5f3ef5e37"}, + {file = "langchain_aws-1.1.0.tar.gz", hash = "sha256:1e2f8570328eae4907c3cf7e900dc68d8034ddc865d9dc96823c9f9d8cccb901"}, ] [package.dependencies] -boto3 = ">=1.39.7" -langchain-core = ">=0.3.76,<0.4.0" -numpy = {version = ">=1.26.0,<3", markers = "python_version >= \"3.12\""} -pydantic = ">=2.10.0,<3" +boto3 = ">=1.40.19" +langchain-core = ">=1.1.0" +numpy = {version = ">=2.3.2,<3", markers = "python_version >= \"3.12\""} +pydantic = ">=2.10.6,<3" [package.extras] tools = ["beautifulsoup4 (>=4.13.4)", "bedrock-agentcore (>=0.1.0) ; python_version >= \"3.10\"", "playwright (>=1.53.0)"] +[[package]] +name = "langchain-classic" +version = "1.0.0" +description = "Building applications with LLMs through composability" +optional = false +python-versions = "<4.0.0,>=3.10.0" +groups = ["main"] +files = [ + {file = "langchain_classic-1.0.0-py3-none-any.whl", hash = "sha256:97f71f150c10123f5511c08873f030e35ede52311d729a7688c721b4e1e01f33"}, + {file = "langchain_classic-1.0.0.tar.gz", hash = "sha256:a63655609254ebc36d660eb5ad7c06c778b2e6733c615ffdac3eac4fbe2b12c5"}, +] + +[package.dependencies] +langchain-core = ">=1.0.0,<2.0.0" +langchain-text-splitters = ">=1.0.0,<2.0.0" +langsmith = ">=0.1.17,<1.0.0" +pydantic = ">=2.7.4,<3.0.0" +pyyaml = ">=5.3.0,<7.0.0" +requests = ">=2.0.0,<3.0.0" +sqlalchemy = ">=1.4.0,<3.0.0" + +[package.extras] +anthropic = ["langchain-anthropic"] +aws = ["langchain-aws"] +deepseek = ["langchain-deepseek"] +fireworks = ["langchain-fireworks"] +google-genai = ["langchain-google-genai"] +google-vertexai = ["langchain-google-vertexai"] +groq = ["langchain-groq"] +mistralai = ["langchain-mistralai"] +ollama = ["langchain-ollama"] +openai = ["langchain-openai"] +perplexity = ["langchain-perplexity"] +together = ["langchain-together"] +xai = ["langchain-xai"] + [[package]] name = "langchain-community" -version = "0.3.31" +version = "0.4.1" description = "Community contributed LangChain integrations." optional = false -python-versions = "<4.0.0,>=3.9.0" +python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_community-0.3.31-py3-none-any.whl", hash = "sha256:1c727e3ebbacd4d891b07bd440647668001cea3e39cbe732499ad655ec5cb569"}, - {file = "langchain_community-0.3.31.tar.gz", hash = "sha256:250e4c1041539130f6d6ac6f9386cb018354eafccd917b01a4cff1950b80fd81"}, + {file = "langchain_community-0.4.1-py3-none-any.whl", hash = "sha256:2135abb2c7748a35c84613108f7ebf30f8505b18c3c18305ffaecfc7651f6c6a"}, + {file = "langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.6.7,<0.7.0" httpx-sse = ">=0.4.0,<1.0.0" -langchain = ">=0.3.27,<2.0.0" -langchain-core = ">=0.3.78,<2.0.0" +langchain-classic = ">=1.0.0,<2.0.0" +langchain-core = ">=1.0.1,<2.0.0" langsmith = ">=0.1.125,<1.0.0" numpy = [ {version = ">=1.26.2", markers = "python_version < \"3.13\""}, @@ -1939,14 +2058,14 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" [[package]] name = "langchain-core" -version = "0.3.79" +version = "1.1.0" description = "Building applications with LLMs through composability" optional = false -python-versions = "<4.0.0,>=3.9.0" +python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_core-0.3.79-py3-none-any.whl", hash = "sha256:92045bfda3e741f8018e1356f83be203ec601561c6a7becfefe85be5ddc58fdb"}, - {file = "langchain_core-0.3.79.tar.gz", hash = "sha256:024ba54a346dd9b13fb8b2342e0c83d0111e7f26fa01f545ada23ad772b55a60"}, + {file = "langchain_core-1.1.0-py3-none-any.whl", hash = "sha256:2c9f27dadc6d21ed4aa46506a37a56e6a7e2d2f9141922dc5c251ba921822ee6"}, + {file = "langchain_core-1.1.0.tar.gz", hash = "sha256:2b76a82d427922c8bc51c08404af4fc2a29e9f161dfe2297cb05091e810201e7"}, ] [package.dependencies] @@ -1954,63 +2073,62 @@ jsonpatch = ">=1.33.0,<2.0.0" langsmith = ">=0.3.45,<1.0.0" packaging = ">=23.2.0,<26.0.0" pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3.0,<7.0.0" +pyyaml = ">=5.3.0,<7.0.0" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7.0,<5.0.0" [[package]] name = "langchain-google-community" -version = "2.0.10" +version = "3.0.1" description = "An integration package connecting miscellaneous Google's products and LangChain" optional = false -python-versions = ">=3.9" +python-versions = "<3.14.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_google_community-2.0.10-py3-none-any.whl", hash = "sha256:9afb3eba04359670ba5797efc2ed8a1749a3de706a17c21f85173b31e4b5474a"}, - {file = "langchain_google_community-2.0.10.tar.gz", hash = "sha256:5476adfa3b64cc2ce52c1fd514d0b1eab8775c4416a9dc555423f387fafd842f"}, + {file = "langchain_google_community-3.0.1-py3-none-any.whl", hash = "sha256:b75737f87e47ea102bd77877932fe4e34b0a85f50e9d587a391a14e35f053ffd"}, + {file = "langchain_google_community-3.0.1.tar.gz", hash = "sha256:b086c2b7d732872b93d99d6a0d17285d0028519ca0cbffb5b17c99eef295d65c"}, ] [package.dependencies] -google-api-core = ">=2.25,<3" -google-api-python-client = ">=2.161,<3" -google-cloud-core = ">=2.4.3,<3" -google-cloud-modelarmor = ">=0.2.8" -grpcio = ">=1.74,<2" -langchain-community = ">=0.3,<1" -langchain-core = ">=0.3,<1" +google-api-core = ">=2.25.0,<3.0.0" +google-api-python-client = ">=2.161.0,<3.0.0" +google-cloud-core = ">=2.4.3,<3.0.0" +google-cloud-modelarmor = ">=0.2.8,<1.0.0" +grpcio = ">=1.74.0,<2.0.0" +langchain-community = ">=0.4.0,<2.0.0" +langchain-core = ">=1.0.0,<2.0.0" [package.extras] -bigquery = ["google-cloud-bigquery (>=3.21,<4)"] -calendar = ["google-auth (>=2.36,<3)", "google-auth-oauthlib (>=1.2,<2)"] -docai = ["gapic-google-longrunning (>=0.11.2,<1)", "google-cloud-contentwarehouse (>=0.7.7,<1)", "google-cloud-documentai (>=2.26,<3)", "google-cloud-documentai-toolbox (>=0.13.3a0,<1)"] -drive = ["google-auth-httplib2 (>=0.2,<1)", "google-auth-oauthlib (>=1.2,<2)"] -featurestore = ["db-dtypes (>=1.2.0,<2)", "google-cloud-aiplatform (>=1.56.0,<2)", "google-cloud-bigquery-storage (>=2.6.0,<3)", "pandas (>=1.0.0) ; python_version < \"3.12\"", "pandas (>=2.0.0,<3.0) ; python_version >= \"3.12\"", "pyarrow (>=6.0.1)", "pydantic (>=2.7.4,<3)"] -gcs = ["google-cloud-storage (>=2.16,<3)"] -gmail = ["beautifulsoup4 (>=4.12.3,<5)", "google-auth-httplib2 (>=0.2,<1)", "google-auth-oauthlib (>=1.2,<2)"] -places = ["googlemaps (>=4.10,<5)"] -speech = ["google-cloud-speech (>=2.26,<3)"] -texttospeech = ["google-cloud-texttospeech (>=2.16.3,<3)"] -translate = ["google-cloud-translate (>=3.15.3,<4)"] -vertexaisearch = ["google-cloud-discoveryengine (>=0.11.14,<1)"] -vision = ["google-cloud-vision (>=3.7.2,<4)"] +calendar = ["google-auth (>=2.36.0,<3.0.0)", "google-auth-oauthlib (>=1.2.0,<2.0.0)"] +docai = ["gapic-google-longrunning (>=0.11.2,<1.0.0)", "google-cloud-contentwarehouse (>=0.7.7,<1.0.0)", "google-cloud-documentai (>=2.26.0,<3.0.0)"] +drive = ["google-auth-httplib2 (>=0.2.0,<1.0.0)", "google-auth-oauthlib (>=1.2.0,<2.0.0)"] +featurestore = ["db-dtypes (>=1.2.0,<2.0.0)", "google-cloud-aiplatform (>=1.56.0,<2.0.0)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0)", "pandas (>=1.0.0) ; python_version < \"3.12\"", "pandas (>=2.0.0,<3.0.0) ; python_version >= \"3.12\"", "pyarrow (>=6.0.1)", "pydantic (>=2.7.4,<3.0.0)"] +gcs = ["google-cloud-storage (>=2.16.0,<4.0.0)"] +gmail = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "google-auth-httplib2 (>=0.2.0,<1.0.0)", "google-auth-oauthlib (>=1.2.0,<2.0.0)"] +places = ["googlemaps (>=4.10.0,<5.0.0)"] +speech = ["google-cloud-speech (>=2.26.0,<3.0.0)"] +texttospeech = ["google-cloud-texttospeech (>=2.16.3,<3.0.0)"] +translate = ["google-cloud-translate (>=3.15.3,<4.0.0)"] +vertexaisearch = ["google-cloud-discoveryengine (>=0.11.14,<1.0.0)"] +vision = ["google-cloud-vision (>=3.7.2,<4.0.0)"] [[package]] name = "langchain-google-genai" -version = "2.1.12" +version = "3.2.0" description = "An integration package connecting Google's genai package and LangChain" optional = false -python-versions = ">=3.9" +python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_google_genai-2.1.12-py3-none-any.whl", hash = "sha256:4c07630419a8fbe7a2ec512c6dea68289663bfe7d5fae0ba431d2cd59a0d0880"}, - {file = "langchain_google_genai-2.1.12.tar.gz", hash = "sha256:4a98371e545eb97fcdf483086a4aebbb8eceeb9597ca5a9c4c35e92f4fbbd271"}, + {file = "langchain_google_genai-3.2.0-py3-none-any.whl", hash = "sha256:689fc159d4623a184678e24771f6d52373e983a8fc8d342e44352aaf28e9445d"}, + {file = "langchain_google_genai-3.2.0.tar.gz", hash = "sha256:1fa620ea9c655a37537e95438857c423e1a3599b5a665b8dd87064c76ee95b72"}, ] [package.dependencies] -filetype = ">=1.2,<2" -google-ai-generativelanguage = ">=0.7,<1" -langchain-core = ">=0.3.75" -pydantic = ">=2,<3" +filetype = ">=1.2.0,<2.0.0" +google-ai-generativelanguage = ">=0.9.0,<1.0.0" +langchain-core = ">=1.1.0,<2.0.0" +pydantic = ">=2.0.0,<3.0.0" [[package]] name = "langchain-mcp-adapters" @@ -2031,40 +2149,41 @@ typing-extensions = ">=4.14.0" [[package]] name = "langchain-openai" -version = "0.3.35" +version = "1.1.0" description = "An integration package connecting OpenAI and LangChain" optional = false -python-versions = "<4.0.0,>=3.9.0" +python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_openai-0.3.35-py3-none-any.whl", hash = "sha256:76d5707e6e81fd461d33964ad618bd326cb661a1975cef7c1cb0703576bdada5"}, - {file = "langchain_openai-0.3.35.tar.gz", hash = "sha256:fa985fd041c3809da256a040c98e8a43e91c6d165b96dcfeb770d8bd457bf76f"}, + {file = "langchain_openai-1.1.0-py3-none-any.whl", hash = "sha256:243bb345d0260ea1326c2b6ac2237ec29f082ab457c59e9306bac349df4577e8"}, + {file = "langchain_openai-1.1.0.tar.gz", hash = "sha256:9a33280c2e8315d013d64e6b15e583be347beb0d0f281755c335ae504ad0c184"}, ] [package.dependencies] -langchain-core = ">=0.3.78,<1.0.0" -openai = ">=1.104.2,<3.0.0" +langchain-core = ">=1.1.0,<2.0.0" +openai = ">=1.109.1,<3.0.0" tiktoken = ">=0.7.0,<1.0.0" [[package]] name = "langchain-postgres" -version = "0.0.13" +version = "0.0.16" description = "An integration package connecting Postgres and LangChain" optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "langchain_postgres-0.0.13-py3-none-any.whl", hash = "sha256:91cb4e62862b1a1f36cdf8462e34990bc112d5824dfb738cab9ca6577cb27cee"}, - {file = "langchain_postgres-0.0.13.tar.gz", hash = "sha256:3a23f95aaeca9bf03af63cf6b9ef1381b6d2a83605179d307a6606b05e335ab1"}, + {file = "langchain_postgres-0.0.16-py3-none-any.whl", hash = "sha256:a7375cf9fc9b6965efc207dbcc959424e96b8ffe75d5ced6055676d2613f8d37"}, + {file = "langchain_postgres-0.0.16.tar.gz", hash = "sha256:d09aa4ea77ee8600a9ff64de9c185fb558aa388c816c7be04dd4559c878530b7"}, ] [package.dependencies] -langchain-core = ">=0.2.13,<0.4.0" -numpy = ">=1.21" -pgvector = "<0.4" -psycopg = ">=3,<4" -psycopg-pool = ">=3.2.1,<4.0.0" -sqlalchemy = ">=2,<3" +asyncpg = ">=0.30.0" +langchain-core = ">=0.2.13,<2.0" +numpy = ">=1.21,<3" +pgvector = ">=0.2.5,<0.4" +psycopg = {version = ">=3,<4", extras = ["binary"]} +psycopg-pool = ">=3.2.1,<4" +sqlalchemy = {version = ">=2,<3", extras = ["asyncio"]} [[package]] name = "langchain-tavily" @@ -2086,38 +2205,38 @@ requests = ">=2.32.3,<3.0.0" [[package]] name = "langchain-text-splitters" -version = "0.3.11" +version = "1.0.0" description = "LangChain text splitting utilities" optional = false -python-versions = ">=3.9" +python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_text_splitters-0.3.11-py3-none-any.whl", hash = "sha256:cf079131166a487f1372c8ab5d0bfaa6c0a4291733d9c43a34a16ac9bcd6a393"}, - {file = "langchain_text_splitters-0.3.11.tar.gz", hash = "sha256:7a50a04ada9a133bbabb80731df7f6ddac51bc9f1b9cab7fa09304d71d38a6cc"}, + {file = "langchain_text_splitters-1.0.0-py3-none-any.whl", hash = "sha256:f00c8219d3468f2c5bd951b708b6a7dd9bc3c62d0cfb83124c377f7170f33b2e"}, + {file = "langchain_text_splitters-1.0.0.tar.gz", hash = "sha256:d8580a20ad7ed10b432feb273e5758b2cc0902d094919629cec0e1ad691a6744"}, ] [package.dependencies] -langchain-core = ">=0.3.75,<2.0.0" +langchain-core = ">=1.0.0,<2.0.0" [[package]] name = "langgraph" -version = "0.4.5" +version = "1.0.4" description = "Building stateful, multi-actor applications with LLMs" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langgraph-0.4.5-py3-none-any.whl", hash = "sha256:73f36caae55137c2bdb2a6c59661f0ae29c1516a0d1f4ad4975ad3862865a979"}, - {file = "langgraph-0.4.5.tar.gz", hash = "sha256:08a8c6577b09cda4e0c16712e762927f00930dabbc7fe235562985ad85891349"}, + {file = "langgraph-1.0.4-py3-none-any.whl", hash = "sha256:b1a835ceb0a8d69b9db48075e1939e28b1ad70ee23fa3fa8f90149904778bacf"}, + {file = "langgraph-1.0.4.tar.gz", hash = "sha256:86d08e25d7244340f59c5200fa69fdd11066aa999b3164b531e2a20036fac156"}, ] [package.dependencies] -langchain-core = {version = ">=0.1", markers = "python_version < \"4.0\""} -langgraph-checkpoint = ">=2.0.26,<3.0.0" -langgraph-prebuilt = {version = ">=0.1.8", markers = "python_version < \"4.0\""} -langgraph-sdk = {version = ">=0.1.42", markers = "python_version < \"4.0\""} +langchain-core = ">=0.1" +langgraph-checkpoint = ">=2.1.0,<4.0.0" +langgraph-prebuilt = ">=1.0.2,<1.1.0" +langgraph-sdk = ">=0.2.2,<0.3.0" pydantic = ">=2.7.4" -xxhash = ">=3.5.0,<4.0.0" +xxhash = ">=3.5.0" [[package]] name = "langgraph-checkpoint" @@ -2137,19 +2256,19 @@ ormsgpack = ">=1.10.0" [[package]] name = "langgraph-prebuilt" -version = "0.1.8" +version = "1.0.5" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false -python-versions = "<4.0.0,>=3.9.0" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langgraph_prebuilt-0.1.8-py3-none-any.whl", hash = "sha256:ae97b828ae00be2cefec503423aa782e1bff165e9b94592e224da132f2526968"}, - {file = "langgraph_prebuilt-0.1.8.tar.gz", hash = "sha256:4de7659151829b2b955b6798df6800e580e617782c15c2c5b29b139697491831"}, + {file = "langgraph_prebuilt-1.0.5-py3-none-any.whl", hash = "sha256:22369563e1848862ace53fbc11b027c28dd04a9ac39314633bb95f2a7e258496"}, + {file = "langgraph_prebuilt-1.0.5.tar.gz", hash = "sha256:85802675ad778cc7240fd02d47db1e0b59c0c86d8369447d77ce47623845db2d"}, ] [package.dependencies] -langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.3.15 || >0.3.15,<0.3.16 || >0.3.16,<0.3.17 || >0.3.17,<0.3.18 || >0.3.18,<0.3.19 || >0.3.19,<0.3.20 || >0.3.20,<0.3.21 || >0.3.21,<0.3.22 || >0.3.22,<0.4.0" -langgraph-checkpoint = ">=2.0.10,<3.0.0" +langchain-core = ">=1.0.0" +langgraph-checkpoint = ">=2.1.0,<4.0.0" [[package]] name = "langgraph-sdk" @@ -2361,14 +2480,14 @@ tests = ["pytest", "simplejson"] [[package]] name = "mcp" -version = "1.19.0" +version = "1.22.0" description = "Model Context Protocol SDK" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572"}, - {file = "mcp-1.19.0.tar.gz", hash = "sha256:213de0d3cd63f71bc08ffe9cc8d4409cc87acffd383f6195d2ce0457c021b5c1"}, + {file = "mcp-1.22.0-py3-none-any.whl", hash = "sha256:bed758e24df1ed6846989c909ba4e3df339a27b4f30f1b8b627862a4bade4e98"}, + {file = "mcp-1.22.0.tar.gz", hash = "sha256:769b9ac90ed42134375b19e777a2858ca300f95f2e800982b3e2be62dfc0ba01"}, ] [package.dependencies] @@ -2378,10 +2497,13 @@ httpx-sse = ">=0.4" jsonschema = ">=4.20.0" pydantic = ">=2.11.0,<3.0.0" pydantic-settings = ">=2.5.2" +pyjwt = {version = ">=2.10.1", extras = ["crypto"]} python-multipart = ">=0.0.9" pywin32 = {version = ">=310", markers = "sys_platform == \"win32\""} sse-starlette = ">=1.6.1" starlette = ">=0.27" +typing-extensions = ">=4.9.0" +typing-inspection = ">=0.4.1" uvicorn = {version = ">=0.31.1", markers = "sys_platform != \"emscripten\""} [package.extras] @@ -2667,28 +2789,28 @@ files = [ [[package]] name = "openai" -version = "1.109.1" +version = "2.8.1" description = "The official Python library for the openai API" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315"}, - {file = "openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869"}, + {file = "openai-2.8.1-py3-none-any.whl", hash = "sha256:c6c3b5a04994734386e8dad3c00a393f56d3b68a27cd2e8acae91a59e4122463"}, + {file = "openai-2.8.1.tar.gz", hash = "sha256:cb1b79eef6e809f6da326a7ef6038719e35aa944c42d081807bfa1be8060f15f"}, ] [package.dependencies] anyio = ">=3.5.0,<5" distro = ">=1.7.0,<2" httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" +jiter = ">=0.10.0,<1" pydantic = ">=1.9.0,<3" sniffio = "*" tqdm = ">4" typing-extensions = ">=4.11,<5" [package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.8)"] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] @@ -2921,127 +3043,111 @@ numpy = "*" [[package]] name = "pillow" -version = "11.3.0" -description = "Python Imaging Library (Fork)" +version = "12.0.0" +description = "Python Imaging Library (fork)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, - {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, - {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, - {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, - {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, - {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, - {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, - {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, - {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, - {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, - {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, - {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, - {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, - {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, - {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, - {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, - {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, - {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, - {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, - {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, - {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, - {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, - {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, - {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, - {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, - {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, - {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, + {file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}, + {file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"}, + {file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"}, + {file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"}, + {file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"}, + {file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"}, + {file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"}, + {file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"}, + {file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"}, + {file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"}, + {file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"}, + {file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"}, + {file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"}, + {file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"}, + {file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"}, + {file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"}, + {file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"}, + {file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"}, + {file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"}, + {file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"}, + {file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"}, + {file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"}, + {file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"}, + {file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] xmp = ["defusedxml"] [[package]] @@ -3574,6 +3680,27 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[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 = "pyparsing" version = "3.2.5" @@ -3591,14 +3718,14 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pypdf" -version = "5.9.0" +version = "6.4.0" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pypdf-5.9.0-py3-none-any.whl", hash = "sha256:be10a4c54202f46d9daceaa8788be07aa8cd5ea8c25c529c50dd509206382c35"}, - {file = "pypdf-5.9.0.tar.gz", hash = "sha256:30f67a614d558e495e1fbb157ba58c1de91ffc1718f5e0dfeb82a029233890a1"}, + {file = "pypdf-6.4.0-py3-none-any.whl", hash = "sha256:55ab9837ed97fd7fcc5c131d52fcc2223bc5c6b8a1488bbf7c0e27f1f0023a79"}, + {file = "pypdf-6.4.0.tar.gz", hash = "sha256:4769d471f8ddc3341193ecc5d6560fa44cf8cd0abfabf21af4e195cc0c224072"}, ] [package.extras] @@ -3655,20 +3782,20 @@ nodejs = ["nodejs-wheel-binaries"] [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, + {file = "pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad"}, + {file = "pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" +iniconfig = ">=1.0.1" +packaging = ">=22" pluggy = ">=1.5,<2" pygments = ">=2.7.2" @@ -3677,18 +3804,19 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-asyncio" -version = "0.26.0" +version = "1.3.0" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0"}, - {file = "pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f"}, + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, ] [package.dependencies] -pytest = ">=8.2,<9" +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] @@ -4402,7 +4530,7 @@ files = [ ] [package.dependencies] -greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = ">=1", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""} typing-extensions = ">=4.6.0" [package.extras] @@ -4464,14 +4592,14 @@ doc = ["sphinx"] [[package]] name = "sse-starlette" -version = "2.4.1" +version = "3.0.3" description = "SSE plugin for Starlette" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a"}, - {file = "sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926"}, + {file = "sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431"}, + {file = "sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971"}, ] [package.dependencies] @@ -4479,7 +4607,7 @@ anyio = ">=4.7.0" [package.extras] daphne = ["daphne (>=4.2.0)"] -examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio,examples] (>=2.0.41)", "starlette (>=0.41.3)", "uvicorn (>=0.34.0)"] +examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio] (>=2.0.41)", "starlette (>=0.49.1)", "uvicorn (>=0.34.0)"] granian = ["granian (>=2.3.1)"] uvicorn = ["uvicorn (>=0.34.0)"] @@ -4534,14 +4662,14 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "testcontainers" -version = "4.13.2" +version = "4.13.3" description = "Python library for throwaway instances of anything that can run in a Docker container" optional = false -python-versions = "<4.0,>=3.9.2" +python-versions = ">=3.9.2" groups = ["dev"] files = [ - {file = "testcontainers-4.13.2-py3-none-any.whl", hash = "sha256:0209baf8f4274b568cde95bef2cadf7b1d33b375321f793790462e235cd684ee"}, - {file = "testcontainers-4.13.2.tar.gz", hash = "sha256:2315f1e21b059427a9d11e8921f85fef322fbe0d50749bcca4eaa11271708ba4"}, + {file = "testcontainers-4.13.3-py3-none-any.whl", hash = "sha256:063278c4805ffa6dd85e56648a9da3036939e6c0ac1001e851c9276b19b05970"}, + {file = "testcontainers-4.13.3.tar.gz", hash = "sha256:9d82a7052c9a53c58b69e1dc31da8e7a715e8b3ec1c4df5027561b47e2efe646"}, ] [package.dependencies] @@ -4556,26 +4684,25 @@ arangodb = ["python-arango (>=7.8,<8.0)"] aws = ["boto3", "httpx"] azurite = ["azure-storage-blob (>=12.19,<13.0)"] chroma = ["chromadb-client (>=1.0.0,<2.0.0)"] -clickhouse = ["clickhouse-driver"] cosmosdb = ["azure-cosmos"] db2 = ["ibm_db_sa ; platform_machine != \"aarch64\" and platform_machine != \"arm64\"", "sqlalchemy"] generic = ["httpx", "redis"] google = ["google-cloud-datastore (>=2)", "google-cloud-pubsub (>=2)"] influxdb = ["influxdb", "influxdb-client"] -k3s = ["kubernetes", "pyyaml"] +k3s = ["kubernetes", "pyyaml (>=6.0.3)"] keycloak = ["python-keycloak"] localstack = ["boto3"] mailpit = ["cryptography"] minio = ["minio"] mongodb = ["pymongo"] -mssql = ["pymssql ; platform_machine != \"arm64\" or python_version >= \"3.10\"", "sqlalchemy"] +mssql = ["pymssql (>=2.3.9) ; platform_machine != \"arm64\" or python_version >= \"3.10\"", "sqlalchemy"] mysql = ["pymysql[rsa]", "sqlalchemy"] nats = ["nats-py"] neo4j = ["neo4j"] openfga = ["openfga-sdk ; python_version >= \"3.10\""] -opensearch = ["opensearch-py"] -oracle = ["oracledb", "sqlalchemy"] -oracle-free = ["oracledb", "sqlalchemy"] +opensearch = ["opensearch-py ; python_version < \"4.0\""] +oracle = ["oracledb (>=3.4.1)", "sqlalchemy"] +oracle-free = ["oracledb (>=3.4.1)", "sqlalchemy"] qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] @@ -5496,4 +5623,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.14" -content-hash = "3276ce57b42f3caf50cb51c08de80466dc5315c01aa1a02766a4841c5b51742f" +content-hash = "669dc5a88a8a24d2431371aee0f8795561008f01566007d0f891ac8d62459c2a" diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 0e2dff1..b8b31d5 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -5,10 +5,10 @@ description = "" authors = [] requires-python = ">=3.12,<3.14" dependencies = [ - "fastapi (>=0.115.8,<0.116.0)", - "uvicorn (>=0.34.0,<0.35.0)", - "openai (>=1.63.2,<2.0.0)", - "sse-starlette (>=2.2.1,<3.0.0)", + "fastapi (>=0.115.8,<0.122.0)", + "uvicorn (>=0.34.0,<0.39.0)", + "openai (>=2.8.1,<3.0.0)", + "sse-starlette (>=3.0.3,<4.0.0)", "pydantic (>=2.10.6,<3.0.0)", "psycopg[binary] (>=3.2.4,<4.0.0)", "sqlalchemy (>=2.0.38,<3.0.0)", @@ -16,42 +16,40 @@ dependencies = [ "python-jose (>=3.4.0,<4.0.0)", "aiohttp (>=3.11.12,<4.0.0)", "sqlmodel (>=0.0.22,<0.1.0)", - "langchain-openai (>=0.3.6,<0.4.0)", - "langchain-core (>=0.3.38,<0.4.0)", - "langchain (>=0.3.19,<0.4.0)", - "aiofiles (>=24.1.0,<25.0.0)", + "langchain-openai (>=1.0.3,<2.0.0)", + "langchain-core (>=1.0.7,<2.0.0)", + "langchain (>=1.0.8,<2.0.0)", + "aiofiles (>=25.1.0,<26.0.0)", "jsonschema (>=4.23.0,<5.0.0)", "python-multipart (>=0.0.20,<0.0.21)", - "langchain-postgres (>=0.0.13,<0.0.14)", + "langchain-postgres (>=0.0.13,<0.0.17)", "chardet (>=5.2.0,<6.0.0)", "pydantic-settings (>=2.9.1,<3.0.0)", - "langgraph (>=0.4.3,<0.5.0)", - # Set langgraph-prebuilt to fixed version because newer versions have conflicts with current implementation - "langgraph-prebuilt (>=0.1.8,<0.2.0)", - "pillow (>=11.2.1,<12.0.0)", + "langgraph (>=1.0.3,<2.0.0)", + "pillow (>=12.0.0,<13.0.0)", "langchain-mcp-adapters (>=0.1.1,<0.2.0)", - "cryptography (>=45.0.3,<46.0.0)", + "cryptography (>=46.0.3,<47.0.0)", "azure-ai-documentintelligence (>=1.0.2,<2.0.0)", "tabulate (>=0.9.0,<0.10.0)", "openpyxl (>=3.1.5,<4.0.0)", - "pypdf (>=5.6.0,<6.0.0)", - "langchain-community (>=0.3.24,<0.4.0)", + "pypdf (>=6.3.0,<7.0.0)", + "langchain-community (>=0.4.1,<0.5.0)", "langchain-tavily (>=0.2.4,<0.3.0)", - "langchain-google-community (>=2.0.7,<3.0.0)", + "langchain-google-community (>=3.0.1,<4.0.0)", "beautifulsoup4 (>=4.13.4,<5.0.0)", - "langchain-aws (>=0.2.27,<0.3.0)", + "langchain-aws (>=1.0.0,<2.0.0)", "boto3 (>=1.39.4,<2.0.0)", - "langchain-google-genai (>=2.1.8,<3.0.0)", + "langchain-google-genai (>=3.1.0,<4.0.0)", "fastapi-mcp (>=0.4.0,<1.0.0)", "jinja2 (>=3.1.6,<4.0.0)", - "aiosmtplib (>=4.0.2,<5.0.0)", + "aiosmtplib (>=5.0.0,<6.0.0)", "xlrd (>=2.0.2,<3.0.0)", "openevals (>=0.1.0,<0.2.0)", "json-schema-to-pydantic (>=0.4.3,<0.5.0)", "transformers (>=4.57.1,<5.0.0)", "python-slugify (>=8.0.4,<9.0.0)", # Set mcp to fixed version because newer versions have conflicts with current implementation - "mcp (>=1.19.0,<1.20.0)", + "mcp (>=1.22.0,<1.23.0)", "pypdfium2 (>=5.0.0,<6.0.0)" ] @@ -61,12 +59,12 @@ requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.group.dev.dependencies] -alembic = "^1.14.1" -pytest = "^8.3.4" -pytest-asyncio = "^0.26.0" +alembic = "^1.17.2" +pytest = "^9.0.1" +pytest-asyncio = "^1.3.0" aiosqlite = "^0.21.0" sqlparse = "^0.5.3" freezegun = "^1.5.1" -testcontainers = {extras = ["postgres"], version = "^4.9.2"} +testcontainers = {extras = ["postgres"], version = "^4.13.3"} pyright = "^1.1.400" -alembic-postgresql-enum = "^1.7.0" +alembic-postgresql-enum = "^1.8.0" diff --git a/src/backend/tero/agents/api.py b/src/backend/tero/agents/api.py index 70a7ad4..ffd000b 100644 --- a/src/backend/tero/agents/api.py +++ b/src/backend/tero/agents/api.py @@ -17,6 +17,7 @@ from ..files.file_quota import QuotaExceededError from ..files.parser import add_encoding_to_content_type from ..files.repos import FileRepository +from ..teams.domain import GLOBAL_TEAM_ID, Role from ..tools.core import AgentTool from ..tools.oauth import ToolOAuthRequest, build_tool_oauth_request_http_exception from ..tools.repos import ToolRepository @@ -41,6 +42,7 @@ Answer in the same language as the user. Use markdown to format your responses. You can include code blocks, tables, plantuml diagrams code blocks, echarts configuration code blocks and any standard markdown format""" + class AgentSort(Enum): LAST_UPDATE = "LAST_UPDATE" ACTIVE_USERS = "ACTIVE_USERS" @@ -116,6 +118,13 @@ async def new_agent(user: Annotated[User, Depends(get_current_user)], async def update_agent(agent_id: int, updated: AgentUpdate, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)]) -> PublicAgent: agent = await find_editable_agent(agent_id, user, db) + + if updated.team_id == GLOBAL_TEAM_ID and env.disable_publish_global and not any(tr.role in [Role.TEAM_OWNER, Role.TEAM_EDITOR] and tr.team_id == GLOBAL_TEAM_ID for tr in user.team_roles): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Global team members cannot publish to global team" + ) + agent.update_with(updated) ret = await AgentRepository(db).update(agent) if updated.publish_prompts: diff --git a/src/backend/tero/agents/distribution.py b/src/backend/tero/agents/distribution.py index fd9f6c3..8264f72 100644 --- a/src/backend/tero/agents/distribution.py +++ b/src/backend/tero/agents/distribution.py @@ -196,7 +196,7 @@ async def update_agent_from_zip(agent: Agent, zip_content: bytes, user: User, db await _update_tests(agent.id, parsed.get('tests', []), user.id, db) return agent - + def _open_zip_file(zip_content: bytes) -> ZipFile: zip_bytes = BytesIO(zip_content) try: diff --git a/src/backend/tero/agents/domain.py b/src/backend/tero/agents/domain.py index 26a3acb..284917f 100644 --- a/src/backend/tero/agents/domain.py +++ b/src/backend/tero/agents/domain.py @@ -80,7 +80,10 @@ def is_visible_by(self, user: User) -> bool: return self.user_id == user.id or user.is_member_of(cast(int, self.team_id)) def is_editable_by(self, user: User) -> bool: - return self.user_id == user.id or any(tr.role == Role.TEAM_OWNER and cast(Team, tr.team).id == self.team_id for tr in user.team_roles) + return self.user_id == user.id or any( + tr.role in [Role.TEAM_OWNER, Role.TEAM_EDITOR] and cast(Team, tr.team).id == self.team_id + for tr in user.team_roles + ) def clone(self, user_id: int) -> "Agent": base_name = (self.name or "").strip() diff --git a/src/backend/tero/agents/prompts/api.py b/src/backend/tero/agents/prompts/api.py index fd1901a..dee45de 100644 --- a/src/backend/tero/agents/prompts/api.py +++ b/src/backend/tero/agents/prompts/api.py @@ -37,7 +37,15 @@ def _map_public_prompt(agent: Agent, user: User, prompt: AgentPrompt): def _is_editable_prompt(prompt: AgentPrompt, agent: Agent, user: User) -> bool: - return prompt.user_id == user.id or (prompt.shared and (agent.user_id == user.id or (agent.team_id is not None and any(tr.role == Role.TEAM_OWNER and cast(Team, tr.team).id == agent.team_id for tr in user.team_roles)))) + return prompt.user_id == user.id or ( + prompt.shared and ( + agent.user_id == user.id or + (agent.team_id is not None and any( + tr.role in [Role.TEAM_OWNER, Role.TEAM_EDITOR] and cast(Team, tr.team).id == agent.team_id + for tr in user.team_roles + )) + ) + ) @router.post(AGENT_PROMPTS_PATH, response_model=AgentPromptPublic, status_code=status.HTTP_201_CREATED) diff --git a/src/backend/tero/ai_models/ai_factory.py b/src/backend/tero/ai_models/ai_factory.py index ea1a0b2..d34171f 100644 --- a/src/backend/tero/ai_models/ai_factory.py +++ b/src/backend/tero/ai_models/ai_factory.py @@ -18,6 +18,7 @@ if env.google_api_key: providers.append(GoogleProvider()) + def get_provider(model: str) -> AiModelProvider: for provider in providers: if provider.supports_model(model): diff --git a/src/backend/tero/ai_models/openai_provider.py b/src/backend/tero/ai_models/openai_provider.py index f5eae3c..675e40d 100644 --- a/src/backend/tero/ai_models/openai_provider.py +++ b/src/backend/tero/ai_models/openai_provider.py @@ -38,6 +38,7 @@ def build_embedding(self, model: str) -> Embeddings: api_key=env.openai_api_key, model=env.openai_model_id_mapping[model]) + class ReasoningTokenCountingChatOpenAI(ChatOpenAI): # we override this method which is the one used by get_num_tokens_from_messages to count the tokens diff --git a/src/backend/tero/api.py b/src/backend/tero/api.py index 361ab15..3c3a2ae 100644 --- a/src/backend/tero/api.py +++ b/src/backend/tero/api.py @@ -43,13 +43,14 @@ def filter(self, record): app.add_middleware(GZipMiddleware) if env.frontend_path: app.mount("/assets", StaticFiles(directory=os.path.join(env.frontend_path, "assets")), name="assets") - setup_mcp_server(app) + def _should_serve_frontend(path: str) -> bool: api_paths = ["/api", "/assets", "/mcp", "/.well-known/", "/resources/"] return not any(path.startswith(prefix) for prefix in api_paths) and path != "/manifest.json" + @app.middleware("http") async def frontend_router(request: Request, call_next) -> Response: if env.frontend_path and _should_serve_frontend(request.url.path): @@ -83,6 +84,7 @@ class Manifest(CamelCaseModel): id: str contact_email: str auth: ManifestAuthConfig + disable_publish_global: bool @app.get("/manifest.json") @@ -93,6 +95,7 @@ async def manifest() -> Manifest: auth=ManifestAuthConfig( url=env.frontend_openid_url or env.openid_url, client_id=env.openid_client_id, scope=env.openid_scope ), + disable_publish_global=env.disable_publish_global or False ) diff --git a/src/backend/tero/core/env.py b/src/backend/tero/core/env.py index 9a01d62..df4cb87 100644 --- a/src/backend/tero/core/env.py +++ b/src/backend/tero/core/env.py @@ -16,7 +16,7 @@ class AzureModelDeployment(BaseModel): class Settings(BaseSettings): - model_config = SettingsConfigDict(enable_decoding=False, extra='allow') + model_config = SettingsConfigDict(enable_decoding=False, extra="allow") db_url : str secret_encryption_key : SecretStr @@ -27,6 +27,7 @@ class Settings(BaseSettings): openid_client_id : str openid_scope : str allowed_users : list[str] = [] + disable_publish_global : Optional[bool] = False contact_email : str azure_app_insights_connection : Optional[str] = None azure_endpoints : list[str] diff --git a/src/backend/tero/teams/domain.py b/src/backend/tero/teams/domain.py index 123924e..64f8b8f 100644 --- a/src/backend/tero/teams/domain.py +++ b/src/backend/tero/teams/domain.py @@ -14,6 +14,7 @@ class Role(str, Enum): TEAM_OWNER = "owner" TEAM_MEMBER = "member" + TEAM_EDITOR = "editor" class Team(SQLModel, table=True): diff --git a/src/backend/tero/tools/docs/tool.py b/src/backend/tero/tools/docs/tool.py index 79b1dd2..95d5237 100644 --- a/src/backend/tero/tools/docs/tool.py +++ b/src/backend/tero/tools/docs/tool.py @@ -7,7 +7,7 @@ from enum import Enum import tiktoken -from langchain.indexes import SQLRecordManager, aindex +from langchain_classic.indexes import SQLRecordManager, aindex from langchain_core.callbacks import AsyncCallbackHandler from langchain_core.callbacks.manager import AsyncCallbackManagerForRetrieverRun, AsyncCallbackManager from langchain_core.documents import Document diff --git a/src/backend/tero/tools/jira/tool.py b/src/backend/tero/tools/jira/tool.py index c9fd0af..4221fe3 100644 --- a/src/backend/tero/tools/jira/tool.py +++ b/src/backend/tero/tools/jira/tool.py @@ -71,13 +71,12 @@ async def _setup_tool(self, prev_config: Optional[AgentToolConfig]) -> Optional[ async def _save_client_info(self, config: dict): repo = ToolOAuthClientInfoRepository(self.db) - client_info = await repo.find_by_ids(self.user_id, self.agent.id, self.id) + client_info = await repo.find_by_ids(self.agent.id, self.id) client_id = config["clientId"] client_secret = config["clientSecret"] scope = " ".join(config["scope"]) if not client_info: client_info = ToolOAuthClientInfo( - user_id=self.user_id, agent_id=self.agent.id, tool_id=self.id, client_id=client_id, @@ -104,7 +103,7 @@ async def _load_oauth(self) -> AgentToolOauth: authorization_endpoint=AnyHttpUrl(f"{base_url}/authorize"), token_endpoint=AnyHttpUrl(f"{base_url}/oauth/token") ) - client_info = await ToolOAuthClientInfoRepository(self.db).find_by_ids(self.user_id, self.agent.id, self.id) + client_info = await ToolOAuthClientInfoRepository(self.db).find_by_ids(self.agent.id, self.id) # add offline_access scope to be able to refresh tokens return AgentToolOauth(base_url, oauth_metadata, cast(str, cast(ToolOAuthClientInfo, client_info).scope) + " offline_access", self.agent.id, self.id, self.user_id, self.db) @@ -136,7 +135,7 @@ async def auth(self, auth_callback: ToolAuthCallback, state: ToolOAuthState): async def teardown(self): await ToolOAuthRepository(self.db).delete_token(self.user_id, self.agent.id, self.id) - await ToolOAuthClientInfoRepository(self.db).delete(self.user_id, self.agent.id, self.id) + await ToolOAuthClientInfoRepository(self.db).delete(self.agent.id, self.id) await JiraToolConfigRepository(self.db).delete(self.agent.id) async def build_langchain_tools(self) -> list[BaseTool]: diff --git a/src/backend/tero/tools/mcp/tool.py b/src/backend/tero/tools/mcp/tool.py index 9d47c25..68d3cdb 100644 --- a/src/backend/tero/tools/mcp/tool.py +++ b/src/backend/tero/tools/mcp/tool.py @@ -44,7 +44,7 @@ async def _setup_tool(self, prev_config: Optional[AgentToolConfig]) -> Optional[ async def teardown(self): await ToolOAuthRepository(self.db).delete_token(self.user_id, self.agent.id, self.id) - await ToolOAuthClientInfoRepository(self.db).delete(self.user_id, self.agent.id, self.id) + await ToolOAuthClientInfoRepository(self.db).delete(self.agent.id, self.id) @asynccontextmanager async def load(self) -> AsyncIterator['McpTool']: diff --git a/src/backend/tero/tools/oauth.py b/src/backend/tero/tools/oauth.py index 4d83472..bea3112 100644 --- a/src/backend/tero/tools/oauth.py +++ b/src/backend/tero/tools/oauth.py @@ -9,8 +9,18 @@ from fastapi import HTTPException, status import httpx from mcp.client.auth import OAuthClientProvider, TokenStorage, PKCEParameters, OAuthFlowError, OAuthRegistrationError +from mcp.client.auth.utils import ( + build_oauth_authorization_server_metadata_discovery_urls, + build_protected_resource_metadata_discovery_urls, + create_client_registration_request, + create_oauth_metadata_request, + get_client_metadata_scopes, + handle_auth_metadata_response, + handle_protected_resource_response, + handle_registration_response, +) from mcp.shared.auth import OAuthClientMetadata, OAuthToken, OAuthClientInformationFull, OAuthMetadata -from pydantic import AnyHttpUrl, BaseModel, ValidationError +from pydantic import AnyHttpUrl, BaseModel from sqlmodel import SQLModel, Field, col, select, delete, and_ from sqlmodel.ext.asyncio.session import AsyncSession @@ -52,7 +62,6 @@ class ToolOAuthState(SQLModel, table=True): class ToolOAuthClientInfo(SQLModel, table=True): __tablename__ : Any = "tool_oauth_client_info" - user_id: int = Field(primary_key=True) agent_id: int = Field(primary_key=True) tool_id: str = Field(primary_key=True) client_id: str @@ -141,15 +150,15 @@ async def save(self, info: ToolOAuthClientInfo): await self._db.merge(info) await self._db.commit() - async def find_by_ids(self, user_id: int, agent_id: int, tool_id: str) -> Optional[ToolOAuthClientInfo]: + async def find_by_ids(self, agent_id: int, tool_id: str) -> Optional[ToolOAuthClientInfo]: stmt = (select(ToolOAuthClientInfo). - where(ToolOAuthClientInfo.user_id == user_id, ToolOAuthClientInfo.agent_id == agent_id, ToolOAuthClientInfo.tool_id == tool_id)) + where(ToolOAuthClientInfo.agent_id == agent_id, ToolOAuthClientInfo.tool_id == tool_id)) result = await self._db.exec(stmt) return result.one_or_none() - async def delete(self, user_id: int, agent_id: int, tool_id: str): + async def delete(self, agent_id: int, tool_id: str): stmt = scalar(delete(ToolOAuthClientInfo). - where(and_(ToolOAuthClientInfo.user_id == user_id, ToolOAuthClientInfo.agent_id == agent_id, ToolOAuthClientInfo.tool_id == tool_id))) + where(and_(ToolOAuthClientInfo.agent_id == agent_id, ToolOAuthClientInfo.tool_id == tool_id))) await self._db.exec(stmt) await self._db.commit() @@ -203,7 +212,7 @@ async def set_tokens(self, tokens: OAuthToken): )) async def get_client_info(self) -> Optional[OAuthClientInformationFull]: - ret = await self._client_info_repo.find_by_ids(self._user_id, self._agent_id, self._tool_id) + ret = await self._client_info_repo.find_by_ids(self._agent_id, self._tool_id) return OAuthClientInformationFull( client_id=ret.client_id, client_secret=ret.client_secret, @@ -211,10 +220,9 @@ async def get_client_info(self) -> Optional[OAuthClientInformationFull]: async def set_client_info(self, client_info: OAuthClientInformationFull): info = ToolOAuthClientInfo( - user_id=self._user_id, agent_id=self._agent_id, tool_id=self._tool_id, - client_id=client_info.client_id, + client_id=cast(str, client_info.client_id), client_secret=cast(str, client_info.client_secret), scope=client_info.scope, updated_at=datetime.now(timezone.utc) @@ -311,11 +319,17 @@ async def ensure_token(self) -> None: await self._discover_oauth_metadata() - registration_request = await self._register_client() - if registration_request: + registration_request = create_client_registration_request( + self.context.oauth_metadata, + self.context.client_metadata, + self.context.get_authorization_base_url(self.context.server_url), + ) + if not self.context.client_info: registration_response = await self._http_request(registration_request) try: - await self._handle_registration_response(registration_response) + client_information = await handle_registration_response(registration_response) + self.context.client_info = client_information + await self.context.storage.set_client_info(client_information) except OAuthRegistrationError as e: # some mcp servers return 404 others may fail with 400 (eg: mcp playwright) when registration is not supported if e.args and e.args[0].startswith("Registration failed: 4"): @@ -335,27 +349,54 @@ async def _http_request(self, request: httpx.Request) -> httpx.Response: return await self._http_client.send(request) async def _discover_oauth_metadata(self) -> None: - # even though the contract of _discover_protected_resource says it expects an httpx.Response, you can actually pass None and it properly handles it - discovery_request = await self._discover_protected_resource(cast(httpx.Response, None)) - discovery_response = await self._http_request(discovery_request) - await self._handle_protected_resource_response(discovery_response) - - discovery_urls = self._get_discovery_urls() - for url in discovery_urls: - oauth_metadata_request = self._create_oauth_metadata_request(url) - oauth_metadata_response = await self._http_request(oauth_metadata_request) + prm_discovery_urls = build_protected_resource_metadata_discovery_urls(None, self.context.server_url) - if oauth_metadata_response.status_code == 200: - try: - await self._handle_oauth_metadata_response(oauth_metadata_response) - break - except ValidationError: - continue - elif oauth_metadata_response.status_code < 400 or oauth_metadata_response.status_code >= 500: + for url in prm_discovery_urls: + discovery_request = create_oauth_metadata_request(url) + discovery_response = await self._http_request(discovery_request) + + prm = await handle_protected_resource_response(discovery_response) + if prm: + self.context.protected_resource_metadata = prm + self.context.auth_server_url = str(prm.authorization_servers[0]) + break + else: + logger.debug(f"Protected resource metadata discovery failed: {url}") + + asm_discovery_urls = build_oauth_authorization_server_metadata_discovery_urls( + self.context.auth_server_url, self.context.server_url + ) + + for url in asm_discovery_urls: + oauth_metadata_request = create_oauth_metadata_request(url) + oauth_metadata_response = await self._http_request(oauth_metadata_request) + + ok, asm = await handle_auth_metadata_response(oauth_metadata_response) + if not ok: + break + if ok and asm: + self.context.oauth_metadata = asm break + else: + logger.debug(f"OAuth metadata discovery failed: {url}") + + # Add this custom logic so if scope was already provided, do not override it + if not self.context.client_metadata.scope: + self.context.client_metadata.scope = get_client_metadata_scopes( + None, + self.context.protected_resource_metadata, + self.context.oauth_metadata, + ) + + async def _perform_authorization(self) -> httpx.Request: + # same as the one in oauth2.py from mcp library but stores code verifier and state in the class so when redirect is invoked it can store them to later resume the flow + if self.context.client_metadata.redirect_uris is None: + raise OAuthFlowError("No redirect URIs provided for authorization code grant") + if not self.context.redirect_handler: + raise OAuthFlowError("No redirect handler provided for authorization code grant") + if not self.context.callback_handler: + raise OAuthFlowError("No callback handler provided for authorization code grant") - async def _perform_authorization(self) -> tuple[str, str]: - # same as the one in auth.py from mcp library but stores code verifier and state in the class so when redirect is invoked it can store them to later resume the flow if self.context.oauth_metadata and self.context.oauth_metadata.authorization_endpoint: auth_endpoint = str(self.context.oauth_metadata.authorization_endpoint) else: @@ -385,16 +426,15 @@ async def _perform_authorization(self) -> tuple[str, str]: auth_params["scope"] = self.context.client_metadata.scope authorization_url = f"{auth_endpoint}?{urlencode(auth_params)}" - await self._redirect_handler(authorization_url) + await self.context.redirect_handler(authorization_url) + # returning dummy request just to satisfy the return type + return httpx.Request("GET", "https://dummy") # part of this logic is the same as async_auth_flow after the callback is invoked async def callback(self, auth_callback: ToolAuthCallback, state: ToolOAuthState): if not self._initialized: await self._initialize() await self._discover_oauth_metadata() - try: - token_request = await self._exchange_token(cast(str, auth_callback.code), state.code_verifier) - token_response = await self._http_request(token_request) - await self._handle_token_response(token_response) - except Exception as e: - raise ToolOAuthCallbackError() if str(e).startswith("Token exchange failed: 401") else e + token_request = await self._exchange_token_authorization_code(cast(str, auth_callback.code), state.code_verifier) + token_response = await self._http_request(token_request) + await self._handle_token_response(token_response) diff --git a/src/backend/tests/cleanup_testcontainers.py b/src/backend/tests/cleanup_testcontainers.py new file mode 100644 index 0000000..89f661d --- /dev/null +++ b/src/backend/tests/cleanup_testcontainers.py @@ -0,0 +1,14 @@ +import docker +import docker.errors +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +client = docker.from_env() + +for container in client.containers.list(all=True, filters={"label": "org.testcontainers"}): + logger.info(f"Removing container {container.name} ({container.id})") + try: + container.remove(force=True) + except docker.errors.APIError as e: + logger.error(f"Failed to remove {container.id}", exc_info=True) diff --git a/src/backend/tests/test_config.py b/src/backend/tests/test_config.py index a39ded0..b1cd3c0 100644 --- a/src/backend/tests/test_config.py +++ b/src/backend/tests/test_config.py @@ -11,4 +11,5 @@ async def test_manifest(client: AsyncClient): "clientId": env.openid_client_id, "scope": env.openid_scope }, + "disablePublishGlobal": env.disable_publish_global or False } diff --git a/src/browser-extension/assets/copilot-title.svg b/src/browser-extension/assets/copilot-title.svg deleted file mode 100644 index e496ed3..0000000 --- a/src/browser-extension/assets/copilot-title.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/browser-extension/entrypoints/background.ts b/src/browser-extension/entrypoints/background.ts index c79efc0..cc51b27 100644 --- a/src/browser-extension/entrypoints/background.ts +++ b/src/browser-extension/entrypoints/background.ts @@ -97,14 +97,16 @@ export default defineBackground(() => { const activateAgent = async (tabId: number, agent: Agent, url: string) => { const session = new AgentSession(tabId, agent, url); let success = true; + let errorStatus = undefined; try { await session.activate((msg) => sendToTab(tabId, msg)); await saveAgentSession(session); } catch (e) { // exceptions from http methods are already logged so no need to handle them success = false; + errorStatus = (e as any)?.status; } - sendToTab(tabId, new AgentActivation(agent, success)); + sendToTab(tabId, new AgentActivation(agent, success, errorStatus)); }; browser.webRequest.onBeforeRequest.addListener( diff --git a/src/browser-extension/entrypoints/iframe/App.vue b/src/browser-extension/entrypoints/iframe/App.vue index 5769b64..0aa25e6 100644 --- a/src/browser-extension/entrypoints/iframe/App.vue +++ b/src/browser-extension/entrypoints/iframe/App.vue @@ -11,6 +11,7 @@ import { findTabState, saveTabState } from '~/utils/tab-state-repository' import { findAgentSession } from '~/utils/agent-session-repository' import { FlowStepError } from '~/utils/flow' import { HttpServiceError } from '~/utils/http' +import { AuthService } from '~/utils/auth' import ToastMessage from '~/components/ToastMessage.vue' import CopilotChat from '~/components/CopilotChat.vue' import CopilotList from '~/components/CopilotList.vue' @@ -180,11 +181,18 @@ const onActivateAgent = async (agentId: string) => { sendToServiceWorker(new ActivateAgent(agentId, tab.url!)) } -const onAgentActivation = (msg: AgentActivation) => { +const onAgentActivation = async (msg: AgentActivation) => { if (displayMode.value === TabDisplayMode.CLOSED) { onToggleSidebar() } if (!msg.success) { + if (msg.errorStatus === 401 && msg.agent.manifest.auth) { + const authService = new AuthService(msg.agent.manifest.auth) + await authService.ensureAuthenticated() + await onActivateAgent(msg.agent.manifest.id!) + return + } + const text = t('activationError', { agentName: msg.agent.manifest.name, contactEmail: msg.agent.manifest.contactEmail }) toast.error({ component: ToastMessage, props: { message: text } }, { icon: IconAlertCircleFilled }) } else { diff --git a/src/browser-extension/utils/agent.ts b/src/browser-extension/utils/agent.ts index 39fa02b..430b273 100644 --- a/src/browser-extension/utils/agent.ts +++ b/src/browser-extension/utils/agent.ts @@ -16,10 +16,9 @@ export abstract class AgentSource { // comparing with agents-hub for backwards compatibility with environments that haven't fully migrated to tero if (manifest.auth && (manifest.auth.clientId === AgentType.TeroAgent || manifest.auth.clientId === "agents-hub")) { const authService = new AuthService(manifest.auth!) - await authService.login() + await authService.ensureAuthenticated() const tero = new TeroServer(url, manifest) agents.push(...await tero.findAgents(authService)) - console.log("loaded agents", agents) } else { agents.push(new StandaloneAgent(url, manifest)) } diff --git a/src/browser-extension/utils/auth.ts b/src/browser-extension/utils/auth.ts index 5582076..30a0e35 100644 --- a/src/browser-extension/utils/auth.ts +++ b/src/browser-extension/utils/auth.ts @@ -128,4 +128,13 @@ export class AuthService { await this.userManager.signinPopup() } + public async ensureAuthenticated(): Promise { + const user = await this.getUser() + if (!user) { + await this.login() + return (await this.getUser())! + } + return user + } + } \ No newline at end of file diff --git a/src/browser-extension/utils/browser-message.ts b/src/browser-extension/utils/browser-message.ts index f5d4224..6ec5b15 100644 --- a/src/browser-extension/utils/browser-message.ts +++ b/src/browser-extension/utils/browser-message.ts @@ -87,15 +87,17 @@ export class ActivateAgent extends BrowserMessage { export class AgentActivation extends BrowserMessage { agent: Agent success: boolean + errorStatus?: number - constructor(agent: Agent, success: boolean) { + constructor(agent: Agent, success: boolean, errorStatus?: number) { super("agentActivation") this.agent = agent this.success = success + this.errorStatus = errorStatus } public static fromJsonObject(obj: any): AgentActivation { - return new AgentActivation(Agent.fromJsonObject(obj.agent), obj.success) + return new AgentActivation(Agent.fromJsonObject(obj.agent), obj.success, obj.errorStatus) } } diff --git a/src/browser-extension/utils/http.ts b/src/browser-extension/utils/http.ts index e41f7e0..aa37e23 100644 --- a/src/browser-extension/utils/http.ts +++ b/src/browser-extension/utils/http.ts @@ -12,20 +12,22 @@ const fetchResponse = async (url: string, options?: RequestInit) => { if (ret.headers.get('Content-Type') === 'application/json') { let json = JSON.parse(body) if ('detail' in json) { - throw new HttpServiceError(json.detail) + throw new HttpServiceError(json.detail, ret.status) } } - throw new HttpServiceError() + throw new HttpServiceError(undefined, ret.status) } return ret } export class HttpServiceError extends Error { detail?: string + status?: number - constructor(detail?: string) { + constructor(detail?: string, status?: number) { super() this.detail = detail + this.status = status } } @@ -53,7 +55,7 @@ async function* fetchSSEStream(resp: Response, url: string, options?: RequestIni for (const event of events) { if (event.event === "error") { console.warn(`Problem while reading stream response from ${options?.method ? options.method : 'GET'} ${url}`, event) - throw new HttpServiceError() + throw new HttpServiceError(undefined, resp.status) } if (event.event || event.data) { yield event diff --git a/src/common/src/assets/styles.css b/src/common/src/assets/styles.css index 4c25cfd..fa5a706 100644 --- a/src/common/src/assets/styles.css +++ b/src/common/src/assets/styles.css @@ -32,6 +32,7 @@ --animate-wiggle: wiggle 1s ease-in-out infinite; --animate-glowing: glowing 1s ease-in-out infinite; --animate-magnify-search: magnify-search 1s ease-in-out infinite; + --animate-fade-in: fade-in 0.4s ease-out forwards; --shadow-light: 0px 2px 5px 0px rgba(0,0,0,0.07); --border: 1px solid #ccc; } @@ -89,6 +90,17 @@ html:root, } } +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + @keyframes border-pulse { 0% { box-shadow: 0 0 0 0 currentColor; diff --git a/src/common/src/components/chat/ChatStatus.vue b/src/common/src/components/chat/ChatStatus.vue index 84ab2d3..8887beb 100644 --- a/src/common/src/components/chat/ChatStatus.vue +++ b/src/common/src/components/chat/ChatStatus.vue @@ -1,7 +1,7 @@ \ No newline at end of file +