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.
Files changed (42) hide show
  1. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/CHANGELOG.md +28 -0
  2. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/PKG-INFO +55 -1
  3. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/README.md +54 -0
  4. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/pyproject.toml +1 -1
  5. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/scripts/gen-types.py +39 -16
  6. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/__init__.py +23 -2
  7. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/_operations.py +11 -0
  8. codai_sdk-0.5.0/src/codai/_resolve_types.py +212 -0
  9. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/_types.py +49 -5
  10. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/__init__.py +12 -0
  11. codai_sdk-0.5.0/src/codai/resources/resolve.py +190 -0
  12. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/triggers.py +8 -0
  13. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/test_parity.py +4 -3
  14. codai_sdk-0.5.0/tests/test_resolve.py +148 -0
  15. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/test_resources.py +126 -2
  16. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  17. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  18. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/dependabot.yml +0 -0
  19. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/pull_request_template.md +0 -0
  20. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.github/workflows/publish.yml +0 -0
  21. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/.gitignore +0 -0
  22. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/CODE_OF_CONDUCT.md +0 -0
  23. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/CONTRIBUTING.md +0 -0
  24. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/LICENSE +0 -0
  25. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/SECURITY.md +0 -0
  26. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/agent.py +0 -0
  27. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/chat.py +0 -0
  28. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/feedback.py +0 -0
  29. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/examples/streaming.py +0 -0
  30. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/scripts/smoke_local.py +0 -0
  31. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/_http.py +0 -0
  32. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/_base.py +0 -0
  33. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/agents.py +0 -0
  34. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/chat.py +0 -0
  35. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/core.py +0 -0
  36. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/environments.py +0 -0
  37. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/messages.py +0 -0
  38. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/platform.py +0 -0
  39. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/responses.py +0 -0
  40. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/src/codai/resources/sessions.py +0 -0
  41. {codai_sdk-0.3.0 → codai_sdk-0.5.0}/tests/conftest.py +0 -0
  42. {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.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.3.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 # write
10
- .\\.venv\\Scripts\\python scripts/gen-types.py --check # exit 1 when stale
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
- SPEC = PKG.parents[1] / "apps" / "docs" / "openapi" / "en" / "gateway.yaml"
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 gateway API.
44
+ HEADER = '''"""Typed dictionaries for the codai {api} API.
34
45
 
35
- generated — do not edit. Source: apps/docs/openapi/en/gateway.yaml.
36
- Regenerate with ``python scripts/gen-types.py`` (see that script's docstring).
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__(self, spec: Dict[str, Any]) -> None:
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 = [HEADER]
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
- with SPEC.open("r", encoding="utf-8") as fh:
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 = OUT.read_text(encoding="utf-8") if OUT.exists() else ""
337
+ current = out.read_text(encoding="utf-8") if out.exists() else ""
315
338
  if current != text:
316
- print(f"STALE: {OUT} differs from a fresh generation", file=sys.stderr)
339
+ print(f"STALE: {out} differs from a fresh generation", file=sys.stderr)
317
340
  return 1
318
- print(f"OK: {OUT} is up to date")
341
+ print(f"OK: {out} is up to date")
319
342
  return 0
320
- OUT.write_text(text, encoding="utf-8", newline="\n")
321
- print(f"wrote {OUT} ({text.count(chr(10))} lines, {len(spec['components']['schemas'])} schemas)")
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.3.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
+ ]