Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
06da564
Decouple agentserver responses from generated model internals
Shivakishore14 Jul 10, 2026
c4e25b2
Regenerate agentserver response models from source emitter
Shivakishore14 Jul 10, 2026
2e9a3b1
Reduce redundant agentserver wire conversions
Shivakishore14 Jul 10, 2026
a7fe835
Use source-fixed AgentServer TypeDict generation
Shivakishore14 Jul 15, 2026
7595462
Update AgentServer TypedDict SDK from spec PR
Shivakishore14 Jul 15, 2026
17b1a55
Remove redundant AgentServer wire helpers
Shivakishore14 Jul 16, 2026
039fbd2
Merge remote-tracking branch 'origin/main' into sshiva/agentserver-ty…
Shivakishore14 Jul 16, 2026
23c9a46
Align AgentServer enum shim with main
Shivakishore14 Jul 16, 2026
e97042d
Sync AgentServer spec pointer
Shivakishore14 Jul 16, 2026
4263a53
Fix AgentServer Responses CI issues
Shivakishore14 Jul 16, 2026
01233dd
Fix AgentServer Responses sample typing
Shivakishore14 Jul 17, 2026
6fe830f
Merge branch 'main' into sshiva/agentserver-typeddict-local
Shivakishore14 Jul 17, 2026
8ab986f
Sync AgentServer contracts from correct spec branch
Shivakishore14 Jul 17, 2026
0e27517
Address AgentServer Responses review comments
Shivakishore14 Jul 20, 2026
439f4be
Fix AgentServer Responses docs CI
Shivakishore14 Jul 20, 2026
53b220a
Fix AgentServer Analyze mypy failures
Shivakishore14 Jul 20, 2026
c8a3d8c
Address AgentServer response model suggestions
Shivakishore14 Jul 20, 2026
04dda5e
Fix AgentServer enum fallback spellcheck
Shivakishore14 Jul 21, 2026
a6977ef
Fix AgentServer enum and timestamp contracts
Shivakishore14 Jul 21, 2026
92db0e3
Address AgentServer review and API consistency
Shivakishore14 Jul 21, 2026
1ec49f6
Fix AgentServer stream TTL contract tests
Shivakishore14 Jul 21, 2026
34b2061
Hide internal AgentServer message models
Shivakishore14 Jul 21, 2026
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
68 changes: 25 additions & 43 deletions sdk/agentserver/azure-ai-agentserver-responses/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
# generate-contracts, clean, install-typespec-deps
#
# Dependencies are resolved from eng/emitter-package.json (via tsp-client sync)
# which creates TempTypeSpecFiles/package.json with pinned versions.
# Do NOT override versions with npm install --no-save.
# which creates TempTypeSpecFiles/package.json with pinned versions. The optional
# TYPESPEC_*_PATH variables are only for validating local emitter builds before
# their package versions are pinned in emitter-package.json.

OUTPUT_DIR ?= azure/ai/agentserver/responses/models/_generated
TYPESPEC_DIR ?= type_spec
TEMP_TSP_DIR := $(TYPESPEC_DIR)/TempTypeSpecFiles
TEMP_OUTPUT_DIR := .tmp_typeddict_codegen
TYPESPEC_PYTHON_EMITTER_PATH ?=
TYPESPEC_HTTP_CLIENT_PYTHON_PATH ?=
OPENAPI_SPEC ?= $(TEMP_TSP_DIR)/Foundry/openapi.virtual-public-preview.yaml
VALIDATORS_OUTPUT ?= $(OUTPUT_DIR)/_validators.py
ROOT_SCHEMAS ?= CreateResponse
OVERLAY ?= _scripts/validation-overlay.yaml
TEMP_OUTPUT_DIR := $(OUTPUT_DIR)/.tmp_codegen
MODEL_PACKAGE_DIR := $(TEMP_OUTPUT_DIR)/azure/ai/agentserver/responses/models
MODEL_SHIMS_DIR := _scripts/generated_shims
CONTRACTS_DIR := $(TEMP_TSP_DIR)/sdk-service-agentserver-contracts
MODEL_BASE := $(OUTPUT_DIR)/sdk/models/_utils/model_base.py

.PHONY: generate-models generate-validators generate-openapi generate-contracts clean install-typespec-deps

Expand Down Expand Up @@ -90,7 +90,7 @@ endif
generate-contracts: generate-models generate-openapi generate-validators

# --------------------------------------------------------------------------
# generate-models: Compile TypeSpec definitions into Python model classes
# generate-models: Compile TypeSpec definitions into local TypedDict model contracts
# --------------------------------------------------------------------------
ifeq ($(OS),Windows_NT)
generate-models:
Expand All @@ -100,25 +100,14 @@ generate-models:
cd /d $(TYPESPEC_DIR) && tsp-client sync
@echo Installing TypeSpec dependencies from emitter-package.json...
cd /d $(TEMP_TSP_DIR) && npm install --silent
@echo Generating Python models...
@if not "$(TYPESPEC_HTTP_CLIENT_PYTHON_PATH)"=="" cd /d $(TEMP_TSP_DIR) && npm install --silent --no-save "$(TYPESPEC_HTTP_CLIENT_PYTHON_PATH)"
@if not "$(TYPESPEC_PYTHON_EMITTER_PATH)"=="" cd /d $(TEMP_TSP_DIR) && npm install --silent --no-save "$(TYPESPEC_PYTHON_EMITTER_PATH)"
@echo Generating local Python TypedDict models...
@if exist "$(OUTPUT_DIR)" rmdir /s /q "$(OUTPUT_DIR)"
cd /d $(TEMP_TSP_DIR) && npx tsp compile sdk-service-agentserver-contracts\client.tsp --emit @azure-tools/typespec-python --option "@azure-tools/typespec-python.emitter-output-dir=$(abspath $(TEMP_OUTPUT_DIR))"
@if not exist "$(MODEL_PACKAGE_DIR)" (echo Error: generated model package was not found. 1>&2 && exit /b 1)
@if not exist "$(OUTPUT_DIR)\sdk" mkdir "$(OUTPUT_DIR)\sdk"
@xcopy /E /I /Y "$(MODEL_PACKAGE_DIR)" "$(OUTPUT_DIR)\sdk\models" >NUL
@if exist "$(OUTPUT_DIR)\sdk\models\aio" rmdir /s /q "$(OUTPUT_DIR)\sdk\models\aio"
@if exist "$(OUTPUT_DIR)\sdk\models\operations" rmdir /s /q "$(OUTPUT_DIR)\sdk\models\operations"
@if exist "$(OUTPUT_DIR)\sdk\models\_client.py" del /q "$(OUTPUT_DIR)\sdk\models\_client.py"
@if exist "$(OUTPUT_DIR)\sdk\models\_configuration.py" del /q "$(OUTPUT_DIR)\sdk\models\_configuration.py"
@if exist "$(OUTPUT_DIR)\sdk\models\_version.py" del /q "$(OUTPUT_DIR)\sdk\models\_version.py"
@copy /Y "$(MODEL_SHIMS_DIR)\sdk_models__init__.py" "$(OUTPUT_DIR)\sdk\models\__init__.py" >NUL
@copy /Y "$(MODEL_SHIMS_DIR)\__init__.py" "$(OUTPUT_DIR)\__init__.py" >NUL
@copy /Y "$(MODEL_SHIMS_DIR)\_enums.py" "$(OUTPUT_DIR)\_enums.py" >NUL
@copy /Y "$(MODEL_SHIMS_DIR)\_models.py" "$(OUTPUT_DIR)\_models.py" >NUL
@copy /Y "$(MODEL_SHIMS_DIR)\_patch.py" "$(OUTPUT_DIR)\_patch.py" >NUL
@copy /Y "$(MODEL_SHIMS_DIR)\models_patch.py" "$(OUTPUT_DIR)\sdk\models\models\_patch.py" >NUL
@REM Patch _deserialize_sequence: reject plain strings so union falls through to str branch
@powershell -Command "(Get-Content '$(MODEL_BASE)') -replace 'return type\(obj\)\(_deserialize\(deserializer, entry, module\) for entry in obj\)','if isinstance(obj, str):\n raise DeserializationError()\n return type(obj)(_deserialize(deserializer, entry, module) for entry in obj)' | Set-Content '$(MODEL_BASE)'"
@if exist "$(TEMP_OUTPUT_DIR)" rmdir /s /q "$(TEMP_OUTPUT_DIR)"
cd /d $(TEMP_TSP_DIR) && npx tsp compile sdk-service-agentserver-contracts\client.tsp --emit @azure-tools/typespec-python --option "@azure-tools/typespec-python.emitter-output-dir=$(abspath $(TEMP_OUTPUT_DIR))" --option "@azure-tools/typespec-python.namespace=ai.agentserver.responses.models._generated" --option "@azure-tools/typespec-python.package-name=azure-ai-agentserver-responses" --option "@azure-tools/typespec-python.models-mode=typeddict" --option "@azure-tools/typespec-python.generate-packaging-files=false"
Comment thread
Shivakishore14 marked this conversation as resolved.
python _scripts\extract_model_contracts.py --emitter-output-root "$(TEMP_OUTPUT_DIR)" --generated-root "$(OUTPUT_DIR)"
@if not exist "$(OUTPUT_DIR)" (echo Error: generated model package was not found. 1>&2 && exit /b 1)
@if exist "$(TEMP_OUTPUT_DIR)" rmdir /s /q "$(TEMP_OUTPUT_DIR)"
else
generate-models:
Expand All @@ -135,28 +124,21 @@ generate-models:
cd $(TYPESPEC_DIR) && tsp-client sync
@echo "Installing TypeSpec dependencies from emitter-package.json..."
cd $(TEMP_TSP_DIR) && npm install --silent
@echo "Generating Python models..."
@if [ -n "$(TYPESPEC_HTTP_CLIENT_PYTHON_PATH)" ]; then \
cd $(TEMP_TSP_DIR) && npm install --silent --no-save "$(TYPESPEC_HTTP_CLIENT_PYTHON_PATH)"; \
fi
@if [ -n "$(TYPESPEC_PYTHON_EMITTER_PATH)" ]; then \
cd $(TEMP_TSP_DIR) && npm install --silent --no-save "$(TYPESPEC_PYTHON_EMITTER_PATH)"; \
fi
@echo "Generating local Python TypedDict models..."
rm -rf $(OUTPUT_DIR)
cd $(TEMP_TSP_DIR) && npx tsp compile sdk-service-agentserver-contracts/client.tsp --emit @azure-tools/typespec-python --option "@azure-tools/typespec-python.emitter-output-dir=$(abspath $(TEMP_OUTPUT_DIR))"
@test -d $(MODEL_PACKAGE_DIR) || { \
rm -rf $(TEMP_OUTPUT_DIR)
cd $(TEMP_TSP_DIR) && npx tsp compile sdk-service-agentserver-contracts/client.tsp --emit @azure-tools/typespec-python --option "@azure-tools/typespec-python.emitter-output-dir=$(abspath $(TEMP_OUTPUT_DIR))" --option "@azure-tools/typespec-python.namespace=ai.agentserver.responses.models._generated" --option "@azure-tools/typespec-python.package-name=azure-ai-agentserver-responses" --option "@azure-tools/typespec-python.models-mode=typeddict" --option "@azure-tools/typespec-python.generate-packaging-files=false"
Comment thread
Shivakishore14 marked this conversation as resolved.
python _scripts/extract_model_contracts.py --emitter-output-root "$(TEMP_OUTPUT_DIR)" --generated-root "$(OUTPUT_DIR)"
@test -d $(OUTPUT_DIR) || { \
echo "Error: generated model package was not found." >&2; \
exit 1; \
}
mkdir -p $(OUTPUT_DIR)/sdk
cp -R $(MODEL_PACKAGE_DIR) $(OUTPUT_DIR)/sdk/models
rm -rf $(OUTPUT_DIR)/sdk/models/aio
rm -rf $(OUTPUT_DIR)/sdk/models/operations
rm -f $(OUTPUT_DIR)/sdk/models/_client.py
rm -f $(OUTPUT_DIR)/sdk/models/_configuration.py
rm -f $(OUTPUT_DIR)/sdk/models/_version.py
cp $(MODEL_SHIMS_DIR)/sdk_models__init__.py $(OUTPUT_DIR)/sdk/models/__init__.py
cp $(MODEL_SHIMS_DIR)/__init__.py $(OUTPUT_DIR)/__init__.py
cp $(MODEL_SHIMS_DIR)/_enums.py $(OUTPUT_DIR)/_enums.py
cp $(MODEL_SHIMS_DIR)/_models.py $(OUTPUT_DIR)/_models.py
cp $(MODEL_SHIMS_DIR)/_patch.py $(OUTPUT_DIR)/_patch.py
cp $(MODEL_SHIMS_DIR)/models_patch.py $(OUTPUT_DIR)/sdk/models/models/_patch.py
# Patch _deserialize_sequence: reject plain strings so union falls through to str branch
sed -i 's/ return type(obj)(_deserialize(deserializer, entry, module) for entry in obj)/ if isinstance(obj, str):\n raise DeserializationError()\n return type(obj)(_deserialize(deserializer, entry, module) for entry in obj)/' $(MODEL_BASE)
rm -rf $(TEMP_OUTPUT_DIR)
endif

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env python
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
"""Extract local OpenAI Responses model contracts from generated output."""

from __future__ import annotations

import re
import shutil
from pathlib import Path


ROOT_INIT_PREFIX = (
"# coding=utf-8\n"
"# --------------------------------------------------------------------------\n"
"# Copyright (c) Microsoft Corporation. All rights reserved.\n"
"# Licensed under the MIT License. See License.txt in the project root for license information.\n"
"# Code generated by Microsoft (R) Python Code Generator.\n"
"# Changes may cause incorrect behavior and will be lost if the code is regenerated.\n"
"# --------------------------------------------------------------------------\n\n"
"from ._unions import * # type: ignore # noqa: F401,F403\n"
"from .types import * # type: ignore # noqa: F401,F403\n"
)

ENUM_FALLBACK_PREFIX = (
"# coding=utf-8\n"
"# --------------------------------------------------------------------------\n"
"# Copyright (c) Microsoft Corporation. All rights reserved.\n"
"# Licensed under the MIT License. See License.txt in the project root for license information.\n"
"# --------------------------------------------------------------------------\n\n"
"import keyword\n"
"import re\n"
"from enum import Enum\n\n"
"from azure.core import CaseInsensitiveEnumMeta\n\n\n"
"_ENUM_VALUES: dict[str, dict[str, str]] = "
)

ENUM_FALLBACK_SUFFIX = (
"\n\n"
"def _normalize_enum_value(value: str) -> str:\n"
" return ''.join(ch for ch in value.upper() if ch.isalnum())\n\n\n"
"def _enum_member_name(value: str) -> str:\n"
" name = re.sub(r'[^A-Z0-9]+', '_', value.upper()).strip('_')\n"
" name = re.sub(r'(?<=\\d)(?=[A-Z])', '_', name)\n"
" if not name or name[0].isdigit() or keyword.iskeyword(name):\n"
" name = f'ENUM_{name}'\n"
" return name\n\n\n"
"def _build_enum(enum_name: str, enum_values: dict[str, str]) -> type[Enum]:\n"
" namespace = CaseInsensitiveEnumMeta.__prepare__(enum_name, (str, Enum))\n"
" namespace['__module__'] = __name__\n"
" namespace['__doc__'] = f'Type of {enum_name}.'\n"
" for normalized_name, wire_value in enum_values.items():\n"
" if normalized_name.isidentifier() and not keyword.iskeyword(normalized_name):\n"
" namespace[normalized_name] = wire_value\n"
" safe_name = _enum_member_name(wire_value)\n"
" if safe_name not in namespace:\n"
" namespace[safe_name] = wire_value\n"
" return CaseInsensitiveEnumMeta(enum_name, (str, Enum), namespace)\n\n\n"
"_ENUM_CLASSES = {name: _build_enum(name, values) for name, values in _ENUM_VALUES.items()}\n"
"globals().update(_ENUM_CLASSES)\n\n\n"
"def __getattr__(name: str) -> type[Enum]:\n"
" try:\n"
" return _ENUM_CLASSES[name]\n"
" except KeyError as exc:\n"
" raise AttributeError(f'module {__name__!r} has no attribute {name!r}') from exc\n"
)


def _remove_pycache(root: Path) -> None:
for pycache in root.rglob("__pycache__"):
shutil.rmtree(pycache)


def _find_emitted_models_root(emitter_output_root: Path) -> Path:
candidates = sorted(
path
for path in emitter_output_root.rglob("types.py")
if path.parent.name == "models"
and (path.parent / "_unions.py").exists()
and (path.parent / "models").is_dir()
)
if not candidates:
raise FileNotFoundError(f"Could not find emitted TypedDict model package under {emitter_output_root}")
if len(candidates) > 1:
formatted = "\n".join(str(path.parent) for path in candidates)
raise RuntimeError(f"Found multiple emitted model packages:\n{formatted}")
return candidates[0].parent


def _normalize_enum_value(value: str) -> str:
return re.sub(r"[^A-Z0-9]", "", value.upper())


def _strip_trailing_whitespace(path: Path) -> None:
path.write_text(
"\n".join(line.rstrip() for line in path.read_text(encoding="utf-8").splitlines()) + "\n",
encoding="utf-8",
)


def _literal_enum_values(types_py: Path) -> dict[str, dict[str, str]]:
text = types_py.read_text(encoding="utf-8")
enum_values: dict[str, dict[str, str]] = {}
for match in re.finditer(r"(?ms)^([A-Za-z][A-Za-z0-9_]*) = Literal\[(.*?)\]\n\"\"\"", text):
enum_name = match.group(1)
values = re.findall(r'"([^"]+)"', match.group(2))
if values:
enum_values[enum_name] = {_normalize_enum_value(value): value for value in values}
return enum_values


def _build_enum_fallback(types_py: Path) -> str:
return ENUM_FALLBACK_PREFIX + repr(_literal_enum_values(types_py)) + ENUM_FALLBACK_SUFFIX


def _build_root_init(types_py: Path) -> str:
enum_names = sorted(_literal_enum_values(types_py))
if not enum_names:
return ROOT_INIT_PREFIX
enum_lines = "\n".join(f'{name} = getattr(_generated_enums, "{name}")' for name in enum_names)
return (
ROOT_INIT_PREFIX
+ "from . import _enums as _generated_enums\n\n"
+ enum_lines
+ "\n"
)


def finalize(emitter_output_root: Path, generated_root: Path) -> None:
"""Copy the real emitted TypedDict package into the local generated package boundary."""
emitted_root = _find_emitted_models_root(emitter_output_root)

if generated_root.exists():
shutil.rmtree(generated_root)
generated_root.mkdir(parents=True)

for file_name in ("types.py", "_unions.py", "py.typed"):
shutil.copy2(emitted_root / file_name, generated_root / file_name)
_strip_trailing_whitespace(generated_root / "types.py")

models_root = generated_root / "models"
models_root.mkdir()
for file_name in ("__init__.py", "_patch.py"):
shutil.copy2(emitted_root / "models" / file_name, models_root / file_name)

(generated_root / "__init__.py").write_text(_build_root_init(generated_root / "types.py"), encoding="utf-8")
(generated_root / "_enums.py").write_text(_build_enum_fallback(generated_root / "types.py"), encoding="utf-8")
_remove_pycache(generated_root)


def main() -> None:
import argparse

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--emitter-output-root", type=Path, required=True)
parser.add_argument("--generated-root", type=Path, required=True)
args = parser.parse_args()
finalize(args.emitter_output_root, args.generated_root)


if __name__ == "__main__":
main()

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading