Repository navigation
feat(translator): tell the systemPrompt builder what it is translating - #180
Conversation
Payload now defaults `auth.forgotPassword.minRequestInterval` to 15000 (collections/config/defaults.js), and a non-zero interval makes it add a `resetPasswordRequestedAt` field to every auth collection (auth/getAuthFields.js). Nothing in this repo configures either; the generated file had simply not been regenerated since the version that introduced the default. Committed on its own so the machine-written change does not ride along with hand-written work and reappear in every branch's diff.
A `systemPrompt` builder could not read anything from the CMS and could not know which collection it was working on. Both seams were blind, and together they made a per-collection prompt impossible. `SystemPromptBuilder` was synchronous, so a builder had nowhere to await a lookup. The provider saw `translate(input, sourceLng, targetLng)` — numbered strings and two language codes, nothing about the document. The reporter of #109 wanted to inject a glossary that lives in the CMS: unable to read it from a builder, they stopped using `systemPrompt` altogether and wrapped the whole provider in their own async `translate` that rebuilds the OpenAI provider per call with the terms in a closure — a wrapper whose only purpose was to get an await. Scoping a term to one collection stayed impossible at any price, because neither seam knew which collection it was. Now: - `SystemPromptBuilder` may answer with a promise. The call site already awaited it through a helper already typed to accept one, so this was a type away. It also fixes a live defect: an async builder's result was previously concatenated unawaited, putting "[object Promise]" into the prompt. - a new `DocumentRef` — `{ collection, id }` — says what is being translated. It is required on the pipeline's own types, because every translation has a subject, and it rides to the provider inside the existing `TranslationRequestOptions`, the type added in 0.13.0 for exactly this. - both surfaces name it. The document route and the field route pass the same pair, so a prompt scoped to a collection applies however the editor started the translation. Identity, not a switch: the two halves are one named type rather than two loose fields, so "a collection with no document" stops being expressible, and `document` is required inside the request description even though the description itself may be omitted — a provider called on its own is asked to translate strings, but a half-description is not allowed. No `payload` handle is passed. The only channel from the handler to the provider runs through src/core, so a handle would put a Payload reference in the kernel; the builder is awaitable now, so a consumer resolves its own. Behaviour note for the release: the fourth argument now reaches a provider on nearly every call, where its presence used to imply inline marks. A provider branching on `if (options)` rather than `options.inlineMarks === true` will see a difference. Closes #109
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🎉 This PR is included in version 0.18.0-next.1 🎉 The release is available on npm package (@major/translator dist-tag) Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 0.18.0 🎉 The release is available on npm package (@latest dist-tag) Your semantic-release bot 📦🚀 |
main released 0.18.0, carrying the prompt-context work (#180, closing #109). The same feature branch was already merged here as 0.18.0-next.1, so the only real conflict was the bookkeeping: the version field keeps 1.0.0-next.4, because the prerelease line is ahead and semantic-release owns that field, and the changelog keeps both headings over the one body they describe. Recording main as an ancestor is what the eventual major/translator to main merge needs.
|
🎉 This PR is included in version 1.10.2 🎉 The release is available on npm package (@latest dist-tag) Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.0.0-next.5 🎉 The release is available on npm package (@next dist-tag) Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.1.1 🎉 The release is available on npm package (@latest dist-tag) Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.4.0 🎉 The release is available on npm package (@latest dist-tag) Your semantic-release bot 📦🚀 |
Closes #109.
A
systemPromptbuilder could not read anything from the CMS, and could not know which collection it was working on. Both seams were blind, and together they made a per-collection prompt impossible.The reporter wanted to inject a glossary that lives in the CMS. Unable to read it from a builder, they stopped using
systemPromptaltogether and wrapped the whole provider in their own asynctranslatethat rebuilds the OpenAI provider on every call with the terms in a closure — a wrapper whose only purpose was to get anawait. Scoping a term to one collection stayed impossible at any price, because neither seam knew which collection it was.What changed
The builder may answer with a promise.
SystemPromptBuilderreturnsstring | Promise<string>. The call site already awaited it through a helper already typed to accept one, so this was a type away — and it fixes a live defect, not only a gap: an async builder's result was previously concatenated unawaited, putting[object Promise]into the prompt sent to the model.The builder is told what it is translating. A new
DocumentRef—{ collection, id }— travels from the handler to the provider and intoSystemPromptContext. Both surfaces name it: the document route and the field route pass the same pair, so a prompt scoped to a collection applies however the editor started the translation.Shape, and why it is this one
collectionanddocumentIdbegan as two loose optional fields besideinlineMarks. That was wrong twice over, and both corrections came from review rather than from the first draft:inlineMarksis a switch about how to translate; these say what is being translated. One name now holds them, so "a collection with no document" stops being expressible.TranslationStage, and it always sets the document — sodocumentis required inside it. The parameter holding it stays optional, because a provider called on its own is asked to translate strings and that is a complete request; what is not allowed is describing a request halfway.No
payloadhandle is passed, though the issue asked for one. The only channel from the handler to the provider runs throughsrc/core, so a handle would put a Payload reference in the kernel — which neithersrc/corenorsrc/translation-providershas today. The builder is awaitable now, so a consumer resolves its own.Compatibility
Declarations were built on
mainand on this branch and diffed:index.d.tsis byte-identical. Two caveats belong in the release note rather than being left to be discovered:if (options)instead ofoptions.inlineMarks === truewill see a difference.`${base(ctx)}\n…`— now silently interpolates[object Promise], because a template literal accepts a promise and the compiler says nothing. Same class as the defect fixed here.Requiring
documentinside the request description is breaking for anyone constructing one by hand without it. Nobody does; the plugin is the sole producer.Scheduled, not done
translate(input, sourceLng, targetLng, options?)keeps two adjacent same-typed strings that can be swapped silently, and a trailing bag that mixes what with how. Recorded astranslate-positional-languagesindocs/DEPRECATIONS.md, statusplanned, to land with the kernel extraction rather than forcing an early major. The path the README recommends —createTranslationProvider({ complete })— is unaffected:CompletionFnalready takes a single object.A deeper question surfaced in review and is now open question 5 in
docs/plans/2026-08-21-translation-mechanism-kernel-extraction.md:documentis carried by three functions that never read it, and the only reader is the consumer's ownsystemPrompt. Moving prompt assembly into the plugin was considered and rejected —systemPromptbelongs to a language-model provider, and a GoogleTranslate or DeepL provider has no system prompt at all. The open question is narrower: once the provider port is reshaped anyway, doesbuildSystemPromptstay part of the provider, or become something a provider declares it accepts, the waycapabilities.inlineMarksalready does.Verification
apps/devsp-diff-checksclean ·sp-lint-deltaintroduced nothingNew coverage: the builder awaited and its text appended to; the document named at the unit, provider and pipeline levels; the field surface naming the same document as the document surface; and an end-to-end case on three databases asserting what the provider was actually handed. Each guard proven by a mutation that reddened it alone and was reverted.
Two tests changed that are worth naming rather than hiding: one asserted the old three-argument call and now asserts four — that edit is the behaviour change — and one that passed before the implementation existed was removed as a tautology.
A separate first commit regenerates
apps/dev/src/payload-types.ts: Payload now defaultsauth.forgotPassword.minRequestInterval, which adds aresetPasswordRequestedAtfield. Machine-written, nothing in this repo configures it, kept out of the hand-written commit.