Skip to content

exp2/1: port codegen #42/#43/#53 onto upstream v4/13 (70c8fff) - #57

Draft
cunninghamcard-bit wants to merge 8 commits into
v4-base/13from
exp2/1-codegen-port
Draft

cunninghamcard-bit wants to merge 8 commits into
v4-base/13from
exp2/1-codegen-port

Conversation

@cunninghamcard-bit

@cunninghamcard-bit cunninghamcard-bit commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Draft, fork-internal. Ports fork #42 / #43 / #53 onto upstream v4/13 @ 70c8fff (arcships/aimux PR arcships#261). Base v4-base/13 is upstream 70c8fff pushed to this fork by this unit (no mirror existed). Upstream head has since moved to e084dc9 (docs-only: .agents/skills); not used.

Commits

v4 adaptations (a9ee048)

  • #[serde(transparent)] -> Newtype (NamespacedKind, aimux-core/src/types.rs:354); extract.rs:761, :1001
  • TS-only tagged wrappers (no serde derive; #[ts(rename="type", type=..)] + #[ts(flatten)]): TypedToolCall tool.rs:198, TaggedToolResult result.rs:163, TypedSource result.rs:325; extract.rs:668, :1020. Struct shape only, no new variants (RFC-0042 T1). ToolCall stays flat (tool.rs:145).
  • FIELD_CODECS (extract.rs:78-235) re-registered for v4 serde fns: crate::wire::present (12 fields, helper extract.rs:218), present tri-state on DecisionBooleanCriteria (decision_model.rs:87), deserialize_known_schema (recording.rs:95), deserialize_prompt (recording.rs:189), non_null_json (tool.rs:219), crate::wire::typed::deserialize (parse_tool_call.rs:468).
  • Dropped stale specials: BooleanCriteria x2 + deserialize_current_schema (evaluation_model.rs gone in v4), model_id.rs category.
  • VERSIONS.json v4 is {version, integrity} per package; manifest sdk records it verbatim (manifest.rs:81).
  • PoC TS/Go for ToolCall: regenerated, byte-identical (v4 ToolCall fields unchanged).

Hard-coded type names left in codegen src (all exist in v4)

Recording recording.rs:87; InputRecord recording.rs:183; ToolResult tool.rs:210; ToolCallRepairReply parse_tool_call.rs:460; DecisionBooleanCriteria decision_model.rs:87; ApiCallError error.rs:34; EmbeddingResponse embedding_model.rs:117; RerankingResponse reranking_model.rs:135; SearchResponse search_model.rs:169; ResponseInfo shared.rs:398; RequestInfo shared.rs:431; SpeechRequest speech_model.rs:153; SpeechResponse speech_model.rs:168; StepResponse step_result.rs:190; TranscriptionResponse transcription_model.rs:226; TranscriptionStreamPart transcription_model.rs:316; AssistantContentPart content.rs:85. No handWritten/flatten/tag/rename special case is keyed by type name (those are attribute-driven).

closure-dtos.txt regenerated from 70c8fff: [exported] 193 -> 210

Counts by (name, file): +41 / -24 (excluding ContentPart, which moved content.rs -> result.rs and is not new; with it +42 / -25). By name only: +39 / -24. The +41 vs +39 delta of 2 comes only from ToolResultOutput and ToolResultPart, which now exist under the same name in two v4 files (1 entry each in a6e55d4, 2 each at 70c8fff). ReasoningPart was already listed twice in a6e55d4, so it adds nothing to the delta.
Added: AssistantContent AssistantContentPart ContentPart DecideOptions DecideResult DecideUsage DecisionInput DecisionQuestionType DecisionUsage FilePartData GenerateObjectResponse GenerateTextResultSnapshot GeneratedFileOutput InputTokenDetails LanguageModelFile LanguageModelUsage LanguageModelV4ToolChoice NamespacedKind Output OutputChunkTimingStats OutputTokenDetails ReasoningFilePartData StepModel StepResponse StepResponseMetadata StepResult StepResultPerformance TaggedToolResult ToolApprovalResponseOutput ToolChoiceViolationError ToolContentPart ToolOutputDenied ToolResultContentPart ToolResultFileId ToolResultOutput ToolResultPart TypedSource TypedToolCall TypedToolError TypedToolResult UserContent UserContentPart

Removed: BooleanCriteria ContentPart DecisionCapabilities DecisionDescription DecisionImage DecisionOption DecisionProbabilitySource DecisionResponse DecisionScoreLevel DecisionValue EvaluationAnswer EvaluationCallOptions EvaluationInput EvaluationQuestion EvaluationQuestionType EvaluationResult EvaluationRounding EvaluationUsage GenerateTextResult MessageContent ModelId ReasoningFileOutput Role StreamTextResultAggregated TextContentBinding

(ContentPart appears in both raw lists only because its file moved content.rs -> result.rs; it is not new.)

shape.kind changes vs old fork manifest (types in both; 160 unchanged)

  • AudioChunk enum -> handWritten
  • AudioData enum -> handWritten
  • AudioInput enum -> handWritten
  • FileBytes enum -> handWritten
  • ImageFileData enum -> handWritten
  • ImageOutputs enum -> handWritten
  • ModelMessage struct -> enum
  • VideoFileData enum -> handWritten

(handWritten: these v4 enums now have hand-written serde + #[ts(type)].)

Follow-up (a578005, 51fd7b7)

  • Manifest per field (tools/aimux-codegen/src/extract.rs): omittedWhen (:340, verbatim skip_serializing_if predicate or null) and null {input, output} (:342, struct :349, rule null_handling :365) from serde attrs + Rust type only. Input: rejected / asAbsent (Option<T>) / distinct (present codec) / asValue (Value) / byType / codec. Output: never / whenNone / asValue / byType / codec.
    • e.g. ApiCallError.data (error.rs:76, Option+present): omittedWhen=Option::is_none, null=distinct/asValue; ApiCallError.response_body (error.rs:64): omittedWhen=Option::is_none, null=asAbsent/never.
  • Rust type text keeps word spacing (rust_text extract.rs:1287): Vec<&'a Source> instead of Vec<&'aSource>.
  • Tests (manifest_test.rs): manifest_records_versions_json_verbatim :99 rewritten (constant asserts removed; edited VERSIONS.json flows into sdk); serde_transparent_is_a_newtype_and_needs_one_field :627 asserts Wrap kind=newtype inner=u32; omitted_and_null_are_distinct_manifest_facts :674 (manifest + real serde round-trip + remove-present mutation flips to asAbsent); ts_only_wrappers_match_wire_tagged_output :764 (wire::Tagged for ToolCall/Source/TypedToolResult + nested ToolApprovalResponseOutput.toolCall vs manifest literal+flatten); rust_type_text_keeps_word_spacing :824; [exported] count computed from extraction :512.

Follow-up 2 (b94cfaf)

  • skip_serializing_if predicates matched by last path segment (SKIP_PREDICATES extract.rs:366: is_none, is_empty, is_language_model[recording.rs:184]); unknown predicate -> generation error (extract.rs:397); Option field skipped by a non-is_none predicate -> error (extract.rs:407). No silent whenNone.
  • presence codec requires Option<Option<T>> or Option<Value> else generation error (extract.rs:429). All 14 existing presence fields pass; manifest.json unchanged.
  • Tests (manifest_test.rs): option_option_with_skip_writes_some_none_as_null :838 (DecisionBooleanCriteria Some(None) -> {"true": null}, manifest asValue); option_without_skip_writes_none_as_null :864 (ChatCompletionChoice.finish_reason None -> null, manifest whenNone); non_null_json_codec_rejects_null :893; deserialize_with_field_null_is_codec :920; skip_predicates_are_registered_by_last_segment :935; presence_codec_needs_a_nullable_payload :971.
  • Mutations (local, not committed): extract.rs:397 && false && (predicate check off) -> skip_predicates_are_registered_by_last_segment red at manifest_test.rs:949 (rc=101, 26/27). extract.rs:424 presence arm -> _ => (always distinct) -> presence_codec_needs_a_nullable_payload red (generation unexpectedly succeeded, panic in errors() manifest_test.rs:57; rc=101, 26/27).

Checks (run locally, real results)

  • cargo test -p aimux-codegen: codegen_test 8/8, manifest_test 27/27 (at b94cfaf)
  • cargo test -p aimux-providers --test descriptor_schema_test: 4/4
  • cargo run -p aimux-codegen -- --check: exit 0
  • coverage.txt == [exported] of closure-dtos.txt (explicit diff equal; also enforced by the tool)
  • python3 scripts/gen_closure_inventory.py --check: up to date
  • python3 scripts/gen_ts_types.py --check: exit 0 (212 + 8 files up to date)
  • cargo clippy -p aimux-codegen --all-targets -- -D warnings: clean; cargo clippy -p aimux-providers --test descriptor_schema_test -- -D warnings: clean; cargo fmt --all --check: clean
  • Not run: full cargo test -p aimux-providers; Swift checks 未运行 (no toolchain).

Mutations (local only, not committed)

  • M5a: extract.rs:1068 if c.transparent { -> if false && c.transparent {; cargo test -p aimux-codegen --test manifest_test serde_transparent -> rc=101, red at manifest_test.rs:647 (left "struct", right "newtype")
  • Integrity: change one char of ai integrity in fixtures/aisdk/VERSIONS.json -> manifest_records_versions_json_verbatim red at manifest_test.rs:106 (rc=101); aimux-codegen --check rc=1 (manifest.json is out of date)
  • Line shift: add blank lines at top/bottom of extract.rs -> closure_dto_list_is_reproducible_from_source stays green (2 passed)
  • rm generated/coverage.txt && aimux-codegen --check -> exit 1 (coverage.txt: No such file)
  • append a line to generated/ts/ToolCall.ts -> --check exit 1 (ToolCall.ts is out of date)
  • In-tree mutation tests in manifest_test (serde_attribute_mutations_change_the_modelled_field, versions_change_makes_manifest_stale, ts_only_tagged_wrapper_is_modelled_and_guarded etc.) go red on mutated scratch copies.

Not in this PR

…lidated descriptors (phase 1 unit 1.7)

Diff from #42 applied verbatim; v4 adaptation follows in later commits.
…lCall -> TS + Go, --check (on #42)

Diff from #43 applied verbatim; v4 adaptation follows in later commits.
… coverage == closure-dtos.txt (on #43, merges #13)

Diff from #53 applied verbatim; v4 adaptation follows in later commits.
…overage, closure-dtos from 70c8fff

- serde(transparent) is a newtype (NamespacedKind, types.rs:354)
- TS-only tagged wrappers (TypedToolCall tool.rs:198, TaggedToolResult result.rs:163, TypedSource result.rs:325) modelled from #[ts] attrs
- FIELD_CODECS re-registered for v4 serde fns (wire::present, typed::deserialize, non_null_json, deserialize_known_schema, deserialize_prompt)
- VERSIONS.json v4 shape {version,integrity} recorded verbatim
- tests updated to v4 names; two new behaviour tests
…acing in Rust type text

- FieldDef.omittedWhen / .null {input, output} from serde attrs + Rust type only
- rust_text keeps spaces between word tokens (Vec<&'a Source>)
- tests: omitted vs null on ApiCallError.data/response_body incl. real serde round-trip and codec-removal mutation; wire::Tagged vs manifest for TypedToolCall/TypedSource/TaggedToolResult + nested ToolApprovalResponseOutput.toolCall; transparent Wrap newtype(u32); sdk verbatim via edited VERSIONS.json; [exported] count computed
…codec needs a nullable payload

- unknown skip_serializing_if predicate, or Option skipped by non-is_none, fails generation (no silent whenNone)
- presence codec on a field whose Some payload cannot hold null fails generation
- serde round-trip tests: Option<Option<T>>+skip Some(None)->null (asValue), Option without skip None->null (whenNone), nonNullJson rejects null, deserialize_with -> codec
- manifest.json unchanged (all 14 presence fields and 3 predicate kinds pass)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant