diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e4307edf..b4b0b98d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ env: CACHE_SALT: v2-vmlx-5b84387 # Pin Xcode so cache keys are stable across runner image bumps. When you # need to upgrade, change here AND in setup-xcode below. - XCODE_VERSION: "26.3" + XCODE_VERSION: "26.4.1" jobs: test-core: @@ -333,7 +333,7 @@ jobs: uses: actions/checkout@v5 # OsaurusCLI's Package.swift requires `swift-tools-version: 6.2`, which - # ships with Xcode 26.3. The runner's default `swift` may be older + # ships with the pinned Xcode. The runner's default `swift` may be older # (currently 6.1), so point xcrun at the pinned Xcode for these steps. # GitHub Actions doesn't let job-level `env:` reference workflow-level # `env:`, so keep XCODE_VERSION in sync here if it ever changes. diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Bundle/MCPBundleManifest.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Bundle/MCPBundleManifest.swift index 4e9375362..73f4c2127 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Bundle/MCPBundleManifest.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Bundle/MCPBundleManifest.swift @@ -3,7 +3,7 @@ // osaurus // // Model for MCPB (MCP Bundle) manifest.json files. -// Supports both standard MCPB format and Claude Desktop integration format. +// Supports both standard MCPB format and desktop-client integration format. // import Foundation @@ -12,7 +12,7 @@ struct MCPBundleManifest: Codable { // Standard MCPB format let mcpVersion: String? - // Claude Desktop format + // Desktop-client integration format let manifestVersion: String? let name: String @@ -21,7 +21,7 @@ struct MCPBundleManifest: Codable { let description: String? let entry: EntryPoint? - // Claude Desktop format + // Desktop-client integration format let server: ServerConfig? let icon: String? @@ -81,7 +81,7 @@ struct MCPBundleManifest: Codable { return (entry.command, entry.args, entry.env) } - // Try Claude Desktop format + // Try desktop-client integration format if let server = server, let config = server.mcpConfig { return (config.command, config.args, config.env) } diff --git a/Packages/OsaurusCore/Models/API/AnthropicAPI.swift b/Packages/OsaurusCore/Models/API/AnthropicAPI.swift index fee6973cd..013183e42 100644 --- a/Packages/OsaurusCore/Models/API/AnthropicAPI.swift +++ b/Packages/OsaurusCore/Models/API/AnthropicAPI.swift @@ -2,7 +2,7 @@ // AnthropicAPI.swift // osaurus // -// Anthropic Messages API compatible request/response models for Claude Code compatibility. +// Anthropic Messages API compatible request/response models for SDK-compatible clients. // import Foundation diff --git a/Packages/OsaurusCore/Models/Chat/ChatConfiguration.swift b/Packages/OsaurusCore/Models/Chat/ChatConfiguration.swift index a5c0c30cd..1783a29ec 100644 --- a/Packages/OsaurusCore/Models/Chat/ChatConfiguration.swift +++ b/Packages/OsaurusCore/Models/Chat/ChatConfiguration.swift @@ -90,7 +90,7 @@ public struct ChatConfiguration: Codable, Equatable, Sendable { // MARK: - Tool Settings /// When true, no tools or preflight context are passed to the model. The raw message is sent /// directly, keeping the prompt stable across turns for maximum KV-cache reuse. Recommended - /// when osaurus is acting as a plain LLM backend for an external agent (e.g. Claude via API). + /// when osaurus is acting as a plain LLM backend for an external agent. public var disableTools: Bool /// Default tool selection mode for the built-in Default agent (nil => .auto). diff --git a/Packages/OsaurusCore/Models/Configuration/ModelOptions.swift b/Packages/OsaurusCore/Models/Configuration/ModelOptions.swift index 57bc403cd..bb33998bd 100644 --- a/Packages/OsaurusCore/Models/Configuration/ModelOptions.swift +++ b/Packages/OsaurusCore/Models/Configuration/ModelOptions.swift @@ -161,14 +161,15 @@ struct QwenThinkingProfile: ModelProfile { // MARK: - Auto Thinking Profile (chat-template driven) -/// Fallback profile that activates for any locally-installed model whose -/// chat template reads an `enable_thinking` kwarg. Registered last so that -/// explicit family profiles (Qwen, Venice, etc.) still win when they match. +/// Fallback profile that activates for locally-installed models whose chat +/// template exposes an `enable_thinking` kwarg and uses thinking markers the +/// runtime can process. Registered last so that explicit family profiles +/// (Qwen, Venice, etc.) still win when they match. struct AutoThinkingProfile: ModelProfile { static let displayName = "Thinking" static func matches(modelId: String) -> Bool { - LocalReasoningCapability.capability(forModelId: modelId).hasEnableThinkingKwarg + LocalReasoningCapability.capability(forModelId: modelId).isToggleableThinking } static let options: [ModelOptionDefinition] = [ diff --git a/Packages/OsaurusCore/Package.swift b/Packages/OsaurusCore/Package.swift index 8d3f55887..910c9e7ad 100644 --- a/Packages/OsaurusCore/Package.swift +++ b/Packages/OsaurusCore/Package.swift @@ -74,24 +74,31 @@ let package = Package( // Swift's Clang importer sees two different definitions // of those typedefs and rejects the build with // 'Fts5ExtensionApi' has different definitions in different modules - // The fix is two-part: + // The fix is three-part: // 1. `include/sqlite3.h` wraps the `_FTS5_H` block in // `#ifndef OSAURUS_OMIT_FTS5_HEADERS` (search for // OSAURUS LOCAL MODIFICATION inside that file). - // 2. The `cSettings` `.define("OSAURUS_OMIT_FTS5_HEADERS")` - // below activates the wrap. + // 2. `include/OsaurusSQLCipher.h` defines + // `OSAURUS_OMIT_FTS5_HEADERS` before including + // sqlite3.h so Swift's Clang module import sees the + // hidden extension API. + // 3. The `cSettings` `.define("OSAURUS_OMIT_FTS5_HEADERS")` + // below keeps the C compilation path aligned. // `sqlite3.c` itself inlines its own copy of the header // text, so FTS5's SQL-level functionality keeps working; // we only hide the C-extension API, which Osaurus // doesn't use. // `Tests/Storage/SQLCipherVendorGuardTests.swift` asserts - // both the header guard and the cSettings flag are in - // place — CI fails if a SQLCipher bump strips the guard. + // the header guard, umbrella define, and cSettings flag + // are in place — CI fails if a SQLCipher bump strips them. // - // ⚠️ `sqlite3ext.h` struct collision. A similar issue occurs - // with `struct sqlite3_api_routines` in `sqlite3ext.h`. - // We wrap its contents in `#ifndef OSAURUS_OMIT_SQLITE3EXT_HEADERS` - // and define it below. + // ⚠️ sqlite3ext.h collision. Newer macOS SDKs append fields + // to `sqlite3_api_routines` before our pinned SQLCipher + // adopts that SQLite version. Osaurus does not compile + // SQLite loadable extensions, so the umbrella header hides + // sqlite3ext.h's loadable-extension API from the Swift + // Clang importer while still including the header to keep + // module import warnings quiet. .target( name: "OsaurusSQLCipher", path: "SQLCipher", @@ -128,12 +135,6 @@ let package = Package( // copy of sqlite3.h text is unaffected, so the C // compilation of FTS5 keeps working. .define("OSAURUS_OMIT_FTS5_HEADERS"), - // Hide the `sqlite3_api_routines` struct from - // `include/sqlite3ext.h` so the Swift Clang importer - // doesn't conflict with the system SQLite3 module. - // `sqlite3.c`'s inlined copy of sqlite3ext.h text is - // unaffected. - .define("OSAURUS_OMIT_SQLITE3EXT_HEADERS"), // The SQLite amalgamation calls a few self-references // before their forward declarations show up; modern // Apple clang upgrades this from a warning to an diff --git a/Packages/OsaurusCore/SQLCipher/README.md b/Packages/OsaurusCore/SQLCipher/README.md index 9e550d8d8..227130327 100644 --- a/Packages/OsaurusCore/SQLCipher/README.md +++ b/Packages/OsaurusCore/SQLCipher/README.md @@ -77,33 +77,40 @@ After copying a fresh `sqlite3.h` over the top, re-apply by: #endif /* OSAURUS_OMIT_FTS5_HEADERS — END OSAURUS LOCAL MODIFICATION */ ``` -Similarly, `include/sqlite3ext.h` has an OSAURUS LOCAL MODIFICATION that -wraps the entire file in `#ifndef OSAURUS_OMIT_SQLITE3EXT_HEADERS` to -prevent `struct sqlite3_api_routines` from colliding with Apple's system -`SQLite3` module. +3. Confirm `OsaurusSQLCipher.h` still defines + `OSAURUS_OMIT_FTS5_HEADERS` before it includes `sqlite3.h`, and + `Package.swift` still defines the same flag in the + `OsaurusSQLCipher` target `cSettings`. -After copying a fresh `sqlite3ext.h` over the top, re-apply by: - -1. Find the `#ifndef SQLITE3EXT_H` line. Insert this block immediately - after `#define SQLITE3EXT_H`: +The amalgamation `sqlite3.c` itself is **not** modified — it inlines +its own copy of sqlite3.h text, so the C compilation of FTS5 keeps +working. - ```c - #ifndef OSAURUS_OMIT_SQLITE3EXT_HEADERS - ``` +### Re-applying the sqlite3ext import guard -2. Find the matching `#endif /* SQLITE3EXT_H */` line at the end. Insert - immediately before it: +`include/sqlite3ext.h` has an OSAURUS LOCAL MODIFICATION that hides the +loadable-extension API behind `OSAURUS_OMIT_SQLITE_EXTENSION_API`. +`include/OsaurusSQLCipher.h` defines that macro before including +`sqlite3ext.h`. - ```c - #endif /* OSAURUS_OMIT_SQLITE3EXT_HEADERS — END OSAURUS LOCAL MODIFICATION */ - ``` +This exists because Osaurus imports SQLCipher from Swift for the core +SQLite API, not for compiling SQLite loadable extensions. Apple's +system `SQLite3` module is also imported by dependencies in the same +Swift build. When newer macOS SDKs append fields to +`sqlite3_api_routines` before this pinned SQLCipher version adopts the +same upstream SQLite release, Swift's Clang importer rejects the build +with a cross-module struct-definition mismatch. -3. Run `swift build`. If anything red, see the explainer in - `sqlcipher_amalgamation.c`. +After copying a fresh `sqlite3ext.h` over the top, re-apply by: -The amalgamation `sqlite3.c` itself is **not** modified — it inlines -its own copy of sqlite3.h text, so the C compilation of FTS5 keeps -working. +1. Find `#include "sqlite3.h"` near the top of the file. Insert the + `#ifndef OSAURUS_OMIT_SQLITE_EXTENSION_API` block immediately after + it. +2. Insert the matching close immediately before the final + `#endif /* SQLITE3EXT_H */`. +3. Confirm `OsaurusSQLCipher.h` still defines + `OSAURUS_OMIT_SQLITE_EXTENSION_API` immediately before it includes + `sqlite3ext.h`. ## Why CommonCrypto? diff --git a/Packages/OsaurusCore/SQLCipher/include/OsaurusSQLCipher.h b/Packages/OsaurusCore/SQLCipher/include/OsaurusSQLCipher.h index 6f67714b0..f205ee6c4 100644 --- a/Packages/OsaurusCore/SQLCipher/include/OsaurusSQLCipher.h +++ b/Packages/OsaurusCore/SQLCipher/include/OsaurusSQLCipher.h @@ -30,22 +30,26 @@ #define SQLITE_HAS_CODEC 1 #endif +#ifndef OSAURUS_OMIT_FTS5_HEADERS +#define OSAURUS_OMIT_FTS5_HEADERS 1 +#endif + #include "sqlite3.h" /* `sqlite3ext.h` lives in the same `include/` dir alongside us, so * Clang's umbrella-header consistency check requires it to either * be included from this umbrella or excluded via a module map. - * Pulling it in here is harmless (it's just the loadable-extension - * SQLite API) and silences the + * We include it with the loadable-extension API hidden because + * Osaurus does not compile SQLite loadable extensions, and newer + * macOS SDKs may expose extension fields that SQLCipher 4.6.1 does + * not. This silences the * * warning: umbrella header for module 'OsaurusSQLCipher' does not * include header 'sqlite3ext.h' * - * that would otherwise fire on every Swift import of this module. - * We define `OSAURUS_OMIT_SQLITE3EXT_HEADERS` to hide the - * `sqlite3_api_routines` struct from the Swift Clang importer - * so it doesn't conflict with Apple's system `SQLite3` module. */ -#ifndef OSAURUS_OMIT_SQLITE3EXT_HEADERS -#define OSAURUS_OMIT_SQLITE3EXT_HEADERS 1 + * without reintroducing Swift Clang-importer type collisions against + * Apple's system SQLite3 module. */ +#ifndef OSAURUS_OMIT_SQLITE_EXTENSION_API +#define OSAURUS_OMIT_SQLITE_EXTENSION_API 1 #endif #include "sqlite3ext.h" diff --git a/Packages/OsaurusCore/SQLCipher/include/sqlite3ext.h b/Packages/OsaurusCore/SQLCipher/include/sqlite3ext.h index bc2bbab04..dba82484c 100644 --- a/Packages/OsaurusCore/SQLCipher/include/sqlite3ext.h +++ b/Packages/OsaurusCore/SQLCipher/include/sqlite3ext.h @@ -17,22 +17,27 @@ */ #ifndef SQLITE3EXT_H #define SQLITE3EXT_H +#include "sqlite3.h" /* * BEGIN OSAURUS LOCAL MODIFICATION (do not remove on SQLCipher * version bumps; re-apply after copying a new sqlite3ext.h). * - * The `sqlite3_api_routines` struct collides with the same - * struct from Apple's system `SQLite3` module when both modules - * end up imported in the same Swift compilation unit. We hide - * the entire contents from the Clang importer here (activated - * by `OSAURUS_OMIT_SQLITE3EXT_HEADERS` in `Package.swift`). - * `sqlite3.c` includes its own copy of this text so the C - * compilation is unaffected. + * Osaurus imports SQLCipher from Swift for the core SQLite API, not + * for compiling SQLite loadable extensions. Apple's system SQLite3 + * module is also imported by dependencies in the same Swift build, + * and modern SDKs may append fields to `sqlite3_api_routines` before + * SQLCipher has adopted the same upstream SQLite version. Swift's + * Clang importer treats those cross-module struct differences as a + * build error. + * + * The umbrella header defines OSAURUS_OMIT_SQLITE_EXTENSION_API + * before including this file so the module stays warning-free without + * exporting the loadable-extension API that Osaurus does not use. + * The SQLCipher amalgamation is unaffected because sqlite3.c inlines + * its own sqlite3ext.h text. */ -#ifndef OSAURUS_OMIT_SQLITE3EXT_HEADERS - -#include "sqlite3.h" +#ifndef OSAURUS_OMIT_SQLITE_EXTENSION_API /* ** The following structure holds pointers to all of the SQLite API @@ -731,6 +736,6 @@ typedef int (*sqlite3_loadext_entry)( # define SQLITE_EXTENSION_INIT3 /*no-op*/ #endif -#endif /* OSAURUS_OMIT_SQLITE3EXT_HEADERS — END OSAURUS LOCAL MODIFICATION */ +#endif /* OSAURUS_OMIT_SQLITE_EXTENSION_API - END OSAURUS LOCAL MODIFICATION */ #endif /* SQLITE3EXT_H */ diff --git a/Packages/OsaurusCore/Services/LocalReasoningCapability.swift b/Packages/OsaurusCore/Services/LocalReasoningCapability.swift index 0d8a11eae..f6d223ede 100644 --- a/Packages/OsaurusCore/Services/LocalReasoningCapability.swift +++ b/Packages/OsaurusCore/Services/LocalReasoningCapability.swift @@ -21,12 +21,15 @@ enum LocalReasoningCapability { struct Capability: Sendable { /// Template references `` or `` tags. let supportsThinking: Bool - /// Template reads an `enable_thinking` kwarg — i.e. thinking is toggleable. + /// Template reads an `enable_thinking` kwarg. let hasEnableThinkingKwarg: Bool /// Template itself injects a literal `` opener into the assistant prompt /// tail, which means the model's generated stream will only contain the closing /// `` and needs a middleware prepend for the UI tag parser to work. let templateInjectsThinkTag: Bool + /// True when the template both exposes a toggle kwarg and uses + /// reasoning markers the runtime recognizes. + var isToggleableThinking: Bool { supportsThinking && hasEnableThinkingKwarg } static let none = Capability( supportsThinking: false, diff --git a/Packages/OsaurusCore/Tests/Model/ModelProfileRegistryTests.swift b/Packages/OsaurusCore/Tests/Model/ModelProfileRegistryTests.swift index c92f59f35..cf63f8bf8 100644 --- a/Packages/OsaurusCore/Tests/Model/ModelProfileRegistryTests.swift +++ b/Packages/OsaurusCore/Tests/Model/ModelProfileRegistryTests.swift @@ -85,9 +85,9 @@ struct ModelProfileRegistryTests { @Test("Non-reasoning Gemma variants do not get a thinking toggle") func gemma_noThinkingToggle() { - let profile = ModelProfileRegistry.profile(for: "gemma-4-e2b-it-4bit") - // Gemma can match an image-options profile but should never expose - // the thinking toggle because it doesn't honor `enable_thinking`. + let profile = ModelProfileRegistry.profile(for: "gemma-2-non-reasoning-\(UUID().uuidString)") + // Use a guaranteed-missing suffix so this stays independent of the + // developer's locally installed model directory. #expect(profile?.thinkingOption == nil) } } diff --git a/Packages/OsaurusCore/Tests/Service/LocalReasoningCapabilityTests.swift b/Packages/OsaurusCore/Tests/Service/LocalReasoningCapabilityTests.swift index c5747d11f..bc09bf5c5 100644 --- a/Packages/OsaurusCore/Tests/Service/LocalReasoningCapabilityTests.swift +++ b/Packages/OsaurusCore/Tests/Service/LocalReasoningCapabilityTests.swift @@ -21,6 +21,7 @@ struct LocalReasoningCapabilityTests { #expect(cap.supportsThinking) #expect(cap.hasEnableThinkingKwarg) #expect(cap.templateInjectsThinkTag) + #expect(cap.isToggleableThinking) } @Test("Qwen3-style: supports thinking but no template-side injection") @@ -35,6 +36,7 @@ struct LocalReasoningCapabilityTests { #expect(cap.supportsThinking) #expect(cap.hasEnableThinkingKwarg) #expect(!cap.templateInjectsThinkTag) + #expect(cap.isToggleableThinking) } @Test("Non-reasoning template: all signals false") @@ -46,6 +48,21 @@ struct LocalReasoningCapabilityTests { #expect(!cap.supportsThinking) #expect(!cap.hasEnableThinkingKwarg) #expect(!cap.templateInjectsThinkTag) + #expect(!cap.isToggleableThinking) + } + + @Test("enable_thinking alone is not enough for a UI toggle") + func enableThinkingWithoutRecognizedThinkingMarkers() { + let template = """ + {%- if enable_thinking is defined and enable_thinking -%} + {{- '<|reason|>' -}} + {%- endif -%} + """ + let cap = LocalReasoningCapability.analyze(template: template) + #expect(!cap.supportsThinking) + #expect(cap.hasEnableThinkingKwarg) + #expect(!cap.templateInjectsThinkTag) + #expect(!cap.isToggleableThinking) } @Test("GLM-flash style: emits without injection (middleware-needed)") @@ -60,6 +77,7 @@ struct LocalReasoningCapabilityTests { #expect(cap.supportsThinking) #expect(!cap.hasEnableThinkingKwarg) #expect(!cap.templateInjectsThinkTag) + #expect(!cap.isToggleableThinking) } /// Gemma-4's chat_template.jinja opens thinking with the pipe-wrapped @@ -83,6 +101,7 @@ struct LocalReasoningCapabilityTests { #expect(cap.supportsThinking) #expect(cap.hasEnableThinkingKwarg) #expect(cap.templateInjectsThinkTag) + #expect(cap.isToggleableThinking) } // MARK: - jang_config.json chat.reasoning fallback (DSV4-class bundles) @@ -123,6 +142,7 @@ struct LocalReasoningCapabilityTests { // Python encoder takes `thinking_mode` as a positional argument // instead, so the kwarg flag stays false. #expect(cap?.hasEnableThinkingKwarg == false) + #expect(cap?.isToggleableThinking == false) // DSV4's template is outside the bundle (Python module) — vmlx // injects the thinking tag itself when the caller picks thinking // mode. From osaurus's perspective there is no on-disk Jinja to diff --git a/Packages/OsaurusCore/Tests/Storage/SQLCipherVendorGuardTests.swift b/Packages/OsaurusCore/Tests/Storage/SQLCipherVendorGuardTests.swift index 5059ce21b..8e3babce7 100644 --- a/Packages/OsaurusCore/Tests/Storage/SQLCipherVendorGuardTests.swift +++ b/Packages/OsaurusCore/Tests/Storage/SQLCipherVendorGuardTests.swift @@ -51,7 +51,7 @@ struct SQLCipherVendorGuardTests { return FileManager.default.fileExists(atPath: header.path) ? header : nil } - private static func sqlite3ExtHeaderURL() -> URL? { + private static func sqlCipherHeaderURL(named name: String) -> URL? { let here = URL(fileURLWithPath: #filePath) var cursor = here.deletingLastPathComponent() // Storage/ cursor.deleteLastPathComponent() // Tests/ @@ -60,7 +60,7 @@ struct SQLCipherVendorGuardTests { pkg .appendingPathComponent("SQLCipher", isDirectory: true) .appendingPathComponent("include", isDirectory: true) - .appendingPathComponent("sqlite3ext.h") + .appendingPathComponent(name) return FileManager.default.fileExists(atPath: header.path) ? header : nil } @@ -108,54 +108,12 @@ struct SQLCipherVendorGuardTests { ) } - @Test - func sqlite3ExtHeader_containsSqlite3ExtOmitGuard() throws { - guard let url = Self.sqlite3ExtHeaderURL() else { - Issue.record( - "Could not locate Packages/OsaurusCore/SQLCipher/include/sqlite3ext.h. If the path moved, update this test." - ) - return - } - let contents = try String(contentsOf: url, encoding: .utf8) - - let openGuard = "#ifndef OSAURUS_OMIT_SQLITE3EXT_HEADERS" - let closeGuard = "#endif /* OSAURUS_OMIT_SQLITE3EXT_HEADERS" - - #expect( - contents.contains(openGuard), - """ - sqlite3ext.h is missing the OSAURUS_OMIT_SQLITE3EXT_HEADERS open guard. - - This means a SQLCipher amalgamation bump overwrote the - OSAURUS LOCAL MODIFICATION block. Without this guard, - `import OsaurusSQLCipher` collides with Apple's system - `SQLite3` module on `struct sqlite3_api_routines`. - - Re-apply the guard by wrapping the contents of sqlite3ext.h. - """ - ) - - #expect( - contents.contains(closeGuard), - """ - sqlite3ext.h has the OSAURUS_OMIT_SQLITE3EXT_HEADERS open guard - but not the matching close. The wrap is incomplete. - """ - ) - } - @Test func umbrellaHeader_definesSqliteHasCodec() throws { - let here = URL(fileURLWithPath: #filePath) - var cursor = here.deletingLastPathComponent() // Storage/ - cursor.deleteLastPathComponent() // Tests/ - let pkg = cursor.deletingLastPathComponent() // OsaurusCore/ - let umbrella = - pkg - .appendingPathComponent("SQLCipher", isDirectory: true) - .appendingPathComponent("include", isDirectory: true) - .appendingPathComponent("OsaurusSQLCipher.h") - guard let contents = try? String(contentsOf: umbrella, encoding: .utf8) else { + guard + let umbrella = Self.sqlCipherHeaderURL(named: "OsaurusSQLCipher.h"), + let contents = try? String(contentsOf: umbrella, encoding: .utf8) + else { Issue.record( """ OsaurusSQLCipher.h umbrella header is missing. Despite @@ -178,12 +136,73 @@ struct SQLCipherVendorGuardTests { fail to compile. """ ) + #expect( + contents.contains("OSAURUS_OMIT_FTS5_HEADERS"), + """ + OsaurusSQLCipher.h must define OSAURUS_OMIT_FTS5_HEADERS + before including sqlite3.h. The target cSettings flag + protects the C compilation, but Swift's Clang module + import parses this umbrella header separately. + """ + ) #expect( contents.contains("#include \"sqlite3.h\""), "OsaurusSQLCipher.h must #include \"sqlite3.h\" so the codec define applies in the same translation unit." ) } + @Test + func sqlite3ExtHeader_omitsLoadableExtensionApiFromSwiftImport() throws { + guard let url = Self.sqlCipherHeaderURL(named: "sqlite3ext.h") else { + Issue.record( + "Could not locate Packages/OsaurusCore/SQLCipher/include/sqlite3ext.h. If the path moved, update this test." + ) + return + } + let contents = try String(contentsOf: url, encoding: .utf8) + + #expect( + contents.contains("#ifndef OSAURUS_OMIT_SQLITE_EXTENSION_API"), + """ + sqlite3ext.h is missing the OSAURUS_OMIT_SQLITE_EXTENSION_API open guard. + + Newer macOS SDKs can append fields to sqlite3_api_routines + before this pinned SQLCipher version adopts the same SQLite + version. Without this guard, Swift's Clang importer can + reject OsaurusSQLCipher when another dependency imports + Apple's system SQLite3 module in the same build. + """ + ) + #expect( + contents.contains("#endif /* OSAURUS_OMIT_SQLITE_EXTENSION_API"), + """ + sqlite3ext.h has the OSAURUS_OMIT_SQLITE_EXTENSION_API open guard + but not the matching close. Re-read the README + "Re-applying the sqlite3ext import guard" section. + """ + ) + } + + @Test + func umbrellaHeader_definesOmitExtensionApi() throws { + guard + let umbrella = Self.sqlCipherHeaderURL(named: "OsaurusSQLCipher.h"), + let contents = try? String(contentsOf: umbrella, encoding: .utf8) + else { + Issue.record("Could not read OsaurusSQLCipher.h") + return + } + #expect( + contents.contains("OSAURUS_OMIT_SQLITE_EXTENSION_API"), + """ + OsaurusSQLCipher.h must define OSAURUS_OMIT_SQLITE_EXTENSION_API + before including sqlite3ext.h. Otherwise the vendored + sqlite3_api_routines struct can collide with the system + SQLite3 module on newer macOS SDKs. + """ + ) + } + @Test func packageManifest_definesOmitFts5HeadersFlag() throws { // Walk up from `#filePath` to `Packages/OsaurusCore/Package.swift`. @@ -207,16 +226,5 @@ struct SQLCipherVendorGuardTests { .define("OSAURUS_OMIT_FTS5_HEADERS"), """ ) - - #expect( - contents.contains("OSAURUS_OMIT_SQLITE3EXT_HEADERS"), - """ - Package.swift no longer defines OSAURUS_OMIT_SQLITE3EXT_HEADERS - in the OsaurusSQLCipher target's cSettings. The header - guard is useless without this flag. Add it back: - - .define("OSAURUS_OMIT_SQLITE3EXT_HEADERS"), - """ - ) } } diff --git a/README.md b/README.md index 8c8ca4adf..6a19fb33d 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Connect to OpenAI, Anthropic, Gemini, xAI/Grok, [Venice AI](https://venice.ai), ## MCP -Osaurus is a full MCP (Model Context Protocol) server. Give Cursor, Claude Desktop, or any MCP client access to your tools: +Osaurus is a full MCP (Model Context Protocol) server. Give any MCP-compatible client access to your tools: ```json { @@ -172,7 +172,7 @@ Drop-in endpoints for existing tools: | Anthropic | `http://127.0.0.1:1337/anthropic/v1/messages` | | Ollama | `http://127.0.0.1:1337/api/chat` | -All prefixes supported (`/v1`, `/api`, `/v1/api`). Full function calling with streaming tool call deltas. `/chat/completions` keeps **strict OpenAI semantics** — it returns `tool_calls` and the client executes them, so Osaurus drops in cleanly behind harnesses (Cursor, OpenWebUI, Continue, Aider) that already manage their own tool loop. For server-side autonomous loops use `POST /agents/{id}/run`; to expose Osaurus tools to remote MCP harnesses use `/mcp/tools` + `/mcp/call`. See [OpenAI API Guide](docs/OpenAI_API_GUIDE.md) for tool calling, streaming, and SDK examples. Building a macOS app that connects to Osaurus? See the [Shared Configuration Guide](docs/SHARED_CONFIGURATION_GUIDE.md). +All prefixes supported (`/v1`, `/api`, `/v1/api`). Full function calling with streaming tool call deltas. `/chat/completions` keeps **strict OpenAI semantics** -- it returns `tool_calls` and the client executes them, so Osaurus drops in cleanly behind harnesses that already manage their own tool loop. For server-side autonomous loops use `POST /agents/{id}/run`; to expose Osaurus tools to remote MCP harnesses use `/mcp/tools` + `/mcp/call`. See [OpenAI API Guide](docs/OpenAI_API_GUIDE.md) for tool calling, streaming, and SDK examples. Building a macOS app that connects to Osaurus? See the [Shared Configuration Guide](docs/SHARED_CONFIGURATION_GUIDE.md). ## CLI diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 4eb7c5859..67f12c994 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -277,7 +277,7 @@ See [INFERENCE_RUNTIME.md](./INFERENCE_RUNTIME.md) for the full runtime architec ### Anthropic API Compatibility -**Purpose:** Provide Anthropic Messages API compatibility for Claude Code and other Anthropic SDK clients. +**Purpose:** Provide Anthropic Messages API compatibility for Anthropic SDK-compatible clients. **Components:** diff --git a/docs/OpenAI_API_GUIDE.md b/docs/OpenAI_API_GUIDE.md index b68fc12ae..f7339bf8e 100644 --- a/docs/OpenAI_API_GUIDE.md +++ b/docs/OpenAI_API_GUIDE.md @@ -38,7 +38,7 @@ Example response: Generate chat completions using the specified model. -> **Tool calling:** `/chat/completions` follows **strict OpenAI semantics** — when the model emits `tool_calls`, the response (or final SSE chunk) returns those calls and the **client is expected to execute them and POST the results back** in the next request. Osaurus deliberately does **not** auto-execute tools on this endpoint so it can serve as a drop-in backend for harnesses that already manage their own tool loop (Cursor, OpenWebUI, Continue, Aider, etc.). +> **Tool calling:** `/chat/completions` follows **strict OpenAI semantics** — when the model emits `tool_calls`, the response (or final SSE chunk) returns those calls and the **client is expected to execute them and POST the results back** in the next request. Osaurus deliberately does **not** auto-execute tools on this endpoint so it can serve as a drop-in backend for harnesses that already manage their own tool loop. > > If you want server-side autonomous loops, use `POST /agents/{id}/run` (it executes tools, manages iteration budget, and streams hint/done frames). If you want to expose Osaurus tools to a remote model harness, use the MCP endpoints (`GET /mcp/tools`, `POST /mcp/call`). @@ -257,7 +257,7 @@ When you want Osaurus to execute tools on your behalf (manage the iteration budg ### Aggregating Osaurus tools through MCP -The Model Context Protocol endpoints let any MCP-aware harness (e.g. Cursor, Claude Desktop with MCP plugins) connect and discover Osaurus tools without committing to the agent endpoint: +The Model Context Protocol endpoints let any MCP-aware harness connect and discover Osaurus tools without committing to the agent endpoint: - `GET /mcp/tools` — list registered tools as MCP `Tool` definitions - `POST /mcp/call` — invoke a tool by name with structured arguments