Compare commits

..

1 Commits

Author SHA1 Message Date
Peter Steinberger
1cac93f844
fix: allow compatible reasoning model identity 2026-06-11 14:23:35 -07:00
12 changed files with 37 additions and 106 deletions

View File

@ -19,7 +19,7 @@ jobs:
os: [macos-15, ubuntu-22.04]
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
@ -37,7 +37,7 @@ jobs:
swift-version: '6.2.1'
- name: Cache Swift Package Manager
uses: actions/cache@v6
uses: actions/cache@v4
with:
path: |
.build
@ -83,7 +83,7 @@ jobs:
os: [macos-15]
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -136,7 +136,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Install SwiftLint
run: brew install swiftlint

View File

@ -27,7 +27,7 @@ jobs:
name: Ubuntu 22.04 LTS
container: swift:6.2
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
run: git clone --depth 1 https://github.com/steipete/Commander.git ../Commander
@ -56,7 +56,7 @@ jobs:
name: Ubuntu 24.04 LTS
container: swift:6.2
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
run: git clone --depth 1 https://github.com/steipete/Commander.git ../Commander
@ -86,7 +86,7 @@ jobs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Create release info
run: |
@ -101,7 +101,7 @@ jobs:
echo "Total: 4 platform configurations tested in parallel" >> release-info.txt
- name: Upload release info
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: cross-platform-validation
path: release-info.txt

View File

@ -16,7 +16,7 @@ jobs:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -28,7 +28,7 @@ jobs:
xcode-version: '26.1'
- name: Cache SwiftLint
uses: actions/cache@v6
uses: actions/cache@v4
with:
path: |
~/.mint
@ -69,7 +69,7 @@ jobs:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -81,7 +81,7 @@ jobs:
xcode-version: '26.1'
- name: Cache SwiftFormat
uses: actions/cache@v6
uses: actions/cache@v4
with:
path: |
~/.mint
@ -111,7 +111,7 @@ jobs:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -158,7 +158,7 @@ jobs:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash

View File

@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -37,7 +37,7 @@ jobs:
xcode-version: '26.1'
- name: Cache Swift Package Manager
uses: actions/cache@v6
uses: actions/cache@v4
with:
path: .build
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
@ -75,7 +75,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -87,7 +87,7 @@ jobs:
swift-version: ${{ matrix.swift-version }}
- name: Cache Swift Package Manager
uses: actions/cache@v6
uses: actions/cache@v4
with:
path: .build
key: ${{ runner.os }}-spm-${{ matrix.swift-version }}-${{ hashFiles('**/Package.resolved') }}
@ -122,7 +122,7 @@ jobs:
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash
@ -155,7 +155,7 @@ jobs:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Fetch Commander dependency
shell: bash

View File

@ -15,7 +15,7 @@ All notable changes to the Tachikoma project will be documented in this file.
- Removed stale direct model support for retired or non-canonical IDs including GPT-5.1/5.2/pseudo-thinking models, deprecated Claude Sonnet/Opus 4 snapshots, Grok 2/3/4-fast rows, old Groq Llama/Mixtral/Gemma aliases, stale Mistral aliases, and invalid LM Studio `current`.
### Fixed
- OpenAI `gpt-5-chat-latest` now preserves its distinct model identity, appears in model listings, and applies GPT-5 parameter filtering instead of being rewritten to `chat-latest`.
- OpenAI `gpt-5-chat-latest` now preserves its distinct model identity instead of being rewritten to `chat-latest`.
- SwiftPM consumers now resolve Commander from the package URL instead of accidentally inheriting a sibling local checkout.
- Ollama model parsing now preserves explicit custom vision model IDs such as `qwen2.5vl:3b` instead of falling back to `llama3.3` (#16).
- Auth resolution now snapshots environment-ignore state consistently, preventing parallel tests and concurrent callers from falling back to stored OpenRouter credentials when an environment override is present.

View File

@ -1,15 +1,6 @@
{
"originHash" : "12a454cd38a6ae2519d652cc0872f7f18feb64690ce83d1507bae6db71c1841c",
"pins" : [
{
"identity" : "commander",
"kind" : "remoteSourceControl",
"location" : "https://github.com/steipete/Commander.git",
"state" : {
"revision" : "ae2ce746b386ff94b26648cfe5625cfa8d02639b",
"version" : "0.2.2"
}
},
{
"identity" : "eventsource",
"kind" : "remoteSourceControl",
@ -33,8 +24,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "a9a5efd40eaf558a2bcd48d64b1d1646be686008",
"version" : "1.7.1"
"revision" : "eb50cbd14606a9161cbc5d452f18797c90ef0bab",
"version" : "1.7.0"
}
},
{
@ -42,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-algorithms.git",
"state" : {
"revision" : "d0b4a06d0f173a2f3be27d3ea21b3c3aa18db440",
"version" : "1.1.4"
"revision" : "9d349bcc328ac3c31ce40e746b5882742a0d1272",
"version" : "1.1.3"
}
},
{
@ -60,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "a0cb0954ecb21e4e31b0070e6ed5674e8556685a",
"version" : "1.6.0"
"revision" : "6675bc0ff86e61436e615df6fc5174e043e57924",
"version" : "1.4.1"
}
},
{
@ -87,8 +78,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "92448c359f00ebe36ae97d3bd9086f13c7692b5a",
"version" : "1.13.2"
"revision" : "5073617dac96330a486245e4c0179cb0a6fd2256",
"version" : "1.12.0"
}
},
{
@ -96,8 +87,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "a8e036cb8628fcc1ff67dfec6ce8168617172c9b",
"version" : "2.101.1"
"revision" : "f71c8d2a5e74a2c6d11a0fbe324774b5d6084237",
"version" : "2.99.0"
}
},
{
@ -114,8 +105,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/modelcontextprotocol/swift-sdk.git",
"state" : {
"revision" : "a0ae212ebf6eab5f754c3129608bc5557637e605",
"version" : "0.12.1"
"revision" : "6132fd4b5b4217ce4717c4775e4607f5c3120129",
"version" : "0.12.0"
}
},
{
@ -132,8 +123,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
"revision" : "7502b711c92a17741fa625d722b0ccbd595d8ed1",
"version" : "1.7.2"
"revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
"version" : "1.6.4"
}
}
],

View File

@ -433,7 +433,7 @@ public func streamText(
}
}
if buffersUntilDone, !didReceiveTerminal {
if buffersUntilDone, !didReceiveTerminal, !bufferedDeltas.isEmpty {
throw TachikomaError.apiError("Stream ended before provider completion status was received")
}
@ -1049,10 +1049,7 @@ extension LanguageModel {
fallbackContent.append(.text(text))
}
fallbackContent.append(contentsOf: missingToolCalls.map { .toolCall($0) })
let fallbackMetadata = needsFallbackBoundary
? MessageMetadata(customData: ["tachikoma.internal.boundary": "reasoning_only"])
: nil
history.append(ModelMessage(role: .assistant, content: fallbackContent, metadata: fallbackMetadata))
history.append(ModelMessage(role: .assistant, content: fallbackContent))
return history
}

View File

@ -242,7 +242,6 @@ public final class ModelCapabilityRegistry: @unchecked Sendable {
)
self.capabilities["openai:chat-latest"] = gpt5Capabilities
self.capabilities["openai:gpt-5-chat-latest"] = gpt5Capabilities
self.capabilities["openai:gpt-5.5"] = gpt5Capabilities
self.capabilities["openai:gpt-5.4"] = gpt5Capabilities
self.capabilities["openai:gpt-5.4-mini"] = gpt5Capabilities

View File

@ -57,7 +57,6 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
public static var allCases: [OpenAI] {
[
.chatLatest,
.gpt5ChatLatest,
.gpt55,
.gpt54,
.gpt54Mini,

View File

@ -1258,29 +1258,6 @@ struct GenerationTests {
.canonical("https://example.test"))
}
@Test
func `GenerateText tags fallback reasoning-only boundary for Anthropic-compatible Fable`() async throws {
let providerResponse = ProviderResponse(
text: "",
finishReason: .stop,
reasoning: [ProviderReasoningBlock(text: "private", signature: "sig")],
)
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setProviderFactoryOverride { _, _ in StaticProvider(response: providerResponse) }
let result = try await generateText(
model: .anthropicCompatible(modelId: "claude-fable-5", baseURL: "https://example.test"),
messages: [.user("hi")],
configuration: config,
)
#expect(result.messages.count == 3)
#expect(result.messages[1].channel == .thinking)
#expect(result.messages[2].role == .assistant)
#expect(result.messages[2].content == [.text("")])
#expect(result.messages[2].metadata?.customData?["tachikoma.internal.boundary"] == "reasoning_only")
}
@Test
func `GenerateText tags fallback reasoning for direct custom Fable`() async throws {
let provider = StaticProvider(
@ -1506,36 +1483,6 @@ struct GenerationTests {
#expect(operation.usage.outputTokens > 0)
}
@Test
func `StreamText buffered mode fails when provider ends without terminal status`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setProviderFactoryOverride { _, _ in
StaticProvider(
response: ProviderResponse(text: "", finishReason: .stop),
capabilities: ModelCapabilities(supportsStreaming: true),
streamDeltas: [],
)
}
let result = try await streamText(
model: .openaiCompatible(modelId: "compatible-model", baseURL: "https://example.test"),
messages: [.user("hi")],
settings: GenerationSettings(streamBuffering: .untilTerminal),
configuration: config,
)
do {
for try await _ in result.stream {}
Issue.record("Expected missing terminal status error")
} catch let error as TachikomaError {
guard case let .apiError(message) = error else {
Issue.record("Expected apiError, got \(error)")
return
}
#expect(message.contains("provider completion status"))
}
}
@Test
func `StreamText stop conditions ignore reasoning deltas`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)

View File

@ -6,7 +6,6 @@ struct LanguageModelCoverageTests {
func `OpenAI enum exposes properties`() {
let models = LanguageModel.OpenAI.allCases
#expect(!models.isEmpty)
#expect(models.contains(.gpt5ChatLatest))
for model in models {
#expect(!model.modelId.isEmpty)
_ = model.supportsVision

View File

@ -7,7 +7,6 @@ enum ModelCapabilitiesTests {
@Test
func `GPT-5 models exclude temperature and topP`() {
let models: [LanguageModel] = [
.openai(.gpt5ChatLatest),
.openai(.gpt55),
.openai(.gpt54),
.openai(.gpt54Mini),