prefactor-core 0.2.3__tar.gz → 0.2.5__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 (33) hide show
  1. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/.gitignore +1 -0
  2. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/PKG-INFO +8 -4
  3. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/README.md +6 -2
  4. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/examples/agent_e2e.py +20 -6
  5. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/pyproject.toml +1 -1
  6. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/_version.py +1 -1
  7. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/client.py +10 -3
  8. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/managers/agent_instance.py +26 -5
  9. prefactor_core-0.2.5/tests/test_agent_instance_finish_status.py +104 -0
  10. prefactor_core-0.2.5/tests/test_agent_instance_register.py +123 -0
  11. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_sdk_header.py +2 -2
  12. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/__init__.py +0 -0
  13. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/config.py +0 -0
  14. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/context_stack.py +0 -0
  15. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/exceptions.py +0 -0
  16. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/managers/__init__.py +0 -0
  17. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/managers/span.py +0 -0
  18. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/models.py +0 -0
  19. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/operations.py +0 -0
  20. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/__init__.py +0 -0
  21. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/base.py +0 -0
  22. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/executor.py +0 -0
  23. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/queue/memory.py +0 -0
  24. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/schema_registry.py +0 -0
  25. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/span_context.py +0 -0
  26. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/src/prefactor_core/utils.py +0 -0
  27. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_client.py +0 -0
  28. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_failure_handling.py +0 -0
  29. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_imports.py +0 -0
  30. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_queue.py +0 -0
  31. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_span_context.py +0 -0
  32. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_span_manager.py +0 -0
  33. {prefactor_core-0.2.3 → prefactor_core-0.2.5}/tests/test_utils.py +0 -0
@@ -70,3 +70,4 @@ htmlcov/
70
70
  .env
71
71
  mise.local.toml
72
72
  prefactor.json
73
+ .worktrees/
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefactor-core
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Core Prefactor SDK with async queue-based operations
5
5
  Author-email: Prefactor Pty Ltd <josh@prefactor.tech>
6
6
  License: MIT
7
7
  Requires-Python: <4.0.0,>=3.11.0
8
- Requires-Dist: prefactor-http>=0.1.1
8
+ Requires-Dist: prefactor-http>=0.1.3
9
9
  Requires-Dist: pydantic>=2.0.0
10
10
  Description-Content-Type: text/markdown
11
11
 
@@ -81,6 +81,10 @@ async def main():
81
81
  asyncio.run(main())
82
82
  ```
83
83
 
84
+ `create_agent_instance()` supports two auth modes:
85
+ - Account-scoped token: pass `agent_id` and usually `environment_id`.
86
+ - Deployment-scoped token: omit `agent_id` and `environment_id`; the API derives both from the token.
87
+
84
88
  ## API Reference
85
89
 
86
90
  ### `PrefactorCoreClient`
@@ -98,9 +102,9 @@ await client.close()
98
102
 
99
103
  ```python
100
104
  handle = await client.create_agent_instance(
101
- agent_id="my-agent",
102
105
  agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
103
106
  agent_schema_version=None, # Optional: auto-generated if schema_registry is configured
107
+ agent_id="my-agent", # Optional for deployment-scoped tokens
104
108
  external_schema_version_id=None, # Optional: reference an existing schema version
105
109
  ) -> AgentInstanceHandle
106
110
  ```
@@ -240,8 +244,8 @@ config = PrefactorCoreConfig(
240
244
  async with PrefactorCoreClient(config) as client:
241
245
  # agent_schema_version is generated automatically from the registry
242
246
  instance = await client.create_agent_instance(
243
- agent_id="my-agent",
244
247
  agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
248
+ agent_id="my-agent", # Optional for deployment-scoped tokens
245
249
  )
246
250
  ```
247
251
 
@@ -70,6 +70,10 @@ async def main():
70
70
  asyncio.run(main())
71
71
  ```
72
72
 
73
+ `create_agent_instance()` supports two auth modes:
74
+ - Account-scoped token: pass `agent_id` and usually `environment_id`.
75
+ - Deployment-scoped token: omit `agent_id` and `environment_id`; the API derives both from the token.
76
+
73
77
  ## API Reference
74
78
 
75
79
  ### `PrefactorCoreClient`
@@ -87,9 +91,9 @@ await client.close()
87
91
 
88
92
  ```python
89
93
  handle = await client.create_agent_instance(
90
- agent_id="my-agent",
91
94
  agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
92
95
  agent_schema_version=None, # Optional: auto-generated if schema_registry is configured
96
+ agent_id="my-agent", # Optional for deployment-scoped tokens
93
97
  external_schema_version_id=None, # Optional: reference an existing schema version
94
98
  ) -> AgentInstanceHandle
95
99
  ```
@@ -229,8 +233,8 @@ config = PrefactorCoreConfig(
229
233
  async with PrefactorCoreClient(config) as client:
230
234
  # agent_schema_version is generated automatically from the registry
231
235
  instance = await client.create_agent_instance(
232
- agent_id="my-agent",
233
236
  agent_version={"name": "My Agent", "external_identifier": "v1.0.0"},
237
+ agent_id="my-agent", # Optional for deployment-scoped tokens
234
238
  )
235
239
  ```
236
240
 
@@ -24,8 +24,11 @@ Run via mise (env vars set automatically):
24
24
  Or manually:
25
25
  PREFACTOR_API_URL=https://api.prefactor.ai \\
26
26
  PREFACTOR_API_TOKEN=your-token \\
27
- PREFACTOR_AGENT_ID=your-agent-id \\
28
27
  python packages/core/examples/agent_e2e.py
28
+
29
+ Set `PREFACTOR_AGENT_ID` only when using an account-scoped token. With a
30
+ deployment-scoped token, leave it unset and the API derives the agent and
31
+ environment from the token.
29
32
  """
30
33
 
31
34
  import asyncio
@@ -182,7 +185,7 @@ async def simulate_retrieval(query: str) -> list[str]:
182
185
 
183
186
 
184
187
  async def main() -> None:
185
- agent_id = os.environ["PREFACTOR_AGENT_ID"]
188
+ agent_id = os.environ.get("PREFACTOR_AGENT_ID")
186
189
 
187
190
  config = PrefactorCoreConfig(
188
191
  http_config=HttpClientConfig(
@@ -194,10 +197,21 @@ async def main() -> None:
194
197
 
195
198
  async with PrefactorCoreClient(config) as client:
196
199
  # Register the agent instance — schema version derived from registry.
197
- instance = await client.create_agent_instance(
198
- agent_id=agent_id,
199
- agent_version={"name": "Example Agent", "external_identifier": "v8.0.0"},
200
- )
200
+ if agent_id is None:
201
+ instance = await client.create_agent_instance(
202
+ agent_version={
203
+ "name": "Example Agent",
204
+ "external_identifier": "v8.0.0",
205
+ }
206
+ )
207
+ else:
208
+ instance = await client.create_agent_instance(
209
+ agent_id=agent_id,
210
+ agent_version={
211
+ "name": "Example Agent",
212
+ "external_identifier": "v8.0.0",
213
+ },
214
+ )
201
215
 
202
216
  await instance.start()
203
217
 
@@ -9,7 +9,7 @@ authors = [
9
9
  ]
10
10
  requires-python = ">=3.11.0, <4.0.0"
11
11
  dependencies = [
12
- "prefactor-http>=0.1.1",
12
+ "prefactor-http>=0.1.3",
13
13
  "pydantic>=2.0.0",
14
14
  ]
15
15
 
@@ -3,5 +3,5 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  PACKAGE_NAME = "prefactor-core"
6
- __version__ = "0.2.3"
6
+ __version__ = "0.2.5"
7
7
  PACKAGE_VERSION = __version__
@@ -274,6 +274,7 @@ class PrefactorCoreClient:
274
274
  elif operation.type == OperationType.FINISH_AGENT_INSTANCE:
275
275
  await self._http.agent_instances.finish(
276
276
  agent_instance_id=operation.payload["instance_id"],
277
+ status=operation.payload.get("status", "complete"),
277
278
  timestamp=operation.timestamp,
278
279
  idempotency_key=operation.payload.get("idempotency_key"),
279
280
  )
@@ -313,11 +314,12 @@ class PrefactorCoreClient:
313
314
 
314
315
  async def create_agent_instance(
315
316
  self,
316
- agent_id: str,
317
317
  agent_version: dict[str, Any],
318
318
  agent_schema_version: dict[str, Any] | None = None,
319
+ agent_id: str | None = None,
319
320
  instance_id: str | None = None,
320
321
  external_schema_version_id: str | None = None,
322
+ environment_id: str | None = None,
321
323
  ) -> "AgentInstanceHandle":
322
324
  """Create a new agent instance.
323
325
 
@@ -328,13 +330,14 @@ class PrefactorCoreClient:
328
330
  the registry's schemas will be used automatically.
329
331
 
330
332
  Args:
331
- agent_id: ID of the agent to create an instance for.
333
+ agent_id: Agent ID. Omit when using a deployment-scoped token.
332
334
  agent_version: Version information (name, etc.).
333
335
  agent_schema_version: Schema version. Uses registry if not provided
334
336
  and registry is configured.
335
337
  instance_id: Optional custom ID for the instance.
336
338
  external_schema_version_id: Optional external identifier for the
337
339
  schema version. Defaults to "auto-generated" when using registry.
340
+ environment_id: Optional environment ID used to scope the agent instance.
338
341
 
339
342
  Returns:
340
343
  AgentInstanceHandle for the created instance.
@@ -355,7 +358,10 @@ class PrefactorCoreClient:
355
358
  # Use registry to generate schema version
356
359
  ext_id = external_schema_version_id
357
360
  if ext_id is None:
358
- ext_id = f"auto-{agent_id}-{time.time()}"
361
+ if agent_id is None:
362
+ ext_id = f"auto-deployment-{time.time()}"
363
+ else:
364
+ ext_id = f"auto-{agent_id}-{time.time()}"
359
365
  final_schema_version = self._config.schema_registry.to_agent_schema_version(
360
366
  ext_id
361
367
  )
@@ -372,6 +378,7 @@ class PrefactorCoreClient:
372
378
  agent_version=agent_version,
373
379
  agent_schema_version=final_schema_version,
374
380
  instance_id=instance_id,
381
+ environment_id=environment_id,
375
382
  )
376
383
 
377
384
  return AgentInstanceHandle(
@@ -14,6 +14,7 @@ from ..utils import generate_idempotency_key
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from prefactor_http.client import PrefactorHttpClient
17
+ from prefactor_http.models.types import FinishStatus
17
18
 
18
19
  from ..client import PrefactorCoreClient
19
20
 
@@ -58,10 +59,11 @@ class AgentInstanceManager:
58
59
 
59
60
  async def register(
60
61
  self,
61
- agent_id: str,
62
62
  agent_version: dict[str, Any],
63
63
  agent_schema_version: dict[str, Any],
64
+ agent_id: str | None = None,
64
65
  instance_id: str | None = None,
66
+ environment_id: str | None = None,
65
67
  ) -> str:
66
68
  """Register a new agent instance.
67
69
 
@@ -69,12 +71,14 @@ class AgentInstanceManager:
69
71
  the API-generated ID.
70
72
 
71
73
  Args:
72
- agent_id: ID of the agent to create an instance for.
74
+ agent_id: Agent ID. Omit when using a deployment-scoped token.
73
75
  agent_version: Version information (name, external_identifier, etc.).
74
76
  agent_schema_version: Schema version information.
75
77
  instance_id: Optional ID to forward to the API as ``id``. When
76
78
  provided, the API uses it as the instance ID; when omitted,
77
79
  the API generates one.
80
+ environment_id: Optional environment ID. Required when using an
81
+ account-scoped token; omit when using a deployment-scoped token.
78
82
 
79
83
  Returns:
80
84
  The instance ID (API-generated).
@@ -83,6 +87,7 @@ class AgentInstanceManager:
83
87
  agent_id=agent_id,
84
88
  agent_version=agent_version,
85
89
  agent_schema_version=agent_schema_version,
90
+ environment_id=environment_id,
86
91
  id=instance_id,
87
92
  idempotency_key=generate_idempotency_key(),
88
93
  )
@@ -115,20 +120,30 @@ class AgentInstanceManager:
115
120
 
116
121
  await self._enqueue(operation)
117
122
 
118
- async def finish(self, instance_id: str) -> None:
123
+ async def finish(
124
+ self,
125
+ instance_id: str,
126
+ status: "FinishStatus" = "complete",
127
+ ) -> None:
119
128
  """Mark an instance as finished.
120
129
 
121
130
  Queues a finish operation for the instance.
122
131
 
123
132
  Args:
124
133
  instance_id: The ID of the instance to finish.
134
+ status: Terminal status for the instance. Defaults to ``"complete"``.
125
135
  """
126
- await self.finish_with_idempotency_key(instance_id, generate_idempotency_key())
136
+ await self.finish_with_idempotency_key(
137
+ instance_id,
138
+ generate_idempotency_key(),
139
+ status=status,
140
+ )
127
141
 
128
142
  async def finish_with_idempotency_key(
129
143
  self,
130
144
  instance_id: str,
131
145
  idempotency_key: str,
146
+ status: "FinishStatus" = "complete",
132
147
  ) -> None:
133
148
  """Queue a finish operation using a stable idempotency key."""
134
149
  operation = Operation(
@@ -136,6 +151,7 @@ class AgentInstanceManager:
136
151
  payload={
137
152
  "instance_id": instance_id,
138
153
  "idempotency_key": idempotency_key,
154
+ "status": status,
139
155
  },
140
156
  timestamp=datetime.now(timezone.utc),
141
157
  )
@@ -199,16 +215,21 @@ class AgentInstanceHandle:
199
215
  self._start_idempotency_key,
200
216
  )
201
217
 
202
- async def finish(self) -> None:
218
+ async def finish(self, status: "FinishStatus" = "complete") -> None:
203
219
  """Mark the instance as finished.
204
220
 
205
221
  This queues a finish operation for the instance.
222
+
223
+ Args:
224
+ status: Terminal status for the instance — one of ``"complete"``,
225
+ ``"failed"``, or ``"cancelled"``. Defaults to ``"complete"``.
206
226
  """
207
227
  manager = self._client.instance_manager
208
228
  assert manager is not None
209
229
  await manager.finish_with_idempotency_key(
210
230
  self._instance_id,
211
231
  self._finish_idempotency_key,
232
+ status=status,
212
233
  )
213
234
 
214
235
  async def create_span(
@@ -0,0 +1,104 @@
1
+ """Tests for AgentInstanceHandle.finish() accepting an optional status.
2
+
3
+ See GitHub issue prefactordev/python-sdk#13.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from types import SimpleNamespace
9
+ from unittest.mock import patch
10
+
11
+ import pytest
12
+ from prefactor_core import PrefactorCoreClient
13
+ from prefactor_core.config import PrefactorCoreConfig, QueueConfig
14
+ from prefactor_http.config import HttpClientConfig
15
+
16
+
17
+ class _StubAgentInstances:
18
+ def __init__(self) -> None:
19
+ self.finish_calls: list[dict] = []
20
+
21
+ async def register(self, **kwargs):
22
+ return SimpleNamespace(id=kwargs.get("id") or "inst-1")
23
+
24
+ async def start(self, **kwargs):
25
+ return SimpleNamespace(id=kwargs["agent_instance_id"])
26
+
27
+ async def finish(self, **kwargs):
28
+ self.finish_calls.append(kwargs)
29
+ return SimpleNamespace(id=kwargs["agent_instance_id"])
30
+
31
+
32
+ class _StubAgentSpans:
33
+ async def create(self, **kwargs):
34
+ return SimpleNamespace(id="span-1")
35
+
36
+ async def finish(self, **kwargs):
37
+ return SimpleNamespace(id=kwargs["agent_span_id"])
38
+
39
+
40
+ class _StubHttpClient:
41
+ def __init__(
42
+ self,
43
+ *_args,
44
+ agent_instances: _StubAgentInstances | None = None,
45
+ **_kwargs,
46
+ ) -> None:
47
+ self.agent_instances = agent_instances or _StubAgentInstances()
48
+ self.agent_spans = _StubAgentSpans()
49
+
50
+ async def __aenter__(self):
51
+ return self
52
+
53
+ async def __aexit__(self, exc_type, exc, tb):
54
+ return None
55
+
56
+
57
+ def _make_client_config() -> PrefactorCoreConfig:
58
+ return PrefactorCoreConfig(
59
+ http_config=HttpClientConfig(
60
+ api_url="https://api.test.com",
61
+ api_token="test-token",
62
+ max_retries=0,
63
+ initial_retry_delay=0.01,
64
+ max_retry_delay=0.02,
65
+ ),
66
+ queue_config=QueueConfig(num_workers=1, max_retries=0),
67
+ )
68
+
69
+
70
+ @pytest.mark.asyncio
71
+ async def test_finish_without_status_defaults_to_complete():
72
+ """Default call to finish() should forward status='complete' to HTTP."""
73
+ stub_http = _StubHttpClient()
74
+
75
+ with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
76
+ async with PrefactorCoreClient(_make_client_config()) as client:
77
+ instance = await client.create_agent_instance(
78
+ agent_id="agent-1",
79
+ agent_version={"name": "v1"},
80
+ agent_schema_version={"span_schemas": {}},
81
+ )
82
+ await instance.finish()
83
+
84
+ assert len(stub_http.agent_instances.finish_calls) == 1
85
+ assert stub_http.agent_instances.finish_calls[0].get("status") == "complete"
86
+
87
+
88
+ @pytest.mark.asyncio
89
+ @pytest.mark.parametrize("status", ["failed", "cancelled", "complete"])
90
+ async def test_finish_forwards_explicit_status(status):
91
+ """Explicit status on handle.finish() should reach the HTTP layer."""
92
+ stub_http = _StubHttpClient()
93
+
94
+ with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
95
+ async with PrefactorCoreClient(_make_client_config()) as client:
96
+ instance = await client.create_agent_instance(
97
+ agent_id="agent-1",
98
+ agent_version={"name": "v1"},
99
+ agent_schema_version={"span_schemas": {}},
100
+ )
101
+ await instance.finish(status=status)
102
+
103
+ assert len(stub_http.agent_instances.finish_calls) == 1
104
+ assert stub_http.agent_instances.finish_calls[0]["status"] == status
@@ -0,0 +1,123 @@
1
+ """Tests for environment_id threading through the core register flow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from types import SimpleNamespace
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+ from prefactor_core import PrefactorCoreClient
10
+ from prefactor_core.config import PrefactorCoreConfig, QueueConfig
11
+ from prefactor_http.config import HttpClientConfig
12
+
13
+
14
+ def _make_config() -> PrefactorCoreConfig:
15
+ return PrefactorCoreConfig(
16
+ http_config=HttpClientConfig(
17
+ api_url="https://api.test.com",
18
+ api_token="test-token",
19
+ max_retries=0,
20
+ ),
21
+ queue_config=QueueConfig(num_workers=1, max_retries=0),
22
+ )
23
+
24
+
25
+ class _StubAgentInstances:
26
+ def __init__(self) -> None:
27
+ self.register_calls: list[dict] = []
28
+
29
+ async def register(self, **kwargs):
30
+ self.register_calls.append(kwargs)
31
+ return SimpleNamespace(id="inst-1")
32
+
33
+ async def start(self, **kwargs):
34
+ return SimpleNamespace(id=kwargs["agent_instance_id"])
35
+
36
+ async def finish(self, **kwargs):
37
+ return SimpleNamespace(id=kwargs["agent_instance_id"])
38
+
39
+
40
+ class _StubAgentSpans:
41
+ async def create(self, **kwargs):
42
+ return SimpleNamespace(id="span-1")
43
+
44
+ async def finish(self, **kwargs):
45
+ return SimpleNamespace(id=kwargs["agent_span_id"])
46
+
47
+
48
+ class _StubHttpClient:
49
+ def __init__(self, *args, **kwargs) -> None:
50
+ self.agent_instances = _StubAgentInstances()
51
+ self.agent_spans = _StubAgentSpans()
52
+
53
+ async def __aenter__(self):
54
+ return self
55
+
56
+ async def __aexit__(self, exc_type, exc, tb):
57
+ return None
58
+
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_create_agent_instance_passes_environment_id_to_register():
62
+ """environment_id passed to create_agent_instance() reaches the HTTP layer."""
63
+ stub_http = _StubHttpClient()
64
+ with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
65
+ async with PrefactorCoreClient(_make_config()) as client:
66
+ await client.create_agent_instance(
67
+ agent_id="agent-1",
68
+ agent_version={"name": "v1"},
69
+ agent_schema_version={"span_schemas": {}},
70
+ environment_id="env-1",
71
+ )
72
+ assert stub_http.agent_instances.register_calls[0]["environment_id"] == "env-1"
73
+
74
+
75
+ @pytest.mark.asyncio
76
+ async def test_create_agent_instance_without_environment_id_omits_field():
77
+ """Omitting environment_id does not send it to the HTTP register call."""
78
+ stub_http = _StubHttpClient()
79
+ with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
80
+ async with PrefactorCoreClient(_make_config()) as client:
81
+ await client.create_agent_instance(
82
+ agent_id="agent-1",
83
+ agent_version={"name": "v1"},
84
+ agent_schema_version={"span_schemas": {}},
85
+ )
86
+ call = stub_http.agent_instances.register_calls[0]
87
+ assert "environment_id" not in call or call["environment_id"] is None
88
+
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_create_agent_instance_without_agent_id_omits_field():
92
+ """Omitting agent_id does not send it to the HTTP register call."""
93
+ stub_http = _StubHttpClient()
94
+ with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
95
+ async with PrefactorCoreClient(_make_config()) as client:
96
+ await client.create_agent_instance(
97
+ agent_version={"name": "v1"},
98
+ agent_schema_version={"span_schemas": {}},
99
+ )
100
+ call = stub_http.agent_instances.register_calls[0]
101
+ assert "agent_id" not in call or call["agent_id"] is None
102
+
103
+
104
+ @pytest.mark.asyncio
105
+ async def test_create_agent_instance_without_agent_id_uses_deployment_schema_fallback():
106
+ """Registry fallback should not require agent_id to generate an external ID."""
107
+ stub_http = _StubHttpClient()
108
+ config = _make_config()
109
+ config.schema_registry = SimpleNamespace(
110
+ to_agent_schema_version=lambda external_id: {"external_identifier": external_id}
111
+ )
112
+
113
+ with patch("prefactor_core.client.PrefactorHttpClient", return_value=stub_http):
114
+ async with PrefactorCoreClient(config) as client:
115
+ await client.create_agent_instance(
116
+ agent_version={"name": "v1"},
117
+ agent_schema_version=None,
118
+ )
119
+
120
+ call = stub_http.agent_instances.register_calls[0]
121
+ assert call["agent_schema_version"]["external_identifier"].startswith(
122
+ "auto-deployment-"
123
+ )
@@ -47,7 +47,7 @@ class TestPrefactorCoreSdkHeader:
47
47
  """Core prepends the adapter entry ahead of the core entry."""
48
48
  client = PrefactorCoreClient(
49
49
  create_config(),
50
- sdk_header_entry="prefactor-langchain@0.2.4",
50
+ sdk_header_entry="prefactor-langchain@0.2.5",
51
51
  )
52
52
 
53
53
  with (
@@ -63,7 +63,7 @@ class TestPrefactorCoreSdkHeader:
63
63
  await client.initialize()
64
64
  assert client._http is not None
65
65
  assert client._http._sdk_header == (
66
- f"prefactor-langchain@0.2.4 {CORE_SDK_HEADER_ENTRY}"
66
+ f"prefactor-langchain@0.2.5 {CORE_SDK_HEADER_ENTRY}"
67
67
  )
68
68
  await client.close()
69
69