Convert agent and plugin metadata/configs to YAML
Replace JSON-based agent and plugin metadata/config files with YAML equivalents and update code paths to read/write YAML. Added python/helpers/yaml for YAML load/dump and files.read_file_yaml helper; subagents, plugins, and files helpers now prefer agent.yaml/plugin.yaml and default_config.yaml. Migration now includes convert_agents_json_yaml and startup_migration is invoked from initialize to perform conversions. Plugin toggle/config handling was refactored (META_FILE_NAME/CONFIG_DEFAULT_FILE_NAME changes, new determined_toggle_from_paths helper) and memory plugin defaults and extensions were updated to use per-agent plugin config lookups with guards for missing configs.
frdel committed
Feb 23, 2026 at 19:36 UTC
6e2a44222203eb97b876110ede1c1c3a08ab7902
33 files changed
+200
-129
agents/agent0/_context.md
deleted
-4
@@ -1,4 +0,0 @@
1
-# Agent 0
2
-- main agent of the system
3
-- communicates to user and delegates to subordinates
4
-- general purpose assistant, communication skills, formatted output
\ No newline at end of file
agents/agent0/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Agent 0",
3
- "description": "Main agent of the system communicating directly with the user.",
4
- "context": ""
5
-}
agents/agent0/agent.yaml
new
+3
@@ -0,0 +1,3 @@
1
+title: Agent 0
2
+description: Main agent of the system communicating directly with the user.
3
+context: ''
agents/default/_context.md
deleted
-3
@@ -1,3 +0,0 @@
1
-# Default prompts
2
-- default prompt file templates
3
-- should be inherited and overriden by specialized prompt profiles
\ No newline at end of file
agents/default/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Default",
3
- "description": "Default prompt file templates. Should be inherited and overriden by specialized prompt profiles.",
4
- "context": ""
5
-}
agents/default/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Default
2
+description: Default prompt file templates. Should be inherited and overriden by specialized
3
+ prompt profiles.
4
+context: ''
agents/developer/_context.md
deleted
-2
@@ -1,2 +0,0 @@
1
-# Developer
2
-- agent specialized in complex software development
\ No newline at end of file
agents/developer/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Developer",
3
- "description": "Agent specialized in complex software development.",
4
- "context": "Use this agent for software development tasks, including writing code, debugging, refactoring, and architectural design."
5
-}
agents/developer/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Developer
2
+description: Agent specialized in complex software development.
3
+context: Use this agent for software development tasks, including writing code, debugging,
4
+ refactoring, and architectural design.
agents/hacker/_context.md
deleted
-2
@@ -1,2 +0,0 @@
1
-# Hacker
2
-- agent specialized in cyber security and penetration testing
\ No newline at end of file
agents/hacker/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Hacker",
3
- "description": "Agent specialized in cyber security and penetration testing.",
4
- "context": "Use this agent for cybersecurity tasks such as penetration testing, vulnerability analysis, and security auditing."
5
-}
agents/hacker/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Hacker
2
+description: Agent specialized in cyber security and penetration testing.
3
+context: Use this agent for cybersecurity tasks such as penetration testing, vulnerability
4
+ analysis, and security auditing.
agents/researcher/_context.md
deleted
-2
@@ -1,2 +0,0 @@
1
-# Researcher
2
-- agent specialized in research, data analysis and reporting
\ No newline at end of file
agents/researcher/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Researcher",
3
- "description": "Agent specialized in research, data analysis and reporting.",
4
- "context": "Use this agent for information gathering, data analysis, topic research, and generating comprehensive reports."
5
-}
agents/researcher/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Researcher
2
+description: Agent specialized in research, data analysis and reporting.
3
+context: Use this agent for information gathering, data analysis, topic research,
4
+ and generating comprehensive reports.
initialize.py
+1
-1
@@ -142,7 +142,7 @@ def initialize_preload():
142
def initialize_migration():
143
from python.helpers import migration, dotenv
144
# run migration
145
- migration.migrate_user_data()
145
+ migration.startup_migration()
146
# reload .env as it might have been moved
147
dotenv.load_dotenv()
148
# reload settings to ensure new paths are picked up
plugins/example_agent/agents/plugin_example/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Plugin Example Agent",
3
- "description": "This is an example agent profile loaded from the 'example_agent' plugin.",
4
- "context": "You are an example agent provided by the 'example_agent' plugin. Your purpose is to demonstrate how plugins can distribute agent profiles."
5
-}
plugins/example_agent/agents/plugin_example/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Plugin Example Agent
2
+description: This is an example agent profile loaded from the 'example_agent' plugin.
3
+context: You are an example agent provided by the 'example_agent' plugin. Your purpose
4
+ is to demonstrate how plugins can distribute agent profiles.
plugins/example_agent/plugin.json
deleted
-8
@@ -1,8 +0,0 @@
1
-{
2
- "name": "Example Agent",
3
- "description": "Example agent plugin demonstrating the Agent Zero plugin system.",
4
- "version": "1.0.0",
5
- "settings_sections": [],
6
- "per_project_config": true,
7
- "per_agent_config": false
8
-}
plugins/example_agent/plugin.yaml
new
+6
@@ -0,0 +1,6 @@
1
+name: Example Agent
2
+description: Example agent plugin demonstrating the Agent Zero plugin system.
3
+version: 1.0.0
4
+settings_sections: []
5
+per_project_config: true
6
+per_agent_config: false
\ No newline at end of file
plugins/memory/config.default.json
deleted
-18
@@ -1,18 +0,0 @@
1
-{
2
- "project_memory_isolation": true,
3
- "memory_recall_enabled": true,
4
- "memory_recall_delayed": false,
5
- "memory_recall_interval": 3,
6
- "memory_recall_history_len": 10000,
7
- "memory_recall_memories_max_search": 12,
8
- "memory_recall_solutions_max_search": 8,
9
- "memory_recall_memories_max_result": 5,
10
- "memory_recall_solutions_max_result": 3,
11
- "memory_recall_similarity_threshold": 0.7,
12
- "memory_recall_query_prep": false,
13
- "memory_recall_post_filter": false,
14
- "memory_memorize_enabled": true,
15
- "memory_memorize_consolidation": true,
16
- "memory_memorize_replace_threshold": 0.9,
17
- "agent_memory_subdir": "default"
18
-}
plugins/memory/default_config.yaml
new
+16
@@ -0,0 +1,16 @@
1
+project_memory_isolation: true
2
+memory_recall_enabled: true
3
+memory_recall_delayed: false
4
+memory_recall_interval: 3
5
+memory_recall_history_len: 10000
6
+memory_recall_memories_max_search: 12
7
+memory_recall_solutions_max_search: 8
8
+memory_recall_memories_max_result: 5
9
+memory_recall_solutions_max_result: 3
10
+memory_recall_similarity_threshold: 0.7
11
+memory_recall_query_prep: false
12
+memory_recall_post_filter: false
13
+memory_memorize_enabled: true
14
+memory_memorize_consolidation: true
15
+memory_memorize_replace_threshold: 0.9
16
+agent_memory_subdir: default
\ No newline at end of file
plugins/memory/extensions/python/message_loop_prompts_after/_50_recall_memories.py
+7
-3
@@ -25,11 +25,13 @@ class RecallMemories(Extension):
25
26
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
27
28
- set = plugins.get_plugin_config("memory")
28
+ set = plugins.get_plugin_config("memory", self.agent)
29
+ if not set:
30
+ return None
31
32
# turned off in settings?
33
if not set["memory_recall_enabled"]:
32
- return
34
+ return None
35
36
# every X iterations (or the first one) recall memories
37
if loop_data.iteration % set["memory_recall_interval"] == 0:
@@ -63,7 +65,9 @@ class RecallMemories(Extension):
65
del extras["solutions"]
66
67
66
- set = plugins.get_plugin_config("memory")
68
+ set = plugins.get_plugin_config("memory", self.agent)
69
+ if not set:
70
+ return None
71
# try:
72
73
# get system message and chat history for util llm
plugins/memory/extensions/python/message_loop_prompts_after/_91_recall_wait.py
+3
-1
@@ -6,7 +6,9 @@ from python.helpers import plugins
6
class RecallWait(Extension):
7
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
8
9
- set = plugins.get_plugin_config("memory")
9
+ set = plugins.get_plugin_config("memory", self.agent)
10
+ if not set:
11
+ return None
12
13
task = self.agent.get_data(DATA_NAME_TASK_MEMORIES)
14
iter = self.agent.get_data(DATA_NAME_ITER_MEMORIES) or 0
plugins/memory/extensions/python/monologue_end/_50_memorize_fragments.py
+6
-2
@@ -16,7 +16,9 @@ class MemorizeMemories(Extension):
16
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
17
# try:
18
19
- set = plugins.get_plugin_config("memory")
19
+ set = plugins.get_plugin_config("memory", self.agent)
20
+ if not set:
21
+ return None
22
23
if not set["memory_memorize_enabled"]:
24
return
@@ -36,7 +38,9 @@ class MemorizeMemories(Extension):
38
async def memorize(self, loop_data: LoopData, log_item: LogItem, **kwargs):
39
40
try:
39
- set = plugins.get_plugin_config("memory")
41
+ set = plugins.get_plugin_config("memory", self.agent)
42
+ if not set:
43
+ return None
44
45
db = await Memory.get(self.agent)
46
plugins/memory/extensions/python/monologue_end/_51_memorize_solutions.py
+7
-2
@@ -15,7 +15,9 @@ class MemorizeSolutions(Extension):
15
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
16
# try:
17
18
- set = plugins.get_plugin_config("memory")
18
+ set = plugins.get_plugin_config("memory", self.agent)
19
+ if not set:
20
+ return None
21
22
if not set["memory_memorize_enabled"]:
23
return
@@ -34,7 +36,10 @@ class MemorizeSolutions(Extension):
36
37
async def memorize(self, loop_data: LoopData, log_item: LogItem, **kwargs):
38
try:
37
- set = plugins.get_plugin_config("memory")
39
+ set = plugins.get_plugin_config("memory", self.agent)
40
+ if not set:
41
+ return None
42
+
43
44
db = await Memory.get(self.agent)
45
plugins/memory/plugin.json
deleted
-8
@@ -1,8 +0,0 @@
1
-{
2
- "name": "Memory",
3
- "description": "Provides persistent memory capabilities to Agent Zero agents.",
4
- "version": "1.0.0",
5
- "settings_sections": ["agent"],
6
- "per_project_config": true,
7
- "per_agent_config": false
8
-}
plugins/memory/plugin.yaml
new
+7
@@ -0,0 +1,7 @@
1
+name: Memory
2
+description: Provides persistent memory capabilities to Agent Zero agents.
3
+version: 1.0.0
4
+settings_sections:
5
+ - agent
6
+per_project_config: true
7
+per_agent_config: false
\ No newline at end of file
python/helpers/files.py
+7
-4
@@ -3,19 +3,16 @@ from fnmatch import fnmatch
3
import json
4
from ntpath import isabs
5
import os
6
-import sys
6
import re
7
import base64
8
import shutil
9
import tempfile
10
from typing import Any, Literal
11
import zipfile
13
-import importlib
14
-import importlib.util
15
-import inspect
12
import glob
13
import mimetypes
14
from simpleeval import simple_eval
15
+from python.helpers import yaml
16
17
AGENTS_DIR = "agents"
18
PLUGINS_DIR = "plugins"
@@ -224,6 +221,12 @@ def read_file_json(relative_path: str, encoding="utf-8"):
221
with open(absolute_path, "r", encoding=encoding) as f:
222
return json.load(f)
223
224
+def read_file_yaml(relative_path: str, encoding="utf-8"):
225
+ absolute_path = get_abs_path(relative_path)
226
+
227
+ with open(absolute_path, "r", encoding=encoding) as f:
228
+ return yaml.loads(f.read())
229
+
230
def read_file_bin(relative_path: str):
231
# Try to get the absolute path for the file from the original directory or backup directories
232
absolute_path = get_abs_path(relative_path)
python/helpers/migration.py
+28
@@ -1,7 +1,15 @@
1
import os
2
+import json
3
from python.helpers import files
4
+from python.helpers import subagents
5
+from python.helpers import yaml as yaml_helper
6
from python.helpers.print_style import PrintStyle
7
8
+
9
+def startup_migration() -> None:
10
+ migrate_user_data()
11
+ convert_agents_json_yaml()
12
+
13
def migrate_user_data() -> None:
14
"""
15
Migrate user data from /tmp and other locations to /usr.
@@ -44,6 +52,26 @@ def migrate_user_data() -> None:
52
53
PrintStyle().print("Migration check complete.")
54
55
+
56
+def convert_agents_json_yaml() -> None:
57
+ for root in subagents.get_agents_roots():
58
+ rel_root = files.deabsolute_path(root)
59
+ for subdir in files.get_subdirectories(rel_root):
60
+ agent_yaml = os.path.join(rel_root, subdir, "agent.yaml")
61
+ if files.exists(agent_yaml):
62
+ continue
63
+
64
+ agent_json = os.path.join(rel_root, subdir, "agent.json")
65
+ if not files.exists(agent_json):
66
+ continue
67
+
68
+ try:
69
+ agent_obj = json.loads(files.read_file(agent_json))
70
+ files.write_file(agent_yaml, yaml_helper.dumps(agent_obj))
71
+ except Exception as e:
72
+ PrintStyle.error(f"Failed to convert {agent_json} to YAML", e)
73
+ continue
74
+
75
# --- Helper Functions ----------------------------------------------------------
76
77
def _move_dir(src: str, dst: str, overwrite: bool = False) -> None:
python/helpers/plugins.py
+50
-35
@@ -2,9 +2,9 @@ from __future__ import annotations
2
3
import re, json
4
from pathlib import Path
5
-from typing import Any, Dict, List, Literal, Optional, TYPE_CHECKING, TypedDict
5
+from typing import Any, Dict, Iterator, List, Literal, Optional, TYPE_CHECKING, TypedDict
6
7
-from python.helpers import files, print_style
7
+from python.helpers import files, print_style, yaml as yaml_helper
8
from pydantic import BaseModel, Field
9
10
if TYPE_CHECKING:
@@ -22,12 +22,12 @@ class PluginAssetFile(TypedDict):
22
project_name: str
23
agent_profile: str
24
25
-META_FILE_NAME = "plugin.json"
25
+META_FILE_NAME = "plugin.yaml"
26
CONFIG_FILE_NAME = "config.json"
27
-CONFIG_DEFAULT_FILE_NAME = "config.default.json"
27
+CONFIG_DEFAULT_FILE_NAME = "default_config.yaml"
28
DISABLED_FILE_NAME = ".toggle-0"
29
ENABLED_FILE_NAME = ".toggle-1"
30
-TOGGLE_FILE_PATTERN = "*.toggle-[01]"
30
+TOGGLE_FILE_PATTERN = ".toggle-[01]"
31
32
33
class PluginMetadata(BaseModel):
@@ -55,11 +55,11 @@ class PluginListItem(BaseModel):
55
toggle_state: ToggleState = "disabled"
56
57
58
-def get_plugin_roots() -> List[str]:
58
+def get_plugin_roots(plugin_name:str="") -> List[str]:
59
"""Plugin root directories, ordered by priority (user first)."""
60
return [
61
- files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR),
62
- files.get_abs_path(files.PLUGINS_DIR),
61
+ files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR, plugin_name),
62
+ files.get_abs_path(files.PLUGINS_DIR, plugin_name),
63
]
64
65
@@ -90,8 +90,11 @@ def get_enhanced_plugins_list(
90
try:
91
if not d.is_dir() or d.name.startswith("."):
92
continue
93
+ meta_file = str(d / META_FILE_NAME)
94
+ if not files.exists(meta_file):
95
+ continue
96
meta = PluginMetadata.model_validate(
94
- files.read_file_json(str(d / META_FILE_NAME))
97
+ files.read_file_yaml(meta_file)
98
)
99
has_main_screen = files.exists(str(d / "webui" / "main.html"))
100
has_config_screen = files.exists(str(d / "webui" / "config.html"))
@@ -112,8 +115,9 @@ def get_enhanced_plugins_list(
115
toggle_state=toggle_state,
116
)
117
)
115
- except:
116
- pass
118
+ except Exception as e:
119
+ print_style.PrintStyle.error(f"Failed to load plugin {d.name}: {e}")
120
+ continue
121
122
if custom:
123
load_plugins(files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR), True)
@@ -127,7 +131,7 @@ def get_plugin_meta(plugin_name: str):
131
if not plugin_dir:
132
return None
133
return PluginMetadata.model_validate(
130
- files.read_file_json(files.get_abs_path(plugin_dir, META_FILE_NAME))
134
+ files.read_file_yaml(files.get_abs_path(plugin_dir, META_FILE_NAME))
135
)
136
137
@@ -192,6 +196,10 @@ def get_enabled_plugins(agent: Agent | None):
196
# every plugin is on by default, unless disabled in usr dir
197
enabled = True
198
199
+ # root plugin paths
200
+ plugin_paths = get_plugin_roots(plugin)
201
+
202
+ # + agent paths
203
if agent:
204
from python.helpers import subagents
205
@@ -201,46 +209,51 @@ def get_enabled_plugins(agent: Agent | None):
209
plugin,
210
must_exist_completely=True,
211
include_default=False,
204
- include_user=True,
212
+ include_user=False,
213
include_plugins=False,
214
include_project=True,
215
)
216
+ plugin_paths = agent_paths + plugin_paths
217
209
- # go through agent paths in reverse order and determine the state
210
- for agent_path in reversed(agent_paths):
211
- if enabled:
212
- enabled = not files.exists(
213
- files.get_abs_path(agent_path, DISABLED_FILE_NAME)
214
- )
215
- else:
216
- enabled = files.exists(
217
- files.get_abs_path(agent_path, ENABLED_FILE_NAME)
218
- )
218
+ # go through paths in reverse order and determine the state
219
+ enabled = determined_toggle_from_paths(enabled, reversed(plugin_paths))
220
221
if enabled:
222
active.append(plugin)
223
224
return active
225
226
+def determined_toggle_from_paths(default:bool, paths:Iterator[str]):
227
+ enabled = default
228
+ for plugin_path in paths:
229
+ if enabled:
230
+ enabled = not files.exists(
231
+ files.get_abs_path(plugin_path, DISABLED_FILE_NAME)
232
+ )
233
+ else:
234
+ enabled = files.exists(
235
+ files.get_abs_path(plugin_path, ENABLED_FILE_NAME)
236
+ )
237
+ return enabled
238
239
def get_toggle_state(plugin_name: str) -> ToggleState:
240
meta = get_plugin_meta(plugin_name)
241
if not meta:
242
return "disabled"
243
if meta.always_enabled:
231
- return "enabled"
244
+ return "enabled"
245
233
- state = "enabled"
246
+ # root plugin paths
247
+ plugin_paths = get_plugin_roots(plugin_name)
248
+ state = "enabled" if determined_toggle_from_paths(True, reversed(plugin_paths)) else "disabled"
249
235
- # toggles inside of user directory (there should be only one, but let's make it work in any case)
236
- usr_toggles = files.find_existing_paths_by_pattern(files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR, plugin_name, TOGGLE_FILE_PATTERN))
237
- for toggle in usr_toggles:
238
- if toggle.endswith(ENABLED_FILE_NAME):
239
- state = "enabled"
240
- if toggle.endswith(DISABLED_FILE_NAME):
241
- state = "disabled"
250
+ # global toggles
251
+ usr_toggles = [
252
+ files.find_existing_paths_by_pattern(files.get_abs_path(files.PLUGINS_DIR, plugin_name, TOGGLE_FILE_PATTERN)),
253
+ files.find_existing_paths_by_pattern(files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR, plugin_name, TOGGLE_FILE_PATTERN))
254
+ ]
255
243
- # if there are more toggles in project/agent directories, return advanced
256
+ # additional toggles in project/agent directories, return advanced
257
if meta.per_agent_config or meta.per_project_config:
258
configs = find_plugin_assets(
259
TOGGLE_FILE_PATTERN,
@@ -311,7 +324,9 @@ def get_plugin_config(
324
find_plugin_dir(plugin_name), CONFIG_DEFAULT_FILE_NAME
325
)
326
if file_path and files.exists(file_path):
314
- return json.loads(files.read_file(file_path))
327
+ return (json.loads if file_path.lower().endswith(".json") else yaml_helper.loads)(
328
+ files.read_file(file_path)
329
+ )
330
return None
331
332
@@ -322,7 +337,7 @@ def save_plugin_config(
337
plugin_name, project_name, agent_profile, CONFIG_FILE_NAME
338
)
339
if file_path:
325
- files.write_file(file_path, json.dumps(settings))
340
+ files.write_file(file_path, yaml_helper.dumps(settings))
341
342
343
def find_plugin_asset(
python/helpers/subagents.py
+15
-4
@@ -1,4 +1,5 @@
1
from python.helpers import files
2
+from python.helpers import yaml as yaml_helper
3
from typing import TypedDict, TYPE_CHECKING
4
from pydantic import BaseModel, model_validator
5
import json
@@ -88,8 +89,13 @@ def _get_agents_list_from_dir(dir: str, origin: Origin) -> dict[str, SubAgentLis
89
90
for subdir in subdirs:
91
try:
91
- agent_json = files.read_file(files.get_abs_path(dir, subdir, "agent.json"))
92
- agent_data = SubAgentListItem.model_validate_json(agent_json)
92
+ agent_yaml_path = files.get_abs_path(dir, subdir, "agent.yaml")
93
+ if files.exists(agent_yaml_path):
94
+ agent_yaml = files.read_file(agent_yaml_path)
95
+ agent_data = SubAgentListItem.model_validate(yaml_helper.loads(agent_yaml) or {})
96
+ else:
97
+ agent_json = files.read_file(files.get_abs_path(dir, subdir, "agent.json"))
98
+ agent_data = SubAgentListItem.model_validate_json(agent_json)
99
name = agent_data.name or subdir
100
agent_data.name = name
101
agent_data.path = files.get_abs_path(dir, subdir)
@@ -176,8 +182,13 @@ def delete_agent_data(name: str) -> None:
182
183
def _load_agent_data_from_dir(dir: str, name: str, origin: Origin) -> SubAgent | None:
184
try:
179
- subagent_json = files.read_file(files.get_abs_path(dir, name, "agent.json"))
180
- subagent = SubAgent.model_validate_json(subagent_json)
185
+ agent_yaml_path = files.get_abs_path(dir, name, "agent.yaml")
186
+ if files.exists(agent_yaml_path):
187
+ agent_yaml = files.read_file(agent_yaml_path)
188
+ subagent = SubAgent.model_validate(yaml_helper.loads(agent_yaml) or {})
189
+ else:
190
+ subagent_json = files.read_file(files.get_abs_path(dir, name, "agent.json"))
191
+ subagent = SubAgent.model_validate_json(subagent_json)
192
except Exception:
193
# backward compatibility (before agent.json existed)
194
try:
python/helpers/yaml.py
new
+24
@@ -0,0 +1,24 @@
1
+import json
2
+import yaml
3
+
4
+
5
+def loads(text: str):
6
+ return yaml.safe_load(text)
7
+
8
+
9
+def dumps(obj, **kwargs) -> str:
10
+ dump_kwargs = {
11
+ "allow_unicode": True,
12
+ "sort_keys": False,
13
+ **kwargs,
14
+ }
15
+ return yaml.safe_dump(obj, **dump_kwargs)
16
+
17
+
18
+def from_json(text: str, **yaml_dump_kwargs) -> str:
19
+ return dumps(json.loads(text), **yaml_dump_kwargs)
20
+
21
+
22
+def to_json(text: str, **json_dump_kwargs) -> str:
23
+ obj = loads(text)
24
+ return json.dumps(obj, ensure_ascii=False, **json_dump_kwargs)