Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
on: [pull_request, workflow_dispatch]

jobs:
swift-bedrock-library:
swift-bedrock-library-build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build
run: swift build --configuration release

swift-bedrock-library-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Tests
run: swift test

soundness:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
Expand Down
29 changes: 16 additions & 13 deletions Sources/Converse/ContentBlocks/DocumentToJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,34 @@ import Foundation

// FIXME: avoid extensions on structs you do not control
extension SmithyDocument {
public func toJSON() throws -> JSON {
private func toJSONValue() throws -> JSONValue {
switch self.type {
case .string:
return JSON(with: try self.asString())
return try JSONValue(self.asString())
case .boolean:
return JSON(with: try self.asBoolean())
return try JSONValue(self.asBoolean())
case .integer:
return JSON(with: try self.asInteger())
return try JSONValue(self.asInteger())
case .double, .float:
return JSON(with: try self.asDouble())
return try JSONValue(self.asDouble())
case .list:
let array = try self.asList().map { try $0.toJSON() }
return JSON(with: array)
let array = try self.asList().map { try $0.toJSONValue() }
return JSONValue(array)
case .map:
let map = try self.asStringMap()
var result: [String: JSON] = [:]
for (key, value) in map {
result[key] = try value.toJSON()
}
return JSON(with: result)
let newMap = try map.mapValues({ try $0.toJSONValue() })
return JSONValue(newMap)
case .blob:
let data = try self.asBlob()
return JSON(with: data)
let json = try JSON(from: data)
return json.value
default:
throw DocumentError.typeMismatch("Unsupported type for JSON conversion: \(self.type)")
}
}

public func toJSON() throws -> JSON {
let value = try self.toJSONValue()
return JSON(with: value)
}
}
202 changes: 130 additions & 72 deletions Sources/Converse/ContentBlocks/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,57 +19,156 @@ import FoundationEssentials
import Foundation
#endif

public struct JSON: Codable, @unchecked Sendable { // FIXME: make Sendable
public var value: Any?
public enum JSONValue: Codable, Sendable {
case null
case int(Int)
case double(Double)
case string(String)
case bool(Bool)
case array([JSONValue])
case object([String: JSONValue])

public init(_ value: Any?) {

guard let value else {
self = .null
return
}
switch value {
case let v as Int:
self = .int(v)
break
case let v as Double:
self = .double(v)
break
case let v as String:
self = .string(v)
break
case let v as Bool:
self = .bool(v)
break
case let v as [Any]:
self = .array(v.map { JSONValue($0) })
break
case let v as [String: JSONValue]:
self = .object(v)
break
case let v as [JSONValue]:
self = .array(v)
break
case let v as JSONValue:
self = v
break
default:
fatalError("JSONValue: Unsupported type: \(type(of: value))")
}
}

/// Returns the value inside the JSON object defined by the given key.
public subscript<T>(key: String) -> T? {
get {
if let dictionary = value as? [String: JSON] {
let json: JSON? = dictionary[key]
let nestedValue: Any? = json?.getValue()
return nestedValue as? T
if case let .object(dictionary) = self {
guard let jsonValue = dictionary[key] else {
return nil
}
switch jsonValue {
case .int(let v): return v as? T
case .double(let v): return v as? T
case .string(let v): return v as? T
case .bool(let v): return v as? T
case .array(let v): return v as? T
case .object(let v): return v as? T
case .null: return nil
}
}
return nil
}
}

/// Returns the JSON object defined by the given key.
public subscript(key: String) -> JSON? {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let intValue = try? container.decode(Int.self) {
self = .int(intValue)
} else if let doubleValue = try? container.decode(Double.self) {
self = .double(doubleValue)
} else if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let boolValue = try? container.decode(Bool.self) {
self = .bool(boolValue)
} else if let arrayValue = try? container.decode([JSONValue].self) {
self = .array(arrayValue)
} else if let dictionaryValue = try? container.decode([String: JSONValue].self) {
self = .object(dictionaryValue)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type")
}
}

// MARK: Public Methods

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .int(let v):
try container.encode(v)
case .double(let v):
try container.encode(v)
case .string(let v):
try container.encode(v)
case .bool(let v):
try container.encode(v)
case .array(let v):
try container.encode(v)
case .object(let v):
try container.encode(v)
}
}

}

public struct JSON: Codable, Sendable {
public let value: JSONValue

public subscript<T>(key: String) -> T? {
get {
if let dictionary = value as? [String: JSON] {
return dictionary[key]
}
return nil
value[key]
}
}

/// Returns the value inside the JSON object defined by the given key.
public func getValue<T>(_ key: String) -> T? {
if let dictionary = value as? [String: JSON] {
return dictionary[key]?.value as? T
public subscript(key: String) -> JSONValue? {
get {
if case let .object(dictionary) = value {
if let v = dictionary[key] {
return v
}
}
return nil
}
return nil
}

/// Returns the value inside the JSON object.
public func getValue<T>() -> T? {
value as? T
switch value {
case .int(let v): return v as? T
case .double(let v): return v as? T
case .string(let v): return v as? T
case .bool(let v): return v as? T
case .array(let v): return v as? T
case .object(let v): return v as? T
case .null: return nil
}
}

// MARK: Initializers

public init(with value: Any?) {
public init(with value: JSONValue) {
self.value = value
}

public init(from string: String) throws {
var s: String!
if string.isEmpty {
s = "{}"
} else {
s = string
}
let s = string.isEmpty ? "{}" : string
guard let data = s.data(using: .utf8) else {
throw BedrockLibraryError.encodingError("Could not encode String to Data")
}
Expand All @@ -78,59 +177,18 @@ public struct JSON: Codable, @unchecked Sendable { // FIXME: make Sendable

public init(from data: Data) throws {
do {
print(String(decoding: data, as: UTF8.self))
self = try JSONDecoder().decode(JSON.self, from: data)
} catch {
throw BedrockLibraryError.decodingError("Failed to decode JSON: \(error)")
}
}

// Codable

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self.value = nil
} else if let intValue = try? container.decode(Int.self) {
self.value = intValue
} else if let doubleValue = try? container.decode(Double.self) {
self.value = doubleValue
} else if let stringValue = try? container.decode(String.self) {
self.value = stringValue
} else if let boolValue = try? container.decode(Bool.self) {
self.value = boolValue
} else if let arrayValue = try? container.decode([JSON].self) {
self.value = arrayValue.map { JSON(with: $0.value) }
} else if let dictionaryValue = try? container.decode([String: JSON].self) {
self.value = dictionaryValue.mapValues { JSON(with: $0.value) }
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type")
}
value = try container.decode(JSONValue.self)
}

// MARK: Public Methods

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let jsonValue = value as? JSON {
try jsonValue.encode(to: encoder)
} else if let intValue = value as? Int {
try container.encode(intValue)
} else if let doubleValue = value as? Double {
try container.encode(doubleValue)
} else if let stringValue = value as? String {
try container.encode(stringValue)
} else if let boolValue = value as? Bool {
try container.encode(boolValue)
} else if let arrayValue = value as? [Any] {
let jsonArray = arrayValue.map { JSON(with: $0) }
try container.encode(jsonArray)
} else if let dictionaryValue = value as? [String: Any] {
let jsonDictionary = dictionaryValue.mapValues { JSON(with: $0) }
try container.encode(jsonDictionary)
} else {
// try container.encode(String(describing: value ?? "nil"))
throw EncodingError.invalidValue(
value ?? "nil",
EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Unsupported type")
)
}
}
}
20 changes: 14 additions & 6 deletions Sources/Converse/ConverseRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,21 @@ public struct ConverseRequest {
}

func getAdditionalModelRequestFields() throws -> Smithy.Document? {
//FIXME: this is incorrect. We should check for all Claude models
if model == .claudev3_7_sonnet, let maxReasoningTokens {
let reasoningConfigJSON = JSON(with: [
"thinking": [
"type": "enabled",
"budget_tokens": maxReasoningTokens,
]
])
let reasoningConfigJSON = JSON(
with: .array(
[
.object([
"thinking": .object([
"type": .string("enabled"),
"budget_tokens": .int(maxReasoningTokens),
])
])
]
)
)

return try reasoningConfigJSON.toDocument()
}
return nil
Expand Down
Loading