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
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"dependencies": {
"@ant-design/icons": "6.2.5",
"@assistant-ui/react": "^0.15.0",
"@assistant-ui/react-ai-sdk": "^1.3.33",
"@assistant-ui/react-ai-sdk": "1.3.33",
"@assistant-ui/react-lexical": "0.2.7",
"@assistant-ui/react-markdown": "^0.14.2",
"@dicebear/core": "^9.2.2",
Expand Down
3 changes: 3 additions & 0 deletions sdk/nexent/core/agent_runtime_support/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""Shared Agent runtime contracts and scoped support; no engine selection."""
58 changes: 58 additions & 0 deletions sdk/nexent/core/agent_runtime_support/contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""Selected model-visible resources shared by Agent runtime implementations."""

from typing import Annotated, Self

from pydantic import Field, model_validator

from nexent.core.model_service.contracts import ModelMessage, ToolDefinition
from nexent.core.model_service.profiles.contracts import NonBlankString, ProfileContract


class ResourceItem(ProfileContract):
message: ModelMessage
source: NonBlankString
order: Annotated[int, Field(ge=0)]


class RuntimeResourceView(ProfileContract):
"""Only selected model-visible data; no executable bindings or arbitrary metadata."""

instructions: list[ResourceItem] = Field(default_factory=list)
references: list[ResourceItem] = Field(default_factory=list)
history: list[ResourceItem] = Field(default_factory=list)
current_user_input: ResourceItem
tool_observations: list[ResourceItem] = Field(default_factory=list)
tool_definitions: list[ToolDefinition] = Field(default_factory=list)
authorized_metadata: list[ResourceItem] = Field(default_factory=list)

@model_validator(mode="after")
def validate_resources(self) -> Self:
items = self.items()
if len({item.order for item in items}) != len(items):
raise ValueError("Resource ordering must be unique")
if len({item.message.message_id for item in items}) != len(items):
raise ValueError("Resource message identities must be unique")
if len({tool.name for tool in self.tool_definitions}) != len(self.tool_definitions):
raise ValueError("Authorized tool names must be unique")
for item in items:
message = item.message
if message.tool_calls and message.semantic_role not in {"assistant_history", "assistant"}:
raise ValueError("Tool calls require an assistant history item")
if message.tool_call_id is not None and message.semantic_role not in {"tool_result", "tool"}:
raise ValueError("Tool result association requires an observation")
for group, roles in (
(self.instructions, {"platform_instruction", "application_instruction"}),
(self.references + self.authorized_metadata, {"reference_data"}),
(self.history, {"user_input", "assistant_history", "tool_result", "user", "assistant", "tool"}),
(self.tool_observations, {"tool_result", "tool"}),
([self.current_user_input], {"user_input"}),
):
if any(item.message.semantic_role not in roles for item in group):
raise ValueError("Resource role does not match its authorized group")
return self

def items(self):
return (self.instructions + self.references + self.history + self.tool_observations
+ self.authorized_metadata + [self.current_user_input])
3 changes: 3 additions & 0 deletions sdk/nexent/core/model_service/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""Model service contracts and provider adapters."""
3 changes: 3 additions & 0 deletions sdk/nexent/core/model_service/adapters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""Registered provider request and response implementations."""
3 changes: 3 additions & 0 deletions sdk/nexent/core/model_service/compatibility/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""Compatibility with existing model consumers; internal imports use concrete modules."""
160 changes: 160 additions & 0 deletions sdk/nexent/core/model_service/contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""Provider-independent model messages, requests and complete responses."""

from typing import Annotated, Literal, Self
from uuid import UUID, uuid4

from pydantic import Field, JsonValue, model_validator

from nexent.core.model_service.profiles.contracts import NonBlankString, ProfileContract


NonnegativeInt = Annotated[int, Field(ge=0)]


class ToolCall(ProfileContract):
"""Complete protocol call; parsing never constitutes execution permission."""

local_call_id: UUID = Field(default_factory=uuid4)
provider_call_id: NonBlankString
index: NonnegativeInt
name: NonBlankString
raw_arguments: str = Field(repr=False)
parsed_arguments: dict[str, JsonValue] | None = Field(default=None, repr=False)
argument_error: Literal["INVALID_TOOL_ARGUMENTS"] | None = None


class ToolDefinition(ProfileContract):
name: NonBlankString
description: str
input_schema: dict[str, JsonValue] = Field(repr=False)


class ModelMessage(ProfileContract):
message_id: UUID = Field(default_factory=uuid4)
semantic_role: NonBlankString
content_parts: list[dict[str, JsonValue]] = Field(default_factory=list, repr=False)
reasoning_parts: list[dict[str, JsonValue]] = Field(default_factory=list, repr=False)
tool_calls: list[ToolCall] = Field(default_factory=list, repr=False)
tool_call_id: NonBlankString | None = None
source: NonBlankString | None = None


class ModelRequest(ProfileContract):
request_id: NonBlankString
outlet_id: Annotated[int, Field(gt=0)] | None
config_revision: Annotated[int, Field(gt=0)]
messages: list[ModelMessage] = Field(min_length=1, repr=False)
tools: list[ToolDefinition] = Field(default_factory=list, repr=False)
stream: bool = False
purpose: NonBlankString = "agent"
run_id: NonBlankString | None = None

@model_validator(mode="after")
def validate_identities(self) -> Self:
if len({message.message_id for message in self.messages}) != len(self.messages):
raise ValueError("Model message identities must be unique")
if len({tool.name for tool in self.tools}) != len(self.tools):
raise ValueError("Model tool names must be unique")
return self


class TokenUsage(ProfileContract):
input_tokens: NonnegativeInt | None = None
output_tokens: NonnegativeInt | None = None
total_tokens: NonnegativeInt | None = None
cached_input_tokens: NonnegativeInt | None = None
reasoning_tokens: NonnegativeInt | None = None
origin: Literal["provider", "unknown"] = "unknown"
semantics_id: NonBlankString | None = None


class AssistantMessage(ProfileContract):
message_id: UUID = Field(default_factory=uuid4)
content_parts: list[dict[str, JsonValue]] = Field(default_factory=list, repr=False)
reasoning_parts: list[dict[str, JsonValue]] = Field(default_factory=list, repr=False)
refusal: str | None = Field(default=None, repr=False)
tool_calls: list[ToolCall] = Field(default_factory=list, repr=False)
finish_reason: NonBlankString
usage: TokenUsage = Field(default_factory=TokenUsage)
provider_metadata: dict[str, JsonValue] = Field(default_factory=dict, repr=False)

@model_validator(mode="after")
def validate_call_identities(self) -> Self:
for values in ([call.provider_call_id for call in self.tool_calls], [call.index for call in self.tool_calls]):
if len(set(values)) != len(values):
raise ValueError("Complete tool call identities must be unique")
return self


class ToolCallDelta(ProfileContract):
"""Display/accumulation only: never contains parsed execution arguments."""

local_call_id: UUID
index: NonnegativeInt
provider_call_id: NonBlankString | None = None
name_delta: str | None = Field(default=None, repr=False)
arguments_delta: str | None = Field(default=None, repr=False)


class ModelError(ProfileContract):
category: NonBlankString
scope: Literal["transport", "provider", "protocol"]
retryable: bool
will_retry: bool = False
attempt: Annotated[int, Field(gt=0)] = 1
max_attempts: Annotated[int, Field(gt=0)] = 1
provider_code: str | None = None
safe_message: NonBlankString
request_id: NonBlankString
finish_reason: NonBlankString | None = None

@model_validator(mode="after")
def validate_attempt(self) -> Self:
if self.attempt > self.max_attempts:
raise ValueError("Model attempt exceeds its configured maximum")
if self.will_retry and (not self.retryable or self.attempt >= self.max_attempts):
raise ValueError("Model retry requires remaining attempts and a retryable error")
return self


class ModelEvent(ProfileContract):
request_id: NonBlankString
message_id: UUID
sequence: Annotated[int, Field(gt=0)]
attempt: Annotated[int, Field(gt=0)] = 1
type: Literal["text_delta", "reasoning_delta", "tool_call_delta", "message_completed", "usage", "error"]
delta: str | None = Field(default=None, repr=False)
content_kind: Literal["text", "refusal"] | None = None
tool_call_delta: ToolCallDelta | None = Field(default=None, repr=False)
message: AssistantMessage | None = Field(default=None, repr=False)
usage: TokenUsage | None = None
finish_reason: NonBlankString | None = None
error: ModelError | None = Field(default=None, repr=False)

@model_validator(mode="after")
def validate_payload(self) -> Self:
required = {
"text_delta": "delta",
"reasoning_delta": "delta",
"tool_call_delta": "tool_call_delta",
"message_completed": "message",
"usage": "usage",
"error": "error",
}[self.type]
payloads = {
key for key in ("delta", "tool_call_delta", "message", "usage", "error") if getattr(self, key) is not None
}
if payloads != {required}:
raise ValueError("Model event requires exactly its declared payload")
if self.content_kind is not None and self.type != "text_delta":
raise ValueError("Content kind applies only to text events")
if self.type == "message_completed":
if self.message.message_id != self.message_id or self.finish_reason != self.message.finish_reason:
raise ValueError("Completion identity and finish reason must agree")
elif self.finish_reason is not None:
raise ValueError("Only completed messages carry finish reasons")
if self.error is not None and self.error.request_id != self.request_id:
raise ValueError("Model error request identity must agree")
return self
127 changes: 127 additions & 0 deletions sdk/nexent/core/model_service/http_contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.

"""HTTP boundary contracts; credentials are resolved only by the sender."""

import json
import re
from dataclasses import dataclass, field
from types import MappingProxyType
from urllib.parse import unquote, urlsplit, urlunsplit

from nexent.core.model_service.profiles.resolved_profile import CredentialReference


CONTROL_HEADERS = frozenset({"host", "content-length", "transfer-encoding", "connection"})
AUTH_HEADERS = frozenset({"authorization", "proxy-authorization", "cookie", "x-api-key", "api-key"})
HEADER_NAME = re.compile(r"^[!#$%&'*+.^_`|~0-9A-Za-z-]+$")


def validate_headers(headers: dict[str, str], *, credentials: bool = False) -> dict[str, str]:
"""Reject invalid/control headers without exposing their values."""
if not isinstance(headers, dict):
raise ValueError("Invalid model HTTP headers")
result = {}
for name, value in headers.items():
if (
not isinstance(name, str)
or not HEADER_NAME.fullmatch(name)
or not isinstance(value, str)
or any(ord(character) < 32 or ord(character) >= 127 for character in value)
or name.lower() in result
or name.lower() in CONTROL_HEADERS
or (not credentials and name.lower() in AUTH_HEADERS)
):
raise ValueError("Invalid model HTTP headers")
result[name.lower()] = value
return result


def model_request_url(base_url: str, relative_path: str) -> str:
"""Preserve the API root and query without guessing a provider prefix."""
if not isinstance(base_url, str) or not isinstance(relative_path, str):
raise ValueError("Invalid model HTTP URL")
if any(character.isspace() or ord(character) < 32 for character in base_url + relative_path):
raise ValueError("Invalid model HTTP URL")
try:
base, relative = urlsplit(base_url), urlsplit(relative_path)
invalid_port = base.port is not None and not 0 < base.port < 65536
except ValueError:
raise ValueError("Invalid model HTTP URL") from None
if (
base.scheme not in {"http", "https"}
or not base.hostname
or base.username is not None
or base.password is not None
or base.fragment
or invalid_port
or relative.scheme
or relative.netloc
or relative.query
or relative.fragment
or not relative.path
or relative.path.startswith("//")
or "\\" in unquote(relative.path)
or any(part in {"", ".", ".."} for part in unquote(relative.path).lstrip("/").split("/"))
):
raise ValueError("Invalid model HTTP URL")
return urlunsplit(
(base.scheme, base.netloc, base.path.rstrip("/") + "/" + relative.path.lstrip("/"), base.query, "")
)


@dataclass(frozen=True)
class PreparedHttpRequest:
"""Store a detached serialized body, never a mutable caller-owned payload."""

request_id: str
method: str
base_url: str = field(repr=False)
relative_path: str
headers: dict[str, str] = field(repr=False)
credential_ref: CredentialReference
json_body: dict = field(repr=False)
timeout_seconds: float
ssl_verify: bool
stream_protocol: str | None = None
url: str = field(init=False, repr=False)
body_bytes: bytes = field(init=False, repr=False)

def __post_init__(self):
if (
not isinstance(self.request_id, str)
or not self.request_id.strip()
or self.method not in {"POST", "GET"}
or type(self.timeout_seconds) not in {int, float}
or not 0 < self.timeout_seconds < float("inf")
or type(self.ssl_verify) is not bool
or self.stream_protocol not in {None, "sse"}
or not isinstance(self.credential_ref, CredentialReference)
or not isinstance(self.json_body, dict)
):
raise ValueError("Invalid prepared model HTTP request")
try:
body = json.dumps(self.json_body, ensure_ascii=False, allow_nan=False, separators=(",", ":")).encode(
"utf-8"
)
except (TypeError, ValueError, UnicodeError, RecursionError):
raise ValueError("Invalid model HTTP body") from None
object.__setattr__(self, "body_bytes", body)
object.__setattr__(self, "json_body", MappingProxyType(json.loads(body)))
object.__setattr__(self, "headers", MappingProxyType(validate_headers(self.headers)))
object.__setattr__(self, "url", model_request_url(self.base_url, self.relative_path))


@dataclass(frozen=True)
class RawHttpResponse:
status_code: int
headers: dict[str, str] = field(repr=False)
body: bytes = field(repr=False)


class ModelTransportError(Exception):
"""Safe common error with private response bytes available to adapters."""

def __init__(self, category: str, *, response: RawHttpResponse | None = None):
self.category = category
self.response = response
super().__init__(category)
Loading
Loading