mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(durable): read a conversation's context as of an earlier entry
closes #10512
This commit is contained in:
parent
b0114ef5fa
commit
76f6c06daf
5 changed files with 42 additions and 4 deletions
|
|
@ -5,6 +5,7 @@
|
|||
### Added
|
||||
|
||||
- `ToolExecutionApi.models` and `HookApi.models`: the Harness's `models`, so tools and hooks can resolve models and make requests with the same catalog, credentials, and request transforms as generation ([#10395](https://github.com/earendil-works/pi/issues/10395)).
|
||||
- `Conversation.context(context, { at })` returns the model context as of a visible earlier entry, the same view `fork(at)` would start with, without creating a conversation ([#10512](https://github.com/earendil-works/pi/issues/10512)).
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -539,7 +539,8 @@ interface Conversation {
|
|||
change: (tx: Tx) => T | Promise<T>,
|
||||
context: Context,
|
||||
): Promise<T>;
|
||||
context(context: Context): Promise<ContextView>;
|
||||
/** With `at`, the context as of that visible entry, as `fork(at)` would start with. */
|
||||
context(context: Context, options?: { readonly at?: EntryId }): Promise<ContextView>;
|
||||
entries(
|
||||
query: Omit<EntryQuery, "conversationId">,
|
||||
limit: number,
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@ class ConversationImpl<Tool extends ToolRegistration> implements Conversation {
|
|||
return this.#host.harness.commitWith(change, context, { conversationId: this.id });
|
||||
}
|
||||
|
||||
context(context: Context): Promise<ContextView> {
|
||||
return readContext(this.#host.harness, this.#host.storage, this.id, context);
|
||||
context(context: Context, options?: { readonly at?: EntryId }): Promise<ContextView> {
|
||||
return readContext(this.#host.harness, this.#host.storage, this.id, context, options?.at);
|
||||
}
|
||||
|
||||
entries(
|
||||
|
|
|
|||
|
|
@ -531,7 +531,11 @@ export interface Conversation {
|
|||
|
||||
/** Session commit whose `tx.createTask()` defaults to this conversation. */
|
||||
commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
|
||||
context(context: Context): Promise<ContextView>;
|
||||
/**
|
||||
* Committed raw active transcript and model context. With `at`, the context as of that visible entry: the same view
|
||||
* `fork(at)` would start with, without creating a conversation.
|
||||
*/
|
||||
context(context: Context, options?: { readonly at?: EntryId }): Promise<ContextView>;
|
||||
/** Newest-first fork-aware history of this conversation. */
|
||||
entries(
|
||||
query: Omit<EntryQuery, "conversationId">,
|
||||
|
|
|
|||
|
|
@ -142,6 +142,38 @@ describe("conversation context", () => {
|
|||
expect(parentView.entries.map((entry) => entry.kind)).toEqual(["reset", "message"]);
|
||||
});
|
||||
|
||||
// #10512
|
||||
it("reads the context as of an earlier entry, as a fork at that entry starts", async () => {
|
||||
const { root, append, message } = await setup();
|
||||
const first = await message(user("first"));
|
||||
const call = await message(assistant("calling", { calls: ["x", "y"] }));
|
||||
const result = await message(toolResult("x"));
|
||||
await message(toolResult("y"));
|
||||
const edit = await append({
|
||||
kind: "edit",
|
||||
edits: [{ target: first.id, action: "replace", messages: [user("first v2")] }],
|
||||
});
|
||||
const reset = await append({ kind: "reset", head: "self", model: [user("fresh start")] });
|
||||
const tail = await message(assistant("after reset"));
|
||||
|
||||
for (const at of [first, call, result, edit, reset, tail]) {
|
||||
const fork = await root.fork(at.id, { ownership: { kind: "ownerless" } }, context);
|
||||
expect(await root.context(context, { at: at.id })).toEqual(await fork.context(context));
|
||||
}
|
||||
// Stepped back before the edit and the reset: neither applies, and a cut call gets synthesized results.
|
||||
expect((await root.context(context, { at: call.id })).messages.map(describeMessage)).toEqual([
|
||||
"user:first",
|
||||
"assistant:calling",
|
||||
"result:x:error",
|
||||
"result:y:error",
|
||||
]);
|
||||
expect(await root.context(context, { at: tail.id })).toEqual(await root.context(context));
|
||||
expect(await root.context(context, {})).toEqual(await root.context(context));
|
||||
|
||||
const other = await root.fork(first.id, { ownership: { kind: "ownerless" } }, context);
|
||||
await expect(other.context(context, { at: tail.id })).rejects.toThrow("is not visible");
|
||||
});
|
||||
|
||||
it("extends a task invocation's context read with only newer entries", async () => {
|
||||
let scanned = 0;
|
||||
const memory = new MemoryStorage();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue