crca 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- .github/ISSUE_TEMPLATE/bug_report.md +65 -0
- .github/ISSUE_TEMPLATE/feature_request.md +41 -0
- .github/PULL_REQUEST_TEMPLATE.md +20 -0
- .github/workflows/publish-manual.yml +61 -0
- .github/workflows/publish.yml +64 -0
- .gitignore +214 -0
- CRCA.py +4156 -0
- LICENSE +201 -0
- MANIFEST.in +43 -0
- PKG-INFO +5035 -0
- README.md +4959 -0
- __init__.py +17 -0
- branches/CRCA-Q.py +2728 -0
- branches/crca_cg/corposwarm.py +9065 -0
- branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
- branches/crca_cg/package.json +5 -0
- branches/crca_cg/test_bolt_integration.py +446 -0
- branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
- branches/crca_cg/test_new_features.py +163 -0
- branches/crca_sd/__init__.py +149 -0
- branches/crca_sd/crca_sd_core.py +770 -0
- branches/crca_sd/crca_sd_governance.py +1325 -0
- branches/crca_sd/crca_sd_mpc.py +1130 -0
- branches/crca_sd/crca_sd_realtime.py +1844 -0
- branches/crca_sd/crca_sd_tui.py +1133 -0
- crca-1.4.0.dist-info/METADATA +5035 -0
- crca-1.4.0.dist-info/RECORD +501 -0
- crca-1.4.0.dist-info/WHEEL +4 -0
- crca-1.4.0.dist-info/licenses/LICENSE +201 -0
- docs/CRCA-Q.md +2333 -0
- examples/config.yaml.example +25 -0
- examples/crca_sd_example.py +513 -0
- examples/data_broker_example.py +294 -0
- examples/logistics_corporation.py +861 -0
- examples/palantir_example.py +299 -0
- examples/policy_bench.py +934 -0
- examples/pridnestrovia-sd.py +705 -0
- examples/pridnestrovia_realtime.py +1902 -0
- prompts/__init__.py +10 -0
- prompts/default_crca.py +101 -0
- pyproject.toml +151 -0
- requirements.txt +76 -0
- schemas/__init__.py +43 -0
- schemas/mcpSchemas.py +51 -0
- schemas/policy.py +458 -0
- templates/__init__.py +38 -0
- templates/base_specialized_agent.py +195 -0
- templates/drift_detection.py +325 -0
- templates/examples/causal_agent_template.py +309 -0
- templates/examples/drag_drop_example.py +213 -0
- templates/examples/logistics_agent_template.py +207 -0
- templates/examples/trading_agent_template.py +206 -0
- templates/feature_mixins.py +253 -0
- templates/graph_management.py +442 -0
- templates/llm_integration.py +194 -0
- templates/module_registry.py +276 -0
- templates/mpc_planner.py +280 -0
- templates/policy_loop.py +1168 -0
- templates/prediction_framework.py +448 -0
- templates/statistical_methods.py +778 -0
- tests/sanity.yml +31 -0
- tests/sanity_check +406 -0
- tests/test_core.py +47 -0
- tests/test_crca_excel.py +166 -0
- tests/test_crca_sd.py +780 -0
- tests/test_data_broker.py +424 -0
- tests/test_palantir.py +349 -0
- tools/__init__.py +38 -0
- tools/actuators.py +437 -0
- tools/bolt.diy/Dockerfile +103 -0
- tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
- tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
- tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
- tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
- tools/bolt.diy/app/components/@settings/index.ts +12 -0
- tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
- tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
- tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
- tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
- tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
- tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
- tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
- tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
- tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
- tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
- tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
- tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
- tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
- tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
- tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
- tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
- tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
- tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
- tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
- tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
- tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
- tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
- tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
- tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
- tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
- tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
- tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
- tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
- tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
- tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
- tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
- tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
- tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
- tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
- tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
- tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
- tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
- tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
- tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
- tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
- tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
- tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
- tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
- tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
- tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
- tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
- tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
- tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
- tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
- tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
- tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
- tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
- tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
- tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
- tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
- tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
- tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
- tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
- tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
- tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
- tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
- tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
- tools/bolt.diy/app/components/header/Header.tsx +42 -0
- tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
- tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
- tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
- tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
- tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
- tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
- tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
- tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
- tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
- tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
- tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
- tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
- tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
- tools/bolt.diy/app/components/txt +1 -0
- tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
- tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
- tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
- tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
- tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
- tools/bolt.diy/app/components/ui/Button.tsx +46 -0
- tools/bolt.diy/app/components/ui/Card.tsx +55 -0
- tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
- tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
- tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
- tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
- tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
- tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
- tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
- tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
- tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
- tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
- tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
- tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
- tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
- tools/bolt.diy/app/components/ui/Input.tsx +22 -0
- tools/bolt.diy/app/components/ui/Label.tsx +20 -0
- tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
- tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
- tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
- tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
- tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
- tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
- tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
- tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
- tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
- tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
- tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
- tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
- tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
- tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
- tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
- tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
- tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
- tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
- tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
- tools/bolt.diy/app/components/ui/index.ts +38 -0
- tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
- tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
- tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
- tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
- tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
- tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
- tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
- tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
- tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
- tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
- tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
- tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
- tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
- tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
- tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
- tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
- tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
- tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
- tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
- tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
- tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
- tools/bolt.diy/app/entry.client.tsx +7 -0
- tools/bolt.diy/app/entry.server.tsx +80 -0
- tools/bolt.diy/app/root.tsx +156 -0
- tools/bolt.diy/app/routes/_index.tsx +175 -0
- tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
- tools/bolt.diy/app/routes/api.chat.ts +463 -0
- tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
- tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
- tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
- tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
- tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
- tools/bolt.diy/app/routes/api.git-info.ts +69 -0
- tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
- tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
- tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
- tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
- tools/bolt.diy/app/routes/api.github-template.ts +242 -0
- tools/bolt.diy/app/routes/api.github-user.ts +287 -0
- tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
- tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
- tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
- tools/bolt.diy/app/routes/api.health.ts +8 -0
- tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
- tools/bolt.diy/app/routes/api.mandate.ts +351 -0
- tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
- tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
- tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
- tools/bolt.diy/app/routes/api.models.ts +90 -0
- tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
- tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
- tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
- tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
- tools/bolt.diy/app/routes/api.supabase.ts +56 -0
- tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
- tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
- tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
- tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
- tools/bolt.diy/app/routes/api.update.ts +21 -0
- tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
- tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
- tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
- tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
- tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
- tools/bolt.diy/app/routes/git.tsx +25 -0
- tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
- tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
- tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
- tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
- tools/bolt.diy/app/styles/animations.scss +49 -0
- tools/bolt.diy/app/styles/components/code.scss +9 -0
- tools/bolt.diy/app/styles/components/editor.scss +135 -0
- tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
- tools/bolt.diy/app/styles/components/terminal.scss +3 -0
- tools/bolt.diy/app/styles/components/toast.scss +23 -0
- tools/bolt.diy/app/styles/diff-view.css +72 -0
- tools/bolt.diy/app/styles/index.scss +73 -0
- tools/bolt.diy/app/styles/variables.scss +255 -0
- tools/bolt.diy/app/styles/z-index.scss +37 -0
- tools/bolt.diy/app/types/GitHub.ts +182 -0
- tools/bolt.diy/app/types/GitLab.ts +103 -0
- tools/bolt.diy/app/types/actions.ts +85 -0
- tools/bolt.diy/app/types/artifact.ts +5 -0
- tools/bolt.diy/app/types/context.ts +26 -0
- tools/bolt.diy/app/types/design-scheme.ts +93 -0
- tools/bolt.diy/app/types/global.d.ts +13 -0
- tools/bolt.diy/app/types/mandate.ts +333 -0
- tools/bolt.diy/app/types/model.ts +25 -0
- tools/bolt.diy/app/types/netlify.ts +94 -0
- tools/bolt.diy/app/types/supabase.ts +54 -0
- tools/bolt.diy/app/types/template.ts +8 -0
- tools/bolt.diy/app/types/terminal.ts +9 -0
- tools/bolt.diy/app/types/theme.ts +1 -0
- tools/bolt.diy/app/types/vercel.ts +67 -0
- tools/bolt.diy/app/utils/buffer.ts +29 -0
- tools/bolt.diy/app/utils/classNames.ts +65 -0
- tools/bolt.diy/app/utils/constants.ts +147 -0
- tools/bolt.diy/app/utils/debounce.ts +13 -0
- tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
- tools/bolt.diy/app/utils/diff.spec.ts +11 -0
- tools/bolt.diy/app/utils/diff.ts +117 -0
- tools/bolt.diy/app/utils/easings.ts +3 -0
- tools/bolt.diy/app/utils/fileLocks.ts +96 -0
- tools/bolt.diy/app/utils/fileUtils.ts +121 -0
- tools/bolt.diy/app/utils/folderImport.ts +73 -0
- tools/bolt.diy/app/utils/formatSize.ts +12 -0
- tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
- tools/bolt.diy/app/utils/githubStats.ts +9 -0
- tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
- tools/bolt.diy/app/utils/logger.ts +162 -0
- tools/bolt.diy/app/utils/markdown.ts +155 -0
- tools/bolt.diy/app/utils/mobile.ts +4 -0
- tools/bolt.diy/app/utils/os.ts +4 -0
- tools/bolt.diy/app/utils/path.ts +19 -0
- tools/bolt.diy/app/utils/projectCommands.ts +197 -0
- tools/bolt.diy/app/utils/promises.ts +19 -0
- tools/bolt.diy/app/utils/react.ts +6 -0
- tools/bolt.diy/app/utils/sampler.ts +49 -0
- tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
- tools/bolt.diy/app/utils/shell.ts +384 -0
- tools/bolt.diy/app/utils/stacktrace.ts +27 -0
- tools/bolt.diy/app/utils/stripIndent.ts +23 -0
- tools/bolt.diy/app/utils/terminal.ts +11 -0
- tools/bolt.diy/app/utils/unreachable.ts +3 -0
- tools/bolt.diy/app/vite-env.d.ts +2 -0
- tools/bolt.diy/assets/entitlements.mac.plist +25 -0
- tools/bolt.diy/assets/icons/icon.icns +0 -0
- tools/bolt.diy/assets/icons/icon.ico +0 -0
- tools/bolt.diy/assets/icons/icon.png +0 -0
- tools/bolt.diy/bindings.js +78 -0
- tools/bolt.diy/bindings.sh +33 -0
- tools/bolt.diy/docker-compose.yaml +145 -0
- tools/bolt.diy/electron/main/index.ts +201 -0
- tools/bolt.diy/electron/main/tsconfig.json +30 -0
- tools/bolt.diy/electron/main/ui/menu.ts +29 -0
- tools/bolt.diy/electron/main/ui/window.ts +54 -0
- tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
- tools/bolt.diy/electron/main/utils/constants.ts +4 -0
- tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
- tools/bolt.diy/electron/main/utils/reload.ts +35 -0
- tools/bolt.diy/electron/main/utils/serve.ts +71 -0
- tools/bolt.diy/electron/main/utils/store.ts +3 -0
- tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
- tools/bolt.diy/electron/main/vite.config.ts +44 -0
- tools/bolt.diy/electron/preload/index.ts +22 -0
- tools/bolt.diy/electron/preload/tsconfig.json +7 -0
- tools/bolt.diy/electron/preload/vite.config.ts +31 -0
- tools/bolt.diy/electron-builder.yml +64 -0
- tools/bolt.diy/electron-update.yml +4 -0
- tools/bolt.diy/eslint.config.mjs +57 -0
- tools/bolt.diy/functions/[[path]].ts +12 -0
- tools/bolt.diy/icons/angular.svg +1 -0
- tools/bolt.diy/icons/astro.svg +8 -0
- tools/bolt.diy/icons/chat.svg +1 -0
- tools/bolt.diy/icons/expo-brand.svg +1 -0
- tools/bolt.diy/icons/expo.svg +4 -0
- tools/bolt.diy/icons/logo-text.svg +1 -0
- tools/bolt.diy/icons/logo.svg +4 -0
- tools/bolt.diy/icons/mcp.svg +1 -0
- tools/bolt.diy/icons/nativescript.svg +1 -0
- tools/bolt.diy/icons/netlify.svg +10 -0
- tools/bolt.diy/icons/nextjs.svg +1 -0
- tools/bolt.diy/icons/nuxt.svg +1 -0
- tools/bolt.diy/icons/qwik.svg +1 -0
- tools/bolt.diy/icons/react.svg +1 -0
- tools/bolt.diy/icons/remix.svg +24 -0
- tools/bolt.diy/icons/remotion.svg +1 -0
- tools/bolt.diy/icons/shadcn.svg +21 -0
- tools/bolt.diy/icons/slidev.svg +60 -0
- tools/bolt.diy/icons/solidjs.svg +1 -0
- tools/bolt.diy/icons/stars.svg +1 -0
- tools/bolt.diy/icons/svelte.svg +1 -0
- tools/bolt.diy/icons/typescript.svg +1 -0
- tools/bolt.diy/icons/vite.svg +1 -0
- tools/bolt.diy/icons/vue.svg +1 -0
- tools/bolt.diy/load-context.ts +9 -0
- tools/bolt.diy/notarize.cjs +31 -0
- tools/bolt.diy/package.json +218 -0
- tools/bolt.diy/playwright.config.preview.ts +35 -0
- tools/bolt.diy/pre-start.cjs +26 -0
- tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
- tools/bolt.diy/public/apple-touch-icon.png +0 -0
- tools/bolt.diy/public/favicon.ico +0 -0
- tools/bolt.diy/public/favicon.svg +4 -0
- tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
- tools/bolt.diy/public/icons/Anthropic.svg +4 -0
- tools/bolt.diy/public/icons/Cohere.svg +4 -0
- tools/bolt.diy/public/icons/Deepseek.svg +5 -0
- tools/bolt.diy/public/icons/Default.svg +4 -0
- tools/bolt.diy/public/icons/Google.svg +4 -0
- tools/bolt.diy/public/icons/Groq.svg +4 -0
- tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
- tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
- tools/bolt.diy/public/icons/LMStudio.svg +5 -0
- tools/bolt.diy/public/icons/Mistral.svg +4 -0
- tools/bolt.diy/public/icons/Ollama.svg +4 -0
- tools/bolt.diy/public/icons/OpenAI.svg +4 -0
- tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
- tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
- tools/bolt.diy/public/icons/Perplexity.svg +4 -0
- tools/bolt.diy/public/icons/Together.svg +4 -0
- tools/bolt.diy/public/icons/xAI.svg +5 -0
- tools/bolt.diy/public/inspector-script.js +292 -0
- tools/bolt.diy/public/logo-dark-styled.png +0 -0
- tools/bolt.diy/public/logo-dark.png +0 -0
- tools/bolt.diy/public/logo-light-styled.png +0 -0
- tools/bolt.diy/public/logo-light.png +0 -0
- tools/bolt.diy/public/logo.svg +15 -0
- tools/bolt.diy/public/social_preview_index.jpg +0 -0
- tools/bolt.diy/scripts/clean.js +45 -0
- tools/bolt.diy/scripts/electron-dev.mjs +181 -0
- tools/bolt.diy/scripts/setup-env.sh +41 -0
- tools/bolt.diy/scripts/update-imports.sh +7 -0
- tools/bolt.diy/scripts/update.sh +52 -0
- tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
- tools/bolt.diy/services/execution-governor/config.ts +42 -0
- tools/bolt.diy/services/execution-governor/index.ts +683 -0
- tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
- tools/bolt.diy/services/execution-governor/package.json +31 -0
- tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
- tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
- tools/bolt.diy/services/execution-governor/types.ts +145 -0
- tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
- tools/bolt.diy/services/headless-executor/executor.ts +210 -0
- tools/bolt.diy/services/headless-executor/index.ts +323 -0
- tools/bolt.diy/services/headless-executor/package.json +27 -0
- tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
- tools/bolt.diy/services/headless-executor/types.ts +38 -0
- tools/bolt.diy/test-workflows.sh +240 -0
- tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
- tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
- tools/bolt.diy/tests/mandates/complex.json +53 -0
- tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
- tools/bolt.diy/tests/mandates/simple.json +35 -0
- tools/bolt.diy/tsconfig.json +37 -0
- tools/bolt.diy/types/istextorbinary.d.ts +15 -0
- tools/bolt.diy/uno.config.ts +279 -0
- tools/bolt.diy/vite-electron.config.ts +76 -0
- tools/bolt.diy/vite.config.ts +112 -0
- tools/bolt.diy/worker-configuration.d.ts +22 -0
- tools/bolt.diy/wrangler.toml +6 -0
- tools/code_generator.py +461 -0
- tools/file_operations.py +465 -0
- tools/mandate_generator.py +337 -0
- tools/mcpClientUtils.py +1216 -0
- tools/sensors.py +285 -0
- utils/Agent_types.py +15 -0
- utils/AnyToStr.py +0 -0
- utils/HHCS.py +277 -0
- utils/__init__.py +30 -0
- utils/agent.py +3627 -0
- utils/aop.py +2948 -0
- utils/canonical.py +143 -0
- utils/conversation.py +1195 -0
- utils/doctrine_versioning +230 -0
- utils/formatter.py +474 -0
- utils/ledger.py +311 -0
- utils/out_types.py +16 -0
- utils/rollback.py +339 -0
- utils/router.py +929 -0
- utils/tui.py +1908 -0
utils/conversation.py
ADDED
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conversation management and history tracking utilities.
|
|
3
|
+
|
|
4
|
+
Provides classes and functions for managing conversation history,
|
|
5
|
+
including message storage, retrieval, persistence, and token-based
|
|
6
|
+
truncation for context window management.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import concurrent.futures
|
|
10
|
+
import datetime
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import traceback
|
|
15
|
+
import uuid
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
17
|
+
|
|
18
|
+
import yaml
|
|
19
|
+
from loguru import logger
|
|
20
|
+
|
|
21
|
+
# Use local any_to_str implementation
|
|
22
|
+
_utils_dir = os.path.dirname(os.path.abspath(__file__))
|
|
23
|
+
# Add utils directory to path for import
|
|
24
|
+
if _utils_dir not in sys.path:
|
|
25
|
+
sys.path.insert(0, _utils_dir)
|
|
26
|
+
try:
|
|
27
|
+
import AnyToStr
|
|
28
|
+
any_to_str = AnyToStr.any_to_str
|
|
29
|
+
except (ImportError, AttributeError):
|
|
30
|
+
# Fallback implementation if import fails
|
|
31
|
+
def any_to_str(data):
|
|
32
|
+
"""Convert any input data type to a nicely formatted string."""
|
|
33
|
+
try:
|
|
34
|
+
if isinstance(data, dict):
|
|
35
|
+
items = []
|
|
36
|
+
for k, v in data.items():
|
|
37
|
+
value = any_to_str(v)
|
|
38
|
+
items.append(f"{k}: {value}")
|
|
39
|
+
return "\n".join(items)
|
|
40
|
+
elif isinstance(data, (list, tuple)):
|
|
41
|
+
items = [any_to_str(x) for x in data]
|
|
42
|
+
if len(items) == 0:
|
|
43
|
+
return "[]" if isinstance(data, list) else "()"
|
|
44
|
+
return f"[{', '.join(items)}]" if isinstance(data, list) else f"({', '.join(items)})"
|
|
45
|
+
elif data is None:
|
|
46
|
+
return "None"
|
|
47
|
+
else:
|
|
48
|
+
return f'"{data}"' if isinstance(data, str) else str(data)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
return f"Error converting data: {str(e)}"
|
|
51
|
+
from swarms.utils.litellm_tokenizer import count_tokens
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from swarms.structs.agent import Agent
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def generate_conversation_id() -> str:
|
|
58
|
+
"""Generate a unique conversation identifier.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
UUID string for conversation identification.
|
|
62
|
+
"""
|
|
63
|
+
return str(uuid.uuid4())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_conversation_dir() -> str:
|
|
67
|
+
"""Get or create directory for storing conversation logs.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Path to conversations directory.
|
|
71
|
+
"""
|
|
72
|
+
conversation_dir = os.path.join(os.getcwd(), "conversations")
|
|
73
|
+
try:
|
|
74
|
+
os.makedirs(conversation_dir, mode=0o755, exist_ok=True)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Failed to create conversations directory: {str(e)}")
|
|
77
|
+
conversation_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "conversations")
|
|
78
|
+
os.makedirs(conversation_dir, mode=0o755, exist_ok=True)
|
|
79
|
+
return conversation_dir
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Conversation:
|
|
83
|
+
"""Manages conversation history with persistence and token management.
|
|
84
|
+
|
|
85
|
+
Provides in-memory storage for conversation messages with support for
|
|
86
|
+
automatic saving, token counting, context window management, and
|
|
87
|
+
multiple export formats (JSON, YAML).
|
|
88
|
+
|
|
89
|
+
Attributes:
|
|
90
|
+
id: Unique conversation identifier.
|
|
91
|
+
name: Conversation name.
|
|
92
|
+
conversation_history: List of message dictionaries.
|
|
93
|
+
system_prompt: Optional system prompt for the conversation.
|
|
94
|
+
context_length: Maximum token count for context window.
|
|
95
|
+
autosave: Whether to automatically save on message addition.
|
|
96
|
+
export_method: Format for saving ('json' or 'yaml').
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
id: str = generate_conversation_id(),
|
|
102
|
+
name: str = "conversation-test",
|
|
103
|
+
system_prompt: Optional[str] = None,
|
|
104
|
+
time_enabled: bool = False,
|
|
105
|
+
autosave: bool = False,
|
|
106
|
+
save_filepath: str = None,
|
|
107
|
+
load_filepath: str = None,
|
|
108
|
+
context_length: int = 8192,
|
|
109
|
+
rules: str = None,
|
|
110
|
+
custom_rules_prompt: str = None,
|
|
111
|
+
user: str = "User",
|
|
112
|
+
save_as_yaml_on: bool = False,
|
|
113
|
+
save_as_json_bool: bool = False,
|
|
114
|
+
token_count: bool = False,
|
|
115
|
+
message_id_on: bool = False,
|
|
116
|
+
tokenizer_model_name: str = "gpt-4.1",
|
|
117
|
+
conversations_dir: Optional[str] = None,
|
|
118
|
+
export_method: str = "json",
|
|
119
|
+
dynamic_context_window: bool = True,
|
|
120
|
+
caching: bool = True,
|
|
121
|
+
output_metadata: bool = False,
|
|
122
|
+
):
|
|
123
|
+
|
|
124
|
+
# Initialize all attributes first
|
|
125
|
+
self.id = id
|
|
126
|
+
self.name = name
|
|
127
|
+
self.save_filepath = save_filepath
|
|
128
|
+
self.system_prompt = system_prompt
|
|
129
|
+
self.time_enabled = time_enabled
|
|
130
|
+
self.autosave = autosave
|
|
131
|
+
self.conversations_dir = conversations_dir
|
|
132
|
+
self.tokenizer_model_name = tokenizer_model_name
|
|
133
|
+
self.message_id_on = message_id_on
|
|
134
|
+
self.load_filepath = load_filepath
|
|
135
|
+
self.context_length = context_length
|
|
136
|
+
self.rules = rules
|
|
137
|
+
self.custom_rules_prompt = custom_rules_prompt
|
|
138
|
+
self.user = user
|
|
139
|
+
self.save_as_yaml_on = save_as_yaml_on
|
|
140
|
+
self.save_as_json_bool = save_as_json_bool
|
|
141
|
+
self.token_count = token_count
|
|
142
|
+
self.export_method = export_method
|
|
143
|
+
self.dynamic_context_window = dynamic_context_window
|
|
144
|
+
self.caching = caching
|
|
145
|
+
self.output_metadata = output_metadata
|
|
146
|
+
|
|
147
|
+
if self.name is None:
|
|
148
|
+
self.name = id
|
|
149
|
+
|
|
150
|
+
self.conversation_history = []
|
|
151
|
+
|
|
152
|
+
self.setup_file_path()
|
|
153
|
+
self.setup()
|
|
154
|
+
|
|
155
|
+
def setup_file_path(self):
|
|
156
|
+
"""Set up the file path for saving the conversation and load existing data if available."""
|
|
157
|
+
# Validate export method
|
|
158
|
+
if self.export_method not in ["json", "yaml"]:
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"Invalid export_method: {self.export_method}. Must be 'json' or 'yaml'"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Set default save filepath if not provided
|
|
164
|
+
if not self.save_filepath:
|
|
165
|
+
# Ensure extension matches export method
|
|
166
|
+
extension = (
|
|
167
|
+
".json" if self.export_method == "json" else ".yaml"
|
|
168
|
+
)
|
|
169
|
+
self.save_filepath = (
|
|
170
|
+
f"conversation_{self.name}{extension}"
|
|
171
|
+
)
|
|
172
|
+
logger.debug(
|
|
173
|
+
f"Setting default save filepath to: {self.save_filepath}"
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
# Validate that provided filepath extension matches export method
|
|
177
|
+
file_ext = os.path.splitext(self.save_filepath)[1].lower()
|
|
178
|
+
expected_ext = (
|
|
179
|
+
".json" if self.export_method == "json" else ".yaml"
|
|
180
|
+
)
|
|
181
|
+
if file_ext != expected_ext:
|
|
182
|
+
logger.warning(
|
|
183
|
+
f"Save filepath extension ({file_ext}) does not match export_method ({self.export_method}). "
|
|
184
|
+
f"Updating filepath extension to match export method."
|
|
185
|
+
)
|
|
186
|
+
base_name = os.path.splitext(self.save_filepath)[0]
|
|
187
|
+
self.save_filepath = f"{base_name}{expected_ext}"
|
|
188
|
+
|
|
189
|
+
self.created_at = datetime.datetime.now().strftime(
|
|
190
|
+
"%Y-%m-%d_%H-%M-%S"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Check if file exists and load it
|
|
194
|
+
if os.path.exists(self.save_filepath):
|
|
195
|
+
logger.debug(
|
|
196
|
+
f"Found existing conversation file at: {self.save_filepath}"
|
|
197
|
+
)
|
|
198
|
+
try:
|
|
199
|
+
self.load(self.save_filepath)
|
|
200
|
+
logger.info(
|
|
201
|
+
f"Loaded existing conversation from {self.save_filepath}"
|
|
202
|
+
)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(
|
|
205
|
+
f"Failed to load existing conversation from {self.save_filepath}: {str(e)}"
|
|
206
|
+
)
|
|
207
|
+
# Keep the empty conversation_history initialized in __init__
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
logger.debug(
|
|
211
|
+
f"No existing conversation file found at: {self.save_filepath}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def setup(self):
|
|
215
|
+
# Set up conversations directory
|
|
216
|
+
self.conversations_dir = (
|
|
217
|
+
self.conversations_dir
|
|
218
|
+
or os.path.join(
|
|
219
|
+
os.path.expanduser("~"), ".swarms", "conversations"
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
os.makedirs(self.conversations_dir, exist_ok=True)
|
|
223
|
+
|
|
224
|
+
# Try to load existing conversation if it exists
|
|
225
|
+
conversation_file = os.path.join(
|
|
226
|
+
self.conversations_dir, f"{self.name}.json"
|
|
227
|
+
)
|
|
228
|
+
if os.path.exists(conversation_file):
|
|
229
|
+
with open(conversation_file, "r") as f:
|
|
230
|
+
saved_data = json.load(f)
|
|
231
|
+
# Update attributes from saved data
|
|
232
|
+
for key, value in saved_data.get(
|
|
233
|
+
"metadata", {}
|
|
234
|
+
).items():
|
|
235
|
+
if hasattr(self, key):
|
|
236
|
+
setattr(self, key, value)
|
|
237
|
+
self.conversation_history = saved_data.get(
|
|
238
|
+
"history", []
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
self._initialize_new_conversation()
|
|
242
|
+
|
|
243
|
+
def _initialize_new_conversation(self):
|
|
244
|
+
"""Initialize a new conversation with system prompt and rules."""
|
|
245
|
+
if self.system_prompt is not None:
|
|
246
|
+
self.add("System", self.system_prompt)
|
|
247
|
+
|
|
248
|
+
if self.rules is not None:
|
|
249
|
+
self.add(self.user or "User", self.rules)
|
|
250
|
+
|
|
251
|
+
if self.custom_rules_prompt is not None:
|
|
252
|
+
self.add(self.user or "User", self.custom_rules_prompt)
|
|
253
|
+
|
|
254
|
+
def _autosave(self):
|
|
255
|
+
"""Automatically save the conversation if autosave is enabled."""
|
|
256
|
+
return self.export()
|
|
257
|
+
|
|
258
|
+
def add_in_memory(
|
|
259
|
+
self,
|
|
260
|
+
role: str,
|
|
261
|
+
content: Union[str, dict, list, Any],
|
|
262
|
+
category: Optional[str] = None,
|
|
263
|
+
):
|
|
264
|
+
"""Add a message to the conversation history.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
role (str): The role of the speaker (e.g., 'User', 'System').
|
|
268
|
+
content (Union[str, dict, list]): The content of the message to be added.
|
|
269
|
+
category (Optional[str]): Optional category for the message.
|
|
270
|
+
"""
|
|
271
|
+
# Base message with role and timestamp
|
|
272
|
+
message = {
|
|
273
|
+
"role": role,
|
|
274
|
+
"content": content,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if self.time_enabled:
|
|
278
|
+
message["timestamp"] = datetime.datetime.now().isoformat()
|
|
279
|
+
|
|
280
|
+
if self.message_id_on:
|
|
281
|
+
message["message_id"] = str(uuid.uuid4())
|
|
282
|
+
|
|
283
|
+
if category:
|
|
284
|
+
message["category"] = category
|
|
285
|
+
|
|
286
|
+
# Add message to conversation history
|
|
287
|
+
self.conversation_history.append(message)
|
|
288
|
+
|
|
289
|
+
# Handle token counting in a separate thread if enabled
|
|
290
|
+
if self.token_count is True:
|
|
291
|
+
tokens = count_tokens(
|
|
292
|
+
text=any_to_str(content),
|
|
293
|
+
model=self.tokenizer_model_name,
|
|
294
|
+
)
|
|
295
|
+
message["token_count"] = tokens
|
|
296
|
+
|
|
297
|
+
return message
|
|
298
|
+
|
|
299
|
+
def export_and_count_categories(self) -> Dict[str, int]:
|
|
300
|
+
"""Count tokens for messages categorized as 'input' or 'output'.
|
|
301
|
+
|
|
302
|
+
Extracts messages by category, concatenates their content, and
|
|
303
|
+
counts tokens using the configured tokenizer model.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Dictionary with 'input_tokens', 'output_tokens', and 'total_tokens'.
|
|
307
|
+
"""
|
|
308
|
+
try:
|
|
309
|
+
# Extract input and output messages
|
|
310
|
+
input_messages = []
|
|
311
|
+
output_messages = []
|
|
312
|
+
|
|
313
|
+
for message in self.conversation_history:
|
|
314
|
+
# Get message content and ensure it's a string
|
|
315
|
+
content = message.get("content", "")
|
|
316
|
+
if not isinstance(content, str):
|
|
317
|
+
content = str(content)
|
|
318
|
+
|
|
319
|
+
# Sort messages by category
|
|
320
|
+
category = message.get("category", "")
|
|
321
|
+
if category == "input":
|
|
322
|
+
input_messages.append(content)
|
|
323
|
+
elif category == "output":
|
|
324
|
+
output_messages.append(content)
|
|
325
|
+
|
|
326
|
+
# Join messages with spaces
|
|
327
|
+
all_input_text = " ".join(input_messages)
|
|
328
|
+
all_output_text = " ".join(output_messages)
|
|
329
|
+
|
|
330
|
+
# Count tokens only if there is text
|
|
331
|
+
input_tokens = (
|
|
332
|
+
count_tokens(
|
|
333
|
+
all_input_text, self.tokenizer_model_name
|
|
334
|
+
)
|
|
335
|
+
if all_input_text.strip()
|
|
336
|
+
else 0
|
|
337
|
+
)
|
|
338
|
+
output_tokens = (
|
|
339
|
+
count_tokens(
|
|
340
|
+
all_output_text, self.tokenizer_model_name
|
|
341
|
+
)
|
|
342
|
+
if all_output_text.strip()
|
|
343
|
+
else 0
|
|
344
|
+
)
|
|
345
|
+
total_tokens = input_tokens + output_tokens
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
"input_tokens": input_tokens,
|
|
349
|
+
"output_tokens": output_tokens,
|
|
350
|
+
"total_tokens": total_tokens,
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
logger.error(
|
|
355
|
+
f"Error in export_and_count_categories: {str(e)}"
|
|
356
|
+
)
|
|
357
|
+
return {
|
|
358
|
+
"input_tokens": 0,
|
|
359
|
+
"output_tokens": 0,
|
|
360
|
+
"total_tokens": 0,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
def add(
|
|
364
|
+
self,
|
|
365
|
+
role: str,
|
|
366
|
+
content: Union[str, dict, list, Any],
|
|
367
|
+
metadata: Optional[dict] = None,
|
|
368
|
+
category: Optional[str] = None,
|
|
369
|
+
):
|
|
370
|
+
"""Add a message to the conversation history.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
role (str): The role of the speaker (e.g., 'User', 'System').
|
|
374
|
+
content (Union[str, dict, list]): The content of the message to be added.
|
|
375
|
+
metadata (Optional[dict]): Optional metadata for the message.
|
|
376
|
+
category (Optional[str]): Optional category for the message.
|
|
377
|
+
"""
|
|
378
|
+
result = self.add_in_memory(
|
|
379
|
+
role=role, content=content, category=category
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Ensure autosave happens after the message is added
|
|
383
|
+
if self.autosave:
|
|
384
|
+
self._autosave()
|
|
385
|
+
|
|
386
|
+
return result
|
|
387
|
+
|
|
388
|
+
def add_multiple_messages(
|
|
389
|
+
self, roles: List[str], contents: List[Union[str, dict, list]]
|
|
390
|
+
):
|
|
391
|
+
added = self.add_multiple(roles, contents)
|
|
392
|
+
|
|
393
|
+
if self.autosave:
|
|
394
|
+
self._autosave()
|
|
395
|
+
|
|
396
|
+
return added
|
|
397
|
+
|
|
398
|
+
def add_multiple(
|
|
399
|
+
self,
|
|
400
|
+
roles: List[str],
|
|
401
|
+
contents: List[Union[str, dict, list, any]],
|
|
402
|
+
):
|
|
403
|
+
"""Add multiple messages to the conversation history."""
|
|
404
|
+
if len(roles) != len(contents):
|
|
405
|
+
raise ValueError(
|
|
406
|
+
"Number of roles and contents must match."
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# Now create a formula to get 25% of available cpus
|
|
410
|
+
max_workers = int(os.cpu_count() * 0.25)
|
|
411
|
+
|
|
412
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
413
|
+
max_workers=max_workers
|
|
414
|
+
) as executor:
|
|
415
|
+
futures = [
|
|
416
|
+
executor.submit(self.add, role, content)
|
|
417
|
+
for role, content in zip(roles, contents)
|
|
418
|
+
]
|
|
419
|
+
concurrent.futures.wait(futures)
|
|
420
|
+
|
|
421
|
+
def delete(self, index: str):
|
|
422
|
+
"""Delete a message from the conversation history."""
|
|
423
|
+
self.conversation_history.pop(int(index))
|
|
424
|
+
|
|
425
|
+
def update(self, index: str, role, content):
|
|
426
|
+
"""Update a message in the conversation history.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
index (int): The index of the message to update.
|
|
430
|
+
role (str): The role of the speaker.
|
|
431
|
+
content: The new content of the message.
|
|
432
|
+
"""
|
|
433
|
+
if 0 <= int(index) < len(self.conversation_history):
|
|
434
|
+
self.conversation_history[int(index)]["role"] = role
|
|
435
|
+
self.conversation_history[int(index)]["content"] = content
|
|
436
|
+
else:
|
|
437
|
+
logger.warning(f"Invalid index: {index}")
|
|
438
|
+
|
|
439
|
+
def query(self, index: str):
|
|
440
|
+
"""Query a message from the conversation history.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
index (int): The index of the message to query.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
dict: The message at the specified index.
|
|
447
|
+
"""
|
|
448
|
+
if 0 <= int(index) < len(self.conversation_history):
|
|
449
|
+
return self.conversation_history[int(index)]
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
def search(self, keyword: str):
|
|
453
|
+
"""Search for messages containing a keyword.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
keyword (str): The keyword to search for.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
list: A list of messages containing the keyword.
|
|
460
|
+
"""
|
|
461
|
+
return [
|
|
462
|
+
message
|
|
463
|
+
for message in self.conversation_history
|
|
464
|
+
if keyword in str(message["content"])
|
|
465
|
+
]
|
|
466
|
+
|
|
467
|
+
def export_conversation(self, filename: str, *args, **kwargs):
|
|
468
|
+
"""Export the conversation history to a file.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
filename (str): Filename to export to.
|
|
472
|
+
"""
|
|
473
|
+
# If the filename ends with .json, use save_as_json
|
|
474
|
+
if filename.endswith(".json"):
|
|
475
|
+
self.save_as_json(force=True)
|
|
476
|
+
else:
|
|
477
|
+
# Simple text export for non-JSON files
|
|
478
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
479
|
+
for message in self.conversation_history:
|
|
480
|
+
f.write(
|
|
481
|
+
f"{message['role']}: {message['content']}\n"
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
def import_conversation(self, filename: str):
|
|
485
|
+
"""Import a conversation history from a file.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
filename (str): Filename to import from.
|
|
489
|
+
"""
|
|
490
|
+
self.load_from_json(filename)
|
|
491
|
+
|
|
492
|
+
def count_messages_by_role(self):
|
|
493
|
+
"""Count the number of messages by role.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
dict: A dictionary with counts of messages by role.
|
|
497
|
+
"""
|
|
498
|
+
# Initialize counts with expected roles
|
|
499
|
+
counts = {
|
|
500
|
+
"system": 0,
|
|
501
|
+
"user": 0,
|
|
502
|
+
"assistant": 0,
|
|
503
|
+
"function": 0,
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
# Count messages by role
|
|
507
|
+
for message in self.conversation_history:
|
|
508
|
+
role = message["role"]
|
|
509
|
+
if role in counts:
|
|
510
|
+
counts[role] += 1
|
|
511
|
+
else:
|
|
512
|
+
# Handle unexpected roles dynamically
|
|
513
|
+
counts[role] = counts.get(role, 0) + 1
|
|
514
|
+
|
|
515
|
+
return counts
|
|
516
|
+
|
|
517
|
+
def return_history_as_string(self):
|
|
518
|
+
"""Return the conversation history as a string.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
str: The conversation history formatted as a string.
|
|
522
|
+
"""
|
|
523
|
+
if self.dynamic_context_window is True:
|
|
524
|
+
return self.dynamic_auto_chunking()
|
|
525
|
+
else:
|
|
526
|
+
return self._return_history_as_string_worker()
|
|
527
|
+
|
|
528
|
+
def _return_history_as_string_worker(self):
|
|
529
|
+
formatted_messages = []
|
|
530
|
+
|
|
531
|
+
for message in self.conversation_history:
|
|
532
|
+
formatted_messages.append(
|
|
533
|
+
f"{message['role']}: {message['content']}"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
return "\n\n".join(formatted_messages)
|
|
537
|
+
|
|
538
|
+
def get_str(self) -> str:
|
|
539
|
+
"""Get the conversation history as a string.
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
str: The conversation history.
|
|
543
|
+
"""
|
|
544
|
+
return self.return_history_as_string()
|
|
545
|
+
|
|
546
|
+
def to_dict(self) -> Dict[Any, Any]:
|
|
547
|
+
"""
|
|
548
|
+
Converts all attributes of the class into a dictionary, including all __init__ parameters
|
|
549
|
+
and conversation history. Automatically extracts parameters from __init__ signature.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
Dict[str, Any]: A dictionary containing:
|
|
553
|
+
- metadata: All initialization parameters and their current values
|
|
554
|
+
- conversation_history: The list of conversation messages
|
|
555
|
+
"""
|
|
556
|
+
return self.conversation_history
|
|
557
|
+
|
|
558
|
+
def _ensure_save_path(self) -> None:
|
|
559
|
+
"""Ensure save filepath is set and directory exists."""
|
|
560
|
+
if not self.save_filepath:
|
|
561
|
+
self.save_filepath = os.path.join(self.conversations_dir or os.getcwd(), f"conversation_{self.id}.json")
|
|
562
|
+
save_dir = os.path.dirname(self.save_filepath)
|
|
563
|
+
if save_dir:
|
|
564
|
+
os.makedirs(save_dir, exist_ok=True)
|
|
565
|
+
|
|
566
|
+
def save_as_json(self, force: bool = True) -> None:
|
|
567
|
+
"""Save conversation history to JSON file.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
force: If True, saves regardless of autosave setting.
|
|
571
|
+
|
|
572
|
+
Raises:
|
|
573
|
+
Exception: If save operation fails.
|
|
574
|
+
"""
|
|
575
|
+
if not self.autosave and not force:
|
|
576
|
+
logger.warning("Autosave is disabled. Use save_as_json(force=True) or enable autosave.")
|
|
577
|
+
return
|
|
578
|
+
|
|
579
|
+
try:
|
|
580
|
+
self._ensure_save_path()
|
|
581
|
+
with open(self.save_filepath, "w", encoding="utf-8") as f:
|
|
582
|
+
json.dump(self.conversation_history, f, indent=4, default=str)
|
|
583
|
+
logger.info(f"Conversation saved to {self.save_filepath}")
|
|
584
|
+
except Exception as e:
|
|
585
|
+
logger.error(f"Failed to save conversation: {str(e)}\n{traceback.format_exc()}")
|
|
586
|
+
raise
|
|
587
|
+
|
|
588
|
+
def save_as_yaml(self, force: bool = True) -> None:
|
|
589
|
+
"""Save conversation history to YAML file.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
force: If True, saves regardless of autosave setting.
|
|
593
|
+
|
|
594
|
+
Raises:
|
|
595
|
+
Exception: If save operation fails.
|
|
596
|
+
"""
|
|
597
|
+
if not self.autosave and not force:
|
|
598
|
+
logger.warning("Autosave is disabled. Use save_as_yaml(force=True) or enable autosave.")
|
|
599
|
+
return
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
self._ensure_save_path()
|
|
603
|
+
with open(self.save_filepath, "w", encoding="utf-8") as f:
|
|
604
|
+
yaml.dump(self.conversation_history, f, indent=4, default_flow_style=False, sort_keys=False)
|
|
605
|
+
logger.info(f"Conversation saved to {self.save_filepath}")
|
|
606
|
+
except Exception as e:
|
|
607
|
+
logger.error(f"Failed to save conversation: {str(e)}\n{traceback.format_exc()}")
|
|
608
|
+
raise
|
|
609
|
+
|
|
610
|
+
def export(self, force: bool = True):
|
|
611
|
+
"""Export the conversation to a file based on the export method.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
force (bool, optional): If True, saves regardless of autosave setting. Defaults to True.
|
|
615
|
+
"""
|
|
616
|
+
try:
|
|
617
|
+
# Validate export method
|
|
618
|
+
if self.export_method not in ["json", "yaml"]:
|
|
619
|
+
raise ValueError(
|
|
620
|
+
f"Invalid export_method: {self.export_method}. Must be 'json' or 'yaml'"
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# Create directory if it doesn't exist
|
|
624
|
+
save_dir = os.path.dirname(self.save_filepath)
|
|
625
|
+
if save_dir:
|
|
626
|
+
os.makedirs(save_dir, exist_ok=True)
|
|
627
|
+
|
|
628
|
+
# Ensure filepath extension matches export method
|
|
629
|
+
file_ext = os.path.splitext(self.save_filepath)[1].lower()
|
|
630
|
+
expected_ext = (
|
|
631
|
+
".json" if self.export_method == "json" else ".yaml"
|
|
632
|
+
)
|
|
633
|
+
if file_ext != expected_ext:
|
|
634
|
+
base_name = os.path.splitext(self.save_filepath)[0]
|
|
635
|
+
self.save_filepath = f"{base_name}{expected_ext}"
|
|
636
|
+
logger.warning(
|
|
637
|
+
f"Updated save filepath to match export method: {self.save_filepath}"
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
if self.export_method == "json":
|
|
641
|
+
self.save_as_json(force=force)
|
|
642
|
+
elif self.export_method == "yaml":
|
|
643
|
+
self.save_as_yaml(force=force)
|
|
644
|
+
|
|
645
|
+
except Exception as e:
|
|
646
|
+
logger.error(
|
|
647
|
+
f"Failed to export conversation to {self.save_filepath}: {str(e)}\nTraceback: {traceback.format_exc()}"
|
|
648
|
+
)
|
|
649
|
+
raise # Re-raise to ensure the error is visible
|
|
650
|
+
|
|
651
|
+
def _load_metadata(self, data: Dict[str, Any]) -> None:
|
|
652
|
+
"""Load metadata from saved data dictionary."""
|
|
653
|
+
metadata = data.get("metadata", {})
|
|
654
|
+
for key, value in metadata.items():
|
|
655
|
+
if hasattr(self, key):
|
|
656
|
+
setattr(self, key, value)
|
|
657
|
+
self.conversation_history = data.get("conversation_history", [])
|
|
658
|
+
|
|
659
|
+
def load_from_json(self, filename: str) -> None:
|
|
660
|
+
"""Load conversation history from JSON file.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
filename: Path to JSON file.
|
|
664
|
+
|
|
665
|
+
Raises:
|
|
666
|
+
Exception: If load operation fails.
|
|
667
|
+
"""
|
|
668
|
+
if filename and os.path.exists(filename):
|
|
669
|
+
try:
|
|
670
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
671
|
+
self._load_metadata(json.load(f))
|
|
672
|
+
logger.info(f"Successfully loaded conversation from {filename}")
|
|
673
|
+
except Exception as e:
|
|
674
|
+
logger.error(f"Failed to load conversation: {str(e)}\n{traceback.format_exc()}")
|
|
675
|
+
raise
|
|
676
|
+
|
|
677
|
+
def load_from_yaml(self, filename: str) -> None:
|
|
678
|
+
"""Load conversation history from YAML file.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
filename: Path to YAML file.
|
|
682
|
+
|
|
683
|
+
Raises:
|
|
684
|
+
Exception: If load operation fails.
|
|
685
|
+
"""
|
|
686
|
+
if filename and os.path.exists(filename):
|
|
687
|
+
try:
|
|
688
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
689
|
+
self._load_metadata(yaml.safe_load(f))
|
|
690
|
+
logger.info(f"Successfully loaded conversation from {filename}")
|
|
691
|
+
except Exception as e:
|
|
692
|
+
logger.error(f"Failed to load conversation: {str(e)}\n{traceback.format_exc()}")
|
|
693
|
+
raise
|
|
694
|
+
|
|
695
|
+
def load(self, filename: str):
|
|
696
|
+
"""Load the conversation history and metadata from a file.
|
|
697
|
+
Automatically detects the file format based on extension.
|
|
698
|
+
|
|
699
|
+
Args:
|
|
700
|
+
filename (str): Filename to load from.
|
|
701
|
+
"""
|
|
702
|
+
if filename is None or not os.path.exists(filename):
|
|
703
|
+
logger.warning(f"File not found: {filename}")
|
|
704
|
+
return
|
|
705
|
+
|
|
706
|
+
file_ext = os.path.splitext(filename)[1].lower()
|
|
707
|
+
try:
|
|
708
|
+
if file_ext == ".json":
|
|
709
|
+
self.load_from_json(filename)
|
|
710
|
+
elif file_ext == ".yaml" or file_ext == ".yml":
|
|
711
|
+
self.load_from_yaml(filename)
|
|
712
|
+
else:
|
|
713
|
+
raise ValueError(
|
|
714
|
+
f"Unsupported file format: {file_ext}. Must be .json, .yaml, or .yml"
|
|
715
|
+
)
|
|
716
|
+
except Exception as e:
|
|
717
|
+
logger.error(
|
|
718
|
+
f"Failed to load conversation from {filename}: {str(e)}\nTraceback: {traceback.format_exc()}"
|
|
719
|
+
)
|
|
720
|
+
raise
|
|
721
|
+
|
|
722
|
+
def search_keyword_in_conversation(self, keyword: str):
|
|
723
|
+
"""Search for a keyword in the conversation history.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
keyword (str): Keyword to search for.
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
list: List of messages containing the keyword.
|
|
730
|
+
"""
|
|
731
|
+
return [
|
|
732
|
+
msg
|
|
733
|
+
for msg in self.conversation_history
|
|
734
|
+
if keyword in msg["content"]
|
|
735
|
+
]
|
|
736
|
+
|
|
737
|
+
def truncate_memory_with_tokenizer(self):
|
|
738
|
+
"""
|
|
739
|
+
Truncate conversation history based on the total token count using tokenizer.
|
|
740
|
+
|
|
741
|
+
This version is more generic, not dependent on a specific LLM model, and can work with any model that provides a counter.
|
|
742
|
+
Uses count_tokens function to calculate and truncate by message, ensuring the result is still valid content.
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
None
|
|
746
|
+
"""
|
|
747
|
+
|
|
748
|
+
total_tokens = 0
|
|
749
|
+
truncated_history = []
|
|
750
|
+
|
|
751
|
+
for message in self.conversation_history:
|
|
752
|
+
role = message.get("role")
|
|
753
|
+
content = message.get("content")
|
|
754
|
+
|
|
755
|
+
# Convert content to string if it's not already a string
|
|
756
|
+
if not isinstance(content, str):
|
|
757
|
+
content = str(content)
|
|
758
|
+
|
|
759
|
+
# Calculate token count for this message
|
|
760
|
+
token_count = count_tokens(
|
|
761
|
+
content, self.tokenizer_model_name
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
# Check if adding this message would exceed the limit
|
|
765
|
+
if total_tokens + token_count <= self.context_length:
|
|
766
|
+
# If not exceeding limit, add the full message
|
|
767
|
+
truncated_history.append(message)
|
|
768
|
+
total_tokens += token_count
|
|
769
|
+
else:
|
|
770
|
+
# Calculate remaining tokens we can include
|
|
771
|
+
remaining_tokens = self.context_length - total_tokens
|
|
772
|
+
|
|
773
|
+
# If no token space left, break the loop
|
|
774
|
+
if remaining_tokens <= 0:
|
|
775
|
+
break
|
|
776
|
+
|
|
777
|
+
# If we have space left, we need to truncate this message
|
|
778
|
+
# Use binary search to find content length that fits remaining token space
|
|
779
|
+
truncated_content = self._binary_search_truncate(
|
|
780
|
+
content,
|
|
781
|
+
remaining_tokens,
|
|
782
|
+
self.tokenizer_model_name,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
# Create the truncated message
|
|
786
|
+
truncated_message = {
|
|
787
|
+
"role": role,
|
|
788
|
+
"content": truncated_content,
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
# Add any other fields from the original message
|
|
792
|
+
for key, value in message.items():
|
|
793
|
+
if key not in ["role", "content"]:
|
|
794
|
+
truncated_message[key] = value
|
|
795
|
+
|
|
796
|
+
truncated_history.append(truncated_message)
|
|
797
|
+
break
|
|
798
|
+
|
|
799
|
+
# Update conversation history
|
|
800
|
+
self.conversation_history = truncated_history
|
|
801
|
+
|
|
802
|
+
def _binary_search_truncate(
|
|
803
|
+
self, text, target_tokens, model_name
|
|
804
|
+
):
|
|
805
|
+
"""
|
|
806
|
+
Use binary search to find the maximum text substring that fits the target token count.
|
|
807
|
+
|
|
808
|
+
Parameters:
|
|
809
|
+
text (str): Original text to truncate
|
|
810
|
+
target_tokens (int): Target token count
|
|
811
|
+
model_name (str): Model name for token counting
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
str: Truncated text with token count not exceeding target_tokens
|
|
815
|
+
"""
|
|
816
|
+
|
|
817
|
+
# If text is empty or target tokens is 0, return empty string
|
|
818
|
+
if not text or target_tokens <= 0:
|
|
819
|
+
return ""
|
|
820
|
+
|
|
821
|
+
# If original text token count is already less than or equal to target, return as is
|
|
822
|
+
original_tokens = count_tokens(text, model_name)
|
|
823
|
+
if original_tokens <= target_tokens:
|
|
824
|
+
return text
|
|
825
|
+
|
|
826
|
+
# Binary search
|
|
827
|
+
left, right = 0, len(text)
|
|
828
|
+
best_text = ""
|
|
829
|
+
|
|
830
|
+
while left <= right:
|
|
831
|
+
mid = (left + right) // 2
|
|
832
|
+
truncated = text[:mid]
|
|
833
|
+
tokens = count_tokens(truncated, model_name)
|
|
834
|
+
|
|
835
|
+
if tokens <= target_tokens:
|
|
836
|
+
# If current truncated text token count is less than or equal to target, try longer text
|
|
837
|
+
best_text = truncated
|
|
838
|
+
left = mid + 1
|
|
839
|
+
else:
|
|
840
|
+
# Otherwise try shorter text
|
|
841
|
+
right = mid - 1
|
|
842
|
+
|
|
843
|
+
# Try to truncate at sentence boundaries if possible
|
|
844
|
+
sentence_delimiters = [".", "!", "?", "\n"]
|
|
845
|
+
for delimiter in sentence_delimiters:
|
|
846
|
+
last_pos = best_text.rfind(delimiter)
|
|
847
|
+
if (
|
|
848
|
+
last_pos > len(best_text) * 0.75
|
|
849
|
+
): # Only truncate at sentence boundary if we don't lose too much content
|
|
850
|
+
truncated_at_sentence = best_text[: last_pos + 1]
|
|
851
|
+
if (
|
|
852
|
+
count_tokens(truncated_at_sentence, model_name)
|
|
853
|
+
<= target_tokens
|
|
854
|
+
):
|
|
855
|
+
return truncated_at_sentence
|
|
856
|
+
|
|
857
|
+
return best_text
|
|
858
|
+
|
|
859
|
+
def clear(self):
|
|
860
|
+
"""Clear the conversation history."""
|
|
861
|
+
self.conversation_history = []
|
|
862
|
+
|
|
863
|
+
def to_json(self):
|
|
864
|
+
"""Convert the conversation history to a JSON string.
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
str: The conversation history as a JSON string.
|
|
868
|
+
"""
|
|
869
|
+
return json.dumps(self.conversation_history)
|
|
870
|
+
|
|
871
|
+
def to_list(self):
|
|
872
|
+
"""Convert the conversation history to a list.
|
|
873
|
+
|
|
874
|
+
Returns:
|
|
875
|
+
list: The conversation history as a list of dictionaries.
|
|
876
|
+
"""
|
|
877
|
+
return self.conversation_history
|
|
878
|
+
|
|
879
|
+
def get_visible_messages(self, agent: "Agent", turn: int):
|
|
880
|
+
"""
|
|
881
|
+
Get the visible messages for a given agent and turn.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
agent (Agent): The agent.
|
|
885
|
+
turn (int): The turn number.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
List[Dict]: The list of visible messages.
|
|
889
|
+
"""
|
|
890
|
+
# Get the messages before the current turn
|
|
891
|
+
prev_messages = [
|
|
892
|
+
message
|
|
893
|
+
for message in self.conversation_history
|
|
894
|
+
if message["turn"] < turn
|
|
895
|
+
]
|
|
896
|
+
|
|
897
|
+
visible_messages = []
|
|
898
|
+
for message in prev_messages:
|
|
899
|
+
if (
|
|
900
|
+
message["visible_to"] == "all"
|
|
901
|
+
or agent.agent_name in message["visible_to"]
|
|
902
|
+
):
|
|
903
|
+
visible_messages.append(message)
|
|
904
|
+
return visible_messages
|
|
905
|
+
|
|
906
|
+
def get_last_message_as_string(self):
|
|
907
|
+
"""Fetch the last message from the conversation history.
|
|
908
|
+
|
|
909
|
+
Returns:
|
|
910
|
+
str: The last message formatted as 'role: content'.
|
|
911
|
+
"""
|
|
912
|
+
if self.conversation_history:
|
|
913
|
+
return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
|
|
914
|
+
return ""
|
|
915
|
+
|
|
916
|
+
def return_messages_as_list(self):
|
|
917
|
+
"""Return the conversation messages as a list of formatted strings.
|
|
918
|
+
|
|
919
|
+
Returns:
|
|
920
|
+
list: List of messages formatted as 'role: content'.
|
|
921
|
+
"""
|
|
922
|
+
return [
|
|
923
|
+
f"{message['role']}: {message['content']}"
|
|
924
|
+
for message in self.conversation_history
|
|
925
|
+
]
|
|
926
|
+
|
|
927
|
+
def return_messages_as_dictionary(self):
|
|
928
|
+
"""Return the conversation messages as a list of dictionaries.
|
|
929
|
+
|
|
930
|
+
Returns:
|
|
931
|
+
list: List of dictionaries containing role and content of each message.
|
|
932
|
+
"""
|
|
933
|
+
return [
|
|
934
|
+
{
|
|
935
|
+
"role": message["role"],
|
|
936
|
+
"content": message["content"],
|
|
937
|
+
}
|
|
938
|
+
for message in self.conversation_history
|
|
939
|
+
]
|
|
940
|
+
|
|
941
|
+
def add_tool_output_to_agent(self, role: str, tool_output: dict):
|
|
942
|
+
"""
|
|
943
|
+
Add a tool output to the conversation history.
|
|
944
|
+
|
|
945
|
+
Args:
|
|
946
|
+
role (str): The role of the tool.
|
|
947
|
+
tool_output (dict): The output from the tool to be added.
|
|
948
|
+
"""
|
|
949
|
+
self.add(role, tool_output)
|
|
950
|
+
|
|
951
|
+
def return_json(self):
|
|
952
|
+
"""Return the conversation messages as a JSON string.
|
|
953
|
+
|
|
954
|
+
Returns:
|
|
955
|
+
str: The conversation messages formatted as a JSON string.
|
|
956
|
+
"""
|
|
957
|
+
return json.dumps(
|
|
958
|
+
self.return_messages_as_dictionary(), indent=4
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
def get_final_message(self):
|
|
962
|
+
"""Return the final message from the conversation history.
|
|
963
|
+
|
|
964
|
+
Returns:
|
|
965
|
+
str: The final message formatted as 'role: content'.
|
|
966
|
+
"""
|
|
967
|
+
if self.conversation_history:
|
|
968
|
+
return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
|
|
969
|
+
return ""
|
|
970
|
+
|
|
971
|
+
def get_final_message_content(self):
|
|
972
|
+
"""Return the content of the final message from the conversation history.
|
|
973
|
+
|
|
974
|
+
Returns:
|
|
975
|
+
str: The content of the final message.
|
|
976
|
+
"""
|
|
977
|
+
if self.conversation_history:
|
|
978
|
+
output = self.conversation_history[-1]["content"]
|
|
979
|
+
return output
|
|
980
|
+
return ""
|
|
981
|
+
|
|
982
|
+
def return_all_except_first(self):
|
|
983
|
+
"""Return all messages except the first one.
|
|
984
|
+
|
|
985
|
+
Returns:
|
|
986
|
+
list: List of messages except the first one.
|
|
987
|
+
"""
|
|
988
|
+
return self.conversation_history[2:]
|
|
989
|
+
|
|
990
|
+
def return_all_except_first_string(self):
|
|
991
|
+
"""Return all messages except the first one as a string.
|
|
992
|
+
|
|
993
|
+
Returns:
|
|
994
|
+
str: All messages except the first one as a string.
|
|
995
|
+
"""
|
|
996
|
+
return "\n".join(
|
|
997
|
+
[
|
|
998
|
+
f"{msg['content']}"
|
|
999
|
+
for msg in self.conversation_history[2:]
|
|
1000
|
+
]
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
def batch_add(self, messages: List[dict]):
|
|
1004
|
+
"""Batch add messages to the conversation history.
|
|
1005
|
+
|
|
1006
|
+
Args:
|
|
1007
|
+
messages (List[dict]): List of messages to add.
|
|
1008
|
+
"""
|
|
1009
|
+
self.conversation_history.extend(messages)
|
|
1010
|
+
|
|
1011
|
+
@classmethod
|
|
1012
|
+
def load_conversation(
|
|
1013
|
+
cls,
|
|
1014
|
+
name: str,
|
|
1015
|
+
conversations_dir: Optional[str] = None,
|
|
1016
|
+
load_filepath: Optional[str] = None,
|
|
1017
|
+
) -> "Conversation":
|
|
1018
|
+
"""Load a conversation from saved file by name or specific file.
|
|
1019
|
+
|
|
1020
|
+
Args:
|
|
1021
|
+
name (str): Name of the conversation to load
|
|
1022
|
+
conversations_dir (Optional[str]): Directory containing conversations
|
|
1023
|
+
load_filepath (Optional[str]): Specific file to load from
|
|
1024
|
+
|
|
1025
|
+
Returns:
|
|
1026
|
+
Conversation: The loaded conversation object
|
|
1027
|
+
"""
|
|
1028
|
+
if load_filepath:
|
|
1029
|
+
conversation = cls(name=name)
|
|
1030
|
+
conversation.load(load_filepath)
|
|
1031
|
+
return conversation
|
|
1032
|
+
|
|
1033
|
+
conv_dir = conversations_dir or get_conversation_dir()
|
|
1034
|
+
|
|
1035
|
+
# Try loading by name with different extensions
|
|
1036
|
+
for ext in [".json", ".yaml", ".yml"]:
|
|
1037
|
+
filepath = os.path.join(conv_dir, f"{name}{ext}")
|
|
1038
|
+
if os.path.exists(filepath):
|
|
1039
|
+
conversation = cls(
|
|
1040
|
+
name=name, conversations_dir=conv_dir
|
|
1041
|
+
)
|
|
1042
|
+
conversation.load(filepath)
|
|
1043
|
+
return conversation
|
|
1044
|
+
|
|
1045
|
+
# If not found by name with extensions, try loading by ID
|
|
1046
|
+
filepath = os.path.join(conv_dir, name)
|
|
1047
|
+
if os.path.exists(filepath):
|
|
1048
|
+
conversation = cls(name=name, conversations_dir=conv_dir)
|
|
1049
|
+
conversation.load(filepath)
|
|
1050
|
+
return conversation
|
|
1051
|
+
|
|
1052
|
+
logger.warning(
|
|
1053
|
+
f"No conversation found with name or ID: {name}"
|
|
1054
|
+
)
|
|
1055
|
+
return cls(name=name, conversations_dir=conv_dir)
|
|
1056
|
+
|
|
1057
|
+
def return_dict_final(self):
|
|
1058
|
+
"""Return the final message as a dictionary."""
|
|
1059
|
+
return (
|
|
1060
|
+
self.conversation_history[-1]["content"],
|
|
1061
|
+
self.conversation_history[-1]["content"],
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
def return_list_final(self):
|
|
1065
|
+
"""Return the final message as a list."""
|
|
1066
|
+
return [
|
|
1067
|
+
self.conversation_history[-1]["content"],
|
|
1068
|
+
]
|
|
1069
|
+
|
|
1070
|
+
@classmethod
|
|
1071
|
+
def list_conversations(
|
|
1072
|
+
cls, conversations_dir: Optional[str] = None
|
|
1073
|
+
) -> List[Dict[str, str]]:
|
|
1074
|
+
"""List all saved conversations.
|
|
1075
|
+
|
|
1076
|
+
Args:
|
|
1077
|
+
conversations_dir (Optional[str]): Directory containing conversations
|
|
1078
|
+
|
|
1079
|
+
Returns:
|
|
1080
|
+
List[Dict[str, str]]: List of conversation metadata
|
|
1081
|
+
"""
|
|
1082
|
+
conv_dir = conversations_dir or get_conversation_dir()
|
|
1083
|
+
if not os.path.exists(conv_dir):
|
|
1084
|
+
return []
|
|
1085
|
+
|
|
1086
|
+
conversations = []
|
|
1087
|
+
seen_ids = (
|
|
1088
|
+
set()
|
|
1089
|
+
) # Track seen conversation IDs to avoid duplicates
|
|
1090
|
+
|
|
1091
|
+
for filename in os.listdir(conv_dir):
|
|
1092
|
+
if filename.endswith(".json"):
|
|
1093
|
+
try:
|
|
1094
|
+
filepath = os.path.join(conv_dir, filename)
|
|
1095
|
+
with open(filepath) as f:
|
|
1096
|
+
data = json.load(f)
|
|
1097
|
+
metadata = data.get("metadata", {})
|
|
1098
|
+
conv_id = metadata.get("id")
|
|
1099
|
+
name = metadata.get("name")
|
|
1100
|
+
created_at = metadata.get("created_at")
|
|
1101
|
+
|
|
1102
|
+
# Skip if we've already seen this ID or if required fields are missing
|
|
1103
|
+
if (
|
|
1104
|
+
not all([conv_id, name, created_at])
|
|
1105
|
+
or conv_id in seen_ids
|
|
1106
|
+
):
|
|
1107
|
+
continue
|
|
1108
|
+
|
|
1109
|
+
seen_ids.add(conv_id)
|
|
1110
|
+
conversations.append(
|
|
1111
|
+
{
|
|
1112
|
+
"id": conv_id,
|
|
1113
|
+
"name": name,
|
|
1114
|
+
"created_at": created_at,
|
|
1115
|
+
"filepath": filepath,
|
|
1116
|
+
}
|
|
1117
|
+
)
|
|
1118
|
+
except json.JSONDecodeError:
|
|
1119
|
+
logger.warning(
|
|
1120
|
+
f"Skipping corrupted conversation file: {filename}"
|
|
1121
|
+
)
|
|
1122
|
+
continue
|
|
1123
|
+
except Exception as e:
|
|
1124
|
+
logger.error(
|
|
1125
|
+
f"Failed to read conversation {filename}: {str(e)}"
|
|
1126
|
+
)
|
|
1127
|
+
continue
|
|
1128
|
+
|
|
1129
|
+
# Sort by creation date, newest first
|
|
1130
|
+
return sorted(
|
|
1131
|
+
conversations, key=lambda x: x["created_at"], reverse=True
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
def clear_memory(self):
|
|
1135
|
+
"""Clear the memory of the conversation."""
|
|
1136
|
+
self.conversation_history = []
|
|
1137
|
+
|
|
1138
|
+
def _dynamic_auto_chunking_worker(self):
|
|
1139
|
+
"""
|
|
1140
|
+
Dynamically chunk the conversation history to fit within the context length.
|
|
1141
|
+
|
|
1142
|
+
Returns:
|
|
1143
|
+
str: The chunked conversation history as a string that fits within context_length tokens.
|
|
1144
|
+
"""
|
|
1145
|
+
all_tokens = self._return_history_as_string_worker()
|
|
1146
|
+
|
|
1147
|
+
total_tokens = count_tokens(
|
|
1148
|
+
all_tokens, self.tokenizer_model_name
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
if total_tokens <= self.context_length:
|
|
1152
|
+
return all_tokens
|
|
1153
|
+
|
|
1154
|
+
# We need to remove characters from the beginning until we're under the limit
|
|
1155
|
+
# Start by removing a percentage of characters and adjust iteratively
|
|
1156
|
+
target_tokens = self.context_length
|
|
1157
|
+
current_string = all_tokens
|
|
1158
|
+
|
|
1159
|
+
# Binary search approach to find the right cutoff point
|
|
1160
|
+
left, right = 0, len(all_tokens)
|
|
1161
|
+
|
|
1162
|
+
while left < right:
|
|
1163
|
+
mid = (left + right) // 2
|
|
1164
|
+
test_string = all_tokens[mid:]
|
|
1165
|
+
|
|
1166
|
+
if not test_string:
|
|
1167
|
+
break
|
|
1168
|
+
|
|
1169
|
+
test_tokens = count_tokens(
|
|
1170
|
+
test_string, self.tokenizer_model_name
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
if test_tokens <= target_tokens:
|
|
1174
|
+
# We can remove more from the beginning
|
|
1175
|
+
right = mid
|
|
1176
|
+
current_string = test_string
|
|
1177
|
+
else:
|
|
1178
|
+
# We need to keep more from the beginning
|
|
1179
|
+
left = mid + 1
|
|
1180
|
+
|
|
1181
|
+
return current_string
|
|
1182
|
+
|
|
1183
|
+
def dynamic_auto_chunking(self):
|
|
1184
|
+
"""
|
|
1185
|
+
Dynamically chunk the conversation history to fit within the context length.
|
|
1186
|
+
|
|
1187
|
+
Returns:
|
|
1188
|
+
str: The chunked conversation history as a string that fits within context_length tokens.
|
|
1189
|
+
"""
|
|
1190
|
+
try:
|
|
1191
|
+
return self._dynamic_auto_chunking_worker()
|
|
1192
|
+
except Exception as e:
|
|
1193
|
+
logger.error(f"Dynamic auto chunking failed: {e}")
|
|
1194
|
+
return self._return_history_as_string_worker()
|
|
1195
|
+
|