Fix C# type conflicts between hand-written and generated partial classes#346
Merged
Conversation
sethjuarez
force-pushed
the
sethjuarez/spec-type-system-overhaul
branch
2 times, most recently
from
April 16, 2026 14:32
d550a5d to
f07f00d
Compare
…e spec Phase 1-3 of the spec & type system overhaul: TypeSpec additions (schema/model/): - message.tsp: Message, Role, ContentPart hierarchy, ToolCall, ToolResult - events.tsp: AgentEventType, StreamChunk hierarchy - guardrails.tsp: GuardrailResult, GuardrailPhase - discovery.tsp: ModelInfo for provider model listing TypeSpec changes: - tools/prompty.tsp: Remove mode field and promptyToolMode alias (PromptyTool is always single-shot) Emitter changes: - Add additional-roots config option for standalone types not reachable from the root Prompty model - Update filterNodes to include additional model trees spec.md changes: - §6.5: Reference TypeSpec for Message/ContentPart types, add ToolResult docs - §9.2: Tool handlers return ToolResult (not string) - §9.4: FormatToolMessages handles ContentPart[] in tool results - §9.5: Rich content examples for OpenAI and Anthropic wire formats - §9.7: PromptyTool is single-shot only, MUST NOT run agent loop - §13.1: Reference TypeSpec AgentEventType, tool_result carries ToolResult - §13.5: Add steering principle and normative requirements - §15: New section for model discovery convention (SHOULD, not MUST) Generated code regenerated for all runtimes (TS, Python, C#, Rust, Go). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ping, stale Rust tests Three emitter bugs fixed: 1. Python save_parts serialization: Collections of types without a 'name' property (ContentPart[], StreamChunk[]) were incorrectly using object-format serialization, causing roundtrip failures. Added hasNameProperty guard to save methods in Python template (matching existing TS/C# behavior). 2. Test string escaping: Python and C# escapeString were no-ops, causing syntax errors in generated tests when @sample values contained double quotes (e.g., ToolCall.arguments with JSON). Now properly escape backslashes and quotes. 3. Stale Rust test files: Polymorphic child types (PromptyTool, FunctionTool, etc.) no longer get individual test files (tested via parent's enum), but old generated files persisted on disk. Deleted 13 stale files. Also fixed load_vectors.rs to remove reference to deleted PromptyTool.mode field. Test results: Python 254/254, TypeScript 334/334, Rust 549/549. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… generation Add verb enforcement infrastructure to the TypeSpec emitter: - @factory decorator: generates static factory methods (fully generated) - GuardrailResult: allow(), deny(reason), rewrite(value) - All 4 runtimes: Rust, TypeScript, Python, C# - @Helper decorator: generates trait/interface stubs (hand-implemented) - Message: text() helper trait in Rust - Other runtimes: pending stub generation - C# factory method name collision detection (Rewrite prop → CreateRewrite method) - ToolResult.fromText deferred to extension files (too language-specific) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…across all runtimes Remove duplicative GuardrailResult definitions from all 4 runtimes and replace with the TypeSpec-emitted model type, using generated factory methods everywhere. Rust: - Remove struct+impl from guardrails.rs, re-export from crate::model - All 538 tests passing TypeScript: - Remove interface from core/guardrails.ts, import generated class - Update all callsites to use GuardrailResult.allow(), .deny(), .rewrite() - Add type assertions for rewrite (unknown vs Message[]) in pipeline.ts - All 371 tests passing Python: - Remove dataclass from core/guardrails.py, import from prompty.model - Add __post_init__ to fix rewrite field/classmethod name collision - Update all callsites to factory methods - All 316 tests passing (1 pre-existing anthropic skip) C#: - Remove hand-written record from Guardrails.cs - Update to Allow(), Deny(), CreateRewrite() factory methods - Pre-existing duplicate type errors in Types.cs unchanged Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s with create_ In Python, a @classmethod with the same name as a dataclass field shadows the field's default value. The emitter now pre-computes safe factory method names: if a factory name (in snake_case) matches any field name, it gets prefixed with 'create_' (e.g., rewrite -> create_rewrite). This mirrors the existing C# collision detection that prefixes with 'Create'. Rust and TypeScript don't need this fix (methods/statics don't collide with fields in those languages). Changes: - schema/emitter/src/ast.ts: Add factoryNameMap to PythonClassContext - schema/emitter/src/python.ts: Compute safe names in buildClassContext() - schema/emitter/src/templates/python/file.py.njk: Use factoryNameMap - runtime/python: Update 3 callsites from .rewrite() to .create_rewrite() - runtime/python: Remove __post_init__ workaround (no longer needed) Verified: Rust 538, TypeScript 371, Python 414 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…TypeScript, Python Remove duplicate ToolCall definitions from types.rs (Rust), types.ts (TypeScript), and processor.py (Python). Each runtime now re-exports the generated ToolCall from the model layer. C# was already using the generated type. - Rust: pub use crate::model::ToolCall in types.rs (538 tests pass) - TypeScript: re-export from model/tool-call.ts in core/types.ts (743 tests pass) - Python: import from model in processor.py, re-export for backward compat (909 tests pass) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace camelCase attribute accesses with snake_case equivalents to match the updated agentschema model types: - agent.model.apiType -> agent.model.api_type - conn.apiKey -> conn.api_key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update test files to use snake_case field names matching the regenerated agentschema model types: - apiType -> api_type - apiKey -> api_key - maxOutputTokens -> max_output_tokens - serverName -> server_name - additionalProperties -> additional_properties Dict key accesses (YAML/JSON keys) remain camelCase as intended. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix Python emitter to generate snake_case field names (was camelCase) - file.py.njk: snakeCase filter on field decls, collection methods, save access, factories - _macros.njk: snakeCase filter on setInstance/saveProperty field access - test-context.ts: Python renderKey now uses toSnakeCase - Update all Python runtime code for snake_case model attributes: - providers/openai/executor.py (29 changes) - providers/anthropic/executor.py (11 changes) - providers/foundry/executor.py (6 changes) - 6 test files updated - Add PromptyTool.mode field to TypeSpec (alias: 'single' | 'agentic') - Enables declarative sub-agent delegation via mode: agentic - Default: 'single' (single-shot invoke, no agent loop) - Regenerated across all 4 runtimes + schemas + docs - Clean up test_spec_vectors.py: filter extension vectors at parametrization level instead of pytest.skip() inside test body - Eliminates 17 spurious SKIPPED results Python: 989 passed, 0 failed, 0 skipped Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…erde The ToolCall struct was swapped to the emitted model type which doesn't derive serde::Serialize. Updated test to use the emitted to_value() method with SaveContext instead of serde_json::to_value(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
to_dict() used hasattr(obj, 'model_dump') to detect Pydantic models, but MagicMock fakes every attribute. This caused recursive model_dump() calls on mocks, each spawning new mocks — ~4s per test. Fix: check hasattr(type(obj), 'model_dump') instead, which only matches real Pydantic classes where model_dump is defined on the class. Result: 989 tests in 10.73s (was ~10 minutes). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…odel core/types.py no longer defines ContentPart, TextPart, ImagePart, FilePart, AudioPart, or Message. Instead it re-exports the generated types from prompty.model and attaches runtime helpers (.text property, .to_text_content() method, metadata default) via monkey-patching. Zero import changes needed at any consumer site — all code that does 'from prompty.core.types import Message' still works identically. 989 tests pass in 10.47s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove hand-written ContentPart, TextPart, ImagePart, FilePart, AudioPart, Message from Types.cs and ToolCall from Pipeline.cs — these are now provided by the generated classes in Model/. Key changes: - Make generated Message partial; add MessageHelpers.cs with Text property and ToTextContent() convenience method - Change Message.Metadata to IDictionary<string, object?> with non-null default (matching existing usage patterns) - Change Message.Role default to "user" (was string.Empty) - Fix WireFormat.cs BuildContentParts param: List -> IList - Fix test files using Metadata = new() (interfaces need explicit ctor) - Fix ToolCallConversionTests.cs string literal encoding (curly quotes) - Fix GuardrailResult.cs null check on non-nullable bool (pre-existing) All 836 tests pass (792 succeeded, 44 skipped — missing API keys). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate all Message instantiations from positional args (role, parts, metadata) to the new generated class constructor taking a Partial<Message> init object. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate all 6 Message constructor calls from positional args
(role, parts, metadata) to the new single options object pattern
({ role, parts, metadata }).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate Message and ContentPart construction to the new Partial<T> init
object pattern from the generated model classes:
- new Message(role, parts, metadata) -> new Message({ role, parts, metadata })
- { kind: 'text', value } -> new TextPart({ value })
- { kind: 'image', source } -> new ImagePart({ source })
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntentPart classes - Change Message constructor from positional args to options object pattern - Replace plain object content part literals with TextPart, ImagePart, AudioPart instances - Add TextPart, ImagePart, AudioPart to imports from @prompty/core Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate all Message instantiations from positional args to the new
Partial<Message> init object pattern:
- new Message(role, parts) → new Message({ role, parts })
- new Message(role, parts, metadata) → new Message({ role, parts, metadata })
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update compaction, resilience, registry, and anthropic e2e tests to use the options-object Message constructor and proper ContentPart class instances instead of plain object literals. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…types Replace hand-written ContentPart/TextPart/ImagePart/FilePart/AudioPart interfaces and Message class in core/types.ts with re-exports from the auto-generated model/ directory. Add Message prototype augmentation for text getter and toTextContent() method. Key changes: - types.ts: Re-export generated model types, add module augmentation - types.ts helpers: text(), textMessage(), dictToMessage(), dictContentToPart() now return class instances instead of plain objects - index.ts: Remove 'type' keyword from ContentPart exports (now classes) - pipeline.ts, steering.ts, context.ts, parsers/prompty.ts: Update Message constructor from positional to Partial<Message> init object - openai/executor.ts, anthropic/executor.ts: Same constructor updates - openai/wire.ts, anthropic/wire.ts: Add explicit casts for ContentPart subclass properties (TextPart, ImagePart, etc.) since generated abstract class uses kind: string (no discriminated union narrowing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The loadParts template unconditionally assumed all collection element
types have a 'name' field (dict-to-array pattern). This broke for
ContentPart collections in Message and ToolResult. Added the same
{% if collection.hasNameProperty %} guard that Python and C# already
use. Types without 'name' now only accept array format.
Regenerated all model files. TS core: 744 tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…json::Value accessors The emitter now generates typed fields (Vec<Property>, Vec<Tool>, Vec<Binding>) on the Prompty struct instead of serde_json::Value with accessor methods. Updated all call sites across the workspace: - pipeline.rs: replace as_inputs/as_outputs/as_tools with direct field access - renderers/common.rs: iterate &agent.inputs directly - tool_dispatch.rs: change ToolHandlerTrait to accept &Tool, rewrite helpers - prompty-openai wire.rs/processor.rs: typed tools, outputs, parameters - prompty-anthropic wire.rs/processor.rs: same patterns - loader_test.rs, load_vectors.rs: update test assertions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ull safety - TypeSpec: add default 'user' for Message.role (propagates to all runtimes) - C# emitter: use verbatim strings for sample values containing quotes - C# runtime: null-safe Metadata access in Pipeline, PromptyChatParser, WireFormat, ContextWindow, AnthropicExecutor - C# test: null-safe Metadata check in ParserTests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove PromptyToolMode and mode field from TypeSpec schema - Regenerate all runtimes via emitter - Wire ToolResult (with Vec<ContentPart>) through entire Rust pipeline: - ToolHandler/ToolFn/AsyncToolFn return ToolResult instead of String - dispatch_tool returns ToolResult - format_tool_messages takes &[ToolResult] - AgentEvent::ToolResult carries ToolResult - Add From<String>, From<&str>, from_text(), text(), to_message_parts() on ToolResult - Update prompty-openai wire format to handle rich content parts - Update prompty-anthropic wire format for rich tool results - Update prompty-foundry executor signature - PromptyToolHandler always uses invoke() (single-shot), no mode branch - Re-export ToolResult from lib.rs - All 538 Rust tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d type augmentation
- Replace module augmentation (declare module + Object.defineProperty) with
standalone utility functions: messageText(), messageToTextContent(),
toolResultText(), textToolResult()
- Update ToolHandler.executeTool() return type: string → ToolResult
- Update dispatchTool() and formatToolMessages() signatures across all packages
- Change ToolCall construction from object literals to new ToolCall({...})
- Fix metadata optional access patterns (msg.metadata → msg.metadata ?? {})
- Add default case to partToWire switch statements (openai, anthropic)
- Remove mode field from PromptyTool spec vectors (load_vectors.json)
- Update all test files to use standalone utility functions
- All 884 tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ptyToolHandler - Changed all tool handler return types from str to ToolResult - Added standalone helpers: text_tool_result(), tool_result_text(), to_tool_result() - Removed mode branch from PromptyToolHandler (always single-shot invoke) - Updated format_tool_messages in OpenAI/Anthropic to accept list[ToolResult] - Updated pipeline dispatch functions to return list[ToolResult] - Updated all test assertions for ToolResult-aware comparisons - 989 tests pass, ruff clean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…omptyToolHandler - Add ToolResultHelpers.cs partial class with FromText(), Text property, and implicit string-to-ToolResult conversion - Change ToolHandler delegate: Task<string> → Task<ToolResult> - Update ToolDispatch registries and DispatchAsync return type - Update Pipeline.cs tool dispatch loop (parallel + sequential) - Update ToolAttribute DiscoverTools/BindTools handler return types - Update OpenAI/Anthropic FormatToolMessages: List<string> → List<ToolResult> - Update all test files: mock executors, tool handler dicts, assertions - All 792 C# tests pass (561 core + 231 provider) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regenerated output from tsp compile after Go emitter changes. No behavioral changes — existing tests pass (261 Python, 341 TypeScript). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add new TypeSpec models for types previously only in spec.md prose: - pipeline.tsp: TurnOptions, CompactionConfig - usage.tsp: TokenUsage - events.tsp: 10 typed event payload models (Token, Thinking, ToolCallStart, ToolResult, Status, MessagesUpdated, Done, Error, CompactionComplete, CompactionFailed) - tools.tsp: ToolContext, ToolDispatchResult All types are generated into 5 runtimes (Python, TypeScript, Rust, C#, Go). All 6 test suites pass (IR:122, Python:331, TS:457, C#:652, Go:pass, Rust:pass). Intentionally excluded errors.tsp — error types need language-specific exception base classes, making them a poor fit for code generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add provider parameter to @knownas and @defaultFor decorators - Create wire/openai.tsp with OpenAI Chat Completions API field mappings - Create wire/anthropic.tsp with Anthropic Messages API field mappings - Wire knownAs/defaultFor through PropertyNode in AST - Add WireDecl/WireFieldMapping to Declaration IR - Add lowerWire() pass to build wire declarations from knownAs data - Generate toWire(provider) methods in all 5 language emitters: - TypeScript: toWire(provider: string): Record<string, unknown> - Python: to_wire(self, provider: str) -> dict[str, Any] - C#: ToWire(string provider): Dictionary<string, object?> - Go: ToWire(provider string) map[string]interface{} - Rust: to_wire(&self, provider: &str) -> serde_json::Value - Add serde::Serialize derive for Rust structs with wire mappings - Generated toWire() on ModelOptions and TokenUsage in all runtimes - All 6 test suites pass (IR:122, Python:331, TS:457, Go, C#, Rust) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cript Replace manual ModelOptions field mapping in buildOptions/buildResponsesOptions with the generated ModelOptions.toWire(provider) method in both OpenAI and Anthropic wire.ts files. Update Anthropic buildChatArgs to use toWire result for max_tokens with DEFAULT_MAX_TOKENS fallback. Fix Anthropic e2e tests to use ModelOptions instances instead of plain objects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…on executors
Replace manual ModelOptions field mapping in _build_options and
_build_responses_options with the generated to_wire() method across
OpenAI, Responses API, and Anthropic executors.
- OpenAI: _build_options uses to_wire('openai')
- Responses API: _build_responses_options uses to_wire('responses')
- Anthropic: _build_options uses to_wire('anthropic'), _build_chat_args
simplified to use wire result for max_tokens default fallback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add protocol interface generation to 4 runtimes (TS, Python, Rust, Go): - TypeScript: export interface with async methods - Python: typing.Protocol class with sync + async variants - Rust: #[async_trait] pub trait with Send + Sync bounds - Go: interface type with method signatures Infrastructure changes: - @method decorator accepts optional params parameter for method signatures - IR declarations.ts: isProtocol on TypeDecl, params on MethodStubDecl - lower.ts: extractMethodTypeRefs() for protocol import resolution - All 5 drivers skip test generation for protocol types - C# driver skips protocol emission entirely (conflicts with hand-written Interfaces.cs) Wire format fixes: - Python toWire() now only maps fields with explicit provider mappings - Added identity mappings in openai.tsp and anthropic.tsp for strict mode - Added OpenAI Responses API wire mappings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tions to_wire() serializes None Option fields as JSON null. The old hand-written code skipped None fields entirely. Without this fix, null values leak into the request body causing vector test mismatches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Rust/Go helpers - Fix nullable protocol type imports in IR lowering (strip ? suffix before scalar check) - Enable C# protocol generation (IRenderer, IParser, IExecutor, IProcessor) - Migrate C# FormatToolMessages → FormatToolMessagesAsync across all implementations - Extract IPreRenderable.cs from deleted Interfaces.cs - Add Rust MessageHelpers + ToolResultHelpers trait implementations (model_ext.rs) - Add Go MessageHelpers + ToolResultHelpers implementations (helpers.go) - Add #![allow(unused_imports, dead_code)] to Rust generated headers - Add nullable type support to all 5 protocol type mappers (C#, TS, Rust, Python, Go) All 6 test suites pass: IR (122), TS (457), Python (331), Rust (587), C# (883), Go (235) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sethjuarez
force-pushed
the
sethjuarez/spec-type-system-overhaul
branch
from
April 16, 2026 15:09
f07f00d to
9be0745
Compare
- Run black on 90 generated Python model/test files - Run dotnet format on generated C# model files - Convert 3 sync test methods to async Task (xUnit1031 warning fix) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace regex-based MIME extraction with indexOf/slice to prevent ReDoS (high) - Add backslash escaping in C# test string literals (medium) - Replace all execSync shell commands with execFileSync across all drivers to prevent shell injection from paths (high + medium) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Python CI enforces 'ruff format --check', not 'black --check'. Replace 'uv run black' with 'uv run ruff format' in the emitter's post-compile formatting step so generated files pass CI automatically. Also apply ruff format to the 18 files that were out of compliance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…elds The Python emitter now generates snake_case field names on dataclasses (e.g. api_type, api_key, max_output_tokens, enum_values, server_name). Update all hand-written runtime code and tests to use the new names. Changes: - providers/openai/executor.py: .apiType -> .api_type, .apiKey -> .api_key, .enumValues -> .enum_values - providers/anthropic/executor.py: same pattern - providers/foundry/executor.py: .apiType -> .api_type, .apiKey -> .api_key - tests/test_loader.py: .apiType, .apiKey, .maxOutputTokens, .serverName - tests/test_spec_vectors.py: .apiType, .apiKey (getattr), .maxOutputTokens, .serverName (getattr) - tests/test_structured.py: .apiType -> .api_type - tests/test_anthropic.py: .apiType -> .api_type Dict key access (wire format) is intentionally unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Dictionary<string, object?>? context parameter to all mock IParser implementations in test files to match the updated IParser interface. Also fix FormatToolMessages return type mismatches (return List<Message> directly instead of wrapping in Task.FromResult). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…c support - Extend @@method decorator with optional and sync flags - Update all 5 language emitters for optional/sync protocol methods - Add preRender (optional, sync) to Parser protocol - Add context parameter to Parser.parse - Mark formatToolMessages as sync (not async) - Fix TS emitter: definite assignment (!) for required complex fields - Update C# runtime: ParseAsync context param, FormatToolMessages sync - All test suites green: Emitter 130, Python 1066, TS 60, Rust 587, C# 883 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ta fix - Add executeStream (optional) to Executor protocol in TypeSpec - Add processStream (optional) to Processor protocol in TypeSpec - Add ThreadMarker model to message.tsp (replaces hand-written versions) - Add toTextContent and text @@method stubs on Message - Make Message.metadata non-optional with empty dict default - Wire tsp format into schema build pipeline (format:tsp script) - Fix Rust emitter: optional async defaults return Err not Ok(None) - Fix C# ThreadMarker: use partial class to merge with generated - Implement MessageHelpers.to_text_content() in Rust model_ext.rs - All 6 runtimes + emitter tests passing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…urce Replace hand-written Message, ContentPart, TextPart, ImagePart, FilePart, AudioPart, and ThreadMarker classes in core/types.py with re-exports from the generated model/ package. Add .text property and .to_text_content() method via monkey-patching on the generated Message class. This eliminates the duplicate type definitions while preserving the full public API surface. All 1,071 Python tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e types The Python emitter previously emitted a comment block for @method declarations on concrete types. Now it emits a real <TypeName>Helpers Protocol class with @runtime_checkable, proper signatures, and docstrings from the @method decorator descriptions. For Message this produces MessageHelpers(Protocol) with text() and to_text_content(). Runtime code can be type-checked against this contract. Matches what Rust and Go emitters already do. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… types The TypeScript emitter previously emitted `// @method X(): T — desc'' comments inside each generated class for @method declarations. Now it emits a real exported <TypeName>Helpers interface alongside the class, with proper signatures and JSDoc comments from the @method decorator descriptions. For Message this produces `export interface MessageHelpers'' with text() and toTextContent(). Runtime code annotated as MessageHelpers will be structurally type-checked by the TS compiler. Matches what Rust, Go, and now Python emitters emit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit a public partial interface IMessageHelpers / IToolResultHelpers after each generated class with methods. The class declaration now includes `: I<Name>Helpers` so the C# compiler enforces that a hand-written partial class provides the declared members. Zero-param methods are emitted as properties unless the name starts with a verb prefix (to, get, fetch, compute, make, build, create, load, save, convert, parse, format, render, serialize, deserialize, find, calculate, invoke, execute, run). This matches existing runtime conventions: Message.Text (property), Message.ToTextContent() (method). Factory name collision detection was extended to also consider @method names emitted as properties. For ToolResult, the factory @factory(`text`, ...) collides with the property Text from @method(`text`, ...), so the factory is renamed to CreateText. - Replace comment-only #region Helpers body with a real interface - Rename ToolResult.Text(string) factory to CreateText - Rename TextContent helper to Text to satisfy IToolResultHelpers - Update one test call site Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sethjuarez
force-pushed
the
sethjuarez/spec-type-system-overhaul
branch
from
April 17, 2026 03:11
4133952 to
18cf57d
Compare
Split the flat schema/model/ directory into semantically grouped subfolders
so contributors can navigate the TypeSpec sources by concept area:
agent/ - Prompty agent + guardrails
connection/ - Connection types
core/ - base aliases + Property schema
discovery/ - ModelInfo
events/ - AgentEvent payloads + StreamChunk
message/ - Message + ContentPart + ToolCall/Result
model/ - Model + ModelOptions + TokenUsage
pipeline/ - TurnOptions + CompactionConfig
protocols/ - Renderer/Parser/Executor/Processor protocols
template/ - Template + Format/Parser configs
tools/ - base.tsp (Tool/FunctionTool/CustomTool/Binding/
ToolContext/ToolDispatchResult) + mcp/openapi/prompty
wire/ - openai + anthropic knownAs mappings
Zero emitter changes: the emitter still writes flat output. The only
generated-code diffs are ordering changes in four barrel files (Python
__init__.py, Rust mod.rs + tests/model/main.rs, TypeScript index.ts)
caused by the new source-file iteration order. Functional contents are
identical.
Python tests: 1071 passed, 17 skipped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sethjuarez
force-pushed
the
sethjuarez/spec-type-system-overhaul
branch
from
April 17, 2026 04:25
99cc355 to
6534670
Compare
- Rename message/ to conversation/ and split into 4 files (message, content, tool-invocation, thread) - Merge protocols/ into pipeline/ as 4 per-protocol files (renderer, parser, executor, processor) - Rename pipeline/pipeline.tsp to pipeline/turn.tsp - Merge discovery/ into model/discovery.tsp - Split tools/base.tsp into tools/tool.tsp + tools/dispatch.tsp; delete obsolete tools/main.tsp - Split events/events.tsp into payloads.tsp + stream-chunks.tsp - Split connection/connection.tsp: extract OAuthConnection and FoundryConnection into their own files - Update main.tsp and all cross-file imports; regenerate runtimes (ordering-only diffs) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…istic - Emitter: zero-param @method stubs with non-verb names now emit as`@property` in the generated `<Type>Helpers` Protocol (matches theC# emitter's `isMethodStyle` heuristic). Accessor-style helpers like`Message.text` stay idiomatic; verbs like `to_text_content` remainmethods. - Emitter: `<Type>Helpers` Protocol classes are now exported from thegenerated `model/__init__.py` alongside their concrete types. This lets runtimes `isinstance(msg, MessageHelpers)` without reaching into private `_Message.py`. - Runtime: `core/types.py` now imports `MessageHelpers` from the generated model, attaches `text` as a `property` (matching the Protocol), and adds a module-level `isinstance` assertion so the emitter contract is verified at import time. - 1,071 Python tests still pass.
…lpers contract Emitter: zero-param @method stubs with non-verb names now emit as`readonly <name>: <type>` in the generated `<Type>Helpers`interface (matches the C#/Python verb heuristic). Accessor-stylehelpers like `Message.text` stay idiomatic; verbs like`toTextContent` remain methods. Runtime: hand-written `Message` class in `core/types.ts` now declares`implements MessageHelpers` so the TypeScript compiler enforces thegenerated contract. Full consolidation onto the generated class (deletinghand-written `class Message` and migrating positional `newMessage(role, parts)` call sites to named `newMessage({role, parts})`) is deferred to a follow-up — ~150 call sitesacross tests and providers. All TS tests still pass.
…pages The markdown emitter now renders the `@method` contract declarations and `@factory` helpers on each type's reference page. This makes the human-readable reference docs the canonical spec.md companion to the TypeSpec source, completing the Blank-Folder Test requirement that the implementation surface for each type is discoverable from the emitted output alone. Reference pages for Message, ToolResult, Executor, Parser, Processor, Renderer, and GuardrailResult now include a Helper Methods table (name, signature, description, optional/sync flags) and a Factory Methods list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves type conflicts in the Prompty C# runtime where hand-written types in
Types.cs,Pipeline.cs, andGuardrails.csconflicted with generatedpartial classtypes in theModel/directory (33 compilation errors).Changes
Types.cs — removed duplicate type definitions
ContentPart,TextPart,ImagePart,FilePart,AudioPart,Message(all duplicated by generatedModel/*.cs)Roles,RichKinds,ThreadMarker(unique, not generated)Pipeline.cs — removed duplicate ToolCall
ToolCallclass (duplicated byModel/ToolCall.cs)ToolCallResultandExecuteErrorGuardrails.cs — removed duplicate GuardrailResult
record GuardrailResult(...)(conflicts withModel/GuardrailResult.cs)Model/GuardrailResultExtensions.cswith a convenience constructorGuardrailResult(bool, string?, object?)to preserve existing call-site syntaxMetadata null safety
The generated
Message.MetadataisIDictionary<string, object>?(nullable), while the old hand-written type used non-nullDictionary<string, object?>. Fixed all null-unsafe accesses:ContextWindow.cs— 3Metadata.TryGetValue()calls →Metadata is not null &&Pipeline.cs— 2new Dictionary<>(msg.Metadata)→ null-checkedPromptyChatParser.cs—message.Metadata[...]→Metadata ??= new Dictionary<>()WireFormat.cs— 4Metadata.TryGetValue()calls → null-checkedAnthropicExecutor.cs— 1Metadata.TryGetValue()call → null-checkedIList vs List compatibility
WireFormat.BuildContentParts()parameter:List<ContentPart>→IList<ContentPart>(matches generatedMessage.Partstype)Test fixes
Metadata = new() { ... }→Metadata = new Dictionary<string, object> { ... }(cannot target-typenew()onIDictionaryinterface)Rewrite:(PascalCase record param) →rewrite:(lowercase constructor param)Metadata.ContainsKey()→Metadata?.ContainsKey() ?? falseVerification