meshagent-api 0.45.7__tar.gz → 0.46.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 (69) hide show
  1. {meshagent_api-0.45.7/meshagent_api.egg-info → meshagent_api-0.46.0}/PKG-INFO +1 -1
  2. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/client.py +0 -1
  3. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/client_test.py +5 -1
  4. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/crdt.py +1 -1
  5. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/room_server_client.py +28 -18
  6. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/room_server_client_test.py +237 -4
  7. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/runtime.py +1 -1
  8. meshagent_api-0.46.0/meshagent/api/runtime_test.py +1447 -0
  9. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/schema_document.py +2 -1
  10. meshagent_api-0.46.0/meshagent/api/schema_document_test.py +494 -0
  11. meshagent_api-0.46.0/meshagent/api/version.py +1 -0
  12. {meshagent_api-0.45.7 → meshagent_api-0.46.0/meshagent_api.egg-info}/PKG-INFO +1 -1
  13. meshagent_api-0.45.7/meshagent/api/runtime_test.py +0 -325
  14. meshagent_api-0.45.7/meshagent/api/schema_document_test.py +0 -107
  15. meshagent_api-0.45.7/meshagent/api/version.py +0 -1
  16. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/LICENSE +0 -0
  17. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/MANIFEST.in +0 -0
  18. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/README.md +0 -0
  19. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/__init__.py +0 -0
  20. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/agent_content.py +0 -0
  21. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/aiohttp_redaction.py +0 -0
  22. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/chan.py +0 -0
  23. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/dataset_schema.py +0 -0
  24. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/entrypoint.js +0 -0
  25. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/error_codes.py +0 -0
  26. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/helpers.py +0 -0
  27. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/http.py +0 -0
  28. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/image_runtime.py +0 -0
  29. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/keys.py +0 -0
  30. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/managed_agents.py +0 -0
  31. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/managed_agents_test.py +0 -0
  32. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/messaging.py +0 -0
  33. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/messaging_test.py +0 -0
  34. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/oauth.py +0 -0
  35. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/oauth_scopes.py +0 -0
  36. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/oauth_scopes_test.py +0 -0
  37. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/participant.py +0 -0
  38. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/participant_token.py +0 -0
  39. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/participant_token_test.py +0 -0
  40. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/port_forward.py +0 -0
  41. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/port_forward_test.py +0 -0
  42. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/protocol.py +0 -0
  43. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/protocol_test.py +0 -0
  44. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/py.typed +0 -0
  45. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/reasoning_schema.py +0 -0
  46. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/registry_auth.py +0 -0
  47. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/room_ports.py +0 -0
  48. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/schema.py +0 -0
  49. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/schema_registry.py +0 -0
  50. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/schema_test.py +0 -0
  51. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/schema_util.py +0 -0
  52. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/service_spec_test.py +0 -0
  53. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/service_template_test.py +0 -0
  54. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/services.py +0 -0
  55. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/specs/service.py +0 -0
  56. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/sql.py +0 -0
  57. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/sql_test.py +0 -0
  58. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/token_test.py +0 -0
  59. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/urls.py +0 -0
  60. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/webhooks.py +0 -0
  61. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/webhooks_test.py +0 -0
  62. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/websocket_protocol.py +0 -0
  63. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent/api/websocket_protocol_test.py +0 -0
  64. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent_api.egg-info/SOURCES.txt +0 -0
  65. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent_api.egg-info/dependency_links.txt +0 -0
  66. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent_api.egg-info/requires.txt +0 -0
  67. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/meshagent_api.egg-info/top_level.txt +0 -0
  68. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/pyproject.toml +0 -0
  69. {meshagent_api-0.45.7 → meshagent_api-0.46.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.45.7
3
+ Version: 0.46.0
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -557,7 +557,6 @@ class SecretVersion(BaseModel):
557
557
  id: str
558
558
  secret_id: str
559
559
  version: int
560
- encryption_key_id: str
561
560
  value_sha256: Optional[str] = None
562
561
  created_by_user_id: Optional[str] = None
563
562
  created_by_service_account_id: Optional[str] = None
@@ -916,7 +916,7 @@ def _secret_version_payload(**overrides):
916
916
  "id": "version-1",
917
917
  "secret_id": "secret-1",
918
918
  "version": 1,
919
- "encryption_key_id": "key-1",
919
+ "encryption_key_id": "supabase-vault",
920
920
  "value_sha256": None,
921
921
  "created_by_user_id": "user-1",
922
922
  "created_by_service_account_id": None,
@@ -1059,7 +1059,9 @@ async def test_user_secret_methods_use_new_user_scoped_routes():
1059
1059
  assert fetched.value_base64 == base64.b64encode(b"value").decode()
1060
1060
  assert updated.name == "renamed"
1061
1061
  assert versions[0].version == 1
1062
+ assert not hasattr(versions[0], "encryption_key_id")
1062
1063
  assert created_version.value_sha256 == base64.b64encode(b"hash").decode()
1064
+ assert not hasattr(created_version, "encryption_key_id")
1063
1065
  assert version_value == b"version value"
1064
1066
  assert proxy_access.continuation_token == "next-grants-page"
1065
1067
  assert proxy_access.access_grants[0].subject.type == "service_account"
@@ -1367,7 +1369,9 @@ async def test_service_account_secret_methods_use_service_account_scoped_routes(
1367
1369
  assert fetched.value_base64 == base64.b64encode(b"service value").decode()
1368
1370
  assert updated.http_only is True
1369
1371
  assert versions[0].created_by_service_account_id == "sa-1"
1372
+ assert not hasattr(versions[0], "encryption_key_id")
1370
1373
  assert created_version.id == "version-2"
1374
+ assert not hasattr(created_version, "encryption_key_id")
1371
1375
  assert version_value == b"service version value"
1372
1376
  assert pull_secrets[0].owner_service_account_id == "sa-1"
1373
1377
  assert session.calls == [
@@ -73,7 +73,7 @@ class ServerXmlDocument:
73
73
  doc["xml"] = XmlFragment()
74
74
  self._root: XmlFragment = doc["xml"] # fragment not element
75
75
  self.doc = doc
76
- self._undo_mgr = UndoManager(self._root) if undo else None
76
+ self._undo_mgr = UndoManager(scopes=[self._root]) if undo else None
77
77
  self._notify = notify_changes
78
78
 
79
79
  # Deep observation (all element / text descendants):contentReference[oaicite:0]{index=0}
@@ -25,6 +25,7 @@ from pydantic import (
25
25
  Field,
26
26
  JsonValue,
27
27
  ConfigDict,
28
+ PositiveInt,
28
29
  ValidationError,
29
30
  field_validator,
30
31
  )
@@ -3152,6 +3153,18 @@ class ServiceRuntimeEvent(BaseModel):
3152
3153
  last_timestamp: float
3153
3154
 
3154
3155
 
3156
+ class ServicePortRuntimeState(BaseModel):
3157
+ num: Literal["*"] | PositiveInt
3158
+ liveness: Optional[str] = None
3159
+ liveness_status: Literal["not_configured", "not_ready", "ready"] = "not_configured"
3160
+ last_checked_at: Optional[float] = None
3161
+ last_error: Optional[str] = None
3162
+
3163
+
3164
+ class ServiceRuntimeStatus(BaseModel):
3165
+ ports: list[ServicePortRuntimeState] = Field(default_factory=list)
3166
+
3167
+
3155
3168
  class ServiceRuntimeState(BaseModel):
3156
3169
  service_id: str
3157
3170
  state: str
@@ -3163,6 +3176,7 @@ class ServiceRuntimeState(BaseModel):
3163
3176
  last_exit_at: Optional[float] = None
3164
3177
  last_start_error: Optional[str] = None
3165
3178
  last_start_error_at: Optional[float] = None
3179
+ status: ServiceRuntimeStatus = Field(default_factory=ServiceRuntimeStatus)
3166
3180
  events: list[ServiceRuntimeEvent] = Field(default_factory=list)
3167
3181
 
3168
3182
 
@@ -3198,15 +3212,6 @@ class ServicesClient:
3198
3212
 
3199
3213
  async def list(
3200
3214
  self,
3201
- ) -> List[ServiceSpec]:
3202
- """
3203
- Fetch a list of services.
3204
- """
3205
-
3206
- return (await self.list_with_state()).services
3207
-
3208
- async def list_with_state(
3209
- self,
3210
3215
  ) -> ListServicesResult:
3211
3216
  """
3212
3217
  Fetch a list of services plus runtime state details from the service controller.
@@ -5596,54 +5601,56 @@ class SqliteDatabaseDetails(BaseModel):
5596
5601
  tables: int | None = None
5597
5602
  size_bytes: int | None = None
5598
5603
 
5604
+ model_config = ConfigDict(extra="forbid", strict=True)
5605
+
5599
5606
 
5600
5607
  class _SqliteListDatabasesResponse(BaseModel):
5601
5608
  databases: list[str]
5602
5609
 
5603
- model_config = ConfigDict(extra="forbid")
5610
+ model_config = ConfigDict(extra="forbid", strict=True)
5604
5611
 
5605
5612
 
5606
5613
  class _SqliteListTablesResponse(BaseModel):
5607
5614
  tables: list[str]
5608
5615
 
5609
- model_config = ConfigDict(extra="forbid")
5616
+ model_config = ConfigDict(extra="forbid", strict=True)
5610
5617
 
5611
5618
 
5612
5619
  class _SqliteRowsAffectedResponse(BaseModel):
5613
5620
  rows_affected: int
5614
5621
 
5615
- model_config = ConfigDict(extra="forbid")
5622
+ model_config = ConfigDict(extra="forbid", strict=True)
5616
5623
 
5617
5624
 
5618
5625
  class _SqliteCountResponse(BaseModel):
5619
5626
  count: int
5620
5627
 
5621
- model_config = ConfigDict(extra="forbid")
5628
+ model_config = ConfigDict(extra="forbid", strict=True)
5622
5629
 
5623
5630
 
5624
5631
  class _SqliteStatementResponse(_SqliteRowsAffectedResponse):
5625
5632
  kind: Literal["statement"]
5626
5633
 
5627
- model_config = ConfigDict(extra="forbid")
5634
+ model_config = ConfigDict(extra="forbid", strict=True)
5628
5635
 
5629
5636
 
5630
5637
  class _SqliteCancelResponse(BaseModel):
5631
5638
  status: Literal["cancelled", "cancelling", "not_cancellable"]
5632
5639
 
5633
- model_config = ConfigDict(extra="forbid")
5640
+ model_config = ConfigDict(extra="forbid", strict=True)
5634
5641
 
5635
5642
 
5636
5643
  class _SqliteQueryHeaders(BaseModel):
5637
5644
  kind: Literal["query"]
5638
5645
  query_id: str
5639
5646
 
5640
- model_config = ConfigDict(extra="forbid")
5647
+ model_config = ConfigDict(extra="forbid", strict=True)
5641
5648
 
5642
5649
 
5643
5650
  class _SqliteKindHeaders(BaseModel):
5644
5651
  kind: str
5645
5652
 
5646
- model_config = ConfigDict(extra="forbid")
5653
+ model_config = ConfigDict(extra="forbid", strict=True)
5647
5654
 
5648
5655
 
5649
5656
  @dataclass(frozen=True, slots=True)
@@ -7757,7 +7764,10 @@ class SqliteClient:
7757
7764
  )
7758
7765
  if not isinstance(response, JsonContent):
7759
7766
  raise self._unexpected_response_error(operation="inspect_database")
7760
- return SqliteDatabaseDetails.model_validate(response.json)
7767
+ try:
7768
+ return SqliteDatabaseDetails.model_validate(response.json)
7769
+ except ValidationError as exc:
7770
+ raise self._unexpected_response_error(operation="inspect_database") from exc
7761
7771
 
7762
7772
  async def list_tables(
7763
7773
  self, *, database: str, namespace: Optional[list[str]] = None
@@ -2577,6 +2577,64 @@ async def test_memory_client_list_validates_typed_response() -> None:
2577
2577
  assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
2578
2578
 
2579
2579
 
2580
+ def test_memory_ingest_request_temperature_uses_pydantic_float_coercion() -> None:
2581
+ request_model = room_server_client._MemoryIngestTextRequest
2582
+
2583
+ assert (
2584
+ request_model.model_validate(
2585
+ {"name": "graph", "text": "Alice", "llm_temperature": "0.4"}
2586
+ ).llm_temperature
2587
+ == 0.4
2588
+ )
2589
+ assert (
2590
+ request_model.model_validate(
2591
+ {"name": "graph", "text": "Alice", "llm_temperature": 1}
2592
+ ).llm_temperature
2593
+ == 1.0
2594
+ )
2595
+ assert (
2596
+ request_model.model_validate(
2597
+ {"name": "graph", "text": "Alice", "llm_temperature": True}
2598
+ ).llm_temperature
2599
+ == 1.0
2600
+ )
2601
+ assert (
2602
+ request_model.model_validate(
2603
+ {"name": "graph", "text": "Alice", "llm_temperature": None}
2604
+ ).llm_temperature
2605
+ is None
2606
+ )
2607
+
2608
+ with pytest.raises(ValidationError):
2609
+ request_model.model_validate(
2610
+ {"name": "graph", "text": "Alice", "llm_temperature": ""}
2611
+ )
2612
+
2613
+
2614
+ def test_memory_integer_limits_use_pydantic_int_coercion() -> None:
2615
+ ingest_request_model = room_server_client._MemoryIngestFromTableRequest
2616
+ recall_request_model = room_server_client._MemoryRecallRequest
2617
+
2618
+ for value in ["5", "5.0", 5.0, True, False, -1, "-1.0", None]:
2619
+ assert ingest_request_model.model_validate(
2620
+ {"name": "graph", "table": "Entity", "limit": value}
2621
+ ).limit == (value if value is None else int(float(value)))
2622
+
2623
+ for value in ["5", "5.0", 5.0, True, False, -1, "-1.0"]:
2624
+ assert recall_request_model.model_validate(
2625
+ {"name": "graph", "query": "Alice", "limit": value}
2626
+ ).limit == int(float(value))
2627
+
2628
+ with pytest.raises(ValidationError):
2629
+ ingest_request_model.model_validate(
2630
+ {"name": "graph", "table": "Entity", "limit": "5.5"}
2631
+ )
2632
+ with pytest.raises(ValidationError):
2633
+ recall_request_model.model_validate(
2634
+ {"name": "graph", "query": "Alice", "limit": None}
2635
+ )
2636
+
2637
+
2580
2638
  @pytest.mark.asyncio
2581
2639
  async def test_memory_client_uses_room_invoke_for_commands() -> None:
2582
2640
  class _FakeMemoryRoom(_FakeRoom):
@@ -3644,6 +3702,20 @@ async def test_sqlite_client_unexpected_response_uses_error_code() -> None:
3644
3702
 
3645
3703
  @pytest.mark.asyncio
3646
3704
  async def test_sqlite_client_validates_typed_responses() -> None:
3705
+ async def assert_unexpected_response(call: Callable[[], object]) -> None:
3706
+ with pytest.raises(
3707
+ RoomException,
3708
+ match=r"unexpected return type from sqlite\.",
3709
+ ) as ex:
3710
+ result = call()
3711
+ if hasattr(result, "__await__"):
3712
+ await result
3713
+ else:
3714
+ async for _ in result: # type: ignore[union-attr]
3715
+ pass
3716
+
3717
+ assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
3718
+
3647
3719
  malformed_databases_client = SqliteClient(
3648
3720
  room=_StaticInvokeRoom(JsonContent(json={"databases": ["app", 2]}))
3649
3721
  ) # type: ignore[arg-type]
@@ -3666,6 +3738,26 @@ async def test_sqlite_client_validates_typed_responses() -> None:
3666
3738
 
3667
3739
  assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
3668
3740
 
3741
+ malformed_inspect_database_client = SqliteClient(
3742
+ room=_StaticInvokeRoom(
3743
+ JsonContent(
3744
+ json={
3745
+ "name": "app",
3746
+ "namespace": ["team"],
3747
+ "tables": "1",
3748
+ "size_bytes": 1024,
3749
+ }
3750
+ )
3751
+ )
3752
+ ) # type: ignore[arg-type]
3753
+
3754
+ with pytest.raises(
3755
+ RoomException, match="unexpected return type from sqlite.inspect_database"
3756
+ ) as ex:
3757
+ await malformed_inspect_database_client.inspect_database(name="app")
3758
+
3759
+ assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
3760
+
3669
3761
  malformed_query_client = SqliteClient(
3670
3762
  room=_StaticInvokeRoom(
3671
3763
  BinaryContent(
@@ -3687,6 +3779,147 @@ async def test_sqlite_client_validates_typed_responses() -> None:
3687
3779
 
3688
3780
  assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
3689
3781
 
3782
+ await assert_unexpected_response(
3783
+ lambda: (
3784
+ SqliteClient( # type: ignore[arg-type]
3785
+ room=_StaticInvokeRoom(JsonContent(json={"count": "1"}))
3786
+ )
3787
+ .database("app")
3788
+ .count(table="records")
3789
+ )
3790
+ )
3791
+ await assert_unexpected_response(
3792
+ lambda: (
3793
+ SqliteClient( # type: ignore[arg-type]
3794
+ room=_StaticInvokeRoom(JsonContent(json={"rows_affected": "2"}))
3795
+ )
3796
+ .database("app")
3797
+ .update(table="records", where="id = ?", values={"name": "x"})
3798
+ )
3799
+ )
3800
+ await assert_unexpected_response(
3801
+ lambda: (
3802
+ SqliteClient( # type: ignore[arg-type]
3803
+ room=_StaticInvokeRoom(JsonContent(json={"rows_affected": "2"}))
3804
+ )
3805
+ .database("app")
3806
+ .delete(table="records", where="id = ?")
3807
+ )
3808
+ )
3809
+ await assert_unexpected_response(
3810
+ lambda: (
3811
+ SqliteClient( # type: ignore[arg-type]
3812
+ room=_StaticInvokeRoom(
3813
+ JsonContent(json={"kind": "statement", "rows_affected": "3"})
3814
+ )
3815
+ )
3816
+ .database("app")
3817
+ .execute_sql(query="DELETE FROM records")
3818
+ )
3819
+ )
3820
+ await assert_unexpected_response(
3821
+ lambda: (
3822
+ SqliteClient( # type: ignore[arg-type]
3823
+ room=_StaticInvokeRoom(JsonContent(json={"rows_affected": "3"}))
3824
+ )
3825
+ .database("app")
3826
+ .execute_sql_statement(query="DELETE FROM records")
3827
+ )
3828
+ )
3829
+ await assert_unexpected_response(
3830
+ lambda: SqliteClient( # type: ignore[arg-type]
3831
+ room=_StaticInvokeRoom(JsonContent(json={"status": "bad"}))
3832
+ ).cancel_sql_query(query_id="sqlite-query-1")
3833
+ )
3834
+ await assert_unexpected_response(
3835
+ lambda: (
3836
+ SqliteClient( # type: ignore[arg-type]
3837
+ room=_StaticInvokeRoom(
3838
+ BinaryContent(
3839
+ data=room_server_client._schema_to_arrow_ipc(
3840
+ pa.schema([pa.field("id", pa.int64())])
3841
+ ),
3842
+ headers={"kind": "query", "query_id": 123},
3843
+ )
3844
+ )
3845
+ )
3846
+ .database("app")
3847
+ .execute_sql(query="SELECT id FROM records")
3848
+ )
3849
+ )
3850
+
3851
+
3852
+ @pytest.mark.asyncio
3853
+ async def test_sqlite_client_propagates_stream_errors_and_rejects_malformed_chunks() -> (
3854
+ None
3855
+ ):
3856
+ class _StaticStreamInvokeRoom:
3857
+ def __init__(self, chunks: list[Content]):
3858
+ self.chunks = chunks
3859
+
3860
+ async def invoke(self, **kwargs) -> AsyncIterable[Content]:
3861
+ del kwargs
3862
+
3863
+ async def stream() -> AsyncIterator[Content]:
3864
+ for chunk in self.chunks:
3865
+ yield chunk
3866
+
3867
+ return stream()
3868
+
3869
+ malformed_write_client = SqliteClient(
3870
+ room=_StaticStreamInvokeRoom(
3871
+ [BinaryContent(data=b"", headers={"kind": "data"})]
3872
+ )
3873
+ ) # type: ignore[arg-type]
3874
+
3875
+ with pytest.raises(
3876
+ RoomException, match="unexpected return type from sqlite.create_table"
3877
+ ) as ex:
3878
+ await malformed_write_client.database("app").create_table_from_data(
3879
+ name="records",
3880
+ data=pa.table({"id": [1]}),
3881
+ )
3882
+
3883
+ assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
3884
+
3885
+ write_error_client = SqliteClient(
3886
+ room=_StaticStreamInvokeRoom(
3887
+ [ErrorContent(text="write failed", code=ErrorCode.INVALID_REQUEST)]
3888
+ )
3889
+ ) # type: ignore[arg-type]
3890
+
3891
+ with pytest.raises(RoomException, match="write failed") as ex:
3892
+ await write_error_client.database("app").insert(
3893
+ table="records",
3894
+ records=pa.table({"id": [1]}),
3895
+ )
3896
+
3897
+ assert ex.value.code == ErrorCode.INVALID_REQUEST
3898
+
3899
+ malformed_read_client = SqliteClient(
3900
+ room=_StaticStreamInvokeRoom(
3901
+ [BinaryContent(data=b"", headers={"kind": "pull"})]
3902
+ )
3903
+ ) # type: ignore[arg-type]
3904
+
3905
+ with pytest.raises(
3906
+ RoomException, match="unexpected return type from sqlite.search"
3907
+ ) as ex:
3908
+ await malformed_read_client.database("app").search(table="records")
3909
+
3910
+ assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
3911
+
3912
+ read_error_client = SqliteClient(
3913
+ room=_StaticStreamInvokeRoom(
3914
+ [ErrorContent(text="read failed", code=ErrorCode.INVALID_REQUEST)]
3915
+ )
3916
+ ) # type: ignore[arg-type]
3917
+
3918
+ with pytest.raises(RoomException, match="read failed") as ex:
3919
+ await read_error_client.database("app").search(table="records")
3920
+
3921
+ assert ex.value.code == ErrorCode.INVALID_REQUEST
3922
+
3690
3923
 
3691
3924
  @pytest.mark.asyncio
3692
3925
  @pytest.mark.parametrize(
@@ -4112,7 +4345,7 @@ async def test_services_client_unexpected_response_uses_error_code() -> None:
4112
4345
  with pytest.raises(
4113
4346
  RoomException, match="unexpected return type from services.list"
4114
4347
  ) as ex:
4115
- await client.list_with_state()
4348
+ await client.list()
4116
4349
 
4117
4350
  assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
4118
4351
 
@@ -4174,7 +4407,7 @@ async def test_services_client_uses_room_invoke_and_translates_service_states()
4174
4407
  room = _InvokeRoom()
4175
4408
  client = ServicesClient(room=room) # type: ignore[arg-type]
4176
4409
 
4177
- result = await client.list_with_state()
4410
+ result = await client.list()
4178
4411
 
4179
4412
  assert room.calls == [
4180
4413
  {
@@ -4212,7 +4445,7 @@ async def test_services_client_defaults_missing_service_events() -> None:
4212
4445
  )
4213
4446
  ) # type: ignore[arg-type]
4214
4447
 
4215
- result = await client.list_with_state()
4448
+ result = await client.list()
4216
4449
 
4217
4450
  assert result.service_states["svc-1"].restart_count == 0
4218
4451
  assert result.service_states["svc-1"].last_start_error is None
@@ -4288,7 +4521,7 @@ async def test_services_client_rejects_nested_service_spec_invalid_payloads() ->
4288
4521
  with pytest.raises(
4289
4522
  RoomException, match="unexpected return type from services.list"
4290
4523
  ) as ex:
4291
- await client.list_with_state()
4524
+ await client.list()
4292
4525
 
4293
4526
  assert ex.value.code == ErrorCode.UNEXPECTED_RESPONSE_TYPE
4294
4527
 
@@ -96,7 +96,7 @@ class DocumentRuntime(AbstractContextManager):
96
96
  self._docs[doc.id] = doc
97
97
 
98
98
  def send_update_to_backend(bytes: str):
99
- runtime.on_document_sync(
99
+ self.on_document_sync(
100
100
  document_id=doc.id, base64=b64.b64encode(bytes).decode()
101
101
  )
102
102