agentplane-primitives 0.1.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.
Files changed (53) hide show
  1. agentplane_primitives/__init__.py +10 -0
  2. agentplane_primitives/_version.py +3 -0
  3. agentplane_primitives/enums/__init__.py +25 -0
  4. agentplane_primitives/enums/base_enum.py +15 -0
  5. agentplane_primitives/enums/finish_reason.py +17 -0
  6. agentplane_primitives/enums/kind.py +14 -0
  7. agentplane_primitives/enums/link_type.py +12 -0
  8. agentplane_primitives/enums/message_role.py +12 -0
  9. agentplane_primitives/enums/model_source.py +17 -0
  10. agentplane_primitives/enums/state.py +13 -0
  11. agentplane_primitives/enums/status.py +14 -0
  12. agentplane_primitives/models/__init__.py +46 -0
  13. agentplane_primitives/models/base.py +20 -0
  14. agentplane_primitives/models/content_block.py +19 -0
  15. agentplane_primitives/models/envelope.py +94 -0
  16. agentplane_primitives/models/envelope_status.py +28 -0
  17. agentplane_primitives/models/link.py +16 -0
  18. agentplane_primitives/models/llm_input.py +33 -0
  19. agentplane_primitives/models/llm_output.py +43 -0
  20. agentplane_primitives/models/llm_params.py +19 -0
  21. agentplane_primitives/models/message.py +26 -0
  22. agentplane_primitives/models/output_error.py +18 -0
  23. agentplane_primitives/models/reasoning_block.py +12 -0
  24. agentplane_primitives/models/refusal_block.py +12 -0
  25. agentplane_primitives/models/registry.py +15 -0
  26. agentplane_primitives/models/span.py +48 -0
  27. agentplane_primitives/models/text_block.py +12 -0
  28. agentplane_primitives/models/tool_call_block.py +16 -0
  29. agentplane_primitives/models/tool_input.py +15 -0
  30. agentplane_primitives/models/tool_output.py +17 -0
  31. agentplane_primitives/models/trace.py +29 -0
  32. agentplane_primitives/models/trace_context.py +29 -0
  33. agentplane_primitives/models/usage.py +21 -0
  34. agentplane_primitives/py.typed +0 -0
  35. agentplane_primitives/schemas/Envelope.json +808 -0
  36. agentplane_primitives/schemas/README.md +39 -0
  37. agentplane_primitives/schemas/Span.json +116 -0
  38. agentplane_primitives/schemas/Trace.json +88 -0
  39. agentplane_primitives/types/__init__.py +36 -0
  40. agentplane_primitives/types/id_types.py +48 -0
  41. agentplane_primitives/types/metadata_types.py +32 -0
  42. agentplane_primitives/types/provider_types.py +18 -0
  43. agentplane_primitives/types/schema_version_types.py +18 -0
  44. agentplane_primitives/utils/__init__.py +25 -0
  45. agentplane_primitives/utils/id_utils.py +32 -0
  46. agentplane_primitives/utils/lifecycle_utils.py +33 -0
  47. agentplane_primitives/utils/metadata_utils.py +11 -0
  48. agentplane_primitives/utils/schema_utils.py +23 -0
  49. agentplane_primitives/utils/traceparent_utils.py +63 -0
  50. agentplane_primitives-0.1.0.dist-info/METADATA +211 -0
  51. agentplane_primitives-0.1.0.dist-info/RECORD +53 -0
  52. agentplane_primitives-0.1.0.dist-info/WHEEL +4 -0
  53. agentplane_primitives-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,10 @@
1
+ """AgentPlane primitives: shared, versioned domain objects.
2
+
3
+ Used by Chronicle, the control plane and TokenOps. This package holds data models only:
4
+ no I/O and no business logic. The entities (Trace, Span, Envelope) are
5
+ added in later releases; see ``docs/design.md``.
6
+ """
7
+
8
+ from agentplane_primitives._version import __version__
9
+
10
+ __all__ = ["__version__"]
@@ -0,0 +1,3 @@
1
+ """Single source of the package version (read by the build backend and at runtime)."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,25 @@
1
+ """Closed sets of values shared by the entities (design section 2.9).
2
+
3
+ Each enum has its own module. A new member of any enum is a schema change: see
4
+ ``docs/versioning.md``.
5
+ """
6
+
7
+ from agentplane_primitives.enums.base_enum import BaseEnum
8
+ from agentplane_primitives.enums.finish_reason import FinishReason
9
+ from agentplane_primitives.enums.kind import Kind
10
+ from agentplane_primitives.enums.link_type import LinkType
11
+ from agentplane_primitives.enums.message_role import MessageRole
12
+ from agentplane_primitives.enums.model_source import ModelSource
13
+ from agentplane_primitives.enums.state import State
14
+ from agentplane_primitives.enums.status import Status
15
+
16
+ __all__ = [
17
+ "BaseEnum",
18
+ "FinishReason",
19
+ "Kind",
20
+ "LinkType",
21
+ "MessageRole",
22
+ "ModelSource",
23
+ "State",
24
+ "Status",
25
+ ]
@@ -0,0 +1,15 @@
1
+ """``BaseEnum``: the parent of every closed enum in this package.
2
+
3
+ Members are strings, and ``str()`` and f-strings give the value (``"llm"``), not the member name
4
+ (``"Kind.LLM"``), on every supported Python version. ``enum.StrEnum`` would do this but needs
5
+ Python 3.11; this package supports 3.10.
6
+ """
7
+
8
+ from enum import Enum
9
+
10
+
11
+ class BaseEnum(str, Enum):
12
+ """A string enum whose text form is its value."""
13
+
14
+ def __str__(self) -> str:
15
+ return str(self.value)
@@ -0,0 +1,17 @@
1
+ """``FinishReason``: why an LLM call stopped (design section 2.9).
2
+
3
+ Closed enum of the canonical set. The provider's own original value stays in ``output.raw``, so
4
+ nothing is lost when a provider value is mapped onto one of these.
5
+ """
6
+
7
+ from agentplane_primitives.enums.base_enum import BaseEnum
8
+
9
+
10
+ class FinishReason(BaseEnum):
11
+ """The canonical reason an LLM response ended."""
12
+
13
+ STOP = "stop"
14
+ LENGTH = "length"
15
+ TOOL_CALLS = "tool_calls"
16
+ CONTENT_FILTER = "content_filter"
17
+ OTHER = "other"
@@ -0,0 +1,14 @@
1
+ """``Kind``: what an envelope records (design sections 2.3 and 2.9).
2
+
3
+ Closed enum: there are no custom or unregistered kinds, and any other value is rejected at
4
+ validation. A new kind is a schema change (see ``docs/versioning.md`` and ADR 0002).
5
+ """
6
+
7
+ from agentplane_primitives.enums.base_enum import BaseEnum
8
+
9
+
10
+ class Kind(BaseEnum):
11
+ """The kind of boundary crossing an envelope records. It selects the input and output shape."""
12
+
13
+ LLM = "llm"
14
+ TOOL = "tool"
@@ -0,0 +1,12 @@
1
+ """``LinkType``: how one envelope relates to another besides parent and child (design 2.5).
2
+
3
+ Closed enum for now. ``joined`` (fan-in) is reserved for future scope and is not a member yet.
4
+ """
5
+
6
+ from agentplane_primitives.enums.base_enum import BaseEnum
7
+
8
+
9
+ class LinkType(BaseEnum):
10
+ """The relationship a link expresses."""
11
+
12
+ RETRY_OF = "retry_of"
@@ -0,0 +1,12 @@
1
+ """``MessageRole``: who a message in an LLM input is from (design section 2.9). Closed enum."""
2
+
3
+ from agentplane_primitives.enums.base_enum import BaseEnum
4
+
5
+
6
+ class MessageRole(BaseEnum):
7
+ """The author of a message."""
8
+
9
+ SYSTEM = "system"
10
+ USER = "user"
11
+ ASSISTANT = "assistant"
12
+ TOOL = "tool"
@@ -0,0 +1,17 @@
1
+ """``ModelSource``: where ``output.model`` came from (design sections 2.3 and 2.9).
2
+
3
+ Closed enum. Some SDKs do not return the served model; then the adapter copies the requested one
4
+ and says so here, so a consumer knows how much to trust it.
5
+ """
6
+
7
+ from agentplane_primitives.enums.base_enum import BaseEnum
8
+
9
+
10
+ class ModelSource(BaseEnum):
11
+ """How the model name in the output was obtained."""
12
+
13
+ SERVED = "served"
14
+ """The response named the model that served the call."""
15
+
16
+ REQUESTED = "requested"
17
+ """The response had no model; the adapter copied the requested one."""
@@ -0,0 +1,13 @@
1
+ """``State``: where a Trace, Span or Envelope is in its lifecycle (design section 2.4).
2
+
3
+ Closed enum. A record is mutable while open and immutable once closed.
4
+ """
5
+
6
+ from agentplane_primitives.enums.base_enum import BaseEnum
7
+
8
+
9
+ class State(BaseEnum):
10
+ """Lifecycle state shared by Trace, Span and Envelope."""
11
+
12
+ OPEN = "open"
13
+ CLOSED = "closed"
@@ -0,0 +1,14 @@
1
+ """``Status``: the result of a Span or Envelope (design sections 2.2 and 2.9).
2
+
3
+ Closed enum. A Trace has no status of its own. There is no ``aborted`` value: a deliberate abort
4
+ is a ``failure`` whose ``output.error.type`` is ``AbortCall``.
5
+ """
6
+
7
+ from agentplane_primitives.enums.base_enum import BaseEnum
8
+
9
+
10
+ class Status(BaseEnum):
11
+ """High-level result of a Span or Envelope."""
12
+
13
+ SUCCESS = "success"
14
+ FAILURE = "failure"
@@ -0,0 +1,46 @@
1
+ """The models: Trace, Span, Envelope with its parts, the TraceContext carrier, the base class."""
2
+
3
+ from agentplane_primitives.models.base import PrimitiveModel
4
+ from agentplane_primitives.models.content_block import ContentBlock
5
+ from agentplane_primitives.models.envelope import Envelope
6
+ from agentplane_primitives.models.envelope_status import EnvelopeStatus
7
+ from agentplane_primitives.models.link import Link
8
+ from agentplane_primitives.models.llm_input import LlmInput
9
+ from agentplane_primitives.models.llm_output import LlmOutput
10
+ from agentplane_primitives.models.llm_params import LlmParams
11
+ from agentplane_primitives.models.message import Message
12
+ from agentplane_primitives.models.output_error import ABORT_CALL, OutputError
13
+ from agentplane_primitives.models.reasoning_block import ReasoningBlock
14
+ from agentplane_primitives.models.refusal_block import RefusalBlock
15
+ from agentplane_primitives.models.span import Span
16
+ from agentplane_primitives.models.text_block import TextBlock
17
+ from agentplane_primitives.models.tool_call_block import ToolCallBlock
18
+ from agentplane_primitives.models.tool_input import ToolInput
19
+ from agentplane_primitives.models.tool_output import ToolOutput
20
+ from agentplane_primitives.models.trace import Trace
21
+ from agentplane_primitives.models.trace_context import TraceContext
22
+ from agentplane_primitives.models.usage import Usage
23
+
24
+ __all__ = [
25
+ "ABORT_CALL",
26
+ "ContentBlock",
27
+ "Envelope",
28
+ "EnvelopeStatus",
29
+ "Link",
30
+ "LlmInput",
31
+ "LlmOutput",
32
+ "LlmParams",
33
+ "Message",
34
+ "OutputError",
35
+ "PrimitiveModel",
36
+ "ReasoningBlock",
37
+ "RefusalBlock",
38
+ "Span",
39
+ "TextBlock",
40
+ "ToolCallBlock",
41
+ "ToolInput",
42
+ "ToolOutput",
43
+ "Trace",
44
+ "TraceContext",
45
+ "Usage",
46
+ ]
@@ -0,0 +1,20 @@
1
+ """``PrimitiveModel``: the base class every model in this package inherits from.
2
+
3
+ It holds the settings all models share, so they are decided once (design section 2.8).
4
+ """
5
+
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+
9
+ class PrimitiveModel(BaseModel):
10
+ """Shared model settings.
11
+
12
+ - Unknown fields are ignored on read. This only matters when writer and reader run
13
+ different versions (for example during a rolling upgrade); the control plane keeps the
14
+ full original payload, so nothing is lost.
15
+ - Assigning a bad value to a field is validated, not silently accepted. Changing a
16
+ container in place (for example ``model.metadata[key] = value``) is not checked: Python
17
+ gives pydantic no hook for it, so such rules hold when the model is built or assigned.
18
+ """
19
+
20
+ model_config = ConfigDict(extra="ignore", validate_assignment=True)
@@ -0,0 +1,19 @@
1
+ """``ContentBlock``: one piece of LLM content (design sections 2.3 and 2.9).
2
+
3
+ A closed set chosen by ``type``: ``text``, ``tool_call``, ``reasoning`` or ``refusal``. Any other
4
+ ``type`` is rejected. Each block has its own module.
5
+ """
6
+
7
+ from typing import Annotated
8
+
9
+ from pydantic import Field
10
+
11
+ from agentplane_primitives.models.reasoning_block import ReasoningBlock
12
+ from agentplane_primitives.models.refusal_block import RefusalBlock
13
+ from agentplane_primitives.models.text_block import TextBlock
14
+ from agentplane_primitives.models.tool_call_block import ToolCallBlock
15
+
16
+ ContentBlock = Annotated[
17
+ TextBlock | ToolCallBlock | ReasoningBlock | RefusalBlock,
18
+ Field(discriminator="type"),
19
+ ]
@@ -0,0 +1,94 @@
1
+ """``Envelope``: one boundary crossing, as a single record (design sections 2.2 to 2.5).
2
+
3
+ The fields fall into the five sections of the design. The identity fields sit at the top level,
4
+ as in the design's example payloads; the other four sections are nested:
5
+
6
+ - identity: ``envelope_id``, ``span_id``, ``trace_id``, ``parent_envelope_id``, ``name``,
7
+ ``kind``, ``attempt``, ``links``
8
+ - ``envelope_status``, ``input``, ``output`` and ``metadata``
9
+
10
+ ``kind`` selects the shape of ``input`` and ``output``: ``tool`` uses ``ToolInput`` and
11
+ ``ToolOutput``, ``llm`` uses ``LlmInput`` and ``LlmOutput``. A payload of the other kind's shape
12
+ is rejected. An envelope is open until closed; a closed envelope must have an ``output`` (an
13
+ error is recorded there).
14
+ """
15
+
16
+ from typing import Any
17
+
18
+ from pydantic import BaseModel, Field, model_validator
19
+
20
+ from agentplane_primitives.enums import Kind, State
21
+ from agentplane_primitives.models.base import PrimitiveModel
22
+ from agentplane_primitives.models.envelope_status import EnvelopeStatus
23
+ from agentplane_primitives.models.link import Link
24
+ from agentplane_primitives.models.llm_input import LlmInput
25
+ from agentplane_primitives.models.llm_output import LlmOutput
26
+ from agentplane_primitives.models.tool_input import ToolInput
27
+ from agentplane_primitives.models.tool_output import ToolOutput
28
+ from agentplane_primitives.types import (
29
+ SCHEMA_VERSION,
30
+ EnvelopeId,
31
+ Metadata,
32
+ SchemaVersion,
33
+ SpanId,
34
+ TraceId,
35
+ )
36
+
37
+ _PAYLOADS: dict[Kind, tuple[type[BaseModel], type[BaseModel]]] = {
38
+ Kind.TOOL: (ToolInput, ToolOutput),
39
+ Kind.LLM: (LlmInput, LlmOutput),
40
+ }
41
+
42
+
43
+ class Envelope(PrimitiveModel):
44
+ """The record of one call across a boundary inside a span."""
45
+
46
+ schema_version: SchemaVersion = SCHEMA_VERSION
47
+
48
+ # identity
49
+ envelope_id: EnvelopeId
50
+ span_id: SpanId
51
+ trace_id: TraceId
52
+ parent_envelope_id: EnvelopeId | None = None
53
+ name: str = Field(min_length=1, description="Boundary name, e.g. agent.chat.")
54
+ kind: Kind
55
+ attempt: int = Field(default=1, ge=1, description="1 for the first try, 2 for a retry, ...")
56
+ links: list[Link] = Field(default_factory=list)
57
+
58
+ envelope_status: EnvelopeStatus
59
+ input: ToolInput | LlmInput = Field(description="Shape chosen by kind: tool or llm input.")
60
+ output: ToolOutput | LlmOutput | None = Field(
61
+ default=None, description="Shape chosen by kind: tool or llm output."
62
+ )
63
+ metadata: Metadata = Field(default_factory=dict)
64
+
65
+ @model_validator(mode="before")
66
+ @classmethod
67
+ def _read_payloads_by_kind(cls, data: Any) -> Any:
68
+ # The two payload shapes overlap when unknown fields are ignored, so the kind, not the
69
+ # shape, decides which model reads input and output.
70
+ if not isinstance(data, dict):
71
+ return data
72
+ try:
73
+ input_model, output_model = _PAYLOADS[Kind(data.get("kind"))]
74
+ except ValueError:
75
+ # Not swallowed: the ``kind`` field is validated next and rejects the value with its
76
+ # own error. This package does no I/O, so there is nothing to log.
77
+ return data
78
+ data = dict(data)
79
+ if isinstance(data.get("input"), dict):
80
+ data["input"] = input_model.model_validate(data["input"])
81
+ if isinstance(data.get("output"), dict):
82
+ data["output"] = output_model.model_validate(data["output"])
83
+ return data
84
+
85
+ @model_validator(mode="after")
86
+ def _check_consistency(self) -> "Envelope":
87
+ input_model, output_model = _PAYLOADS[self.kind]
88
+ if not isinstance(self.input, input_model):
89
+ raise ValueError(f"input does not match kind {self.kind.value!r}")
90
+ if self.output is not None and not isinstance(self.output, output_model):
91
+ raise ValueError(f"output does not match kind {self.kind.value!r}")
92
+ if self.envelope_status.state is State.CLOSED and self.output is None:
93
+ raise ValueError("a closed envelope needs an output")
94
+ return self
@@ -0,0 +1,28 @@
1
+ """``EnvelopeStatus``: the lifecycle and result of an envelope (design sections 2.2 and 2.4).
2
+
3
+ An envelope is one record that is mutable until it is closed. While ``open`` it has a start time
4
+ and nothing else; once ``closed`` it has a result and an end time. To close an envelope, build a
5
+ new ``EnvelopeStatus``: changing the fields one by one would pass through an invalid state.
6
+ """
7
+
8
+ from pydantic import AwareDatetime, model_validator
9
+
10
+ from agentplane_primitives.enums import State, Status
11
+ from agentplane_primitives.models.base import PrimitiveModel
12
+ from agentplane_primitives.utils.lifecycle_utils import validate_state_status_and_times
13
+
14
+
15
+ class EnvelopeStatus(PrimitiveModel):
16
+ """State, result and timing of an envelope. Times must carry a time zone."""
17
+
18
+ state: State
19
+ status: Status | None = None
20
+ started_at: AwareDatetime
21
+ ended_at: AwareDatetime | None = None
22
+
23
+ @model_validator(mode="after")
24
+ def _validate_state_fields(self) -> "EnvelopeStatus":
25
+ validate_state_status_and_times(
26
+ self.state, self.status, self.started_at, self.ended_at, "envelope"
27
+ )
28
+ return self
@@ -0,0 +1,16 @@
1
+ """``Link``: a typed pointer from one envelope to another (design section 2.5).
2
+
3
+ Retries are declared explicitly: an envelope that retries an earlier one carries a ``retry_of``
4
+ link to it. The linked envelope is in the same trace.
5
+ """
6
+
7
+ from agentplane_primitives.enums import LinkType
8
+ from agentplane_primitives.models.base import PrimitiveModel
9
+ from agentplane_primitives.types import EnvelopeId
10
+
11
+
12
+ class Link(PrimitiveModel):
13
+ """A relationship to another envelope in the same trace."""
14
+
15
+ type: LinkType
16
+ envelope_id: EnvelopeId
@@ -0,0 +1,33 @@
1
+ """``LlmInput``: the input of an ``llm`` envelope (design sections 2.3 and 2.9).
2
+
3
+ Input is what the caller passed on this call: the requested model, the messages and the
4
+ instructions, tool definitions and sampling settings. ``raw`` holds the arguments exactly as
5
+ they were passed to the boundaried method, as JSON by argument name, and is always stored; the
6
+ canonical fields are derived from it.
7
+
8
+ ``tools``, ``tool_choice`` and ``response_format`` are kept as JSON objects because their shape
9
+ differs by provider. The design does not fix their structure.
10
+ """
11
+
12
+ from pydantic import Field, JsonValue
13
+
14
+ from agentplane_primitives.models.base import PrimitiveModel
15
+ from agentplane_primitives.models.llm_params import LlmParams
16
+ from agentplane_primitives.models.message import Message
17
+
18
+
19
+ class LlmInput(PrimitiveModel):
20
+ """What was sent to the model."""
21
+
22
+ model: str = Field(min_length=1, description="The model or alias the caller asked for.")
23
+ messages: list[Message]
24
+ system: str | None = Field(default=None, description="Instructions the caller supplied.")
25
+ tools: list[dict[str, JsonValue]] | None = Field(
26
+ default=None, description="Tool definitions offered to the model."
27
+ )
28
+ tool_choice: JsonValue = Field(
29
+ default=None, description="Whether, or which, tool the model must use."
30
+ )
31
+ response_format: dict[str, JsonValue] | None = None
32
+ params: LlmParams = Field(default_factory=LlmParams)
33
+ raw: dict[str, JsonValue] = Field(description="Arguments as passed, by name, as JSON.")
@@ -0,0 +1,43 @@
1
+ """``LlmOutput``: the output of an ``llm`` envelope (design sections 2.3 and 2.9).
2
+
3
+ Output is what is known only from the response. It carries the fixed homes for cost data, which a
4
+ consumer such as TokenOps reads and nothing else:
5
+
6
+ - ``provider``: who served the call (``unknown`` when it cannot be determined)
7
+ - ``model`` and ``model_source``: what served it, and whether the response said so
8
+ - ``usage``: exclusive, additive token buckets
9
+
10
+ A call that failed has only ``error``; everything else is then absent. ``usage`` is null for a
11
+ custom boundary with no adapter, and consumers must tolerate that. ``raw`` is the value returned
12
+ by the boundaried method, as JSON, and is always stored.
13
+ """
14
+
15
+ from pydantic import Field, JsonValue, model_validator
16
+
17
+ from agentplane_primitives.enums import FinishReason, ModelSource
18
+ from agentplane_primitives.models.base import PrimitiveModel
19
+ from agentplane_primitives.models.content_block import ContentBlock
20
+ from agentplane_primitives.models.output_error import OutputError
21
+ from agentplane_primitives.models.usage import Usage
22
+ from agentplane_primitives.types import UNKNOWN_PROVIDER, Provider
23
+
24
+
25
+ class LlmOutput(PrimitiveModel):
26
+ """What came back from the model, or the error it raised."""
27
+
28
+ content: list[ContentBlock] = Field(default_factory=list)
29
+ finish_reason: FinishReason | None = None
30
+ provider: Provider = UNKNOWN_PROVIDER
31
+ model: str | None = Field(default=None, description="The model that served the call.")
32
+ model_source: ModelSource | None = None
33
+ usage: Usage | None = None
34
+ response_id: str | None = None
35
+ deployment_id: str | None = None
36
+ raw: JsonValue = Field(default=None, description="The returned value, as JSON.")
37
+ error: OutputError | None = None
38
+
39
+ @model_validator(mode="after")
40
+ def _model_and_source_go_together(self) -> "LlmOutput":
41
+ if (self.model is None) != (self.model_source is None):
42
+ raise ValueError("model and model_source must be given together")
43
+ return self
@@ -0,0 +1,19 @@
1
+ """``LlmParams``: the sampling settings the caller passed (design section 2.3).
2
+
3
+ Every field is optional: only what the caller set is recorded. Defaults baked into a client's
4
+ configuration are metadata, not input.
5
+ """
6
+
7
+ from pydantic import Field
8
+
9
+ from agentplane_primitives.models.base import PrimitiveModel
10
+
11
+
12
+ class LlmParams(PrimitiveModel):
13
+ """Sampling parameters passed on this call."""
14
+
15
+ temperature: float | None = None
16
+ top_p: float | None = None
17
+ max_tokens: int | None = Field(default=None, ge=0)
18
+ seed: int | None = None
19
+ stop: list[str] | None = None
@@ -0,0 +1,26 @@
1
+ """``Message``: one message in an LLM input (design sections 2.3 and 2.9).
2
+
3
+ ``content`` is plain text or a list of content blocks. A ``tool`` message (a tool result being
4
+ handed back to the model) names the call it answers in ``tool_call_id``: the ``id`` of the
5
+ ``ToolCallBlock`` the model produced, copied from the provider's own id (OpenAI ``tool_call_id``,
6
+ Anthropic ``tool_use_id``, Bedrock ``toolUseId``). Not every provider sends one, so it is
7
+ optional, and it is not checked against earlier messages. Anthropic carries a tool result as a
8
+ ``tool_result`` block inside a user message; an adapter maps that to a ``tool`` message here. The
9
+ provider's exact message shape is kept in ``input.raw``.
10
+ """
11
+
12
+ from pydantic import Field
13
+
14
+ from agentplane_primitives.enums import MessageRole
15
+ from agentplane_primitives.models.base import PrimitiveModel
16
+ from agentplane_primitives.models.content_block import ContentBlock
17
+
18
+
19
+ class Message(PrimitiveModel):
20
+ """A single message with a role."""
21
+
22
+ role: MessageRole
23
+ content: str | list[ContentBlock]
24
+ tool_call_id: str | None = Field(
25
+ default=None, description="For role=tool: the id of the tool call this answers."
26
+ )
@@ -0,0 +1,18 @@
1
+ """``OutputError``: how a failed call is described inside an output (design sections 2.3, 2.9).
2
+
3
+ Error information belongs to ``output``, not to metadata. ``type`` is the exception class name; the
4
+ reserved value ``"AbortCall"`` means a hook stopped the call on purpose.
5
+ """
6
+
7
+ from pydantic import Field
8
+
9
+ from agentplane_primitives.models.base import PrimitiveModel
10
+
11
+ ABORT_CALL = "AbortCall"
12
+
13
+
14
+ class OutputError(PrimitiveModel):
15
+ """An error raised by the call, recorded in ``output.error``."""
16
+
17
+ type: str = Field(min_length=1, description="Exception class name, e.g. RateLimitError.")
18
+ message: str = Field(description="The exception message.")
@@ -0,0 +1,12 @@
1
+ """``ReasoningBlock``: the model's visible reasoning text (design section 2.3)."""
2
+
3
+ from typing import Literal
4
+
5
+ from agentplane_primitives.models.base import PrimitiveModel
6
+
7
+
8
+ class ReasoningBlock(PrimitiveModel):
9
+ """Reasoning content returned by the model."""
10
+
11
+ type: Literal["reasoning"] = "reasoning"
12
+ text: str
@@ -0,0 +1,12 @@
1
+ """``RefusalBlock``: the model declining to answer (design section 2.3)."""
2
+
3
+ from typing import Literal
4
+
5
+ from agentplane_primitives.models.base import PrimitiveModel
6
+
7
+
8
+ class RefusalBlock(PrimitiveModel):
9
+ """A refusal returned by the model."""
10
+
11
+ type: Literal["refusal"] = "refusal"
12
+ text: str
@@ -0,0 +1,15 @@
1
+ """``PUBLIC_MODELS``: the models that get a committed JSON Schema file.
2
+
3
+ Add a model here in the same pull request that introduces it, then run
4
+ ``python scripts/generate_schemas.py`` and commit the new file. Helper models that only appear
5
+ inside another model (such as ``Link`` inside ``Envelope``) do not need an entry: they are
6
+ included in that model's schema.
7
+ """
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from agentplane_primitives.models.envelope import Envelope
12
+ from agentplane_primitives.models.span import Span
13
+ from agentplane_primitives.models.trace import Trace
14
+
15
+ PUBLIC_MODELS: tuple[type[BaseModel], ...] = (Trace, Span, Envelope)
@@ -0,0 +1,48 @@
1
+ """``Span``: one service's handling of a request (design sections 2.1 and 2.2).
2
+
3
+ A Span sits between the Trace and its Envelopes: one process, one agent run. It is minted by its
4
+ own service, so ``span_id`` is unique within the trace. ``parent_envelope_id`` is the envelope in
5
+ the caller's span that made the outbound call; it is the only link between services, and it is
6
+ empty for the first span in a trace.
7
+
8
+ ``status`` is ``success`` or ``failure`` and is set when the span closes, by the entry point:
9
+ ``failure`` if the request handler raised, otherwise ``success``. It is independent of envelope
10
+ failures (a retry that was handled does not fail the span). Like an envelope, a span is open until
11
+ closed; to close one, build a new ``Span``, because the fields are validated together.
12
+ """
13
+
14
+ from pydantic import AwareDatetime, Field, model_validator
15
+
16
+ from agentplane_primitives.enums import State, Status
17
+ from agentplane_primitives.models.base import PrimitiveModel
18
+ from agentplane_primitives.types import (
19
+ SCHEMA_VERSION,
20
+ EnvelopeId,
21
+ Metadata,
22
+ SchemaVersion,
23
+ SpanId,
24
+ TraceId,
25
+ )
26
+ from agentplane_primitives.utils.lifecycle_utils import validate_state_status_and_times
27
+
28
+
29
+ class Span(PrimitiveModel):
30
+ """One service's handling of a request inside a trace. Times must carry a time zone."""
31
+
32
+ schema_version: SchemaVersion = SCHEMA_VERSION
33
+ span_id: SpanId
34
+ trace_id: TraceId
35
+ parent_envelope_id: EnvelopeId | None = None
36
+ service: str = Field(min_length=1, description="Service or agent name.")
37
+ state: State
38
+ status: Status | None = None
39
+ started_at: AwareDatetime
40
+ ended_at: AwareDatetime | None = None
41
+ metadata: Metadata = Field(default_factory=dict, description="Span-level setup.")
42
+
43
+ @model_validator(mode="after")
44
+ def _validate_state_fields(self) -> "Span":
45
+ validate_state_status_and_times(
46
+ self.state, self.status, self.started_at, self.ended_at, "span"
47
+ )
48
+ return self
@@ -0,0 +1,12 @@
1
+ """``TextBlock``: plain text produced or sent in an LLM call (design section 2.3)."""
2
+
3
+ from typing import Literal
4
+
5
+ from agentplane_primitives.models.base import PrimitiveModel
6
+
7
+
8
+ class TextBlock(PrimitiveModel):
9
+ """A piece of text."""
10
+
11
+ type: Literal["text"] = "text"
12
+ text: str