Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
df170ed
feat(core): add circuit_breaker, retry, and constants
gabrielvfonseca Oct 11, 2026
b8c9b61
feat(core): enhance error_for_status() with fix messages and error codes
gabrielvfonseca Oct 11, 2026
6eb64fa
fix(sdk): restrict safe_to_retry to idempotent methods only
gabrielvfonseca Oct 11, 2026
6b848ab
feat(sdk): add lowercase namespace aliases for API resources
gabrielvfonseca Oct 11, 2026
53ab3a0
feat(sdk): add shared utilities package
gabrielvfonseca Oct 11, 2026
847275b
feat(sdk): add module entry point for python -m frontal_sdk
gabrielvfonseca Oct 11, 2026
d0f1d23
refactor(sdk): re-export circuit breaker helpers from core
gabrielvfonseca Oct 11, 2026
6e93ff3
refactor(core): clean up error construction and sort exports
gabrielvfonseca Oct 11, 2026
b06b801
refactor(sdk): drop legacy API key env var from module entry point
gabrielvfonseca Oct 11, 2026
3d91fd9
docs(sdk): document public builder and accessor methods
gabrielvfonseca Oct 11, 2026
968be48
feat(repo): add streaming and error-handling example scripts
gabrielvfonseca Oct 11, 2026
10c0ade
test(sdk): cover streaming and error-handling examples
gabrielvfonseca Oct 11, 2026
0f27198
docs(sdk): add getting started guide
gabrielvfonseca Oct 11, 2026
5d7f97e
style(sdk): apply formatter fixes to core, api, and utils
gabrielvfonseca Oct 11, 2026
ae5811f
refactor(sdk): remove obsolete utils package
gabrielvfonseca Oct 11, 2026
ac0f1b8
chore(repo): re-baseline api contract coverage floor to 142
gabrielvfonseca Oct 11, 2026
a292cfe
docs(repo): record openapi snapshot drift in contract reports
gabrielvfonseca Oct 11, 2026
c18667f
fix(scripts): limit docs manifest to tracked markdown files
gabrielvfonseca Oct 11, 2026
82b0a9e
chore(repo): regenerate documentation catalogs
gabrielvfonseca Oct 11, 2026
ab0cf9d
chore(scripts): break down contract drift per domain
gabrielvfonseca Oct 11, 2026
cd9d63f
docs(repo): document contract snapshot provenance and refresh path
gabrielvfonseca Oct 11, 2026
0d91a0e
test(sdk): assert error fix hints per status and code
gabrielvfonseca Oct 11, 2026
19719a5
docs(repo): disambiguate conformance report counters
gabrielvfonseca Oct 11, 2026
857cd8d
refactor(core): dispatch error construction through a lookup table
gabrielvfonseca Oct 12, 2026
75267c3
refactor(scripts): split contract checker main into focused functions
gabrielvfonseca Oct 12, 2026
bf7c4ab
fix(core): export circuit breaker error and state from the facade
gabrielvfonseca Oct 12, 2026
c96789a
fix(core): invoke circuit breaker callbacks outside the lock
gabrielvfonseca Oct 12, 2026
6cc3b95
docs(resources): correct rerank, patch, and approval descriptions
gabrielvfonseca Oct 12, 2026
6db1ede
fix(scripts): skip deleted markdown in the docs manifest
gabrielvfonseca Oct 12, 2026
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
22 changes: 20 additions & 2 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,27 @@

The JSON and OpenAPI files here are shared Frontal API inputs.
`sdk-endpoints.json` records the SDK's service methods and paths, while
`coverage-floor.json` records an existing coverage baseline. Keep the public
client hand-written and do not edit upstream OpenAPI snapshots by hand.
`coverage-floor.json` ratchets how many published snapshot operations the
inventory matches. Keep the public client hand-written and do not edit
upstream OpenAPI snapshots by hand.

`reports/conformance.json` and `reports/migration-matrix.md` describe the
implemented method and path coverage. Run `python scripts/check_contracts.py`
after changing the endpoint inventory or service methods.

The published `api.openapi.json` snapshot is a pinned upstream baseline
(`sha256` in `openapi/manifest.json`) that covers a subset of the API with a
different path vocabulary than the SDK implements, so the checker reports
inventory operations absent from the snapshot and snapshot operations outside
the inventory as informational drift, not failures. The report breaks the
drift down per domain so it is actionable. Update `coverage-floor.json` when
the matched baseline legitimately changes.

`api.openapi.json` is shared byte-for-byte with the TypeScript SDK
(`sdk-typescript/contracts/openapi/api.openapi.json`). That repository's
manifest pins the upstream artifact as `sha256 c4603577…` while both
repositories committed `ca5f245…`, and `https://openapi.frontal.dev` no longer
serves the spec, so the committed snapshot predates the upstream baseline it
should track. Refreshing it is an upstream action: republish the artifact,
copy it in unchanged, and update the `sha256` in `openapi/manifest.json`. The
drift lines and `coverage-floor.json` then recompute automatically.
2 changes: 1 addition & 1 deletion contracts/coverage-floor.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"api": 146,
"api": 142,
"ai": 7
}
21 changes: 18 additions & 3 deletions contracts/reports/conformance.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@
"repository": "sdk-python",
"language": "Python",
"status": "implemented",
"matched": 370,
"unmatched": 0,
"note": "Hand-written resource methods match contracts/sdk-endpoints.json. Payloads use the validated JSON boundary where operation schemas are absent."
"inventoryMethodsImplemented": 383,
"inventoryMethodsUnimplemented": 0,
"note": "inventoryMethodsImplemented counts contracts/sdk-endpoints.json entries backed by hand-written resource methods; inventoryMethodsUnimplemented counts entries with no method. Snapshot conformance is reported separately under openapiConformance.",
"openapiConformance": {
"api": {
"snapshotOperations": 383,
"matchedByInventory": 142,
"inventoryOperationsAbsentFromSnapshot": 227,
"snapshotOperationsOutsideInventory": 241
},
"ai": {
"snapshotOperations": 7,
"matchedByInventory": 7,
"inventoryOperationsAbsentFromSnapshot": 0,
"snapshotOperationsOutsideInventory": 0
}
},
"openapiNote": "contracts/openapi/api.openapi.json is the pinned upstream snapshot (sha256 in contracts/openapi/manifest.json) and covers a subset of the API with a different path vocabulary, so the gaps above are expected. It is authoritative and not edited here; contracts/coverage-floor.json ratchets the matched baseline."
}
24 changes: 22 additions & 2 deletions contracts/reports/migration-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,28 @@ _Status: hand-written endpoint methods follow the committed SDK endpoint invento

| Metric | Value |
| --- | ---: |
| Implemented endpoint methods | 370 |
| Contract endpoint entries | 370 |
| Implemented endpoint methods | 383 |
| Contract endpoint entries | 383 |
| Coverage | 100% |

Method and path coverage is complete for `contracts/sdk-endpoints.json`. Request and response payloads use a Pydantic-validated JSON boundary because the inventory does not include operation-specific payload schemas.

## Published OpenAPI snapshot conformance

The snapshots in `contracts/openapi/` are pinned upstream inputs (`sha256`
values in `contracts/openapi/manifest.json`) and are not edited in this
repository. `python scripts/check_contracts.py` reports how the endpoint
inventory lines up with them:

| Metric | api surface | ai surface |
| --- | ---: | ---: |
| Snapshot operations | 383 | 7 |
| Matched by the inventory | 142 | 7 |
| Inventory operations absent from the snapshot | 227 | 0 |
| Snapshot operations outside the inventory | 241 | 0 |

The published `api.openapi.json` baseline covers a subset of the API with a
different path vocabulary than the SDK implements (for example `/v1/integrations/...`
instead of the SDK's `/connectors/...`), so both gaps are expected drift, not
failures. The inventory remains the contract source for implemented methods,
and the matched baseline is ratcheted by `contracts/coverage-floor.json`.
143 changes: 143 additions & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Getting started

Install the package, set `FRONTAL_API_KEY`, and make your first request in
under five minutes.

## Install

Python 3.9–3.12 is supported.

```bash
pip install frontal
# or
uv add frontal
```

## Configure

The client reads its API key from `FRONTAL_API_KEY`. The base URL,
environment header, and debug logging read `FRONTAL_API_URL`,
`FRONTAL_ENV`, and `FRONTAL_DEBUG` when their constructor arguments are
omitted. The SDK does not load `.env` files.

```bash
export FRONTAL_API_KEY="frt_..."
```

## Hello world

```python
from frontal_sdk import Frontal

with Frontal() as client:
result = client.ai.generate_text(
{"model": "frontal-ai-fast", "prompt": "Say hello."}
)
print(result.text)
```

The async client exposes the same services and operation methods:

```python
import asyncio

from frontal_sdk import AsyncFrontal


async def main() -> None:
async with AsyncFrontal() as client:
result = await client.ai.generate_text(
{"model": "frontal-ai-fast", "prompt": "Say hello."}
)
print(result.text)


asyncio.run(main())
```

## Define an agent

Builders validate inputs before anything is sent:

```python
from frontal_sdk import Frontal

with Frontal() as client:
agent = (
client.agents.define("Ticket triage")
.description("Route incoming support tickets")
.trigger("ticket.created")
.can_read("tickets")
.create()
)
```

Message it, poll a run, or watch run events:

```python
from frontal_sdk import AgentExecution, Frontal

with Frontal() as client:
accessor = client.agents.use("agt_ticket_triager")
run = AgentExecution.model_validate(
accessor.message("support.ticket.created", {"ticketId": "t_987"})
)
final = accessor.wait_for_completion(run.id)
for event in accessor.watch(run.id):
print(event.event, event.data)
```

## Stream text

`stream_text()` yields typed parts — text deltas, tool calls, a finish
part with usage, error parts, and a terminal done part:

```python
with Frontal() as client:
for part in client.ai.stream_text(
{"model": "frontal-ai-fast", "prompt": "Count to three."}
):
if part.type == "text":
print(part.text, end="", flush=True)
elif part.type == "finish":
print(f"\n[finished: {part.finish_reason}]")
```

## Handle errors

Every API failure raises a `FrontalError` subclass carrying the error
`code`, `request_id`, `status_code`, a `transient` flag, an explicit
`safe_to_retry` signal, and — when the SDK knows one — a human-readable
`fix` hint:

```python
from frontal_sdk import Frontal, FrontalError, RateLimitError, ValidationError

with Frontal() as client:
try:
client.agents.list(query={"limit": 50})
except RateLimitError as error:
print(f"Rate limited; retry after {error.retry_after}s")
except ValidationError as error:
for field in error.fields:
print(f" - {field.field}: {field.message}")
except FrontalError as error:
print(f"Request failed [{error.code}]: {error}")
if error.fix is not None:
print(f"Hint: {error.fix}")
```

A transient error from a write may still represent an operation the
server accepted, so inspect `safe_to_retry` before replaying it.

## Next steps

- [API resources](RESOURCES.md) — AI helpers, builders, endpoint methods,
pagination, and retry safety.
- [Functions API](FUNCTIONS.md) — function definitions, invocations, and
executions.
- [Architecture](ARCHITECTURE.md) — package layout, config, transport,
retries, and streaming semantics.
- [Runnable examples](../examples/README.md) — offline scripts covering
agents, functions, workflows, streaming, and error handling.
- [Testing](TESTING.md) — offline RESPX coverage and runnable documentation.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Python documentation

- [Getting started](GETTING_STARTED.md) — install, first request, agents, streaming, and errors.
- [Overview](OVERVIEW.md) — install, clients, service attributes, and type boundaries.
- [Architecture](ARCHITECTURE.md) — package layout, config, transport, and retries.
- [API resources](RESOURCES.md) — AI helpers, builders, and endpoint methods.
Expand Down
108 changes: 108 additions & 0 deletions docs/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
"version": "1.0.0",
"status": "stable",
"documents": [
{
"path": ".claude/rules/sdk-language.md",
"title": "sdk-language"
},
{
"path": ".cursor/README.md",
"title": "README"
},
{
"path": ".cursor/commands/review-sdk-contract.md",
"title": "review-sdk-contract"
},
{
"path": ".cursor/commands/verify-sdk-change.md",
"title": "verify-sdk-change"
},
{
"path": ".github/BRANCH_PROTECTION.md",
"title": "BRANCH PROTECTION"
Expand All @@ -12,6 +28,10 @@
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"title": "PULL REQUEST TEMPLATE"
},
{
"path": ".vscode/README.md",
"title": "README"
},
{
"path": "AGENTS.md",
"title": "AGENTS"
Expand All @@ -20,6 +40,10 @@
"path": "CHANGELOG.md",
"title": "CHANGELOG"
},
{
"path": "CLAUDE.md",
"title": "CLAUDE"
},
{
"path": "CODE_OF_CONDUCT.md",
"title": "CODE OF CONDUCT"
Expand Down Expand Up @@ -64,6 +88,14 @@
"path": "docs/ENTERPRISE_READINESS.md",
"title": "ENTERPRISE READINESS"
},
{
"path": "docs/FUNCTIONS.md",
"title": "FUNCTIONS"
},
{
"path": "docs/GETTING_STARTED.md",
"title": "GETTING STARTED"
},
{
"path": "docs/ONBOARDING.md",
"title": "ONBOARDING"
Expand Down Expand Up @@ -112,10 +144,86 @@
"path": "scripts/README.md",
"title": "README"
},
{
"path": "skills/SOURCES.md",
"title": "SOURCES"
},
{
"path": "skills/frontal-sdk/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-lint/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-lint/references/migration.md",
"title": "migration"
},
{
"path": "skills/python-lint/references/rule-selection.md",
"title": "rule-selection"
},
{
"path": "skills/python-packaging/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-packaging/references/build-backends.md",
"title": "build-backends"
},
{
"path": "skills/python-pydantic-httpx-contracts/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-supply-chain/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-supply-chain/references/dependabot.md",
"title": "dependabot"
},
{
"path": "skills/python-supply-chain/references/scanning-and-provenance.md",
"title": "scanning-and-provenance"
},
{
"path": "skills/python-sync-async-clients/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-testing/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-testing/references/beyond-coverage.md",
"title": "beyond-coverage"
},
{
"path": "skills/python-testing/references/configuration.md",
"title": "configuration"
},
{
"path": "skills/python-testing/references/multi-version.md",
"title": "multi-version"
},
{
"path": "skills/python-typing/SKILL.md",
"title": "SKILL"
},
{
"path": "skills/python-typing/references/checker-selection.md",
"title": "checker-selection"
},
{
"path": "skills/python-typing/references/py-typed-distribution.md",
"title": "py-typed-distribution"
},
{
"path": "skills/python-typing/references/strictness-ratchet.md",
"title": "strictness-ratchet"
},
{
"path": "templates/README.md",
"title": "README"
Expand Down
Loading
Loading