Content
View differences
Updated by Jonas Jabari 26 days ago
## <img class="op-uc-image op-uc-image_inline" src="/api/v3/attachments/923084/content">
##
## Summary
The HTTP interface between the editor and the description assistant: list available actions for
a work package, and execute an action against client-side text. Owns the server-side prompt
assembly. This is the contract work package between the two work streams — align the endpoint
shapes with the LLM connection developer before implementation.
## Acceptance criteria
**List endpoint**
* `GET` endpoint returning the actions available for a given work package (or type, for new,
<br>
unsaved work packages): `[{ id, label, position }]`, ordered by position.
* Filtering is done server-side: action `active`, feature toggle on, global AI toggle on, LLM
<br>
connection configured, type restriction matches, and for `sort_into_template` the type has a
<br>
non-blank default description. The client applies no availability logic.
* Returns an empty list (not an error) when the assistant is disabled — the editor simply
<br>
shows no dropdown.
**Execute endpoint**
* `POST` endpoint accepting `{ action_id, content, work_package_id | type_id }` where `content`
<br>
is the current client-side editor markdown (possibly unsaved).
* Server-side prompt assembly:
* system message = hardcoded scaffold + effective action prompt (+ the type's default
<br>
description injected for `sort_into_template`)
* user message = the submitted content, verbatim — user content is never interpolated into
<br>
the system message (prompt-injection hygiene)
* The LLM call goes through the client/service of AI-3, using the action's model override or
<br>
the default model, and returns `{ text }` (markdown).
* Availability is re-validated on execute (action active, type matches, toggles on) →
<br>
`422` with a translated message otherwise.
* Error taxonomy mapped to distinct, translated messages: LLM not configured / disabled,
<br>
connection failed, timeout, upstream error (with status). No raw upstream payloads are
<br>
leaked to the client.
* Request/response content is not persisted and not written to logs above debug level.
## Technical notes
* **Sync vs. job — decide here, together with AI-3:** OpenProject has no active
<br>
websocket/ActionCable infrastructure; Turbo Streams are HTTP-response based. Options:
1. Synchronous request with a strict server-side timeout (~30–60s). Simplest; ties up a
<br>
Puma thread per call; subject to LB timeouts.
2. GoodJob background job + status endpoint the frontend polls; job result kept briefly
<br>
(e.g. in the DB row or cache) and deleted after retrieval.
<br>
Recommendation: start with (1) behind a clean service interface so the switch to (2) does
<br>
not change the client contract more than "response may be deferred". v1 must not depend on
<br>
streaming.
* Endpoint style (API v3 vs. Rails controller under `/ai/…`) to be aligned with frontend
<br>
conventions of the CKEditor work package; the contract above is style-agnostic.
* The transform is stateless: nothing is written to the work package. Persistence happens only
<br>
through the regular save flow after the user accepts the result.
## Permissions and visibility considerations
* Caller must be allowed to edit the work package's description (existing edit permission on
<br>
the WP / add permission for new ones). No new permission in v1.
* Content of other users is never involved — the user only transforms text they submitted.
## Out of scope
* Selection-based transforms (second response contract; later increment)
* Streaming responses
* Usage counting, budgets, rate limiting (anticipated later; the endpoint is the natural
<br>
chokepoint)
##
## Summary
The HTTP interface between the editor and the description assistant: list available actions for
a work package, and execute an action against client-side text. Owns the server-side prompt
assembly. This is the contract work package between the two work streams — align the endpoint
shapes with the LLM connection developer before implementation.
## Acceptance criteria
**List endpoint**
* `GET` endpoint returning the actions available for a given work package (or type, for new,
<br>
* Filtering is done server-side: action `active`, feature toggle on, global AI toggle on, LLM
<br>
<br>
* Returns an empty list (not an error) when the assistant is disabled — the editor simply
<br>
**Execute endpoint**
* `POST` endpoint accepting `{ action_id, content, work_package_id | type_id }` where `content`
<br>
* Server-side prompt assembly:
* system message = hardcoded scaffold + effective action prompt (+ the type's default
<br>
* user message = the submitted content, verbatim — user content is never interpolated into
<br>
* The LLM call goes through the client/service of AI-3, using the action's model override or
<br>
* Availability is re-validated on execute (action active, type matches, toggles on) →
<br>
* Error taxonomy mapped to distinct, translated messages: LLM not configured / disabled,
<br>
<br>
* Request/response content is not persisted and not written to logs above debug level.
## Technical notes
* **Sync vs. job — decide here, together with AI-3:** OpenProject has no active
<br>
1. Synchronous request with a strict server-side timeout (~30–60s). Simplest; ties up a
<br>
2. GoodJob background job + status endpoint the frontend polls; job result kept briefly
<br>
<br>
<br>
<br>
* Endpoint style (API v3 vs. Rails controller under `/ai/…`) to be aligned with frontend
<br>
* The transform is stateless: nothing is written to the work package. Persistence happens only
<br>
## Permissions and visibility considerations
* Caller must be allowed to edit the work package's description (existing edit permission on
<br>
* Content of other users is never involved — the user only transforms text they submitted.
## Out of scope
* Selection-based transforms (second response contract; later increment)
* Streaming responses
* Usage counting, budgets, rate limiting (anticipated later; the endpoint is the natural
<br>