mainsequence 6.0.45__tar.gz → 6.0.47__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.
- {mainsequence-6.0.45/mainsequence.egg-info → mainsequence-6.0.47}/PKG-INFO +1 -1
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +48 -5
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/api.py +4 -4
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/cli.py +12 -9
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/agent_runtime_models.py +304 -8
- {mainsequence-6.0.45 → mainsequence-6.0.47/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-6.0.45 → mainsequence-6.0.47}/pyproject.toml +1 -1
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_cli.py +1 -1
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_filter_normalization.py +202 -3
- {mainsequence-6.0.45 → mainsequence-6.0.47}/LICENSE +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/README.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/__main__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/bootstrap.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/config.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/migrations.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/cli/ui.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/base.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/client.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/metatables/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/metatables/core.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/models_user.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/client/utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/defaults.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/logconf.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/alembic.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/env.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/provider.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/registry.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/schema_names.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/project_context.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/project_skills.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence/scaffold_skills.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/setup.cfg +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_auth_precedence.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_canonical_api_route_contract.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_cli_migrations.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_client.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_dependency_extras.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_environment_resource_context.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_environment_response_contracts.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_instrumentation.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_local_ops.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_logconf.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_meta_table_migrations.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_meta_tables_client_models.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_notification_uid_contract.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_project_branch_contract.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_project_runtime_context.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_project_skills.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_run_configuration.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_scaffold_skills.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_scheduler_client_model.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_schema_names.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_secret_client_model.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_ssh_utils.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_update_statistics.py +0 -0
- {mainsequence-6.0.45 → mainsequence-6.0.47}/tests/test_update_uid_guards.py +0 -0
{mainsequence-6.0.45 → mainsequence-6.0.47}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md
RENAMED
|
@@ -30,6 +30,9 @@ internally and sends a standard A2A message.
|
|
|
30
30
|
- Do not expose runtime credentials in prompts, logs, or user-facing answers.
|
|
31
31
|
- Do not use commands that require both target agent UID and target session UID for chat.
|
|
32
32
|
- Use `--strict-dictionary` when the target answer must be machine-parseable JSON.
|
|
33
|
+
- Read `a2a_profile` from discovery, default to `message`, and never infer Task
|
|
34
|
+
support.
|
|
35
|
+
- Use `--response-kind task` only when the selected Agent advertises `task`.
|
|
33
36
|
- Do not send or request reasoning/thinking traces; standard A2A responses expose only message parts.
|
|
34
37
|
|
|
35
38
|
## Discovery Flow
|
|
@@ -53,6 +56,7 @@ Agent's environment.
|
|
|
53
56
|
5. Prefer the highest `combined_score` when present.
|
|
54
57
|
6. If the user asked only which agents are available, summarize the candidates and stop.
|
|
55
58
|
Include agent name, agent UID, and a short capability summary.
|
|
59
|
+
7. Preserve the selected Agent's `a2a_profile` for request validation.
|
|
56
60
|
|
|
57
61
|
## Target Session
|
|
58
62
|
|
|
@@ -106,6 +110,15 @@ mainsequence agent session a2a send \
|
|
|
106
110
|
--message "Summarize the current portfolio drift."
|
|
107
111
|
```
|
|
108
112
|
|
|
113
|
+
For advertised asynchronous work, explicitly request a Task:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
mainsequence agent session a2a send \
|
|
117
|
+
<target_agent_session_uid> \
|
|
118
|
+
--message "Run the full analysis asynchronously." \
|
|
119
|
+
--response-kind task
|
|
120
|
+
```
|
|
121
|
+
|
|
109
122
|
For a second message to the same target session, send another chat command
|
|
110
123
|
with the same target `AgentSession` UID:
|
|
111
124
|
|
|
@@ -189,7 +202,11 @@ handle_unique_id -> AgentSession.uid -> A2A message.contextId
|
|
|
189
202
|
Direct SDK flow:
|
|
190
203
|
|
|
191
204
|
```python
|
|
192
|
-
from mainsequence.client.agent_runtime_models import
|
|
205
|
+
from mainsequence.client.agent_runtime_models import (
|
|
206
|
+
A2AResponseKind,
|
|
207
|
+
Agent,
|
|
208
|
+
AgentSession,
|
|
209
|
+
)
|
|
193
210
|
|
|
194
211
|
|
|
195
212
|
agent = Agent.get_by_uid(target_agent_uid)
|
|
@@ -202,7 +219,24 @@ response = AgentSession.send_a2a_message(
|
|
|
202
219
|
session.uid,
|
|
203
220
|
message="Return a JSON object with keys: summary, risk_level, next_action.",
|
|
204
221
|
strict_dictionary=True,
|
|
222
|
+
response_kind=A2AResponseKind.MESSAGE,
|
|
223
|
+
a2a_profile=agent.a2a_profile,
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Use asynchronous Task mode only when discovery advertises it:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
if A2AResponseKind.TASK not in agent.a2a_profile.supported_response_kinds:
|
|
231
|
+
raise ValueError("The selected agent does not advertise A2A Task responses")
|
|
232
|
+
|
|
233
|
+
result = AgentSession.send_a2a_message(
|
|
234
|
+
session,
|
|
235
|
+
message="Run the full analysis asynchronously.",
|
|
236
|
+
response_kind=A2AResponseKind.TASK,
|
|
237
|
+
a2a_profile=agent.a2a_profile,
|
|
205
238
|
)
|
|
239
|
+
task = AgentSession.wait_for_a2a_task(session, task_id=result.task.id)
|
|
206
240
|
```
|
|
207
241
|
|
|
208
242
|
For another message in the same conversation, reuse the same `session.uid`:
|
|
@@ -267,12 +301,19 @@ handle for a different task or conversation.
|
|
|
267
301
|
## Response Handling
|
|
268
302
|
|
|
269
303
|
- Parse the CLI output as the standard A2A JSON response.
|
|
270
|
-
-
|
|
304
|
+
- The default `message` result is direct execution and does not create an A2A
|
|
305
|
+
Task. Consume only its documented `message.parts`.
|
|
306
|
+
- A `task` result is valid only after explicitly selecting `--response-kind
|
|
307
|
+
task` for an Agent whose discovery profile advertises it. Preserve its ID and
|
|
308
|
+
follow it with the SDK task get/wait/cancel helpers.
|
|
309
|
+
- Treat a result kind different from the requested kind as a protocol error.
|
|
271
310
|
- Do not depend on reasoning, tool traces, runtime paths, or transport metadata.
|
|
272
311
|
- If a send times out or disconnects and must be retried, reuse the same
|
|
273
|
-
`--message-id` value
|
|
312
|
+
`--message-id` value to preserve request identity. Direct `message` sends do
|
|
313
|
+
not have durable replay protection, so do not retry automatically: the first
|
|
314
|
+
turn may already have executed. Use `task` when durable recovery is required.
|
|
274
315
|
- If the CLI generated the message id and the send failed, reuse the
|
|
275
|
-
`A2A message id for
|
|
316
|
+
`A2A message id for request retry` value printed with the error.
|
|
276
317
|
|
|
277
318
|
## Deterministic Execution Path
|
|
278
319
|
|
|
@@ -282,7 +323,9 @@ handle for a different task or conversation.
|
|
|
282
323
|
4. Create or resolve the target session with `mainsequence agent session get_or_create`.
|
|
283
324
|
5. Use the returned session `uid`.
|
|
284
325
|
6. Run `mainsequence agent session a2a send <target_agent_session_uid> ...`.
|
|
285
|
-
7.
|
|
326
|
+
7. Inspect the selected Agent's `a2a_profile` and default to `message`.
|
|
327
|
+
8. Parse `message.parts`, or follow the returned Task only when Task mode was
|
|
328
|
+
explicitly selected and advertised.
|
|
286
329
|
|
|
287
330
|
## Role-Specific Behavior
|
|
288
331
|
|
|
@@ -1417,7 +1417,7 @@ def send_agent_session_a2a_message(
|
|
|
1417
1417
|
message_id: str | None = None,
|
|
1418
1418
|
strict_dictionary: bool = False,
|
|
1419
1419
|
json_repair_attempts: int = 3,
|
|
1420
|
-
|
|
1420
|
+
response_kind: str = "message",
|
|
1421
1421
|
timeout: int | None = None,
|
|
1422
1422
|
) -> dict[str, Any]:
|
|
1423
1423
|
"""
|
|
@@ -1437,7 +1437,7 @@ def send_agent_session_a2a_message(
|
|
|
1437
1437
|
cached_runtime_access=cached_runtime_access,
|
|
1438
1438
|
strict_dictionary=strict_dictionary,
|
|
1439
1439
|
json_repair_attempts=json_repair_attempts,
|
|
1440
|
-
|
|
1440
|
+
response_kind=response_kind,
|
|
1441
1441
|
timeout=timeout,
|
|
1442
1442
|
),
|
|
1443
1443
|
)
|
|
@@ -1463,7 +1463,7 @@ def _send_agent_session_a2a_message_with_cache(
|
|
|
1463
1463
|
cached_runtime_access: dict[str, Any] | None,
|
|
1464
1464
|
strict_dictionary: bool,
|
|
1465
1465
|
json_repair_attempts: int,
|
|
1466
|
-
|
|
1466
|
+
response_kind: str,
|
|
1467
1467
|
timeout: int | None,
|
|
1468
1468
|
):
|
|
1469
1469
|
if cached_runtime_access is not None:
|
|
@@ -1476,7 +1476,7 @@ def _send_agent_session_a2a_message_with_cache(
|
|
|
1476
1476
|
message_id=message_id,
|
|
1477
1477
|
strict_dictionary=strict_dictionary,
|
|
1478
1478
|
json_repair_attempts=json_repair_attempts,
|
|
1479
|
-
|
|
1479
|
+
response_kind=response_kind,
|
|
1480
1480
|
timeout=timeout,
|
|
1481
1481
|
)
|
|
1482
1482
|
|
|
@@ -3788,7 +3788,7 @@ def _agent_session_a2a_send_impl(
|
|
|
3788
3788
|
strict_dictionary: bool,
|
|
3789
3789
|
json_repair_attempts: int,
|
|
3790
3790
|
message_id: str | None,
|
|
3791
|
-
|
|
3791
|
+
response_kind: str,
|
|
3792
3792
|
timeout: int | None,
|
|
3793
3793
|
) -> None:
|
|
3794
3794
|
try:
|
|
@@ -3806,6 +3806,9 @@ def _agent_session_a2a_send_impl(
|
|
|
3806
3806
|
if json_repair_attempts < 1:
|
|
3807
3807
|
error("--json-repair-attempts must be greater than 0.")
|
|
3808
3808
|
raise typer.Exit(1)
|
|
3809
|
+
if response_kind not in {"message", "task"}:
|
|
3810
|
+
error("--response-kind must be 'message' or 'task'.")
|
|
3811
|
+
raise typer.Exit(1)
|
|
3809
3812
|
attachment_paths = list(files or [])
|
|
3810
3813
|
attachment_media_types = list(media_types or [])
|
|
3811
3814
|
if attachment_media_types and len(attachment_media_types) != len(attachment_paths):
|
|
@@ -3841,12 +3844,12 @@ def _agent_session_a2a_send_impl(
|
|
|
3841
3844
|
message_id=effective_message_id,
|
|
3842
3845
|
strict_dictionary=strict_dictionary,
|
|
3843
3846
|
json_repair_attempts=json_repair_attempts,
|
|
3844
|
-
|
|
3847
|
+
response_kind=response_kind,
|
|
3845
3848
|
timeout=timeout,
|
|
3846
3849
|
)
|
|
3847
3850
|
except ApiError as e:
|
|
3848
3851
|
error(str(e))
|
|
3849
|
-
error(f"A2A message id for
|
|
3852
|
+
error(f"A2A message id for request retry: {effective_message_id}")
|
|
3850
3853
|
raise typer.Exit(1) from e
|
|
3851
3854
|
|
|
3852
3855
|
typer.echo(json.dumps(response_payload, indent=2))
|
|
@@ -4991,12 +4994,12 @@ def agent_session_a2a_send_cmd(
|
|
|
4991
4994
|
message_id: str | None = typer.Option(
|
|
4992
4995
|
None,
|
|
4993
4996
|
"--message-id",
|
|
4994
|
-
help="Stable A2A message.messageId to
|
|
4997
|
+
help="Stable A2A message.messageId to preserve request identity across a retry.",
|
|
4995
4998
|
),
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
"--
|
|
4999
|
-
help="
|
|
4999
|
+
response_kind: str = typer.Option(
|
|
5000
|
+
"message",
|
|
5001
|
+
"--response-kind",
|
|
5002
|
+
help="Select the A2A result contract: message or task.",
|
|
5000
5003
|
),
|
|
5001
5004
|
timeout: int | None = typer.Option(None, "--timeout", help="Request timeout in seconds"),
|
|
5002
5005
|
):
|
|
@@ -5015,7 +5018,7 @@ def agent_session_a2a_send_cmd(
|
|
|
5015
5018
|
strict_dictionary=strict_dictionary,
|
|
5016
5019
|
json_repair_attempts=json_repair_attempts,
|
|
5017
5020
|
message_id=message_id,
|
|
5018
|
-
|
|
5021
|
+
response_kind=response_kind,
|
|
5019
5022
|
timeout=timeout,
|
|
5020
5023
|
)
|
|
5021
5024
|
|
|
@@ -14,13 +14,18 @@ from pydantic import ConfigDict, Field, model_validator
|
|
|
14
14
|
|
|
15
15
|
from .base import BaseObjectOrm, BasePydanticModel, ShareableObjectMixin
|
|
16
16
|
from .exceptions import ApiError, raise_for_response
|
|
17
|
+
from .models_helpers import AutomaticRedeploymentPolicy
|
|
17
18
|
from .utils import make_request, serialize_to_json
|
|
18
19
|
|
|
19
20
|
DEFAULT_AGENT_SESSION_LONG_REQUEST_TIMEOUT = (5.0, 900.0)
|
|
20
21
|
DEFAULT_AGENT_RUNTIME_ACCESS_CACHE_TTL_SECONDS = 60.0
|
|
21
22
|
DEFAULT_AGENT_RUNTIME_ACCESS_CACHE_EXPIRY_SKEW_SECONDS = 30.0
|
|
22
23
|
STANDARD_A2A_MESSAGE_SEND_PATH = "/api/a2a/v1/message:send"
|
|
24
|
+
STANDARD_A2A_TASKS_PATH = "/api/a2a/v1/tasks"
|
|
23
25
|
STANDARD_A2A_CONTENT_TYPE = "application/a2a+json"
|
|
26
|
+
STANDARD_A2A_RESPONSE_KIND_EXTENSION_URI = (
|
|
27
|
+
"https://mainsequence.ai/a2a/extensions/response-kind/v1"
|
|
28
|
+
)
|
|
24
29
|
STANDARD_A2A_OUTPUT_CONTRACT_METADATA_KEY = (
|
|
25
30
|
"https://mainsequence.ai/a2a/extensions/output-contract/v1"
|
|
26
31
|
)
|
|
@@ -35,6 +40,67 @@ class AgentSessionStatus(str, Enum):
|
|
|
35
40
|
CANCELED = "canceled"
|
|
36
41
|
|
|
37
42
|
|
|
43
|
+
class A2AResponseKind(str, Enum):
|
|
44
|
+
MESSAGE = "message"
|
|
45
|
+
TASK = "task"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AgentA2AProfile(BasePydanticModel):
|
|
49
|
+
response_kind_extension_uri: str = STANDARD_A2A_RESPONSE_KIND_EXTENSION_URI
|
|
50
|
+
supported_response_kinds: list[A2AResponseKind] = Field(
|
|
51
|
+
default_factory=lambda: [A2AResponseKind.MESSAGE]
|
|
52
|
+
)
|
|
53
|
+
default_response_kind: A2AResponseKind = A2AResponseKind.MESSAGE
|
|
54
|
+
|
|
55
|
+
@model_validator(mode="after")
|
|
56
|
+
def _validate_default(self) -> AgentA2AProfile:
|
|
57
|
+
if self.response_kind_extension_uri != STANDARD_A2A_RESPONSE_KIND_EXTENSION_URI:
|
|
58
|
+
raise ValueError("response_kind_extension_uri is not supported")
|
|
59
|
+
self.supported_response_kinds = list(dict.fromkeys(self.supported_response_kinds))
|
|
60
|
+
if not self.supported_response_kinds:
|
|
61
|
+
raise ValueError("supported_response_kinds must not be empty")
|
|
62
|
+
if A2AResponseKind.MESSAGE not in self.supported_response_kinds:
|
|
63
|
+
raise ValueError("supported_response_kinds must include 'message'")
|
|
64
|
+
if self.default_response_kind is not A2AResponseKind.MESSAGE:
|
|
65
|
+
raise ValueError("default_response_kind must be 'message'")
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class A2ATaskStatus(BasePydanticModel):
|
|
70
|
+
model_config = ConfigDict(extra="allow")
|
|
71
|
+
|
|
72
|
+
state: str
|
|
73
|
+
timestamp: str | None = None
|
|
74
|
+
message: Any | None = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class A2ATask(BasePydanticModel):
|
|
78
|
+
model_config = ConfigDict(extra="allow", populate_by_name=True, serialize_by_alias=True)
|
|
79
|
+
|
|
80
|
+
kind: Literal["task"] = "task"
|
|
81
|
+
id: str
|
|
82
|
+
context_id: str = Field(alias="contextId")
|
|
83
|
+
status: A2ATaskStatus
|
|
84
|
+
artifacts: list[dict[str, Any]] = Field(default_factory=list)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class A2AMessageSendResult(BasePydanticModel):
|
|
88
|
+
model_config = ConfigDict(serialize_by_alias=True)
|
|
89
|
+
|
|
90
|
+
message: dict[str, Any] | None = None
|
|
91
|
+
task: A2ATask | None = None
|
|
92
|
+
|
|
93
|
+
@model_validator(mode="after")
|
|
94
|
+
def _require_exact_result(self) -> A2AMessageSendResult:
|
|
95
|
+
if (self.message is None) == (self.task is None):
|
|
96
|
+
raise ValueError("A2A message-send result must contain exactly one of message or task")
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def response_kind(self) -> A2AResponseKind:
|
|
101
|
+
return A2AResponseKind.TASK if self.task is not None else A2AResponseKind.MESSAGE
|
|
102
|
+
|
|
103
|
+
|
|
38
104
|
class AgentHarnessKind(str, Enum):
|
|
39
105
|
PI = "pi"
|
|
40
106
|
TAU = "tau"
|
|
@@ -56,6 +122,7 @@ class AgentSemanticSearchResult(BasePydanticModel):
|
|
|
56
122
|
"",
|
|
57
123
|
description="Short description returned by semantic search for the matched agent.",
|
|
58
124
|
)
|
|
125
|
+
a2a_profile: AgentA2AProfile = Field(default_factory=AgentA2AProfile)
|
|
59
126
|
project_branch_uid: str = Field(
|
|
60
127
|
...,
|
|
61
128
|
description="Public UID of the ProjectBranch that owns the matched Project Coding Agent.",
|
|
@@ -278,6 +345,7 @@ class Agent(ShareableObjectMixin, BaseObjectOrm, BasePydanticModel):
|
|
|
278
345
|
None,
|
|
279
346
|
description="Optional structured agent card payload.",
|
|
280
347
|
)
|
|
348
|
+
a2a_profile: AgentA2AProfile = Field(default_factory=AgentA2AProfile)
|
|
281
349
|
|
|
282
350
|
llm_thinking: str
|
|
283
351
|
llm_provider: str = Field(
|
|
@@ -499,6 +567,10 @@ class CodingAgentService(BaseObjectOrm, BasePydanticModel):
|
|
|
499
567
|
}
|
|
500
568
|
|
|
501
569
|
uid: str | None = Field(None, description="Public UID of the coding-agent service.")
|
|
570
|
+
harness: AgentHarnessKind = Field(
|
|
571
|
+
...,
|
|
572
|
+
description="Runtime harness deployed by this coding-agent service.",
|
|
573
|
+
)
|
|
502
574
|
agent_uid: str | None = Field(None, description="Public UID of the owning Agent.")
|
|
503
575
|
agent_type: str | None = Field(
|
|
504
576
|
None,
|
|
@@ -519,12 +591,56 @@ class CodingAgentService(BaseObjectOrm, BasePydanticModel):
|
|
|
519
591
|
image_drift: dict[str, Any] | None = Field(
|
|
520
592
|
None, description="Runtime image drift status payload."
|
|
521
593
|
)
|
|
594
|
+
llm_provider: str | None = Field(
|
|
595
|
+
...,
|
|
596
|
+
description="Read-only LLM provider projected from the owning Agent.",
|
|
597
|
+
)
|
|
598
|
+
llm_model: str | None = Field(
|
|
599
|
+
...,
|
|
600
|
+
description="Read-only LLM model projected from the owning Agent.",
|
|
601
|
+
)
|
|
602
|
+
llm_thinking: str | None = Field(
|
|
603
|
+
...,
|
|
604
|
+
description="Read-only thinking level projected from the owning Agent.",
|
|
605
|
+
)
|
|
606
|
+
cpu_request: str | None = Field(
|
|
607
|
+
...,
|
|
608
|
+
description="Read-only CPU request projected from the backing Job.",
|
|
609
|
+
)
|
|
610
|
+
cpu_limit: str | None = Field(
|
|
611
|
+
...,
|
|
612
|
+
description="Read-only CPU limit projected from the backing Job.",
|
|
613
|
+
)
|
|
614
|
+
memory_request: str | None = Field(
|
|
615
|
+
...,
|
|
616
|
+
description="Read-only memory request projected from the backing Job.",
|
|
617
|
+
)
|
|
618
|
+
memory_limit: str | None = Field(
|
|
619
|
+
...,
|
|
620
|
+
description="Read-only memory limit projected from the backing Job.",
|
|
621
|
+
)
|
|
622
|
+
gpu_request: str | None = Field(
|
|
623
|
+
...,
|
|
624
|
+
description="Read-only GPU request projected from the backing Job.",
|
|
625
|
+
)
|
|
626
|
+
gpu_type: str | None = Field(
|
|
627
|
+
...,
|
|
628
|
+
description="Read-only GPU type projected from the backing Job.",
|
|
629
|
+
)
|
|
630
|
+
spot: bool | None = Field(
|
|
631
|
+
...,
|
|
632
|
+
description="Read-only spot scheduling flag projected from the backing Job.",
|
|
633
|
+
)
|
|
522
634
|
related_job_uid: str | None = Field(
|
|
523
635
|
None, description="Public UID of the backing job, if attached."
|
|
524
636
|
)
|
|
525
637
|
service_runtime_uid: str | None = Field(
|
|
526
638
|
None, description="Public UID of the backing service runtime, if attached."
|
|
527
639
|
)
|
|
640
|
+
automatic_redeployment_policy: AutomaticRedeploymentPolicy | None = Field(
|
|
641
|
+
...,
|
|
642
|
+
description="Automatic redeployment policy for a Project Executor service.",
|
|
643
|
+
)
|
|
528
644
|
|
|
529
645
|
@classmethod
|
|
530
646
|
def _post_collection_action(
|
|
@@ -958,7 +1074,7 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
958
1074
|
message_id: str | None = None,
|
|
959
1075
|
strict_dictionary: bool = False,
|
|
960
1076
|
json_repair_attempts: int = 3,
|
|
961
|
-
|
|
1077
|
+
response_kind: A2AResponseKind = A2AResponseKind.MESSAGE,
|
|
962
1078
|
) -> dict[str, Any]:
|
|
963
1079
|
normalized_message = str(message)
|
|
964
1080
|
if not normalized_message.strip():
|
|
@@ -970,6 +1086,10 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
970
1086
|
normalized_message_id = f"msg-{uuid.uuid4()}"
|
|
971
1087
|
|
|
972
1088
|
accepted_output_modes = ["application/json"] if strict_dictionary else ["text/plain"]
|
|
1089
|
+
try:
|
|
1090
|
+
normalized_response_kind = A2AResponseKind(response_kind)
|
|
1091
|
+
except ValueError as exc:
|
|
1092
|
+
raise ValueError("response_kind must be 'message' or 'task'") from exc
|
|
973
1093
|
parts: list[dict[str, Any]] = [{"text": normalized_message}]
|
|
974
1094
|
for file_spec in files or []:
|
|
975
1095
|
parts.append(AgentSession._build_standard_a2a_raw_file_part(file_spec))
|
|
@@ -983,7 +1103,7 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
983
1103
|
},
|
|
984
1104
|
"configuration": {
|
|
985
1105
|
"acceptedOutputModes": accepted_output_modes,
|
|
986
|
-
"
|
|
1106
|
+
"responseKind": normalized_response_kind.value,
|
|
987
1107
|
},
|
|
988
1108
|
}
|
|
989
1109
|
if strict_dictionary:
|
|
@@ -1060,6 +1180,7 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
1060
1180
|
"Authorization": f"Bearer {access.token.strip()}",
|
|
1061
1181
|
"Content-Type": STANDARD_A2A_CONTENT_TYPE,
|
|
1062
1182
|
"Accept": STANDARD_A2A_CONTENT_TYPE,
|
|
1183
|
+
"A2A-Extensions": STANDARD_A2A_RESPONSE_KIND_EXTENSION_URI,
|
|
1063
1184
|
}
|
|
1064
1185
|
return requests.post(
|
|
1065
1186
|
url,
|
|
@@ -1069,8 +1190,10 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
1069
1190
|
)
|
|
1070
1191
|
|
|
1071
1192
|
@staticmethod
|
|
1072
|
-
def extract_a2a_message_text(
|
|
1073
|
-
|
|
1193
|
+
def extract_a2a_message_text(
|
|
1194
|
+
payload: dict[str, Any] | A2AMessageSendResult,
|
|
1195
|
+
) -> str:
|
|
1196
|
+
message = payload.message if isinstance(payload, A2AMessageSendResult) else payload.get("message")
|
|
1074
1197
|
if not isinstance(message, dict):
|
|
1075
1198
|
return ""
|
|
1076
1199
|
parts = message.get("parts")
|
|
@@ -1092,13 +1215,38 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
1092
1215
|
message_id: str | None = None,
|
|
1093
1216
|
strict_dictionary: bool = False,
|
|
1094
1217
|
json_repair_attempts: int = 3,
|
|
1095
|
-
|
|
1218
|
+
response_kind: A2AResponseKind = A2AResponseKind.MESSAGE,
|
|
1219
|
+
a2a_profile: AgentA2AProfile | dict[str, Any] | None = None,
|
|
1096
1220
|
timeout=None,
|
|
1097
|
-
) ->
|
|
1221
|
+
) -> A2AMessageSendResult:
|
|
1098
1222
|
"""
|
|
1099
1223
|
Send one standard A2A message to the runtime for this agent session.
|
|
1100
1224
|
"""
|
|
1101
1225
|
session_uid = cls._resolve_agent_session_uid(agent_session)
|
|
1226
|
+
normalized_response_kind = A2AResponseKind(response_kind)
|
|
1227
|
+
resolved_profile: AgentA2AProfile | None = None
|
|
1228
|
+
if a2a_profile is not None:
|
|
1229
|
+
resolved_profile = (
|
|
1230
|
+
a2a_profile
|
|
1231
|
+
if isinstance(a2a_profile, AgentA2AProfile)
|
|
1232
|
+
else AgentA2AProfile.model_validate(a2a_profile)
|
|
1233
|
+
)
|
|
1234
|
+
elif normalized_response_kind is A2AResponseKind.TASK:
|
|
1235
|
+
session = (
|
|
1236
|
+
agent_session
|
|
1237
|
+
if isinstance(agent_session, AgentSession)
|
|
1238
|
+
else cls.get(pk=session_uid, timeout=timeout)
|
|
1239
|
+
)
|
|
1240
|
+
if not session.agent_uid:
|
|
1241
|
+
raise ValueError("Task response_kind requires a session with an agent_uid")
|
|
1242
|
+
resolved_profile = Agent.get(pk=session.agent_uid, timeout=timeout).a2a_profile
|
|
1243
|
+
if (
|
|
1244
|
+
resolved_profile is not None
|
|
1245
|
+
and normalized_response_kind not in resolved_profile.supported_response_kinds
|
|
1246
|
+
):
|
|
1247
|
+
raise ValueError(
|
|
1248
|
+
f"response_kind {normalized_response_kind.value!r} is not advertised by the agent"
|
|
1249
|
+
)
|
|
1102
1250
|
body = cls._build_standard_a2a_message_send_body(
|
|
1103
1251
|
agent_session_uid=session_uid,
|
|
1104
1252
|
message=message,
|
|
@@ -1106,7 +1254,7 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
1106
1254
|
message_id=message_id,
|
|
1107
1255
|
strict_dictionary=strict_dictionary,
|
|
1108
1256
|
json_repair_attempts=json_repair_attempts,
|
|
1109
|
-
|
|
1257
|
+
response_kind=normalized_response_kind,
|
|
1110
1258
|
)
|
|
1111
1259
|
access = cls._resolve_runtime_access_for_message_send(session_uid, timeout=timeout)
|
|
1112
1260
|
response = cls._post_standard_a2a_message(
|
|
@@ -1140,7 +1288,150 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
1140
1288
|
) from exc
|
|
1141
1289
|
if not isinstance(payload, dict):
|
|
1142
1290
|
raise TypeError("Standard A2A response must be a JSON object")
|
|
1143
|
-
|
|
1291
|
+
try:
|
|
1292
|
+
result = A2AMessageSendResult.model_validate(payload)
|
|
1293
|
+
except ValueError as exc:
|
|
1294
|
+
raise ApiError(
|
|
1295
|
+
"Standard A2A response must contain exactly one valid message or task result.",
|
|
1296
|
+
response=response,
|
|
1297
|
+
payload=body,
|
|
1298
|
+
) from exc
|
|
1299
|
+
if result.response_kind is not normalized_response_kind:
|
|
1300
|
+
raise ApiError(
|
|
1301
|
+
"Standard A2A response kind does not match the requested response_kind.",
|
|
1302
|
+
response=response,
|
|
1303
|
+
payload=body,
|
|
1304
|
+
)
|
|
1305
|
+
return result
|
|
1306
|
+
|
|
1307
|
+
@classmethod
|
|
1308
|
+
def _request_a2a_task(
|
|
1309
|
+
cls,
|
|
1310
|
+
agent_session: str | AgentSession,
|
|
1311
|
+
*,
|
|
1312
|
+
task_id: str,
|
|
1313
|
+
cancel: bool,
|
|
1314
|
+
timeout=None,
|
|
1315
|
+
) -> A2ATask:
|
|
1316
|
+
session_uid = cls._resolve_agent_session_uid(agent_session)
|
|
1317
|
+
normalized_task_id = str(task_id or "").strip()
|
|
1318
|
+
if not normalized_task_id or "/" in normalized_task_id:
|
|
1319
|
+
raise ValueError("task_id must be a non-empty A2A task identifier")
|
|
1320
|
+
|
|
1321
|
+
def request(access: AgentSessionRuntimeAccess) -> requests.Response:
|
|
1322
|
+
if access.mode != "token":
|
|
1323
|
+
raise ApiError("Coding-agent runtime access is unavailable.")
|
|
1324
|
+
if not isinstance(access.token, str) or not access.token.strip():
|
|
1325
|
+
raise ApiError("Runtime access response is missing token.")
|
|
1326
|
+
path = f"{STANDARD_A2A_TASKS_PATH}/{normalized_task_id}"
|
|
1327
|
+
method = "GET"
|
|
1328
|
+
if cancel:
|
|
1329
|
+
path = f"{path}:cancel"
|
|
1330
|
+
method = "POST"
|
|
1331
|
+
request_timeout = (
|
|
1332
|
+
DEFAULT_AGENT_SESSION_LONG_REQUEST_TIMEOUT if timeout is None else timeout
|
|
1333
|
+
)
|
|
1334
|
+
return requests.request(
|
|
1335
|
+
method,
|
|
1336
|
+
_join_runtime_url(access.rpc_url, path),
|
|
1337
|
+
headers={
|
|
1338
|
+
"Authorization": f"Bearer {access.token.strip()}",
|
|
1339
|
+
"Accept": STANDARD_A2A_CONTENT_TYPE,
|
|
1340
|
+
},
|
|
1341
|
+
timeout=request_timeout,
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
access = cls._resolve_runtime_access_for_message_send(session_uid, timeout=timeout)
|
|
1345
|
+
response = request(access)
|
|
1346
|
+
if response.status_code in (401, 403):
|
|
1347
|
+
cls.clear_cached_runtime_access(session_uid)
|
|
1348
|
+
access = cls.resolve_runtime_access(session_uid, cache=True, timeout=timeout)
|
|
1349
|
+
response = request(access)
|
|
1350
|
+
if response.status_code in (401, 403):
|
|
1351
|
+
cls.clear_cached_runtime_access(session_uid)
|
|
1352
|
+
if not (200 <= response.status_code < 300):
|
|
1353
|
+
raise_for_response(response)
|
|
1354
|
+
try:
|
|
1355
|
+
payload = response.json()
|
|
1356
|
+
except Exception as exc:
|
|
1357
|
+
raise ApiError(
|
|
1358
|
+
"Standard A2A task response must be a JSON object.",
|
|
1359
|
+
response=response,
|
|
1360
|
+
) from exc
|
|
1361
|
+
task_payload = payload.get("task") if isinstance(payload, dict) else None
|
|
1362
|
+
if not isinstance(task_payload, dict):
|
|
1363
|
+
raise ApiError(
|
|
1364
|
+
"Standard A2A task response must contain a task object.",
|
|
1365
|
+
response=response,
|
|
1366
|
+
)
|
|
1367
|
+
return A2ATask.model_validate(task_payload)
|
|
1368
|
+
|
|
1369
|
+
@classmethod
|
|
1370
|
+
def get_a2a_task(
|
|
1371
|
+
cls,
|
|
1372
|
+
agent_session: str | AgentSession,
|
|
1373
|
+
*,
|
|
1374
|
+
task_id: str,
|
|
1375
|
+
timeout=None,
|
|
1376
|
+
) -> A2ATask:
|
|
1377
|
+
return cls._request_a2a_task(
|
|
1378
|
+
agent_session,
|
|
1379
|
+
task_id=task_id,
|
|
1380
|
+
cancel=False,
|
|
1381
|
+
timeout=timeout,
|
|
1382
|
+
)
|
|
1383
|
+
|
|
1384
|
+
@classmethod
|
|
1385
|
+
def cancel_a2a_task(
|
|
1386
|
+
cls,
|
|
1387
|
+
agent_session: str | AgentSession,
|
|
1388
|
+
*,
|
|
1389
|
+
task_id: str,
|
|
1390
|
+
timeout=None,
|
|
1391
|
+
) -> A2ATask:
|
|
1392
|
+
return cls._request_a2a_task(
|
|
1393
|
+
agent_session,
|
|
1394
|
+
task_id=task_id,
|
|
1395
|
+
cancel=True,
|
|
1396
|
+
timeout=timeout,
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
@classmethod
|
|
1400
|
+
def wait_for_a2a_task(
|
|
1401
|
+
cls,
|
|
1402
|
+
agent_session: str | AgentSession,
|
|
1403
|
+
*,
|
|
1404
|
+
task_id: str,
|
|
1405
|
+
poll_interval_seconds: float = 1.0,
|
|
1406
|
+
wait_timeout_seconds: float = 900.0,
|
|
1407
|
+
timeout=None,
|
|
1408
|
+
) -> A2ATask:
|
|
1409
|
+
if poll_interval_seconds <= 0:
|
|
1410
|
+
raise ValueError("poll_interval_seconds must be greater than 0")
|
|
1411
|
+
if wait_timeout_seconds <= 0:
|
|
1412
|
+
raise ValueError("wait_timeout_seconds must be greater than 0")
|
|
1413
|
+
deadline = time.monotonic() + wait_timeout_seconds
|
|
1414
|
+
terminal_states = {
|
|
1415
|
+
"completed",
|
|
1416
|
+
"failed",
|
|
1417
|
+
"canceled",
|
|
1418
|
+
"rejected",
|
|
1419
|
+
"TASK_STATE_COMPLETED",
|
|
1420
|
+
"TASK_STATE_FAILED",
|
|
1421
|
+
"TASK_STATE_CANCELED",
|
|
1422
|
+
"TASK_STATE_REJECTED",
|
|
1423
|
+
}
|
|
1424
|
+
while True:
|
|
1425
|
+
task = cls.get_a2a_task(
|
|
1426
|
+
agent_session,
|
|
1427
|
+
task_id=task_id,
|
|
1428
|
+
timeout=timeout,
|
|
1429
|
+
)
|
|
1430
|
+
if task.status.state in terminal_states:
|
|
1431
|
+
return task
|
|
1432
|
+
if time.monotonic() >= deadline:
|
|
1433
|
+
raise TimeoutError(f"Timed out waiting for A2A task {task_id}")
|
|
1434
|
+
time.sleep(poll_interval_seconds)
|
|
1144
1435
|
|
|
1145
1436
|
@classmethod
|
|
1146
1437
|
def resolve_runtime_access(
|
|
@@ -1415,7 +1706,12 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
|
|
|
1415
1706
|
|
|
1416
1707
|
|
|
1417
1708
|
__all__ = [
|
|
1709
|
+
"A2AMessageSendResult",
|
|
1710
|
+
"A2AResponseKind",
|
|
1711
|
+
"A2ATask",
|
|
1712
|
+
"A2ATaskStatus",
|
|
1418
1713
|
"Agent",
|
|
1714
|
+
"AgentA2AProfile",
|
|
1419
1715
|
"AgentRuntimeImageDrift",
|
|
1420
1716
|
"AgentRuntimeImageDriftCheck",
|
|
1421
1717
|
"AgentHarnessKind",
|
|
@@ -6867,7 +6867,7 @@ def test_agent_session_a2a_send_always_returns_json(cli_mod, runner, monkeypatch
|
|
|
6867
6867
|
assert captured["kwargs"]["message_id"] == "msg-client-1"
|
|
6868
6868
|
assert captured["kwargs"]["strict_dictionary"] is True
|
|
6869
6869
|
assert captured["kwargs"]["json_repair_attempts"] == 3
|
|
6870
|
-
assert captured["kwargs"]["
|
|
6870
|
+
assert captured["kwargs"]["response_kind"] == "message"
|
|
6871
6871
|
assert "omit_reasoning" not in captured["kwargs"]
|
|
6872
6872
|
|
|
6873
6873
|
|