codespar 0.10.2__tar.gz → 0.12.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.
- {codespar-0.10.2 → codespar-0.12.0}/CHANGELOG.md +46 -0
- {codespar-0.10.2 → codespar-0.12.0}/PKG-INFO +5 -2
- {codespar-0.10.2 → codespar-0.12.0}/pyproject.toml +8 -1
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/__init__.py +3 -1
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/_async_client.py +29 -1
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/_async_session.py +87 -26
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/_http.py +53 -2
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/_sync_client.py +91 -32
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/errors.py +4 -0
- codespar-0.12.0/src/codespar/mandate.py +424 -0
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/types.py +1 -1
- codespar-0.12.0/tests/_fixtures/canonical.v3.fixture.json +31 -0
- codespar-0.12.0/tests/_fixtures/canonical.v4.fixture.json +32 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/_fixtures/shop_canonical.json +1 -1
- codespar-0.12.0/tests/test_mandate.py +223 -0
- codespar-0.12.0/tests/test_mandate_strict.py +321 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_session.py +84 -0
- codespar-0.12.0/tests/test_timeout.py +237 -0
- {codespar-0.10.2 → codespar-0.12.0}/.gitignore +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/README.md +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/README.md +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/async_basic.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/connect_link.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/ecommerce_checkout.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/mocks_round_trip.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/pix_payment.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/examples/proxy_execute.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/_presets.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/py.typed +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/src/codespar/tool_result_codes.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/__init__.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/_fixtures/mocks_canonical.json +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_base_url_resolution.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_config.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_error_code_precedence.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_forward_mocks.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_mocks_types.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_shop.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_streaming.py +0 -0
- {codespar-0.10.2 → codespar-0.12.0}/tests/test_tool_result_codes.py +0 -0
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# codespar (Python SDK) — CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
`codespar.mandate` is strict, in parity with `@codespar/sdk/mandate` 0.16.11
|
|
6
|
+
and `codespar mandate verify` 0.17.0 (codespar-core#123, p2-identity#6).
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- `verify_mandate_token(...).verified` is `True` only when both Ed25519
|
|
11
|
+
signatures are carried and verify, the agent's under the key the token
|
|
12
|
+
names, and the token is not expired. It used to be `True` with the agent
|
|
13
|
+
signature stripped, with a signature left unchecked (no key given), and
|
|
14
|
+
after `expires_at`. `verify_mandate_token(t, agent_public_key=...)` alone is
|
|
15
|
+
now `verified=False` with `failures == ["issuer_sig_unchecked"]`.
|
|
16
|
+
- The key is the one the signed `agent_kid` names; an envelope `kid` that
|
|
17
|
+
differs is refused (`kid_mismatch`).
|
|
18
|
+
- `__version__` says `0.12.0`; it had said `0.10.2` through 0.11.0.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `agent_did_document=`: the agent's parsed did:web document; only the
|
|
23
|
+
`verificationMethod` whose `id` equals the signed `agent_kid` is used, so a
|
|
24
|
+
retired key listed beside the active one does not verify a token naming the
|
|
25
|
+
active one. Mutually exclusive with `agent_public_key` (`TypeError`).
|
|
26
|
+
- `now=` (UNIX seconds) for the expiry check; default is the current time.
|
|
27
|
+
- `MandateVerification.failures`, `.expired`, `.issued_at`.
|
|
28
|
+
- V4 tokens (`format_version: 4`, signed `issued_at`), byte-locked against the
|
|
29
|
+
enterprise fixture. An unknown later format raises
|
|
30
|
+
`MandateDecodeError("mandate_format_unsupported")`.
|
|
31
|
+
|
|
32
|
+
## 0.11.0
|
|
33
|
+
|
|
34
|
+
Offline V3 mandate verification lands as the `codespar.mandate`
|
|
35
|
+
submodule, in parity with `@codespar/sdk/mandate` and the CLI. See
|
|
36
|
+
[codespar/codespar-core#114](https://github.com/codespar/codespar-core/pull/114).
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `codespar.mandate`: `decode_mandate_token` and
|
|
41
|
+
`reconstruct_signing_string` are pure stdlib; `verify_ed25519` and
|
|
42
|
+
`verify_mandate_token` verify the V3 dual Ed25519 signatures and
|
|
43
|
+
lazily import `cryptography`, raising a clear install hint when it is
|
|
44
|
+
absent. New optional extra: `pip install codespar[verify]`. The base
|
|
45
|
+
package keeps its single runtime dependency (httpx).
|
|
46
|
+
- The canonical signing string is byte-locked against the platform's
|
|
47
|
+
frozen fixture, shared with the TS SDK and CLI tests.
|
|
48
|
+
|
|
3
49
|
## 0.10.0
|
|
4
50
|
|
|
5
51
|
The hosted test-mode SDK surface lands across the `codespar` Python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: codespar
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Python SDK for CodeSpar — commerce infrastructure for AI agents in Latin America.
|
|
5
5
|
Project-URL: Homepage, https://codespar.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.codespar.dev
|
|
@@ -22,11 +22,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
23
|
Requires-Dist: httpx>=0.27.0
|
|
24
24
|
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: cryptography>=42; extra == 'dev'
|
|
25
26
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
26
27
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
28
|
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
28
29
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
30
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
31
|
+
Provides-Extra: verify
|
|
32
|
+
Requires-Dist: cryptography>=42; extra == 'verify'
|
|
30
33
|
Description-Content-Type: text/markdown
|
|
31
34
|
|
|
32
35
|
# codespar — Python SDK
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codespar"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.12.0"
|
|
8
8
|
description = "Python SDK for CodeSpar — commerce infrastructure for AI agents in Latin America."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -30,10 +30,17 @@ dependencies = [
|
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[project.optional-dependencies]
|
|
33
|
+
# Offline V3 mandate verification (codespar.mandate) needs Ed25519, which is
|
|
34
|
+
# not in the stdlib. Opt in with `pip install codespar[verify]`; the base
|
|
35
|
+
# package keeps its single runtime dependency (httpx).
|
|
36
|
+
verify = [
|
|
37
|
+
"cryptography>=42",
|
|
38
|
+
]
|
|
33
39
|
dev = [
|
|
34
40
|
"pytest>=8.0",
|
|
35
41
|
"pytest-asyncio>=0.23",
|
|
36
42
|
"pytest-httpx>=0.30",
|
|
43
|
+
"cryptography>=42",
|
|
37
44
|
"ruff>=0.6",
|
|
38
45
|
"mypy>=1.11",
|
|
39
46
|
]
|
|
@@ -35,6 +35,7 @@ from .errors import (
|
|
|
35
35
|
ConfigError,
|
|
36
36
|
NotConnectedError,
|
|
37
37
|
StreamError,
|
|
38
|
+
TimeoutError,
|
|
38
39
|
)
|
|
39
40
|
from .tool_result_codes import (
|
|
40
41
|
APPROVAL_REQUIRED,
|
|
@@ -140,7 +141,7 @@ from .types import (
|
|
|
140
141
|
WizardAction,
|
|
141
142
|
)
|
|
142
143
|
|
|
143
|
-
__version__ = "0.
|
|
144
|
+
__version__ = "0.12.0"
|
|
144
145
|
|
|
145
146
|
__all__ = [
|
|
146
147
|
"APPROVAL_REQUIRED",
|
|
@@ -249,6 +250,7 @@ __all__ = [
|
|
|
249
250
|
"StreamError",
|
|
250
251
|
# Streaming events
|
|
251
252
|
"StreamEvent",
|
|
253
|
+
"TimeoutError",
|
|
252
254
|
"Tool",
|
|
253
255
|
"ToolCallRecord",
|
|
254
256
|
"ToolNotMockedOutput",
|
|
@@ -21,7 +21,7 @@ from ._async_session import (
|
|
|
21
21
|
build_session_info,
|
|
22
22
|
wait_for_connections,
|
|
23
23
|
)
|
|
24
|
-
from ._http import DEFAULT_BASE_URL, request_json
|
|
24
|
+
from ._http import DEFAULT_BASE_URL, normalize_timeout, request_json
|
|
25
25
|
from ._presets import preset_to_servers
|
|
26
26
|
from .errors import ApiError, ConfigError
|
|
27
27
|
from .types import SessionConfig
|
|
@@ -57,6 +57,18 @@ class AsyncCodeSpar:
|
|
|
57
57
|
session = await cs.create("user_123", preset="brazilian")
|
|
58
58
|
result = await session.send("charge R$500 via Pix")
|
|
59
59
|
print(result.message)
|
|
60
|
+
|
|
61
|
+
Timeout UNIT: every ``timeout`` here is in **seconds** (httpx
|
|
62
|
+
convention) — e.g. ``timeout=30`` is 30 seconds. The TypeScript SDK
|
|
63
|
+
(``@codespar/sdk``) uses **milliseconds** for the equivalent option.
|
|
64
|
+
Do not copy a numeric timeout between the two SDKs without
|
|
65
|
+
converting (30s in Python == ``timeout: 30000`` in TS).
|
|
66
|
+
|
|
67
|
+
Cancellation: there is no AbortSignal in Python. Cancel an in-flight
|
|
68
|
+
call by cancelling the awaiting asyncio task (e.g. asyncio.timeout()
|
|
69
|
+
or task.cancel()); httpx tears the connection down. Note this only
|
|
70
|
+
closes the connection — it does not undo an upstream side effect the
|
|
71
|
+
backend already dispatched.
|
|
60
72
|
"""
|
|
61
73
|
|
|
62
74
|
def __init__(
|
|
@@ -77,6 +89,9 @@ class AsyncCodeSpar:
|
|
|
77
89
|
raise ConfigError(
|
|
78
90
|
"project_id must match ^prj_[A-Za-z0-9]{16}$ (e.g. 'prj_...')."
|
|
79
91
|
)
|
|
92
|
+
# Fail fast on a misconfigured client-default timeout, same
|
|
93
|
+
# rule the per-call paths use (parity with TS validateTimeout).
|
|
94
|
+
timeout = normalize_timeout(timeout) or 60.0
|
|
80
95
|
self._api_key = api_key
|
|
81
96
|
self._base_url = _resolve_base_url(base_url).rstrip("/")
|
|
82
97
|
self._project_id = project_id
|
|
@@ -112,6 +127,18 @@ class AsyncCodeSpar:
|
|
|
112
127
|
await cs.create("user_123", preset="brazilian")
|
|
113
128
|
await cs.create("user_123", SessionConfig(preset="brazilian"))
|
|
114
129
|
"""
|
|
130
|
+
# Extract before _resolve_config so it doesn't reach the allowed-kwarg check.
|
|
131
|
+
raw_timeout = kwargs.pop("timeout", None)
|
|
132
|
+
timeout: float | None
|
|
133
|
+
if raw_timeout is None:
|
|
134
|
+
timeout = None
|
|
135
|
+
elif isinstance(raw_timeout, bool) or not isinstance(raw_timeout, (int, float)):
|
|
136
|
+
raise ConfigError(
|
|
137
|
+
f"create(): 'timeout' must be a number of seconds, got {type(raw_timeout).__name__}"
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
timeout = float(raw_timeout)
|
|
141
|
+
|
|
115
142
|
resolved = self._resolve_config(config, kwargs)
|
|
116
143
|
servers = resolved.servers or preset_to_servers(resolved.preset)
|
|
117
144
|
project_id = resolved.project_id or self._project_id
|
|
@@ -133,6 +160,7 @@ class AsyncCodeSpar:
|
|
|
133
160
|
api_key=self._api_key,
|
|
134
161
|
project_id=project_id,
|
|
135
162
|
body=body,
|
|
163
|
+
timeout=timeout,
|
|
136
164
|
)
|
|
137
165
|
if not isinstance(data, dict):
|
|
138
166
|
raise ApiError("create: malformed response", status=0, body=data)
|
|
@@ -18,7 +18,7 @@ from typing import Any
|
|
|
18
18
|
import httpx
|
|
19
19
|
|
|
20
20
|
from ._http import request_json, stream_sse, stream_sse_get
|
|
21
|
-
from .errors import ApiError, ConfigError
|
|
21
|
+
from .errors import ApiError, ConfigError, TimeoutError
|
|
22
22
|
from .types import (
|
|
23
23
|
AssistantTextEvent,
|
|
24
24
|
AuthConfig,
|
|
@@ -397,7 +397,13 @@ class AsyncSession:
|
|
|
397
397
|
|
|
398
398
|
# ── execution ───────────────────────────────────────────────────────
|
|
399
399
|
|
|
400
|
-
async def execute(
|
|
400
|
+
async def execute(
|
|
401
|
+
self,
|
|
402
|
+
tool_name: str,
|
|
403
|
+
params: dict[str, Any],
|
|
404
|
+
*,
|
|
405
|
+
timeout: float | None = None,
|
|
406
|
+
) -> ToolResult:
|
|
401
407
|
"""Call a specific tool by name. Always returns a ToolResult, even on error."""
|
|
402
408
|
start = _now_ms()
|
|
403
409
|
try:
|
|
@@ -408,6 +414,7 @@ class AsyncSession:
|
|
|
408
414
|
api_key=self._api_key,
|
|
409
415
|
project_id=self._project_id,
|
|
410
416
|
body={"tool": tool_name, "input": params},
|
|
417
|
+
timeout=timeout,
|
|
411
418
|
)
|
|
412
419
|
except ApiError as exc:
|
|
413
420
|
return ToolResult(
|
|
@@ -444,6 +451,8 @@ class AsyncSession:
|
|
|
444
451
|
self,
|
|
445
452
|
use_case: str,
|
|
446
453
|
options: DiscoverOptions | None = None,
|
|
454
|
+
*,
|
|
455
|
+
timeout: float | None = None,
|
|
447
456
|
) -> DiscoverResult:
|
|
448
457
|
"""
|
|
449
458
|
Search the catalog for a tool that matches a free-form use case.
|
|
@@ -459,7 +468,7 @@ class AsyncSession:
|
|
|
459
468
|
params["country"] = options.country
|
|
460
469
|
if options.limit is not None:
|
|
461
470
|
params["limit"] = options.limit
|
|
462
|
-
result = await self.execute("codespar_discover", params)
|
|
471
|
+
result = await self.execute("codespar_discover", params, timeout=timeout)
|
|
463
472
|
if not result.success:
|
|
464
473
|
raise ApiError(
|
|
465
474
|
f"discover failed: {result.error or 'unknown'}",
|
|
@@ -473,6 +482,8 @@ class AsyncSession:
|
|
|
473
482
|
async def connection_wizard(
|
|
474
483
|
self,
|
|
475
484
|
options: ConnectionWizardOptions,
|
|
485
|
+
*,
|
|
486
|
+
timeout: float | None = None,
|
|
476
487
|
) -> ConnectionWizardResult:
|
|
477
488
|
"""
|
|
478
489
|
Surface the connection wizard for a server (or list every
|
|
@@ -497,7 +508,7 @@ class AsyncSession:
|
|
|
497
508
|
params["environment"] = options.environment
|
|
498
509
|
if options.return_to is not None:
|
|
499
510
|
params["return_to"] = options.return_to
|
|
500
|
-
result = await self.execute("codespar_manage_connections", params)
|
|
511
|
+
result = await self.execute("codespar_manage_connections", params, timeout=timeout)
|
|
501
512
|
if not result.success:
|
|
502
513
|
raise ApiError(
|
|
503
514
|
f"connection_wizard failed: {result.error or 'unknown'}",
|
|
@@ -512,7 +523,7 @@ class AsyncSession:
|
|
|
512
523
|
)
|
|
513
524
|
return _parse_wizard_result(result.data)
|
|
514
525
|
|
|
515
|
-
async def charge(self, args: ChargeArgs) -> ChargeResult:
|
|
526
|
+
async def charge(self, args: ChargeArgs, *, timeout: float | None = None) -> ChargeResult:
|
|
516
527
|
"""
|
|
517
528
|
Create an INBOUND charge — the buyer pays the merchant. Typed
|
|
518
529
|
wrapper around ``execute("codespar_charge", {...})``. Distinct
|
|
@@ -538,7 +549,7 @@ class AsyncSession:
|
|
|
538
549
|
}
|
|
539
550
|
if args.due_date is not None:
|
|
540
551
|
params["due_date"] = args.due_date
|
|
541
|
-
result = await self.execute("codespar_charge", params)
|
|
552
|
+
result = await self.execute("codespar_charge", params, timeout=timeout)
|
|
542
553
|
if not result.success:
|
|
543
554
|
raise ApiError(
|
|
544
555
|
f"charge failed: {result.error or 'unknown'}",
|
|
@@ -549,7 +560,7 @@ class AsyncSession:
|
|
|
549
560
|
raise ApiError("charge: malformed response", status=0, body=result.data)
|
|
550
561
|
return _parse_charge_result(result.data)
|
|
551
562
|
|
|
552
|
-
async def ship(self, args: ShipArgs) -> ShipResult:
|
|
563
|
+
async def ship(self, args: ShipArgs, *, timeout: float | None = None) -> ShipResult:
|
|
553
564
|
"""
|
|
554
565
|
Generate a shipping label OR fetch tracking status. Typed
|
|
555
566
|
wrapper around ``execute("codespar_ship", {...})``. Routes to
|
|
@@ -606,7 +617,7 @@ class AsyncSession:
|
|
|
606
617
|
params["tracking_code"] = args.tracking_code
|
|
607
618
|
if args.metadata is not None:
|
|
608
619
|
params["metadata"] = args.metadata
|
|
609
|
-
result = await self.execute("codespar_ship", params)
|
|
620
|
+
result = await self.execute("codespar_ship", params, timeout=timeout)
|
|
610
621
|
if not result.success:
|
|
611
622
|
raise ApiError(
|
|
612
623
|
f"ship failed: {result.error or 'unknown'}",
|
|
@@ -617,7 +628,7 @@ class AsyncSession:
|
|
|
617
628
|
raise ApiError("ship: malformed response", status=0, body=result.data)
|
|
618
629
|
return _parse_ship_result(result.data)
|
|
619
630
|
|
|
620
|
-
async def ledger(self, args: LedgerArgs) -> LedgerResult:
|
|
631
|
+
async def ledger(self, args: LedgerArgs, *, timeout: float | None = None) -> LedgerResult:
|
|
621
632
|
"""
|
|
622
633
|
Post a double-entry journal entry, read an account's balances,
|
|
623
634
|
or create an account on the tenant's self-hosted Lerian Midaz
|
|
@@ -657,7 +668,7 @@ class AsyncSession:
|
|
|
657
668
|
params["type"] = args.type
|
|
658
669
|
if args.metadata is not None:
|
|
659
670
|
params["metadata"] = args.metadata
|
|
660
|
-
result = await self.execute("codespar_ledger", params)
|
|
671
|
+
result = await self.execute("codespar_ledger", params, timeout=timeout)
|
|
661
672
|
if not result.success:
|
|
662
673
|
raise ApiError(
|
|
663
674
|
f"ledger failed: {result.error or 'unknown'}",
|
|
@@ -668,7 +679,7 @@ class AsyncSession:
|
|
|
668
679
|
raise ApiError("ledger: malformed response", status=0, body=result.data)
|
|
669
680
|
return _parse_ledger_result(result.data)
|
|
670
681
|
|
|
671
|
-
async def issue(self, args: IssueArgs) -> IssueResult:
|
|
682
|
+
async def issue(self, args: IssueArgs, *, timeout: float | None = None) -> IssueResult:
|
|
672
683
|
"""
|
|
673
684
|
Issue a virtual/physical card, control (freeze/unfreeze/cancel)
|
|
674
685
|
one, or read its status on the tenant's Pomelo card-issuing
|
|
@@ -694,7 +705,7 @@ class AsyncSession:
|
|
|
694
705
|
params["shipping_address"] = args.shipping_address
|
|
695
706
|
if args.metadata is not None:
|
|
696
707
|
params["metadata"] = args.metadata
|
|
697
|
-
result = await self.execute("codespar_issue", params)
|
|
708
|
+
result = await self.execute("codespar_issue", params, timeout=timeout)
|
|
698
709
|
if not result.success:
|
|
699
710
|
raise ApiError(
|
|
700
711
|
f"issue failed: {result.error or 'unknown'}",
|
|
@@ -705,7 +716,7 @@ class AsyncSession:
|
|
|
705
716
|
raise ApiError("issue: malformed response", status=0, body=result.data)
|
|
706
717
|
return _parse_issue_result(result.data)
|
|
707
718
|
|
|
708
|
-
async def shop(self, args: ShopArgs) -> ShopResult:
|
|
719
|
+
async def shop(self, args: ShopArgs, *, timeout: float | None = None) -> ShopResult:
|
|
709
720
|
"""
|
|
710
721
|
Buy-side shopping: catalog search → async checkout → Pix mint.
|
|
711
722
|
Typed wrapper around ``execute("codespar_shop", {...})``. The
|
|
@@ -778,7 +789,7 @@ class AsyncSession:
|
|
|
778
789
|
else: # checkout_status
|
|
779
790
|
if args.checkout_session_id is not None:
|
|
780
791
|
params["checkout_session_id"] = args.checkout_session_id
|
|
781
|
-
result = await self.execute("codespar_shop", params)
|
|
792
|
+
result = await self.execute("codespar_shop", params, timeout=timeout)
|
|
782
793
|
if not result.success:
|
|
783
794
|
raise ApiError(
|
|
784
795
|
f"shop failed: {result.error or 'unknown'}",
|
|
@@ -790,7 +801,10 @@ class AsyncSession:
|
|
|
790
801
|
return _parse_shop_result(result.data, args.action)
|
|
791
802
|
|
|
792
803
|
async def verification_status(
|
|
793
|
-
self,
|
|
804
|
+
self,
|
|
805
|
+
tool_call_id: str,
|
|
806
|
+
*,
|
|
807
|
+
timeout: float | None = None,
|
|
794
808
|
) -> VerificationStatusResult:
|
|
795
809
|
"""
|
|
796
810
|
Async KYC poll for a meta-tool ``codespar_kyc`` call. Correlates
|
|
@@ -817,6 +831,7 @@ class AsyncSession:
|
|
|
817
831
|
f"/v1/tool-calls/{quote(tool_call_id, safe='')}/verification-status",
|
|
818
832
|
api_key=self._api_key,
|
|
819
833
|
project_id=self._project_id,
|
|
834
|
+
timeout=timeout,
|
|
820
835
|
)
|
|
821
836
|
if not isinstance(data, dict):
|
|
822
837
|
raise ApiError(
|
|
@@ -847,6 +862,7 @@ class AsyncSession:
|
|
|
847
862
|
*,
|
|
848
863
|
on_update: Callable[[VerificationStatusResult], Awaitable[None] | None]
|
|
849
864
|
| None = None,
|
|
865
|
+
timeout: float | None = None,
|
|
850
866
|
) -> VerificationStatusResult:
|
|
851
867
|
"""
|
|
852
868
|
SSE-streamed sibling of ``verification_status``. Opens
|
|
@@ -872,6 +888,7 @@ class AsyncSession:
|
|
|
872
888
|
f"/v1/tool-calls/{quote(tool_call_id, safe='')}/verification-status/stream",
|
|
873
889
|
api_key=self._api_key,
|
|
874
890
|
project_id=self._project_id,
|
|
891
|
+
timeout=timeout,
|
|
875
892
|
):
|
|
876
893
|
if event_name in ("snapshot", "update"):
|
|
877
894
|
if not isinstance(raw, dict):
|
|
@@ -908,7 +925,12 @@ class AsyncSession:
|
|
|
908
925
|
)
|
|
909
926
|
return last
|
|
910
927
|
|
|
911
|
-
async def payment_status(
|
|
928
|
+
async def payment_status(
|
|
929
|
+
self,
|
|
930
|
+
tool_call_id: str,
|
|
931
|
+
*,
|
|
932
|
+
timeout: float | None = None,
|
|
933
|
+
) -> PaymentStatusResult:
|
|
912
934
|
"""
|
|
913
935
|
Async settlement check for a meta-tool payment call. Correlates
|
|
914
936
|
a ``tool_call_id`` (the ``tc_xxx`` returned by ``execute``) back
|
|
@@ -927,6 +949,7 @@ class AsyncSession:
|
|
|
927
949
|
f"/v1/tool-calls/{quote(tool_call_id, safe='')}/payment-status",
|
|
928
950
|
api_key=self._api_key,
|
|
929
951
|
project_id=self._project_id,
|
|
952
|
+
timeout=timeout,
|
|
930
953
|
)
|
|
931
954
|
if not isinstance(data, dict):
|
|
932
955
|
raise ApiError(
|
|
@@ -957,6 +980,7 @@ class AsyncSession:
|
|
|
957
980
|
*,
|
|
958
981
|
on_update: Callable[[PaymentStatusResult], Awaitable[None] | None]
|
|
959
982
|
| None = None,
|
|
983
|
+
timeout: float | None = None,
|
|
960
984
|
) -> PaymentStatusResult:
|
|
961
985
|
"""
|
|
962
986
|
SSE-streamed sibling of ``payment_status``. Opens
|
|
@@ -978,6 +1002,7 @@ class AsyncSession:
|
|
|
978
1002
|
f"/v1/tool-calls/{quote(tool_call_id, safe='')}/payment-status/stream",
|
|
979
1003
|
api_key=self._api_key,
|
|
980
1004
|
project_id=self._project_id,
|
|
1005
|
+
timeout=timeout,
|
|
981
1006
|
):
|
|
982
1007
|
if event_name in ("snapshot", "update"):
|
|
983
1008
|
if not isinstance(raw, dict):
|
|
@@ -1016,7 +1041,12 @@ class AsyncSession:
|
|
|
1016
1041
|
|
|
1017
1042
|
# ── proxy ───────────────────────────────────────────────────────────
|
|
1018
1043
|
|
|
1019
|
-
async def proxy_execute(
|
|
1044
|
+
async def proxy_execute(
|
|
1045
|
+
self,
|
|
1046
|
+
request: ProxyRequest,
|
|
1047
|
+
*,
|
|
1048
|
+
timeout: float | None = None,
|
|
1049
|
+
) -> ProxyResult:
|
|
1020
1050
|
"""
|
|
1021
1051
|
Raw HTTP proxy to a connected server's upstream API. Auth is
|
|
1022
1052
|
injected by the backend — never send provider keys here.
|
|
@@ -1035,6 +1065,7 @@ class AsyncSession:
|
|
|
1035
1065
|
"params": request.params,
|
|
1036
1066
|
"headers": request.headers,
|
|
1037
1067
|
},
|
|
1068
|
+
timeout=timeout,
|
|
1038
1069
|
)
|
|
1039
1070
|
if not isinstance(data, dict):
|
|
1040
1071
|
raise ApiError("proxy_execute: malformed response", status=0, body=data)
|
|
@@ -1048,7 +1079,12 @@ class AsyncSession:
|
|
|
1048
1079
|
|
|
1049
1080
|
# ── natural-language ────────────────────────────────────────────────
|
|
1050
1081
|
|
|
1051
|
-
async def send(
|
|
1082
|
+
async def send(
|
|
1083
|
+
self,
|
|
1084
|
+
message: str,
|
|
1085
|
+
*,
|
|
1086
|
+
timeout: float | None = None,
|
|
1087
|
+
) -> SendResult:
|
|
1052
1088
|
"""Send a natural-language message. Blocks until the agent loop finishes."""
|
|
1053
1089
|
data = await request_json(
|
|
1054
1090
|
self._client,
|
|
@@ -1057,12 +1093,18 @@ class AsyncSession:
|
|
|
1057
1093
|
api_key=self._api_key,
|
|
1058
1094
|
project_id=self._project_id,
|
|
1059
1095
|
body={"message": message},
|
|
1096
|
+
timeout=timeout,
|
|
1060
1097
|
)
|
|
1061
1098
|
if not isinstance(data, dict):
|
|
1062
1099
|
raise ApiError("send: malformed response", status=0, body=data)
|
|
1063
1100
|
return _parse_send_result(data)
|
|
1064
1101
|
|
|
1065
|
-
async def send_stream(
|
|
1102
|
+
async def send_stream(
|
|
1103
|
+
self,
|
|
1104
|
+
message: str,
|
|
1105
|
+
*,
|
|
1106
|
+
timeout: float | None = None,
|
|
1107
|
+
) -> AsyncIterator[StreamEvent]:
|
|
1066
1108
|
"""
|
|
1067
1109
|
Stream a natural-language turn. Yields events as they arrive.
|
|
1068
1110
|
|
|
@@ -1081,6 +1123,7 @@ class AsyncSession:
|
|
|
1081
1123
|
api_key=self._api_key,
|
|
1082
1124
|
project_id=self._project_id,
|
|
1083
1125
|
body={"message": message},
|
|
1126
|
+
timeout=timeout,
|
|
1084
1127
|
):
|
|
1085
1128
|
event = _parse_stream_event(raw)
|
|
1086
1129
|
if event is not None:
|
|
@@ -1088,7 +1131,13 @@ class AsyncSession:
|
|
|
1088
1131
|
|
|
1089
1132
|
# ── Connect Links ───────────────────────────────────────────────────
|
|
1090
1133
|
|
|
1091
|
-
async def authorize(
|
|
1134
|
+
async def authorize(
|
|
1135
|
+
self,
|
|
1136
|
+
server_id: str,
|
|
1137
|
+
config: AuthConfig,
|
|
1138
|
+
*,
|
|
1139
|
+
timeout: float | None = None,
|
|
1140
|
+
) -> AuthResult:
|
|
1092
1141
|
"""
|
|
1093
1142
|
Start a Connect Link OAuth flow. Returns the URL your UI should
|
|
1094
1143
|
open for the end user; CodeSpar's callback stores tokens and
|
|
@@ -1106,6 +1155,7 @@ class AsyncSession:
|
|
|
1106
1155
|
"redirect_uri": config.redirect_uri,
|
|
1107
1156
|
"scopes": config.scopes,
|
|
1108
1157
|
},
|
|
1158
|
+
timeout=timeout,
|
|
1109
1159
|
)
|
|
1110
1160
|
if not isinstance(data, dict):
|
|
1111
1161
|
raise ApiError("authorize: malformed response", status=0, body=data)
|
|
@@ -1117,7 +1167,11 @@ class AsyncSession:
|
|
|
1117
1167
|
|
|
1118
1168
|
# ── connections ─────────────────────────────────────────────────────
|
|
1119
1169
|
|
|
1120
|
-
async def connections(
|
|
1170
|
+
async def connections(
|
|
1171
|
+
self,
|
|
1172
|
+
*,
|
|
1173
|
+
timeout: float | None = None,
|
|
1174
|
+
) -> list[ServerConnection]:
|
|
1121
1175
|
"""List server connections and refresh the internal tools cache."""
|
|
1122
1176
|
try:
|
|
1123
1177
|
data = await request_json(
|
|
@@ -1126,8 +1180,9 @@ class AsyncSession:
|
|
|
1126
1180
|
f"/v1/sessions/{self.id}/connections",
|
|
1127
1181
|
api_key=self._api_key,
|
|
1128
1182
|
project_id=self._project_id,
|
|
1183
|
+
timeout=timeout,
|
|
1129
1184
|
)
|
|
1130
|
-
except ApiError:
|
|
1185
|
+
except (ApiError, TimeoutError):
|
|
1131
1186
|
return list(self._cached_connections or [])
|
|
1132
1187
|
if not isinstance(data, dict):
|
|
1133
1188
|
return list(self._cached_connections or [])
|
|
@@ -1162,17 +1217,23 @@ class AsyncSession:
|
|
|
1162
1217
|
|
|
1163
1218
|
# ── lifecycle ───────────────────────────────────────────────────────
|
|
1164
1219
|
|
|
1165
|
-
async def close(
|
|
1220
|
+
async def close(
|
|
1221
|
+
self,
|
|
1222
|
+
*,
|
|
1223
|
+
timeout: float | None = None,
|
|
1224
|
+
) -> None:
|
|
1166
1225
|
"""Close the session on the backend. Safe to call multiple times.
|
|
1167
|
-
Best-effort — a 4xx/5xx here shouldn't
|
|
1168
|
-
backend cleans up stale sessions on a
|
|
1169
|
-
|
|
1226
|
+
Best-effort — a 4xx/5xx OR a backend timeout here shouldn't
|
|
1227
|
+
crash the caller. The backend cleans up stale sessions on a
|
|
1228
|
+
timer anyway."""
|
|
1229
|
+
with contextlib.suppress(ApiError, TimeoutError):
|
|
1170
1230
|
await request_json(
|
|
1171
1231
|
self._client,
|
|
1172
1232
|
"DELETE",
|
|
1173
1233
|
f"/v1/sessions/{self.id}",
|
|
1174
1234
|
api_key=self._api_key,
|
|
1175
1235
|
project_id=self._project_id,
|
|
1236
|
+
timeout=timeout,
|
|
1176
1237
|
)
|
|
1177
1238
|
|
|
1178
1239
|
|