codai-sdk 0.3.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/CHANGELOG.md +28 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/PKG-INFO +55 -1
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/README.md +54 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/pyproject.toml +1 -1
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/scripts/gen-types.py +39 -16
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/__init__.py +23 -2
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/_operations.py +11 -0
- codai_sdk-0.5.0/src/codai/_resolve_types.py +212 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/_types.py +49 -5
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/__init__.py +12 -0
- codai_sdk-0.5.0/src/codai/resources/resolve.py +190 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/triggers.py +8 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/test_parity.py +4 -3
- codai_sdk-0.5.0/tests/test_resolve.py +148 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/test_resources.py +126 -2
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/dependabot.yml +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/pull_request_template.md +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/workflows/publish.yml +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.gitignore +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/CODE_OF_CONDUCT.md +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/CONTRIBUTING.md +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/LICENSE +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/SECURITY.md +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/agent.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/chat.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/feedback.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/streaming.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/scripts/smoke_local.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/_http.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/_base.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/agents.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/chat.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/core.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/environments.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/messages.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/platform.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/responses.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/sessions.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/conftest.py +0 -0
- {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/test_http.py +0 -0
|
@@ -6,6 +6,34 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-09-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Resolve job webhooks: `callback_url` on `ResolveIntake`, one-time `callback_secret` on
|
|
14
|
+
`ResolveQuote` / `ResolveDeclined`, and `ResolveWebhookEvent` / `ResolveWebhookEventName` /
|
|
15
|
+
`ResolveWebhookJob` TypedDicts (regenerated `_resolve_types.py`).
|
|
16
|
+
- `verify_resolve_webhook(secret, raw_body, signature_header, tolerance_seconds=300, now=None)`
|
|
17
|
+
— constant-time (`hmac.compare_digest`) check of `x-codai-signature: t=<unix>,v1=<hex>`
|
|
18
|
+
with a timestamp window. Exported from `codai` and `codai.resources`.
|
|
19
|
+
|
|
20
|
+
## [0.4.0] - 2026-09-27
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `client.resolve` — codai Resolve (pay per verified fix, `https://resolve.codai.ro`,
|
|
25
|
+
override with `resolve_base_url=`): `submit`, `get`, `accept`, `attestation`,
|
|
26
|
+
`health`, `wait_for`. `ResolveError` / `ResolveInsufficientFundsError` (402 carries
|
|
27
|
+
`topup_url`, `balance`, `required`). `RESOLVE_OPERATION_METHODS` maps every
|
|
28
|
+
`resolve.yaml` operationId; `_resolve_types.py` is generated from it.
|
|
29
|
+
|
|
30
|
+
## [0.3.1] - 2026-09-26
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Regenerated `_types.py`: project prebuild pool fields, environment `prebuild`
|
|
35
|
+
and `prebuild_claimed` on create (CE-0072).
|
|
36
|
+
|
|
9
37
|
## [0.3.0] - 2026-09-25
|
|
10
38
|
|
|
11
39
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codai-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Official Python SDK for the codai AI gateway — chat, messages, responses, streaming, agents, sessions, hosts, tasks, account.
|
|
5
5
|
Project-URL: Homepage, https://codai.ro
|
|
6
6
|
Project-URL: Repository, https://github.com/codai-ro/codai-sdk-python
|
|
@@ -147,6 +147,60 @@ Typed request/response shapes live in `codai._types` (generated from the
|
|
|
147
147
|
OpenAPI spec — `TypedDict`s such as `ChatCompletionRequest`, `Task`,
|
|
148
148
|
`SessionEvent`, `CreateSessionShareResponse`).
|
|
149
149
|
|
|
150
|
+
## codai Resolve (0.4.0)
|
|
151
|
+
|
|
152
|
+
Pay per verified fix: submit a public GitHub issue, get a flat quote, accept it
|
|
153
|
+
(held on your wallet), and pay only when the fix passes your tests. First 3 fixes
|
|
154
|
+
are free. Lives on `https://resolve.codai.ro` (same key; `resolve_base_url=` to override).
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from codai import Codai, ResolveInsufficientFundsError
|
|
158
|
+
|
|
159
|
+
client = Codai(api_key="codai_...")
|
|
160
|
+
job = client.resolve.submit({
|
|
161
|
+
"repo_url": "https://github.com/owner/repo",
|
|
162
|
+
"issue_url": "https://github.com/owner/repo/issues/42",
|
|
163
|
+
})
|
|
164
|
+
if job["status"] == "quoted":
|
|
165
|
+
try:
|
|
166
|
+
client.resolve.accept(job["id"])
|
|
167
|
+
except ResolveInsufficientFundsError as e:
|
|
168
|
+
print("top up first:", e.topup_url)
|
|
169
|
+
raise
|
|
170
|
+
done = client.resolve.wait_for(job["id"])
|
|
171
|
+
print(done["status"], done.get("attestation_url"))
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Failed attempts and infrastructure errors refund the hold in full. Types:
|
|
175
|
+
`codai._resolve_types` (generated from `resolve.yaml`).
|
|
176
|
+
|
|
177
|
+
### Webhooks instead of polling (0.5.0)
|
|
178
|
+
|
|
179
|
+
Pass `callback_url` (https, public host) on `submit`; the response carries
|
|
180
|
+
`callback_secret` **once** — store it. Resolve POSTs `job.quoted`, `job.declined`,
|
|
181
|
+
`job.resolved`, `job.failed` and `job.error`, signed with
|
|
182
|
+
`x-codai-signature: t=<unix>,v1=<hex HMAC-SHA256>`. Verify the raw body with
|
|
183
|
+
`verify_resolve_webhook` (constant-time, 300 s tolerance by default). Up to 3
|
|
184
|
+
attempts on 5xx/429/network errors; deduplicate on `x-codai-delivery`; delivery
|
|
185
|
+
never changes job state — `get(id)` stays the source of truth.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import json
|
|
189
|
+
from codai import verify_resolve_webhook
|
|
190
|
+
|
|
191
|
+
job = client.resolve.submit({
|
|
192
|
+
"repo_url": "https://github.com/owner/repo",
|
|
193
|
+
"issue_url": "https://github.com/owner/repo/issues/42",
|
|
194
|
+
"callback_url": "https://ci.example.com/hooks/codai-resolve",
|
|
195
|
+
})
|
|
196
|
+
save_secret(job["id"], job["callback_secret"]) # shown once
|
|
197
|
+
|
|
198
|
+
# in your webhook handler (FastAPI: raw = await request.body())
|
|
199
|
+
if not verify_resolve_webhook(secret, raw, request.headers.get("x-codai-signature")):
|
|
200
|
+
raise HTTPException(401, "bad signature")
|
|
201
|
+
event = json.loads(raw) # event["event"], event["job"]["attestation_url"]
|
|
202
|
+
```
|
|
203
|
+
|
|
150
204
|
## Development
|
|
151
205
|
|
|
152
206
|
```bash
|
|
@@ -123,6 +123,60 @@ Typed request/response shapes live in `codai._types` (generated from the
|
|
|
123
123
|
OpenAPI spec — `TypedDict`s such as `ChatCompletionRequest`, `Task`,
|
|
124
124
|
`SessionEvent`, `CreateSessionShareResponse`).
|
|
125
125
|
|
|
126
|
+
## codai Resolve (0.4.0)
|
|
127
|
+
|
|
128
|
+
Pay per verified fix: submit a public GitHub issue, get a flat quote, accept it
|
|
129
|
+
(held on your wallet), and pay only when the fix passes your tests. First 3 fixes
|
|
130
|
+
are free. Lives on `https://resolve.codai.ro` (same key; `resolve_base_url=` to override).
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from codai import Codai, ResolveInsufficientFundsError
|
|
134
|
+
|
|
135
|
+
client = Codai(api_key="codai_...")
|
|
136
|
+
job = client.resolve.submit({
|
|
137
|
+
"repo_url": "https://github.com/owner/repo",
|
|
138
|
+
"issue_url": "https://github.com/owner/repo/issues/42",
|
|
139
|
+
})
|
|
140
|
+
if job["status"] == "quoted":
|
|
141
|
+
try:
|
|
142
|
+
client.resolve.accept(job["id"])
|
|
143
|
+
except ResolveInsufficientFundsError as e:
|
|
144
|
+
print("top up first:", e.topup_url)
|
|
145
|
+
raise
|
|
146
|
+
done = client.resolve.wait_for(job["id"])
|
|
147
|
+
print(done["status"], done.get("attestation_url"))
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Failed attempts and infrastructure errors refund the hold in full. Types:
|
|
151
|
+
`codai._resolve_types` (generated from `resolve.yaml`).
|
|
152
|
+
|
|
153
|
+
### Webhooks instead of polling (0.5.0)
|
|
154
|
+
|
|
155
|
+
Pass `callback_url` (https, public host) on `submit`; the response carries
|
|
156
|
+
`callback_secret` **once** — store it. Resolve POSTs `job.quoted`, `job.declined`,
|
|
157
|
+
`job.resolved`, `job.failed` and `job.error`, signed with
|
|
158
|
+
`x-codai-signature: t=<unix>,v1=<hex HMAC-SHA256>`. Verify the raw body with
|
|
159
|
+
`verify_resolve_webhook` (constant-time, 300 s tolerance by default). Up to 3
|
|
160
|
+
attempts on 5xx/429/network errors; deduplicate on `x-codai-delivery`; delivery
|
|
161
|
+
never changes job state — `get(id)` stays the source of truth.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
import json
|
|
165
|
+
from codai import verify_resolve_webhook
|
|
166
|
+
|
|
167
|
+
job = client.resolve.submit({
|
|
168
|
+
"repo_url": "https://github.com/owner/repo",
|
|
169
|
+
"issue_url": "https://github.com/owner/repo/issues/42",
|
|
170
|
+
"callback_url": "https://ci.example.com/hooks/codai-resolve",
|
|
171
|
+
})
|
|
172
|
+
save_secret(job["id"], job["callback_secret"]) # shown once
|
|
173
|
+
|
|
174
|
+
# in your webhook handler (FastAPI: raw = await request.body())
|
|
175
|
+
if not verify_resolve_webhook(secret, raw, request.headers.get("x-codai-signature")):
|
|
176
|
+
raise HTTPException(401, "bad signature")
|
|
177
|
+
event = json.loads(raw) # event["event"], event["job"]["attestation_url"]
|
|
178
|
+
```
|
|
179
|
+
|
|
126
180
|
## Development
|
|
127
181
|
|
|
128
182
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codai-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Official Python SDK for the codai AI gateway — chat, messages, responses, streaming, agents, sessions, hosts, tasks, account."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Generate ``src/codai/_types.py`` from ``apps/docs/openapi/en/gateway.yaml
|
|
2
|
+
"""Generate ``src/codai/_types.py`` from ``apps/docs/openapi/en/gateway.yaml``
|
|
3
|
+
(and ``src/codai/_resolve_types.py`` from ``resolve.yaml`` with ``--spec resolve``).
|
|
3
4
|
|
|
4
5
|
TypedDicts for every ``components.schemas`` entry plus a ``<operationId>Body`` /
|
|
5
6
|
``<operationId>Response`` alias for each operation's JSON request / 2xx body.
|
|
6
7
|
Deterministic: the same spec always produces byte-identical output, so a second
|
|
7
8
|
run is a no-op (the parity CI step diffs the committed file against a fresh run).
|
|
8
9
|
|
|
9
|
-
.\\.venv\\Scripts\\python scripts/gen-types.py
|
|
10
|
-
.\\.venv\\Scripts\\python scripts/gen-types.py --check
|
|
10
|
+
.\\.venv\\Scripts\\python scripts/gen-types.py # write
|
|
11
|
+
.\\.venv\\Scripts\\python scripts/gen-types.py --check # exit 1 when stale
|
|
12
|
+
.\\.venv\\Scripts\\python scripts/gen-types.py --spec resolve # Resolve API types
|
|
11
13
|
|
|
12
14
|
Dev-only dependency: pyyaml (``pip install -e ".[dev]"``). The runtime SDK stays
|
|
13
15
|
zero-dependency — this script is never imported by the package.
|
|
@@ -25,15 +27,24 @@ import yaml
|
|
|
25
27
|
|
|
26
28
|
HERE = Path(__file__).resolve().parent
|
|
27
29
|
PKG = HERE.parent
|
|
28
|
-
|
|
30
|
+
OPENAPI = PKG.parents[1] / "apps" / "docs" / "openapi" / "en"
|
|
31
|
+
SPEC = OPENAPI / "gateway.yaml"
|
|
29
32
|
OUT = PKG / "src" / "codai" / "_types.py"
|
|
33
|
+
RESOLVE_SPEC = OPENAPI / "resolve.yaml"
|
|
34
|
+
RESOLVE_OUT = PKG / "src" / "codai" / "_resolve_types.py"
|
|
35
|
+
|
|
36
|
+
# --spec name → (spec file, output file, API label, regenerate command)
|
|
37
|
+
TARGETS: Dict[str, Tuple[Path, Path, str, str]] = {
|
|
38
|
+
"gateway": (SPEC, OUT, "gateway", "python scripts/gen-types.py"),
|
|
39
|
+
"resolve": (RESOLVE_SPEC, RESOLVE_OUT, "Resolve", "python scripts/gen-types.py --spec resolve"),
|
|
40
|
+
}
|
|
30
41
|
|
|
31
42
|
HTTP_METHODS = ("get", "post", "put", "patch", "delete")
|
|
32
43
|
|
|
33
|
-
HEADER = '''"""Typed dictionaries for the codai
|
|
44
|
+
HEADER = '''"""Typed dictionaries for the codai {api} API.
|
|
34
45
|
|
|
35
|
-
generated — do not edit. Source: apps/docs/openapi/en/
|
|
36
|
-
Regenerate with ``
|
|
46
|
+
generated — do not edit. Source: apps/docs/openapi/en/{source}.
|
|
47
|
+
Regenerate with ``{regen}`` (see that script's docstring).
|
|
37
48
|
|
|
38
49
|
Every ``components.schemas`` entry is a ``TypedDict`` (``total=False`` when the
|
|
39
50
|
schema has optional fields; a ``_<Name>Required`` base carries the required
|
|
@@ -76,8 +87,15 @@ def union(parts: List[str]) -> str:
|
|
|
76
87
|
|
|
77
88
|
|
|
78
89
|
class Generator:
|
|
79
|
-
def __init__(
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
spec: Dict[str, Any],
|
|
93
|
+
api: str = "gateway",
|
|
94
|
+
source: str = "gateway.yaml",
|
|
95
|
+
regen: str = "python scripts/gen-types.py",
|
|
96
|
+
) -> None:
|
|
80
97
|
self.spec = spec
|
|
98
|
+
self.header = HEADER.format(api=api, source=source, regen=regen)
|
|
81
99
|
self.schemas: Dict[str, Any] = spec["components"]["schemas"]
|
|
82
100
|
self.blocks: List[Tuple[str, str]] = []
|
|
83
101
|
self.emitted: Dict[str, str] = {}
|
|
@@ -295,7 +313,7 @@ class Generator:
|
|
|
295
313
|
for name in self.schemas: # YAML order
|
|
296
314
|
self.ensure_schema(name)
|
|
297
315
|
self.emit_operations()
|
|
298
|
-
out = [
|
|
316
|
+
out = [self.header]
|
|
299
317
|
for _, code in self.blocks:
|
|
300
318
|
out.append(code)
|
|
301
319
|
out.append("\n\n")
|
|
@@ -307,18 +325,23 @@ class Generator:
|
|
|
307
325
|
|
|
308
326
|
|
|
309
327
|
def main(argv: List[str]) -> int:
|
|
310
|
-
|
|
328
|
+
name = argv[argv.index("--spec") + 1] if "--spec" in argv else "gateway"
|
|
329
|
+
if name not in TARGETS:
|
|
330
|
+
print(f"unknown --spec {name!r}; one of {', '.join(TARGETS)}", file=sys.stderr)
|
|
331
|
+
return 2
|
|
332
|
+
spec_path, out, api, regen = TARGETS[name]
|
|
333
|
+
with spec_path.open("r", encoding="utf-8") as fh:
|
|
311
334
|
spec = yaml.safe_load(fh)
|
|
312
|
-
text = Generator(spec).render()
|
|
335
|
+
text = Generator(spec, api=api, source=spec_path.name, regen=regen).render()
|
|
313
336
|
if "--check" in argv:
|
|
314
|
-
current =
|
|
337
|
+
current = out.read_text(encoding="utf-8") if out.exists() else ""
|
|
315
338
|
if current != text:
|
|
316
|
-
print(f"STALE: {
|
|
339
|
+
print(f"STALE: {out} differs from a fresh generation", file=sys.stderr)
|
|
317
340
|
return 1
|
|
318
|
-
print(f"OK: {
|
|
341
|
+
print(f"OK: {out} is up to date")
|
|
319
342
|
return 0
|
|
320
|
-
|
|
321
|
-
print(f"wrote {
|
|
343
|
+
out.write_text(text, encoding="utf-8", newline="\n")
|
|
344
|
+
print(f"wrote {out} ({text.count(chr(10))} lines, {len(spec['components']['schemas'])} schemas)")
|
|
322
345
|
return 0
|
|
323
346
|
|
|
324
347
|
|
|
@@ -32,8 +32,9 @@ from __future__ import annotations
|
|
|
32
32
|
from typing import Any, Dict, Generator, List, Optional
|
|
33
33
|
|
|
34
34
|
from ._http import CodaiError, HttpClient, RequestExtensions, SseFrame, extension_headers, frame_json, read_sse
|
|
35
|
-
from ._operations import OPERATION_METHODS, STREAMING_OPERATIONS
|
|
35
|
+
from ._operations import OPERATION_METHODS, RESOLVE_OPERATION_METHODS, STREAMING_OPERATIONS
|
|
36
36
|
from .resources import (
|
|
37
|
+
RESOLVE_TERMINAL_STATUSES,
|
|
37
38
|
Account,
|
|
38
39
|
AgentRunResult,
|
|
39
40
|
Agents,
|
|
@@ -55,6 +56,9 @@ from .resources import (
|
|
|
55
56
|
PhoneModels,
|
|
56
57
|
Projects,
|
|
57
58
|
Receipts,
|
|
59
|
+
Resolve,
|
|
60
|
+
ResolveError,
|
|
61
|
+
ResolveInsufficientFundsError,
|
|
58
62
|
Responses,
|
|
59
63
|
Sessions,
|
|
60
64
|
SystemOne,
|
|
@@ -62,6 +66,7 @@ from .resources import (
|
|
|
62
66
|
Tokens,
|
|
63
67
|
Tools,
|
|
64
68
|
Triggers,
|
|
69
|
+
verify_resolve_webhook,
|
|
65
70
|
)
|
|
66
71
|
|
|
67
72
|
__all__ = [
|
|
@@ -75,12 +80,17 @@ __all__ = [
|
|
|
75
80
|
"RequestExtensions",
|
|
76
81
|
"SseFrame",
|
|
77
82
|
"OPERATION_METHODS",
|
|
83
|
+
"RESOLVE_OPERATION_METHODS",
|
|
84
|
+
"RESOLVE_TERMINAL_STATUSES",
|
|
85
|
+
"ResolveError",
|
|
86
|
+
"ResolveInsufficientFundsError",
|
|
78
87
|
"STREAMING_OPERATIONS",
|
|
79
88
|
"extension_headers",
|
|
80
89
|
"frame_json",
|
|
81
90
|
"read_sse",
|
|
91
|
+
"verify_resolve_webhook",
|
|
82
92
|
]
|
|
83
|
-
__version__ = "0.
|
|
93
|
+
__version__ = "0.5.0"
|
|
84
94
|
|
|
85
95
|
|
|
86
96
|
class _CallableGroup:
|
|
@@ -113,6 +123,7 @@ class Codai:
|
|
|
113
123
|
client: Optional[str] = None,
|
|
114
124
|
defaults: Optional[RequestExtensions] = None,
|
|
115
125
|
opener: Any = None,
|
|
126
|
+
resolve_base_url: str = "https://resolve.codai.ro",
|
|
116
127
|
) -> None:
|
|
117
128
|
if not api_key:
|
|
118
129
|
raise ValueError("Codai: api_key is required")
|
|
@@ -156,6 +167,16 @@ class Codai:
|
|
|
156
167
|
self.phone_models = PhoneModels(http)
|
|
157
168
|
self.triggers = Triggers(http)
|
|
158
169
|
self.notifications = Notifications(http)
|
|
170
|
+
# codai Resolve lives on its own host; same key, no X-Codai-* defaults.
|
|
171
|
+
self.resolve = Resolve(
|
|
172
|
+
HttpClient(
|
|
173
|
+
api_key=api_key,
|
|
174
|
+
base_url=resolve_base_url,
|
|
175
|
+
timeout=timeout,
|
|
176
|
+
max_retries=max_retries,
|
|
177
|
+
opener=opener,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
159
180
|
|
|
160
181
|
self.chat: Any = _CallableGroup(Chat(http), self._legacy_chat)
|
|
161
182
|
self.embeddings: Any = _CallableGroup(Embeddings(http), self._legacy_embeddings)
|
|
@@ -123,6 +123,8 @@ OPERATION_METHODS: Dict[str, str] = {
|
|
|
123
123
|
"receiveTriggerHook": "triggers.fire_hook",
|
|
124
124
|
"listNotifications": "notifications.list",
|
|
125
125
|
"markNotificationsRead": "notifications.mark_read",
|
|
126
|
+
"deleteNotification": "notifications.delete",
|
|
127
|
+
"deleteNotifications": "notifications.delete_many",
|
|
126
128
|
# Account
|
|
127
129
|
"getAccount": "account.get",
|
|
128
130
|
"patchAccount": "account.update",
|
|
@@ -133,3 +135,12 @@ OPERATION_METHODS: Dict[str, str] = {
|
|
|
133
135
|
|
|
134
136
|
# Operations whose SDK method is a generator (SSE-only endpoints).
|
|
135
137
|
STREAMING_OPERATIONS: FrozenSet[str] = frozenset({"streamAgentRun", "streamSession", "streamHost"})
|
|
138
|
+
|
|
139
|
+
# codai Resolve (``apps/docs/openapi/en/resolve.yaml``) → ``client.resolve.*``.
|
|
140
|
+
RESOLVE_OPERATION_METHODS: Dict[str, str] = {
|
|
141
|
+
"resolveHealth": "resolve.health",
|
|
142
|
+
"createResolveJob": "resolve.submit",
|
|
143
|
+
"getResolveJob": "resolve.get",
|
|
144
|
+
"acceptResolveJob": "resolve.accept",
|
|
145
|
+
"getResolveAttestation": "resolve.attestation",
|
|
146
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Typed dictionaries for the codai Resolve API.
|
|
2
|
+
|
|
3
|
+
generated — do not edit. Source: apps/docs/openapi/en/resolve.yaml.
|
|
4
|
+
Regenerate with ``python scripts/gen-types.py --spec resolve`` (see that script's docstring).
|
|
5
|
+
|
|
6
|
+
Every ``components.schemas`` entry is a ``TypedDict`` (``total=False`` when the
|
|
7
|
+
schema has optional fields; a ``_<Name>Required`` base carries the required
|
|
8
|
+
keys of mixed schemas). Non-object schemas (enums, unions, arrays) are type
|
|
9
|
+
aliases. ``<operationId>Body`` / ``<operationId>Response`` name each
|
|
10
|
+
operation's JSON request body and first 2xx JSON response.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
|
16
|
+
|
|
17
|
+
class ResolveHealth(TypedDict):
|
|
18
|
+
ok: bool
|
|
19
|
+
service: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ResolveValidationDetails(TypedDict):
|
|
23
|
+
"""Zod `flatten()` output returned when the intake body fails schema validation."""
|
|
24
|
+
formErrors: List[str]
|
|
25
|
+
fieldErrors: Dict[str, List[str]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ResolveError(TypedDict):
|
|
29
|
+
"""Flat error envelope used by every Resolve endpoint. Not the gateway `{ error: { code, message } }` shape."""
|
|
30
|
+
error: Union[str, ResolveValidationDetails]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _ResolveIntakeRequired(TypedDict):
|
|
34
|
+
repo_url: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ResolveIntake(_ResolveIntakeRequired, total=False):
|
|
38
|
+
"""Body of `POST /v1/resolve/jobs`. Provide `issue_url` or `issue_text` (at least one)."""
|
|
39
|
+
issue_url: str
|
|
40
|
+
issue_text: str
|
|
41
|
+
test_command: str
|
|
42
|
+
callback_url: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# `free` (first 3 resolves per user), `t7` ($7), `t19` ($19), `t49` ($49). Prices are in USD micros on the wire.
|
|
46
|
+
ResolveTier = Literal['free', 't7', 't19', 't49']
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Lifecycle: `triage → quoted → accepted → running → resolved | failed`, plus `declined`
|
|
50
|
+
ResolveJobStatus = Literal['triage', 'quoted', 'declined', 'accepted', 'running', 'resolved', 'failed', 'error']
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _ResolveQuoteRequired(TypedDict):
|
|
54
|
+
id: str
|
|
55
|
+
status: Literal['quoted', 'accepted']
|
|
56
|
+
tier: ResolveTier
|
|
57
|
+
quote_micro_usd: int
|
|
58
|
+
triage_reason: str
|
|
59
|
+
free_tier_remaining: int
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ResolveQuote(_ResolveQuoteRequired, total=False):
|
|
63
|
+
"""`201` body — the job was quoted (`quoted`) or auto-accepted on the free tier (`accepted`)."""
|
|
64
|
+
callback_secret: str
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _ResolveDeclinedRequired(TypedDict):
|
|
68
|
+
id: str
|
|
69
|
+
status: str
|
|
70
|
+
decline_reason: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ResolveDeclined(_ResolveDeclinedRequired, total=False):
|
|
74
|
+
"""`200` body — triage declined the issue. The job is persisted with `status: declined` and is never billed."""
|
|
75
|
+
callback_secret: str
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ResolveAccepted(TypedDict):
|
|
79
|
+
"""Accepted"""
|
|
80
|
+
id: str
|
|
81
|
+
status: str
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ResolveInsufficientFunds(TypedDict):
|
|
85
|
+
"""`402` body of `POST /v1/resolve/jobs/{id}/accept` when the wallet cannot hold the quote."""
|
|
86
|
+
error: str
|
|
87
|
+
balance: int
|
|
88
|
+
required: int
|
|
89
|
+
currency: str
|
|
90
|
+
topup_url: str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ResolveJob(TypedDict):
|
|
94
|
+
"""Full job view returned by `GET /v1/resolve/jobs/{id}`. Nullable fields are `null` until the corresponding stag…"""
|
|
95
|
+
id: str
|
|
96
|
+
status: ResolveJobStatus
|
|
97
|
+
repo_url: str
|
|
98
|
+
tier: Optional[ResolveTier]
|
|
99
|
+
quote_micro_usd: Optional[int]
|
|
100
|
+
triage_reason: Optional[str]
|
|
101
|
+
decline_reason: Optional[str]
|
|
102
|
+
fail_reason: Optional[str]
|
|
103
|
+
cost_micro_usd: int
|
|
104
|
+
attestation_id: Optional[str]
|
|
105
|
+
attestation_url: Optional[str]
|
|
106
|
+
created_at: str
|
|
107
|
+
completed_at: Optional[str]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ResolveReproTest(TypedDict):
|
|
111
|
+
"""Repro test written by the agent when you did not supply `test_command`."""
|
|
112
|
+
path: str
|
|
113
|
+
content: Optional[str]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ResolveAttestation(TypedDict):
|
|
117
|
+
"""Public execution proof. Everything needed to reproduce the verification locally."""
|
|
118
|
+
id: str
|
|
119
|
+
kind: str
|
|
120
|
+
repo_url: str
|
|
121
|
+
base_commit: str
|
|
122
|
+
patch: str
|
|
123
|
+
repro_test: Optional[ResolveReproTest]
|
|
124
|
+
test_command: str
|
|
125
|
+
regression_command: Optional[str]
|
|
126
|
+
test_output_before: str
|
|
127
|
+
test_output_after: str
|
|
128
|
+
regression_output: Optional[str]
|
|
129
|
+
runner_image_digest: str
|
|
130
|
+
started_at: str
|
|
131
|
+
verified_at: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# `job.quoted` (paid quote ready), `job.declined` (triage refused), `job.resolved` (verified fix + attestation),…
|
|
135
|
+
ResolveWebhookEventName = Literal['job.quoted', 'job.declined', 'job.resolved', 'job.failed', 'job.error']
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ResolveWebhookJob(TypedDict):
|
|
139
|
+
"""Snapshot of the job at send time. Nullable fields are `null` until the corresponding stage has run."""
|
|
140
|
+
id: str
|
|
141
|
+
status: ResolveJobStatus
|
|
142
|
+
tier: Optional[ResolveTier]
|
|
143
|
+
quote_micro_usd: Optional[int]
|
|
144
|
+
repo_url: str
|
|
145
|
+
decline_reason: Optional[str]
|
|
146
|
+
fail_reason: Optional[str]
|
|
147
|
+
cost_micro_usd: int
|
|
148
|
+
attestation_id: Optional[str]
|
|
149
|
+
attestation_url: Optional[str]
|
|
150
|
+
completed_at: Optional[str]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class ResolveWebhookEvent(TypedDict):
|
|
154
|
+
"""JSON body POSTed to `callback_url`. Verify `x-codai-signature` over the raw bytes before trusting it."""
|
|
155
|
+
event: ResolveWebhookEventName
|
|
156
|
+
job: ResolveWebhookJob
|
|
157
|
+
sent_at: str
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# GET /health — 200 response
|
|
161
|
+
ResolveHealthResponse = ResolveHealth
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# POST /v1/resolve/jobs — request body
|
|
165
|
+
CreateResolveJobBody = ResolveIntake
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# POST /v1/resolve/jobs — 200 response
|
|
169
|
+
CreateResolveJobResponse = ResolveDeclined
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# POST /v1/resolve/jobs — 201 response
|
|
173
|
+
CreateResolveJobResponse201 = ResolveQuote
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# GET /v1/resolve/jobs/{id} — 200 response
|
|
177
|
+
GetResolveJobResponse = ResolveJob
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# POST /v1/resolve/jobs/{id}/accept — 200 response
|
|
181
|
+
AcceptResolveJobResponse = ResolveAccepted
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# GET /v1/resolve/attestations/{id} — 200 response
|
|
185
|
+
GetResolveAttestationResponse = ResolveAttestation
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = [
|
|
189
|
+
'AcceptResolveJobResponse',
|
|
190
|
+
'CreateResolveJobBody',
|
|
191
|
+
'CreateResolveJobResponse',
|
|
192
|
+
'CreateResolveJobResponse201',
|
|
193
|
+
'GetResolveAttestationResponse',
|
|
194
|
+
'GetResolveJobResponse',
|
|
195
|
+
'ResolveAccepted',
|
|
196
|
+
'ResolveAttestation',
|
|
197
|
+
'ResolveDeclined',
|
|
198
|
+
'ResolveError',
|
|
199
|
+
'ResolveHealth',
|
|
200
|
+
'ResolveHealthResponse',
|
|
201
|
+
'ResolveInsufficientFunds',
|
|
202
|
+
'ResolveIntake',
|
|
203
|
+
'ResolveJob',
|
|
204
|
+
'ResolveJobStatus',
|
|
205
|
+
'ResolveQuote',
|
|
206
|
+
'ResolveReproTest',
|
|
207
|
+
'ResolveTier',
|
|
208
|
+
'ResolveValidationDetails',
|
|
209
|
+
'ResolveWebhookEvent',
|
|
210
|
+
'ResolveWebhookEventName',
|
|
211
|
+
'ResolveWebhookJob',
|
|
212
|
+
]
|