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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@ robolearn-interface/
.env
.env.local

cookies.txt
cookies.txt

rag-agent/
4 changes: 4 additions & 0 deletions packages/api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ dependencies = [
"uvicorn[standard]>=0.32.0",
"asyncpg>=0.30.0",
"sqlalchemy[asyncio]>=2.0.0",
"python-dotenv>=1.0.0",
# ChatKit + OpenAI Agents SDK for chat server
"openai-agents>=0.0.9",
"openai-chatkit>=1.4.0",
]

[project.optional-dependencies]
Expand Down
16 changes: 16 additions & 0 deletions packages/api/src/taskflow_api/chatkit_store/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
TaskFlow ChatKit Store Implementation.

Provides PostgreSQL-based Store for ChatKit conversation persistence.
Adapted from rag-agent/chatkit_store for TaskFlow API.
"""

from .config import StoreConfig
from .context import RequestContext
from .postgres_store import PostgresStore

__all__ = [
"StoreConfig",
"PostgresStore",
"RequestContext",
]
83 changes: 83 additions & 0 deletions packages/api/src/taskflow_api/chatkit_store/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Configuration for TaskFlow ChatKit store."""

from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class StoreConfig(BaseSettings):
"""
PostgreSQL Store configuration for TaskFlow ChatKit.

All settings can be overridden via environment variables with the
TASKFLOW_CHATKIT_ prefix (e.g., TASKFLOW_CHATKIT_DATABASE_URL).
"""

model_config = SettingsConfigDict(
env_prefix="TASKFLOW_CHATKIT_",
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)

# Database connection
database_url: str = Field(
...,
description="PostgreSQL connection URL (postgresql+asyncpg://user:pass@host:port/db)",
)

# Connection pool settings
pool_size: int = Field(
default=20,
description="Maximum number of connections in the pool",
ge=1,
le=100,
)

max_overflow: int = Field(
default=10,
description="Maximum overflow connections beyond pool_size",
ge=0,
le=50,
)

pool_timeout: float = Field(
default=30.0,
description="Seconds to wait before timing out on getting a connection",
gt=0,
)

pool_recycle: int = Field(
default=3600, description="Seconds after which connections are recycled", ge=300
)

# Query settings
statement_timeout: int = Field(
default=30000, description="Statement timeout in milliseconds", ge=1000
)

# Schema - use taskflow_chat to isolate from main taskflow schema
schema_name: str = Field(default="taskflow_chat", description="Database schema name for tables")

@field_validator("database_url")
@classmethod
def validate_database_url(cls, v: str) -> str:
"""Ensure database URL uses asyncpg driver and fix SSL parameters."""
if not v.startswith("postgresql://") and not v.startswith("postgresql+asyncpg://"):
raise ValueError("database_url must start with postgresql:// or postgresql+asyncpg://")

# Convert to asyncpg if needed
if v.startswith("postgresql://"):
v = v.replace("postgresql://", "postgresql+asyncpg://", 1)

# Fix SSL parameters for asyncpg
if "sslmode=require" in v:
v = v.replace("sslmode=require", "ssl=require")
elif "sslmode=prefer" in v:
v = v.replace("sslmode=prefer", "ssl=prefer")
elif "sslmode=allow" in v:
v = v.replace("sslmode=allow", "ssl=allow")
elif "sslmode=disable" in v:
v = v.replace("sslmode=disable", "ssl=disable")

return v
37 changes: 37 additions & 0 deletions packages/api/src/taskflow_api/chatkit_store/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Request context for ChatKit store operations."""

from typing import Any

from pydantic import BaseModel, Field


class RequestContext(BaseModel):
"""
Request context passed to all store operations.

Provides user isolation and optional metadata for access control,
logging, and tracing across all store operations.
"""

user_id: str | None = Field(
...,
description="Unique identifier for the user making the request",
min_length=1,
)

organization_id: str | None = Field(
default=None, description="Optional organization ID for multi-org tenancy"
)

request_id: str | None = Field(
default=None, description="Optional request ID for tracing and logging"
)

metadata: dict[str, Any] = Field(
default_factory=dict, description="Additional context metadata"
)

class Config:
"""Pydantic configuration."""

frozen = False # Allow mutation for adding trace info
Loading