diff --git a/README.md b/README.md index fa80bbc..a01770a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ pip install calle-ai Pin the current stable release when your deployment process requires exact package reproducibility: ```bash -pip install calle-ai==0.6.0 +pip install calle-ai==0.7.0 ``` Use a local checkout for development and package smoke tests: @@ -113,6 +113,7 @@ run = client.goals.run_and_wait( ) if run["result"] is not None: + print(run["call_id"]) print(run["result"]) else: print(run["error"]) @@ -182,11 +183,11 @@ Manual stable PyPI publish: ```bash python -m venv .venv . .venv/bin/activate -pip install calle-ai==0.6.0 +pip install calle-ai==0.7.0 python -c 'from calle import CalleClient; c = CalleClient(api_key="smoke"); assert callable(c.goals.run_and_wait); c.close()' ``` -The current stable version is `0.6.0`. Do not reuse a previously published +The current stable version is `0.7.0`. Do not reuse a previously published PyPI version. ## Project Documents diff --git a/RELEASE.md b/RELEASE.md index bc247ba..6b8ff06 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -13,7 +13,7 @@ calle-ai==0.1.0b1 The current production PyPI release version is: ```text -calle-ai==0.6.0 +calle-ai==0.7.0 ``` For this release, use token-based PyPI publishing with the GitHub Actions secret `PYPI_API_TOKEN`. @@ -67,7 +67,7 @@ tmpdir="$(mktemp -d)" python -m venv "$tmpdir/.venv" . "$tmpdir/.venv/bin/activate" python -m pip install --upgrade pip -python -m pip install calle-ai==0.6.0 +python -m pip install calle-ai==0.7.0 python -c 'from calle import CalleClient; c = CalleClient(api_key="smoke"); assert callable(c.goals.run_and_wait); c.close()' ``` diff --git a/openapi-python-client.yml b/openapi-python-client.yml index 5a09d76..190f81f 100644 --- a/openapi-python-client.yml +++ b/openapi-python-client.yml @@ -1,6 +1,6 @@ project_name_override: calle-generated package_name_override: generated -package_version_override: 0.6.0 +package_version_override: 0.7.0 literal_enums: true generate_all_tags: true use_path_prefixes_for_title_model_names: false diff --git a/openapi/calle.openapi.yaml b/openapi/calle.openapi.yaml index 269e273..7c5004f 100644 --- a/openapi/calle.openapi.yaml +++ b/openapi/calle.openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: CALL-E Developer API - version: 0.6.0 + version: 0.7.0 description: Developer API contract used by the CALL-E TypeScript and Python SDKs. servers: - url: https://api.heycall-e.com @@ -32,12 +32,8 @@ paths: recipients: - phones: - "" - region: US - locale: en-US - phones: - "" - region: US - locale: en-US result_schema: type: object required: @@ -328,6 +324,8 @@ paths: `after` value; clients must not parse or construct cursor values. `title` and `description` help operators recognize each published workflow, but integrations should still store the intended `goal_id` at publish time and must not execute the first list item blindly. + Candidates whose published RunSpec is not execution-ready are omitted without preventing + later executable Goals from appearing in the page. parameters: - $ref: "#/components/parameters/GoalListLimit" - $ref: "#/components/parameters/GoalListAfter" @@ -399,8 +397,6 @@ paths: $ref: "#/components/responses/ErrorResponse" "403": $ref: "#/components/responses/ErrorResponse" - "409": - $ref: "#/components/responses/ErrorResponse" "429": $ref: "#/components/responses/ErrorResponse" "500": @@ -520,6 +516,7 @@ paths: id: rgrp_delivery_ord_8472 goal_id: goal_delivery_confirmation run_id: run_delivery_ord_8472 + call_id: null status: queued run_spec: id: rspec_delivery_v4 @@ -597,6 +594,7 @@ paths: id: rgrp_delivery_ord_8472 goal_id: goal_delivery_confirmation run_id: run_delivery_ord_8472 + call_id: calling_call_delivery_ord_8472 status: completed run_spec: id: rspec_delivery_v4 @@ -614,6 +612,7 @@ paths: id: rgrp_delivery_ord_8472 goal_id: goal_delivery_confirmation run_id: run_delivery_ord_8472 + call_id: null status: failed run_spec: id: rspec_delivery_v4 @@ -998,6 +997,7 @@ components: - id - goal_id - run_id + - call_id - run_spec - status - result @@ -1021,6 +1021,17 @@ components: type: string description: Internal execution member exposed for correlation; do not use it in the Goal Run polling path. minLength: 1 + call_id: + type: + - string + - "null" + description: |- + Calling call identifier selected for this Goal Run when that trusted fact is available, + or `null` before a call identifier is persisted or when no identifier is available. This + is different from the Goal Run `id` and nested `run_id`; it does not expose other provider + diagnostics and must not be treated as an independent answered-call boolean. + minLength: 1 + maxLength: 255 run_spec: description: Read-only identity and version of the exact RunSpec pinned by this Run. $ref: "#/components/schemas/GoalRunSpecSnapshot" diff --git a/pyproject.toml b/pyproject.toml index 7a9d350..33050f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "calle-ai" -version = "0.6.0" +version = "0.7.0" description = "Python server SDK for the CALL-E Developer API." readme = "README.md" requires-python = ">=3.11" diff --git a/scripts/verify_openapi_contract.py b/scripts/verify_openapi_contract.py index 36bb82b..4fbbf59 100644 --- a/scripts/verify_openapi_contract.py +++ b/scripts/verify_openapi_contract.py @@ -49,7 +49,7 @@ def main() -> None: "unexpected title", ) assert_contract( - spec.get("info", {}).get("version") == "0.6.0", + spec.get("info", {}).get("version") == "0.7.0", "unexpected API version", ) @@ -82,7 +82,7 @@ def main() -> None: "method": "get", "operation_id": "listGoals", "response_schema": "#/components/schemas/GoalList", - "error_statuses": ["400", "401", "403", "409", "429", "500"], + "error_statuses": ["400", "401", "403", "429", "500"], }, { "path": "/v1/goals/{goal_id}", @@ -319,6 +319,7 @@ def main() -> None: "id", "goal_id", "run_id", + "call_id", "run_spec", "status", "result", @@ -330,6 +331,10 @@ def main() -> None: property_name in goal_run_properties, f"GoalRun missing {property_name}", ) + assert_contract( + set(goal_run_properties["call_id"].get("type", [])) == {"string", "null"}, + "GoalRun.call_id must be nullable string", + ) goal_run_parameter_refs = parameter_refs( spec, diff --git a/src/calle/generated/api/goals/list_goals.py b/src/calle/generated/api/goals/list_goals.py index 8c6fd0a..45150b0 100644 --- a/src/calle/generated/api/goals/list_goals.py +++ b/src/calle/generated/api/goals/list_goals.py @@ -58,11 +58,6 @@ def _parse_response( return response_403 - if response.status_code == 409: - response_409 = ErrorEnvelope.from_dict(response.json()) - - return response_409 - if response.status_code == 429: response_429 = ErrorEnvelope.from_dict(response.json()) @@ -106,6 +101,8 @@ def sync_detailed( `after` value; clients must not parse or construct cursor values. `title` and `description` help operators recognize each published workflow, but integrations should still store the intended `goal_id` at publish time and must not execute the first list item blindly. + Candidates whose published RunSpec is not execution-ready are omitted without preventing + later executable Goals from appearing in the page. Args: limit (int | Unset): Default: 20. @@ -147,6 +144,8 @@ def sync( `after` value; clients must not parse or construct cursor values. `title` and `description` help operators recognize each published workflow, but integrations should still store the intended `goal_id` at publish time and must not execute the first list item blindly. + Candidates whose published RunSpec is not execution-ready are omitted without preventing + later executable Goals from appearing in the page. Args: limit (int | Unset): Default: 20. @@ -183,6 +182,8 @@ async def asyncio_detailed( `after` value; clients must not parse or construct cursor values. `title` and `description` help operators recognize each published workflow, but integrations should still store the intended `goal_id` at publish time and must not execute the first list item blindly. + Candidates whose published RunSpec is not execution-ready are omitted without preventing + later executable Goals from appearing in the page. Args: limit (int | Unset): Default: 20. @@ -222,6 +223,8 @@ async def asyncio( `after` value; clients must not parse or construct cursor values. `title` and `description` help operators recognize each published workflow, but integrations should still store the intended `goal_id` at publish time and must not execute the first list item blindly. + Candidates whose published RunSpec is not execution-ready are omitted without preventing + later executable Goals from appearing in the page. Args: limit (int | Unset): Default: 20. diff --git a/src/calle/generated/models/goal_run.py b/src/calle/generated/models/goal_run.py index 2e68e20..6f2c592 100644 --- a/src/calle/generated/models/goal_run.py +++ b/src/calle/generated/models/goal_run.py @@ -33,6 +33,10 @@ class GoalRun: id (str): Public Goal Run identity. Persist this value and use it as `goal_run_id` when polling. goal_id (str): Goal identity supplied in the create path. run_id (str): Internal execution member exposed for correlation; do not use it in the Goal Run polling path. + call_id (None | str): Calling call identifier selected for this Goal Run when that trusted fact is available, + or `null` before a call identifier is persisted or when no identifier is available. This + is different from the Goal Run `id` and nested `run_id`; it does not expose other provider + diagnostics and must not be treated as an independent answered-call boolean. run_spec (GoalRunSpecSnapshot): Exact immutable RunSpec identity and version pinned by a Goal Run. status (GoalRunStatus): Stable telephone execution state. `queued` and `in_progress` are non-terminal; `completed`, @@ -53,6 +57,7 @@ class GoalRun: id: str goal_id: str run_id: str + call_id: None | str run_spec: GoalRunSpecSnapshot status: GoalRunStatus result: GoalRunResultType0 | None @@ -72,6 +77,9 @@ def to_dict(self) -> dict[str, Any]: run_id = self.run_id + call_id: None | str + call_id = self.call_id + run_spec = self.run_spec.to_dict() status: str = self.status @@ -104,6 +112,7 @@ def to_dict(self) -> dict[str, Any]: "id": id, "goal_id": goal_id, "run_id": run_id, + "call_id": call_id, "run_spec": run_spec, "status": status, "result": result, @@ -130,6 +139,13 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: run_id = d.pop("run_id") + def _parse_call_id(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + call_id = _parse_call_id(d.pop("call_id")) + run_spec = GoalRunSpecSnapshot.from_dict(d.pop("run_spec")) status = check_goal_run_status(d.pop("status")) @@ -186,6 +202,7 @@ def _parse_completed_at(data: object) -> datetime.datetime | None: id=id, goal_id=goal_id, run_id=run_id, + call_id=call_id, run_spec=run_spec, status=status, result=result, diff --git a/tests/test_goals.py b/tests/test_goals.py index 3dee33d..b9b68ad 100644 --- a/tests/test_goals.py +++ b/tests/test_goals.py @@ -37,6 +37,7 @@ "id": "rgrp_delivery_8472", "goal_id": "goal_delivery", "run_id": "run_delivery_8472", + "call_id": None, "run_spec": {"id": "rspec_delivery_v4", "version": 4}, "status": "queued", "result": None, @@ -53,6 +54,7 @@ def test_generated_goal_models_parse_public_api_examples() -> None: assert goal.id == "goal_delivery" assert goal.published_run_spec.version == 4 assert run.id == "rgrp_delivery_8472" + assert run.call_id is None assert run.run_spec.id == "rspec_delivery_v4" @@ -150,6 +152,7 @@ def test_wait_for_result_ignores_completed_status_until_result_exists() -> None: } succeeded = { **materializing, + "call_id": "calling_call_delivery_8472", "result": {"delivery_outcome": "confirmed"}, } route = respx.get("https://api.heycall-e.com/v1/goals/goal_delivery/runs/rgrp_delivery_8472").mock( @@ -168,6 +171,7 @@ def test_wait_for_result_ignores_completed_status_until_result_exists() -> None: ) assert run["result"] == {"delivery_outcome": "confirmed"} + assert run["call_id"] == "calling_call_delivery_8472" assert route.call_count == 2 diff --git a/uv.lock b/uv.lock index 61fa50d..3cac1c7 100644 --- a/uv.lock +++ b/uv.lock @@ -88,7 +88,7 @@ wheels = [ [[package]] name = "calle-ai" -version = "0.6.0" +version = "0.7.0" source = { editable = "." } dependencies = [ { name = "attrs" },