diff --git a/internal/usecase/repairer.go b/internal/usecase/repairer.go index cafe2e3..9128957 100644 --- a/internal/usecase/repairer.go +++ b/internal/usecase/repairer.go @@ -55,19 +55,20 @@ func (r *defaultOutputRepairer) Repair(ctx context.Context, req RepairRequest) ( return nil, err } - messages := domain.ConcatRenderedMessages(req.OriginalMessages, nil) - if strings.TrimSpace(req.PreviousOutput) != "" { - messages = append(messages, domain.RenderedMessage{ + hasPreviousOutput := strings.TrimSpace(req.PreviousOutput) != "" + suffix := make([]domain.RenderedMessage, 0, 2) + if hasPreviousOutput { + suffix = append(suffix, domain.RenderedMessage{ Role: domain.RoleAssistant, Content: req.PreviousOutput, }) } previousResponse := "The previous response was empty." - if strings.TrimSpace(req.PreviousOutput) != "" { + if hasPreviousOutput { previousResponse = "The previous response is included immediately before this instruction." } - messages = append(messages, domain.RenderedMessage{ + suffix = append(suffix, domain.RenderedMessage{ Role: domain.RoleUser, Content: fmt.Sprintf( "Repair attempt %d of %d for validation mode %s.\n"+ @@ -82,6 +83,7 @@ func (r *defaultOutputRepairer) Repair(ctx context.Context, req RepairRequest) ( formatRepairDiagnostics(req.ValidationErrors), ), }) + messages := domain.ConcatRenderedMessages(req.OriginalMessages, suffix) resp, err := r.llm.Generate(ctx, newGenerationRequest( domain.RenderedPrompt{Messages: messages}, diff --git a/types.go b/types.go index 9d05100..ae71504 100644 --- a/types.go +++ b/types.go @@ -123,13 +123,20 @@ type RunRequest struct { // Validation optionally replaces the prompt's complete output contract. It // does not merge individual fields. Nil uses the prompt contract. Validation *OutputContract - // AppendedMessages are already-rendered messages appended after every prompt - // definition message. Promptkit neither templates nor resolves files in - // them, and preserves valid content exactly. Roles are trimmed and + // AppendedMessages are already-rendered messages appended in caller order + // after every prompt definition message. Promptkit neither templates nor + // resolves files in them. Content must be valid UTF-8 and is preserved + // exactly, including empty or whitespace-only content. Roles are trimmed and // lowercased, then must be [RoleDeveloper], [RoleSystem], [RoleUser], or - // [RoleAssistant]. Nil and empty slices are equivalent. Prepare, - // PrepareExecution, and Run validate and copy the messages before source or - // model work; malformed values return an error matching ErrInvalidRequest. + // [RoleAssistant]. Any CacheControl is normalized as documented on that + // type. + // + // Nil and empty slices are equivalent. Promptkit imposes no message-count, + // byte-size, token, or context-window limit and does not truncate content; + // an upstream rejection follows the ordinary generation-error contract. + // Prepare, PrepareExecution, and Run validate and copy the messages before + // source or model work. Malformed values return an error matching + // ErrInvalidRequest. AppendedMessages []RenderedMessage } @@ -170,8 +177,9 @@ type PreparedRun struct { SessionID string `json:"session_id,omitempty"` // RenderedPromptHash is an opaque equality value for SessionID and Messages. RenderedPromptHash string `json:"rendered_prompt_hash"` - // Messages are the rendered messages that Run or RunPrepared passes to the - // LLM client. + // Messages are the complete effective messages that Run or RunPrepared passes + // to the LLM client: definition messages in rendered order followed by any + // RunRequest.AppendedMessages in caller order. Messages []RenderedMessage `json:"messages"` // StartTime is the UTC time at which preparation began. StartTime time.Time `json:"start_time,omitempty"` @@ -633,8 +641,8 @@ type RenderedPrompt struct { // SessionID is the optional effective direct or rendered session // identifier supplied to the model client. SessionID string `json:"session_id,omitempty"` - // Messages contains the frozen effective messages: definition messages in - // their rendered order followed by any RunRequest.AppendedMessages. + // Messages contains the effective messages in provider order: definition + // messages in rendered order followed by any RunRequest.AppendedMessages. Messages []RenderedMessage `json:"messages"` } @@ -645,14 +653,17 @@ type RenderedPrompt struct { type RenderedMessage struct { // Role is the provider-bound chat role. Role string `json:"role"` - // Content is the provider-bound message text and may be empty or whitespace. + // Content is the provider-bound message text. Request-supplied content must + // be valid UTF-8 and may be empty or whitespace-only. Content string `json:"content"` // CacheControl is optional provider cache metadata. CacheControl *CacheControl `json:"cache_control,omitempty"` } // CacheControl describes provider cache metadata attached to prompt content -// and has a stable JSON representation. +// and has a stable JSON representation. At a request boundary Type and TTL +// must be valid UTF-8 and are trimmed. Type must be [CacheControlEphemeral], +// and TTL must be empty or "1h"; other values make the request invalid. type CacheControl struct { // Type identifies the cache behavior. Type CacheControlType `json:"type"`