meshagent-api 0.22.0__tar.gz → 0.22.2__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.
Files changed (46) hide show
  1. {meshagent_api-0.22.0/meshagent_api.egg-info → meshagent_api-0.22.2}/PKG-INFO +2 -1
  2. meshagent_api-0.22.2/meshagent/api/service_template_test.py +181 -0
  3. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/specs/service.py +50 -10
  4. meshagent_api-0.22.2/meshagent/api/version.py +1 -0
  5. {meshagent_api-0.22.0 → meshagent_api-0.22.2/meshagent_api.egg-info}/PKG-INFO +2 -1
  6. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent_api.egg-info/SOURCES.txt +1 -0
  7. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent_api.egg-info/requires.txt +1 -0
  8. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/pyproject.toml +2 -1
  9. meshagent_api-0.22.0/meshagent/api/version.py +0 -1
  10. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/LICENSE +0 -0
  11. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/MANIFEST.in +0 -0
  12. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/README.md +0 -0
  13. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/__init__.py +0 -0
  14. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/chan.py +0 -0
  15. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/client.py +0 -0
  16. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/crdt.py +0 -0
  17. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/entrypoint.js +0 -0
  18. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/helpers.py +0 -0
  19. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/keys.py +0 -0
  20. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/messaging.py +0 -0
  21. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/oauth.py +0 -0
  22. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/participant.py +0 -0
  23. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/participant_token.py +0 -0
  24. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/participant_token_test.py +0 -0
  25. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/port_forward.py +0 -0
  26. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/protocol.py +0 -0
  27. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/protocol_test.py +0 -0
  28. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/py.typed +0 -0
  29. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/reasoning_schema.py +0 -0
  30. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/room_server_client.py +0 -0
  31. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/runtime.py +0 -0
  32. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/runtime_test.py +0 -0
  33. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/schema.py +0 -0
  34. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/schema_document.py +0 -0
  35. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/schema_document_test.py +0 -0
  36. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/schema_registry.py +0 -0
  37. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/schema_test.py +0 -0
  38. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/schema_util.py +0 -0
  39. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/services.py +0 -0
  40. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/token_test.py +0 -0
  41. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/urls.py +0 -0
  42. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/webhooks.py +0 -0
  43. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent/api/websocket_protocol.py +0 -0
  44. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent_api.egg-info/dependency_links.txt +0 -0
  45. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/meshagent_api.egg-info/top_level.txt +0 -0
  46. {meshagent_api-0.22.0 → meshagent_api-0.22.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.22.0
3
+ Version: 0.22.2
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -15,6 +15,7 @@ Requires-Dist: jsonschema~=4.23
15
15
  Requires-Dist: pycrdt~=0.12.26
16
16
  Requires-Dist: opentelemetry-distro~=0.54b1
17
17
  Requires-Dist: pydantic~=2.11.7
18
+ Requires-Dist: jinja2~=3.1.6
18
19
  Provides-Extra: all
19
20
  Provides-Extra: sync
20
21
  Provides-Extra: stpyv8
@@ -0,0 +1,181 @@
1
+ import json
2
+
3
+ from pydantic_yaml import parse_yaml_raw_as
4
+
5
+ from meshagent.api.specs.service import (
6
+ AgentSpec,
7
+ ContainerTemplateSpec,
8
+ EnvironmentVariable,
9
+ ExternalServiceTemplateSpec,
10
+ ServiceTemplateMetadata,
11
+ ServiceTemplateSpec,
12
+ )
13
+
14
+
15
+ def test_service_template_spec_renders_jinja_values():
16
+ template = ServiceTemplateSpec(
17
+ version="v1",
18
+ kind="ServiceTemplate",
19
+ metadata=ServiceTemplateMetadata(
20
+ name="!template {{service_name}}",
21
+ description="!template Hello {{user}}",
22
+ repo="https://example.com/{{service_name}}",
23
+ annotations={"greeting": "!template hi {{user}}"},
24
+ ),
25
+ agents=[
26
+ AgentSpec(
27
+ name="!template agent-{{service_name}}",
28
+ description="!template handles {{role}}",
29
+ annotations={"role": "!template {{role}}"},
30
+ )
31
+ ],
32
+ external=ExternalServiceTemplateSpec(url="!template https://{{host}}/api"),
33
+ )
34
+
35
+ values = {
36
+ "service_name": "Concierge",
37
+ "user": "Rina",
38
+ "role": "support",
39
+ "host": "meshagent.dev",
40
+ }
41
+
42
+ service = template.to_service_spec(values=values)
43
+
44
+ assert service.metadata.annotations is not None
45
+ assert service.agents is not None
46
+ assert service.external is not None
47
+ assert service.metadata.name == "Concierge"
48
+ assert service.metadata.description == "Hello Rina"
49
+ assert service.metadata.repo == "https://example.com/{{service_name}}"
50
+ assert service.metadata.annotations["greeting"] == "hi Rina"
51
+ assert service.external.url == "https://meshagent.dev/api"
52
+ assert service.agents[0].name == "agent-Concierge"
53
+ assert service.agents[0].description == "handles support"
54
+ assert service.agents[0].annotations is not None
55
+ assert service.agents[0].annotations["role"] == "support"
56
+
57
+ source = service.metadata.annotations["meshagent.service.template.source"]
58
+ values_json = service.metadata.annotations["meshagent.service.template.values"]
59
+ assert json.loads(values_json) == values
60
+ assert "ServiceTemplate" in source
61
+
62
+
63
+ def test_service_template_spec_from_yaml():
64
+ yaml_spec = """
65
+ version: v1
66
+ kind: ServiceTemplate
67
+ metadata:
68
+ name: "!template {{service_name}}"
69
+ description: "!template Hello {{user}}"
70
+ repo: null
71
+ annotations:
72
+ greeting: "!template hi {{user}}"
73
+ agents:
74
+ - name: "!template agent-{{service_name}}"
75
+ description: "!template handles {{role}}"
76
+ external:
77
+ url: "!template https://{{host}}/api"
78
+ """
79
+
80
+ template = parse_yaml_raw_as(ServiceTemplateSpec, yaml_spec.encode())
81
+ values = {
82
+ "service_name": "Concierge",
83
+ "user": "Rina",
84
+ "role": "support",
85
+ "host": "meshagent.dev",
86
+ }
87
+
88
+ service = template.to_service_spec(values=values)
89
+
90
+ assert service.metadata.annotations is not None
91
+ assert service.agents is not None
92
+ assert service.external is not None
93
+ assert service.metadata.name == "Concierge"
94
+ assert service.metadata.description == "Hello Rina"
95
+ assert service.metadata.annotations["greeting"] == "hi Rina"
96
+ assert service.external.url == "https://meshagent.dev/api"
97
+ assert service.agents[0].name == "agent-Concierge"
98
+ assert service.agents[0].description == "handles support"
99
+
100
+
101
+ def test_service_template_spec_replaces_email_in_command():
102
+ template = ServiceTemplateSpec(
103
+ version="v1",
104
+ kind="ServiceTemplate",
105
+ metadata=ServiceTemplateMetadata(
106
+ name="PropertyAssistant",
107
+ description="Email template",
108
+ repo=None,
109
+ annotations=None,
110
+ ),
111
+ container=ContainerTemplateSpec(
112
+ image="us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz",
113
+ command=(
114
+ '!template meshagent multi service -c "chatbot --require-uuid '
115
+ "--agent-name=PropertyAssistant --image-generation=gpt-image-1 "
116
+ "--require-storage --require-toolkit=propertyemail --require-table-write=propertyinsurance "
117
+ "--require-table-write=propertyexpenses --mcp --web-search "
118
+ "-rr='agents/PropertyAssistant/assistantrules.txt' --rule='you have access to "
119
+ "the email tool, and you can send out emails.'; mailbot --reply-all "
120
+ "--enable-attachments --room-rules='/agents/PropertyAssistant/emailrules.txt' "
121
+ "--rule='never respnod in JSON or HTML, only in text.' --agent-name=PropertyAssistant "
122
+ "--require-table-write=propertyinsurance --require-table-write=propertyexpenses "
123
+ "--queue={{email}} --require-uuid --reply-all --require-storage "
124
+ "--email-address={{email}} --require-web-search --toolkit-name=propertyemail; "
125
+ "worker --require-storage --room-rules='/agents/PropertyAssistant/workerrules.txt' "
126
+ "--agent-name=PropertyAssistant --require-toolkit=propertyemail --queue=sendupdate "
127
+ "--require-table-read=propertyinsurance --require-table-read=propertyexpenses "
128
+ "--rule='Use the read_file tool to read PDFs.'\""
129
+ ),
130
+ ),
131
+ )
132
+
133
+ service = template.to_service_spec(values={"email": "owner@example.com"})
134
+
135
+ assert service.container is not None
136
+ assert service.container.command is not None
137
+ assert "{{email}}" not in service.container.command
138
+ assert "--queue=owner@example.com" in service.container.command
139
+ assert "--email-address=owner@example.com" in service.container.command
140
+
141
+
142
+ def test_service_template_spec_handles_none_values():
143
+ template = ServiceTemplateSpec(
144
+ version="v1",
145
+ kind="ServiceTemplate",
146
+ metadata=ServiceTemplateMetadata(
147
+ name="Plain Service",
148
+ description=None,
149
+ repo=None,
150
+ icon=None,
151
+ annotations=None,
152
+ ),
153
+ agents=[
154
+ AgentSpec(
155
+ name="Support",
156
+ description=None,
157
+ annotations=None,
158
+ )
159
+ ],
160
+ container=ContainerTemplateSpec(
161
+ image="meshagent/example",
162
+ command=None,
163
+ environment=[EnvironmentVariable(name="EMPTY", value=None)],
164
+ ),
165
+ )
166
+
167
+ service = template.to_service_spec(values={})
168
+
169
+ assert service.metadata.annotations is not None
170
+ assert service.agents is not None
171
+ assert service.container is not None
172
+ assert service.container.environment is not None
173
+ assert service.metadata.description is None
174
+ assert service.metadata.repo is None
175
+ assert service.metadata.icon is None
176
+ assert service.metadata.annotations["meshagent.service.template.source"]
177
+ assert service.metadata.annotations["meshagent.service.template.values"] == "{}"
178
+ assert len(service.metadata.annotations) == 2
179
+ assert service.agents[0].description is None
180
+ assert service.agents[0].annotations is None
181
+ assert service.container.environment[0].value is None
@@ -207,6 +207,8 @@ class ContainerTemplateSpec(BaseModel):
207
207
  image: Optional[str] = None
208
208
  command: Optional[str] = None
209
209
  storage: Optional[ServiceTemplateContainerMountSpec] = None
210
+ on_demand: Optional[bool] = None
211
+ writable_root_fs: Optional[bool] = None
210
212
 
211
213
 
212
214
  class ExternalServiceTemplateSpec(BaseModel):
@@ -214,6 +216,31 @@ class ExternalServiceTemplateSpec(BaseModel):
214
216
  url: str
215
217
 
216
218
 
219
+ def format_yaml_value(original: Optional[str], values: dict[str, str]):
220
+ if original is None:
221
+ return None
222
+
223
+ if original.startswith("!template "):
224
+ from jinja2 import Template
225
+
226
+ template = Template(original.removeprefix("!template "))
227
+ return template.render(**values)
228
+
229
+ else:
230
+ return original
231
+
232
+
233
+ def format_yaml_map(original: Optional[dict[str, str]], values: dict[str, str]):
234
+ if original is None:
235
+ return None
236
+ output = {}
237
+
238
+ for k, v in original.items():
239
+ output[k] = format_yaml_value(v, values)
240
+
241
+ return output
242
+
243
+
217
244
  class ServiceTemplateSpec(BaseModel):
218
245
  model_config = ConfigDict(extra="forbid")
219
246
  version: Literal["v1"]
@@ -232,28 +259,39 @@ class ServiceTemplateSpec(BaseModel):
232
259
  for e in self.container.environment:
233
260
  env.append(
234
261
  EnvironmentVariable(
235
- name=e.name, value=e.value.format_map(values)
262
+ name=e.name, value=format_yaml_value(e.value, values)
236
263
  )
237
264
  )
238
265
 
239
266
  return ServiceSpec(
240
267
  version=self.version,
241
268
  kind="Service",
242
- agents=self.agents,
269
+ agents=[
270
+ *(
271
+ AgentSpec(
272
+ name=format_yaml_value(a.name, values),
273
+ description=format_yaml_value(a.description, values),
274
+ annotations=format_yaml_map(a.annotations, values),
275
+ )
276
+ for a in self.agents
277
+ )
278
+ ]
279
+ if self.agents is not None
280
+ else None,
243
281
  metadata=ServiceMetadata(
244
- name=self.metadata.name,
245
- description=self.metadata.description,
246
- repo=self.metadata.repo,
247
- icon=self.metadata.icon,
282
+ name=format_yaml_value(self.metadata.name, values),
283
+ description=format_yaml_value(self.metadata.description, values),
284
+ repo=format_yaml_value(self.metadata.repo, values),
285
+ icon=format_yaml_value(self.metadata.icon, values),
248
286
  annotations={
249
287
  "meshagent.service.template.source": self.model_dump_json(),
250
288
  "meshagent.service.template.values": json.dumps(values),
251
- **self.metadata.annotations,
289
+ **(format_yaml_map(self.metadata.annotations, values) or {}),
252
290
  },
253
291
  ),
254
292
  container=ContainerSpec(
255
- command=self.container.command,
256
- image=self.container.image,
293
+ command=format_yaml_value(self.container.command, values),
294
+ image=format_yaml_value(self.container.image, values),
257
295
  environment=env,
258
296
  storage=ContainerMountSpec(
259
297
  room=self.container.storage.room
@@ -266,11 +304,13 @@ class ServiceTemplateSpec(BaseModel):
266
304
  if self.container.storage is not None
267
305
  else None,
268
306
  ),
307
+ writable_root_fs=self.container.writable_root_fs,
308
+ on_demand=self.container.on_demand,
269
309
  )
270
310
  if self.container is not None
271
311
  else None,
272
312
  external=ExternalServiceSpec(
273
- url=self.external.url,
313
+ url=format_yaml_value(self.external.url, values),
274
314
  )
275
315
  if self.external is not None
276
316
  else None,
@@ -0,0 +1 @@
1
+ __version__ = "0.22.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.22.0
3
+ Version: 0.22.2
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -15,6 +15,7 @@ Requires-Dist: jsonschema~=4.23
15
15
  Requires-Dist: pycrdt~=0.12.26
16
16
  Requires-Dist: opentelemetry-distro~=0.54b1
17
17
  Requires-Dist: pydantic~=2.11.7
18
+ Requires-Dist: jinja2~=3.1.6
18
19
  Provides-Extra: all
19
20
  Provides-Extra: sync
20
21
  Provides-Extra: stpyv8
@@ -28,6 +28,7 @@ meshagent/api/schema_document_test.py
28
28
  meshagent/api/schema_registry.py
29
29
  meshagent/api/schema_test.py
30
30
  meshagent/api/schema_util.py
31
+ meshagent/api/service_template_test.py
31
32
  meshagent/api/services.py
32
33
  meshagent/api/token_test.py
33
34
  meshagent/api/urls.py
@@ -4,6 +4,7 @@ jsonschema~=4.23
4
4
  pycrdt~=0.12.26
5
5
  opentelemetry-distro~=0.54b1
6
6
  pydantic~=2.11.7
7
+ jinja2~=3.1.6
7
8
 
8
9
  [all]
9
10
 
@@ -15,7 +15,8 @@ dependencies = [
15
15
  "jsonschema~=4.23",
16
16
  "pycrdt~=0.12.26",
17
17
  "opentelemetry-distro~=0.54b1",
18
- "pydantic~=2.11.7"
18
+ "pydantic~=2.11.7",
19
+ "jinja2~=3.1.6"
19
20
  ]
20
21
  classifiers = [
21
22
  # You can add your classifiers here if needed.
@@ -1 +0,0 @@
1
- __version__ = "0.22.0"
File without changes
File without changes
File without changes