Skip to content

Commit 672293d

Browse files
leogdionclaude
andcommitted
Update skit-analyze plan to use Swift OpenAPI Generator
Replace manual ClaudeAPIClient implementation with Swift OpenAPI Generator approach: - Add swift-openapi-generator, runtime, and urlsession dependencies - Use unofficial OpenAPI spec from laszukdawid/anthropic-openapi-spec - Generate type-safe client code at build time - Include setup instructions for OpenAPI spec and config files - Add AuthenticationMiddleware for API key injection Benefits: type safety, maintainability, automatic updates when official spec is released. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 3395cf6 commit 672293d

File tree

1 file changed

+218
-27
lines changed

1 file changed

+218
-27
lines changed

Docs/skit-analyze-plan.md

Lines changed: 218 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ This addresses the problem of implementing missing SyntaxKit features. Instead o
2525
- `SyntaxParser` (existing) - for parsing Swift code to AST
2626
- `ConfigKeyKit` (existing in project) - for configuration key management
2727
- `swift-configuration` - for CLI argument and environment variable handling
28-
- Foundation (URLSession) - for HTTP requests to Claude API
28+
- `swift-openapi-generator` - for generating type-safe Claude API client from OpenAPI spec
29+
- `swift-openapi-runtime` - runtime support for generated OpenAPI client
30+
- `swift-openapi-urlsession` - URLSession transport for OpenAPI client
2931

3032
### 2. Core Components
3133

@@ -437,26 +439,143 @@ struct ASTGenerator {
437439

438440
#### I. Claude API Client (`ClaudeAPIClient.swift`)
439441

440-
Handles communication with Claude API for code generation:
442+
Wraps the OpenAPI-generated client for code generation:
441443

442444
```swift
445+
import OpenAPIRuntime
446+
import OpenAPIURLSession
447+
443448
struct ClaudeAPIClient {
444449
let apiKey: String
445450
let model: String
451+
private let client: Client // Generated from OpenAPI spec
452+
453+
init(apiKey: String, model: String) {
454+
self.apiKey = apiKey
455+
self.model = model
456+
457+
// Create OpenAPI client with URLSession transport
458+
self.client = Client(
459+
serverURL: try! Servers.server1(), // https://api.anthropic.com
460+
transport: URLSessionTransport(),
461+
middlewares: [
462+
AuthenticationMiddleware(apiKey: apiKey)
463+
]
464+
)
465+
}
446466

447467
/// Sends request to generate updated library code
448468
func generateUpdatedLibrary(
449469
syntaxKitLibrary: String,
450470
expectedSwift: String,
451471
swiftAST: String,
452472
swiftDSL: String
453-
) async throws -> LibraryUpdateResult
473+
) async throws -> LibraryUpdateResult {
474+
// Create prompt using template
475+
let prompt = PromptTemplate.createAnalysisAndCodeGeneration(
476+
syntaxKitLibrary: syntaxKitLibrary,
477+
expectedSwift: expectedSwift,
478+
swiftAST: swiftAST,
479+
swiftDSL: swiftDSL
480+
)
454481

455-
/// Formats the API request payload with code generation instructions
456-
private func createRequestPayload(...) -> [String: Any]
482+
// Call Claude API using generated client
483+
let response = try await client.postV1Messages(
484+
body: .json(
485+
Components.Schemas.MessageRequest(
486+
model: model,
487+
max_tokens: 20000,
488+
temperature: 1.0,
489+
messages: [
490+
Components.Schemas.Message(
491+
role: .user,
492+
content: prompt
493+
)
494+
]
495+
)
496+
)
497+
)
498+
499+
// Extract response content
500+
let messageResponse = try response.ok.body.json
501+
let responseText = messageResponse.content
502+
.compactMap { content -> String? in
503+
if case .text(let text) = content {
504+
return text.text
505+
}
506+
return nil
507+
}
508+
.joined()
509+
510+
// Parse code generation response
511+
return try parseCodeGenerationResponse(responseText)
512+
}
457513

458514
/// Parses API response containing generated code
459-
private func parseCodeGenerationResponse(_ data: Data) throws -> LibraryUpdateResult
515+
private func parseCodeGenerationResponse(_ text: String) throws -> LibraryUpdateResult {
516+
// Extract <file> blocks using regex
517+
let filePattern = #"<file path="([^"]+)">(.+?)</file>"#
518+
let regex = try NSRegularExpression(pattern: filePattern, options: [.dotMatchesLineSeparators])
519+
let nsText = text as NSString
520+
let matches = regex.matches(in: text, range: NSRange(location: 0, length: nsText.length))
521+
522+
var updatedFiles: [LibraryUpdateResult.UpdatedFile] = []
523+
var newFiles: [LibraryUpdateResult.NewFile] = []
524+
525+
for match in matches {
526+
let pathRange = match.range(at: 1)
527+
let contentRange = match.range(at: 2)
528+
529+
let relativePath = nsText.substring(with: pathRange)
530+
let content = nsText.substring(with: contentRange)
531+
.trimmingCharacters(in: .whitespacesAndNewlines)
532+
533+
// Determine if new or updated file based on library scan
534+
// For now, treat all as new files (can enhance later)
535+
newFiles.append(
536+
LibraryUpdateResult.NewFile(
537+
relativePath: relativePath,
538+
content: content,
539+
purpose: "Generated implementation"
540+
)
541+
)
542+
}
543+
544+
// Extract summary (text before first <file> tag)
545+
let summaryPattern = #"^(.+?)(?=<file|$)"#
546+
let summaryRegex = try NSRegularExpression(pattern: summaryPattern, options: [.dotMatchesLineSeparators])
547+
let summaryMatch = summaryRegex.firstMatch(in: text, range: NSRange(location: 0, length: nsText.length))
548+
let summary = summaryMatch.map { nsText.substring(with: $0.range(at: 1)) } ?? "No summary provided"
549+
550+
return LibraryUpdateResult(
551+
updatedFiles: updatedFiles,
552+
newFiles: newFiles,
553+
unchangedFiles: [],
554+
includeUnchangedFiles: false,
555+
summary: summary.trimmingCharacters(in: .whitespacesAndNewlines)
556+
)
557+
}
558+
}
559+
560+
/// Custom middleware for adding Anthropic API key header
561+
struct AuthenticationMiddleware: ClientMiddleware {
562+
let apiKey: String
563+
564+
func intercept(
565+
_ request: HTTPRequest,
566+
baseURL: URL,
567+
operationID: String,
568+
next: (HTTPRequest, URL) async throws -> HTTPResponse
569+
) async throws -> HTTPResponse {
570+
var modifiedRequest = request
571+
modifiedRequest.headerFields.append(
572+
.init(name: "x-api-key", value: apiKey)
573+
)
574+
modifiedRequest.headerFields.append(
575+
.init(name: "anthropic-version", value: "2023-06-01")
576+
)
577+
return try await next(modifiedRequest, baseURL)
578+
}
460579
}
461580

462581
struct LibraryUpdateResult: Codable {
@@ -485,20 +604,31 @@ struct FileReference: Codable {
485604
}
486605
```
487606

488-
API request structure:
489-
- Endpoint: `https://api.anthropic.com/v1/messages`
490-
- Headers: `x-api-key`, `anthropic-version: 2023-06-01`, `content-type: application/json`
491-
- Body: Enhanced prompt asking Claude to generate actual Swift code, not just describe changes
607+
**OpenAPI Specification Source**:
608+
- Uses unofficial OpenAPI spec from [laszukdawid/anthropic-openapi-spec](https://github.com/laszukdawid/anthropic-openapi-spec)
609+
- Specifically the `hosted_spec.json` file (derived from Anthropic's TypeScript SDK)
610+
- Download to `Sources/skit-analyze/openapi.json` or `openapi.yaml`
611+
- Swift OpenAPI Generator will create type-safe client code at build time
612+
613+
**Setup Steps**:
614+
1. Download OpenAPI spec: `curl -o Sources/skit-analyze/openapi.json https://raw.githubusercontent.com/laszukdawid/anthropic-openapi-spec/main/hosted_spec.json`
615+
2. Create `Sources/skit-analyze/openapi-generator-config.yaml`:
616+
```yaml
617+
generate:
618+
- types
619+
- client
620+
```
621+
3. Swift OpenAPI Generator plugin will generate client code automatically during build
492622
493-
Modified Prompt Strategy:
494-
- Still uses the Workbench prompt for analysis
623+
**Modified Prompt Strategy**:
624+
- Uses the Workbench prompt for analysis
495625
- **Additionally** asks Claude to generate the actual implementation code
496626
- Requests output in structured format using XML-style markers
497627
- Each code file marked with: `<file path="relative/path.swift">...code...</file>`
498628

499629
**Response Parsing**:
500-
The API client will parse Claude's response by:
501-
1. Extract all `<file>` blocks using regex or XML parsing
630+
The API client parses Claude's response by:
631+
1. Extract all `<file>` blocks using regex
502632
2. For each file block:
503633
- Extract `path` attribute (relative path like "Declarations/Subscript.swift")
504634
- Extract content between tags (complete Swift code)
@@ -598,15 +728,27 @@ User runs: skit-analyze examples/subscript-feature Sources/SyntaxKit output/Synt
598728
599729
### 4. Package.swift Changes
600730
601-
Add ConfigKeyKit as a local target and integrate swift-configuration:
731+
Add ConfigKeyKit as a local target, integrate swift-configuration, and add OpenAPI Generator:
602732
603733
```swift
604-
// In dependencies (add swift-configuration):
734+
// In dependencies:
605735
.package(
606736
url: "https://github.com/apple/swift-configuration",
607737
from: "1.0.0",
608738
traits: ["CommandLineArguments"] // Enable CLI args trait
609739
),
740+
.package(
741+
url: "https://github.com/apple/swift-openapi-generator",
742+
from: "1.0.0"
743+
),
744+
.package(
745+
url: "https://github.com/apple/swift-openapi-runtime",
746+
from: "1.0.0"
747+
),
748+
.package(
749+
url: "https://github.com/apple/swift-openapi-urlsession",
750+
from: "1.0.0"
751+
),
610752
611753
// Add ConfigKeyKit as a target:
612754
.target(
@@ -617,13 +759,18 @@ Add ConfigKeyKit as a local target and integrate swift-configuration:
617759
swiftSettings: swiftSettings
618760
),
619761
620-
// Add new executable target:
762+
// Add new executable target with OpenAPI Generator plugin:
621763
.executableTarget(
622764
name: "skit-analyze",
623765
dependencies: [
624766
"SyntaxParser",
625767
"ConfigKeyKit",
626-
.product(name: "Configuration", package: "swift-configuration")
768+
.product(name: "Configuration", package: "swift-configuration"),
769+
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
770+
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession")
771+
],
772+
plugins: [
773+
.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
627774
],
628775
swiftSettings: swiftSettings
629776
),
@@ -635,7 +782,27 @@ Add ConfigKeyKit as a local target and integrate swift-configuration:
635782
),
636783
```
637784

638-
**Note**: ConfigKeyKit already exists in the project directory, so we just need to add it as a target in Package.swift.
785+
**Setup Requirements**:
786+
1. Download OpenAPI spec to `Sources/skit-analyze/`:
787+
```bash
788+
curl -o Sources/skit-analyze/openapi.json \
789+
https://raw.githubusercontent.com/laszukdawid/anthropic-openapi-spec/main/hosted_spec.json
790+
```
791+
792+
2. Create `Sources/skit-analyze/openapi-generator-config.yaml`:
793+
```yaml
794+
generate:
795+
- types
796+
- client
797+
accessModifier: internal
798+
```
799+
800+
3. The OpenAPI Generator plugin will automatically generate type-safe client code during build
801+
802+
**Notes**:
803+
- ConfigKeyKit already exists in the project directory, so we just need to add it as a target in Package.swift
804+
- OpenAPI Generator runs as a build plugin and generates Swift code from the OpenAPI spec at build time
805+
- Generated code includes type-safe request/response models and client methods
639806
640807
### 5. Configuration & Environment
641808
@@ -729,6 +896,7 @@ Include full API request/response, intermediate parsing steps, file collection d
729896
730897
## Critical Files to Create
731898
899+
### Source Files
732900
1. **Sources/skit-analyze/main.swift** - Main entry point
733901
2. **Sources/skit-analyze/AnalyzeCommand.swift** - Command implementation using ConfigKeyKit
734902
3. **Sources/skit-analyze/AnalyzerConfiguration.swift** - Configuration structure using ConfigKeyKit
@@ -737,10 +905,20 @@ Include full API request/response, intermediate parsing steps, file collection d
737905
6. **Sources/skit-analyze/LibraryCollector.swift** - Collects SyntaxKit source files
738906
7. **Sources/skit-analyze/LibraryWriter.swift** - Writes updated library to output folder
739907
8. **Sources/skit-analyze/ASTGenerator.swift** - Wraps SyntaxParser for AST generation
740-
9. **Sources/skit-analyze/ClaudeAPIClient.swift** - Claude API communication for code generation
908+
9. **Sources/skit-analyze/ClaudeAPIClient.swift** - Wraps OpenAPI-generated client for code generation
741909
10. **Sources/skit-analyze/PromptTemplate.swift** - Enhanced Workbench prompt with code generation
742910
11. **Sources/skit-analyze/Models.swift** - Data models (LibraryUpdateResult, UpdatedFile, NewFile, AnalyzerError)
743-
12. **Package.swift** (modify) - Add ConfigKeyKit target, swift-configuration dependency, and executable target
911+
912+
### Configuration Files
913+
12. **Sources/skit-analyze/openapi.json** - Anthropic OpenAPI specification (downloaded)
914+
13. **Sources/skit-analyze/openapi-generator-config.yaml** - OpenAPI Generator configuration
915+
14. **Package.swift** (modify) - Add ConfigKeyKit target, dependencies, and OpenAPI plugin
916+
917+
### Test Mode Files (Section 8)
918+
15. **Sources/skit-analyze/Testing/TestRunner.swift** - Orchestrates test execution
919+
16. **Sources/skit-analyze/Testing/TestCaseDiscoverer.swift** - Discovers and loads test cases
920+
17. **Sources/skit-analyze/Testing/TestValidator.swift** - Validates results against expectations
921+
18. **Sources/skit-analyze/Testing/TestModels.swift** - Test data structures
744922
745923
## Verification Steps
746924
@@ -949,19 +1127,32 @@ skit-analyze --test --test-cases=custom-tests/
9491127
9501128
**New Dependencies**:
9511129
- `swift-configuration` (1.0.0+) with `CommandLineArguments` trait - Configuration management
1130+
- `swift-openapi-generator` (1.0.0+) - Generates type-safe API client from OpenAPI spec
1131+
- `swift-openapi-runtime` (1.0.0+) - Runtime support for generated OpenAPI client
1132+
- `swift-openapi-urlsession` (1.0.0+) - URLSession transport for OpenAPI client
9521133
9531134
**Existing Dependencies** (reused):
9541135
- `ConfigKeyKit` (in project) - Configuration key abstraction
9551136
- `SwiftSyntax` (601.0.1+) - via SyntaxParser
9561137
- `SyntaxParser` (existing module) - AST generation
9571138
- Foundation - HTTP requests, file I/O
9581139
959-
**Advantages of swift-configuration + ConfigKeyKit**:
960-
- Unified handling of CLI args and environment variables
961-
- Type-safe configuration keys with automatic naming transformations
962-
- Composable provider hierarchy (CLI overrides ENV)
963-
- Consistent with project's existing ConfigKeyKit architecture
964-
- More flexible than ArgumentParser for complex configuration scenarios
1140+
**External Resources**:
1141+
- [Unofficial Anthropic OpenAPI Spec](https://github.com/laszukdawid/anthropic-openapi-spec) - `hosted_spec.json` derived from Anthropic's TypeScript SDK
1142+
1143+
**Advantages of This Approach**:
1144+
- **swift-configuration + ConfigKeyKit**:
1145+
- Unified handling of CLI args and environment variables
1146+
- Type-safe configuration keys with automatic naming transformations
1147+
- Composable provider hierarchy (CLI overrides ENV)
1148+
- Consistent with project's existing ConfigKeyKit architecture
1149+
1150+
- **Swift OpenAPI Generator**:
1151+
- Type-safe API client generated at build time from OpenAPI spec
1152+
- Automatic request/response serialization
1153+
- Built-in error handling and validation
1154+
- No manual JSON parsing or HTTP request construction
1155+
- Easy to update when Anthropic publishes official OpenAPI spec (just replace the spec file)
9651156
9661157
## Build & Install
9671158

0 commit comments

Comments
 (0)