Skip to content

Commit 5f8f365

Browse files
committed
add create_from_* functions to simplify calling conventions for agent creation
1 parent 38ce9ea commit 5f8f365

File tree

2 files changed

+352
-0
lines changed

2 files changed

+352
-0
lines changed

src/runloop_api_client/sdk/sync.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,151 @@ def create(
508508
)
509509
return Agent(self._client, agent_view.id, agent_view)
510510

511+
def create_from_npm(
512+
self,
513+
*,
514+
package_name: str,
515+
npm_version: Optional[str] = None,
516+
registry_url: Optional[str] = None,
517+
agent_setup: Optional[list[str]] = None,
518+
**params: Unpack[SDKAgentCreateParams],
519+
) -> Agent:
520+
"""Create an agent from an NPM package.
521+
522+
:param package_name: NPM package name
523+
:type package_name: str
524+
:param npm_version: NPM version constraint, defaults to None
525+
:type npm_version: Optional[str], optional
526+
:param registry_url: NPM registry URL, defaults to None
527+
:type registry_url: Optional[str], optional
528+
:param agent_setup: Setup commands to run after installation, defaults to None
529+
:type agent_setup: Optional[list[str]], optional
530+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
531+
:return: Wrapper bound to the newly created agent
532+
:rtype: Agent
533+
:raises ValueError: If 'source' is provided in params
534+
"""
535+
if "source" in params:
536+
raise ValueError("Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration")
537+
538+
npm_config: dict = {"package_name": package_name}
539+
if npm_version is not None:
540+
npm_config["npm_version"] = npm_version
541+
if registry_url is not None:
542+
npm_config["registry_url"] = registry_url
543+
if agent_setup is not None:
544+
npm_config["agent_setup"] = agent_setup
545+
546+
return self.create(
547+
source={"type": "npm", "npm": npm_config},
548+
**params,
549+
)
550+
551+
def create_from_pip(
552+
self,
553+
*,
554+
package_name: str,
555+
pip_version: Optional[str] = None,
556+
registry_url: Optional[str] = None,
557+
agent_setup: Optional[list[str]] = None,
558+
**params: Unpack[SDKAgentCreateParams],
559+
) -> Agent:
560+
"""Create an agent from a Pip package.
561+
562+
:param package_name: Pip package name
563+
:type package_name: str
564+
:param pip_version: Pip version constraint, defaults to None
565+
:type pip_version: Optional[str], optional
566+
:param registry_url: Pip registry URL, defaults to None
567+
:type registry_url: Optional[str], optional
568+
:param agent_setup: Setup commands to run after installation, defaults to None
569+
:type agent_setup: Optional[list[str]], optional
570+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
571+
:return: Wrapper bound to the newly created agent
572+
:rtype: Agent
573+
:raises ValueError: If 'source' is provided in params
574+
"""
575+
if "source" in params:
576+
raise ValueError("Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration")
577+
578+
pip_config: dict = {"package_name": package_name}
579+
if pip_version is not None:
580+
pip_config["pip_version"] = pip_version
581+
if registry_url is not None:
582+
pip_config["registry_url"] = registry_url
583+
if agent_setup is not None:
584+
pip_config["agent_setup"] = agent_setup
585+
586+
return self.create(
587+
source={"type": "pip", "pip": pip_config},
588+
**params,
589+
)
590+
591+
def create_from_git(
592+
self,
593+
*,
594+
repository: str,
595+
ref: Optional[str] = None,
596+
agent_setup: Optional[list[str]] = None,
597+
**params: Unpack[SDKAgentCreateParams],
598+
) -> Agent:
599+
"""Create an agent from a Git repository.
600+
601+
:param repository: Git repository URL
602+
:type repository: str
603+
:param ref: Optional Git ref (branch/tag/commit), defaults to main/HEAD
604+
:type ref: Optional[str], optional
605+
:param agent_setup: Setup commands to run after cloning, defaults to None
606+
:type agent_setup: Optional[list[str]], optional
607+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
608+
:return: Wrapper bound to the newly created agent
609+
:rtype: Agent
610+
:raises ValueError: If 'source' is provided in params
611+
"""
612+
if "source" in params:
613+
raise ValueError("Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration")
614+
615+
git_config: dict = {"repository": repository}
616+
if ref is not None:
617+
git_config["ref"] = ref
618+
if agent_setup is not None:
619+
git_config["agent_setup"] = agent_setup
620+
621+
return self.create(
622+
source={"type": "git", "git": git_config},
623+
**params,
624+
)
625+
626+
def create_from_object(
627+
self,
628+
*,
629+
object_id: str,
630+
agent_setup: Optional[list[str]] = None,
631+
**params: Unpack[SDKAgentCreateParams],
632+
) -> Agent:
633+
"""Create an agent from a storage object.
634+
635+
:param object_id: Storage object ID
636+
:type object_id: str
637+
:param agent_setup: Setup commands to run after unpacking, defaults to None
638+
:type agent_setup: Optional[list[str]], optional
639+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
640+
:return: Wrapper bound to the newly created agent
641+
:rtype: Agent
642+
:raises ValueError: If 'source' is provided in params
643+
"""
644+
if "source" in params:
645+
raise ValueError("Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration")
646+
647+
object_config: dict = {"object_id": object_id}
648+
if agent_setup is not None:
649+
object_config["agent_setup"] = agent_setup
650+
651+
return self.create(
652+
source={"type": "object", "object": object_config},
653+
**params,
654+
)
655+
511656
def from_id(self, agent_id: str) -> Agent:
512657
"""Attach to an existing agent by ID.
513658

tests/sdk/test_clients.py

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

572572
mock_client.agents.list.assert_called_once()
573573

574+
def test_create_from_npm(self, mock_client: Mock, agent_view: MockAgentView) -> None:
575+
"""Test create_from_npm factory method."""
576+
mock_client.agents.create.return_value = agent_view
577+
578+
client = AgentOps(mock_client)
579+
agent = client.create_from_npm(
580+
name="test-agent",
581+
package_name="@runloop/example-agent",
582+
)
583+
584+
assert isinstance(agent, Agent)
585+
assert agent.id == "agent_123"
586+
mock_client.agents.create.assert_called_once_with(
587+
source={
588+
"type": "npm",
589+
"npm": {
590+
"package_name": "@runloop/example-agent",
591+
},
592+
},
593+
name="test-agent",
594+
)
595+
596+
def test_create_from_npm_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
597+
"""Test create_from_npm factory method with all optional parameters."""
598+
mock_client.agents.create.return_value = agent_view
599+
600+
client = AgentOps(mock_client)
601+
agent = client.create_from_npm(
602+
package_name="@runloop/example-agent",
603+
npm_version="1.2.3",
604+
registry_url="https://registry.example.com",
605+
agent_setup=["npm install", "npm run setup"],
606+
name="test-agent",
607+
extra_headers={"X-Custom": "header"},
608+
)
609+
610+
assert isinstance(agent, Agent)
611+
assert agent.id == "agent_123"
612+
mock_client.agents.create.assert_called_once_with(
613+
source={
614+
"type": "npm",
615+
"npm": {
616+
"package_name": "@runloop/example-agent",
617+
"npm_version": "1.2.3",
618+
"registry_url": "https://registry.example.com",
619+
"agent_setup": ["npm install", "npm run setup"],
620+
},
621+
},
622+
name="test-agent",
623+
extra_headers={"X-Custom": "header"},
624+
)
625+
626+
def test_create_from_npm_raises_when_source_provided(self, mock_client: Mock) -> None:
627+
"""Test create_from_npm raises ValueError when source is provided in params."""
628+
client = AgentOps(mock_client)
629+
630+
with pytest.raises(ValueError, match="Cannot specify 'source' when using create_from_npm"):
631+
client.create_from_npm(
632+
package_name="@runloop/example-agent",
633+
name="test-agent",
634+
source={"type": "git", "git": {"repository": "https://github.com/example/repo"}},
635+
)
636+
637+
def test_create_from_pip(self, mock_client: Mock, agent_view: MockAgentView) -> None:
638+
"""Test create_from_pip factory method."""
639+
mock_client.agents.create.return_value = agent_view
640+
641+
client = AgentOps(mock_client)
642+
agent = client.create_from_pip(
643+
package_name="runloop-example-agent",
644+
name="test-agent",
645+
)
646+
647+
assert isinstance(agent, Agent)
648+
assert agent.id == "agent_123"
649+
mock_client.agents.create.assert_called_once_with(
650+
source={
651+
"type": "pip",
652+
"pip": {
653+
"package_name": "runloop-example-agent",
654+
},
655+
},
656+
name="test-agent",
657+
)
658+
659+
def test_create_from_pip_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
660+
"""Test create_from_pip factory method with all optional parameters."""
661+
mock_client.agents.create.return_value = agent_view
662+
663+
client = AgentOps(mock_client)
664+
agent = client.create_from_pip(
665+
package_name="runloop-example-agent",
666+
pip_version="1.2.3",
667+
registry_url="https://pypi.example.com",
668+
agent_setup=["pip install extra-deps"],
669+
name="test-agent",
670+
)
671+
672+
assert isinstance(agent, Agent)
673+
assert agent.id == "agent_123"
674+
mock_client.agents.create.assert_called_once_with(
675+
source={
676+
"type": "pip",
677+
"pip": {
678+
"package_name": "runloop-example-agent",
679+
"pip_version": "1.2.3",
680+
"registry_url": "https://pypi.example.com",
681+
"agent_setup": ["pip install extra-deps"],
682+
},
683+
},
684+
name="test-agent",
685+
)
686+
687+
def test_create_from_git(self, mock_client: Mock, agent_view: MockAgentView) -> None:
688+
"""Test create_from_git factory method."""
689+
mock_client.agents.create.return_value = agent_view
690+
691+
client = AgentOps(mock_client)
692+
agent = client.create_from_git(
693+
repository="https://github.com/example/agent-repo",
694+
name="test-agent",
695+
)
696+
697+
assert isinstance(agent, Agent)
698+
assert agent.id == "agent_123"
699+
mock_client.agents.create.assert_called_once_with(
700+
source={
701+
"type": "git",
702+
"git": {
703+
"repository": "https://github.com/example/agent-repo",
704+
},
705+
},
706+
name="test-agent",
707+
)
708+
709+
def test_create_from_git_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
710+
"""Test create_from_git factory method with all optional parameters."""
711+
mock_client.agents.create.return_value = agent_view
712+
713+
client = AgentOps(mock_client)
714+
agent = client.create_from_git(
715+
repository="https://github.com/example/agent-repo",
716+
ref="develop",
717+
agent_setup=["npm install", "npm run build"],
718+
name="test-agent",
719+
)
720+
721+
assert isinstance(agent, Agent)
722+
assert agent.id == "agent_123"
723+
mock_client.agents.create.assert_called_once_with(
724+
source={
725+
"type": "git",
726+
"git": {
727+
"repository": "https://github.com/example/agent-repo",
728+
"ref": "develop",
729+
"agent_setup": ["npm install", "npm run build"],
730+
},
731+
},
732+
name="test-agent",
733+
)
734+
735+
def test_create_from_object(self, mock_client: Mock, agent_view: MockAgentView) -> None:
736+
"""Test create_from_object factory method."""
737+
mock_client.agents.create.return_value = agent_view
738+
739+
client = AgentOps(mock_client)
740+
agent = client.create_from_object(
741+
object_id="obj_123",
742+
name="test-agent",
743+
)
744+
745+
assert isinstance(agent, Agent)
746+
assert agent.id == "agent_123"
747+
mock_client.agents.create.assert_called_once_with(
748+
source={
749+
"type": "object",
750+
"object": {
751+
"object_id": "obj_123",
752+
},
753+
},
754+
name="test-agent",
755+
)
756+
757+
def test_create_from_object_with_agent_setup(self, mock_client: Mock, agent_view: MockAgentView) -> None:
758+
"""Test create_from_object factory method with agent_setup."""
759+
mock_client.agents.create.return_value = agent_view
760+
761+
client = AgentOps(mock_client)
762+
agent = client.create_from_object(
763+
object_id="obj_123",
764+
agent_setup=["chmod +x setup.sh", "./setup.sh"],
765+
name="test-agent",
766+
)
767+
768+
assert isinstance(agent, Agent)
769+
assert agent.id == "agent_123"
770+
mock_client.agents.create.assert_called_once_with(
771+
source={
772+
"type": "object",
773+
"object": {
774+
"object_id": "obj_123",
775+
"agent_setup": ["chmod +x setup.sh", "./setup.sh"],
776+
},
777+
},
778+
name="test-agent",
779+
)
780+
574781

575782
class TestRunloopSDK:
576783
"""Tests for RunloopSDK class."""

0 commit comments

Comments
 (0)