diff --git a/packages/durable/CHANGELOG.md b/packages/durable/CHANGELOG.md index 229da9950..e7c315955 100644 --- a/packages/durable/CHANGELOG.md +++ b/packages/durable/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Breaking Changes + +- `TaskRuntime.context()` takes the cutoff as an options object, matching `Conversation.context()`: `runtime.context(conversationId, context, { at })` replaces `runtime.context(conversationId, context, at)`. + ### 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)). diff --git a/packages/durable/docs/spec.md b/packages/durable/docs/spec.md index 14244bc08..8ce6fddf0 100644 --- a/packages/durable/docs/spec.md +++ b/packages/durable/docs/spec.md @@ -1698,7 +1698,7 @@ interface TaskRuntime extends DocumentObserver, Docum entry(id: EntryId, context: Context): Promise; entry(token: Entry, id: EntryId, context: Context): Promise | undefined>; /** Committed raw active transcript and model context, optionally cut off at `at`. */ - context(conversationId: ConversationId, context: Context, at?: EntryId): Promise; + context(conversationId: ConversationId, context: Context, options?: { readonly at?: EntryId }): Promise; /** The Harness clock. */ now(): number; /** Forward a non-fatal failure to `HarnessOptions.onReport`. */ diff --git a/packages/durable/src/harness/compaction.ts b/packages/durable/src/harness/compaction.ts index 89db279d8..db700163b 100644 --- a/packages/durable/src/harness/compaction.ts +++ b/packages/durable/src/harness/compaction.ts @@ -151,7 +151,7 @@ export const CompactionTask = defineTask entry.id === firstKept); const now = runtime.now(); const messages: Message[] = [ diff --git a/packages/durable/src/harness/generation.ts b/packages/durable/src/harness/generation.ts index 9bedcfb68..ea384605a 100644 --- a/packages/durable/src/harness/generation.ts +++ b/packages/durable/src/harness/generation.ts @@ -192,7 +192,7 @@ export const GenerationTask = defineTask { const replaced = await hook({ messages }, runtime, context); @@ -460,7 +460,7 @@ async function classify( const overflow = message.stopReason === "error" && isContextOverflow(message); if (overflow && compacted === undefined && settings.compaction.enabled) { const policy = settings.compaction; - const view = await runtime.context(conversationId, context, cutoff); + const view = await runtime.context(conversationId, context, { at: cutoff }); if (selectCut(view, policy.keepRecentTokens) !== undefined) { const text = message.errorMessage ?? "Context overflow"; await runtime.commit(async (tx): Promise => { @@ -550,7 +550,8 @@ async function startToolRound( context: Context, ): Promise { const conversationId = runtime.conversationId; - const messages = request.messages ?? (await runtime.context(conversationId, context, request.cutoff)).messages; + const messages = + request.messages ?? (await runtime.context(conversationId, context, { at: request.cutoff })).messages; const offered = new Set(getCurrentTools(messages).map((tool) => tool.name)); // Read as the round starts; a tool is resolved as its tool task resolves it. const tools = (await runtime.agent(context)).tools; diff --git a/packages/durable/src/harness/scheduler.ts b/packages/durable/src/harness/scheduler.ts index 2c9e9fd0f..05056ac53 100644 --- a/packages/durable/src/harness/scheduler.ts +++ b/packages/durable/src/harness/scheduler.ts @@ -1174,14 +1174,14 @@ export class TaskScheduler { return token === undefined || entry?.kind === token.kind ? entry : undefined; }); }) as ErasedRuntime["entry"], - context: (conversationId, context, at) => + context: (conversationId, context, options) => this.#read(invocation, async () => { const { view, range } = await readContextFrom( this.#session, this.#storage, conversationId, context, - at, + options?.at, invocation.contextRange, ); if (!invocation.ended) invocation.contextRange = range; diff --git a/packages/durable/src/types.ts b/packages/durable/src/types.ts index 7c0afcc04..fa6f88149 100644 --- a/packages/durable/src/types.ts +++ b/packages/durable/src/types.ts @@ -220,7 +220,7 @@ export interface TaskRuntime extends DocumentObserver /** Undefined when the entry is absent, not visible, or has another kind. */ entry(token: Entry, id: EntryId, context: Context): Promise | undefined>; /** Committed raw active transcript and model context, optionally cut off at the visible entry `at`. */ - context(conversationId: ConversationId, context: Context, at?: EntryId): Promise; + context(conversationId: ConversationId, context: Context, options?: { readonly at?: EntryId }): Promise; /** The Harness clock. */ now(): number; /** Forward a non-fatal failure to `HarnessOptions.onReport`. */ diff --git a/packages/durable/test/harness-context.test.ts b/packages/durable/test/harness-context.test.ts index a3779282e..fae70e106 100644 --- a/packages/durable/test/harness-context.test.ts +++ b/packages/durable/test/harness-context.test.ts @@ -210,7 +210,7 @@ describe("conversation context", () => { }, taskContext); const read = async (at?: EntryId): Promise<{ view: ContextView; rows: number }> => { const before = scanned; - const view = await runtime.context(root.id, taskContext, at); + const view = await runtime.context(root.id, taskContext, { at }); return { view, rows: scanned - before }; }; const initial = await read(); diff --git a/packages/durable/test/harness-tasks.test.ts b/packages/durable/test/harness-tasks.test.ts index 8cf843cb8..9bebc8fee 100644 --- a/packages/durable/test/harness-tasks.test.ts +++ b/packages/durable/test/harness-tasks.test.ts @@ -544,8 +544,8 @@ describe("task runtime", () => { ); seen.push((await runtime.snapshot(Notes, runtime.conversationId, ctx))?.text); seen.push((await runtime.snapshotAsOf(Notes, runtime.conversationId, first!, ctx))?.text); - seen.push((await runtime.context(runtime.conversationId, ctx, first)).entries.length); - seen.push((await runtime.context(runtime.conversationId, ctx, second)).messages.length); + seen.push((await runtime.context(runtime.conversationId, ctx, { at: first })).entries.length); + seen.push((await runtime.context(runtime.conversationId, ctx, { at: second })).messages.length); seen.push(runtime.now()); runtime.report(new Error("reported")); await runtime.commit(() => completed(null), ctx);