Skip to content

Add missing Foundation Models 26 APIs - #288

Merged
mattt merged 11 commits into
mainfrom
mattt/foundation-models-26-gaps
Oct 3, 2026
Merged

mattt merged 11 commits into
mainfrom
mattt/foundation-models-26-gaps

Conversation

@mattt

@mattt mattt commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

This PR adds the Foundation Models 26 APIs that AnyLanguageModel was missing:

  • @Generable(description:representNilExplicitlyInGeneratedContent:), GenerationSchema.init(type:description:representNilExplicitlyInGeneratedContent:properties:), and DynamicGenerationSchema.init(name:description:representNilExplicitlyInGeneratedContent:properties:)
  • DynamicGenerationSchema.null
  • logFeedbackAttachment(sentiment:issues:desiredResponseText:) and logFeedbackAttachment(sentiment:issues:desiredResponseContent:)
  • SystemLanguageModel.supportedLanguages, supportsLocale(_:), and the five tokenCount(for:) overloads (OS 26.4)

To match Foundation Models, a @Generable type's generated content now leaves out optional properties whose value is nil, instead of writing null. Pass representNilExplicitlyInGeneratedContent: true to keep the null values. With that flag, a session also fills in null for optional properties that a model's structured response leaves out, as Foundation Models does.

This PR also fixes SystemLanguageModel.logFeedbackAttachment and PrivateCloudComputeLanguageModel.logFeedbackAttachment, which dropped desiredOutput instead of passing it to Foundation Models.

@mattt
mattt requested a balanced review from Copilot October 2, 2026 13:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Explicit-null behavior is lost for streaming, union schemas, and schema serialization round trips.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Adds missing Foundation Models 26 APIs, explicit optional-nil representation, null schemas, feedback helpers, and system-model metadata/token-counting APIs.

Changes:

  • Adds configurable explicit-nil generation and null schema support.
  • Adds feedback convenience APIs and forwards desired output correctly.
  • Exposes supported-language and token-count APIs.
File Description
Sources/​AnyLanguageModel/​Generable.swift Adds the explicit-nil macro overload.
Sources/​AnyLanguageModelMacros/​GenerableMacro.swift Generates omitted or explicit-null optional properties.
Sources/​AnyLanguageModel/​GenerationSchema.swift Adds null nodes and explicit-nil postprocessing.
Sources/​AnyLanguageModel/​DynamicGenerationSchema.swift Adds dynamic null and explicit-nil schemas.
Sources/​AnyLanguageModel/​LanguageModelSession.swift Applies explicit-null behavior and adds feedback helpers.
Sources/​AnyLanguageModel/​Transcript.swift Exposes response schemas internally.
Sources/​AnyLanguageModel/​Models/​SystemLanguageModel.swift Adds language/token APIs and feedback forwarding.
Sources/​AnyLanguageModel/​Shared/​StructuredGeneration.swift Generates constrained null values.
Sources/​AnyLanguageModel/​Shared/​ResponseSchemaInlining.swift Supports null nodes during inlining.
Tests/​AnyLanguageModelTests/​ExplicitNilTests.swift Tests explicit and omitted nil behavior.
Tests/​AnyLanguageModelTests/​DynamicGenerationSchemaTests.swift Tests null-schema coding.
Tests/​AnyLanguageModelTests/​LanguageModelFeedbackTests.swift Tests desired feedback responses.
Tests/​AnyLanguageModelTests/​SystemLanguageModelTests.swift Tests languages and token counting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/AnyLanguageModel/GenerationSchema.swift Outdated
Comment thread Sources/AnyLanguageModel/GenerationSchema.swift
Comment thread Sources/AnyLanguageModel/LanguageModelSession.swift
@mattt
mattt requested a balanced review from Copilot October 2, 2026 15:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Dynamic response consistency, union handling, constrained nullable generation, and tvOS compilation issues remain.

Review effort: Balanced
Findings: 2 High severity · 2 Medium severity

Open (4)
Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Schema overload returns untransformed response content

Sources/​AnyLanguageModel/​LanguageModelSession.swift:218

For the schema-based overloads, Content is GeneratedContent, so this keeps response.content in its original form while rawContent gains the explicit null properties. Callers reading the primary content value therefore see different data from rawContent; use the transformed value when it can be cast to Content.

Comment on lines +390 to +391
case .null:
return try await emit("null")
Comment thread Tests/AnyLanguageModelTests/SystemLanguageModelTests.swift Outdated
Comment thread Sources/AnyLanguageModel/GenerationSchema.swift
@mattt
mattt requested a balanced review from Copilot October 2, 2026 16:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Schema-based responses can return inconsistent normalized content and rawContent values.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity

Open (3)
Resolved since last review (2)

Comment thread Sources/AnyLanguageModel/LanguageModelSession.swift Outdated
@mattt
mattt requested a balanced review from Copilot October 2, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Private Cloud Compute still discards desired feedback responses passed through the new convenience APIs.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Forward desiredOutput in PrivateCloudComputeLanguageModel feedback

Sources/​AnyLanguageModel/​LanguageModelSession.swift:1087

Both new convenience overloads ultimately rely on each model's desiredOutput forwarding. PrivateCloudComputeLanguageModel.logFeedbackAttachment still hard-codes desiredOutput: nil (Sources/AnyLanguageModel/Models/PrivateCloudComputeLanguageModel.swift:137), even though it creates the same Foundation Models session, so these APIs silently discard the requested text/content for PCC. Convert and forward the entry there as this PR now does for SystemLanguageModel.

@mattt
mattt requested a balanced review from Copilot October 2, 2026 17:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Nullable unions cannot select null during constrained generation, and nested unions skip explicit-nil normalization.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Support nested anyOf schemas during content matching

Sources/​AnyLanguageModel/​GenerationSchema.swift:1014

Nested anyOf schemas are never considered a match. For example, an outer union containing a referenced inner [ExplicitNilObject, .null] resolves that reference to .anyOf; this switch rejects it, so traversal stops and omitted optionals in the explicit object are not filled with null. Recurse into nested union variants when matching the content shape.

@mattt
mattt requested a balanced review from Copilot October 2, 2026 18:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Nonliteral macro flags and pre-26.4 null-schema fallback can silently produce incorrect generated content.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Boolean macro expressions are incorrectly treated as false

Sources/​AnyLanguageModelMacros/​GenerableMacro.swift:99

Any valid Boolean expression other than a bare literal—such as a static constant or even a parenthesized true—is silently interpreted as false, so the generated content contradicts the value supplied to the public macro. Preserve the argument expression in generated code, or emit a macro diagnostic requiring a Boolean literal rather than changing its meaning.

return .null
}
#endif
return .init(type: String.self)
@mattt
mattt requested a balanced review from Copilot October 2, 2026 20:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Typed and streamed response content can remain inconsistent with normalized explicit-null raw content.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Initialize final snapshot partial content from normalized content

Sources/​AnyLanguageModel/​LanguageModelSession.swift:271

rawContent cannot be cast to a typed Content.PartiallyGenerated, so the synthetic final snapshot pairs normalized raw content with the previous, unnormalized partial value. Consumers iterating the stream can therefore see omitted keys through snapshot.content.generatedContent even though snapshot.rawContent contains explicit nulls. Initialize the partial value from the normalized content instead.

@mattt
mattt requested a balanced review from Copilot October 2, 2026 21:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Nullable-union matching, property ordering, and tvOS availability handling can currently produce incorrect behavior.

Review effort: Balanced
Findings: 2 High severity · 4 Medium severity

Open (6)

Comment thread Sources/AnyLanguageModel/GenerationSchema.swift Outdated
Comment on lines +1008 to +1010
case (.object(let object)?, .structure(let properties, _)):
return properties.keys.allSatisfy { object.properties[$0] != nil }
&& object.required.allSatisfy { properties[$0] != nil }
Comment thread Sources/AnyLanguageModel/Models/SystemLanguageModel.swift
Comment thread Sources/AnyLanguageModel/Models/SystemLanguageModel.swift Outdated
@mattt
mattt requested a balanced review from Copilot October 3, 2026 01:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattt
mattt requested a balanced review from Copilot October 3, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Feedback sessions omit conversation history, and explicit-nil normalization can select or order schema fields incorrectly.

Review effort: Balanced
Findings: 2 High severity · 3 Medium severity

Open (5)
Resolved since last review (3)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Rehydrate session transcript before attaching feedback

Sources/​AnyLanguageModel/​Models/​PrivateCloudComputeLanguageModel.swift:137

This newly created Foundation Models session does not contain session.transcript, so it has no most recent PCC response to which the feedback and desired output can be attached. Rehydrate it from the converted transcript before calling logFeedbackAttachment, matching the generation path.

Medium severity Feedback logs against a fresh session without the response transcript

Sources/​AnyLanguageModel/​Models/​SystemLanguageModel.swift:296

The Foundation Models session is created only from tools and instructions, so it has none of session.transcript—including the response this feedback is supposed to describe. Passing desiredOutput therefore still logs against a fresh session rather than the actual exchange. Initialize the Foundation Models session from the converted transcript, as the response paths do.

Comment thread Sources/AnyLanguageModel/GenerationSchema.swift
Comment on lines +984 to +991
if object.representsNilExplicitly {
let declaredKeys = object.propertyOrder.isEmpty ? object.properties.keys.sorted() : object.propertyOrder
for key in declaredKeys
where properties[key] == nil && !object.required.contains(key) {
properties[key] = GeneratedContent(kind: .null)
orderedKeys.append(key)
}
}
@mattt
mattt merged commit 1d1794c into main Oct 3, 2026
7 checks passed
@mattt
mattt deleted the mattt/foundation-models-26-gaps branch October 3, 2026 11:48
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.

2 participants