acp-sdk 0.10.0__py3-none-any.whl → 0.11.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.
acp_sdk/server/app.py CHANGED
@@ -5,19 +5,24 @@ from contextlib import asynccontextmanager
5
5
  from datetime import timedelta
6
6
  from enum import Enum
7
7
 
8
- from fastapi import Depends, FastAPI, HTTPException, status
8
+ import httpx
9
+ import obstore.store
10
+ from fastapi import Depends, FastAPI, HTTPException, Request, status
9
11
  from fastapi.applications import AppType, Lifespan
10
12
  from fastapi.encoders import jsonable_encoder
11
13
  from fastapi.middleware.cors import CORSMiddleware
12
14
  from fastapi.responses import JSONResponse, StreamingResponse
15
+ from obstore.exceptions import NotFoundError
13
16
 
14
17
  from acp_sdk.models import (
15
- Agent as AgentModel,
16
- )
17
- from acp_sdk.models import (
18
+ ACPError,
18
19
  AgentName,
19
20
  AgentReadResponse,
20
21
  AgentsListResponse,
22
+ AwaitResume,
23
+ PingResponse,
24
+ ResourceId,
25
+ ResourceUrl,
21
26
  Run,
22
27
  RunCancelResponse,
23
28
  RunCreateRequest,
@@ -28,11 +33,15 @@ from acp_sdk.models import (
28
33
  RunReadResponse,
29
34
  RunResumeRequest,
30
35
  RunResumeResponse,
36
+ RunStatus,
37
+ Session,
38
+ SessionId,
39
+ SessionReadResponse,
40
+ )
41
+ from acp_sdk.models import (
42
+ AgentManifest as AgentModel,
31
43
  )
32
- from acp_sdk.models.errors import ACPError
33
- from acp_sdk.models.models import AwaitResume, RunStatus
34
- from acp_sdk.models.schemas import PingResponse
35
- from acp_sdk.server.agent import Agent
44
+ from acp_sdk.server.agent import AgentManifest
36
45
  from acp_sdk.server.errors import (
37
46
  RequestValidationError,
38
47
  StarletteHTTPException,
@@ -42,9 +51,9 @@ from acp_sdk.server.errors import (
42
51
  validation_exception_handler,
43
52
  )
44
53
  from acp_sdk.server.executor import CancelData, Executor, RunData
45
- from acp_sdk.server.session import Session
46
54
  from acp_sdk.server.store import MemoryStore, Store
47
55
  from acp_sdk.server.utils import stream_sse, wait_util_stop
56
+ from acp_sdk.shared import ResourceLoader, ResourceStore
48
57
 
49
58
 
50
59
  class Headers(str, Enum):
@@ -52,23 +61,34 @@ class Headers(str, Enum):
52
61
 
53
62
 
54
63
  def create_app(
55
- *agents: Agent,
64
+ *agents: AgentManifest,
56
65
  store: Store | None = None,
66
+ resource_store: ResourceStore | None = None,
67
+ resource_loader: ResourceLoader | None = None,
68
+ forward_resources: bool = True,
57
69
  lifespan: Lifespan[AppType] | None = None,
58
70
  dependencies: list[Depends] | None = None,
59
71
  ) -> FastAPI:
72
+ if not forward_resources and (
73
+ resource_store is None
74
+ or isinstance(resource_store._store, (obstore.store.MemoryStore, obstore.store.LocalStore))
75
+ ):
76
+ raise ValueError("Resource forwarding must be enabled when resource store does not support HTTP URLs")
77
+
60
78
  executor: ThreadPoolExecutor
79
+ client = httpx.AsyncClient()
61
80
 
62
81
  @asynccontextmanager
63
82
  async def internal_lifespan(app: FastAPI) -> AsyncGenerator[None]:
64
83
  nonlocal executor
65
- with ThreadPoolExecutor() as exec:
66
- executor = exec
67
- if not lifespan:
68
- yield None
69
- else:
70
- async with lifespan(app) as state:
71
- yield state
84
+ async with client:
85
+ with ThreadPoolExecutor() as exec:
86
+ executor = exec
87
+ if not lifespan:
88
+ yield None
89
+ else:
90
+ async with lifespan(app) as state:
91
+ yield state
72
92
 
73
93
  app = FastAPI(
74
94
  lifespan=internal_lifespan,
@@ -83,7 +103,7 @@ def create_app(
83
103
  allow_credentials=True,
84
104
  )
85
105
 
86
- agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
106
+ agents: dict[AgentName, AgentManifest] = {agent.name: agent for agent in agents}
87
107
 
88
108
  store = store or MemoryStore(limit=1000, ttl=timedelta(hours=1))
89
109
  run_store = store.as_store(model=RunData, prefix="run_")
@@ -91,6 +111,9 @@ def create_app(
91
111
  run_resume_store = store.as_store(model=AwaitResume, prefix="run_resume_")
92
112
  session_store = store.as_store(model=Session, prefix="session_")
93
113
 
114
+ resource_loader = resource_loader or ResourceLoader(client=client)
115
+ resource_store = resource_store or ResourceStore(store=obstore.store.MemoryStore())
116
+
94
117
  app.exception_handler(ACPError)(acp_error_handler)
95
118
  app.exception_handler(StarletteHTTPException)(http_exception_handler)
96
119
  app.exception_handler(RequestValidationError)(validation_exception_handler)
@@ -107,7 +130,7 @@ def create_app(
107
130
  run_data.run.status = RunStatus.CANCELLING
108
131
  return run_data
109
132
 
110
- def find_agent(agent_name: AgentName) -> Agent:
133
+ def find_agent(agent_name: AgentName) -> AgentManifest:
111
134
  agent = agents.get(agent_name, None)
112
135
  if not agent:
113
136
  raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")
@@ -132,36 +155,54 @@ def create_app(
132
155
  return PingResponse()
133
156
 
134
157
  @app.post("/runs")
135
- async def create_run(request: RunCreateRequest) -> RunCreateResponse:
158
+ async def create_run(request: RunCreateRequest, req: Request) -> RunCreateResponse:
136
159
  agent = find_agent(request.agent_name)
137
160
 
138
- session = (
139
- (await session_store.get(request.session_id)) or Session(id=request.session_id)
161
+ if request.session_id and request.session and request.session_id != request.session.id:
162
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Session ID mismatch")
163
+
164
+ session = request.session or (
165
+ (
166
+ await session_store.get(request.session_id)
167
+ or Session(id=request.session_id, loader=resource_loader, store=resource_store)
168
+ )
140
169
  if request.session_id
141
- else Session()
170
+ else Session(loader=resource_loader, store=resource_store)
142
171
  )
172
+
143
173
  nonlocal executor
144
174
  run_data = RunData(
145
- run=Run(agent_name=agent.name, session_id=session.id),
146
- input=request.input,
175
+ run=Run(
176
+ agent_name=agent.name,
177
+ session_id=session.id,
178
+ )
147
179
  )
148
180
  await run_store.set(run_data.key, run_data)
149
-
150
- session.append(run_data.run.run_id)
151
181
  await session_store.set(session.id, session)
152
182
 
153
183
  headers = {Headers.RUN_ID: str(run_data.run.run_id)}
154
184
  ready = asyncio.Event()
155
185
 
186
+ async def create_resource_url(id: ResourceId) -> ResourceUrl:
187
+ if forward_resources:
188
+ return ResourceUrl(url=str(req.url_for("get_resource", resource_id=id)))
189
+ else:
190
+ return await resource_store.url(id)
191
+
156
192
  Executor(
157
193
  agent=agent,
158
194
  run_data=run_data,
159
- history=await session.history(run_store),
195
+ session=session,
196
+ session_store=session_store,
160
197
  run_store=run_store,
161
198
  cancel_store=run_cancel_store,
162
199
  resume_store=run_resume_store,
163
200
  executor=executor,
164
- ).execute(wait=ready)
201
+ request=req,
202
+ resource_store=resource_store,
203
+ resource_loader=resource_loader,
204
+ create_resource_url=create_resource_url,
205
+ ).execute(request.input, wait=ready)
165
206
 
166
207
  match request.mode:
167
208
  case RunMode.STREAM:
@@ -242,4 +283,21 @@ def create_app(
242
283
  run_data.run.status = RunStatus.CANCELLING
243
284
  return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder(run_data.run))
244
285
 
286
+ @app.get("/sessions/{session_id}")
287
+ async def read_session(session_id: SessionId) -> SessionReadResponse:
288
+ session = await session_store.get(session_id)
289
+ if not session:
290
+ raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
291
+ return session
292
+
293
+ if forward_resources:
294
+
295
+ @app.get("/resources/{resource_id}", name="get_resource")
296
+ async def read_resource(resource_id: ResourceId) -> StreamingResponse:
297
+ try:
298
+ result = await resource_store.load(resource_id)
299
+ return StreamingResponse(result)
300
+ except NotFoundError:
301
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Resource {resource_id} not found")
302
+
245
303
  return app
acp_sdk/server/context.py CHANGED
@@ -1,22 +1,30 @@
1
1
  from concurrent.futures import ThreadPoolExecutor
2
2
 
3
3
  import janus
4
+ from fastapi import Request
4
5
 
5
- from acp_sdk.models import SessionId
6
+ from acp_sdk.models import Session
6
7
  from acp_sdk.server.types import RunYield, RunYieldResume
8
+ from acp_sdk.shared import ResourceLoader, ResourceStore
7
9
 
8
10
 
9
11
  class Context:
10
12
  def __init__(
11
13
  self,
12
14
  *,
13
- session_id: SessionId | None = None,
15
+ session: Session,
16
+ store: ResourceStore,
17
+ loader: ResourceLoader,
14
18
  executor: ThreadPoolExecutor,
19
+ request: Request,
15
20
  yield_queue: janus.Queue[RunYield],
16
21
  yield_resume_queue: janus.Queue[RunYieldResume],
17
22
  ) -> None:
18
- self.session_id = session_id
23
+ self.session = session
24
+ self.storage = store
25
+ self.loader = loader
19
26
  self.executor = executor
27
+ self.request = request
20
28
  self._yield_queue = yield_queue
21
29
  self._yield_resume_queue = yield_resume_queue
22
30
 
@@ -1,10 +1,14 @@
1
1
  import asyncio
2
+ import inspect
2
3
  import logging
3
- from collections.abc import AsyncIterator
4
+ import uuid
5
+ from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Generator
4
6
  from concurrent.futures import ThreadPoolExecutor
5
7
  from datetime import datetime, timezone
6
- from typing import Self
8
+ from typing import Callable, Self
7
9
 
10
+ import janus
11
+ from fastapi import Request
8
12
  from pydantic import BaseModel, ValidationError
9
13
 
10
14
  from acp_sdk.instrumentation import get_tracer
@@ -22,6 +26,8 @@ from acp_sdk.models import (
22
26
  MessageCreatedEvent,
23
27
  MessagePart,
24
28
  MessagePartEvent,
29
+ ResourceId,
30
+ ResourceUrl,
25
31
  Run,
26
32
  RunAwaitingEvent,
27
33
  RunCancelledEvent,
@@ -30,15 +36,18 @@ from acp_sdk.models import (
30
36
  RunFailedEvent,
31
37
  RunInProgressEvent,
32
38
  RunStatus,
39
+ Session,
33
40
  )
34
- from acp_sdk.server.agent import Agent
41
+ from acp_sdk.server.agent import AgentManifest
42
+ from acp_sdk.server.context import Context
35
43
  from acp_sdk.server.logging import logger
36
44
  from acp_sdk.server.store import Store
45
+ from acp_sdk.server.types import RunYield, RunYieldResume
46
+ from acp_sdk.shared import ResourceLoader, ResourceStore
37
47
 
38
48
 
39
49
  class RunData(BaseModel):
40
50
  run: Run
41
- input: list[Message]
42
51
  events: list[Event] = []
43
52
 
44
53
  @property
@@ -62,27 +71,38 @@ class Executor:
62
71
  def __init__(
63
72
  self,
64
73
  *,
65
- agent: Agent,
74
+ agent: AgentManifest,
66
75
  run_data: RunData,
67
- history: list[Message],
76
+ session: Session,
68
77
  executor: ThreadPoolExecutor,
78
+ request: Request,
69
79
  run_store: Store[RunData],
70
80
  cancel_store: Store[CancelData],
71
81
  resume_store: Store[AwaitResume],
82
+ session_store: Store[Session],
83
+ resource_store: ResourceStore,
84
+ resource_loader: ResourceLoader,
85
+ create_resource_url: Callable[[ResourceId], Awaitable[ResourceUrl]],
72
86
  ) -> None:
73
87
  self.agent = agent
74
- self.history = history
88
+ self.session = session
75
89
  self.run_data = run_data
76
90
  self.executor = executor
91
+ self.request = request
77
92
 
78
93
  self.run_store = run_store
79
94
  self.cancel_store = cancel_store
80
95
  self.resume_store = resume_store
96
+ self.session_store = session_store
97
+ self.resource_store = resource_store
98
+ self.resource_loader = resource_loader
99
+
100
+ self.create_resource_url = create_resource_url
81
101
 
82
102
  self.logger = logging.LoggerAdapter(logger, {"run_id": str(run_data.run.run_id)})
83
103
 
84
- def execute(self, *, wait: asyncio.Event) -> None:
85
- self.task = asyncio.create_task(self._execute(self.run_data, executor=self.executor, wait=wait))
104
+ def execute(self, input: list[Message], *, wait: asyncio.Event) -> None:
105
+ self.task = asyncio.create_task(self._execute(input=input, executor=self.executor, wait=wait))
86
106
  self.watcher = asyncio.create_task(self._watch_for_cancellation())
87
107
 
88
108
  async def _push(self) -> None:
@@ -108,7 +128,16 @@ class Executor:
108
128
  except Exception:
109
129
  logger.warning("Cancellation watcher failed, restarting")
110
130
 
111
- async def _execute(self, run_data: RunData, *, executor: ThreadPoolExecutor, wait: asyncio.Event) -> None:
131
+ async def _record_session(self, history: list[Message]) -> None:
132
+ for message in history:
133
+ id = uuid.uuid4()
134
+ url = await self.create_resource_url(id)
135
+ await self.resource_store.store(id, message.model_dump_json().encode())
136
+ self.session.history.append(url)
137
+ await self.session_store.set(self.session.id, self.session)
138
+
139
+ async def _execute(self, input: list[Message], *, executor: ThreadPoolExecutor, wait: asyncio.Event) -> None:
140
+ run_data = self.run_data
112
141
  with get_tracer().start_as_current_span("run"):
113
142
  in_message = False
114
143
 
@@ -118,15 +147,22 @@ class Executor:
118
147
  message = run_data.run.output[-1]
119
148
  message.completed_at = datetime.now(timezone.utc)
120
149
  await self._emit(MessageCompletedEvent(message=message))
150
+ session_history.append(message)
121
151
  in_message = False
122
152
 
153
+ session_history = input.copy()
123
154
  try:
124
155
  await wait.wait()
125
156
 
126
157
  await self._emit(RunCreatedEvent(run=run_data.run))
127
158
 
128
- generator = self.agent.execute(
129
- input=self.history + run_data.input, session_id=run_data.run.session_id, executor=executor
159
+ generator = self._execute_agent(
160
+ input=input,
161
+ session=self.session,
162
+ storage=self.resource_store,
163
+ loader=self.resource_loader,
164
+ executor=executor,
165
+ request=self.request,
130
166
  )
131
167
  self.logger.info("Run started")
132
168
 
@@ -141,18 +177,21 @@ class Executor:
141
177
  if isinstance(next, str):
142
178
  next = MessagePart(content=next)
143
179
  if not in_message:
144
- run_data.run.output.append(Message(parts=[], completed_at=None))
180
+ run_data.run.output.append(
181
+ Message(role=f"agent/{self.agent.name}", parts=[], completed_at=None)
182
+ )
145
183
  in_message = True
146
184
  await self._emit(MessageCreatedEvent(message=run_data.run.output[-1]))
147
185
  run_data.run.output[-1].parts.append(next)
148
186
  await self._emit(MessagePartEvent(part=next))
149
187
  elif isinstance(next, Message):
150
188
  await flush_message()
151
- run_data.run.output.append(next)
189
+ run_data.run.output.append(next.model_copy(update={"role": f"agent/{self.agent.name}"}))
152
190
  await self._emit(MessageCreatedEvent(message=next))
153
191
  for part in next.parts:
154
192
  await self._emit(MessagePartEvent(part=part))
155
193
  await self._emit(MessageCompletedEvent(message=next))
194
+ session_history.append(next)
156
195
  elif isinstance(next, AwaitRequest):
157
196
  run_data.run.await_request = next
158
197
  run_data.run.status = RunStatus.AWAITING
@@ -180,6 +219,10 @@ class Executor:
180
219
  await flush_message()
181
220
  run_data.run.status = RunStatus.COMPLETED
182
221
  run_data.run.finished_at = datetime.now(timezone.utc)
222
+ try:
223
+ await self._record_session(session_history)
224
+ except Exception as e:
225
+ self.logger.warning(f"Failed to record session: {e}")
183
226
  await self._emit(RunCompletedEvent(run=run_data.run))
184
227
  self.logger.info("Run completed")
185
228
  except asyncio.CancelledError:
@@ -196,3 +239,85 @@ class Executor:
196
239
  run_data.run.finished_at = datetime.now(timezone.utc)
197
240
  await self._emit(RunFailedEvent(run=run_data.run))
198
241
  self.logger.exception("Run failed")
242
+
243
+ async def _execute_agent(
244
+ self,
245
+ input: list[Message],
246
+ session: Session,
247
+ storage: ResourceStore,
248
+ loader: ResourceLoader,
249
+ executor: ThreadPoolExecutor,
250
+ request: Request,
251
+ ) -> AsyncGenerator[RunYield, RunYieldResume]:
252
+ yield_queue: janus.Queue[RunYield] = janus.Queue()
253
+ yield_resume_queue: janus.Queue[RunYieldResume] = janus.Queue()
254
+
255
+ context = Context(
256
+ session=session,
257
+ store=storage,
258
+ loader=loader,
259
+ executor=executor,
260
+ request=request,
261
+ yield_queue=yield_queue,
262
+ yield_resume_queue=yield_resume_queue,
263
+ )
264
+
265
+ if inspect.isasyncgenfunction(self.agent.run):
266
+ run = asyncio.create_task(self._run_async_gen(input, context))
267
+ elif inspect.iscoroutinefunction(self.agent.run):
268
+ run = asyncio.create_task(self._run_coro(input, context))
269
+ elif inspect.isgeneratorfunction(self.agent.run):
270
+ run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen, input, context)
271
+ else:
272
+ run = asyncio.get_running_loop().run_in_executor(executor, self._run_func, input, context)
273
+
274
+ try:
275
+ while not run.done() or yield_queue.async_q.qsize() > 0:
276
+ value = yield await yield_queue.async_q.get()
277
+ if isinstance(value, Exception):
278
+ raise value
279
+ await yield_resume_queue.async_q.put(value)
280
+ except janus.AsyncQueueShutDown:
281
+ pass
282
+
283
+ async def _run_async_gen(self, input: list[Message], context: Context) -> None:
284
+ try:
285
+ gen: AsyncGenerator[RunYield, RunYieldResume] = self.agent.run(input, context)
286
+ value = None
287
+ while True:
288
+ value = await context.yield_async(await gen.asend(value))
289
+ except StopAsyncIteration:
290
+ pass
291
+ except Exception as e:
292
+ await context.yield_async(e)
293
+ finally:
294
+ context.shutdown()
295
+
296
+ async def _run_coro(self, input: list[Message], context: Context) -> None:
297
+ try:
298
+ await context.yield_async(await self.agent.run(input, context))
299
+ except Exception as e:
300
+ await context.yield_async(e)
301
+ finally:
302
+ context.shutdown()
303
+
304
+ def _run_gen(self, input: list[Message], context: Context) -> None:
305
+ try:
306
+ gen: Generator[RunYield, RunYieldResume] = self.agent.run(input, context)
307
+ value = None
308
+ while True:
309
+ value = context.yield_sync(gen.send(value))
310
+ except StopIteration:
311
+ pass
312
+ except Exception as e:
313
+ context.yield_sync(e)
314
+ finally:
315
+ context.shutdown()
316
+
317
+ def _run_func(self, input: list[Message], context: Context) -> None:
318
+ try:
319
+ context.yield_sync(self.agent.run(input, context))
320
+ except Exception as e:
321
+ context.yield_sync(e)
322
+ finally:
323
+ context.shutdown()
acp_sdk/server/server.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import os
3
+ import re
3
4
  from collections.abc import AsyncGenerator, Awaitable
4
5
  from contextlib import asynccontextmanager
5
6
  from typing import Any, Callable
@@ -10,7 +11,7 @@ import uvicorn.config
10
11
  from fastapi import FastAPI
11
12
 
12
13
  from acp_sdk.models import Metadata
13
- from acp_sdk.server.agent import Agent
14
+ from acp_sdk.server.agent import AgentManifest
14
15
  from acp_sdk.server.agent import agent as agent_decorator
15
16
  from acp_sdk.server.app import create_app
16
17
  from acp_sdk.server.logging import configure_logger as configure_logger_func
@@ -18,11 +19,12 @@ from acp_sdk.server.logging import logger
18
19
  from acp_sdk.server.store import Store
19
20
  from acp_sdk.server.telemetry import configure_telemetry as configure_telemetry_func
20
21
  from acp_sdk.server.utils import async_request_with_retry
22
+ from acp_sdk.shared.resources import ResourceLoader, ResourceStore
21
23
 
22
24
 
23
25
  class Server:
24
26
  def __init__(self) -> None:
25
- self.agents: list[Agent] = []
27
+ self.agents: list[AgentManifest] = []
26
28
  self.server: uvicorn.Server | None = None
27
29
 
28
30
  def agent(
@@ -41,7 +43,7 @@ class Server:
41
43
 
42
44
  return decorator
43
45
 
44
- def register(self, *agents: Agent) -> None:
46
+ def register(self, *agents: AgentManifest) -> None:
45
47
  self.agents.extend(agents)
46
48
 
47
49
  @asynccontextmanager
@@ -55,6 +57,8 @@ class Server:
55
57
  configure_telemetry: bool = False,
56
58
  self_registration: bool = True,
57
59
  store: Store | None = None,
60
+ resource_store: ResourceStore | None = None,
61
+ resource_loader: ResourceLoader | None = None,
58
62
  host: str = "127.0.0.1",
59
63
  port: int = 8000,
60
64
  uds: str | None = None,
@@ -117,7 +121,13 @@ class Server:
117
121
 
118
122
  import uvicorn
119
123
 
120
- app = create_app(*self.agents, lifespan=self.lifespan, store=store)
124
+ app = create_app(
125
+ *self.agents,
126
+ lifespan=self.lifespan,
127
+ store=store,
128
+ resource_loader=resource_loader,
129
+ resource_store=resource_store,
130
+ )
121
131
 
122
132
  if configure_logger:
123
133
  configure_logger_func()
@@ -184,6 +194,8 @@ class Server:
184
194
  configure_telemetry: bool = False,
185
195
  self_registration: bool = True,
186
196
  store: Store | None = None,
197
+ resource_store: ResourceStore | None = None,
198
+ resource_loader: ResourceLoader | None = None,
187
199
  host: str = "127.0.0.1",
188
200
  port: int = 8000,
189
201
  uds: str | None = None,
@@ -242,6 +254,8 @@ class Server:
242
254
  configure_telemetry=configure_telemetry,
243
255
  self_registration=self_registration,
244
256
  store=store,
257
+ resource_store=resource_store,
258
+ resource_loader=resource_loader,
245
259
  host=host,
246
260
  port=port,
247
261
  uds=uds,
@@ -313,22 +327,33 @@ class Server:
313
327
  return
314
328
 
315
329
  url = os.getenv("PLATFORM_URL", "http://127.0.0.1:8333")
330
+ host = re.sub(r"localhost|127\.0\.0\.1", "host.docker.internal", self.server.config.host)
316
331
  request_data = {
317
- "location": f"http://{self.server.config.host}:{self.server.config.port}",
332
+ "location": f"http://{host}:{self.server.config.port}",
318
333
  }
334
+ await async_request_with_retry(lambda client, data=request_data: client.get(f"{url}/api/v1/providers"))
319
335
  try:
320
336
  await async_request_with_retry(
321
- lambda client, data=request_data: client.post(f"{url}/api/v1/providers/register/unmanaged", json=data)
337
+ lambda client, data=request_data: client.post(
338
+ f"{url}/api/v1/providers", json=data, params={"auto_remove": True}
339
+ )
322
340
  )
323
341
  logger.info("Agent registered to the beeai server.")
324
342
 
325
343
  # check missing env keyes
326
344
  envs_request = await async_request_with_retry(lambda client: client.get(f"{url}/api/v1/variables"))
327
345
  envs = envs_request.get("env")
346
+ os.environ["LLM_MODEL"] = "dummy"
347
+ os.environ["LLM_API_KEY"] = "dummy"
348
+ os.environ["LLM_API_BASE"] = f"{url.rstrip('/')}/api/v1/llm"
349
+
328
350
  for agent in self.agents:
329
351
  # register all available envs
330
352
  missing_keyes = []
331
353
  for env in agent.metadata.model_dump().get("env", []):
354
+ # Those envs are set to use LLM gateway from platform server
355
+ if env["name"] in {"LLM_MODEL", "LLM_API_KEY", "LLM_API_BASE"}:
356
+ continue
332
357
  server_env = envs.get(env.get("name"))
333
358
  if server_env:
334
359
  logger.debug(f"Env variable {env['name']} = '{server_env}' added dynamically")
@@ -5,25 +5,25 @@ from typing import Generic
5
5
 
6
6
  from cachetools import TTLCache
7
7
 
8
- from acp_sdk.server.store.store import Store, T
8
+ from acp_sdk.server.store.store import Store, StoreModel, T
9
9
  from acp_sdk.server.store.utils import Stringable
10
10
 
11
11
 
12
12
  class MemoryStore(Store[T], Generic[T]):
13
13
  def __init__(self, *, limit: int, ttl: int | None = None) -> None:
14
14
  super().__init__()
15
- self._cache: TTLCache[str, T] = TTLCache(maxsize=limit, ttl=ttl, timer=datetime.now)
15
+ self._cache: TTLCache[str, str] = TTLCache(maxsize=limit, ttl=ttl, timer=datetime.now)
16
16
  self._event = asyncio.Event()
17
17
 
18
18
  async def get(self, key: Stringable) -> T | None:
19
19
  value = self._cache.get(str(key))
20
- return value.model_copy(deep=True) if value else value
20
+ return StoreModel.model_validate_json(value) if value else value
21
21
 
22
22
  async def set(self, key: Stringable, value: T | None) -> None:
23
23
  if value is None:
24
24
  del self._cache[str(key)]
25
25
  else:
26
- self._cache[str(key)] = value.model_copy(deep=True)
26
+ self._cache[str(key)] = value.model_dump_json()
27
27
  self._event.set()
28
28
 
29
29
  async def watch(self, key: Stringable, *, ready: asyncio.Event | None = None) -> AsyncIterator[T | None]:
acp_sdk/server/utils.py CHANGED
@@ -9,7 +9,7 @@ from pydantic import BaseModel
9
9
  from acp_sdk.models import RunStatus
10
10
  from acp_sdk.server.executor import RunData
11
11
  from acp_sdk.server.logging import logger
12
- from acp_sdk.server.store.store import Store
12
+ from acp_sdk.server.store import Store
13
13
 
14
14
 
15
15
  def encode_sse(model: BaseModel) -> str:
@@ -0,0 +1,2 @@
1
+ from acp_sdk.shared.resources import ResourceLoader as ResourceLoader
2
+ from acp_sdk.shared.resources import ResourceStore as ResourceStore