Skip to content

feat: add Python SDK, OpenAPI spec, and build tooling#420

Closed
KooshaPari wants to merge 1 commit intomainfrom
feat/sdk-openapi-cherry-pick
Closed

feat: add Python SDK, OpenAPI spec, and build tooling#420
KooshaPari wants to merge 1 commit intomainfrom
feat/sdk-openapi-cherry-pick

Conversation

@KooshaPari
Copy link
Copy Markdown
Owner

Cherry-picked valuable additions from #409 (fix/test-cleanups) before closing the superseded PR.

Changes

  • .gitignore — add build artifacts (cliproxyapi++, .air/, logs/)
  • sdk/python/cliproxy/api.py — comprehensive Python SDK with native classes
  • api/openapi.yaml — OpenAPI spec for core endpoints
  • .github/workflows/generate-sdks.yaml — Python/TypeScript SDK generation workflow

Context

PR #409 could not be merged due to unresolvable history divergence (was built on clean-main before rename). This PR captures only the new additions and applies them cleanly on top of current main.

Closes #409

…cleanups

- Add api/openapi.yaml — OpenAPI spec for core endpoints
- Add .github/workflows/generate-sdks.yaml — Python/TypeScript SDK generation
- Add sdk/python/cliproxy/api.py — comprehensive Python SDK with native classes
- Update .gitignore — add build artifacts (cliproxyapi++, .air/, logs/)

Cherry-picked from fix/test-cleanups (commits a4e4c2b, ad78f86, 05242f0)
before closing superseded PR #409.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @KooshaPari, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request aims to integrate valuable additions from a previously unmergeable PR, focusing on enhancing the developer experience. It primarily introduces a comprehensive Python SDK, providing native Python classes for seamless API interaction. Additionally, it updates the .gitignore file to properly manage build artifacts. While the PR description also mentions the addition of an OpenAPI specification and an SDK generation workflow, these specific file changes are not included in the provided patch content.

Highlights

  • Python SDK: Introduced a comprehensive Python SDK for cliproxyapi-plusplus, featuring native Python classes, enums, and functions for interacting with the API, moving beyond simple HTTP wrappers.
  • OpenAPI Specification (Intended): The pull request description indicates the addition of an openapi.yaml file for core API endpoints, though this specific file change is not present in the provided patch content.
  • Build Tooling & Workflow (Intended): The pull request description indicates the implementation of a GitHub Actions workflow (generate-sdks.yaml) for SDK generation, though this specific file change is not present in the provided patch content.
  • Gitignore Updates: Updated the .gitignore file to include various build artifacts and temporary files, improving repository cleanliness and reducing unnecessary commits.
Changelog
  • .gitignore
    • Added entries for cliproxyapi++, .air/, boardsync, releasebatch, .cache, and logs/ to ignore build artifacts.
  • sdk/python/cliproxy/api.py
    • Added a new Python file defining a comprehensive SDK.
    • Included ModelProvider enum for supported AI model providers.
    • Defined dataclasses for ProviderConfig, AuthEntry, ChatMessage, ChatChoice, Usage, ChatCompletion, Model, and ModelList.
    • Implemented CliproxyClient with high-level chat and complete methods, mid-level operations for providers, auth, and config, and low-level HTTP request handling.
    • Added convenience functions client and chat for easy SDK initialization and usage.
Activity
  • No human activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a Python SDK, an OpenAPI specification, and updates to build tooling. The Python SDK is a valuable addition, but it has some areas for improvement regarding type safety, security, and maintainability. A critical issue was found in .gitignore where merge conflict markers have been included, which must be resolved before merging.

Comment thread .gitignore
Comment on lines +57 to +73
<<<<<<< HEAD
=======
server
cli-proxy-api-plus-integration-test

boardsync
releasebatch
.cache
>>>>>>> a4e4c2b8 (chore: add build artifacts to .gitignore)

# Build artifacts (cherry-picked from fix/test-cleanups)
cliproxyapi++
.air/
boardsync
releasebatch
.cache
logs/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This change introduces merge conflict markers which should be resolved. It also adds duplicate entries. Please resolve the conflicts and remove duplicates.


# Build artifacts (cherry-picked from fix/test-cleanups)
cli-proxy-api-plus-integration-test
cliproxyapi++
.air/
boardsync
releasebatch
.cache
logs/

timeout: int = 30,
):
self.base_url = base_url.rstrip("/")
self.api_key = api_key or os.getenv("CLIPROXY_API_KEY", "8317")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using a hardcoded default value of "8317" for the API key is a security risk. This value appears to be the default port, which could be confusing and lead to insecure deployments if a user forgets to set a proper key. It's better to default to None if the environment variable is not set. The _request method should then be updated to only add the Authorization header if an API key is present.

Suggested change
self.api_key = api_key or os.getenv("CLIPROXY_API_KEY", "8317")
self.api_key = api_key or os.getenv("CLIPROXY_API_KEY")

Comment on lines +229 to +230
headers = {"Authorization": f"Bearer {self.api_key}"}
headers.update(kwargs.pop("headers", {}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

To handle cases where self.api_key might be None, the Authorization header should only be added if a key is present.

Suggested change
headers = {"Authorization": f"Bearer {self.api_key}"}
headers.update(kwargs.pop("headers", {}))
headers = {}
if self.api_key:
headers["Authorization"] = f"Bearer {self.api_key}"
headers.update(kwargs.pop("headers", {}))

"""

import httpx
from dataclasses import dataclass, field
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To use asdict for dataclass serialization, it needs to be imported from the dataclasses module.

Suggested change
from dataclasses import dataclass, field
from dataclasses import dataclass, field, asdict

from dataclasses import dataclass, field
from typing import Any, Optional
from enum import Enum
import os
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To avoid hardcoding the default model name in multiple places, it's good practice to define it as a constant.

Suggested change
import os
import os
DEFAULT_MODEL = "claude-3-5-sonnet-20241022"

class ChatChoice:
"""Single chat choice."""
index: int
message: dict
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The message field is typed as a dict. For better type safety and consistency, it should be typed as ChatMessage. This requires updating the parsing logic in _parse_completion.

Suggested change
message: dict
message: ChatMessage

Comment on lines +92 to +95
def first_choice(self) -> str:
if self.choices and self.choices[0].message:
return self.choices[0].message.get("content", "")
return ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

With message being a ChatMessage object, you can now access its content attribute directly for cleaner code.

Suggested change
def first_choice(self) -> str:
if self.choices and self.choices[0].message:
return self.choices[0].message.get("content", "")
return ""
def first_choice(self) -> str:
if self.choices and self.choices[0].message:
return self.choices[0].message.content or ""
return ""

def chat(
self,
messages: list[ChatMessage],
model: str = "claude-3-5-sonnet-20241022",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Use the DEFAULT_MODEL constant here and in other places where this model name is hardcoded (lines 164, 273) to improve maintainability.

Suggested change
model: str = "claude-3-5-sonnet-20241022",
model: str = DEFAULT_MODEL,


def auth_add(self, auth: AuthEntry) -> dict:
"""Add auth entry - native Python."""
return self.management_request("POST", "/v0/management/auth", json=auth.__dict__)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using __dict__ to serialize a dataclass is not recommended. The dataclasses.asdict() function should be used instead, as it's the official and safer way to convert a dataclass instance to a dictionary, and it correctly handles nested dataclasses.

Suggested change
return self.management_request("POST", "/v0/management/auth", json=auth.__dict__)
return self.management_request("POST", "/v0/management/auth", json=asdict(auth))


def _parse_completion(self, resp: dict) -> ChatCompletion:
"""Parse completion response to Python object."""
choices = [ChatChoice(**c) for c in resp.get("choices", [])]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To support ChatChoice.message being a ChatMessage object, the parsing logic needs to be updated to explicitly construct ChatMessage objects from the response data.

Suggested change
choices = [ChatChoice(**c) for c in resp.get("choices", [])]
choices = [
ChatChoice(
index=c.get("index", 0),
message=ChatMessage(**c.get("message", {})),
finish_reason=c.get("finish_reason")
) for c in resp.get("choices", [])
]

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 25, 2026

Warning

Rate limit exceeded

@KooshaPari has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 57 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 23c05ea and 45bb73d.

📒 Files selected for processing (2)
  • .gitignore
  • sdk/python/cliproxy/api.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/sdk-openapi-cherry-pick

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@KooshaPari
Copy link
Copy Markdown
Owner Author

Superseded by layered CI-fix PR #509.

@KooshaPari KooshaPari closed this Feb 26, 2026
@KooshaPari KooshaPari deleted the feat/sdk-openapi-cherry-pick branch February 26, 2026 10:40
KooshaPari pushed a commit that referenced this pull request Mar 28, 2026
Fixed: preserve Responses computer tool passthrough
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