Skip to content

Commit 90ecd1e

Browse files
committed
add create_from_* functions to simplify calling conventions for agent creation
1 parent 72267bc commit 90ecd1e

2 files changed

Lines changed: 352 additions & 0 deletions

File tree

src/runloop_api_client/sdk/sync.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,151 @@ def create(
576576
)
577577
return Agent(self._client, agent_view.id, agent_view)
578578

579+
def create_from_npm(
580+
self,
581+
*,
582+
package_name: str,
583+
npm_version: Optional[str] = None,
584+
registry_url: Optional[str] = None,
585+
agent_setup: Optional[list[str]] = None,
586+
**params: Unpack[SDKAgentCreateParams],
587+
) -> Agent:
588+
"""Create an agent from an NPM package.
589+
590+
:param package_name: NPM package name
591+
:type package_name: str
592+
:param npm_version: NPM version constraint, defaults to None
593+
:type npm_version: Optional[str], optional
594+
:param registry_url: NPM registry URL, defaults to None
595+
:type registry_url: Optional[str], optional
596+
:param agent_setup: Setup commands to run after installation, defaults to None
597+
:type agent_setup: Optional[list[str]], optional
598+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
599+
:return: Wrapper bound to the newly created agent
600+
:rtype: Agent
601+
:raises ValueError: If 'source' is provided in params
602+
"""
603+
if "source" in params:
604+
raise ValueError("Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration")
605+
606+
npm_config: dict = {"package_name": package_name}
607+
if npm_version is not None:
608+
npm_config["npm_version"] = npm_version
609+
if registry_url is not None:
610+
npm_config["registry_url"] = registry_url
611+
if agent_setup is not None:
612+
npm_config["agent_setup"] = agent_setup
613+
614+
return self.create(
615+
source={"type": "npm", "npm": npm_config},
616+
**params,
617+
)
618+
619+
def create_from_pip(
620+
self,
621+
*,
622+
package_name: str,
623+
pip_version: Optional[str] = None,
624+
registry_url: Optional[str] = None,
625+
agent_setup: Optional[list[str]] = None,
626+
**params: Unpack[SDKAgentCreateParams],
627+
) -> Agent:
628+
"""Create an agent from a Pip package.
629+
630+
:param package_name: Pip package name
631+
:type package_name: str
632+
:param pip_version: Pip version constraint, defaults to None
633+
:type pip_version: Optional[str], optional
634+
:param registry_url: Pip registry URL, defaults to None
635+
:type registry_url: Optional[str], optional
636+
:param agent_setup: Setup commands to run after installation, defaults to None
637+
:type agent_setup: Optional[list[str]], optional
638+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
639+
:return: Wrapper bound to the newly created agent
640+
:rtype: Agent
641+
:raises ValueError: If 'source' is provided in params
642+
"""
643+
if "source" in params:
644+
raise ValueError("Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration")
645+
646+
pip_config: dict = {"package_name": package_name}
647+
if pip_version is not None:
648+
pip_config["pip_version"] = pip_version
649+
if registry_url is not None:
650+
pip_config["registry_url"] = registry_url
651+
if agent_setup is not None:
652+
pip_config["agent_setup"] = agent_setup
653+
654+
return self.create(
655+
source={"type": "pip", "pip": pip_config},
656+
**params,
657+
)
658+
659+
def create_from_git(
660+
self,
661+
*,
662+
repository: str,
663+
ref: Optional[str] = None,
664+
agent_setup: Optional[list[str]] = None,
665+
**params: Unpack[SDKAgentCreateParams],
666+
) -> Agent:
667+
"""Create an agent from a Git repository.
668+
669+
:param repository: Git repository URL
670+
:type repository: str
671+
:param ref: Optional Git ref (branch/tag/commit), defaults to main/HEAD
672+
:type ref: Optional[str], optional
673+
:param agent_setup: Setup commands to run after cloning, defaults to None
674+
:type agent_setup: Optional[list[str]], optional
675+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
676+
:return: Wrapper bound to the newly created agent
677+
:rtype: Agent
678+
:raises ValueError: If 'source' is provided in params
679+
"""
680+
if "source" in params:
681+
raise ValueError("Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration")
682+
683+
git_config: dict = {"repository": repository}
684+
if ref is not None:
685+
git_config["ref"] = ref
686+
if agent_setup is not None:
687+
git_config["agent_setup"] = agent_setup
688+
689+
return self.create(
690+
source={"type": "git", "git": git_config},
691+
**params,
692+
)
693+
694+
def create_from_object(
695+
self,
696+
*,
697+
object_id: str,
698+
agent_setup: Optional[list[str]] = None,
699+
**params: Unpack[SDKAgentCreateParams],
700+
) -> Agent:
701+
"""Create an agent from a storage object.
702+
703+
:param object_id: Storage object ID
704+
:type object_id: str
705+
:param agent_setup: Setup commands to run after unpacking, defaults to None
706+
:type agent_setup: Optional[list[str]], optional
707+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
708+
:return: Wrapper bound to the newly created agent
709+
:rtype: Agent
710+
:raises ValueError: If 'source' is provided in params
711+
"""
712+
if "source" in params:
713+
raise ValueError("Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration")
714+
715+
object_config: dict = {"object_id": object_id}
716+
if agent_setup is not None:
717+
object_config["agent_setup"] = agent_setup
718+
719+
return self.create(
720+
source={"type": "object", "object": object_config},
721+
**params,
722+
)
723+
579724
def from_id(self, agent_id: str) -> Agent:
580725
"""Attach to an existing agent by ID.
581726

tests/sdk/test_ops.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,213 @@ def test_list(self, mock_client: Mock) -> None:
752752

753753
mock_client.agents.list.assert_called_once()
754754

755+
def test_create_from_npm(self, mock_client: Mock, agent_view: MockAgentView) -> None:
756+
"""Test create_from_npm factory method."""
757+
mock_client.agents.create.return_value = agent_view
758+
759+
client = AgentOps(mock_client)
760+
agent = client.create_from_npm(
761+
name="test-agent",
762+
package_name="@runloop/example-agent",
763+
)
764+
765+
assert isinstance(agent, Agent)
766+
assert agent.id == "agent_123"
767+
mock_client.agents.create.assert_called_once_with(
768+
source={
769+
"type": "npm",
770+
"npm": {
771+
"package_name": "@runloop/example-agent",
772+
},
773+
},
774+
name="test-agent",
775+
)
776+
777+
def test_create_from_npm_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
778+
"""Test create_from_npm factory method with all optional parameters."""
779+
mock_client.agents.create.return_value = agent_view
780+
781+
client = AgentOps(mock_client)
782+
agent = client.create_from_npm(
783+
package_name="@runloop/example-agent",
784+
npm_version="1.2.3",
785+
registry_url="https://registry.example.com",
786+
agent_setup=["npm install", "npm run setup"],
787+
name="test-agent",
788+
extra_headers={"X-Custom": "header"},
789+
)
790+
791+
assert isinstance(agent, Agent)
792+
assert agent.id == "agent_123"
793+
mock_client.agents.create.assert_called_once_with(
794+
source={
795+
"type": "npm",
796+
"npm": {
797+
"package_name": "@runloop/example-agent",
798+
"npm_version": "1.2.3",
799+
"registry_url": "https://registry.example.com",
800+
"agent_setup": ["npm install", "npm run setup"],
801+
},
802+
},
803+
name="test-agent",
804+
extra_headers={"X-Custom": "header"},
805+
)
806+
807+
def test_create_from_npm_raises_when_source_provided(self, mock_client: Mock) -> None:
808+
"""Test create_from_npm raises ValueError when source is provided in params."""
809+
client = AgentOps(mock_client)
810+
811+
with pytest.raises(ValueError, match="Cannot specify 'source' when using create_from_npm"):
812+
client.create_from_npm(
813+
package_name="@runloop/example-agent",
814+
name="test-agent",
815+
source={"type": "git", "git": {"repository": "https://github.com/example/repo"}},
816+
)
817+
818+
def test_create_from_pip(self, mock_client: Mock, agent_view: MockAgentView) -> None:
819+
"""Test create_from_pip factory method."""
820+
mock_client.agents.create.return_value = agent_view
821+
822+
client = AgentOps(mock_client)
823+
agent = client.create_from_pip(
824+
package_name="runloop-example-agent",
825+
name="test-agent",
826+
)
827+
828+
assert isinstance(agent, Agent)
829+
assert agent.id == "agent_123"
830+
mock_client.agents.create.assert_called_once_with(
831+
source={
832+
"type": "pip",
833+
"pip": {
834+
"package_name": "runloop-example-agent",
835+
},
836+
},
837+
name="test-agent",
838+
)
839+
840+
def test_create_from_pip_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
841+
"""Test create_from_pip factory method with all optional parameters."""
842+
mock_client.agents.create.return_value = agent_view
843+
844+
client = AgentOps(mock_client)
845+
agent = client.create_from_pip(
846+
package_name="runloop-example-agent",
847+
pip_version="1.2.3",
848+
registry_url="https://pypi.example.com",
849+
agent_setup=["pip install extra-deps"],
850+
name="test-agent",
851+
)
852+
853+
assert isinstance(agent, Agent)
854+
assert agent.id == "agent_123"
855+
mock_client.agents.create.assert_called_once_with(
856+
source={
857+
"type": "pip",
858+
"pip": {
859+
"package_name": "runloop-example-agent",
860+
"pip_version": "1.2.3",
861+
"registry_url": "https://pypi.example.com",
862+
"agent_setup": ["pip install extra-deps"],
863+
},
864+
},
865+
name="test-agent",
866+
)
867+
868+
def test_create_from_git(self, mock_client: Mock, agent_view: MockAgentView) -> None:
869+
"""Test create_from_git factory method."""
870+
mock_client.agents.create.return_value = agent_view
871+
872+
client = AgentOps(mock_client)
873+
agent = client.create_from_git(
874+
repository="https://github.com/example/agent-repo",
875+
name="test-agent",
876+
)
877+
878+
assert isinstance(agent, Agent)
879+
assert agent.id == "agent_123"
880+
mock_client.agents.create.assert_called_once_with(
881+
source={
882+
"type": "git",
883+
"git": {
884+
"repository": "https://github.com/example/agent-repo",
885+
},
886+
},
887+
name="test-agent",
888+
)
889+
890+
def test_create_from_git_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
891+
"""Test create_from_git factory method with all optional parameters."""
892+
mock_client.agents.create.return_value = agent_view
893+
894+
client = AgentOps(mock_client)
895+
agent = client.create_from_git(
896+
repository="https://github.com/example/agent-repo",
897+
ref="develop",
898+
agent_setup=["npm install", "npm run build"],
899+
name="test-agent",
900+
)
901+
902+
assert isinstance(agent, Agent)
903+
assert agent.id == "agent_123"
904+
mock_client.agents.create.assert_called_once_with(
905+
source={
906+
"type": "git",
907+
"git": {
908+
"repository": "https://github.com/example/agent-repo",
909+
"ref": "develop",
910+
"agent_setup": ["npm install", "npm run build"],
911+
},
912+
},
913+
name="test-agent",
914+
)
915+
916+
def test_create_from_object(self, mock_client: Mock, agent_view: MockAgentView) -> None:
917+
"""Test create_from_object factory method."""
918+
mock_client.agents.create.return_value = agent_view
919+
920+
client = AgentOps(mock_client)
921+
agent = client.create_from_object(
922+
object_id="obj_123",
923+
name="test-agent",
924+
)
925+
926+
assert isinstance(agent, Agent)
927+
assert agent.id == "agent_123"
928+
mock_client.agents.create.assert_called_once_with(
929+
source={
930+
"type": "object",
931+
"object": {
932+
"object_id": "obj_123",
933+
},
934+
},
935+
name="test-agent",
936+
)
937+
938+
def test_create_from_object_with_agent_setup(self, mock_client: Mock, agent_view: MockAgentView) -> None:
939+
"""Test create_from_object factory method with agent_setup."""
940+
mock_client.agents.create.return_value = agent_view
941+
942+
client = AgentOps(mock_client)
943+
agent = client.create_from_object(
944+
object_id="obj_123",
945+
agent_setup=["chmod +x setup.sh", "./setup.sh"],
946+
name="test-agent",
947+
)
948+
949+
assert isinstance(agent, Agent)
950+
assert agent.id == "agent_123"
951+
mock_client.agents.create.assert_called_once_with(
952+
source={
953+
"type": "object",
954+
"object": {
955+
"object_id": "obj_123",
956+
"agent_setup": ["chmod +x setup.sh", "./setup.sh"],
957+
},
958+
},
959+
name="test-agent",
960+
)
961+
755962

756963
class TestRunloopSDK:
757964
"""Tests for RunloopSDK class."""

0 commit comments

Comments
 (0)