Skip to content

api: support local Codex CLI and Claude#95

Open
khaledmhirsi wants to merge 8 commits into
farzaa:mainfrom
khaledmhirsi:feature/codex-compatibility
Open

api: support local Codex CLI and Claude#95
khaledmhirsi wants to merge 8 commits into
farzaa:mainfrom
khaledmhirsi:feature/codex-compatibility

Conversation

@khaledmhirsi
Copy link
Copy Markdown

@khaledmhirsi khaledmhirsi commented May 22, 2026

Summary

  • Add a provider picker so users can choose Codex or Claude from the Clicky panel.
  • Run Codex through the user's local Codex CLI session with codex exec, attached screenshots, and the selected Codex model. No OpenAI API key or Worker Codex endpoint is required.
  • Update the Codex model menu to the current Codex CLI docs list: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark, and gpt-5.2, with gpt-5.5 as the default.
  • Keep Claude on the Worker-backed Anthropic Messages route and remove the Worker OpenAI/Codex proxy path.
  • Update README and agent docs so setup says codex login for Codex and ANTHROPIC_API_KEY only for Claude chat.

Validation

  • swiftc -parse leanring-buddy/*.swift
  • npx --yes --package typescript tsc --noEmit --target ES2022 --module ESNext worker/src/index.ts
  • git diff --check
  • codex exec --help checked for supported CLI flags

@khaledmhirsi khaledmhirsi marked this pull request as ready for review May 22, 2026 19:44
@khaledmhirsi khaledmhirsi changed the title docs: add Codex setup guidance api: power Clicky with Codex May 22, 2026
@khaledmhirsi khaledmhirsi changed the title api: power Clicky with Codex api: support Codex and Claude chat providers May 22, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c1e6ab8b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread leanring-buddy/CodexAPI.swift Outdated
Comment on lines +157 to +167
if eventType == "response.output_text.delta",
let textChunk = eventPayload["delta"] as? String {
accumulatedResponseText += textChunk
let currentAccumulatedText = accumulatedResponseText
await onTextChunk(currentAccumulatedText)
} else if eventType == "response.output_text.done",
accumulatedResponseText.isEmpty,
let completedText = eventPayload["text"] as? String {
accumulatedResponseText = completedText
await onTextChunk(completedText)
} else if eventType == "error" {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse refusal events from the Codex SSE stream

The streaming parser only consumes response.output_text.* events and ignores response.refusal.*, so safety refusals from the Responses API produce an empty accumulatedResponseText instead of a spoken/user-visible explanation. In practice this makes blocked prompts look like silent failures (no TTS text), which is a regression in runtime behavior for moderated inputs.

Useful? React with 👍 / 👎.

Comment thread leanring-buddy/CodexAPI.swift Outdated
Comment on lines +167 to +174
} else if eventType == "error" {
let message = Self.extractErrorMessage(from: eventPayload)
throw NSError(
domain: "CodexAPI",
code: -1,
userInfo: [NSLocalizedDescriptionKey: message]
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat response.failed SSE events as errors

The Responses API can send a response.failed lifecycle event on a 200 streaming connection, but this parser only throws on event type error and otherwise falls through. When that happens, the method returns an empty string as if it succeeded, and the caller skips both TTS and fallback error speech, creating a silent no-response path for users.

Useful? React with 👍 / 👎.

Comment on lines +159 to +163
private static func persistedAIProvider() -> CompanionAIProvider {
guard let storedProvider = UserDefaults.standard.string(forKey: "selectedAIProvider"),
let provider = CompanionAIProvider(rawValue: storedProvider) else {
return defaultAIProvider
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Claude preference when selectedAIProvider is unset

On upgrade, users coming from the previous Claude-only flow have no selectedAIProvider key, so this fallback forces .codex and ignores their existing Claude setup. In environments where only ANTHROPIC_API_KEY is configured (the prior default), first-run requests now fail against /chat/codex until the user manually changes providers, which is a user-facing regression introduced by the new default.

Useful? React with 👍 / 👎.

@khaledmhirsi khaledmhirsi changed the title api: support Codex and Claude chat providers api: support local Codex CLI and Claude May 22, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9154d36282

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +99 to +102
process.waitUntilExit()

let standardOutput = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let standardError = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Drain subprocess output before waiting for exit

This waits for codex to exit before reading either pipe, which can deadlock if the CLI writes enough stdout/stderr to fill the OS pipe buffer (common with verbose/error output). In that case the child blocks on write, never exits, and the app can stay stuck in the processing state indefinitely.

Useful? React with 👍 / 👎.

userPrompt: String
) async throws -> String {
let selectedModel = model
return try await Task.detached(priority: .userInitiated) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cancel detached Codex runs when interaction is canceled

The CLI execution is launched in an unstructured Task.detached, so canceling the parent response task (for example when the user starts a new push-to-talk turn) doesn't stop the in-flight Codex process. This can leave orphaned codex exec runs consuming CPU/network in the background and piling up across rapid interruptions.

Useful? React with 👍 / 👎.

Comment on lines +131 to +137
let candidatePaths = [
"/opt/homebrew/bin/codex",
"/usr/local/bin/codex",
"/usr/bin/codex"
]

for candidatePath in candidatePaths where FileManager.default.isExecutableFile(atPath: candidatePath) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve Codex binary from PATH instead of fixed paths

Executable discovery only checks three hard-coded locations, so installs in common custom prefixes (for example npm/nvm global bins) are treated as missing even when codex is correctly on the user's PATH. That makes the Codex provider fail to start on otherwise valid setups.

Useful? React with 👍 / 👎.

Comment on lines +75 to +76
"--sandbox", "read-only",
"--cd", temporaryDirectory.path,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Constrain Codex sandbox to prevent arbitrary file reads

Running codex exec with --sandbox read-only still allows shell execution and read access to files outside the temp screenshot directory, so prompt injection from on-screen content can coerce the model to read sensitive local files (for example ~/.ssh or browser data) and echo them back in the assistant response. This is a new privacy/security exposure compared with the previous server-only provider path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ad2d5a53c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


private lazy var claudeAPI: ClaudeAPI = {
return ClaudeAPI(proxyURL: "\(Self.workerBaseURL)/chat", model: selectedModel)
return ClaudeAPI(proxyURL: "\(Self.workerBaseURL)/chat/claude", model: selectedModel)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Claude API path backward-compatible

Pointing ClaudeAPI at /chat/claude makes the app depend on a newly added Worker route, so deployments where the app updates before the Worker redeploy (older Worker only exposing /chat) will get 404s for every Claude request. Using the existing /chat path from the app side would keep Claude functional across staggered rollouts while still working with the updated Worker.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant