donna 0.2.0__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.
Files changed (85) hide show
  1. donna/__init__.py +1 -0
  2. donna/artifacts/__init__.py +0 -0
  3. donna/artifacts/usage/__init__.py +0 -0
  4. donna/artifacts/usage/artifacts.md +224 -0
  5. donna/artifacts/usage/cli.md +117 -0
  6. donna/artifacts/usage/worlds.md +36 -0
  7. donna/artifacts/work/__init__.py +0 -0
  8. donna/artifacts/work/do_it.md +142 -0
  9. donna/artifacts/work/do_it_fast.md +98 -0
  10. donna/artifacts/work/planning.md +245 -0
  11. donna/cli/__init__.py +0 -0
  12. donna/cli/__main__.py +6 -0
  13. donna/cli/application.py +17 -0
  14. donna/cli/commands/__init__.py +0 -0
  15. donna/cli/commands/artifacts.py +110 -0
  16. donna/cli/commands/projects.py +49 -0
  17. donna/cli/commands/sessions.py +77 -0
  18. donna/cli/types.py +138 -0
  19. donna/cli/utils.py +53 -0
  20. donna/core/__init__.py +0 -0
  21. donna/core/entities.py +27 -0
  22. donna/core/errors.py +126 -0
  23. donna/core/result.py +99 -0
  24. donna/core/utils.py +37 -0
  25. donna/domain/__init__.py +0 -0
  26. donna/domain/errors.py +47 -0
  27. donna/domain/ids.py +497 -0
  28. donna/lib/__init__.py +21 -0
  29. donna/lib/sources.py +5 -0
  30. donna/lib/worlds.py +7 -0
  31. donna/machine/__init__.py +0 -0
  32. donna/machine/action_requests.py +50 -0
  33. donna/machine/artifacts.py +200 -0
  34. donna/machine/changes.py +91 -0
  35. donna/machine/errors.py +122 -0
  36. donna/machine/operations.py +31 -0
  37. donna/machine/primitives.py +77 -0
  38. donna/machine/sessions.py +215 -0
  39. donna/machine/state.py +244 -0
  40. donna/machine/tasks.py +89 -0
  41. donna/machine/templates.py +83 -0
  42. donna/primitives/__init__.py +1 -0
  43. donna/primitives/artifacts/__init__.py +0 -0
  44. donna/primitives/artifacts/specification.py +20 -0
  45. donna/primitives/artifacts/workflow.py +195 -0
  46. donna/primitives/directives/__init__.py +0 -0
  47. donna/primitives/directives/goto.py +44 -0
  48. donna/primitives/directives/task_variable.py +73 -0
  49. donna/primitives/directives/view.py +45 -0
  50. donna/primitives/operations/__init__.py +0 -0
  51. donna/primitives/operations/finish_workflow.py +37 -0
  52. donna/primitives/operations/request_action.py +89 -0
  53. donna/primitives/operations/run_script.py +250 -0
  54. donna/protocol/__init__.py +0 -0
  55. donna/protocol/cell_shortcuts.py +9 -0
  56. donna/protocol/cells.py +44 -0
  57. donna/protocol/errors.py +17 -0
  58. donna/protocol/formatters/__init__.py +0 -0
  59. donna/protocol/formatters/automation.py +25 -0
  60. donna/protocol/formatters/base.py +15 -0
  61. donna/protocol/formatters/human.py +36 -0
  62. donna/protocol/formatters/llm.py +39 -0
  63. donna/protocol/modes.py +40 -0
  64. donna/protocol/nodes.py +59 -0
  65. donna/world/__init__.py +0 -0
  66. donna/world/artifacts.py +122 -0
  67. donna/world/artifacts_discovery.py +90 -0
  68. donna/world/config.py +198 -0
  69. donna/world/errors.py +232 -0
  70. donna/world/initialization.py +42 -0
  71. donna/world/markdown.py +267 -0
  72. donna/world/sources/__init__.py +1 -0
  73. donna/world/sources/base.py +62 -0
  74. donna/world/sources/markdown.py +260 -0
  75. donna/world/templates.py +181 -0
  76. donna/world/tmp.py +33 -0
  77. donna/world/worlds/__init__.py +0 -0
  78. donna/world/worlds/base.py +68 -0
  79. donna/world/worlds/filesystem.py +189 -0
  80. donna/world/worlds/python.py +196 -0
  81. donna-0.2.0.dist-info/METADATA +44 -0
  82. donna-0.2.0.dist-info/RECORD +85 -0
  83. donna-0.2.0.dist-info/WHEEL +4 -0
  84. donna-0.2.0.dist-info/entry_points.txt +3 -0
  85. donna-0.2.0.dist-info/licenses/LICENSE +28 -0
donna/domain/ids.py ADDED
@@ -0,0 +1,497 @@
1
+ from typing import Any, Generic, Sequence, TypeVar
2
+
3
+ from pydantic_core import PydanticCustomError, core_schema
4
+
5
+ from donna.core.errors import ErrorsList
6
+ from donna.core.result import Err, Ok, Result
7
+ from donna.domain import errors as domain_errors
8
+
9
+
10
+ def _id_crc(number: int) -> str:
11
+ """Translates int into a compact string representation with a-zA-Z0-9 characters."""
12
+ charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
13
+ base = len(charset)
14
+
15
+ if number == 0:
16
+ return charset[0]
17
+
18
+ chars = []
19
+ while number > 0:
20
+ number, rem = divmod(number, base)
21
+ chars.append(charset[rem])
22
+
23
+ chars.reverse()
24
+ return "".join(chars)
25
+
26
+
27
+ def _match_pattern_parts(pattern_parts: Sequence[str], value_parts: Sequence[str]) -> bool: # noqa: CCR001
28
+ def match_at(p_index: int, v_index: int) -> bool: # noqa: CCR001
29
+ while True:
30
+ if p_index >= len(pattern_parts):
31
+ return v_index >= len(value_parts)
32
+
33
+ token = pattern_parts[p_index]
34
+
35
+ if token == "**": # noqa: S105
36
+ for next_index in range(v_index, len(value_parts) + 1):
37
+ if match_at(p_index + 1, next_index):
38
+ return True
39
+ return False
40
+
41
+ if v_index >= len(value_parts):
42
+ return False
43
+
44
+ if token != "*" and token != value_parts[v_index]: # noqa: S105
45
+ return False
46
+
47
+ p_index += 1
48
+ v_index += 1
49
+
50
+ return match_at(0, 0)
51
+
52
+
53
+ def _stringify_value(value: Any) -> str:
54
+ if isinstance(value, str):
55
+ return value
56
+ return repr(value)
57
+
58
+
59
+ def _pydantic_type_error(type_name: str, value: Any) -> PydanticCustomError:
60
+ return PydanticCustomError(
61
+ "type_error",
62
+ "{type_name} must be a str, got {actual_type}",
63
+ {"type_name": type_name, "actual_type": type(value).__name__},
64
+ )
65
+
66
+
67
+ def _pydantic_value_error(type_name: str, value: Any) -> PydanticCustomError:
68
+ return PydanticCustomError(
69
+ "value_error",
70
+ "Invalid {type_name}: {value}",
71
+ {"type_name": type_name, "value": _stringify_value(value)},
72
+ )
73
+
74
+
75
+ TParsed = TypeVar("TParsed")
76
+
77
+
78
+ def _invalid_format(id_type: str, value: Any) -> Result[TParsed, ErrorsList]:
79
+ return Err([domain_errors.InvalidIdFormat(id_type=id_type, value=_stringify_value(value))])
80
+
81
+
82
+ def _invalid_pattern(id_type: str, value: Any) -> Result[TParsed, ErrorsList]:
83
+ return Err([domain_errors.InvalidIdPattern(id_type=id_type, value=_stringify_value(value))])
84
+
85
+
86
+ class InternalId(str):
87
+ __slots__ = ()
88
+
89
+ def __new__(cls, value: str) -> "InternalId":
90
+ if not cls.validate(value):
91
+ raise domain_errors.InvalidInternalId(value=value)
92
+
93
+ return super().__new__(cls, value)
94
+
95
+ @classmethod
96
+ def build(cls, prefix: str, value: int) -> "InternalId":
97
+ return cls(f"{prefix}-{value}-{_id_crc(value)}")
98
+
99
+ @classmethod
100
+ def validate(cls, id: str) -> bool:
101
+ if not isinstance(id, str):
102
+ return False
103
+
104
+ try:
105
+ _prefix, value, crc = id.rsplit("-", maxsplit=2)
106
+ except ValueError:
107
+ return False
108
+
109
+ try:
110
+ expected_crc = _id_crc(int(value))
111
+ except ValueError:
112
+ return False
113
+
114
+ return crc == expected_crc
115
+
116
+ @classmethod
117
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema: # noqa: CCR001
118
+
119
+ def validate(v: Any) -> "InternalId":
120
+ if isinstance(v, cls):
121
+ return v
122
+
123
+ if not isinstance(v, str):
124
+ raise _pydantic_type_error(cls.__name__, v)
125
+
126
+ if not cls.validate(v):
127
+ raise _pydantic_value_error(cls.__name__, v)
128
+
129
+ return cls(v)
130
+
131
+ return core_schema.json_or_python_schema(
132
+ json_schema=core_schema.str_schema(),
133
+ python_schema=core_schema.no_info_plain_validator_function(validate),
134
+ serialization=core_schema.to_string_ser_schema(),
135
+ )
136
+
137
+
138
+ class WorkUnitId(InternalId):
139
+ __slots__ = ()
140
+
141
+
142
+ class ActionRequestId(InternalId):
143
+ __slots__ = ()
144
+
145
+
146
+ class TaskId(InternalId):
147
+ __slots__ = ()
148
+
149
+
150
+ class Identifier(str):
151
+ __slots__ = ()
152
+
153
+ def __new__(cls, value: str) -> "Identifier":
154
+ if not cls.validate(value):
155
+ raise domain_errors.InvalidIdentifier(value=value)
156
+
157
+ return super().__new__(cls, value)
158
+
159
+ @classmethod
160
+ def validate(cls, value: str) -> bool:
161
+ if not isinstance(value, str):
162
+ return False
163
+ return value.isidentifier()
164
+
165
+ @classmethod
166
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema: # noqa: CCR001
167
+
168
+ def validate(v: Any) -> "Identifier":
169
+ if isinstance(v, cls):
170
+ return v
171
+
172
+ if not isinstance(v, str):
173
+ raise _pydantic_type_error(cls.__name__, v)
174
+
175
+ if not cls.validate(v):
176
+ raise _pydantic_value_error(cls.__name__, v)
177
+
178
+ return cls(v)
179
+
180
+ return core_schema.json_or_python_schema(
181
+ json_schema=core_schema.str_schema(),
182
+ python_schema=core_schema.no_info_plain_validator_function(validate),
183
+ serialization=core_schema.to_string_ser_schema(),
184
+ )
185
+
186
+
187
+ class WorldId(Identifier):
188
+ __slots__ = ()
189
+
190
+
191
+ class IdPath(str):
192
+ __slots__ = ()
193
+ delimiter: str = ""
194
+ min_parts: int = 1
195
+ validate_json: bool = False
196
+
197
+ def __new__(cls, value: str | tuple[str, ...] | list[str]) -> "IdPath":
198
+ text = cls._coerce_to_text(value)
199
+
200
+ if not cls.validate(text):
201
+ raise domain_errors.InvalidIdPath(id_type=cls.__name__, value=text)
202
+
203
+ return super().__new__(cls, text)
204
+
205
+ @classmethod
206
+ def _coerce_to_text(cls, value: str | tuple[str, ...] | list[str]) -> str:
207
+ if isinstance(value, str):
208
+ return value
209
+
210
+ return cls.delimiter.join(str(part) for part in value)
211
+
212
+ @classmethod
213
+ def _split(cls, value: str) -> list[str]:
214
+ return value.split(cls.delimiter)
215
+
216
+ @classmethod
217
+ def _validate_parts(cls, parts: Sequence[str]) -> bool:
218
+ return all(part.isidentifier() for part in parts)
219
+
220
+ @classmethod
221
+ def validate(cls, value: str) -> bool:
222
+ if not isinstance(value, str) or not value:
223
+ return False
224
+
225
+ if not cls.delimiter:
226
+ return False
227
+
228
+ parts = cls._split(value)
229
+
230
+ if any(part == "" for part in parts):
231
+ return False
232
+
233
+ if len(parts) < cls.min_parts:
234
+ return False
235
+
236
+ return cls._validate_parts(parts)
237
+
238
+ @property
239
+ def parts(self) -> tuple[str, ...]:
240
+ return tuple(self._split(str.__str__(self)))
241
+
242
+ @classmethod
243
+ def _build_pydantic_schema(cls, validate_func: Any) -> core_schema.CoreSchema:
244
+ str_then_validate = core_schema.no_info_after_validator_function(
245
+ validate_func,
246
+ core_schema.str_schema(),
247
+ )
248
+
249
+ json_schema = str_then_validate if cls.validate_json else core_schema.str_schema()
250
+
251
+ return core_schema.json_or_python_schema(
252
+ json_schema=json_schema,
253
+ python_schema=core_schema.no_info_plain_validator_function(validate_func),
254
+ serialization=core_schema.to_string_ser_schema(),
255
+ )
256
+
257
+ @classmethod
258
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
259
+
260
+ def validate(v: Any) -> "IdPath":
261
+ if isinstance(v, cls):
262
+ return v
263
+
264
+ if not isinstance(v, str):
265
+ raise _pydantic_type_error(cls.__name__, v)
266
+
267
+ if not cls.validate(v):
268
+ raise _pydantic_value_error(cls.__name__, v)
269
+
270
+ return cls(v)
271
+
272
+ return cls._build_pydantic_schema(validate)
273
+
274
+
275
+ TIdPath = TypeVar("TIdPath", bound="IdPath")
276
+ TIdPathPattern = TypeVar("TIdPathPattern", bound="IdPathPattern[Any]")
277
+
278
+
279
+ class IdPathPattern(tuple[str, ...], Generic[TIdPath]):
280
+ __slots__ = ()
281
+ id_class: type[TIdPath]
282
+
283
+ def __str__(self) -> str:
284
+ return self.id_class.delimiter.join(self)
285
+
286
+ @classmethod
287
+ def _validate_pattern_part(cls, part: str) -> bool:
288
+ if part in {"*", "**"}:
289
+ return True
290
+
291
+ return part.isidentifier()
292
+
293
+ @classmethod
294
+ def parse(cls: type[TIdPathPattern], text: str) -> Result[TIdPathPattern, ErrorsList]: # noqa: CCR001
295
+ if not isinstance(text, str) or not text:
296
+ return _invalid_pattern(cls.__name__, text)
297
+
298
+ if not cls.id_class.delimiter:
299
+ return _invalid_pattern(cls.__name__, text)
300
+
301
+ parts = text.split(cls.id_class.delimiter)
302
+
303
+ if any(part == "" for part in parts):
304
+ return _invalid_pattern(cls.__name__, text)
305
+
306
+ for part in parts:
307
+ if not cls._validate_pattern_part(part):
308
+ return _invalid_pattern(cls.__name__, text)
309
+
310
+ return Ok(cls(parts))
311
+
312
+ def matches(self, value: TIdPath) -> bool:
313
+ return _match_pattern_parts(self, self.id_class._split(str(value)))
314
+
315
+ @classmethod
316
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema: # noqa: CCR001
317
+
318
+ def validate(v: Any) -> "IdPathPattern[TIdPath]":
319
+ if isinstance(v, cls):
320
+ return v
321
+
322
+ if not isinstance(v, str):
323
+ raise _pydantic_type_error(cls.__name__, v)
324
+
325
+ result = cls.parse(v)
326
+ errors = result.err()
327
+ if errors is not None:
328
+ error = errors[0]
329
+ raise PydanticCustomError("value_error", error.message.format(error=error))
330
+
331
+ parsed = result.ok()
332
+ if parsed is None:
333
+ raise _pydantic_value_error(cls.__name__, v)
334
+
335
+ return parsed
336
+
337
+ str_then_validate = core_schema.no_info_after_validator_function(
338
+ validate,
339
+ core_schema.str_schema(),
340
+ )
341
+
342
+ return core_schema.json_or_python_schema(
343
+ json_schema=str_then_validate,
344
+ python_schema=core_schema.no_info_plain_validator_function(validate),
345
+ serialization=core_schema.to_string_ser_schema(),
346
+ )
347
+
348
+
349
+ class DottedPath(IdPath):
350
+ __slots__ = ()
351
+ delimiter = "."
352
+
353
+
354
+ class ColonPath(IdPath):
355
+ __slots__ = ()
356
+ delimiter = ":"
357
+
358
+
359
+ class ArtifactId(ColonPath):
360
+ __slots__ = ()
361
+
362
+
363
+ class PythonImportPath(DottedPath):
364
+ __slots__ = ()
365
+
366
+ @classmethod
367
+ def parse(cls, text: str) -> Result["PythonImportPath", ErrorsList]:
368
+ if not isinstance(text, str) or not text:
369
+ return _invalid_format(cls.__name__, text)
370
+
371
+ if not cls.validate(text):
372
+ return _invalid_format(cls.__name__, text)
373
+
374
+ return Ok(cls(text))
375
+
376
+
377
+ class FullArtifactId(ColonPath):
378
+ __slots__ = ()
379
+ min_parts = 2
380
+ validate_json = True
381
+
382
+ def __str__(self) -> str:
383
+ return f"{self.world_id}{self.delimiter}{self.artifact_id}"
384
+
385
+ @property
386
+ def world_id(self) -> WorldId:
387
+ return WorldId(self.parts[0])
388
+
389
+ @property
390
+ def artifact_id(self) -> ArtifactId:
391
+ return ArtifactId(self.delimiter.join(self.parts[1:]))
392
+
393
+ def to_full_local(self, local_id: "ArtifactSectionId") -> "FullArtifactSectionId":
394
+ return FullArtifactSectionId(f"{self}:{local_id}")
395
+
396
+ @classmethod
397
+ def parse(cls, text: str) -> Result["FullArtifactId", ErrorsList]:
398
+ if not isinstance(text, str) or not text:
399
+ return _invalid_format(f"{cls.__name__} format", text)
400
+
401
+ if not cls.delimiter:
402
+ return _invalid_format(f"{cls.__name__} format", text)
403
+
404
+ parts = text.split(cls.delimiter, maxsplit=1)
405
+
406
+ if len(parts) != 2:
407
+ return _invalid_format(f"{cls.__name__} format", text)
408
+
409
+ world_part, artifact_part = parts
410
+
411
+ if not WorldId.validate(world_part):
412
+ return _invalid_format(f"{cls.__name__} format", text)
413
+
414
+ if not ArtifactId.validate(artifact_part):
415
+ return _invalid_format(f"{cls.__name__} format", text)
416
+
417
+ return Ok(FullArtifactId(f"{world_part}{cls.delimiter}{artifact_part}"))
418
+
419
+
420
+ class FullArtifactIdPattern(IdPathPattern["FullArtifactId"]):
421
+ __slots__ = ()
422
+ id_class = FullArtifactId
423
+
424
+ def matches_full_id(self, full_id: "FullArtifactId") -> bool:
425
+ return self.matches(full_id)
426
+
427
+
428
+ class ArtifactSectionId(Identifier):
429
+ __slots__ = ()
430
+
431
+ @classmethod
432
+ def parse(cls, text: str) -> Result["ArtifactSectionId", ErrorsList]:
433
+ if not isinstance(text, str) or not text:
434
+ return _invalid_format(cls.__name__, text)
435
+
436
+ if not cls.validate(text):
437
+ return _invalid_format(cls.__name__, text)
438
+
439
+ return Ok(cls(text))
440
+
441
+
442
+ class FullArtifactSectionId(ColonPath):
443
+ __slots__ = ()
444
+ min_parts = 3
445
+ validate_json = True
446
+
447
+ def __str__(self) -> str:
448
+ return f"{self.world_id}{self.delimiter}{self.artifact_id}{self.delimiter}{self.local_id}"
449
+
450
+ @property
451
+ def world_id(self) -> WorldId:
452
+ return WorldId(self.parts[0])
453
+
454
+ @property
455
+ def artifact_id(self) -> ArtifactId:
456
+ return ArtifactId(self.delimiter.join(self.parts[1:-1]))
457
+
458
+ @property
459
+ def full_artifact_id(self) -> FullArtifactId:
460
+ return FullArtifactId(f"{self.world_id}{self.delimiter}{self.artifact_id}")
461
+
462
+ @property
463
+ def local_id(self) -> ArtifactSectionId:
464
+ return ArtifactSectionId(self.parts[-1])
465
+
466
+ @classmethod
467
+ def parse(cls, text: str) -> Result["FullArtifactSectionId", ErrorsList]: # noqa: CCR001
468
+ if not isinstance(text, str) or not text:
469
+ return _invalid_format(f"{cls.__name__} format", text)
470
+
471
+ if not cls.delimiter:
472
+ return _invalid_format(f"{cls.__name__} format", text)
473
+
474
+ try:
475
+ artifact_part, local_part = text.rsplit(cls.delimiter, maxsplit=1)
476
+ except ValueError:
477
+ return _invalid_format(f"{cls.__name__} format", text)
478
+
479
+ full_artifact_id_result = FullArtifactId.parse(artifact_part)
480
+ errors = full_artifact_id_result.err()
481
+ if errors is not None:
482
+ return Err(errors)
483
+
484
+ full_artifact_id = full_artifact_id_result.ok()
485
+ if full_artifact_id is None:
486
+ return _invalid_format(f"{cls.__name__} format", text)
487
+
488
+ local_id_result = ArtifactSectionId.parse(local_part)
489
+ local_errors = local_id_result.err()
490
+ if local_errors is not None:
491
+ return Err(local_errors)
492
+
493
+ local_id = local_id_result.ok()
494
+ if local_id is None:
495
+ return _invalid_format(f"{cls.__name__} format", text)
496
+
497
+ return Ok(FullArtifactSectionId(f"{full_artifact_id}{cls.delimiter}{local_id}"))
donna/lib/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """Shared instances for standard library kind definitions."""
2
+
3
+ from donna.primitives.artifacts.specification import Specification, Text
4
+ from donna.primitives.artifacts.workflow import Workflow
5
+ from donna.primitives.directives.goto import GoTo
6
+ from donna.primitives.directives.task_variable import TaskVariable
7
+ from donna.primitives.directives.view import View
8
+ from donna.primitives.operations.finish_workflow import FinishWorkflow
9
+ from donna.primitives.operations.request_action import RequestAction
10
+ from donna.primitives.operations.run_script import RunScript
11
+
12
+ specification = Specification()
13
+ workflow = Workflow()
14
+ text = Text()
15
+ request_action = RequestAction()
16
+ finish = FinishWorkflow()
17
+ run_script = RunScript()
18
+
19
+ view = View(analyze_id="view")
20
+ goto = GoTo(analyze_id="goto")
21
+ task_variable = TaskVariable(analyze_id="task_variable")
donna/lib/sources.py ADDED
@@ -0,0 +1,5 @@
1
+ """Shared source constructor instances for default configuration."""
2
+
3
+ from donna.world.sources.markdown import MarkdownSourceConstructor
4
+
5
+ markdown = MarkdownSourceConstructor()
donna/lib/worlds.py ADDED
@@ -0,0 +1,7 @@
1
+ """Shared world constructor instances for default configuration."""
2
+
3
+ from donna.world.worlds.filesystem import FilesystemWorldConstructor
4
+ from donna.world.worlds.python import PythonWorldConstructor
5
+
6
+ filesystem = FilesystemWorldConstructor()
7
+ python = PythonWorldConstructor()
File without changes
@@ -0,0 +1,50 @@
1
+ import textwrap
2
+
3
+ import pydantic
4
+
5
+ from donna.core.entities import BaseEntity
6
+ from donna.domain.ids import ActionRequestId, FullArtifactSectionId
7
+ from donna.protocol.cells import Cell
8
+ from donna.protocol.nodes import Node
9
+
10
+
11
+ class ActionRequest(BaseEntity):
12
+ id: ActionRequestId | None
13
+ request: str
14
+ operation_id: FullArtifactSectionId
15
+
16
+ # TODO: we may want to make queue items frozen later
17
+ model_config = pydantic.ConfigDict(frozen=False)
18
+
19
+ @classmethod
20
+ def build(cls, request: str, operation_id: FullArtifactSectionId) -> "ActionRequest":
21
+ return cls(
22
+ id=None,
23
+ request=request,
24
+ operation_id=operation_id,
25
+ )
26
+
27
+ def node(self) -> "ActionRequestNode":
28
+ return ActionRequestNode(self)
29
+
30
+
31
+ class ActionRequestNode(Node):
32
+ __slots__ = ("_action_request",)
33
+
34
+ def __init__(self, action_request: ActionRequest) -> None:
35
+ self._action_request = action_request
36
+
37
+ def status(self) -> Cell:
38
+ message = textwrap.dedent(
39
+ """
40
+ **This is an action request for the agent. You MUST follow the instructions below.**
41
+
42
+ {request}
43
+ """
44
+ ).format(request=self._action_request.request)
45
+
46
+ return Cell.build_markdown(
47
+ kind="action_request",
48
+ content=message,
49
+ action_request_id=str(self._action_request.id),
50
+ )