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
12 changes: 10 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,16 @@ Configuration follows one precedence rule:

`api_key` resolves from `COMETAPI_KEY` and has no default. `base_url` resolves
from `COMETAPI_BASE_URL` and defaults to
`https://api.cometapi.com/v1`. Complete credentials must never appear in
CometAPI-generated exceptions or logs.
`https://api.cometapi.com/v1`. Direct and environment string values are
trimmed. An explicitly blank value is rejected without fallback; a blank
environment key is missing, while a blank environment base URL selects the
default. Callable keys and `httpx.URL` objects pass through unchanged. Complete
credentials must never appear in CometAPI-generated exceptions or logs.

The inherited OpenAI `copy` and `with_options` helpers are outside the 0.1
support contract. They must remain fail-closed for provider routing, workload
identity, private credential controls, and injected keyword mappings rather
than weakening the explicit CometAPI constructor boundary.

## Supported resource boundary

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ automation.

### Fixed

- Reject explicitly blank API keys and base URLs without fallback, treat blank
environment keys as missing, and use the default CometAPI URL for a blank
`COMETAPI_BASE_URL`.
- Add a fail-closed immutable-release recovery path with an environment-secret
preflight before any live request.
- Execute PyPI Trusted Publishing directly in the single top-level
Expand All @@ -17,6 +20,13 @@ automation.
release chain after a successful selector, while continuing to reject
cancellation, reruns, and every non-successful direct dependency.

### Changed

- Trim leading and trailing whitespace from direct and environment string API
keys and base URLs. Applications that intentionally supplied surrounding
whitespace must pass the intended credential or URL without that padding;
callable keys and `httpx.URL` objects are unchanged.

### Documentation

- Record completed stable publication, provenance, digest, and clean-install
Expand Down
6 changes: 4 additions & 2 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ remains upstream-supported.

| Behavior | Contract |
| --- | --- |
| API key | Explicit `api_key`, then `COMETAPI_KEY`; required |
| Base URL | Explicit `base_url`, then `COMETAPI_BASE_URL`, then `https://api.cometapi.com/v1` |
| API key | Trim explicit/environment strings; explicit blank is invalid, blank `COMETAPI_KEY` is missing; required |
| Base URL | Trim explicit/environment strings; explicit blank is invalid, blank `COMETAPI_BASE_URL` uses `https://api.cometapi.com/v1` |
| Callable key and `httpx.URL` | Preserve official deferred-key and URL-object behavior unchanged |
| OpenAI options | Documented constructor options forwarded unchanged |
| Inherited copy helpers | Unsupported and fail-closed for provider, workload-identity, and private-option injection |
| Complete-key disclosure | Forbidden in CometAPI-generated errors and logs |

Applications can configure `openai.OpenAI` or `openai.AsyncOpenAI` directly
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ variable, then the default:
| `api_key` | `COMETAPI_KEY` | Required |
| `base_url` | `COMETAPI_BASE_URL` | `https://api.cometapi.com/v1` |

Leading and trailing whitespace is removed from string API keys and base URLs.
An explicitly blank string is invalid and never falls back to the environment
or default. A blank `COMETAPI_KEY` is treated as missing, while a blank
`COMETAPI_BASE_URL` uses the default CometAPI URL. Callable API keys and
`httpx.URL` values retain their official OpenAI behavior.

## Usage

### Chat Completions
Expand Down Expand Up @@ -138,7 +144,9 @@ The constructor exposes a named, typed option set: `api_key`, `admin_api_key`,
that documented custom HTTP client path. OpenAI provider routing and workload
identity are deliberately excluded because they replace CometAPI routing or
authentication; private underscore-prefixed OpenAI controls and arbitrary
keywords are rejected.
keywords are rejected. Inherited `copy` and `with_options` helpers are not part
of the supported 0.1 surface and cannot be used to introduce those excluded
routing or authentication options.

## Direct OpenAI interoperability

Expand Down
13 changes: 13 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ claims remain limited to the evidence defined in this roadmap and
| `0.3.0` CometAPI resources | Planned | First schema-backed typed CometAPI-specific resource. |
| Media and task APIs | Planned | Coherent task lifecycle precedes individual media helpers. |

## `0.1.x` maintenance

Stable maintenance preserves the 0.1 public operation and constructor surface.
String API keys and base URLs are trimmed at their direct or environment
boundary. Explicit blank values fail without fallback; a blank environment key
is missing, while a blank environment base URL selects the default CometAPI
URL. Callable keys and `httpx.URL` values keep their official OpenAI semantics.
Inherited `copy` and `with_options` helpers remain unsupported and fail-closed
for provider routing, workload identity, and private-option injection.

No maintenance release activates 0.2 provider adapters or adds a new resource,
CLI, translation, or Go surface without a separate maintainer request.

## Repository foundation

Deliverables:
Expand Down
16 changes: 11 additions & 5 deletions scripts/check_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
RELEASE_PLEASE_LOCK_JSONPATH = "$.package[?(@.name.value == 'cometapi')].version"
RELEASE_PLEASE_ACTION_SHA = "5c625bfb5d1ff62eadeeb3772007f7f66fdcf071"
RELEASE_PLEASE_BRIDGE_VERSION = "0.1.0-alpha.1"
RELEASE_PLEASE_STABLE_VERSION = "0.1.0"
RELEASE_PLEASE_STABLE_VERSION_PATTERN = re.compile(r"0\.1\.(?:0|[1-9][0-9]*)")
RELEASE_PLEASE_VERIFY_COMMAND = """\
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
Expand Down Expand Up @@ -953,7 +953,7 @@ def check_release_recovery_workflow(text: str) -> None:


def check_release_please_config(text: str, manifest_text: str) -> None:
"""Require either the reviewed bridge or its exact stable cleanup state."""
"""Require either the reviewed bridge or a stable 0.1.x cleanup state."""
try:
value = cast(object, json.loads(text))
except json.JSONDecodeError as error:
Expand All @@ -966,8 +966,14 @@ def check_release_please_config(text: str, manifest_text: str) -> None:
manifest = _mapping(manifest_value, "Release Please manifest")
_require_exact_keys(manifest, {"."}, "Release Please manifest")
version = manifest["."]
if version not in {RELEASE_PLEASE_BRIDGE_VERSION, RELEASE_PLEASE_STABLE_VERSION}:
raise CheckError("Release Please manifest must be the reviewed bridge or stable version")
stable_version = (
isinstance(version, str)
and RELEASE_PLEASE_STABLE_VERSION_PATTERN.fullmatch(version) is not None
)
if version != RELEASE_PLEASE_BRIDGE_VERSION and not stable_version:
raise CheckError(
"Release Please manifest must be the reviewed bridge or a stable 0.1.x version"
)

common_keys = {
"$schema",
Expand Down Expand Up @@ -996,7 +1002,7 @@ def check_release_please_config(text: str, manifest_text: str) -> None:
)
else:
_require_exact_keys(config, common_keys, "Release Please stable config")
if version != RELEASE_PLEASE_STABLE_VERSION:
if not stable_version:
raise CheckError("Release Please may remove the one-time bridge only after stable")
if config["release-type"] != "python":
raise CheckError("Release Please must retain its Python release type")
Expand Down
30 changes: 27 additions & 3 deletions src/cometapi/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,43 @@
_T = TypeVar("_T")


def _strip_configuration_whitespace(value: str) -> str:
start = 0
end = len(value)
while start < end and (value[start].isspace() or value[start] == "\ufeff"):
start += 1
while end > start and (value[end - 1].isspace() or value[end - 1] == "\ufeff"):
end -= 1
return value[start:end]


def resolve_api_key(api_key: _T | None) -> _T | str:
"""Resolve an explicit API key before consulting ``COMETAPI_KEY``."""
if api_key is not None and not isinstance(api_key, str):
return api_key

resolved = api_key if api_key is not None else os.environ.get("COMETAPI_KEY")
if resolved is None or (isinstance(resolved, str) and not resolved):
normalized = _strip_configuration_whitespace(resolved) if resolved is not None else ""
if not normalized:
raise OpenAIError(
"The CometAPI API key must be provided with the api_key client option "
"or the COMETAPI_KEY environment variable."
)
return resolved
return normalized


def resolve_base_url(base_url: _T | None) -> _T | str:
"""Resolve an explicit base URL before environment and default values."""
if base_url is not None:
if isinstance(base_url, str):
normalized = _strip_configuration_whitespace(base_url)
if not normalized:
raise OpenAIError("The CometAPI base_url client option must not be empty.")
return normalized
return base_url
return os.environ.get("COMETAPI_BASE_URL") or DEFAULT_BASE_URL

environment_url = os.environ.get("COMETAPI_BASE_URL")
normalized = (
_strip_configuration_whitespace(environment_url) if environment_url is not None else ""
)
return normalized or DEFAULT_BASE_URL
Loading