mrmd-ai 0.1.0__py3-none-any.whl → 0.1.2__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.
@@ -0,0 +1,215 @@
1
+ """
2
+ Custom Program Factory - Generate DSPy modules from user-defined templates.
3
+
4
+ This module allows users to create custom AI commands without writing code.
5
+ Users define their commands with:
6
+ - name: Display name for the command
7
+ - inputType: What text to process (selection, cursor, fullDoc)
8
+ - outputType: What to do with result (replace, insert)
9
+ - instructions: Natural language instructions for the AI
10
+
11
+ The factory generates DSPy Signature and Module classes dynamically.
12
+ """
13
+
14
+ from typing import Any
15
+ import dspy
16
+
17
+
18
+ def create_custom_signature(
19
+ name: str,
20
+ instructions: str,
21
+ input_type: str = "selection",
22
+ output_type: str = "replace",
23
+ ) -> type:
24
+ """Create a DSPy Signature class from user configuration.
25
+
26
+ Args:
27
+ name: Command name (used for class naming)
28
+ instructions: User's instructions for the AI
29
+ input_type: "selection" | "cursor" | "fullDoc"
30
+ output_type: "replace" | "insert"
31
+
32
+ Returns:
33
+ A DSPy Signature class
34
+ """
35
+ # Build the docstring from user instructions
36
+ docstring = f"""{instructions}
37
+
38
+ IMPORTANT RULES:
39
+ - Output ONLY the result text, no explanations or meta-commentary
40
+ - Maintain appropriate formatting (markdown, code style, etc.)
41
+ - Be concise and direct
42
+ """
43
+
44
+ # Define input fields based on input type
45
+ if input_type == "selection":
46
+ input_fields = {
47
+ "text": dspy.InputField(desc="The selected text to process"),
48
+ "local_context": dspy.InputField(desc="Text surrounding the selection for context"),
49
+ "document_context": dspy.InputField(desc="Broader document context"),
50
+ }
51
+ elif input_type == "cursor":
52
+ input_fields = {
53
+ "text_before_cursor": dspy.InputField(desc="Text before the cursor position"),
54
+ "local_context": dspy.InputField(desc="Text surrounding the cursor for context"),
55
+ "document_context": dspy.InputField(desc="Broader document context"),
56
+ }
57
+ else: # fullDoc
58
+ input_fields = {
59
+ "document_context": dspy.InputField(desc="The full document content"),
60
+ }
61
+
62
+ # Define output field
63
+ output_field_name = "result"
64
+ output_fields = {
65
+ output_field_name: dspy.OutputField(desc="The processed result text. Output ONLY the result.")
66
+ }
67
+
68
+ # Create the Signature class dynamically
69
+ # Clean name for class naming (remove spaces, special chars)
70
+ class_name = "".join(c for c in name if c.isalnum()) + "Signature"
71
+
72
+ signature_class = type(
73
+ class_name,
74
+ (dspy.Signature,),
75
+ {
76
+ "__doc__": docstring,
77
+ "__annotations__": {
78
+ **{k: str for k in input_fields},
79
+ **{k: str for k in output_fields},
80
+ },
81
+ **input_fields,
82
+ **output_fields,
83
+ }
84
+ )
85
+
86
+ return signature_class
87
+
88
+
89
+ def create_custom_module(
90
+ name: str,
91
+ instructions: str,
92
+ input_type: str = "selection",
93
+ output_type: str = "replace",
94
+ ) -> type:
95
+ """Create a DSPy Module class from user configuration.
96
+
97
+ Args:
98
+ name: Command name
99
+ instructions: User's instructions for the AI
100
+ input_type: "selection" | "cursor" | "fullDoc"
101
+ output_type: "replace" | "insert"
102
+
103
+ Returns:
104
+ A DSPy Module class (not instance)
105
+ """
106
+ signature = create_custom_signature(name, instructions, input_type, output_type)
107
+
108
+ # Clean name for class naming
109
+ class_name = "".join(c for c in name if c.isalnum()) + "Predict"
110
+
111
+ class CustomModule(dspy.Module):
112
+ """Dynamically generated custom command module."""
113
+
114
+ def __init__(self):
115
+ super().__init__()
116
+ self.predictor = dspy.Predict(signature)
117
+ self._input_type = input_type
118
+ self._output_type = output_type
119
+
120
+ def forward(self, **kwargs) -> Any:
121
+ return self.predictor(**kwargs)
122
+
123
+ # Set the class name
124
+ CustomModule.__name__ = class_name
125
+ CustomModule.__qualname__ = class_name
126
+
127
+ return CustomModule
128
+
129
+
130
+ class CustomProgramRegistry:
131
+ """Registry for user-defined custom programs.
132
+
133
+ Manages creation and caching of custom DSPy modules.
134
+ """
135
+
136
+ def __init__(self):
137
+ self._modules: dict[str, type] = {}
138
+ self._configs: dict[str, dict] = {}
139
+
140
+ def register(self, program_id: str, config: dict) -> type:
141
+ """Register a custom program from configuration.
142
+
143
+ Args:
144
+ program_id: Unique identifier for this program
145
+ config: Dict with name, instructions, inputType, outputType
146
+
147
+ Returns:
148
+ The generated Module class
149
+ """
150
+ module_class = create_custom_module(
151
+ name=config.get("name", program_id),
152
+ instructions=config.get("instructions", "Process this text."),
153
+ input_type=config.get("inputType", "selection"),
154
+ output_type=config.get("outputType", "replace"),
155
+ )
156
+
157
+ self._modules[program_id] = module_class
158
+ self._configs[program_id] = config
159
+
160
+ return module_class
161
+
162
+ def get(self, program_id: str) -> type | None:
163
+ """Get a registered module class by ID."""
164
+ return self._modules.get(program_id)
165
+
166
+ def get_config(self, program_id: str) -> dict | None:
167
+ """Get the configuration for a registered program."""
168
+ return self._configs.get(program_id)
169
+
170
+ def unregister(self, program_id: str) -> bool:
171
+ """Remove a program from the registry."""
172
+ if program_id in self._modules:
173
+ del self._modules[program_id]
174
+ del self._configs[program_id]
175
+ return True
176
+ return False
177
+
178
+ def clear(self):
179
+ """Clear all registered programs."""
180
+ self._modules.clear()
181
+ self._configs.clear()
182
+
183
+ def list_programs(self) -> list[str]:
184
+ """List all registered program IDs."""
185
+ return list(self._modules.keys())
186
+
187
+ def is_registered(self, program_id: str) -> bool:
188
+ """Check if a program is registered."""
189
+ return program_id in self._modules
190
+
191
+
192
+ # Global registry instance
193
+ custom_registry = CustomProgramRegistry()
194
+
195
+
196
+ def register_custom_programs(commands: list[dict]) -> None:
197
+ """Register multiple custom programs from a list of command configs.
198
+
199
+ Args:
200
+ commands: List of command configurations, each with:
201
+ - id or program: Unique identifier
202
+ - name: Display name
203
+ - instructions: AI instructions
204
+ - inputType: selection | cursor | fullDoc
205
+ - outputType: replace | insert
206
+ """
207
+ for cmd in commands:
208
+ program_id = cmd.get("program") or cmd.get("id")
209
+ if program_id:
210
+ custom_registry.register(program_id, cmd)
211
+
212
+
213
+ def get_custom_program(program_id: str) -> type | None:
214
+ """Get a custom program module class by ID."""
215
+ return custom_registry.get(program_id)