Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,4 @@ components.d.ts
**/.vscode/*
.wxt
var
.vscfavoriterc
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Before submitting changes, please run checks and tests as described in [Automate
- [Docker](https://www.docker.com/)
- [devbox](https://www.jetpack.io/devbox)
- [git-lfs](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage)
- If you use a Visual Studio Code based IDE (like Cursor, Antigravity, etc), then consider installing the [EditorConfig extension](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) to apply all the rules defined in `.editorconfig` and keep the code format consistent.

### Setup

Expand Down
5 changes: 5 additions & 0 deletions devbox.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.0/.schema/devbox.schema.json",
"packages": {
"python": "3.13",
"git": "latest",
"uv": "latest",
"poetry": {
"version": "latest",
// this fails because is trying to find a pyproject.toml at the root of the project
Expand Down Expand Up @@ -56,6 +58,9 @@
"cd src/backend",
"poetry run python -m tero.secrets_cleanup"
],
"vllm": [
"./scripts/vllm.sh"
],
"playwright": [
"docker compose up playwright"
],
Expand Down
120 changes: 120 additions & 0 deletions devbox.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,78 @@
{
"lockfile_version": "1",
"packages": {
"git@latest": {
"last_modified": "2026-01-23T17:20:52Z",
"resolved": "github:NixOS/nixpkgs/a1bab9e494f5f4939442a57a58d0449a109593fe#git",
"source": "devbox-search",
"version": "2.52.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/vnhprisb777byfjpp5mdd0mxwkpvhbc0-git-2.52.0",
"default": true
},
{
"name": "doc",
"path": "/nix/store/p0dx3053175fpr3kjf0fqgs9x6gm3dri-git-2.52.0-doc"
}
],
"store_path": "/nix/store/vnhprisb777byfjpp5mdd0mxwkpvhbc0-git-2.52.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/sd4bjblxfljbm13mpl50x8g336gw2dri-git-2.52.0",
"default": true
},
{
"name": "debug",
"path": "/nix/store/cx033x4nmr12d9dcn8hxjfrajc3983f0-git-2.52.0-debug"
},
{
"name": "doc",
"path": "/nix/store/ylsmgdrnp78p8hn7n9lxpada78dka41v-git-2.52.0-doc"
}
],
"store_path": "/nix/store/sd4bjblxfljbm13mpl50x8g336gw2dri-git-2.52.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/sr42bfqa0pc2ysba678mrm49g78jdynp-git-2.52.0",
"default": true
},
{
"name": "doc",
"path": "/nix/store/fky0ci2bhwgyh9klg5682pmqswqg7wk4-git-2.52.0-doc"
}
],
"store_path": "/nix/store/sr42bfqa0pc2ysba678mrm49g78jdynp-git-2.52.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/ipwndaag56mm8g8gn8j98z0jvn8x4mk1-git-2.52.0",
"default": true
},
{
"name": "debug",
"path": "/nix/store/zd8di23fmzjwrhb5ij1bjnlfqkx9j7d6-git-2.52.0-debug"
},
{
"name": "doc",
"path": "/nix/store/d8kc4kqzcrbcj92msxrpdsdjglh2q5gp-git-2.52.0-doc"
}
],
"store_path": "/nix/store/ipwndaag56mm8g8gn8j98z0jvn8x4mk1-git-2.52.0"
}
}
},
"github:NixOS/nixpkgs/nixpkgs-unstable": {
"last_modified": "2025-10-23T16:27:14Z",
"resolved": "github:NixOS/nixpkgs/d5faa84122bc0a1fd5d378492efce4e289f8eac1?lastModified=1761236834&narHash=sha256-%2Bpthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE%2BfV2Q%3D"
Expand Down Expand Up @@ -238,6 +310,54 @@
"store_path": "/nix/store/2mab9iiwhcqwk75qwvp3zv0bvbiaq6cs-python3-3.13.3"
}
}
},
"uv@latest": {
"last_modified": "2026-01-30T02:32:49Z",
"resolved": "github:NixOS/nixpkgs/6308c3b21396534d8aaeac46179c14c439a89b8a#uv",
"source": "devbox-search",
"version": "0.9.27",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/bj9jidx7h317hmnz220qpka6850sjvi1-uv-0.9.27",
"default": true
}
],
"store_path": "/nix/store/bj9jidx7h317hmnz220qpka6850sjvi1-uv-0.9.27"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/2703hnsgll85c1rpzzqycnmp0jvy579z-uv-0.9.27",
"default": true
}
],
"store_path": "/nix/store/2703hnsgll85c1rpzzqycnmp0jvy579z-uv-0.9.27"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/il24573a65xa0bfy2d6kli6nvnx80qhv-uv-0.9.27",
"default": true
}
],
"store_path": "/nix/store/il24573a65xa0bfy2d6kli6nvnx80qhv-uv-0.9.27"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/k9b6fbx5pzj04fws4na77i4pb5l64li8-uv-0.9.27",
"default": true
}
],
"store_path": "/nix/store/k9b6fbx5pzj04fws4na77i4pb5l64li8-uv-0.9.27"
}
}
}
}
}
36 changes: 36 additions & 0 deletions scripts/vllm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -e

# using v0.14.1 since when using v0.15.1 we get "Failed to import from vllm._C ... _C.abi.so" and then "AttributeError: '_OpNamespace' '_C_utils' object has no attribute 'init_cpu_threads_env'" when starting vllm serve.
[ -d var/vllm/.git ] || git clone -b v0.14.1 https://github.com/vllm-project/vllm.git var/vllm
cd var/vllm

# C++: 0 < M <= 8 is parsed as (0 < M) <= 8; fix to (0 < M) && (M <= 8) so Clang on macOS accepts it
sed -i.bak 's/static_assert(0 < M <= 8);/static_assert(0 < M \&\& M <= 8);/g' csrc/cpu/cpu_attn_vec.hpp 2>/dev/null || true
sed -i.bak 's/static_assert(0 < M <= 16);/static_assert(0 < M \&\& M <= 16);/g' csrc/cpu/cpu_attn_vec16.hpp 2>/dev/null || true

if [ ! -x .venv/bin/vllm ]; then
([ -d .venv ] || uv venv --seed .venv)
. .venv/bin/activate
uv pip install -r requirements/cpu.txt --index-strategy unsafe-best-match
uv pip install -e ".[audio]"
else
. .venv/bin/activate
fi

cleanup() {
[ -n "$CHAT_PID" ] && kill "$CHAT_PID" 2>/dev/null || true
[ -n "$EMBED_PID" ] && kill "$EMBED_PID" 2>/dev/null || true
[ -n "$TRANSCRIBE_PID" ] && kill "$TRANSCRIBE_PID" 2>/dev/null || true
wait 2>/dev/null || true
}
trap cleanup EXIT INT TERM

vllm serve Qwen/Qwen2.5-1.5B-Instruct --dtype auto --api-key test-token --port 8001 --enable-auto-tool-choice --tool-call-parser hermes &
CHAT_PID=$!
sleep 2
vllm serve nomic-ai/nomic-embed-text-v1 --api-key test-token --trust-remote-code --port 8002 &
EMBED_PID=$!

wait

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""qwen_2.5_1.5b_model

Revision ID: 613cc99427e2
Revises: c3ae0aefa4d1
Create Date: 2026-02-05 16:23:32.147821

"""
from typing import Sequence, Union
from alembic import op
from alembic_postgresql_enum import TableReference

# revision identifiers, used by Alembic.
revision: str = '613cc99427e2'
down_revision: Union[str, None] = 'c3ae0aefa4d1'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.sync_enum_values( # type: ignore
enum_schema='public',
enum_name='llmmodelvendor',
new_values=['ANTHROPIC', 'GOOGLE', 'OPENAI', 'QWEN'],
affected_columns=[TableReference(table_schema='public', table_name='llm_model', column_name='model_vendor')],
enum_values_to_rename=[],
)
op.execute("""
INSERT INTO llm_model (id, name, description, token_limit, output_token_limit, prompt_1k_token_usd, completion_1k_token_usd, model_type, model_vendor) VALUES
('qwen-2.5-1.5b', 'Qwen 2.5 1.5B', 'This is a free, open‑source model for simple tasks and basic coding; less capable than GPT‑4o Mini and GPT‑4.1 Nano.', 128000, 8000, 0.0, 0.0, 'CHAT', 'QWEN')
""")


def downgrade() -> None:
op.execute("DELETE FROM llm_model WHERE id = 'qwen-2.5-1.5b'")
op.sync_enum_values( # type: ignore
enum_schema='public',
enum_name='llmmodelvendor',
new_values=['ANTHROPIC', 'GOOGLE', 'OPENAI'],
affected_columns=[TableReference(table_schema='public', table_name='llm_model', column_name='model_vendor')],
enum_values_to_rename=[],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""recursion-limit

Revision ID: fd24763a078c
Revises: 613cc99427e2
Create Date: 2026-02-10 12:30:05.846201

"""

import sqlalchemy as sa
import sqlmodel
from typing import Sequence, Union
from alembic import op


# revision identifiers, used by Alembic.
revision: str = "fd24763a078c"
down_revision: Union[str, None] = "613cc99427e2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column(
"agent",
sa.Column(
"recursion_limit",
sa.Integer(),
nullable=False,
server_default=sa.literal_column("40"),
),
)
op.create_check_constraint(
"agent_recursion_limit_range",
"agent",
"recursion_limit >= 20 AND recursion_limit <= 100",
)


def downgrade() -> None:
op.execute(
"ALTER TABLE agent DROP CONSTRAINT IF EXISTS agent_recursion_limit_range"
)
op.drop_column("agent", "recursion_limit")
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""thread_message_origin_system

Revision ID: 8e9d8ca4dd0c
Revises: fd24763a078c
Create Date: 2026-02-11 19:05:25.867844

"""
import sqlalchemy as sa
import sqlmodel
from typing import Sequence, Union
from alembic import op
from alembic_postgresql_enum import TableReference

# revision identifiers, used by Alembic.
revision: str = '8e9d8ca4dd0c'
down_revision: Union[str, None] = 'fd24763a078c'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.sync_enum_values( # type: ignore
enum_schema="public",
enum_name="threadmessageorigin",
new_values=["USER", "AGENT", "SYSTEM"],
affected_columns=[TableReference(table_schema="public", table_name="thread_message", column_name="origin")],
enum_values_to_rename=[],
)


def downgrade() -> None:
op.sync_enum_values( # type: ignore
enum_schema="public",
enum_name="threadmessageorigin",
new_values=["USER", "AGENT"],
affected_columns=[TableReference(table_schema="public", table_name="thread_message", column_name="origin")],
enum_values_to_rename=[],
)
3 changes: 1 addition & 2 deletions src/backend/tero/agents/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
from ..core.env import env
from ..core.repos import get_db
from ..files.api import build_file_download_response
from ..files.core import QuotaExceededError, add_encoding_to_content_type
from ..files.domain import File, FileStatus, FileUpdate, FileMetadata, FileMetadataWithContent
from ..files.file_quota import QuotaExceededError
from ..files.parser import add_encoding_to_content_type
from ..files.repos import FileRepository
from ..teams.domain import GLOBAL_TEAM_ID, Role
from ..tools.core import AgentTool
Expand Down
13 changes: 8 additions & 5 deletions src/backend/tero/agents/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

class BaseAgent(CamelCaseModel, abc.ABC):
id: int = Field(primary_key=True, default=None)
name: Optional[str] = Field(max_length=NAME_MAX_LENGTH, default=None)
name: Optional[str] = Field(max_length=NAME_MAX_LENGTH, default=None)
description: Optional[str] = Field(max_length=100, default=None)
last_update: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

def set_default_name(self):
self.name = f"Agent #{self.id}"

Expand All @@ -38,6 +38,7 @@ class AgentUpdate(CamelCaseModel):
system_prompt: Optional[str] = None
temperature: Optional[LlmTemperature] = None
reasoning_effort: Optional[ReasoningEffort] = None
recursion_limit: Optional[int] = None
publish_prompts: Optional[bool] = None
team_id: Optional[int] = None

Expand All @@ -55,10 +56,11 @@ class Agent(BaseAgent, table=True):
system_prompt: str = Field(sa_column=Column(Text))
temperature: LlmTemperature = LlmTemperature.NEUTRAL
reasoning_effort: ReasoningEffort = ReasoningEffort.LOW
recursion_limit: int = Field(default=20, ge=20, le=100)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
team: Optional[Team] = Relationship()
evaluator_id: Optional[int] = Field(default=None, foreign_key="evaluator.id")

def update_with(self, update: AgentUpdate):
update_dict = update.model_dump(exclude_none=True)
update_dict["icon"] = base64.b64decode(update_dict["icon"]) if update_dict.get("icon") else None
Expand All @@ -79,7 +81,7 @@ def is_visible_by(self, user: User) -> bool:

def is_editable_by(self, user: User) -> bool:
return self.user_id == user.id or any(
tr.role in [Role.TEAM_OWNER, Role.TEAM_EDITOR] and cast(Team, tr.team).id == self.team_id
tr.role in [Role.TEAM_OWNER, Role.TEAM_EDITOR] and cast(Team, tr.team).id == self.team_id
for tr in user.team_roles
)

Expand Down Expand Up @@ -130,9 +132,10 @@ class PublicAgent(BaseAgent):
system_prompt: str
temperature: LlmTemperature
reasoning_effort: ReasoningEffort
recursion_limit: int
user: Optional[UserListItem] = None
team: Optional[Team] = None

@staticmethod
def from_agent(a: Agent, can_edit: bool) -> 'PublicAgent':
agent_dict = a.model_dump()
Expand Down
Loading
Loading