Skip to content
Draft
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
19 changes: 19 additions & 0 deletions models/src/agent_control_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@
ValidateControlDataRequest,
ValidateControlDataResponse,
)
from .target import (
AttachTargetControlRequest,
CreateTargetRequest,
CreateTargetResponse,
ListTargetControlsResponse,
ListTargetsResponse,
TargetControlSummary,
TargetSummary,
ToggleTargetControlRequest,
)

__all__ = [
# Health
Expand Down Expand Up @@ -191,4 +201,13 @@
"StatsResponse",
"StatsTotals",
"TimeseriesBucket",
# Target models
"AttachTargetControlRequest",
"CreateTargetRequest",
"CreateTargetResponse",
"ListTargetControlsResponse",
"ListTargetsResponse",
"TargetControlSummary",
"TargetSummary",
"ToggleTargetControlRequest",
]
6 changes: 6 additions & 0 deletions models/src/agent_control_models/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class ErrorCode(StrEnum):
POLICY_NOT_FOUND = "POLICY_NOT_FOUND"
CONTROL_NOT_FOUND = "CONTROL_NOT_FOUND"
EVALUATOR_NOT_FOUND = "EVALUATOR_NOT_FOUND"
TARGET_NOT_FOUND = "TARGET_NOT_FOUND"
TARGET_CONTROL_NOT_FOUND = "TARGET_CONTROL_NOT_FOUND"

# Conflict Errors (3xx pattern)
AGENT_NAME_CONFLICT = "AGENT_NAME_CONFLICT"
Expand All @@ -71,6 +73,7 @@ class ErrorCode(StrEnum):
CONTROL_TEMPLATE_CONFLICT = "CONTROL_TEMPLATE_CONFLICT"
EVALUATOR_IN_USE = "EVALUATOR_IN_USE"
SCHEMA_INCOMPATIBLE = "SCHEMA_INCOMPATIBLE"
TARGET_CONFLICT = "TARGET_CONFLICT"

# Validation Errors (4xx pattern)
VALIDATION_ERROR = "VALIDATION_ERROR"
Expand Down Expand Up @@ -365,6 +368,8 @@ def make_error_type(error_code: ErrorCode) -> str:
ErrorCode.POLICY_NOT_FOUND: "Policy Not Found",
ErrorCode.CONTROL_NOT_FOUND: "Control Not Found",
ErrorCode.EVALUATOR_NOT_FOUND: "Evaluator Not Found",
ErrorCode.TARGET_NOT_FOUND: "Target Not Found",
ErrorCode.TARGET_CONTROL_NOT_FOUND: "Target Control Not Found",
# Conflict errors
ErrorCode.AGENT_NAME_CONFLICT: "Agent Name Already Exists",
ErrorCode.POLICY_NAME_CONFLICT: "Policy Name Already Exists",
Expand All @@ -374,6 +379,7 @@ def make_error_type(error_code: ErrorCode) -> str:
ErrorCode.CONTROL_TEMPLATE_CONFLICT: "Control Template Conflict",
ErrorCode.EVALUATOR_IN_USE: "Evaluator In Use",
ErrorCode.SCHEMA_INCOMPATIBLE: "Schema Incompatible",
ErrorCode.TARGET_CONFLICT: "Target Already Exists",
# Validation errors
ErrorCode.VALIDATION_ERROR: "Validation Error",
ErrorCode.INVALID_CONFIG: "Invalid Configuration",
Expand Down
98 changes: 98 additions & 0 deletions models/src/agent_control_models/target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Pydantic models for target management APIs.

Targets are typed, tenant-scoped, attachable objects. ``target_type`` is an
opaque string supplied by the caller (e.g. ``environment``); the server
treats it as data. The field is named ``target_type`` rather than ``type``
to avoid shadowing Python's builtin and keep greps for the field specific.
"""

from __future__ import annotations

from typing import Any

from pydantic import Field

from .base import BaseModel


class CreateTargetRequest(BaseModel):
"""Request body for creating a new target."""

target_type: str = Field(
...,
min_length=1,
max_length=64,
description="Opaque target kind (e.g. 'environment').",
)
external_id: str = Field(
...,
min_length=1,
max_length=255,
description="Stable caller-supplied identifier for the target.",
)
name: str | None = Field(
default=None,
max_length=255,
description="Optional display name for the target.",
)
data: dict[str, Any] = Field(
default_factory=dict,
description="Optional target metadata payload.",
)


class CreateTargetResponse(BaseModel):
"""Response returned after creating a target."""

target_id: int = Field(..., description="Identifier of the created target row.")


class TargetSummary(BaseModel):
"""Full target record returned from get/list endpoints."""

id: int = Field(..., description="Internal target ID.")
tenant_id: str = Field(..., description="Owning tenant.")
target_type: str = Field(..., description="Opaque target kind.")
external_id: str = Field(..., description="Caller-supplied stable identifier.")
name: str | None = Field(default=None, description="Optional display name.")
data: dict[str, Any] = Field(default_factory=dict, description="Target metadata payload.")
created_at: str = Field(..., description="ISO 8601 timestamp when the target was created.")


class ListTargetsResponse(BaseModel):
"""Response for listing targets."""

targets: list[TargetSummary] = Field(..., description="Targets visible to the current tenant.")


class AttachTargetControlRequest(BaseModel):
"""Optional body for attaching a control to a target."""

enabled: bool = Field(
default=True,
description="Whether the attachment starts enabled. Defaults to true.",
)


class ToggleTargetControlRequest(BaseModel):
"""Body for toggling an existing target-control attachment's enabled flag."""

enabled: bool = Field(..., description="New enabled state for the attachment.")


class TargetControlSummary(BaseModel):
"""A single control attached to a target."""

id: int = Field(..., description="target_controls row identifier.")
control_id: int = Field(..., description="Attached control ID.")
enabled: bool = Field(..., description="Whether the attachment is enabled.")


class ListTargetControlsResponse(BaseModel):
"""Response for listing controls attached to a target."""

target_id: int = Field(..., description="Target whose controls are returned.")
controls: list[TargetControlSummary] = Field(
default_factory=list,
description="Controls attached to the target.",
)
40 changes: 40 additions & 0 deletions sdks/typescript/overlays/method-names.overlay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,46 @@ actions:
x-speakeasy-group: policies
x-speakeasy-name-override: removeControl

- target: $["paths"]["/api/v1/targets"]["get"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: list

- target: $["paths"]["/api/v1/targets"]["post"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: create

- target: $["paths"]["/api/v1/targets/{target_id}"]["get"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: get

- target: $["paths"]["/api/v1/targets/{target_id}"]["delete"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: delete

- target: $["paths"]["/api/v1/targets/{target_id}/controls"]["get"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: listControls

- target: $["paths"]["/api/v1/targets/{target_id}/controls/{control_id}"]["post"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: attachControl

- target: $["paths"]["/api/v1/targets/{target_id}/controls/{control_id}"]["patch"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: toggleControl

- target: $["paths"]["/api/v1/targets/{target_id}/controls/{control_id}"]["delete"]
update:
x-speakeasy-group: targets
x-speakeasy-name-override: detachControl

- target: $["paths"]["/health"]["get"]
update:
x-speakeasy-group: system
Expand Down
Loading
Loading