pal-code 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {pal_code-0.1.1 → pal_code-0.1.2}/LICENSE +1 -1
- {pal_code-0.1.1 → pal_code-0.1.2}/PKG-INFO +2 -2
- {pal_code-0.1.1 → pal_code-0.1.2}/pyproject.toml +2 -2
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/__init__.py +1 -1
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/cli.py +5 -2
- pal_code-0.1.2/src/pal/context_window.py +42 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/routes.py +2 -2
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/runs.py +21 -2
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/bench.py +8 -1
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/cli.py +111 -20
- pal_code-0.1.2/src/pal/terminal/logo.py +140 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/session.py +13 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/setup.py +104 -3
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/ui.py +53 -29
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/app.py +6 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/studio.css +2 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/templates/index.html +14 -6
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/templates/studio.html +2 -2
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal_code.egg-info/PKG-INFO +2 -2
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal_code.egg-info/SOURCES.txt +1 -0
- pal_code-0.1.1/src/pal/terminal/logo.py +0 -117
- {pal_code-0.1.1 → pal_code-0.1.2}/README.md +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/setup.cfg +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/__main__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/components/access_control.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/components/intervention.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/components/privilege_registry.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/access_manager/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agent_constraints/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agent_constraints/components/AGENT_OUTPUT.md +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agent_constraints/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agent_constraints/components/constraints.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agent_constraints/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agent_constraints/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agents/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agents/coding.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/agents/docs.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/demo.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/providers.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/service.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/services/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/services/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/services/data.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/services/media.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/services/web.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/sessions.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/tools.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/chat/uploads.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/adverbs.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/hedges.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/interjections.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/minifier.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/output_compressor.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/pleonasms.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/punctuations.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/components/stopwords.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/history.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/utils/negations.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/utils/nltk_setup.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/utils/pronouns.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/utils/proselint_setup.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/compression/utils/spacy_setup.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/config/default.yaml +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/config.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/chat_context.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/assembler.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/context_cache.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/kv_bridge.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/pager.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/simple_context_manager.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/components/turns.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/utils/hashing.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/utils/relevance.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/context_manager/utils/storage.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/documents/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/documents/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/documents/components/chunker.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/documents/components/index.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/documents/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/errors.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/components/pandoc_converter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/components/pdf_converter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/components/pptx_converter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/components/xlsx_converter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_converters/workspace.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/INPUT_RULES.md +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/components/methods.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/components/verifier.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/input_savers/utils/guards.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/components/builder.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/components/retriever.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/components/workspace.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/knowledge_graph/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/latency_tracker/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/latency_tracker/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/api.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/code.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/core.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/local.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_scheduler/types.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/adapter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/components/adapter_types.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/components/features.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/components/litellm_wrapper.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/components/openrouter_wrapper.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/modes.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/utils/registry.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/llm_wrapper/utils/token_counting.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/local_llm/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/local_llm/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/local_llm/components/huggingface_local.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/local_llm/components/local_llm.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/logging_setup.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/components/lru_k.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/components/manager.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/components/note.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/components/storage_manager.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory/utils/keyword_overlap.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory_tracker/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory_tracker/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory_tracker/components/process.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/memory_tracker/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/metrics/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/metrics/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/metrics/components/collector.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/metrics/components/exporter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/metrics/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/metrics/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/notes_manager/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/notes_manager/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/notes_manager/components/privacy.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/notes_manager/components/recall.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/notes_manager/components/tidy.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/notes_manager/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/agent.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/cgroup_manager.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/context_interrupt.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/limits.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/scheduler.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/components/syscall_scheduler.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/utils/device.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/orchestrator/utils/protocol.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/os_kernel/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/os_kernel/filesystem.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/os_kernel/network.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/os_kernel/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/os_kernel/sysinfo.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/paths.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/py.typed +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/redact.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/adapters.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/agent.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/autogen_adapter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/autogen_client.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/framework_adapter_base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/metagpt_adapter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/open_interpreter_adapter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/components/tool_catalog.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/os_api.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/patterns.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/queries.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/savers.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/sdk/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/components/local_file_backend.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/components/storage_queue.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/components/vector_store_backend.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/utils/memory_adapter.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/storage_manager/utils/sdk.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/builder.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/catalog.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/checks.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/clients.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/editor.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/files.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/models.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/plan.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/planner.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/studio/policy.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/components/answerer.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/components/classifier.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/components/gates.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/task_router/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/backend.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/clients.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/commands.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/decide.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/files.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/flow_cli.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/hardware.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/history.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/keys.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/local_models.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/models.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/policy.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/project.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/review.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/security.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/syntax.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/theme.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/voice.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/terminal/workflow.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/token_tracker/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/token_tracker/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tokens.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_catalog.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/builtin.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/components/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/components/base.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/components/conflict_resolver.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/components/param_validator.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/components/tool_call_queue.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/components/tool_registry.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/pipeline.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/tool_manager/utils/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/__init__.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/editor.js +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/fonts/LICENSE-Inter.txt +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-400.woff2 +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-500.woff2 +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-600.woff2 +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-700.woff2 +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/inter.css +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/pal-logo-256.png +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/pal-logo-32.png +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/pal-logo-64.png +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/studio.js +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal/web/static/voice.js +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal_code.egg-info/dependency_links.txt +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal_code.egg-info/entry_points.txt +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal_code.egg-info/requires.txt +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/src/pal_code.egg-info/top_level.txt +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_cli.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_config.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_lockfile.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_paths.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_redact.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_run_script.py +0 -0
- {pal_code-0.1.1 → pal_code-0.1.2}/tests/test_tool_catalog.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pal-code
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Pal: build and plan with AI in your terminal (pal cli) or browser (pal web), on a layer that cuts tokens and latency.
|
|
5
|
-
Author:
|
|
5
|
+
Author: Vrushal More
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: ai,coding-assistant,cli,llm,ollama,tokens,code-graph
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pal-code"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Pal: build and plan with AI in your terminal (pal cli) or browser (pal web), on a layer that cuts tokens and latency."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "MIT" }
|
|
12
|
-
authors = [{ name = "
|
|
12
|
+
authors = [{ name = "Vrushal More" }]
|
|
13
13
|
keywords = ["ai", "coding-assistant", "cli", "llm", "ollama", "tokens", "code-graph"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 4 - Beta",
|
|
@@ -212,7 +212,7 @@ def load_dotenv(path: str | os.PathLike | None = None, env=None) -> list[str]:
|
|
|
212
212
|
return loaded
|
|
213
213
|
|
|
214
214
|
|
|
215
|
-
def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph=None, documents: list | None = None) -> int:
|
|
215
|
+
def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph=None, documents: list | None = None, project_dir=None) -> int:
|
|
216
216
|
from pal.chat import ChatService
|
|
217
217
|
from pal.web import create_app
|
|
218
218
|
|
|
@@ -221,7 +221,7 @@ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph
|
|
|
221
221
|
service = ChatService(settings, graph=graph) if graph is not None else ChatService(settings)
|
|
222
222
|
if documents:
|
|
223
223
|
service.preload_docs = list(documents)
|
|
224
|
-
app = create_app(service)
|
|
224
|
+
app = create_app(service, studio={"project_dir": project_dir}) if project_dir else create_app(service)
|
|
225
225
|
if s.host not in _LOOPBACK:
|
|
226
226
|
print(f"WARNING: listening on {s.host}. Pal has no login: anyone who can reach this port can use "
|
|
227
227
|
"your API keys' quota and read stored memory. Put it behind an authenticating reverse proxy.",
|
|
@@ -239,6 +239,9 @@ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph
|
|
|
239
239
|
"the app still works and sends everything to the API model.", file=sys.stderr)
|
|
240
240
|
else:
|
|
241
241
|
print(f"Local model: {status['detail']}")
|
|
242
|
+
import threading as _t
|
|
243
|
+
from pal.context_window import context_window
|
|
244
|
+
_t.Thread(target=lambda: context_window("openai", "gpt-4o"), daemon=True).start() # loads LiteLLM's model list now, not on the first page load
|
|
242
245
|
if open_browser:
|
|
243
246
|
import threading
|
|
244
247
|
import webbrowser
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""How many tokens a model can take in one request (its context window), for the "tokens left" counters in the terminal and the web page.
|
|
2
|
+
|
|
3
|
+
API models: the size LiteLLM knows for the model, else a safe default. Local models: the context Pal gives Ollama (num_ctx), which is what
|
|
4
|
+
really limits them here, whatever the model could do on paper."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
|
|
9
|
+
LOCAL_CONTEXT = 8192 # Pal runs local models with num_ctx 8192 (pal.terminal.backend.make_core)
|
|
10
|
+
API_DEFAULT = 128_000 # a model LiteLLM does not know: most current API models take at least this
|
|
11
|
+
LOCAL_PROVIDERS = ("local", "ollama", "lmstudio", "vllm", "llamacpp", "llama.cpp", "huggingface")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@lru_cache(maxsize=256)
|
|
15
|
+
def context_window(provider: str, model: str) -> int:
|
|
16
|
+
provider = (provider or "").lower()
|
|
17
|
+
if provider in LOCAL_PROVIDERS:
|
|
18
|
+
return LOCAL_CONTEXT
|
|
19
|
+
if not model:
|
|
20
|
+
return API_DEFAULT
|
|
21
|
+
try:
|
|
22
|
+
import litellm
|
|
23
|
+
for name in (f"{provider}/{model}" if provider else model, model, model.split("/")[-1]):
|
|
24
|
+
try:
|
|
25
|
+
info = litellm.get_model_info(name)
|
|
26
|
+
except Exception: # noqa: BLE001 (not in LiteLLM's table under this name)
|
|
27
|
+
continue
|
|
28
|
+
size = info.get("max_input_tokens") or info.get("max_tokens")
|
|
29
|
+
if size:
|
|
30
|
+
return int(size)
|
|
31
|
+
except Exception: # noqa: BLE001 (LiteLLM missing or broken: the default)
|
|
32
|
+
pass
|
|
33
|
+
return API_DEFAULT
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def tokens_left(window: int, last_prompt: int) -> int:
|
|
37
|
+
"""What is left of the window after the last request's input (the next one is usually at least as big)."""
|
|
38
|
+
return max(0, window - max(0, last_prompt))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def short(n: int) -> str:
|
|
42
|
+
return f"{n / 1000:.1f}k".replace(".0k", "k") if n >= 10_000 else f"{n:,}"
|
|
@@ -49,7 +49,7 @@ def clean_references(raw) -> list[dict]:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def register_studio(app, service, *, planner_core: Callable | None = None, core_factory: Callable | None = None, discover_fn: Callable | None = None,
|
|
52
|
-
hot_turns: int | None = None) -> RunStore:
|
|
52
|
+
hot_turns: int | None = None, project_dir: Path | None = None) -> RunStore:
|
|
53
53
|
"""Add the Studio page and endpoints to the app. The three callables are for tests: the planner's model, the builder's model, local-model discovery."""
|
|
54
54
|
cfg = service.settings
|
|
55
55
|
home = Path(cfg.storage_dir).expanduser() if cfg.storage_dir else data_dir()
|
|
@@ -71,7 +71,7 @@ def register_studio(app, service, *, planner_core: Callable | None = None, core_
|
|
|
71
71
|
raise UserInputError("no local model is chosen: start Ollama and pull a model, or switch to an API model")
|
|
72
72
|
return local_core(m["model"], num_ctx())
|
|
73
73
|
|
|
74
|
-
runs = RunStore(home / "studio", core_factory or default_core_factory, max_runs=cfg.studio.max_runs, hot_turns=hot_turns)
|
|
74
|
+
runs = RunStore(home / "studio", core_factory or default_core_factory, max_runs=cfg.studio.max_runs, hot_turns=hot_turns, project_dir=project_dir)
|
|
75
75
|
app.extensions["studio_runs"] = runs
|
|
76
76
|
|
|
77
77
|
def local() -> dict:
|
|
@@ -95,9 +95,22 @@ class Run:
|
|
|
95
95
|
return self.arms[name]
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
def build_target(project: Path, folder: str) -> Path:
|
|
99
|
+
"""Where a build is written in the folder Pal was started in: the folder itself when it is empty, else a new subfolder named after the
|
|
100
|
+
product (with -2, -3 ... when that name is taken), so nothing that is already there is overwritten."""
|
|
101
|
+
project = Path(project)
|
|
102
|
+
if not any(p for p in project.iterdir() if not p.name.startswith(".")):
|
|
103
|
+
return project
|
|
104
|
+
target, n = project / folder, 2
|
|
105
|
+
while target.exists() and (not target.is_dir() or any(target.iterdir())):
|
|
106
|
+
target, n = project / f"{folder}-{n}", n + 1
|
|
107
|
+
return target
|
|
108
|
+
|
|
109
|
+
|
|
98
110
|
class RunStore:
|
|
99
|
-
def __init__(self, root: Path, core_factory: Callable, max_runs: int = 6, hot_turns: int | None = None):
|
|
111
|
+
def __init__(self, root: Path, core_factory: Callable, max_runs: int = 6, hot_turns: int | None = None, project_dir: Path | None = None):
|
|
100
112
|
self.root, self.core_factory, self.max_runs, self.hot_turns = Path(root), core_factory, max_runs, hot_turns
|
|
113
|
+
self.project_dir = Path(project_dir).resolve() if project_dir else None # pal web's folder: the build is written there
|
|
101
114
|
self._runs: dict[str, Run] = {}
|
|
102
115
|
self._editors: dict[tuple[str, str], EditorSession] = {}
|
|
103
116
|
self._lock = threading.Lock()
|
|
@@ -140,11 +153,17 @@ class RunStore:
|
|
|
140
153
|
break
|
|
141
154
|
policy = Policy(dict(plan["permissions"]))
|
|
142
155
|
save = plan["permissions"].get("write_project")
|
|
143
|
-
|
|
156
|
+
where = None
|
|
157
|
+
if save:
|
|
158
|
+
where = (build_target(self.project_dir, plan["product"]["folder"]) if self.project_dir and arm != "baseline"
|
|
159
|
+
else self.root / run.id / arm / plan["product"]["folder"]) # the plain client's comparison copy stays in Pal's folder
|
|
160
|
+
state = ArmState(arm, label, where)
|
|
144
161
|
run.arms[arm] = state
|
|
145
162
|
if state.workspace:
|
|
146
163
|
state.workspace.mkdir(parents=True, exist_ok=True)
|
|
147
164
|
run.emit("arm_start", f"{label}: starting" if twice else "Starting", arm=arm)
|
|
165
|
+
if state.workspace and self.project_dir and arm != "baseline":
|
|
166
|
+
run.emit("saved_to", f"Files are written to {state.workspace}", arm=arm, path=str(state.workspace))
|
|
148
167
|
client: PlainClient | PalClient
|
|
149
168
|
if arm == "baseline":
|
|
150
169
|
client = PlainClient(core, SYSTEM)
|
|
@@ -67,6 +67,7 @@ class BenchResult:
|
|
|
67
67
|
plain: Arm
|
|
68
68
|
pal: Arm
|
|
69
69
|
when: float = field(default_factory=time.time)
|
|
70
|
+
pal_files: dict = field(default_factory=dict, repr=False) # what Pal's way wrote: {path: bytes}, offered to keep in your folder
|
|
70
71
|
|
|
71
72
|
@property
|
|
72
73
|
def saved(self) -> int:
|
|
@@ -78,6 +79,7 @@ class BenchResult:
|
|
|
78
79
|
|
|
79
80
|
def to_dict(self) -> dict:
|
|
80
81
|
d = asdict(self)
|
|
82
|
+
d.pop("pal_files", None)
|
|
81
83
|
d.update(saved=self.saved, saved_pct=self.saved_pct, plain_total=self.plain.total, pal_total=self.pal.total)
|
|
82
84
|
return d
|
|
83
85
|
|
|
@@ -161,7 +163,12 @@ def run_bench(folder: Path, task: str, core_factory: Callable[[], Any], model: s
|
|
|
161
163
|
pal_arm = _run_arm("pal", pal, steps, progress)
|
|
162
164
|
finally:
|
|
163
165
|
client.close()
|
|
164
|
-
|
|
166
|
+
kept = {}
|
|
167
|
+
for rel in sorted({f for st in pal_arm.steps for f in st.files}):
|
|
168
|
+
f = pal_dir / rel
|
|
169
|
+
if f.is_file():
|
|
170
|
+
kept[rel] = f.read_bytes()
|
|
171
|
+
result = BenchResult(task, model, str(folder), plain_arm, pal_arm, pal_files=kept)
|
|
165
172
|
save(result, home)
|
|
166
173
|
return result
|
|
167
174
|
|
|
@@ -70,7 +70,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
70
70
|
a.add_argument("--build", action="store_true", help="use the Build agent: it may propose file changes (asks before writing)")
|
|
71
71
|
b = sub.add_parser("bench", help="run one task as a plain chat and through Pal, on the same model, and compare the real tokens")
|
|
72
72
|
_common(b, suppress=True)
|
|
73
|
-
b.add_argument("task", nargs="
|
|
73
|
+
b.add_argument("task", nargs="*", help="what to build, as you would type it (leave it out to open the bench screen and type tasks there)")
|
|
74
74
|
b.add_argument("--then", action="append", default=None, metavar="MESSAGE", help="a follow-up message (repeat for more; default: two standard ones)")
|
|
75
75
|
b.add_argument("--no-follow-ups", action="store_true", help="only the task itself")
|
|
76
76
|
b.add_argument("--json", action="store_true", help="print the result as JSON")
|
|
@@ -79,13 +79,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
79
79
|
return p
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def bench(args) -> int:
|
|
83
|
-
"""pal bench: the same task, plain chat versus Pal, on the same model.""
|
|
82
|
+
def bench(args, core_factory: Callable | None = None) -> int:
|
|
83
|
+
"""pal bench: the same task, plain chat versus Pal, on the same model. Without a task it opens the usual Pal screen (\"pal bench\"), and every
|
|
84
|
+
task typed there is run both ways, with the results as a table and bar charts."""
|
|
84
85
|
import json as _json
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if not args.task:
|
|
88
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
89
|
+
print("pal bench: give a task, e.g. pal bench \"make a todo app\"", file=sys.stderr)
|
|
90
|
+
return 2
|
|
91
|
+
args.bench_screen = True
|
|
92
|
+
return interactive(args, core_factory)
|
|
89
93
|
console = ui.make_console()
|
|
90
94
|
host = args.host or backend.DEFAULT_HOST
|
|
91
95
|
prefs = backend.load_prefs()
|
|
@@ -103,17 +107,36 @@ def bench(args) -> int:
|
|
|
103
107
|
except PalError as e:
|
|
104
108
|
print(f"pal bench: {e}", file=sys.stderr)
|
|
105
109
|
return 2
|
|
106
|
-
|
|
107
|
-
task = " ".join(args.task)
|
|
108
|
-
status = console.status(Text("Starting\u2026", style=theme.MUTED), spinner="dots", spinner_style=theme.RED)
|
|
109
|
-
with status:
|
|
110
|
-
result = run_bench(Path(args.dir), task, lambda: backend.make_core(provider, model, host), model, follow,
|
|
111
|
-
progress=lambda m: status.update(Text(m, style=theme.MUTED)))
|
|
110
|
+
result = bench_run(console, Path(args.dir), " ".join(args.task), provider, model, host, [] if args.no_follow_ups else args.then)
|
|
112
111
|
if args.json:
|
|
113
112
|
print(_json.dumps(result.to_dict(), indent=1))
|
|
114
113
|
return 0
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
show_bench(console, result, provider, model, Path(args.dir))
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def bench_run(console, folder: Path, task: str, provider: str, model: str, host: str, follow=None, core_factory: Callable | None = None):
|
|
119
|
+
from pal.terminal.bench import run_bench
|
|
120
|
+
status = console.status(Text("Starting\u2026", style=theme.MUTED), spinner="dots", spinner_style=theme.RED)
|
|
121
|
+
with status:
|
|
122
|
+
return run_bench(folder, task, lambda: (core_factory or backend.make_core)(provider, model, host), model, follow,
|
|
123
|
+
progress=lambda m: status.update(Text(m, style=theme.MUTED)))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def bars(console, title: str, rows: list[tuple[str, float, str]], width: int = 46) -> None:
|
|
127
|
+
"""A horizontal bar chart in the terminal: (label, value, shown value). Plain chat in grey, Pal in red."""
|
|
128
|
+
top = max((v for _, v, _ in rows), default=0) or 1
|
|
129
|
+
ui.say(console, Text(title, style=f"bold {theme.WHITE}"))
|
|
130
|
+
for label, value, shown in rows:
|
|
131
|
+
n = max(1, round(width * value / top)) if value > 0 else 0
|
|
132
|
+
colour = theme.RED if label.lower().startswith("pal") else theme.MUTED
|
|
133
|
+
ui.say(console, Text.assemble((" " + f"{label:<11}", theme.INK), ("\u2588" * n, colour), (" " * (width - n + 2), ""), (shown, f"bold {theme.WHITE}")))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def show_bench(console, result, provider: str, model: str, folder: Path) -> None:
|
|
137
|
+
from rich.table import Table
|
|
138
|
+
ui.say(console, Text.assemble(("pal bench ", f"bold {theme.WHITE}"), (f"{model} ({provider}) \u00B7 {ui.short_path(Path(folder).resolve())}", theme.MUTED)))
|
|
139
|
+
ui.say(console, Text(f"Task: {result.task}", style=theme.INK))
|
|
117
140
|
console.print()
|
|
118
141
|
grid = Table(box=None, padding=(0, 2), header_style=f"bold {theme.WHITE}")
|
|
119
142
|
for col, just in (("step", "left"), ("agent", "left"), ("plain chat", "right"), ("Pal", "right"), ("saved", "right")):
|
|
@@ -129,6 +152,13 @@ def bench(args) -> int:
|
|
|
129
152
|
Text(f"{result.saved:,} ({result.saved_pct:g}%)", style=f"bold {theme.RED_HI}"))
|
|
130
153
|
ui.say(console, grid)
|
|
131
154
|
console.print()
|
|
155
|
+
bars(console, "Tokens", [("plain chat", result.plain.total, f"{result.plain.total:,}"), ("Pal", result.pal.total, f"{result.pal.total:,}")])
|
|
156
|
+
console.print()
|
|
157
|
+
bars(console, "Time", [("plain chat", result.plain.seconds, f"{result.plain.seconds:g}s"), ("Pal", result.pal.seconds, f"{result.pal.seconds:g}s")])
|
|
158
|
+
console.print()
|
|
159
|
+
verdict = (f"Pal used {result.saved:,} fewer tokens ({result.saved_pct:g}% less)." if result.saved > 0 else
|
|
160
|
+
f"Pal used {-result.saved:,} more tokens on this task (it pays off with bigger projects, documents and longer conversations).")
|
|
161
|
+
ui.say(console, Text(verdict, style=f"bold {theme.RED_HI}"))
|
|
132
162
|
for arm in (result.plain, result.pal):
|
|
133
163
|
files = sorted({f for s in arm.steps for f in s.files})
|
|
134
164
|
ui.say(console, Text(f"{arm.name:<6} {arm.seconds:g}s \u00B7 {arm.calls} model call{'s' if arm.calls != 1 else ''} \u00B7 wrote: {', '.join(files) or 'no files'}"
|
|
@@ -137,7 +167,28 @@ def bench(args) -> int:
|
|
|
137
167
|
ui.say(console, Text("Token counts are the model's own." if not est else f"{est} call(s) had no token count from the model, so those are estimates (characters / 4).", style=theme.MUTED))
|
|
138
168
|
ui.say(console, Text("Pal's local helper jobs are off in the bench, so its saving here comes from the project map, graph, documents and history alone.", style=theme.MUTED))
|
|
139
169
|
console.print()
|
|
140
|
-
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def keep_bench_files(console, result, folder: Path) -> list[str]:
|
|
173
|
+
"""Both ways build in temporary copies; offer to write Pal's version into the folder (asking first, never overwriting without saying so)."""
|
|
174
|
+
if not result.pal_files:
|
|
175
|
+
return []
|
|
176
|
+
folder = Path(folder).resolve()
|
|
177
|
+
clash = [p for p in result.pal_files if (folder / p).exists()]
|
|
178
|
+
question = f"Save Pal's version ({len(result.pal_files)} file{'s' if len(result.pal_files) != 1 else ''}) in {ui.short_path(folder)}?" + (
|
|
179
|
+
f" It replaces {len(clash)} existing file{'s' if len(clash) != 1 else ''}." if clash else "")
|
|
180
|
+
if not ui.confirm(console, question):
|
|
181
|
+
return []
|
|
182
|
+
written = []
|
|
183
|
+
for rel, data in result.pal_files.items():
|
|
184
|
+
target = (folder / rel).resolve()
|
|
185
|
+
if folder not in target.parents:
|
|
186
|
+
continue # never outside the folder
|
|
187
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
188
|
+
target.write_bytes(data)
|
|
189
|
+
written.append(rel)
|
|
190
|
+
ui.say(console, Text(f"Saved: {', '.join(written)}", style=theme.INK))
|
|
191
|
+
return written
|
|
141
192
|
|
|
142
193
|
|
|
143
194
|
def setup(args) -> int:
|
|
@@ -165,7 +216,7 @@ def first_run() -> None:
|
|
|
165
216
|
console.print()
|
|
166
217
|
|
|
167
218
|
|
|
168
|
-
def prepare_folder(args, console, core_factory: Callable | None = None) -> tuple[str, Project | None]:
|
|
219
|
+
def prepare_folder(args, console, core_factory: Callable | None = None, choose: bool = True) -> tuple[str, Project | None]:
|
|
169
220
|
"""Before a session: get local models ready (install and start Ollama, download the models for this GPU, asking first), then set up the
|
|
170
221
|
folder: documents to markdown, the project map, the code graph (in the background). Returns (a line about the local model, the Project)."""
|
|
171
222
|
from pal.terminal.project import Project
|
|
@@ -173,7 +224,7 @@ def prepare_folder(args, console, core_factory: Callable | None = None) -> tuple
|
|
|
173
224
|
local = ""
|
|
174
225
|
if core_factory is None:
|
|
175
226
|
tty = sys.stdin.isatty() and sys.stdout.isatty()
|
|
176
|
-
local = ensure_local(console, backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=tty).detail
|
|
227
|
+
local = ensure_local(console, backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=tty, choose=choose).detail
|
|
177
228
|
folder = Path(getattr(args, "dir", ".") or ".")
|
|
178
229
|
if os.environ.get("PAL_SKIP_PROJECT"):
|
|
179
230
|
return local, None
|
|
@@ -217,7 +268,7 @@ def web(args) -> int:
|
|
|
217
268
|
from pal.terminal.project import ProjectGraph
|
|
218
269
|
graph = ProjectGraph(project.root / "graph", settings.knowledge_graph)
|
|
219
270
|
return _serve(settings, open_browser=not args.no_open, page="studio" if args.page == "studio" else "", graph=graph,
|
|
220
|
-
documents=project.documents() if project is not None else None)
|
|
271
|
+
documents=project.documents() if project is not None else None, project_dir=Path(getattr(args, "dir", ".") or ".").resolve())
|
|
221
272
|
|
|
222
273
|
|
|
223
274
|
def _utf8() -> None:
|
|
@@ -488,6 +539,14 @@ def handle(line: str, args, session: PalSession, console, core_factory, box: dic
|
|
|
488
539
|
return session, True
|
|
489
540
|
if cmd == "/help":
|
|
490
541
|
ui.show_help(console)
|
|
542
|
+
elif cmd == "/agent":
|
|
543
|
+
from pal.terminal.setup import choose_agent
|
|
544
|
+
mode = rest if rest in MODES else choose_agent(console, current=session.mode)
|
|
545
|
+
if mode:
|
|
546
|
+
session.mode = mode
|
|
547
|
+
backend.save_prefs(mode=mode)
|
|
548
|
+
ui.say(console, Text(f"Agent: {mode.capitalize()} ({AGENT_ABOUT[mode]})", style=theme.MUTED))
|
|
549
|
+
console.print()
|
|
491
550
|
elif cmd == "/mode":
|
|
492
551
|
if rest in MODES:
|
|
493
552
|
session.mode = rest
|
|
@@ -707,13 +766,37 @@ def voice_flow(console) -> str:
|
|
|
707
766
|
return heard.text
|
|
708
767
|
|
|
709
768
|
|
|
769
|
+
def choosing(args, core_factory: Callable | None = None) -> bool:
|
|
770
|
+
"""pal cli asks for its models at every start (nothing preselected), unless they were given on the command line or this is not a terminal."""
|
|
771
|
+
return (core_factory is None and not args.provider and not args.model and sys.stdin.isatty() and sys.stdout.isatty()
|
|
772
|
+
and not os.environ.get("PAL_SKIP_SETUP") and not api_only(backend.hardware()))
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def ensure_ready_line(args) -> str:
|
|
776
|
+
"""The local-model line again, after the models were chosen (and maybe downloaded)."""
|
|
777
|
+
from pal.terminal.setup import ensure_local
|
|
778
|
+
return ensure_local(ui.make_console(), backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=False, choose=False).detail
|
|
779
|
+
|
|
780
|
+
|
|
710
781
|
def interactive(args, core_factory: Callable | None = None) -> int:
|
|
711
782
|
console = ui.make_console()
|
|
712
|
-
|
|
783
|
+
ask = choosing(args, core_factory)
|
|
784
|
+
local, project = prepare_folder(args, console, core_factory, choose=not ask)
|
|
785
|
+
if ask:
|
|
786
|
+
from pal.terminal.setup import choose_models
|
|
787
|
+
console.print()
|
|
788
|
+
if not choose_models(console, backend.hardware(), args.host or backend.DEFAULT_HOST):
|
|
789
|
+
console.print(Text("\nNo model chosen. Run pal cli again to choose one.\n", style=theme.MUTED))
|
|
790
|
+
return 1
|
|
791
|
+
if not args.mode:
|
|
792
|
+
from pal.terminal.setup import choose_agent
|
|
793
|
+
args.mode = choose_agent(console) or None
|
|
794
|
+
local = ensure_ready_line(args)
|
|
713
795
|
session = start(args, core_factory, project)
|
|
714
796
|
if session is None:
|
|
715
797
|
return 1
|
|
716
798
|
session.local_status = local
|
|
799
|
+
session.brand = "bench" if getattr(args, "bench_screen", False) else "cli"
|
|
717
800
|
if project is not None:
|
|
718
801
|
ui.show_project(console, session)
|
|
719
802
|
console.print()
|
|
@@ -753,6 +836,14 @@ def interactive(args, core_factory: Callable | None = None) -> int:
|
|
|
753
836
|
session, go = handle(text, args, session, console, core_factory, box)
|
|
754
837
|
if not go:
|
|
755
838
|
break
|
|
839
|
+
elif text and getattr(args, "bench_screen", False): # pal bench: the task both ways, then the comparison
|
|
840
|
+
host = args.host or backend.DEFAULT_HOST
|
|
841
|
+
try:
|
|
842
|
+
result = bench_run(console, session.cwd, text, session.provider, session.model, host, core_factory=core_factory)
|
|
843
|
+
show_bench(console, result, session.provider, session.model, session.cwd)
|
|
844
|
+
keep_bench_files(console, result, session.cwd)
|
|
845
|
+
except PalError as e:
|
|
846
|
+
ui.say(console, Text(f"The bench stopped: {e}", style=theme.RED_HI))
|
|
756
847
|
elif text:
|
|
757
848
|
flow_cli.run_turn(box["wf"], text, console)
|
|
758
849
|
except KeyboardInterrupt: # Esc or Ctrl+C at an awkward moment: back to the box, never out of Pal
|
|
@@ -813,7 +904,7 @@ def main(argv: Sequence[str] | None = None, core_factory: Callable | None = None
|
|
|
813
904
|
if args.cmd == "setup":
|
|
814
905
|
return setup(args)
|
|
815
906
|
if args.cmd == "bench":
|
|
816
|
-
return bench(args)
|
|
907
|
+
return bench(args, core_factory)
|
|
817
908
|
if args.cmd == "ask":
|
|
818
909
|
return ask_once(args, core_factory)
|
|
819
910
|
if not (sys.stdin.isatty() and sys.stdout.isatty()) and core_factory is None:
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""The Pal logo (two people hugging, white and red with a black outline) as pixels for the terminal.
|
|
2
|
+
|
|
3
|
+
Drawn from the logo image at 24 x 26 pixels with half blocks, two pixel rows per text line, so it shows in any terminal with colour and needs no
|
|
4
|
+
image support. W white, R red, K the black outline, . see-through (the terminal's own background).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pal.terminal import theme
|
|
9
|
+
|
|
10
|
+
GRID = [
|
|
11
|
+
'......KKKK.....K........',
|
|
12
|
+
'.....KWWWWWK.KRRRK......',
|
|
13
|
+
'....KWWWWWWWKRRRRRK.....',
|
|
14
|
+
'....KWWWWWWWKRRRRRRK....',
|
|
15
|
+
'....KWWWWWWWWKRRRRRR....',
|
|
16
|
+
'....KWWWWWWWWKRRRRRR....',
|
|
17
|
+
'....KWWWWWWWWKRRRRRR....',
|
|
18
|
+
'....KWWWWWWWWWRRRRRR....',
|
|
19
|
+
'....KWWWWWWWWKRRRRRK....',
|
|
20
|
+
'...KRKWWWWWWWKRRRRKKK...',
|
|
21
|
+
'..KRRKKWWWWWWKRRRKWWK...',
|
|
22
|
+
'..KRRRKKWWWWKRRRRKWWK...',
|
|
23
|
+
'...KRRRRKKKKRRRRRKKKK...',
|
|
24
|
+
'..KKKRRRRRRRRRRRRRRRRK..',
|
|
25
|
+
'..WWWKRRRRRRRRKKWKRRRR..',
|
|
26
|
+
'.KWWWWKRRRRRRKWWWKRRRRK.',
|
|
27
|
+
'.KWWWWWKKRRRKWWWWKRRRRR.',
|
|
28
|
+
'KWWWWWWWWKKKWWWWKRRRRRRK',
|
|
29
|
+
'KWWWWWWWWWWWWWWWKRRRRRRK',
|
|
30
|
+
'KWWWWWWWWWWWWWWKRRRRRRRK',
|
|
31
|
+
'KWWWWWWWWWWWWWKRRRRRRRRK',
|
|
32
|
+
'KWWWWWWWWWWWWKRRRRRRRRRK',
|
|
33
|
+
'KWWWWWWWWWKKRRRRRRRRRRRK',
|
|
34
|
+
'KWWWWWWWWKRRRRRRRRRRRRRK',
|
|
35
|
+
'KWWWWWWWWKRRRRRRRRRRRRRK',
|
|
36
|
+
'KKKKKKKKKKKKKKKKKKKKKKKK',
|
|
37
|
+
]
|
|
38
|
+
COLOURS = {"W": theme.WHITE, "R": theme.RED, "K": "#000000"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def logo_lines(grid: list[str] | None = None) -> list[list[tuple[str, str]]]:
|
|
42
|
+
"""Lines of (characters, style). The style is 'fg bg' in hex, or '' for nothing drawn: rich and prompt_toolkit both read it after a small change."""
|
|
43
|
+
g = grid or GRID
|
|
44
|
+
out: list[list[tuple[str, str]]] = []
|
|
45
|
+
for y in range(0, len(g), 2):
|
|
46
|
+
top, bottom = g[y], g[y + 1] if y + 1 < len(g) else "." * len(g[y])
|
|
47
|
+
line: list[tuple[str, str]] = []
|
|
48
|
+
for t, b in zip(top, bottom):
|
|
49
|
+
ct, cb = COLOURS.get(t), COLOURS.get(b)
|
|
50
|
+
if ct is None and cb is None:
|
|
51
|
+
line.append((" ", ""))
|
|
52
|
+
elif ct is None:
|
|
53
|
+
line.append(("▄", f"{cb}|"))
|
|
54
|
+
elif cb is None or ct == cb:
|
|
55
|
+
line.append(("▀" if cb is None else "█", f"{ct}|"))
|
|
56
|
+
else:
|
|
57
|
+
line.append(("▀", f"{ct}|{cb}"))
|
|
58
|
+
out.append(line)
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def rich_style(style: str) -> str:
|
|
63
|
+
fg, _, bg = style.partition("|")
|
|
64
|
+
return (fg + (f" on {bg}" if bg else "")) if fg else ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def ptk_style(style: str) -> str:
|
|
68
|
+
fg, _, bg = style.partition("|")
|
|
69
|
+
return (f"fg:{fg}" + (f" bg:{bg}" if bg else "")) if fg else ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# The start screen's logo: drawn from the logo image as solid pixels (half blocks, two pixel rows per text line), at two sizes.
|
|
73
|
+
LOGO = ['.....KKKK...........', '....KWWWWK.KRRR.....', '....WWWWWWKRRRRR....', '...KWWWWWWWKRRRRK...', '...KWWWWWWWKRRRRK...',
|
|
74
|
+
'...KWWWWWWWKRRRRK...', '....WWWWWWWKRRRRK...', '...KKWWWWWWKRRRRK...', '..KRKWWWWWWKRRRKW...', '..KRRKWWWWKRRRKWWK..',
|
|
75
|
+
'..KRRRKKKKRRRRKKK...', '..KKRRRRRRRRRKRRRK..', '.KWWKRRRRRRRKWKRRRK.', '.WWWWWKRRRRKWWKRRRR.', '.WWWWWWKRKKWWWKRRRRK',
|
|
76
|
+
'KWWWWWWWWWWWWKRRRRRK', 'KWWWWWWWWWWWKRRRRRRK', 'KWWWWWWWWWWKRRRRRRRK', 'KWWWWWWWWWKRRRRRRRRK', 'KWWWWWWWKRRRRRRRRRRK',
|
|
77
|
+
'KWWWWWWWKRRRRRRRRRRK']
|
|
78
|
+
LOGO_SMALL = ['....KK..K.....', '...WWWWKRRR...', '...WWWWKRRRK..', '..KWWWWWRRRR..', '..KWWWWWRRRR..', '..KWWWWWRRRK..', '..RKWWWKRRKW..',
|
|
79
|
+
'..RRKWKRRRKK..', '.KKRRRRRRKRRK.', '.WWWRRRRWWRRR.', '.WWWWKRWWWRRRR', 'WWWWWWWWWKRRRR', 'WWWWWWWWKRRRRR', 'WWWWWWWKRRRRRR',
|
|
80
|
+
'WWWWWWRRRRRRRR', 'KWWWWKRRRRRRRK']
|
|
81
|
+
|
|
82
|
+
# The wordmark "pal code" in pixel letters. Rows 0-1 ascenders, 2-6 the letters, 7-8 the tail of the p.
|
|
83
|
+
FONT = {
|
|
84
|
+
"p": [".....", ".....", "####.", "#...#", "#...#", "#...#", "####.", "#....", "#....", "....."],
|
|
85
|
+
"a": [".....", ".....", ".###.", "....#", ".####", "#...#", ".####", ".....", ".....", "....."],
|
|
86
|
+
"l": ["##.", ".#.", ".#.", ".#.", ".#.", ".#.", ".##", "...", "...", "..."],
|
|
87
|
+
"c": [".....", ".....", ".####", "#....", "#....", "#....", ".####", ".....", ".....", "....."],
|
|
88
|
+
"o": [".....", ".....", ".###.", "#...#", "#...#", "#...#", ".###.", ".....", ".....", "....."],
|
|
89
|
+
"d": ["....#", "....#", ".####", "#...#", "#...#", "#...#", ".####", ".....", ".....", "....."],
|
|
90
|
+
"e": [".....", ".....", ".###.", "#...#", "#####", "#....", ".####", ".....", ".....", "....."],
|
|
91
|
+
"i": ["#", ".", "#", "#", "#", "#", "#", ".", ".", "."],
|
|
92
|
+
"w": [".......", ".......", "#.....#", "#.....#", "#..#..#", "#.#.#.#", ".#...#.", ".......", ".......", "......."],
|
|
93
|
+
"b": ["#....", "#....", "####.", "#...#", "#...#", "#...#", "####.", ".....", ".....", "....."],
|
|
94
|
+
"n": [".....", ".....", "####.", "#...#", "#...#", "#...#", "#...#", ".....", ".....", "....."],
|
|
95
|
+
"h": ["#....", "#....", "####.", "#...#", "#...#", "#...#", "#...#", ".....", ".....", "....."],
|
|
96
|
+
}
|
|
97
|
+
WORDS = {"cli": "pal code", "web": "pal code", "bench": "pal code"} # the wordmark everywhere: "pal" in white, "code" in red
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def word_for(kind: str = "cli") -> list[str]:
|
|
101
|
+
text = WORDS.get(kind, WORDS["cli"])
|
|
102
|
+
return word_grid(text, "WWW " + "R" * (len(text) - 4))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def word_grid(text: str = "pal code", colours: str = "WWW RRRR") -> list[str]:
|
|
106
|
+
rows = [""] * 10
|
|
107
|
+
for ch, col in zip(text, colours):
|
|
108
|
+
if ch == " ":
|
|
109
|
+
rows = [r + "..." for r in rows]
|
|
110
|
+
continue
|
|
111
|
+
rows = [r + FONT[ch][y].replace("#", col) + "." for y, r in enumerate(rows)]
|
|
112
|
+
width = max(len(r.rstrip(".")) for r in rows)
|
|
113
|
+
return [r[:width] for r in rows]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _see_through(grid: list[str]) -> list[str]:
|
|
117
|
+
"""The black outline is left to the terminal's own background, so it matches any dark theme."""
|
|
118
|
+
return [r.replace("K", ".") for r in grid]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def banner(size: str = "full", kind: str = "cli") -> list[list[tuple[str, str]]]:
|
|
122
|
+
"""The start screen's mark as lines of (characters, 'fg|bg') runs: the logo with the wordmark beside it ('full' or 'small'), or the
|
|
123
|
+
wordmark alone ('word')."""
|
|
124
|
+
word = logo_lines(word_for(kind))
|
|
125
|
+
if size == "word":
|
|
126
|
+
return word
|
|
127
|
+
art = logo_lines(_see_through(LOGO if size == "full" else LOGO_SMALL))
|
|
128
|
+
gap, top = 4, (len(art) - len(word)) // 2
|
|
129
|
+
blank_art = [(" " * len(art[0]), "")]
|
|
130
|
+
out = []
|
|
131
|
+
for i in range(max(len(art), len(word) + top)):
|
|
132
|
+
left = art[i] if i < len(art) else blank_art
|
|
133
|
+
right = word[i - top] if 0 <= i - top < len(word) else []
|
|
134
|
+
line = list(left) + [(" " * gap, "")] + list(right)
|
|
135
|
+
width = sum(len(c) for c, _ in line)
|
|
136
|
+
out.append(line + [(" " * (len(art[0]) + gap + len(word[0]) - width), "")]) # every line the same width, so centring keeps the shape
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
BANNER_ROWS = {size: len(banner(size)) for size in ("full", "small", "word")}
|
|
@@ -169,6 +169,7 @@ class PalSession:
|
|
|
169
169
|
self.history_tokens = 0 # what a plain chat would carry along: every earlier message and reply
|
|
170
170
|
self.history_paste = 0 # the part of that history that is @files pasted into earlier messages
|
|
171
171
|
self.turns = 0
|
|
172
|
+
self.last_prompt = 0 # the input tokens of the last request: how full the model's context was
|
|
172
173
|
self.pal_tokens = self.helper_tokens = self.plain_tokens = 0
|
|
173
174
|
self.cost_usd = 0.0 # what the model calls cost so far, when the provider's price is known
|
|
174
175
|
self.seconds = 0.0
|
|
@@ -194,6 +195,17 @@ class PalSession:
|
|
|
194
195
|
own = self.role_models.get(mode or self.mode)
|
|
195
196
|
return own if own else (self.provider, self.model)
|
|
196
197
|
|
|
198
|
+
def tokens_left(self) -> tuple[int, int]:
|
|
199
|
+
"""(tokens left in the active agent's context window, the window). Shown under the input box and in the status line."""
|
|
200
|
+
from pal.context_window import context_window, tokens_left
|
|
201
|
+
provider, model = self.model_for()
|
|
202
|
+
window = context_window(provider, model)
|
|
203
|
+
return tokens_left(window, self.last_prompt), window
|
|
204
|
+
|
|
205
|
+
def budget_left(self) -> int | None:
|
|
206
|
+
"""What is left of the session's token cap (/budget), or None without one."""
|
|
207
|
+
return max(0, self.budget - self.pal_tokens) if self.budget else None
|
|
208
|
+
|
|
197
209
|
def client_add_documents(self) -> None:
|
|
198
210
|
"""After /project rebuild: index the folder's documents and map again in this conversation."""
|
|
199
211
|
add = getattr(self.client, "add_documents", None)
|
|
@@ -345,6 +357,7 @@ class PalSession:
|
|
|
345
357
|
self.records.append(Record(text, reply.prompt_tokens + reply.completion_tokens, reply.helper_tokens, plain_prompt + reply.completion_tokens, saved_by,
|
|
346
358
|
reply.seconds, getattr(reply, "route", "api")))
|
|
347
359
|
self.last_reply = reply.text.strip()
|
|
360
|
+
self.last_prompt = reply.prompt_tokens or self.last_prompt
|
|
348
361
|
turn = Turn(text=reply.text.strip(), warnings=warnings, prompt_tokens=reply.prompt_tokens, completion_tokens=reply.completion_tokens,
|
|
349
362
|
helper_tokens=reply.helper_tokens, plain_estimate=plain_prompt + reply.completion_tokens, seconds=reply.seconds or round(time.perf_counter() - t0, 1), saved_by=saved_by)
|
|
350
363
|
if self.mode == "ask": # the Ask agent may ask the person to choose, instead of guessing
|