nanoscrypt 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nanoscrypt/__init__.py +1 -0
- nanoscrypt/api/__init__.py +1 -0
- nanoscrypt/api/app.py +56 -0
- nanoscrypt/api/dependencies.py +92 -0
- nanoscrypt/api/routers/__init__.py +1 -0
- nanoscrypt/api/routers/agents.py +94 -0
- nanoscrypt/api/routers/approval.py +66 -0
- nanoscrypt/api/routers/audit.py +67 -0
- nanoscrypt/api/routers/health.py +12 -0
- nanoscrypt/api/routers/sessions.py +24 -0
- nanoscrypt/api/routers/tasks.py +47 -0
- nanoscrypt/api/routers/tools.py +55 -0
- nanoscrypt/api/schemas.py +99 -0
- nanoscrypt/cli/__init__.py +1 -0
- nanoscrypt/cli/commands/__init__.py +1 -0
- nanoscrypt/cli/commands/agents.py +118 -0
- nanoscrypt/cli/commands/init.py +43 -0
- nanoscrypt/cli/commands/run.py +572 -0
- nanoscrypt/cli/commands/serve.py +14 -0
- nanoscrypt/cli/commands/tools.py +92 -0
- nanoscrypt/cli/main.py +30 -0
- nanoscrypt/config/__init__.py +1 -0
- nanoscrypt/config/settings.py +123 -0
- nanoscrypt/core/__init__.py +1 -0
- nanoscrypt/core/approval.py +152 -0
- nanoscrypt/core/audit.py +61 -0
- nanoscrypt/core/code_agent.py +406 -0
- nanoscrypt/core/command_handlers.py +219 -0
- nanoscrypt/core/command_router.py +22 -0
- nanoscrypt/core/compressor.py +91 -0
- nanoscrypt/core/context.py +229 -0
- nanoscrypt/core/events.py +80 -0
- nanoscrypt/core/generator.py +61 -0
- nanoscrypt/core/guardrails.py +206 -0
- nanoscrypt/core/harness.py +120 -0
- nanoscrypt/core/hooks.py +74 -0
- nanoscrypt/core/loop.py +76 -0
- nanoscrypt/core/memmachine_engine.py +78 -0
- nanoscrypt/core/memory.py +270 -0
- nanoscrypt/core/orchestrator.py +1019 -0
- nanoscrypt/core/pipeline.py +119 -0
- nanoscrypt/core/planner.py +38 -0
- nanoscrypt/core/postprocessor.py +442 -0
- nanoscrypt/core/registry.py +200 -0
- nanoscrypt/core/repair.py +393 -0
- nanoscrypt/core/runtime.py +429 -0
- nanoscrypt/core/validator.py +1009 -0
- nanoscrypt/core/versioning.py +157 -0
- nanoscrypt/llm/__init__.py +1 -0
- nanoscrypt/llm/base.py +22 -0
- nanoscrypt/llm/litellm_provider.py +286 -0
- nanoscrypt/llm/prompts/__init__.py +1 -0
- nanoscrypt/llm/prompts/generator.py +158 -0
- nanoscrypt/llm/prompts/planner.py +35 -0
- nanoscrypt/llm/prompts/repair.py +217 -0
- nanoscrypt/logging.py +29 -0
- nanoscrypt/models/__init__.py +1 -0
- nanoscrypt/models/agent.py +27 -0
- nanoscrypt/models/application.py +46 -0
- nanoscrypt/models/database.py +147 -0
- nanoscrypt/models/permissions.py +19 -0
- nanoscrypt/models/plan.py +39 -0
- nanoscrypt/models/session.py +22 -0
- nanoscrypt/models/tool.py +46 -0
- nanoscrypt/py.typed +1 -0
- nanoscrypt/utils/__init__.py +1 -0
- nanoscrypt/utils/async_runner.py +52 -0
- nanoscrypt/utils/filesystem.py +38 -0
- nanoscrypt/utils/hashing.py +8 -0
- nanoscrypt-0.2.0.dist-info/METADATA +769 -0
- nanoscrypt-0.2.0.dist-info/RECORD +73 -0
- nanoscrypt-0.2.0.dist-info/WHEEL +4 -0
- nanoscrypt-0.2.0.dist-info/entry_points.txt +2 -0
nanoscrypt/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Initialize subpackage
|
nanoscrypt/api/app.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
|
|
6
|
+
from nanoscrypt.api.dependencies import get_registry
|
|
7
|
+
from nanoscrypt.api.routers import (
|
|
8
|
+
agents,
|
|
9
|
+
approval,
|
|
10
|
+
audit,
|
|
11
|
+
health,
|
|
12
|
+
sessions,
|
|
13
|
+
tasks,
|
|
14
|
+
tools,
|
|
15
|
+
)
|
|
16
|
+
from nanoscrypt.logging import setup_logging
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def lifespan(app: FastAPI):
|
|
21
|
+
setup_logging()
|
|
22
|
+
yield
|
|
23
|
+
registry = await get_registry()
|
|
24
|
+
await registry.engine.dispose()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_app() -> FastAPI:
|
|
28
|
+
app = FastAPI(
|
|
29
|
+
title="Nanoscrypt API",
|
|
30
|
+
description="REST API for the standalone Nanoscrypt tool-synthesis framework",
|
|
31
|
+
version="0.2.0",
|
|
32
|
+
lifespan=lifespan,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Enable CORS for frontend integration
|
|
36
|
+
app.add_middleware(
|
|
37
|
+
CORSMiddleware,
|
|
38
|
+
allow_origins=["*"],
|
|
39
|
+
allow_credentials=True,
|
|
40
|
+
allow_methods=["*"],
|
|
41
|
+
allow_headers=["*"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Mount API routers under /api/v1 prefix
|
|
45
|
+
app.include_router(health.router, prefix="/api/v1")
|
|
46
|
+
app.include_router(sessions.router, prefix="/api/v1")
|
|
47
|
+
app.include_router(tasks.router, prefix="/api/v1")
|
|
48
|
+
app.include_router(tools.router, prefix="/api/v1")
|
|
49
|
+
app.include_router(agents.router, prefix="/api/v1")
|
|
50
|
+
app.include_router(approval.router, prefix="/api/v1")
|
|
51
|
+
app.include_router(audit.router, prefix="/api/v1")
|
|
52
|
+
|
|
53
|
+
return app
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
app = create_app()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from nanoscrypt.config.settings import settings
|
|
2
|
+
from nanoscrypt.core.approval import ApprovalGate
|
|
3
|
+
from nanoscrypt.core.audit import AuditLogger
|
|
4
|
+
from nanoscrypt.core.context import ContextBuilder
|
|
5
|
+
from nanoscrypt.core.generator import ToolGenerator
|
|
6
|
+
|
|
7
|
+
# Enterprise imports v0.2.0
|
|
8
|
+
from nanoscrypt.core.hooks import HookManager
|
|
9
|
+
from nanoscrypt.core.memory import LongTermMemory, ShortTermMemory
|
|
10
|
+
from nanoscrypt.core.orchestrator import Orchestrator
|
|
11
|
+
from nanoscrypt.core.planner import Planner
|
|
12
|
+
from nanoscrypt.core.registry import ToolRegistry
|
|
13
|
+
from nanoscrypt.core.repair import RepairLoop
|
|
14
|
+
from nanoscrypt.core.runtime import RuntimeManager
|
|
15
|
+
from nanoscrypt.core.validator import ToolValidator
|
|
16
|
+
from nanoscrypt.core.versioning import VersionManager
|
|
17
|
+
from nanoscrypt.llm.litellm_provider import LiteLLMProvider
|
|
18
|
+
|
|
19
|
+
# We cache registry instance globally to prevent re-opening database connections
|
|
20
|
+
_registry: ToolRegistry | None = None
|
|
21
|
+
_orchestrator: Orchestrator | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_settings():
|
|
25
|
+
return settings
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def get_registry() -> ToolRegistry:
|
|
29
|
+
global _registry
|
|
30
|
+
if _registry is None:
|
|
31
|
+
cfg = get_settings()
|
|
32
|
+
_registry = ToolRegistry(cfg.registry.database_url)
|
|
33
|
+
await _registry.initialize_db()
|
|
34
|
+
return _registry
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def get_orchestrator() -> Orchestrator:
|
|
38
|
+
global _orchestrator
|
|
39
|
+
if _orchestrator is not None:
|
|
40
|
+
return _orchestrator
|
|
41
|
+
|
|
42
|
+
cfg = get_settings()
|
|
43
|
+
|
|
44
|
+
# Initialize LiteLLM wrapper
|
|
45
|
+
llm = LiteLLMProvider(
|
|
46
|
+
default_model=cfg.llm.model,
|
|
47
|
+
temperature=cfg.llm.temperature,
|
|
48
|
+
max_tokens=cfg.llm.max_tokens,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
registry = await get_registry()
|
|
52
|
+
|
|
53
|
+
context_builder = ContextBuilder(workspace_root="./") # Scan workspace root
|
|
54
|
+
planner = Planner(llm=llm)
|
|
55
|
+
generator = ToolGenerator(llm=llm)
|
|
56
|
+
validator = ToolValidator(llm=llm)
|
|
57
|
+
runtime_manager = RuntimeManager(
|
|
58
|
+
workspace_root=cfg.runtime.workspace_root,
|
|
59
|
+
timeout_seconds=cfg.runtime.timeout_seconds,
|
|
60
|
+
)
|
|
61
|
+
version_manager = VersionManager(tools_dir=cfg.registry.tools_dir)
|
|
62
|
+
|
|
63
|
+
repair_loop = RepairLoop(
|
|
64
|
+
llm=llm,
|
|
65
|
+
validator=validator,
|
|
66
|
+
runtime_manager=runtime_manager,
|
|
67
|
+
max_attempts=cfg.resilience.max_repair_attempts,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Instantiate enterprise helpers
|
|
71
|
+
hook_manager = HookManager()
|
|
72
|
+
approval_gate = ApprovalGate()
|
|
73
|
+
audit_logger = AuditLogger(session_factory=registry.session_factory)
|
|
74
|
+
short_term_memory = ShortTermMemory(max_entries=cfg.memory.short_term_max_entries)
|
|
75
|
+
long_term_memory = LongTermMemory(session_factory=registry.session_factory)
|
|
76
|
+
|
|
77
|
+
_orchestrator = Orchestrator(
|
|
78
|
+
context_builder=context_builder,
|
|
79
|
+
planner=planner,
|
|
80
|
+
generator=generator,
|
|
81
|
+
validator=validator,
|
|
82
|
+
runtime_manager=runtime_manager,
|
|
83
|
+
registry=registry,
|
|
84
|
+
version_manager=version_manager,
|
|
85
|
+
repair_loop=repair_loop,
|
|
86
|
+
hook_manager=hook_manager,
|
|
87
|
+
approval_gate=approval_gate,
|
|
88
|
+
audit_logger=audit_logger,
|
|
89
|
+
short_term_memory=short_term_memory,
|
|
90
|
+
long_term_memory=long_term_memory,
|
|
91
|
+
)
|
|
92
|
+
return _orchestrator
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Initialize subpackage
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
2
|
+
from sqlalchemy import select
|
|
3
|
+
|
|
4
|
+
from nanoscrypt.api.dependencies import get_registry
|
|
5
|
+
from nanoscrypt.api.schemas import AgentCreate, AgentPermissionsSchema, AgentResponse
|
|
6
|
+
from nanoscrypt.core.registry import ToolRegistry
|
|
7
|
+
from nanoscrypt.models.database import DBAgentDefinition
|
|
8
|
+
|
|
9
|
+
router = APIRouter(prefix="/agents", tags=["agents"])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.post("", response_model=AgentResponse)
|
|
13
|
+
async def create_agent(
|
|
14
|
+
payload: AgentCreate, registry: ToolRegistry = Depends(get_registry)
|
|
15
|
+
):
|
|
16
|
+
"""Registers a new agent role in the persistent registry database."""
|
|
17
|
+
async with registry.session_factory() as session:
|
|
18
|
+
async with session.begin():
|
|
19
|
+
# Check if name unique
|
|
20
|
+
stmt = select(DBAgentDefinition).where(
|
|
21
|
+
DBAgentDefinition.name == payload.name
|
|
22
|
+
)
|
|
23
|
+
res = await session.execute(stmt)
|
|
24
|
+
existing = res.scalar_one_or_none()
|
|
25
|
+
if existing:
|
|
26
|
+
raise HTTPException(
|
|
27
|
+
status_code=400,
|
|
28
|
+
detail=f"Agent with name '{payload.name}' already exists.",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
agent_db = DBAgentDefinition(
|
|
32
|
+
name=payload.name,
|
|
33
|
+
role=payload.role,
|
|
34
|
+
goal=payload.goal,
|
|
35
|
+
backstory=payload.backstory,
|
|
36
|
+
tools=payload.tools,
|
|
37
|
+
permissions=payload.permissions.model_dump(),
|
|
38
|
+
)
|
|
39
|
+
session.add(agent_db)
|
|
40
|
+
await session.flush()
|
|
41
|
+
|
|
42
|
+
return AgentResponse(
|
|
43
|
+
name=agent_db.name,
|
|
44
|
+
role=agent_db.role,
|
|
45
|
+
goal=agent_db.goal,
|
|
46
|
+
backstory=agent_db.backstory,
|
|
47
|
+
tools=agent_db.tools,
|
|
48
|
+
permissions=AgentPermissionsSchema(**agent_db.permissions),
|
|
49
|
+
created_at=agent_db.created_at,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@router.get("", response_model=list[AgentResponse])
|
|
54
|
+
async def list_agents(registry: ToolRegistry = Depends(get_registry)):
|
|
55
|
+
"""Lists all registered agent configurations."""
|
|
56
|
+
async with registry.session_factory() as session:
|
|
57
|
+
stmt = select(DBAgentDefinition)
|
|
58
|
+
res = await session.execute(stmt)
|
|
59
|
+
agents = res.scalars().all()
|
|
60
|
+
response = []
|
|
61
|
+
for a in agents:
|
|
62
|
+
response.append(
|
|
63
|
+
AgentResponse(
|
|
64
|
+
name=a.name,
|
|
65
|
+
role=a.role,
|
|
66
|
+
goal=a.goal,
|
|
67
|
+
backstory=a.backstory,
|
|
68
|
+
tools=a.tools,
|
|
69
|
+
permissions=AgentPermissionsSchema(**a.permissions),
|
|
70
|
+
created_at=a.created_at,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
return response
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.get("/{name}", response_model=AgentResponse)
|
|
77
|
+
async def get_agent(name: str, registry: ToolRegistry = Depends(get_registry)):
|
|
78
|
+
"""Retrieves details of a specific agent role configuration."""
|
|
79
|
+
async with registry.session_factory() as session:
|
|
80
|
+
stmt = select(DBAgentDefinition).where(DBAgentDefinition.name == name)
|
|
81
|
+
res = await session.execute(stmt)
|
|
82
|
+
a = res.scalar_one_or_none()
|
|
83
|
+
if not a:
|
|
84
|
+
raise HTTPException(status_code=404, detail=f"Agent '{name}' not found.")
|
|
85
|
+
|
|
86
|
+
return AgentResponse(
|
|
87
|
+
name=a.name,
|
|
88
|
+
role=a.role,
|
|
89
|
+
goal=a.goal,
|
|
90
|
+
backstory=a.backstory,
|
|
91
|
+
tools=a.tools,
|
|
92
|
+
permissions=AgentPermissionsSchema(**a.permissions),
|
|
93
|
+
created_at=a.created_at,
|
|
94
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
2
|
+
|
|
3
|
+
from nanoscrypt.api.dependencies import get_orchestrator
|
|
4
|
+
from nanoscrypt.api.schemas import ApprovalRecordResponse, ApprovalResolution
|
|
5
|
+
from nanoscrypt.core.approval import ApprovalStatus
|
|
6
|
+
from nanoscrypt.core.orchestrator import Orchestrator
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/approvals", tags=["approvals"])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("/pending", response_model=list[ApprovalRecordResponse])
|
|
12
|
+
async def list_pending_approvals(
|
|
13
|
+
session_id: str | None = Query(
|
|
14
|
+
None, description="Filter approvals by active session ID"
|
|
15
|
+
),
|
|
16
|
+
orchestrator: Orchestrator = Depends(get_orchestrator),
|
|
17
|
+
):
|
|
18
|
+
"""Lists all active human-in-the-loop pending approval requests."""
|
|
19
|
+
pending = orchestrator.approval_gate.get_pending(session_id)
|
|
20
|
+
response = []
|
|
21
|
+
for r in pending:
|
|
22
|
+
response.append(
|
|
23
|
+
ApprovalRecordResponse(
|
|
24
|
+
id=r.id,
|
|
25
|
+
session_id=r.session_id,
|
|
26
|
+
approval_type=r.approval_type.value,
|
|
27
|
+
description=r.description,
|
|
28
|
+
risk_level=r.risk_level,
|
|
29
|
+
resource_details=r.resource_details,
|
|
30
|
+
agent_name=r.agent_name,
|
|
31
|
+
status=r.status.value,
|
|
32
|
+
timestamp=r.timestamp,
|
|
33
|
+
resolved_at=r.resolved_at,
|
|
34
|
+
reason=r.reason,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
return response
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.post("/{request_id}/resolve", response_model=dict[str, str])
|
|
41
|
+
async def resolve_approval_request(
|
|
42
|
+
request_id: str,
|
|
43
|
+
payload: ApprovalResolution,
|
|
44
|
+
orchestrator: Orchestrator = Depends(get_orchestrator),
|
|
45
|
+
):
|
|
46
|
+
"""Approves or denies a pending approval request."""
|
|
47
|
+
if request_id not in orchestrator.approval_gate.pending_requests:
|
|
48
|
+
raise HTTPException(
|
|
49
|
+
status_code=404, detail="Pending approval request not found."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
status = ApprovalStatus.APPROVED if payload.approved else ApprovalStatus.DENIED
|
|
53
|
+
success = orchestrator.approval_gate.resolve_request(
|
|
54
|
+
request_id=request_id,
|
|
55
|
+
status=status,
|
|
56
|
+
reason=payload.reason or "Resolved via API call.",
|
|
57
|
+
)
|
|
58
|
+
if not success:
|
|
59
|
+
raise HTTPException(
|
|
60
|
+
status_code=500, detail="Failed to resolve approval request."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
"status": "success",
|
|
65
|
+
"message": f"Request has been resolved to '{status.value}'.",
|
|
66
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, Query
|
|
4
|
+
from sqlalchemy import select
|
|
5
|
+
|
|
6
|
+
from nanoscrypt.api.dependencies import get_registry
|
|
7
|
+
from nanoscrypt.api.schemas import AuditLogResponse
|
|
8
|
+
from nanoscrypt.core.registry import ToolRegistry
|
|
9
|
+
from nanoscrypt.models.database import DBAuditLog
|
|
10
|
+
|
|
11
|
+
router = APIRouter(prefix="/audit", tags=["audit"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("", response_model=list[AuditLogResponse])
|
|
15
|
+
async def query_audit_logs(
|
|
16
|
+
session_id: str | None = Query(None, description="Filter logs by session ID"),
|
|
17
|
+
event_type: str | None = Query(None, description="Filter logs by event type"),
|
|
18
|
+
limit: int = Query(50, description="Maximum number of log entries to retrieve"),
|
|
19
|
+
registry: ToolRegistry = Depends(get_registry),
|
|
20
|
+
):
|
|
21
|
+
"""Retrieves immutable audit logs from database with optional filters."""
|
|
22
|
+
async with registry.session_factory() as session:
|
|
23
|
+
stmt = select(DBAuditLog)
|
|
24
|
+
|
|
25
|
+
# Apply filters
|
|
26
|
+
if session_id:
|
|
27
|
+
stmt = stmt.where(DBAuditLog.session_id == session_id)
|
|
28
|
+
if event_type:
|
|
29
|
+
stmt = stmt.where(DBAuditLog.event_type == event_type)
|
|
30
|
+
|
|
31
|
+
stmt = stmt.order_by(DBAuditLog.timestamp.desc()).limit(limit)
|
|
32
|
+
res = await session.execute(stmt)
|
|
33
|
+
logs = res.scalars().all()
|
|
34
|
+
|
|
35
|
+
response = []
|
|
36
|
+
for l in logs:
|
|
37
|
+
response.append(
|
|
38
|
+
AuditLogResponse(
|
|
39
|
+
id=l.id,
|
|
40
|
+
event_type=l.event_type,
|
|
41
|
+
session_id=l.session_id,
|
|
42
|
+
agent_name=l.agent_name,
|
|
43
|
+
details=l.details,
|
|
44
|
+
cost=l.cost,
|
|
45
|
+
token_usage=l.token_usage,
|
|
46
|
+
timestamp=l.timestamp,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
return response
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.get("/summary", response_model=dict[str, Any])
|
|
53
|
+
async def get_audit_summary(registry: ToolRegistry = Depends(get_registry)):
|
|
54
|
+
"""Aggregates costs and token usage totals across all runs."""
|
|
55
|
+
async with registry.session_factory() as session:
|
|
56
|
+
stmt = select(DBAuditLog.cost, DBAuditLog.token_usage)
|
|
57
|
+
res = await session.execute(stmt)
|
|
58
|
+
records = res.all()
|
|
59
|
+
|
|
60
|
+
total_cost = sum(r[0] for r in records)
|
|
61
|
+
total_tokens = sum(r[1] for r in records)
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
"total_runs": len(records),
|
|
65
|
+
"total_estimated_cost_usd": float(total_cost),
|
|
66
|
+
"total_tokens_consumed": int(total_tokens),
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
|
|
3
|
+
router = APIRouter(prefix="/health", tags=["system"])
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@router.get("")
|
|
7
|
+
def health_check():
|
|
8
|
+
"""
|
|
9
|
+
Check the health status of the API.
|
|
10
|
+
Returns a simple JSON response indicating the service is running.
|
|
11
|
+
"""
|
|
12
|
+
return {"status": "ok", "service": "nanoscrypt-api"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends
|
|
5
|
+
|
|
6
|
+
from nanoscrypt.api.dependencies import get_settings
|
|
7
|
+
from nanoscrypt.api.schemas import SessionCreate, SessionResponse
|
|
8
|
+
from nanoscrypt.config.settings import Settings
|
|
9
|
+
|
|
10
|
+
router = APIRouter(prefix="/sessions", tags=["sessions"])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@router.post("", response_model=SessionResponse)
|
|
14
|
+
def create_session(payload: SessionCreate, cfg: Settings = Depends(get_settings)):
|
|
15
|
+
"""
|
|
16
|
+
Create a new execution session.
|
|
17
|
+
Allocates a workspace path based on the session ID.
|
|
18
|
+
"""
|
|
19
|
+
session_id = payload.session_id or f"sess_{uuid.uuid4().hex[:8]}"
|
|
20
|
+
workspace_path = f"{cfg.runtime.workspace_root}/{session_id}"
|
|
21
|
+
|
|
22
|
+
return SessionResponse(
|
|
23
|
+
id=session_id, workspace_path=workspace_path, created_at=datetime.now(timezone.utc)
|
|
24
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
2
|
+
|
|
3
|
+
from nanoscrypt.api.dependencies import get_orchestrator
|
|
4
|
+
from nanoscrypt.api.schemas import TaskResponse, TaskSubmit
|
|
5
|
+
from nanoscrypt.core.orchestrator import Orchestrator
|
|
6
|
+
from nanoscrypt.models.session import Session
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/tasks", tags=["tasks"])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.post("", response_model=TaskResponse)
|
|
12
|
+
async def submit_task(
|
|
13
|
+
payload: TaskSubmit,
|
|
14
|
+
session_id: str = Query(
|
|
15
|
+
..., description="Active session ID to bound the execution workspace"
|
|
16
|
+
),
|
|
17
|
+
orchestrator: Orchestrator = Depends(get_orchestrator),
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Submit a task to be executed by the orchestrator.
|
|
21
|
+
Requires a session_id to bound the execution workspace.
|
|
22
|
+
"""
|
|
23
|
+
# Initialize ephemeral session tracking
|
|
24
|
+
session = Session(id=session_id, workspace_path=f"./workspaces/{session_id}")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
result = await orchestrator.execute_task(
|
|
28
|
+
user_prompt=payload.prompt, session=session
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Map execution outcome to target HTTP response format
|
|
32
|
+
return TaskResponse(
|
|
33
|
+
status=result.get("status", "error"),
|
|
34
|
+
action_taken=result.get("action_taken", "none"),
|
|
35
|
+
tool_name=result.get("tool_name"),
|
|
36
|
+
version=result.get("version"),
|
|
37
|
+
output=result.get("output") or result.get("response"),
|
|
38
|
+
error=result.get("error") or result.get("message"),
|
|
39
|
+
runtime_ms=result.get("runtime_ms"),
|
|
40
|
+
)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
import logging
|
|
43
|
+
|
|
44
|
+
logging.error(f"Task execution failed inside engine: {e!s}")
|
|
45
|
+
raise HTTPException(
|
|
46
|
+
status_code=500, detail="An internal error occurred during task execution."
|
|
47
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
2
|
+
|
|
3
|
+
from nanoscrypt.api.dependencies import get_registry
|
|
4
|
+
from nanoscrypt.api.schemas import ToolResponse
|
|
5
|
+
from nanoscrypt.core.registry import ToolRegistry
|
|
6
|
+
|
|
7
|
+
router = APIRouter(prefix="/tools", tags=["tools"])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@router.get("", response_model=list[ToolResponse])
|
|
11
|
+
async def list_tools(query: str = "", registry: ToolRegistry = Depends(get_registry)):
|
|
12
|
+
"""
|
|
13
|
+
List or search available tools in the registry.
|
|
14
|
+
Optional query parameter filters tools by name or description.
|
|
15
|
+
"""
|
|
16
|
+
tools = await registry.search(query)
|
|
17
|
+
response = []
|
|
18
|
+
for t in tools:
|
|
19
|
+
response.append(
|
|
20
|
+
ToolResponse(
|
|
21
|
+
name=t.name,
|
|
22
|
+
purpose=t.purpose,
|
|
23
|
+
language=t.language,
|
|
24
|
+
current_version=t.current_version,
|
|
25
|
+
success_rate=t.success_rate,
|
|
26
|
+
usage_count=t.usage_count,
|
|
27
|
+
status=t.status,
|
|
28
|
+
created_at=t.created_at,
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
return response
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.get("/{name}", response_model=ToolResponse)
|
|
35
|
+
async def get_tool(name: str, registry: ToolRegistry = Depends(get_registry)):
|
|
36
|
+
"""
|
|
37
|
+
Get a specific tool's details by its name.
|
|
38
|
+
Returns a 404 error if the tool is not found.
|
|
39
|
+
"""
|
|
40
|
+
t = await registry.get(name)
|
|
41
|
+
if not t:
|
|
42
|
+
raise HTTPException(
|
|
43
|
+
status_code=404, detail=f"Tool '{name}' not found or inactive in registry."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return ToolResponse(
|
|
47
|
+
name=t.name,
|
|
48
|
+
purpose=t.purpose,
|
|
49
|
+
language=t.language,
|
|
50
|
+
current_version=t.current_version,
|
|
51
|
+
success_rate=t.success_rate,
|
|
52
|
+
usage_count=t.usage_count,
|
|
53
|
+
status=t.status,
|
|
54
|
+
created_at=t.created_at,
|
|
55
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SessionCreate(BaseModel):
|
|
8
|
+
session_id: str | None = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SessionResponse(BaseModel):
|
|
12
|
+
id: str
|
|
13
|
+
workspace_path: str
|
|
14
|
+
created_at: datetime
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TaskSubmit(BaseModel):
|
|
18
|
+
prompt: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaskResponse(BaseModel):
|
|
22
|
+
status: str
|
|
23
|
+
action_taken: str
|
|
24
|
+
tool_name: str | None = None
|
|
25
|
+
version: int | None = None
|
|
26
|
+
output: str | None = None
|
|
27
|
+
error: str | None = None
|
|
28
|
+
runtime_ms: int | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ToolResponse(BaseModel):
|
|
32
|
+
name: str
|
|
33
|
+
purpose: str
|
|
34
|
+
language: str
|
|
35
|
+
current_version: int
|
|
36
|
+
success_rate: float
|
|
37
|
+
usage_count: int
|
|
38
|
+
status: str
|
|
39
|
+
created_at: datetime
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# --- ENTERPRISE SCHEMAS V0.2.0 ---
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AgentPermissionsSchema(BaseModel):
|
|
46
|
+
file_system: str = "deny"
|
|
47
|
+
network: str = "deny"
|
|
48
|
+
tool_generation: str = "execute"
|
|
49
|
+
tool_execution: str = "execute"
|
|
50
|
+
delegation: bool = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AgentCreate(BaseModel):
|
|
54
|
+
name: str
|
|
55
|
+
role: str
|
|
56
|
+
goal: str
|
|
57
|
+
backstory: str = ""
|
|
58
|
+
tools: list[str] = Field(default_factory=list)
|
|
59
|
+
permissions: AgentPermissionsSchema = Field(default_factory=AgentPermissionsSchema)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AgentResponse(BaseModel):
|
|
63
|
+
name: str
|
|
64
|
+
role: str
|
|
65
|
+
goal: str
|
|
66
|
+
backstory: str
|
|
67
|
+
tools: list[str]
|
|
68
|
+
permissions: AgentPermissionsSchema
|
|
69
|
+
created_at: datetime
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ApprovalRecordResponse(BaseModel):
|
|
73
|
+
id: str
|
|
74
|
+
session_id: str
|
|
75
|
+
approval_type: str
|
|
76
|
+
description: str
|
|
77
|
+
risk_level: str
|
|
78
|
+
resource_details: dict[str, Any]
|
|
79
|
+
agent_name: str
|
|
80
|
+
status: str
|
|
81
|
+
timestamp: datetime
|
|
82
|
+
resolved_at: datetime | None = None
|
|
83
|
+
reason: str | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ApprovalResolution(BaseModel):
|
|
87
|
+
approved: bool
|
|
88
|
+
reason: str | None = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AuditLogResponse(BaseModel):
|
|
92
|
+
id: int
|
|
93
|
+
event_type: str
|
|
94
|
+
session_id: str
|
|
95
|
+
agent_name: str
|
|
96
|
+
details: dict[str, Any]
|
|
97
|
+
cost: float
|
|
98
|
+
token_usage: int
|
|
99
|
+
timestamp: datetime
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Initialize subpackage
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Initialize subpackage
|