Skip to content

[codex] Add FoundationModels facade and provider expansion#65

Merged
christopherkarani merged 6 commits into
mainfrom
codex/conduit-foundationmodels-provider-expansion
May 18, 2026
Merged

[codex] Add FoundationModels facade and provider expansion#65
christopherkarani merged 6 commits into
mainfrom
codex/conduit-foundationmodels-provider-expansion

Conversation

@christopherkarani
Copy link
Copy Markdown
Owner

Summary

  • Add ConduitLanguageModel and ConduitLanguageModelSession as a FoundationModels-familiar facade over the existing Conduit provider/session runtime.
  • Add delegate-aware tool execution with execute, stop, provided-output, success, failure, and deterministic callback behavior.
  • Add provider-keyed typed custom options on GenerateConfig.
  • Add first-class Gemini, Open Responses, and native Ollama provider surfaces with mocked transport tests.
  • Add DocC and mirrored docs for FoundationModels migration plus Gemini/Open Responses/Ollama provider guidance.

Verification

  • swift build
  • swift test
  • swift test --filter ConduitLanguageModelSessionTests
  • swift test --filter ToolExecutionDelegateTests
  • swift test --filter GenerateConfigCustomOptionsTests
  • swift test --traits Gemini --filter GeminiProviderTests
  • swift test --traits OpenAI --filter OpenResponsesProviderTests
  • swift test --traits Ollama --filter OllamaProviderTests
  • swift build --traits OpenAI
  • swift build --traits Gemini
  • swift build --traits Ollama

Notes

  • Provider tests use mocked transports and do not require live credentials or a local Ollama server.
  • Live endpoint validation for Gemini/Open Responses/Ollama remains optional follow-up outside this PR.
  • Existing local artifacts .build-default/, Examples/, and RESEARCH_DynamicGenerationSchema.md were preserved and not included.

Adds ConduitLanguageModel/ConduitLanguageModelSession compatibility APIs, delegate-aware tool execution, typed provider custom options, Gemini/Open Responses/Ollama provider surfaces, and focused tests/docs.\n\nVerification already run: swift build; swift test; targeted facade, tool delegate, custom options, Gemini, Open Responses, and Ollama tests; trait builds for OpenAI, Gemini, and Ollama.
@christopherkarani
Copy link
Copy Markdown
Owner Author

@codex

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f2d5b0a00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +249 to +251
let finishReason = toolCalls.isEmpty ? FinishReason.stop : .toolCalls
return GenerationResult(
text: toolCalls.isEmpty ? text : "",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve Gemini candidate finish reason in parsed result

The Gemini response parser ignores candidates[0].finishReason and always returns .stop when there are no tool calls. Gemini uses finishReason to signal truncation/safety/block outcomes, so this mapping can report incomplete or filtered generations as successful stops and break caller logic that retries/handles non-stop endings.

Useful? React with 👍 / 👎.

private nonisolated func generationResult(text: String, json: [String: Any]) -> GenerationResult {
let completionTokens = json["eval_count"] as? Int ?? 0
let promptTokens = json["prompt_eval_count"] as? Int ?? 0
let finishReason: FinishReason = (json["done"] as? Bool) == false ? .maxTokens : .stop
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not map in-progress Ollama chunks to max-tokens finish

done == false in Ollama stream payloads means the generation is still in progress, not that it stopped because of token limits. Mapping every non-final chunk to .maxTokens emits incorrect termination semantics to stream consumers (and final chunks are always .stop regardless of provider done_reason), so downstream code can misinterpret successful streams as token-limit stops.

Useful? React with 👍 / 👎.

@christopherkarani christopherkarani marked this pull request as ready for review May 18, 2026 10:09
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d43a0c147

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +292 to +294
guard let payload = data.data(using: .utf8),
let result = try? parseGenerationResponse(data: payload) else {
return nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Surface Gemini stream parse/API errors

decodeStreamEvent turns any parsing or API error into nil with try?, and the streaming loop skips nil events. In streaming mode this can silently drop Gemini error payloads (or malformed chunks) and finish without throwing, so callers may treat a failed/incomplete generation as success with partial output.

Useful? React with 👍 / 👎.

Comment on lines +328 to +333
var body: [String: Any] = [
"model": model.rawValue,
"messages": messages.map { message in
var serialized: [String: Any] = [
"role": message.role.rawValue,
"content": message.content.textValue
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include configured tools in Ollama chat requests

This request body construction never serializes config.tools, so tool definitions are not sent to /api/chat. For Ollama tool-calling flows, that prevents the model from receiving callable tool schemas, which means sessions that register tools in Conduit cannot reliably enter the tool-call loop on this provider.

Useful? React with 👍 / 👎.

Comment thread Sources/Conduit/ConduitAPI.swift Outdated
let promptEntry = makePromptEntry(prompt, options: options, responseFormat: nil)
append(.prompt(promptEntry))

let text = try await session.run(promptEntry.textContent, config: promptEntry.generateConfig)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve session tool config when responding with options

ConduitLanguageModelSession.respond passes promptEntry.generateConfig as a full per-call config, but that config only carries prompt options/response format and drops session-level tool definitions. Because ChatSession.send(..., config:) uses the override directly, initializing ConduitLanguageModelSession with tools: can result in requests that no longer advertise tools to providers that require schemas each turn.

Useful? React with 👍 / 👎.

@christopherkarani christopherkarani merged commit c79c68f into main May 18, 2026
0 of 4 checks passed
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