|
28 | 28 | logger = logging.getLogger(__name__) |
29 | 29 |
|
30 | 30 |
|
| 31 | +def _orchestration_dispatcher(orchestration_name: str) -> ExecutorFn: |
| 32 | + """Build an executor that hands the call off to attune-author's orchestration runtime. |
| 33 | +
|
| 34 | + The gui keeps a thin :class:`CommandSpec` so its profile filtering, |
| 35 | + job runner, and ``GET /api/commands`` continue to work with no |
| 36 | + changes. The actual executor body lives in attune-author. The |
| 37 | + returned closure converts the gui's :class:`JobContext` into the |
| 38 | + orchestration-runtime equivalent and unwraps ``RunResult.output`` |
| 39 | + so the existing job runner sees a plain dict. |
| 40 | + """ |
| 41 | + |
| 42 | + async def _dispatch(args: dict[str, Any], ctx: JobContext) -> Any: |
| 43 | + from attune_author.orchestration import ( # noqa: PLC0415 |
| 44 | + JobContext as AuthorCtx, |
| 45 | + ) |
| 46 | + from attune_author.orchestration import ( |
| 47 | + run_command, |
| 48 | + ) |
| 49 | + |
| 50 | + author_ctx = AuthorCtx(job_id=ctx.job_id, log=ctx.log) |
| 51 | + result = await run_command(orchestration_name, args, author_ctx) |
| 52 | + return result.output |
| 53 | + |
| 54 | + _dispatch.__name__ = f"_dispatch_{orchestration_name.replace('.', '_')}" |
| 55 | + return _dispatch |
| 56 | + |
| 57 | + |
| 58 | +def _proxy_command(orchestration_name: str) -> CommandSpec: |
| 59 | + """Mirror an attune-author orchestration spec into a gui ``CommandSpec``. |
| 60 | +
|
| 61 | + Importing :mod:`attune_author.orchestration.commands.<ns>` triggers |
| 62 | + registration on the orchestration registry; this helper then copies |
| 63 | + the metadata into a gui-shaped ``CommandSpec`` whose executor is a |
| 64 | + dispatcher closure. Phases D2 and D3 will repeat this pattern for |
| 65 | + each migrated command, eventually leaving this module with only |
| 66 | + proxy registrations and the executors removed. |
| 67 | + """ |
| 68 | + |
| 69 | + from attune_author.orchestration import COMMANDS as _AUTHOR_COMMANDS # noqa: PLC0415 |
| 70 | + |
| 71 | + src = _AUTHOR_COMMANDS[orchestration_name] |
| 72 | + return CommandSpec( |
| 73 | + name=src.name, |
| 74 | + title=src.title, |
| 75 | + domain=src.domain, |
| 76 | + description=src.description, |
| 77 | + args_schema=src.args_schema, |
| 78 | + executor=_orchestration_dispatcher(orchestration_name), |
| 79 | + cancellable=src.cancellable, |
| 80 | + profiles=src.profiles, |
| 81 | + ) |
| 82 | + |
| 83 | + |
31 | 84 | def _require_absolute(field: str, raw: str) -> None: |
32 | 85 | """Reject relative paths up-front so users see a clear error. |
33 | 86 |
|
@@ -327,54 +380,15 @@ async def _exec_author_generate(args: dict[str, Any], ctx: JobContext) -> dict[s |
327 | 380 |
|
328 | 381 |
|
329 | 382 | # --------------------------------------------------------------------------- |
330 | | -# RAG: corpus-info |
| 383 | +# RAG: corpus-info — owned by attune-author since Phase D1 of the |
| 384 | +# architecture-realignment spec. Importing the orchestration command |
| 385 | +# module triggers registration; we mirror it into this gui registry |
| 386 | +# so /api/commands and the job runner stay unchanged. |
331 | 387 | # --------------------------------------------------------------------------- |
332 | 388 |
|
| 389 | +import attune_author.orchestration.commands.rag # noqa: F401, E402 |
333 | 390 |
|
334 | | -async def _exec_rag_corpus_info(args: dict[str, Any], ctx: JobContext) -> dict[str, Any]: |
335 | | - from attune_gui.routes.rag import _get_pipeline # noqa: PLC0415 |
336 | | - from attune_gui.workspace import get_workspace # noqa: PLC0415 |
337 | | - |
338 | | - project_path_raw = str(args.get("project_path", "")).strip() |
339 | | - workspace = ( |
340 | | - Path(project_path_raw).expanduser().resolve() if project_path_raw else get_workspace() |
341 | | - ) |
342 | | - pipeline = _get_pipeline(workspace) |
343 | | - entries = await asyncio.to_thread(list, pipeline.corpus.entries()) |
344 | | - kinds = sorted({e.path.split("/")[0] for e in entries if "/" in e.path}) |
345 | | - ctx.log(f"{len(entries)} entries across {len(kinds)} kind(s)") |
346 | | - return { |
347 | | - "corpus_class": type(pipeline.corpus).__name__, |
348 | | - "entry_count": len(entries), |
349 | | - "kinds": kinds, |
350 | | - } |
351 | | - |
352 | | - |
353 | | -COMMANDS["rag.corpus-info"] = CommandSpec( |
354 | | - name="rag.corpus-info", |
355 | | - title="Corpus info", |
356 | | - domain="rag", |
357 | | - description="Show entry count and category breakdown for the loaded attune-rag corpus.", |
358 | | - args_schema={ |
359 | | - "type": "object", |
360 | | - "properties": { |
361 | | - "project_path": { |
362 | | - "type": "string", |
363 | | - "title": "Project path", |
364 | | - "default": "", |
365 | | - "description": ( |
366 | | - "Root of the project whose corpus to inspect. " |
367 | | - "Leave blank to use the configured workspace." |
368 | | - ), |
369 | | - "ui:widget": "path", |
370 | | - "ui:browseHint": "project", |
371 | | - }, |
372 | | - }, |
373 | | - }, |
374 | | - executor=_exec_rag_corpus_info, |
375 | | - cancellable=False, |
376 | | - profiles=("developer", "author"), |
377 | | -) |
| 391 | +COMMANDS["rag.corpus-info"] = _proxy_command("rag.corpus-info") |
378 | 392 |
|
379 | 393 |
|
380 | 394 | # --------------------------------------------------------------------------- |
|
0 commit comments