feat(addons): serve the op history in the shared connector route

A platform that implements HistoryReader answers the op history. The
route checks the thread, bounds the limit and the answer, and does not
wait out a rate limit.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto committed 2026-11-03 11:30:11 +00:00
1 parent f6f0dce5e5
commit f426ef1532
2 files changed
+79

No files matched your search

+26
View File
@@ -93,6 +93,14 @@ type Drafter interface {
Draft(ctx context.Context, req contract.ConnectorRequest) error
}
// HistoryReader is a Platform that reads the earlier messages of a thread for
// the op history. It returns the messages oldest first, the newest of those
// before req.Before, at most req.Limit of them, and whether older messages
// exist. Serve checks the request and bounds the answer.
type HistoryReader interface {
History(ctx context.Context, req contract.ConnectorRequest) ([]contract.ConnectorHistoryMessage, bool, error)
}
// Splitter is a Platform that cuts its texts itself; others use Split.
type Splitter interface {
Split(text string) []string
@@ -159,6 +167,24 @@ func Serve(ctx context.Context, p Platform, req contract.ConnectorRequest) (cont
if f, err = m.Media(ctx, req.Token, req.Media); err == nil {
out.File = &f
}
case contract.ConnectorOpHistory:
h, ok := p.(HistoryReader)
if !ok || req.Chat == "" || req.Thread == "" {
return contract.ConnectorResponse{}, fmt.Errorf("%w: no thread, or a platform that cannot read one", contract.ErrRefused)
}
if req.Limit < 1 || req.Limit > contract.MaxHistoryMessages {
req.Limit = contract.DefaultHistoryMessages
}
// Not retried after a wait: the history is context, and the host
// would rather answer without it than hold the answer behind it.
var msgs []contract.ConnectorHistoryMessage
var more bool
if msgs, more, err = h.History(ctx, req); err == nil {
if len(msgs) > req.Limit {
msgs, more = msgs[len(msgs)-req.Limit:], true
}
out.Messages, out.Truncated = msgs, more
}
default:
return contract.ConnectorResponse{}, fmt.Errorf("%w: unknown op", contract.ErrRefused)
}
+53
View File
@@ -94,6 +94,19 @@ func (r *rich) Draft(_ context.Context, req contract.ConnectorRequest) error {
return r.err
}
// reading also reads the history of a thread.
type reading struct {
script
msgs []contract.ConnectorHistoryMessage
more bool
got []contract.ConnectorRequest
}
func (r *reading) History(_ context.Context, req contract.ConnectorRequest) ([]contract.ConnectorHistoryMessage, bool, error) {
r.got = append(r.got, req)
return r.msgs, r.more, r.err
}
var _ = Describe("Serve", func() {
ctx := context.Background()
req := func(op string) contract.ConnectorRequest {
@@ -287,4 +300,44 @@ var _ = Describe("Serve", func() {
}
Expect(f.files).To(HaveLen(1))
})
Describe("the op history", func() {
hist := func() contract.ConnectorRequest {
r := req("history")
r.Thread, r.Limit, r.Before = "t1", 2, "9"
return r
}
It("is refused on a platform that cannot read a thread, and without a thread", func() {
_, err := connector.Serve(ctx, &script{}, hist())
Expect(err).To(MatchError(contract.ErrRefused))
r := hist()
r.Thread = ""
_, err = connector.Serve(ctx, &reading{}, r)
Expect(err).To(MatchError(contract.ErrRefused))
})
It("returns the newest messages within the limit and says that older ones exist", func() {
s := &reading{msgs: []contract.ConnectorHistoryMessage{{Text: "a"}, {Text: "b"}, {Text: "c"}}}
out, err := connector.Serve(ctx, s, hist())
Expect(err).NotTo(HaveOccurred())
Expect(out.Messages).To(HaveLen(2))
Expect(out.Messages[0].Text).To(Equal("b"))
Expect(out.Truncated).To(BeTrue())
Expect(s.got[0].Before).To(Equal("9"))
})
It("uses the default limit when the host asks for none or too many", func() {
s := &reading{}
r := hist()
r.Limit = contract.MaxHistoryMessages + 1
_, err := connector.Serve(ctx, s, r)
Expect(err).NotTo(HaveOccurred())
Expect(s.got[0].Limit).To(Equal(contract.DefaultHistoryMessages))
})
It("types the error of the platform and does not wait for a rate limit", func() {
s := &reading{script: script{err: &connector.RetryAfter{Seconds: 30}}}
out, err := connector.Serve(ctx, s, hist())
Expect(err).NotTo(HaveOccurred())
Expect(out.Error.Code).To(Equal(contract.ConnectorRateLimited))
Expect(s.got).To(HaveLen(1))
})
})
})