Skip to content

Commit a9ddfa0

Browse files
Merge pull request #5 from Compiler-Inc/add/simple-calls
feat: add simple calls
2 parents 22f9c70 + d10071a commit a9ddfa0

38 files changed

+1067
-1828
lines changed

Sources/CompilerSwiftAI/Auth/CompilerClient+Auth.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ extension CompilerClient {
2929
}
3030

3131
func authenticateWithServer(idToken: String, nonce: String? = nil) async throws -> String {
32-
let lowercasedAppID = appID.uuidString.lowercased()
32+
let lowercasedAppID = appID.lowercased()
3333
let endpoint = "\(baseURL)/v1/apps/\(lowercasedAppID)/end-users/apple"
34+
3435
guard let url = URL(string: endpoint) else {
3536
authLogger.error("Invalid URL: \(self.baseURL)")
3637
throw AuthError.invalidResponse
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// File.swift
3+
// CompilerSwiftAI
4+
//
5+
// Created by Atharva Vaidya on 3/8/25.
6+
//
7+
8+
import Foundation
9+
10+
public protocol AppStateProtocol: Encodable & Sendable {}

Sources/CompilerSwiftAI/Function Calling/CompilerClient+Functions.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import OSLog
44

5-
extension CompilerClient {
5+
public extension CompilerClient {
66
// Request model
7-
struct Request<State>: Encodable, Sendable where State: Encodable & Sendable {
7+
internal struct Request<State>: Encodable, Sendable where State: Encodable & Sendable {
88
let id: String
99
let prompt: String
1010
let state: State
@@ -16,10 +16,13 @@ extension CompilerClient {
1616
/// - state: Current state of your app (as defined by the developer, only needs to conform to Encodable and Sendable)
1717
/// - token: Authorization token
1818
/// - Returns: An array of functions with Parameters that are both Decodable and Sendable
19-
public func processFunction<State: Encodable & Sendable, Parameters: Decodable & Sendable>(_ prompt: String, for state: State, using _: String) async throws -> [Function<Parameters>] {
19+
func processFunction<State: AppStateProtocol, FunctionType: Decodable & Sendable>(
20+
prompt: String,
21+
for state: State
22+
) async throws -> [FunctionType] {
2023
functionLogger.debug("Starting processFunction with prompt: \(prompt)")
2124

22-
let endpoint = "\(baseURL)/v1/function-call/\(appID.uuidString)"
25+
let endpoint = "\(baseURL)/v1/function-call/\(appID)"
2326

2427
guard let url = URL(string: endpoint) else {
2528
functionLogger.error("Invalid URL: \(self.baseURL)")
@@ -28,7 +31,7 @@ extension CompilerClient {
2831

2932
functionLogger.debug("URL created: \(url)")
3033

31-
let request = Request(id: appID.uuidString, prompt: prompt, state: state)
34+
let request = Request(id: appID, prompt: prompt, state: state)
3235

3336
var urlRequest = URLRequest(url: url)
3437
urlRequest.httpMethod = "POST"
@@ -56,7 +59,7 @@ extension CompilerClient {
5659
functionLogger.debug("Response body: \(String(data: data, encoding: .utf8) ?? "nil")")
5760

5861
do {
59-
let functions = try JSONDecoder().decode([Function<Parameters>].self, from: data)
62+
let functions = try JSONDecoder().decode([FunctionType].self, from: data)
6063
functionLogger.debug("Decoded response: \(functions)")
6164
return functions
6265
} catch {
Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
// Copyright © 2025 Compiler, Inc. All rights reserved.
22

33
/// The Function struct hold's the function call being returned from Compiler with parameters defined by in the applcation
4-
public struct Function<Parameters>: Decodable, Sendable where Parameters: Decodable & Sendable {
4+
public struct Function<Parameters: Decodable & Sendable>: FunctionCallProtocol {
5+
private enum CodingKeys: String, CodingKey {
6+
case id = "function"
7+
case parameters
8+
case colloquialDescription = "colloquial_response"
9+
}
10+
511
/// Function name
6-
public let name: String
12+
public let id: String
13+
714
/// Parameters are Decodable and Sendable and can be anything you need
815
public let parameters: Parameters?
9-
10-
private enum CodingKeys: String, CodingKey {
11-
case name = "function"
12-
case parameters
16+
17+
/// The description to show the user while this function is being executed.
18+
public let colloquialDescription: String
19+
20+
public init(id: String, parameters: Parameters?, colloquialDescription: String) {
21+
self.id = id
22+
self.parameters = parameters
23+
self.colloquialDescription = colloquialDescription
1324
}
1425
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// File.swift
3+
// CompilerSwiftAI
4+
//
5+
// Created by Atharva Vaidya on 3/8/25.
6+
//
7+
8+
import Foundation
9+
10+
public protocol FunctionCallProtocol: Decodable & Sendable {
11+
associatedtype Parameters: Decodable & Sendable
12+
13+
/// The identifier for this function
14+
var id: String { get }
15+
16+
/// All the parameters for this function type.
17+
var parameters: Parameters { get }
18+
19+
/// The description to show the user while this function is being executed.
20+
var colloquialDescription: String { get }
21+
}

Sources/CompilerSwiftAI/Model Calling/ChatHistory.swift

Lines changed: 22 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -3,104 +3,43 @@
33
import Foundation
44

55
/// A convenience type to help manage conversation history with LLMs
6-
@available(macOS 14.0, iOS 17.0, *)
7-
actor ChatHistory {
8-
var _messages: [Message]
9-
var messageID: UUID?
10-
11-
/// We'll store the active continuation if someone requests `messagesStream`.
12-
var continuation: AsyncStream<[Message]>.Continuation?
13-
14-
var messages: [Message] {
15-
// Return all messages except those that are *still* streaming
16-
get async {
17-
_messages.filter { $0.state == .complete }
18-
}
19-
}
20-
21-
/// A continuous stream of *all* messages, including .streaming states
22-
var messagesStream: AsyncStream<[Message]> {
23-
AsyncStream { continuation in
24-
self.continuation = continuation
25-
// Immediately yield whatever we have
26-
continuation.yield(_messages)
27-
}
28-
}
29-
30-
init(systemPrompt: String) {
31-
_messages = [Message(role: .system, content: systemPrompt)]
32-
}
33-
34-
func notifyMessageUpdate() {
35-
continuation?.yield(_messages)
6+
actor ChatHistory: ObservableObject {
7+
@Published var messages: [Message] = []
8+
9+
/// The current message that is being streamed.
10+
private var messageId: String?
11+
12+
func addSystemPrompt(_ content: String) {
13+
messages.append(Message.systemMessage(content: content))
3614
}
3715

3816
func addUserMessage(_ content: String) {
39-
_messages.append(Message(role: .user, content: content))
40-
notifyMessageUpdate()
41-
}
42-
43-
func addAssistantMessage(_ content: String) {
44-
_messages.append(Message(role: .assistant, content: content))
45-
notifyMessageUpdate()
17+
messages.append(Message.userMessage(content: content))
4618
}
4719

4820
/// Start a new streaming response from the assistant
49-
@discardableResult
50-
func beginStreamingResponse() -> UUID {
51-
let id = UUID()
52-
let msg = Message(id: id, role: .assistant, content: "", state: .streaming(""))
53-
_messages.append(msg)
54-
messageID = id
55-
notifyMessageUpdate()
56-
return id
21+
func beginStreamingResponse() {
22+
let msg = Message.assistantMessage(content: "")
23+
messages.append(msg)
24+
messageId = msg.id
5725
}
5826

5927
/// Update the partial text of the *current* streaming assistant message
6028
func updateStreamingMessage(_ partial: String) {
61-
guard let id = messageID,
62-
let idx = _messages.firstIndex(where: { $0.id == id })
63-
else {
29+
guard let id = messageId, let index = messages.firstIndex(where: { $0.id == id }) else {
6430
return
6531
}
66-
let old = _messages[idx]
67-
_messages[idx] = Message(
68-
id: old.id,
69-
role: old.role,
70-
content: partial,
71-
state: .streaming(partial)
32+
33+
let streamingMessage = messages[index]
34+
messages[index] = Message(
35+
id: streamingMessage.id,
36+
role: streamingMessage.role,
37+
content: streamingMessage.content + partial
7238
)
73-
notifyMessageUpdate()
7439
}
7540

7641
/// Mark the streaming message complete with final text
77-
func completeStreamingMessage(_ finalContent: String) {
78-
guard let id = messageID,
79-
let idx = _messages.firstIndex(where: { $0.id == id })
80-
else {
81-
return
82-
}
83-
_messages[idx] = Message(
84-
id: id,
85-
role: .assistant,
86-
content: finalContent,
87-
state: .complete
88-
)
89-
messageID = nil
90-
notifyMessageUpdate()
91-
}
92-
93-
func clearHistory(keepingSystemPrompt: Bool = true) {
94-
messageID = nil
95-
if keepingSystemPrompt, let systemMessage = _messages.first, systemMessage.role == .system {
96-
_messages = [systemMessage]
97-
} else {
98-
_messages.removeAll()
99-
}
100-
notifyMessageUpdate()
101-
}
102-
103-
deinit {
104-
continuation?.finish()
42+
func completeStreamingMessage() {
43+
messageId = nil
10544
}
10645
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2025 Compiler, Inc. All rights reserved.
2+
3+
import OSLog
4+
5+
extension CompilerClient {
6+
func makeModelCallWithResponse(
7+
using metadata: ModelMetadata,
8+
request: CompletionRequestDTO
9+
) async throws -> ChatCompletionResponse {
10+
let endpoint = "\(baseURL)/v1/apps/\(appID)/end-users/model-call"
11+
guard let url = URL(string: endpoint) else {
12+
modelLogger.error("Invalid URL: \(self.baseURL)")
13+
throw URLError(.badURL)
14+
}
15+
16+
modelLogger.debug("Making completion model call to: \(endpoint)")
17+
18+
var urlRequest = URLRequest(url: url)
19+
urlRequest.httpMethod = "POST"
20+
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
21+
22+
let token = try await getValidToken()
23+
urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
24+
25+
let encoder = JSONEncoder()
26+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
27+
let jsonData = try encoder.encode(request)
28+
urlRequest.httpBody = jsonData
29+
30+
return try await performRequest(urlRequest)
31+
}
32+
33+
func performRequest(_ request: URLRequest) async throws -> ChatCompletionResponse {
34+
let (data, response) = try await URLSession.shared.data(for: request)
35+
36+
guard let httpResponse = response as? HTTPURLResponse else {
37+
modelLogger.error("Invalid response type received")
38+
throw AuthError.invalidResponse
39+
}
40+
41+
modelLogger.debug("Response status: \(httpResponse.statusCode)")
42+
modelLogger.debug("Response headers: \(httpResponse.allHeaderFields)")
43+
44+
if let responseString = String(data: data, encoding: .utf8) {
45+
modelLogger.debug("Raw response data: \(responseString)")
46+
}
47+
48+
guard 200 ... 299 ~= httpResponse.statusCode else {
49+
modelLogger.error("Model call failed with status \(httpResponse.statusCode)")
50+
throw AuthError.serverError("Model call failed with status \(httpResponse.statusCode)")
51+
}
52+
53+
return try JSONDecoder().decode(ChatCompletionResponse.self, from: data)
54+
}
55+
}

0 commit comments

Comments
 (0)