feat(api): 建立前后端接口契约壳子
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class Contract(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class PageMeta(Contract):
|
||||
total: int = 0
|
||||
limit: int = 50
|
||||
offset: int = 0
|
||||
|
||||
|
||||
class ErrorDetail(Contract):
|
||||
code: str
|
||||
message: str
|
||||
details: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ErrorResponse(Contract):
|
||||
error: ErrorDetail
|
||||
|
||||
|
||||
class OperationResponse(Contract):
|
||||
status: Literal["accepted", "completed"]
|
||||
resource_id: str | None = None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
# Notes and retrieval
|
||||
class NoteBlock(Contract):
|
||||
block_id: str
|
||||
note_id: str
|
||||
heading_path: list[str] = Field(default_factory=list)
|
||||
start_offset: int
|
||||
end_offset: int
|
||||
content: str
|
||||
content_hash: str
|
||||
token_count: int
|
||||
|
||||
|
||||
class NoteSummary(Contract):
|
||||
note_id: str
|
||||
title: str
|
||||
file_path: str
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class Note(NoteSummary):
|
||||
markdown: str
|
||||
blocks: list[NoteBlock] = Field(default_factory=list)
|
||||
|
||||
|
||||
class NoteListResponse(Contract):
|
||||
items: list[NoteSummary] = Field(default_factory=list)
|
||||
page: PageMeta = Field(default_factory=PageMeta)
|
||||
|
||||
|
||||
class NoteCreateRequest(Contract):
|
||||
title: str = Field(min_length=1)
|
||||
markdown: str = ""
|
||||
folder: str | None = None
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class NoteUpdateRequest(Contract):
|
||||
title: str | None = None
|
||||
markdown: str | None = None
|
||||
tags: list[str] | None = None
|
||||
|
||||
|
||||
class NoteMoveRequest(Contract):
|
||||
folder: str
|
||||
|
||||
|
||||
class SearchMode(str, Enum):
|
||||
fts = "fts"
|
||||
vector = "vector"
|
||||
hybrid = "hybrid"
|
||||
|
||||
|
||||
class SearchRequest(Contract):
|
||||
query: str = Field(min_length=1)
|
||||
mode: SearchMode = SearchMode.hybrid
|
||||
folders: list[str] = Field(default_factory=list)
|
||||
note_ids: list[str] = Field(default_factory=list)
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
created_from: datetime | None = None
|
||||
created_to: datetime | None = None
|
||||
updated_from: datetime | None = None
|
||||
updated_to: datetime | None = None
|
||||
limit: int = Field(default=20, ge=1, le=100)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
include_snippet: bool = True
|
||||
|
||||
|
||||
class Citation(Contract):
|
||||
citation_id: str
|
||||
note_id: str
|
||||
block_id: str
|
||||
file_path: str
|
||||
heading_path: list[str] = Field(default_factory=list)
|
||||
start_offset: int | None = None
|
||||
end_offset: int | None = None
|
||||
source_audio: str | None = None
|
||||
start_time: float | None = None
|
||||
end_time: float | None = None
|
||||
speaker: str | None = None
|
||||
|
||||
|
||||
class SearchResult(Contract):
|
||||
note_id: str
|
||||
block_id: str
|
||||
title: str
|
||||
file_path: str
|
||||
heading_path: list[str] = Field(default_factory=list)
|
||||
snippet: str | None = None
|
||||
score: float
|
||||
citation: Citation
|
||||
|
||||
|
||||
class SearchResponse(Contract):
|
||||
query: str
|
||||
mode: SearchMode
|
||||
items: list[SearchResult] = Field(default_factory=list)
|
||||
page: PageMeta = Field(default_factory=PageMeta)
|
||||
|
||||
|
||||
# Model, chat and tools
|
||||
class MessageRole(str, Enum):
|
||||
system = "system"
|
||||
user = "user"
|
||||
assistant = "assistant"
|
||||
tool = "tool"
|
||||
|
||||
|
||||
class Message(Contract):
|
||||
role: MessageRole
|
||||
content: str
|
||||
name: str | None = None
|
||||
tool_call_id: str | None = None
|
||||
|
||||
|
||||
class ToolDefinition(Contract):
|
||||
name: str
|
||||
description: str
|
||||
parameters: dict[str, Any] = Field(default_factory=dict)
|
||||
permission: str | None = None
|
||||
source: Literal["builtin", "plugin"] = "builtin"
|
||||
|
||||
|
||||
class ToolListResponse(Contract):
|
||||
items: list[ToolDefinition] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModelCapability(str, Enum):
|
||||
chat = "chat"
|
||||
vision = "vision"
|
||||
tool_calling = "tool_calling"
|
||||
reasoning = "reasoning"
|
||||
streaming = "streaming"
|
||||
structured_output = "structured_output"
|
||||
embedding = "embedding"
|
||||
|
||||
|
||||
class ModelRequest(Contract):
|
||||
provider_id: str
|
||||
model: str
|
||||
system: str | None = None
|
||||
messages: list[Message]
|
||||
tools: list[ToolDefinition] = Field(default_factory=list)
|
||||
temperature: float | None = Field(default=None, ge=0, le=2)
|
||||
max_tokens: int | None = Field(default=None, ge=1)
|
||||
response_format: dict[str, Any] | None = None
|
||||
attachments: list[str] = Field(default_factory=list)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ChatRequest(ModelRequest):
|
||||
conversation_id: str | None = None
|
||||
use_rag: bool = True
|
||||
retrieval: SearchRequest | None = None
|
||||
|
||||
|
||||
class ModelEventType(str, Enum):
|
||||
text_delta = "TextDelta"
|
||||
thinking_delta = "ThinkingDelta"
|
||||
tool_call_start = "ToolCallStart"
|
||||
tool_call_delta = "ToolCallDelta"
|
||||
tool_call_end = "ToolCallEnd"
|
||||
usage = "Usage"
|
||||
error = "Error"
|
||||
done = "Done"
|
||||
|
||||
|
||||
class ModelEvent(Contract):
|
||||
event: ModelEventType
|
||||
sequence: int = 0
|
||||
data: dict[str, Any] = Field(default_factory=dict)
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
# Agent
|
||||
class AgentRunStatus(str, Enum):
|
||||
queued = "queued"
|
||||
running = "running"
|
||||
waiting_permission = "waiting_permission"
|
||||
completed = "completed"
|
||||
failed = "failed"
|
||||
cancelled = "cancelled"
|
||||
|
||||
|
||||
class AgentRunCreateRequest(Contract):
|
||||
input: str = Field(min_length=1)
|
||||
provider_id: str
|
||||
model: str
|
||||
skill_id: str | None = None
|
||||
allowed_tools: list[str] = Field(default_factory=list)
|
||||
max_steps: int = Field(default=10, ge=1, le=100)
|
||||
tool_timeout_seconds: int = Field(default=30, ge=1)
|
||||
run_timeout_seconds: int = Field(default=300, ge=1)
|
||||
token_budget: int | None = Field(default=None, ge=1)
|
||||
max_concurrent_tools: int = Field(default=1, ge=1)
|
||||
allow_network: bool = False
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentRun(Contract):
|
||||
run_id: str
|
||||
status: AgentRunStatus
|
||||
input: str
|
||||
provider_id: str
|
||||
model: str
|
||||
skill_id: str | None = None
|
||||
current_step: int = 0
|
||||
max_steps: int
|
||||
token_budget: int | None = None
|
||||
cancelled: bool = False
|
||||
citations: list[Citation] = Field(default_factory=list)
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class AgentRunListResponse(Contract):
|
||||
items: list[AgentRun] = Field(default_factory=list)
|
||||
page: PageMeta = Field(default_factory=PageMeta)
|
||||
|
||||
|
||||
class AgentEventType(str, Enum):
|
||||
run_started = "RunStarted"
|
||||
text_delta = "TextDelta"
|
||||
thinking_delta = "ThinkingDelta"
|
||||
tool_call = "ToolCall"
|
||||
tool_result = "ToolResult"
|
||||
permission_required = "PermissionRequired"
|
||||
usage = "Usage"
|
||||
citation = "Citation"
|
||||
run_completed = "RunCompleted"
|
||||
run_failed = "RunFailed"
|
||||
run_cancelled = "RunCancelled"
|
||||
|
||||
|
||||
class AgentEvent(Contract):
|
||||
event: AgentEventType
|
||||
run_id: str
|
||||
sequence: int
|
||||
data: dict[str, Any] = Field(default_factory=dict)
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
class PermissionDecisionRequest(Contract):
|
||||
decision: Literal["allow_once", "allow_session", "deny"]
|
||||
|
||||
|
||||
# Skills and plugins
|
||||
class RetrievalConfig(Contract):
|
||||
top_k: int = Field(default=10, ge=1, le=100)
|
||||
rerank: bool = True
|
||||
citation: bool = True
|
||||
|
||||
|
||||
class SkillModelConfig(Contract):
|
||||
required_capabilities: list[ModelCapability] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SkillManifest(Contract):
|
||||
skill_id: str
|
||||
name: str
|
||||
version: str
|
||||
description: str = ""
|
||||
permissions: list[str] = Field(default_factory=list)
|
||||
tools: list[str] = Field(default_factory=list)
|
||||
retrieval: RetrievalConfig = Field(default_factory=RetrievalConfig)
|
||||
model: SkillModelConfig = Field(default_factory=SkillModelConfig)
|
||||
|
||||
|
||||
class SkillStatus(str, Enum):
|
||||
installed = "installed"
|
||||
disabled = "disabled"
|
||||
ready = "ready"
|
||||
dependency_missing = "dependency_missing"
|
||||
permission_required = "permission_required"
|
||||
error = "error"
|
||||
|
||||
|
||||
class Skill(Contract):
|
||||
manifest: SkillManifest
|
||||
status: SkillStatus
|
||||
enabled: bool = False
|
||||
missing_dependencies: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SkillListResponse(Contract):
|
||||
items: list[Skill] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ExtensionInstallRequest(Contract):
|
||||
package_path: str
|
||||
|
||||
|
||||
class PluginBackend(Contract):
|
||||
type: Literal["mcp", "internal_rpc", "none"] = "none"
|
||||
transport: Literal["stdio", "http", "none"] = "none"
|
||||
|
||||
|
||||
class PluginContribution(Contract):
|
||||
tools: list[str] = Field(default_factory=list)
|
||||
commands: list[str] = Field(default_factory=list)
|
||||
importers: list[str] = Field(default_factory=list)
|
||||
exporters: list[str] = Field(default_factory=list)
|
||||
panels: list[str] = Field(default_factory=list)
|
||||
settings_sections: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PluginManifest(Contract):
|
||||
plugin_id: str
|
||||
name: str
|
||||
version: str
|
||||
description: str = ""
|
||||
permissions: list[str] = Field(default_factory=list)
|
||||
contributes: PluginContribution = Field(default_factory=PluginContribution)
|
||||
backend: PluginBackend = Field(default_factory=PluginBackend)
|
||||
|
||||
|
||||
class PluginStatus(str, Enum):
|
||||
installed = "installed"
|
||||
disabled = "disabled"
|
||||
starting = "starting"
|
||||
ready = "ready"
|
||||
error = "error"
|
||||
dependency_missing = "dependency_missing"
|
||||
permission_required = "permission_required"
|
||||
|
||||
|
||||
class Plugin(Contract):
|
||||
manifest: PluginManifest
|
||||
status: PluginStatus
|
||||
enabled: bool = False
|
||||
error_message: str | None = None
|
||||
|
||||
|
||||
class PluginListResponse(Contract):
|
||||
items: list[Plugin] = Field(default_factory=list)
|
||||
|
||||
|
||||
# Providers
|
||||
class ProviderType(str, Enum):
|
||||
openai_responses = "openai_responses"
|
||||
openai_chat = "openai_chat"
|
||||
openai_compatible = "openai_compatible"
|
||||
anthropic_messages = "anthropic_messages"
|
||||
ollama = "ollama"
|
||||
|
||||
|
||||
class ProviderConfig(Contract):
|
||||
provider_id: str
|
||||
provider_type: ProviderType
|
||||
name: str
|
||||
base_url: str | None = None
|
||||
default_model: str | None = None
|
||||
credential_id: str | None = None
|
||||
enabled: bool = True
|
||||
capabilities: list[ModelCapability] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ProviderCreateRequest(Contract):
|
||||
provider_type: ProviderType
|
||||
name: str
|
||||
base_url: str | None = None
|
||||
default_model: str | None = None
|
||||
credential_id: str | None = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class ProviderUpdateRequest(Contract):
|
||||
name: str | None = None
|
||||
base_url: str | None = None
|
||||
default_model: str | None = None
|
||||
credential_id: str | None = None
|
||||
enabled: bool | None = None
|
||||
|
||||
|
||||
class ProviderListResponse(Contract):
|
||||
items: list[ProviderConfig] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModelInfo(Contract):
|
||||
model: str
|
||||
display_name: str
|
||||
capabilities: list[ModelCapability] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ProviderModelsResponse(Contract):
|
||||
provider_id: str
|
||||
items: list[ModelInfo] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ProviderTestRequest(Contract):
|
||||
provider_id: str
|
||||
model: str | None = None
|
||||
credential_context_id: str | None = None
|
||||
|
||||
|
||||
class ProviderTestResponse(Contract):
|
||||
provider_id: str
|
||||
success: bool
|
||||
latency_ms: int | None = None
|
||||
message: str
|
||||
|
||||
|
||||
# Tasks, media and index
|
||||
class TaskStatus(str, Enum):
|
||||
todo = "todo"
|
||||
in_progress = "in_progress"
|
||||
done = "done"
|
||||
cancelled = "cancelled"
|
||||
|
||||
|
||||
class Task(Contract):
|
||||
task_id: str
|
||||
title: str
|
||||
description: str = ""
|
||||
status: TaskStatus = TaskStatus.todo
|
||||
note_id: str | None = None
|
||||
due_at: datetime | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class TaskCreateRequest(Contract):
|
||||
title: str = Field(min_length=1)
|
||||
description: str = ""
|
||||
note_id: str | None = None
|
||||
due_at: datetime | None = None
|
||||
|
||||
|
||||
class TaskUpdateRequest(Contract):
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
status: TaskStatus | None = None
|
||||
note_id: str | None = None
|
||||
due_at: datetime | None = None
|
||||
|
||||
|
||||
class TaskListResponse(Contract):
|
||||
items: list[Task] = Field(default_factory=list)
|
||||
page: PageMeta = Field(default_factory=PageMeta)
|
||||
|
||||
|
||||
class TranscriptionRequest(Contract):
|
||||
attachment_id: str
|
||||
language: str | None = None
|
||||
diarization: bool = False
|
||||
|
||||
|
||||
class TranscriptionJob(Contract):
|
||||
job_id: str
|
||||
attachment_id: str
|
||||
status: Literal["queued", "processing", "completed", "failed"]
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class IndexStatus(Contract):
|
||||
status: Literal["idle", "queued", "running", "failed"] = "idle"
|
||||
pending_jobs: int = 0
|
||||
active_job_id: str | None = None
|
||||
last_completed_at: datetime | None = None
|
||||
error_message: str | None = None
|
||||
|
||||
|
||||
class IndexRebuildRequest(Contract):
|
||||
scope: Literal["all", "notes", "vectors"] = "all"
|
||||
note_ids: list[str] = Field(default_factory=list)
|
||||
force: bool = False
|
||||
|
||||
|
||||
class IndexJob(Contract):
|
||||
job_id: str
|
||||
status: Literal["queued", "running", "completed", "failed"]
|
||||
scope: Literal["all", "notes", "vectors"]
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,58 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHttpException
|
||||
|
||||
from app.contracts import ErrorDetail, ErrorResponse
|
||||
|
||||
|
||||
class ApiError(Exception):
|
||||
def __init__(
|
||||
self,
|
||||
status_code: int,
|
||||
code: str,
|
||||
message: str,
|
||||
details: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
self.status_code = status_code
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details or {}
|
||||
|
||||
|
||||
async def api_error_handler(_: Request, exc: ApiError) -> JSONResponse:
|
||||
body = ErrorResponse(
|
||||
error=ErrorDetail(code=exc.code, message=exc.message, details=exc.details)
|
||||
)
|
||||
return JSONResponse(status_code=exc.status_code, content=jsonable_encoder(body))
|
||||
|
||||
|
||||
async def validation_error_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
body = ErrorResponse(
|
||||
error=ErrorDetail(
|
||||
code="VALIDATION_ERROR",
|
||||
message="Request validation failed.",
|
||||
details={"errors": exc.errors()},
|
||||
)
|
||||
)
|
||||
return JSONResponse(status_code=422, content=jsonable_encoder(body))
|
||||
|
||||
|
||||
async def http_error_handler(_: Request, exc: StarletteHttpException) -> JSONResponse:
|
||||
code = "RESOURCE_NOT_FOUND" if exc.status_code == 404 else "HTTP_ERROR"
|
||||
body = ErrorResponse(
|
||||
error=ErrorDetail(code=code, message=str(exc.detail), details={})
|
||||
)
|
||||
return JSONResponse(status_code=exc.status_code, content=jsonable_encoder(body))
|
||||
|
||||
|
||||
def not_implemented(resource: str) -> None:
|
||||
raise ApiError(
|
||||
status_code=501,
|
||||
code="NOT_IMPLEMENTED",
|
||||
message=f"{resource} contract is available, but its business service is not implemented.",
|
||||
details={"resource": resource},
|
||||
)
|
||||
@@ -1,7 +1,11 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.exceptions import HTTPException as StarletteHttpException
|
||||
|
||||
from app.config import get_settings
|
||||
from app.errors import ApiError, api_error_handler, http_error_handler, validation_error_handler
|
||||
from app.routes import router as api_router
|
||||
from app.schemas import HealthResponse, ServiceStatusResponse
|
||||
|
||||
settings = get_settings()
|
||||
@@ -20,6 +24,11 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.add_exception_handler(ApiError, api_error_handler)
|
||||
app.add_exception_handler(RequestValidationError, validation_error_handler)
|
||||
app.add_exception_handler(StarletteHttpException, http_error_handler)
|
||||
app.include_router(api_router)
|
||||
|
||||
|
||||
@app.get("/health", response_model=HealthResponse, tags=["System"])
|
||||
async def health() -> HealthResponse:
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app.contracts import (
|
||||
AgentEvent,
|
||||
AgentEventType,
|
||||
AgentRun,
|
||||
AgentRunCreateRequest,
|
||||
AgentRunListResponse,
|
||||
ChatRequest,
|
||||
ErrorResponse,
|
||||
ExtensionInstallRequest,
|
||||
IndexJob,
|
||||
IndexRebuildRequest,
|
||||
IndexStatus,
|
||||
ModelEvent,
|
||||
ModelEventType,
|
||||
Note,
|
||||
NoteCreateRequest,
|
||||
NoteListResponse,
|
||||
NoteMoveRequest,
|
||||
NoteUpdateRequest,
|
||||
OperationResponse,
|
||||
PageMeta,
|
||||
PermissionDecisionRequest,
|
||||
Plugin,
|
||||
PluginListResponse,
|
||||
ProviderConfig,
|
||||
ProviderCreateRequest,
|
||||
ProviderListResponse,
|
||||
ProviderModelsResponse,
|
||||
ProviderTestRequest,
|
||||
ProviderTestResponse,
|
||||
ProviderUpdateRequest,
|
||||
SearchRequest,
|
||||
SearchResponse,
|
||||
Skill,
|
||||
SkillListResponse,
|
||||
Task,
|
||||
TaskCreateRequest,
|
||||
TaskListResponse,
|
||||
TaskUpdateRequest,
|
||||
ToolListResponse,
|
||||
TranscriptionJob,
|
||||
TranscriptionRequest,
|
||||
)
|
||||
from app.errors import not_implemented
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
not_implemented_response = {501: {"model": ErrorResponse, "description": "业务服务尚未实现"}}
|
||||
|
||||
|
||||
def utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def as_sse(event: str, payload: str) -> str:
|
||||
return f"event: {event}\ndata: {payload}\n\n"
|
||||
|
||||
|
||||
# Notes
|
||||
@router.get("/notes", response_model=NoteListResponse, tags=["Notes"])
|
||||
async def list_notes(
|
||||
limit: int = Query(default=50, ge=1, le=100),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
folder: str | None = None,
|
||||
tag: str | None = None,
|
||||
) -> NoteListResponse:
|
||||
return NoteListResponse(page=PageMeta(limit=limit, offset=offset))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/notes", response_model=Note, responses=not_implemented_response, tags=["Notes"]
|
||||
)
|
||||
async def create_note(_: NoteCreateRequest) -> Note:
|
||||
not_implemented("notes.create")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/notes/{note_id}", response_model=Note, responses=not_implemented_response, tags=["Notes"]
|
||||
)
|
||||
async def get_note(note_id: str) -> Note:
|
||||
not_implemented(f"notes.read:{note_id}")
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/notes/{note_id}", response_model=Note, responses=not_implemented_response, tags=["Notes"]
|
||||
)
|
||||
async def update_note(note_id: str, _: NoteUpdateRequest) -> Note:
|
||||
not_implemented(f"notes.update:{note_id}")
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/notes/{note_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Notes"],
|
||||
)
|
||||
async def delete_note(note_id: str) -> OperationResponse:
|
||||
not_implemented(f"notes.delete:{note_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/notes/{note_id}/move", response_model=Note, responses=not_implemented_response, tags=["Notes"]
|
||||
)
|
||||
async def move_note(note_id: str, _: NoteMoveRequest) -> Note:
|
||||
not_implemented(f"notes.move:{note_id}")
|
||||
|
||||
|
||||
# Retrieval and chat
|
||||
@router.post("/search", response_model=SearchResponse, tags=["Search"])
|
||||
async def search_notes(request: SearchRequest) -> SearchResponse:
|
||||
return SearchResponse(
|
||||
query=request.query,
|
||||
mode=request.mode,
|
||||
page=PageMeta(limit=request.limit, offset=request.offset),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/chat",
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
"description": "ModelEvent Server-Sent Events stream",
|
||||
"content": {"text/event-stream": {}},
|
||||
}
|
||||
},
|
||||
tags=["Chat"],
|
||||
)
|
||||
async def chat(_: ChatRequest) -> StreamingResponse:
|
||||
async def stream() -> AsyncIterator[str]:
|
||||
error = ModelEvent(
|
||||
event=ModelEventType.error,
|
||||
data={"code": "NOT_IMPLEMENTED", "message": "Chat runtime is not implemented."},
|
||||
timestamp=utc_now(),
|
||||
)
|
||||
done = ModelEvent(event=ModelEventType.done, sequence=1, timestamp=utc_now())
|
||||
yield as_sse(error.event.value, error.model_dump_json())
|
||||
yield as_sse(done.event.value, done.model_dump_json())
|
||||
|
||||
return StreamingResponse(stream(), media_type="text/event-stream")
|
||||
|
||||
|
||||
# Agent
|
||||
@router.get("/agent/runs", response_model=AgentRunListResponse, tags=["Agent"])
|
||||
async def list_agent_runs(
|
||||
limit: int = Query(default=50, ge=1, le=100), offset: int = Query(default=0, ge=0)
|
||||
) -> AgentRunListResponse:
|
||||
return AgentRunListResponse(page=PageMeta(limit=limit, offset=offset))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/agent/runs",
|
||||
response_model=AgentRun,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def create_agent_run(_: AgentRunCreateRequest) -> AgentRun:
|
||||
not_implemented("agent.runs.create")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent/runs/{run_id}",
|
||||
response_model=AgentRun,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def get_agent_run(run_id: str) -> AgentRun:
|
||||
not_implemented(f"agent.runs.read:{run_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/agent/runs/{run_id}/cancel",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def cancel_agent_run(run_id: str) -> OperationResponse:
|
||||
not_implemented(f"agent.runs.cancel:{run_id}")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent/runs/{run_id}/events",
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
"description": "AgentEvent Server-Sent Events stream",
|
||||
"content": {"text/event-stream": {}},
|
||||
}
|
||||
},
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def agent_events(run_id: str) -> StreamingResponse:
|
||||
async def stream() -> AsyncIterator[str]:
|
||||
event = AgentEvent(
|
||||
event=AgentEventType.run_failed,
|
||||
run_id=run_id,
|
||||
sequence=0,
|
||||
data={"code": "NOT_IMPLEMENTED", "message": "Agent runtime is not implemented."},
|
||||
timestamp=utc_now(),
|
||||
)
|
||||
yield as_sse(event.event.value, event.model_dump_json())
|
||||
|
||||
return StreamingResponse(stream(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/agent/runs/{run_id}/permissions/{request_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def decide_agent_permission(
|
||||
run_id: str, request_id: str, _: PermissionDecisionRequest
|
||||
) -> OperationResponse:
|
||||
not_implemented(f"agent.permissions:{run_id}:{request_id}")
|
||||
|
||||
|
||||
@router.get("/tools", response_model=ToolListResponse, tags=["Agent"])
|
||||
async def list_tools() -> ToolListResponse:
|
||||
return ToolListResponse()
|
||||
|
||||
|
||||
# Skills
|
||||
@router.get("/skills", response_model=SkillListResponse, tags=["Skills"])
|
||||
async def list_skills() -> SkillListResponse:
|
||||
return SkillListResponse()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/skills/{skill_id}", response_model=Skill, responses=not_implemented_response, tags=["Skills"]
|
||||
)
|
||||
async def get_skill(skill_id: str) -> Skill:
|
||||
not_implemented(f"skills.read:{skill_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/skills/install",
|
||||
response_model=Skill,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def install_skill(_: ExtensionInstallRequest) -> Skill:
|
||||
not_implemented("skills.install")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/skills/{skill_id}/enable",
|
||||
response_model=Skill,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def enable_skill(skill_id: str) -> Skill:
|
||||
not_implemented(f"skills.enable:{skill_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/skills/{skill_id}/disable",
|
||||
response_model=Skill,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def disable_skill(skill_id: str) -> Skill:
|
||||
not_implemented(f"skills.disable:{skill_id}")
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/skills/{skill_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def uninstall_skill(skill_id: str) -> OperationResponse:
|
||||
not_implemented(f"skills.uninstall:{skill_id}")
|
||||
|
||||
|
||||
# Plugins
|
||||
@router.get("/plugins", response_model=PluginListResponse, tags=["Plugins"])
|
||||
async def list_plugins() -> PluginListResponse:
|
||||
return PluginListResponse()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/plugins/{plugin_id}",
|
||||
response_model=Plugin,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def get_plugin(plugin_id: str) -> Plugin:
|
||||
not_implemented(f"plugins.read:{plugin_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/plugins/install",
|
||||
response_model=Plugin,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def install_plugin(_: ExtensionInstallRequest) -> Plugin:
|
||||
not_implemented("plugins.install")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/plugins/{plugin_id}/enable",
|
||||
response_model=Plugin,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def enable_plugin(plugin_id: str) -> Plugin:
|
||||
not_implemented(f"plugins.enable:{plugin_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/plugins/{plugin_id}/disable",
|
||||
response_model=Plugin,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def disable_plugin(plugin_id: str) -> Plugin:
|
||||
not_implemented(f"plugins.disable:{plugin_id}")
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/plugins/{plugin_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def uninstall_plugin(plugin_id: str) -> OperationResponse:
|
||||
not_implemented(f"plugins.uninstall:{plugin_id}")
|
||||
|
||||
|
||||
# Providers
|
||||
@router.get("/providers", response_model=ProviderListResponse, tags=["Providers"])
|
||||
async def list_providers() -> ProviderListResponse:
|
||||
return ProviderListResponse()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/providers/{provider_id}",
|
||||
response_model=ProviderConfig,
|
||||
responses=not_implemented_response,
|
||||
tags=["Providers"],
|
||||
)
|
||||
async def get_provider(provider_id: str) -> ProviderConfig:
|
||||
not_implemented(f"providers.read:{provider_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/providers",
|
||||
response_model=ProviderConfig,
|
||||
responses=not_implemented_response,
|
||||
tags=["Providers"],
|
||||
)
|
||||
async def create_provider(_: ProviderCreateRequest) -> ProviderConfig:
|
||||
not_implemented("providers.create")
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/providers/{provider_id}",
|
||||
response_model=ProviderConfig,
|
||||
responses=not_implemented_response,
|
||||
tags=["Providers"],
|
||||
)
|
||||
async def update_provider(provider_id: str, _: ProviderUpdateRequest) -> ProviderConfig:
|
||||
not_implemented(f"providers.update:{provider_id}")
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/providers/{provider_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Providers"],
|
||||
)
|
||||
async def delete_provider(provider_id: str) -> OperationResponse:
|
||||
not_implemented(f"providers.delete:{provider_id}")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/providers/{provider_id}/models",
|
||||
response_model=ProviderModelsResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Providers"],
|
||||
)
|
||||
async def list_provider_models(provider_id: str) -> ProviderModelsResponse:
|
||||
not_implemented(f"providers.models:{provider_id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/providers/test",
|
||||
response_model=ProviderTestResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Providers"],
|
||||
)
|
||||
async def test_provider(_: ProviderTestRequest) -> ProviderTestResponse:
|
||||
not_implemented("providers.test")
|
||||
|
||||
|
||||
# Tasks
|
||||
@router.get("/tasks", response_model=TaskListResponse, tags=["Tasks"])
|
||||
async def list_tasks(
|
||||
limit: int = Query(default=50, ge=1, le=100), offset: int = Query(default=0, ge=0)
|
||||
) -> TaskListResponse:
|
||||
return TaskListResponse(page=PageMeta(limit=limit, offset=offset))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tasks", response_model=Task, responses=not_implemented_response, tags=["Tasks"]
|
||||
)
|
||||
async def create_task(_: TaskCreateRequest) -> Task:
|
||||
not_implemented("tasks.create")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/tasks/{task_id}", response_model=Task, responses=not_implemented_response, tags=["Tasks"]
|
||||
)
|
||||
async def get_task(task_id: str) -> Task:
|
||||
not_implemented(f"tasks.read:{task_id}")
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/tasks/{task_id}", response_model=Task, responses=not_implemented_response, tags=["Tasks"]
|
||||
)
|
||||
async def update_task(task_id: str, _: TaskUpdateRequest) -> Task:
|
||||
not_implemented(f"tasks.update:{task_id}")
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/tasks/{task_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Tasks"],
|
||||
)
|
||||
async def delete_task(task_id: str) -> OperationResponse:
|
||||
not_implemented(f"tasks.delete:{task_id}")
|
||||
|
||||
|
||||
# Media and index
|
||||
@router.post(
|
||||
"/media/transcriptions",
|
||||
response_model=TranscriptionJob,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Media"],
|
||||
)
|
||||
async def create_transcription(_: TranscriptionRequest) -> TranscriptionJob:
|
||||
not_implemented("media.transcriptions.create")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/media/transcriptions/{job_id}",
|
||||
response_model=TranscriptionJob,
|
||||
responses=not_implemented_response,
|
||||
tags=["Media"],
|
||||
)
|
||||
async def get_transcription(job_id: str) -> TranscriptionJob:
|
||||
not_implemented(f"media.transcriptions.read:{job_id}")
|
||||
|
||||
|
||||
@router.get("/index/status", response_model=IndexStatus, tags=["Index"])
|
||||
async def get_index_status() -> IndexStatus:
|
||||
return IndexStatus()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/index/rebuild",
|
||||
response_model=IndexJob,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Index"],
|
||||
)
|
||||
async def rebuild_index(_: IndexRebuildRequest) -> IndexJob:
|
||||
not_implemented("index.rebuild")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/index/jobs/{job_id}",
|
||||
response_model=IndexJob,
|
||||
responses=not_implemented_response,
|
||||
tags=["Index"],
|
||||
)
|
||||
async def get_index_job(job_id: str) -> IndexJob:
|
||||
not_implemented(f"index.jobs.read:{job_id}")
|
||||
Reference in New Issue
Block a user