First public release v2.3.1
This commit is contained in:
@@ -70,8 +70,9 @@ class AnthropicProvider: AIProvider {
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Models (hardcoded — Anthropic has no public models list endpoint)
|
||||
// MARK: - Models
|
||||
|
||||
/// Local metadata used to enrich API results (pricing, context length) and as offline fallback.
|
||||
private static let knownModels: [ModelInfo] = [
|
||||
ModelInfo(
|
||||
id: "claude-opus-4-6",
|
||||
@@ -123,12 +124,64 @@ class AnthropicProvider: AIProvider {
|
||||
),
|
||||
]
|
||||
|
||||
/// Fetch live model list from GET /v1/models, enriched with local pricing/context metadata.
|
||||
/// Falls back to knownModels if the request fails (no key, offline, etc.).
|
||||
func listModels() async throws -> [ModelInfo] {
|
||||
return Self.knownModels
|
||||
guard let url = URL(string: "\(baseURL)/models") else { return Self.knownModels }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
do {
|
||||
try await applyAuth(to: &request)
|
||||
} catch {
|
||||
Log.api.warning("Anthropic listModels: auth failed, using fallback — \(error.localizedDescription)")
|
||||
return Self.knownModels
|
||||
}
|
||||
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
|
||||
let code = (response as? HTTPURLResponse)?.statusCode ?? 0
|
||||
Log.api.warning("Anthropic listModels: HTTP \(code), using fallback")
|
||||
return Self.knownModels
|
||||
}
|
||||
|
||||
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let items = json["data"] as? [[String: Any]] else {
|
||||
Log.api.warning("Anthropic listModels: unexpected JSON shape, using fallback")
|
||||
return Self.knownModels
|
||||
}
|
||||
|
||||
let enrichment = Dictionary(uniqueKeysWithValues: Self.knownModels.map { ($0.id, $0) })
|
||||
|
||||
let models: [ModelInfo] = items.compactMap { item in
|
||||
guard let id = item["id"] as? String,
|
||||
id.hasPrefix("claude-") else { return nil }
|
||||
let displayName = item["display_name"] as? String ?? id
|
||||
if let known = enrichment[id] { return known }
|
||||
// Unknown new model — use display name and sensible defaults
|
||||
return ModelInfo(
|
||||
id: id,
|
||||
name: displayName,
|
||||
description: item["description"] as? String ?? "",
|
||||
contextLength: 200_000,
|
||||
pricing: .init(prompt: 0, completion: 0),
|
||||
capabilities: .init(vision: true, tools: true, online: false)
|
||||
)
|
||||
}
|
||||
|
||||
Log.api.info("Anthropic listModels: fetched \(models.count) model(s) from API")
|
||||
return models.isEmpty ? Self.knownModels : models
|
||||
|
||||
} catch {
|
||||
Log.api.warning("Anthropic listModels: network error (\(error.localizedDescription)), using fallback")
|
||||
return Self.knownModels
|
||||
}
|
||||
}
|
||||
|
||||
func getModel(_ id: String) async throws -> ModelInfo? {
|
||||
return Self.knownModels.first { $0.id == id }
|
||||
let models = try await listModels()
|
||||
return models.first { $0.id == id }
|
||||
}
|
||||
|
||||
// MARK: - Chat Completion
|
||||
@@ -212,7 +265,7 @@ class AnthropicProvider: AIProvider {
|
||||
var body: [String: Any] = [
|
||||
"model": model,
|
||||
"messages": conversationMessages,
|
||||
"max_tokens": maxTokens ?? 4096,
|
||||
"max_tokens": maxTokens ?? 16000,
|
||||
"stream": false
|
||||
]
|
||||
if let systemText = systemText {
|
||||
@@ -282,6 +335,7 @@ class AnthropicProvider: AIProvider {
|
||||
|
||||
var currentId = ""
|
||||
var currentModel = request.model
|
||||
var inputTokens = 0
|
||||
|
||||
for try await line in bytes.lines {
|
||||
// Anthropic SSE: "event: ..." and "data: {...}"
|
||||
@@ -299,6 +353,9 @@ class AnthropicProvider: AIProvider {
|
||||
if let message = event["message"] as? [String: Any] {
|
||||
currentId = message["id"] as? String ?? ""
|
||||
currentModel = message["model"] as? String ?? request.model
|
||||
if let usageDict = message["usage"] as? [String: Any] {
|
||||
inputTokens = usageDict["input_tokens"] as? Int ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
case "content_block_delta":
|
||||
@@ -321,7 +378,7 @@ class AnthropicProvider: AIProvider {
|
||||
var usage: ChatResponse.Usage? = nil
|
||||
if let usageDict = event["usage"] as? [String: Any] {
|
||||
let outputTokens = usageDict["output_tokens"] as? Int ?? 0
|
||||
usage = ChatResponse.Usage(promptTokens: 0, completionTokens: outputTokens, totalTokens: outputTokens)
|
||||
usage = ChatResponse.Usage(promptTokens: inputTokens, completionTokens: outputTokens, totalTokens: inputTokens + outputTokens)
|
||||
}
|
||||
continuation.yield(StreamChunk(
|
||||
id: currentId,
|
||||
@@ -431,7 +488,7 @@ class AnthropicProvider: AIProvider {
|
||||
var body: [String: Any] = [
|
||||
"model": request.model,
|
||||
"messages": apiMessages,
|
||||
"max_tokens": request.maxTokens ?? 4096,
|
||||
"max_tokens": request.maxTokens ?? 16000,
|
||||
"stream": stream
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user