langgraph-agent-common 2.0.0__py3-none-any.whl

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 (31) hide show
  1. langgraph_a2a_api/__init__.py +0 -0
  2. langgraph_a2a_api/adaptor/__init__.py +0 -0
  3. langgraph_a2a_api/adaptor/agent_executor.py +421 -0
  4. langgraph_a2a_api/adaptor/conversion.py +167 -0
  5. langgraph_a2a_api/adaptor/sanitize_messages_for_llm.py +34 -0
  6. langgraph_a2a_api/fastapi_application.py +152 -0
  7. langgraph_a2a_api/langgraph/__init__.py +0 -0
  8. langgraph_a2a_api/langgraph/checkpointer.py +102 -0
  9. langgraph_a2a_api/langgraph/graph.py +49 -0
  10. langgraph_a2a_api/langgraph/store.py +64 -0
  11. langgraph_a2a_api/langgraph/validator.py +42 -0
  12. langgraph_a2a_api/resources/openapi_schema.json +277 -0
  13. langgraph_a2a_api/resources/static/css/style.css +480 -0
  14. langgraph_a2a_api/resources/static/index.html +107 -0
  15. langgraph_a2a_api/resources/static/js/app.js +896 -0
  16. langgraph_a2a_api/server.py +149 -0
  17. langgraph_a2a_api/tasks/__init__.py +0 -0
  18. langgraph_a2a_api/tasks/push_notification_config_store.py +24 -0
  19. langgraph_a2a_api/tasks/task_store.py +66 -0
  20. langgraph_a2a_api/utils/__init__.py +0 -0
  21. langgraph_a2a_api/utils/lock.py +31 -0
  22. langgraph_a2a_api/utils/types.py +3 -0
  23. langgraph_agent_common-2.0.0.dist-info/METADATA +67 -0
  24. langgraph_agent_common-2.0.0.dist-info/RECORD +31 -0
  25. langgraph_agent_common-2.0.0.dist-info/WHEEL +5 -0
  26. langgraph_agent_common-2.0.0.dist-info/top_level.txt +2 -0
  27. langgraph_aux/__init__.py +0 -0
  28. langgraph_aux/callbacks/__init__.py +11 -0
  29. langgraph_aux/callbacks/talos_callback_handler.py +340 -0
  30. langgraph_aux/utils/__init__.py +0 -0
  31. langgraph_aux/utils/stream_output.py +13 -0
@@ -0,0 +1,152 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ from fastapi import FastAPI
6
+ from fastapi.openapi.utils import get_openapi
7
+ from fastapi.responses import FileResponse
8
+ from fastapi.staticfiles import StaticFiles
9
+ from starlette.requests import Request
10
+ from starlette.responses import Response
11
+ from starlette.routing import BaseRoute, Mount
12
+
13
+ from a2a.server.request_handlers import DefaultRequestHandler
14
+ from a2a.server.routes.agent_card_routes import create_agent_card_routes
15
+ from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes
16
+ from a2a.server.routes.rest_routes import create_rest_routes
17
+ from a2a.types import AgentCard
18
+ from a2a.utils import constants
19
+
20
+ _OPENAPI_SCHEMA_PATH = Path(__file__).parent / "resources" / "openapi_schema.json"
21
+ _STATIC_PATH = Path(__file__).parent / "resources" / "static"
22
+
23
+
24
+ def _load_openapi_schema() -> dict:
25
+ with open(_OPENAPI_SCHEMA_PATH) as f:
26
+ return json.load(f)
27
+
28
+
29
+ def _create_a2a_routes(agent_card: AgentCard, request_handler: DefaultRequestHandler) -> list[BaseRoute]:
30
+ return [
31
+ *create_agent_card_routes(agent_card),
32
+ *create_jsonrpc_routes(request_handler, rpc_url='/'),
33
+ *create_rest_routes(request_handler),
34
+ ]
35
+
36
+
37
+ async def _schema_placeholder() -> Response:
38
+ return Response(status_code=501)
39
+
40
+
41
+ def _derive_route_tag(path: str) -> str:
42
+ if path == '/':
43
+ return 'jsonrpc'
44
+ if path == '/.well-known/agent-card.json':
45
+ return 'agent-card'
46
+ return 'rest'
47
+
48
+
49
+ def _derive_route_summary(route: BaseRoute) -> str:
50
+ endpoint = getattr(route, 'endpoint', None)
51
+ path = getattr(route, 'path', 'A2A route')
52
+ if endpoint is None:
53
+ return f'Handle {path}'
54
+ doc = getattr(endpoint, '__doc__', None)
55
+ if doc:
56
+ return doc.strip().splitlines()[0]
57
+ name = getattr(route, 'name', None) or getattr(endpoint, '__name__', None)
58
+ if name:
59
+ return name.replace('_', ' ').strip().capitalize()
60
+ return f'Handle {path}'
61
+
62
+
63
+ def _register_routes(app: FastAPI, routes: list[BaseRoute]):
64
+ for route in routes:
65
+ app.router.routes.append(route)
66
+ if isinstance(route, Mount):
67
+ continue
68
+ methods = sorted(method for method in (getattr(route, 'methods', None) or []) if method != 'HEAD')
69
+ if not methods:
70
+ continue
71
+ app.add_api_route(
72
+ path=route.path,
73
+ endpoint=_schema_placeholder,
74
+ methods=methods,
75
+ include_in_schema=True,
76
+ tags=[_derive_route_tag(route.path)],
77
+ summary=_derive_route_summary(route),
78
+ name=f"openapi:{','.join(methods)}:{route.path}",
79
+ )
80
+
81
+
82
+ def _add_common_routes(app: FastAPI):
83
+ if not _STATIC_PATH.exists():
84
+ return
85
+
86
+ app.mount('/static', StaticFiles(directory=str(_STATIC_PATH)), name='static')
87
+
88
+ @app.get('/chat')
89
+ async def chat_ui():
90
+ return FileResponse(str(_STATIC_PATH / 'index.html'))
91
+
92
+
93
+ def build_fastapi_application(agent_card: AgentCard, request_handler: DefaultRequestHandler, lifespan: Any, root_path: str = "") -> FastAPI:
94
+ app = FastAPI(
95
+ lifespan=lifespan,
96
+ title=agent_card.name or 'A2A Agent API',
97
+ description=agent_card.description or 'A2A Protocol API with JSON-RPC and REST endpoints',
98
+ version=agent_card.version or '1.0.0',
99
+ docs_url='/docs',
100
+ redoc_url='/redoc',
101
+ openapi_url='/openapi.json',
102
+ root_path=root_path,
103
+ )
104
+
105
+ routes = _create_a2a_routes(agent_card, request_handler)
106
+
107
+ @app.middleware('http')
108
+ async def ensure_a2a_version_header(request: Request, call_next):
109
+ if constants.VERSION_HEADER.encode() not in request.headers.raw:
110
+ request.scope['headers'] = [
111
+ *request.scope['headers'],
112
+ (constants.VERSION_HEADER.lower().encode(), constants.PROTOCOL_VERSION_1_0.encode()),
113
+ ]
114
+ return await call_next(request)
115
+
116
+ _add_common_routes(app)
117
+ _register_routes(app, routes)
118
+ _customize_openapi(app)
119
+ return app
120
+
121
+
122
+ def _customize_openapi(app: FastAPI):
123
+ external_schema = _load_openapi_schema()
124
+
125
+ def openapi_schema():
126
+ if app.openapi_schema:
127
+ return app.openapi_schema
128
+
129
+ schema = get_openapi(
130
+ title=app.title,
131
+ version=app.version,
132
+ openapi_version=app.openapi_version,
133
+ description=app.description,
134
+ routes=app.routes,
135
+ )
136
+
137
+ external_schemas = external_schema.get('components', {}).get('schemas', {})
138
+ if external_schemas:
139
+ schema.setdefault('components', {}).setdefault('schemas', {}).update(external_schemas)
140
+
141
+ external_paths = external_schema.get('paths', {})
142
+ app_paths = schema.get('paths', {})
143
+ for path, methods in external_paths.items():
144
+ if path in app_paths:
145
+ for method, config in methods.items():
146
+ if method in app_paths[path]:
147
+ app_paths[path][method].update(config)
148
+
149
+ app.openapi_schema = schema
150
+ return app.openapi_schema
151
+
152
+ app.openapi = openapi_schema
File without changes
@@ -0,0 +1,102 @@
1
+ from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
2
+ from langgraph.checkpoint.memory import InMemorySaver
3
+ from langgraph.checkpoint.base import BaseCheckpointSaver
4
+
5
+ from contextlib import asynccontextmanager
6
+ from collections.abc import AsyncIterator
7
+
8
+ from psycopg.rows import DictRow
9
+ from psycopg import AsyncCursor
10
+ from psycopg_pool import AsyncConnectionPool
11
+
12
+ import re
13
+ import os
14
+ import textwrap
15
+
16
+ class AsyncPostgresSaverWrapper(AsyncPostgresSaver):
17
+ def __init__(self, *args, **kwargs):
18
+ super().__init__(*args, **kwargs)
19
+ self.checkpoint_table_prefix = None
20
+ self.GET_EXPIRED_THREADS_SQL = textwrap.dedent("""
21
+ SELECT thread_id
22
+ FROM checkpoints
23
+ GROUP BY thread_id
24
+ HAVING MAX((checkpoint ->> 'ts')::timestamptz) < (NOW() - (%s || ' MINUTES')::INTERVAL);
25
+ """)
26
+
27
+ def set_checkpoint_table_prefix(self, checkpoint_table_prefix: str):
28
+ self.checkpoint_table_prefix = checkpoint_table_prefix
29
+
30
+ @asynccontextmanager
31
+ async def _cursor(self, *, pipeline: bool = False) -> AsyncIterator[AsyncCursor[DictRow]]:
32
+ async with super()._cursor(pipeline = pipeline) as cur:
33
+ yield AsyncCursorWrapper(cur, self.checkpoint_table_prefix)
34
+
35
+ async def sweep_threads(self, ttl: int) -> int:
36
+ async with self._cursor() as cur:
37
+ await cur.execute(self.GET_EXPIRED_THREADS_SQL, (ttl,), binary=True)
38
+ values = await cur.fetchmany(1024)
39
+ for value in values:
40
+ await self.adelete_thread(value["thread_id"])
41
+ return len(values)
42
+
43
+ class AsyncCursorWrapper():
44
+ def __init__(self, cursor: AsyncCursor, checkpoint_table_prefix: str):
45
+ self._cursor = cursor
46
+ self.checkpoint_table_prefix = checkpoint_table_prefix
47
+
48
+ def _add_checkpoint_table_prefix(self, query: str) -> str:
49
+ checkpoint_migrations_regex = r"(?<=[ ])checkpoint_migrations(?=[_ .(]|\n)"
50
+ checkpoints_regex = r"(?<=[ ])checkpoints(?=[_ .(]|\n)"
51
+ checkpoint_blobs_regex = r"(?<=[ ])checkpoint_blobs(?=[_ .(]|\n)"
52
+ checkpoint_writes_regex = r"(?<=[ ])checkpoint_writes(?=[_ .(]|\n)"
53
+ checkpoint_index_regex = r"(?<=\W)CREATE INDEX CONCURRENTLY(?=[ ])"
54
+
55
+ checkpoint_migrations_sub = re.sub(checkpoint_migrations_regex, f"{self.checkpoint_table_prefix}__checkpoint_migrations", query)
56
+ checkpoints_sub = re.sub(checkpoints_regex, f"{self.checkpoint_table_prefix}__checkpoints", checkpoint_migrations_sub)
57
+ checkpoint_blobs_sub = re.sub(checkpoint_blobs_regex, f"{self.checkpoint_table_prefix}__checkpoint_blobs", checkpoints_sub)
58
+ checkpoint_writes_sub = re.sub(checkpoint_writes_regex, f"{self.checkpoint_table_prefix}__checkpoint_writes", checkpoint_blobs_sub)
59
+ checkpoint_index_sub = re.sub(checkpoint_index_regex, "CREATE INDEX", checkpoint_writes_sub)
60
+ return checkpoint_index_sub
61
+
62
+
63
+ def execute(self, *args, **kwargs):
64
+ modified_args = (self._add_checkpoint_table_prefix(args[0]),) + args[1:]
65
+ return self._cursor.execute(*modified_args, **kwargs)
66
+
67
+ def executemany(self, *args, **kwargs):
68
+ modified_args = (self._add_checkpoint_table_prefix(args[0]),) + args[1:]
69
+ return self._cursor.executemany(*modified_args, **kwargs)
70
+
71
+ def __getattr__(self, name):
72
+ return getattr(self._cursor, name)
73
+
74
+ @asynccontextmanager
75
+ async def get_async_postgres_checkpointer(checkpoint_table_prefix: str) -> AsyncIterator[AsyncPostgresSaver]:
76
+ postgres_uri = os.environ["POSTGRES_URI"]
77
+ async with AsyncConnectionPool(postgres_uri, min_size=1, max_size=8, max_idle=600, timeout=30) as pool:
78
+ checkpointer = AsyncPostgresSaverWrapper(conn=pool)
79
+ checkpointer.set_checkpoint_table_prefix(checkpoint_table_prefix)
80
+ await checkpointer.setup()
81
+ yield checkpointer
82
+
83
+
84
+ class InMemorySaverWrapper(InMemorySaver):
85
+ async def sweep_threads(self, ttl: int) -> int:
86
+ return 0
87
+
88
+
89
+ @asynccontextmanager
90
+ async def get_inmemory_checkpointer(checkpoint_table_prefix: str) -> AsyncIterator[BaseCheckpointSaver]:
91
+ yield InMemorySaverWrapper()
92
+
93
+
94
+ @asynccontextmanager
95
+ async def get_checkpointer(checkpoint_table_prefix: str) -> AsyncIterator[BaseCheckpointSaver]:
96
+ if os.getenv("POSTGRES_URI"):
97
+ async with get_async_postgres_checkpointer(checkpoint_table_prefix) as cp:
98
+ yield cp
99
+ else:
100
+ async with get_inmemory_checkpointer(checkpoint_table_prefix) as cp:
101
+ yield cp
102
+
@@ -0,0 +1,49 @@
1
+ import os
2
+ import importlib
3
+ from typing import NamedTuple
4
+ from random import choice
5
+ import sys
6
+ from contextlib import asynccontextmanager
7
+ from collections.abc import AsyncIterator
8
+ import inflection
9
+
10
+ from langgraph.graph.state import CompiledStateGraph
11
+ from langgraph.types import Checkpointer
12
+
13
+ from langgraph.graph.message import MessagesState
14
+ from a2a.types import AgentSkill
15
+
16
+
17
+ class GraphSpec(NamedTuple):
18
+ name: str
19
+ description: str
20
+ version: str
21
+ skills: list[AgentSkill]
22
+
23
+
24
+ def get_graph() -> (CompiledStateGraph, GraphSpec):
25
+ path_and_var = os.environ["GRAPH_PATH_AND_VAR"]
26
+ (path, var) = path_and_var.rsplit(":", maxsplit = 1)
27
+ module_name = "".join(choice("abcdefghijklmnopqrstuvwxyz") for _ in range(24))
28
+ module_spec = importlib.util.spec_from_file_location(module_name, path)
29
+ if module_spec is None:
30
+ raise ValueError(f"Could not find python file: {path} for graph: {var}")
31
+ module = importlib.util.module_from_spec(module_spec)
32
+ sys.modules[module_name] = module
33
+ module_spec.loader.exec_module(module)
34
+
35
+ graph = module.__dict__[var]
36
+ if not graph.get_name():
37
+ raise Exception(f"Langgraph graph must include graph name")
38
+ if not issubclass(type(graph.InputType), type(MessagesState)):
39
+ raise Exception(f"Langgraph state must be a subclass of {MessagesState}.")
40
+ description = graph.config.get("metadata", {}).get("description", "")
41
+ version = graph.config.get("metadata", {}).get("version", "1.0.0")
42
+ graph_spec = GraphSpec(
43
+ name = inflection.underscore(graph.get_name()),
44
+ description = description,
45
+ version = version,
46
+ skills = graph.config.get("metadata", {}).get("skills", []),
47
+ )
48
+ return graph, graph_spec
49
+
@@ -0,0 +1,64 @@
1
+ from collections.abc import AsyncIterator
2
+ from contextlib import asynccontextmanager
3
+
4
+ import os
5
+
6
+ from psycopg.rows import dict_row
7
+ from psycopg_pool import AsyncConnectionPool
8
+
9
+ from langgraph.store.base import BaseStore
10
+ from langgraph.store.postgres.aio import AsyncPostgresStore
11
+ from langgraph.store.memory import InMemoryStore
12
+
13
+
14
+ class AsyncPostgresStoreWrapper(AsyncPostgresStore):
15
+ async def sweep(self, ttl: int) -> int:
16
+ async with self._cursor() as cur:
17
+ await cur.execute(
18
+ """
19
+ DELETE FROM store
20
+ WHERE updated_at < (NOW() - (%s || ' MINUTES')::INTERVAL)
21
+ """,
22
+ (ttl,),
23
+ )
24
+ return cur.rowcount
25
+
26
+
27
+ @asynccontextmanager
28
+ async def get_async_postgres_store() -> AsyncIterator[BaseStore]:
29
+ postgres_uri = os.environ["POSTGRES_URI"]
30
+ async with AsyncConnectionPool(
31
+ postgres_uri,
32
+ min_size = 1,
33
+ max_size = 8,
34
+ max_idle = 600,
35
+ timeout = 30,
36
+ kwargs = {
37
+ "autocommit": True,
38
+ "prepare_threshold": 0,
39
+ "row_factory": dict_row,
40
+ },
41
+ ) as pool:
42
+ store = AsyncPostgresStoreWrapper(conn = pool)
43
+ await store.setup()
44
+ yield store
45
+
46
+
47
+ class InMemoryStoreWrapper(InMemoryStore):
48
+ async def sweep(self, ttl: int) -> int:
49
+ return 0
50
+
51
+
52
+ @asynccontextmanager
53
+ async def get_inmemory_store() -> AsyncIterator[BaseStore]:
54
+ yield InMemoryStoreWrapper()
55
+
56
+
57
+ @asynccontextmanager
58
+ async def get_store() -> AsyncIterator[BaseStore]:
59
+ if os.getenv("POSTGRES_URI"):
60
+ async with get_async_postgres_store() as s:
61
+ yield s
62
+ else:
63
+ async with get_inmemory_store() as s:
64
+ yield s
@@ -0,0 +1,42 @@
1
+ from typing import Any, Literal
2
+
3
+ from pydantic import BaseModel, ConfigDict, ValidationError
4
+
5
+
6
+ class HitlAction(BaseModel):
7
+ name: str
8
+ args: dict[str, Any]
9
+
10
+
11
+ class ApproveDecision(BaseModel):
12
+ type: Literal["approve"]
13
+
14
+
15
+ class EditDecision(BaseModel):
16
+ type: Literal["edit"]
17
+ edited_action: HitlAction
18
+
19
+
20
+ class RejectDecision(BaseModel):
21
+ type: Literal["reject"]
22
+ message: str | None = None
23
+
24
+
25
+ class RespondDecision(BaseModel):
26
+ type: Literal["respond"]
27
+ message: str
28
+
29
+
30
+ class HITLResponseModel(BaseModel):
31
+ model_config = ConfigDict(extra="forbid")
32
+
33
+ hitl_id: str
34
+ decision: ApproveDecision | EditDecision | RejectDecision | RespondDecision
35
+
36
+
37
+ def validate_HITLResponse(payload: Any) -> bool:
38
+ try:
39
+ HITLResponseModel.model_validate(payload)
40
+ except ValidationError:
41
+ return False
42
+ return True
@@ -0,0 +1,277 @@
1
+ {
2
+ "components": {
3
+ "schemas": {
4
+ "Part": {
5
+ "type": "object",
6
+ "properties": {
7
+ "text": { "type": "string", "description": "Text content" },
8
+ "raw": { "type": "string", "format": "byte", "description": "Base64 encoded raw bytes" },
9
+ "url": { "type": "string", "format": "uri", "description": "URL to external content" },
10
+ "data": { "description": "Structured data (JSON object/value)" },
11
+ "metadata": { "type": "object", "description": "Optional metadata" },
12
+ "filename": { "type": "string", "description": "File name" },
13
+ "mediaType": { "type": "string", "description": "MIME type" }
14
+ }
15
+ },
16
+ "Message": {
17
+ "type": "object",
18
+ "required": ["messageId", "role", "parts"],
19
+ "properties": {
20
+ "messageId": { "type": "string", "description": "Unique message ID (UUID)" },
21
+ "contextId": { "type": "string", "description": "Context ID for grouping related interactions" },
22
+ "taskId": { "type": "string", "description": "Task ID this message belongs to" },
23
+ "role": {
24
+ "type": "string",
25
+ "enum": ["ROLE_USER", "ROLE_AGENT"],
26
+ "description": "Message sender role"
27
+ },
28
+ "parts": {
29
+ "type": "array",
30
+ "items": { "$ref": "#/components/schemas/Part" },
31
+ "description": "Content parts of the message"
32
+ },
33
+ "metadata": { "type": "object", "description": "Optional metadata for extensions" },
34
+ "extensions": {
35
+ "type": "array",
36
+ "items": { "type": "string" },
37
+ "description": "Extension URIs relevant to this message"
38
+ },
39
+ "referenceTaskIds": {
40
+ "type": "array",
41
+ "items": { "type": "string" },
42
+ "description": "Related task IDs for context"
43
+ }
44
+ }
45
+ },
46
+ "SendMessageConfiguration": {
47
+ "type": "object",
48
+ "properties": {
49
+ "acceptedOutputModes": {
50
+ "type": "array",
51
+ "items": { "type": "string" },
52
+ "description": "Accepted output MIME types"
53
+ },
54
+ "taskPushNotificationConfig": { "$ref": "#/components/schemas/TaskPushNotificationConfig" },
55
+ "historyLength": { "type": "integer", "description": "Number of recent history messages to return" },
56
+ "returnImmediately": { "type": "boolean", "description": "Return immediately without waiting" }
57
+ }
58
+ },
59
+ "SendMessageRequest": {
60
+ "type": "object",
61
+ "required": ["message"],
62
+ "properties": {
63
+ "tenant": { "type": "string", "description": "Tenant identifier" },
64
+ "message": { "$ref": "#/components/schemas/Message" },
65
+ "configuration": { "$ref": "#/components/schemas/SendMessageConfiguration" },
66
+ "metadata": { "type": "object", "description": "Optional metadata" }
67
+ }
68
+ },
69
+ "TaskStatus": {
70
+ "type": "object",
71
+ "required": ["state"],
72
+ "properties": {
73
+ "state": {
74
+ "type": "string",
75
+ "enum": [
76
+ "TASK_STATE_UNSPECIFIED",
77
+ "TASK_STATE_SUBMITTED",
78
+ "TASK_STATE_WORKING",
79
+ "TASK_STATE_COMPLETED",
80
+ "TASK_STATE_FAILED",
81
+ "TASK_STATE_CANCELED",
82
+ "TASK_STATE_INPUT_REQUIRED",
83
+ "TASK_STATE_REJECTED",
84
+ "TASK_STATE_AUTH_REQUIRED"
85
+ ],
86
+ "description": "Current task state"
87
+ },
88
+ "message": { "$ref": "#/components/schemas/Message" },
89
+ "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" }
90
+ }
91
+ },
92
+ "Artifact": {
93
+ "type": "object",
94
+ "required": ["artifactId", "parts"],
95
+ "properties": {
96
+ "artifactId": { "type": "string", "description": "Unique artifact ID" },
97
+ "name": { "type": "string", "description": "Artifact name" },
98
+ "description": { "type": "string", "description": "Artifact description" },
99
+ "parts": {
100
+ "type": "array",
101
+ "items": { "$ref": "#/components/schemas/Part" },
102
+ "description": "Content parts"
103
+ },
104
+ "metadata": { "type": "object", "description": "Optional metadata" },
105
+ "extensions": {
106
+ "type": "array",
107
+ "items": { "type": "string" },
108
+ "description": "Extension URIs"
109
+ }
110
+ }
111
+ },
112
+ "Task": {
113
+ "type": "object",
114
+ "required": ["id", "contextId", "status"],
115
+ "properties": {
116
+ "id": { "type": "string", "description": "Unique task ID" },
117
+ "contextId": { "type": "string", "description": "Context ID for this task" },
118
+ "status": { "$ref": "#/components/schemas/TaskStatus" },
119
+ "artifacts": {
120
+ "type": "array",
121
+ "items": { "$ref": "#/components/schemas/Artifact" },
122
+ "description": "Generated artifacts"
123
+ },
124
+ "history": {
125
+ "type": "array",
126
+ "items": { "$ref": "#/components/schemas/Message" },
127
+ "description": "Conversation history"
128
+ },
129
+ "metadata": { "type": "object", "description": "Optional metadata" }
130
+ }
131
+ },
132
+ "SendMessageResponse": {
133
+ "type": "object",
134
+ "properties": {
135
+ "task": { "$ref": "#/components/schemas/Task" },
136
+ "message": { "$ref": "#/components/schemas/Message" }
137
+ }
138
+ },
139
+ "TaskPushNotificationConfig": {
140
+ "type": "object",
141
+ "required": ["id", "taskId", "url"],
142
+ "properties": {
143
+ "tenant": { "type": "string" },
144
+ "id": { "type": "string", "description": "Config ID" },
145
+ "taskId": { "type": "string", "description": "Task ID" },
146
+ "url": { "type": "string", "format": "uri", "description": "Callback URL" },
147
+ "token": { "type": "string", "description": "Auth token" },
148
+ "authentication": { "$ref": "#/components/schemas/AuthenticationInfo" }
149
+ }
150
+ },
151
+ "AuthenticationInfo": {
152
+ "type": "object",
153
+ "properties": {
154
+ "scheme": { "type": "string", "description": "Auth scheme" },
155
+ "credentials": { "type": "string", "description": "Auth credentials" }
156
+ }
157
+ },
158
+ "JSONRPCRequest": {
159
+ "type": "object",
160
+ "required": ["jsonrpc", "method"],
161
+ "properties": {
162
+ "jsonrpc": { "type": "string", "default": "2.0" },
163
+ "id": { "type": "string", "description": "Request ID" },
164
+ "method": {
165
+ "type": "string",
166
+ "enum": [
167
+ "SendMessage",
168
+ "SendStreamingMessage",
169
+ "GetTask",
170
+ "ListTasks",
171
+ "CancelTask",
172
+ "CreateTaskPushNotificationConfig",
173
+ "GetTaskPushNotificationConfig",
174
+ "ListTaskPushNotificationConfigs",
175
+ "DeleteTaskPushNotificationConfig",
176
+ "SubscribeToTask",
177
+ "GetExtendedAgentCard"
178
+ ]
179
+ },
180
+ "params": { "type": "object" }
181
+ }
182
+ }
183
+ }
184
+ },
185
+ "paths": {
186
+ "/message:send": {
187
+ "post": {
188
+ "requestBody": {
189
+ "required": true,
190
+ "content": {
191
+ "application/json": {
192
+ "schema": { "$ref": "#/components/schemas/SendMessageRequest" }
193
+ }
194
+ }
195
+ },
196
+ "responses": {
197
+ "200": {
198
+ "description": "Successful response",
199
+ "content": {
200
+ "application/json": {
201
+ "schema": { "$ref": "#/components/schemas/SendMessageResponse" }
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+ },
208
+ "/message:stream": {
209
+ "post": {
210
+ "requestBody": {
211
+ "required": true,
212
+ "content": {
213
+ "application/json": {
214
+ "schema": { "$ref": "#/components/schemas/SendMessageRequest" }
215
+ }
216
+ }
217
+ },
218
+ "responses": {
219
+ "200": {
220
+ "description": "SSE stream of events",
221
+ "content": {
222
+ "text/event-stream": {
223
+ "schema": { "type": "string" }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ },
230
+ "/tasks/{id}": {
231
+ "get": {
232
+ "parameters": [
233
+ {
234
+ "name": "id",
235
+ "in": "path",
236
+ "required": true,
237
+ "schema": { "type": "string" },
238
+ "description": "Task ID"
239
+ }
240
+ ],
241
+ "responses": {
242
+ "200": {
243
+ "description": "Task details",
244
+ "content": {
245
+ "application/json": {
246
+ "schema": { "$ref": "#/components/schemas/Task" }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ },
253
+ "/tasks/{id}:cancel": {
254
+ "post": {
255
+ "parameters": [
256
+ {
257
+ "name": "id",
258
+ "in": "path",
259
+ "required": true,
260
+ "schema": { "type": "string" },
261
+ "description": "Task ID to cancel"
262
+ }
263
+ ],
264
+ "responses": {
265
+ "200": {
266
+ "description": "Canceled task",
267
+ "content": {
268
+ "application/json": {
269
+ "schema": { "$ref": "#/components/schemas/Task" }
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }