Skip to content

Commit 8024f02

Browse files
committed
Add X-Title field to OpenRouter requests
1 parent da8e49e commit 8024f02

2 files changed

Lines changed: 81 additions & 79 deletions

File tree

Tool/Sources/OpenAIService/APIs/OpenAIChatCompletionsService.swift

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -233,28 +233,9 @@ actor OpenAIChatCompletionsService: ChatCompletionsStreamAPI, ChatCompletionsAPI
233233
let encoder = JSONEncoder()
234234
request.httpBody = try encoder.encode(requestBody)
235235
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
236-
if !apiKey.isEmpty {
237-
switch model.format {
238-
case .openAI:
239-
if !model.info.openAIInfo.organizationID.isEmpty {
240-
request.setValue(
241-
model.info.openAIInfo.organizationID,
242-
forHTTPHeaderField: "OpenAI-Organization"
243-
)
244-
}
245-
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
246-
case .openAICompatible:
247-
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
248-
case .azureOpenAI:
249-
request.setValue(apiKey, forHTTPHeaderField: "api-key")
250-
case .googleAI:
251-
assertionFailure("Unsupported")
252-
case .ollama:
253-
assertionFailure("Unsupported")
254-
case .claude:
255-
assertionFailure("Unsupported")
256-
}
257-
}
236+
237+
Self.setupRequestTitle(&request)
238+
Self.setupAPIKey(&request, model: model, apiKey: apiKey)
258239

259240
let (result, response) = try await URLSession.shared.bytes(for: request)
260241
guard let response = response as? HTTPURLResponse else {
@@ -303,13 +284,50 @@ actor OpenAIChatCompletionsService: ChatCompletionsStreamAPI, ChatCompletionsAPI
303284
let encoder = JSONEncoder()
304285
request.httpBody = try encoder.encode(requestBody)
305286
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
287+
288+
Self.setupRequestTitle(&request)
289+
Self.setupAPIKey(&request, model: model, apiKey: apiKey)
290+
291+
let (result, response) = try await URLSession.shared.data(for: request)
292+
guard let response = response as? HTTPURLResponse else {
293+
throw ChatGPTServiceError.responseInvalid
294+
}
295+
296+
guard response.statusCode == 200 else {
297+
let error = try? JSONDecoder().decode(CompletionAPIError.self, from: result)
298+
throw error ?? ChatGPTServiceError
299+
.otherError(String(data: result, encoding: .utf8) ?? "Unknown Error")
300+
}
301+
302+
do {
303+
let body = try JSONDecoder().decode(ResponseBody.self, from: result)
304+
return body.formalized()
305+
} catch {
306+
dump(error)
307+
throw error
308+
}
309+
}
310+
311+
static func setupRequestTitle(_ request: inout URLRequest) {
312+
if #available(macOS 13.0, *) {
313+
if request.url?.host == "openrouter.ai" {
314+
request.setValue("Copilot for Xcode", forHTTPHeaderField: "X-Title")
315+
}
316+
} else {
317+
if request.url?.host == "openrouter.ai" {
318+
request.setValue("Copilot for Xcode", forHTTPHeaderField: "X-Title")
319+
}
320+
}
321+
}
322+
323+
static func setupAPIKey(_ request: inout URLRequest, model: ChatModel, apiKey: String) {
306324
if !apiKey.isEmpty {
307325
switch model.format {
308326
case .openAI:
309327
if !model.info.openAIInfo.organizationID.isEmpty {
310328
request.setValue(
311-
"OpenAI-Organization",
312-
forHTTPHeaderField: model.info.openAIInfo.organizationID
329+
model.info.openAIInfo.organizationID,
330+
forHTTPHeaderField: "OpenAI-Organization"
313331
)
314332
}
315333
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
@@ -325,25 +343,6 @@ actor OpenAIChatCompletionsService: ChatCompletionsStreamAPI, ChatCompletionsAPI
325343
assertionFailure("Unsupported")
326344
}
327345
}
328-
329-
let (result, response) = try await URLSession.shared.data(for: request)
330-
guard let response = response as? HTTPURLResponse else {
331-
throw ChatGPTServiceError.responseInvalid
332-
}
333-
334-
guard response.statusCode == 200 else {
335-
let error = try? JSONDecoder().decode(CompletionAPIError.self, from: result)
336-
throw error ?? ChatGPTServiceError
337-
.otherError(String(data: result, encoding: .utf8) ?? "Unknown Error")
338-
}
339-
340-
do {
341-
let body = try JSONDecoder().decode(ResponseBody.self, from: result)
342-
return body.formalized()
343-
} catch {
344-
dump(error)
345-
throw error
346-
}
347346
}
348347
}
349348

Tool/Sources/OpenAIService/APIs/OpenAIEmbeddingService.swift

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,9 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
3131
model: model.info.modelName
3232
))
3333
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
34-
if !apiKey.isEmpty {
35-
switch model.format {
36-
case .openAI:
37-
if model.info.openAIInfo.organizationID.isEmpty {
38-
request.setValue(
39-
"OpenAI-Organization",
40-
forHTTPHeaderField: model.info.openAIInfo.organizationID
41-
)
42-
}
43-
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
44-
case .openAICompatible:
45-
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
46-
case .azureOpenAI:
47-
request.setValue(apiKey, forHTTPHeaderField: "api-key")
48-
case .ollama:
49-
assertionFailure("Unsupported")
50-
}
51-
}
34+
35+
Self.setupRequestTitle(&request)
36+
Self.setupAPIKey(&request, model: model, apiKey: apiKey)
5237

5338
let (result, response) = try await URLSession.shared.data(for: request)
5439
guard let response = response as? HTTPURLResponse else {
@@ -87,24 +72,9 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
8772
model: model.info.modelName
8873
))
8974
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
90-
if !apiKey.isEmpty {
91-
switch model.format {
92-
case .openAI:
93-
if model.info.openAIInfo.organizationID.isEmpty {
94-
request.setValue(
95-
"OpenAI-Organization",
96-
forHTTPHeaderField: model.info.openAIInfo.organizationID
97-
)
98-
}
99-
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
100-
case .openAICompatible:
101-
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
102-
case .azureOpenAI:
103-
request.setValue(apiKey, forHTTPHeaderField: "api-key")
104-
case .ollama:
105-
assertionFailure("Unsupported")
106-
}
107-
}
75+
76+
Self.setupRequestTitle(&request)
77+
Self.setupAPIKey(&request, model: model, apiKey: apiKey)
10878

10979
let (result, response) = try await URLSession.shared.data(for: request)
11080
guard let response = response as? HTTPURLResponse else {
@@ -132,5 +102,38 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
132102
#endif
133103
return embeddingResponse
134104
}
105+
106+
static func setupRequestTitle(_ request: inout URLRequest) {
107+
if #available(macOS 13.0, *) {
108+
if request.url?.host == "openrouter.ai" {
109+
request.setValue("Copilot for Xcode", forHTTPHeaderField: "X-Title")
110+
}
111+
} else {
112+
if request.url?.host == "openrouter.ai" {
113+
request.setValue("Copilot for Xcode", forHTTPHeaderField: "X-Title")
114+
}
115+
}
116+
}
117+
118+
static func setupAPIKey(_ request: inout URLRequest, model: EmbeddingModel, apiKey: String) {
119+
if !apiKey.isEmpty {
120+
switch model.format {
121+
case .openAI:
122+
if model.info.openAIInfo.organizationID.isEmpty {
123+
request.setValue(
124+
model.info.openAIInfo.organizationID,
125+
forHTTPHeaderField: "OpenAI-Organization"
126+
)
127+
}
128+
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
129+
case .openAICompatible:
130+
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
131+
case .azureOpenAI:
132+
request.setValue(apiKey, forHTTPHeaderField: "api-key")
133+
case .ollama:
134+
assertionFailure("Unsupported")
135+
}
136+
}
137+
}
135138
}
136139

0 commit comments

Comments
 (0)