Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/agent-core-v2/src/agent/loop/loopService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1314,7 +1314,7 @@ export class AgentLoopService extends Disposable implements IAgentLoopService {
turn.interruptStep = undefined;
if (turn.retryRequested) {
turn.retryRequested = false;
this.machineEngine().resetHistory(historyFromContext(this.context.get()));
await this.machineEngine().resetHistory(historyFromContext(this.context.get()));
this.machineEngine().notify(EMPTY_MACHINE_PROMPT);
}
return;
Expand Down
32 changes: 29 additions & 3 deletions packages/agent-core-v2/src/agent/loop/machine/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import type { ToolInfo, ToolResult as AgentToolResult, ToolUpdate as AgentToolUp
import type { ToolInputDisplay } from '#/tool/toolInputDisplay';
import { createAgentMachine } from '#human/agent/machine';
import { createTurnMachine, type AssistantEntry, type HistoryMessage } from '#human/agent/turn';
import { messageAppended, turnEnded } from '#human/agent/events';
import { agentSlices, type AgentEventStore } from '#human/agent/slices';
import { createEventStoreSync } from '#human/eventStore/eventStore';
import { memoryJournal } from '#human/eventStore/journal';
import type { LlmErrorMessage } from '#human/llm/errors';
import type { FinishInfo } from '#human/llm/finish-reason';
import type { StreamedMessagePart, UserMessage } from '#human/llm/message';
Expand Down Expand Up @@ -157,7 +161,7 @@ export interface MachineEngine {
remind(key: string, message: UserMessage): void;
cancelQueueItem(id: string): void;
abort(): void;
resetHistory(history: readonly HistoryMessage[]): void;
resetHistory(history: readonly HistoryMessage[]): Promise<void>;
stop(): void;
snapshot(): MachineEngineSnapshot;
currentStep(): number;
Expand Down Expand Up @@ -264,6 +268,16 @@ export function createMachineEngine(options: CreateMachineEngineOptions): Machin
publish({ type: 'toolBatchFailed', error });
},
});
const journal = memoryJournal();
const initialTurnId = options.initialTurnId ?? 0;
if (initialTurnId > 0) {
void journal.append({
type: turnEnded.type,
kind: 'event',
data: turnEnded({ turnId: initialTurnId - 1, outcome: 'done' }),
});
}
const store: AgentEventStore = createEventStoreSync({ journal, slices: agentSlices });
const actor = createActor(
createAgentMachine({
tools: tools.tools,
Expand All @@ -278,7 +292,7 @@ export function createMachineEngine(options: CreateMachineEngineOptions): Machin
),
abortTimeoutMs: options.abortTimeoutMs,
}),
{ input: { request: { model: options.model, systemPrompt: options.systemPrompt }, turnId: options.initialTurnId } },
{ input: { request: { model: options.model, systemPrompt: options.systemPrompt }, store } },
);
const subscriptions: Subscription[] = [
actor.on('turn.started', (event) => {
Expand Down Expand Up @@ -433,7 +447,19 @@ export function createMachineEngine(options: CreateMachineEngineOptions): Machin
actor.send({ type: 'input.abort' });
},
resetHistory: (history) => {
actor.send({ type: 'context.reset', history });
const journal = memoryJournal();
for (const message of history) {
void journal.append({ type: messageAppended.type, kind: 'event', data: messageAppended({ message }) });
}
const nextTurnId = (actor.getSnapshot() as unknown as MachineSnapshotLike).context.turnId;
if (nextTurnId > 0) {
void journal.append({
type: turnEnded.type,
kind: 'event',
data: turnEnded({ turnId: nextTurnId - 1, outcome: 'done' }),
});
}
return store.reset(journal);
},
stop: () => {
for (const subscription of subscriptions) subscription.unsubscribe();
Expand Down
75 changes: 75 additions & 0 deletions packages/agent-core-v2/src/human/agent/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { z } from 'zod';

import { defineEvent } from '#/eventStore/events';

import { historyMessageSchema, systemMessageSchema, userMessageSchema } from './historySchema';

export const messageAppended = defineEvent({
type: 'message.appended',
schema: z.object({ message: historyMessageSchema }),
});
export type MessageAppended = ReturnType<typeof messageAppended>;

export const turnStarted = defineEvent({
type: 'turn.started',
schema: z.object({ turnId: z.number().int(), queueItemId: z.string().optional() }),
});
export type TurnStarted = ReturnType<typeof turnStarted>;

export const turnEnded = defineEvent({
type: 'turn.ended',
schema: z.object({
turnId: z.number().int(),
outcome: z.enum(['done', 'failed', 'aborted']),
errorMessage: z.string().optional(),
}),
});
export type TurnEnded = ReturnType<typeof turnEnded>;

export const inputSubmitted = defineEvent({
type: 'input.submitted',
schema: z.object({ id: z.string().optional(), message: userMessageSchema }),
});
export type InputSubmitted = ReturnType<typeof inputSubmitted>;

export const inputNotified = defineEvent({
type: 'input.notified',
schema: z.object({ message: userMessageSchema, source: z.string().optional() }),
});
export type InputNotified = ReturnType<typeof inputNotified>;

export const inputReminded = defineEvent({
type: 'input.reminded',
schema: z.object({ key: z.string(), message: z.union([userMessageSchema, systemMessageSchema]) }),
});
export type InputReminded = ReturnType<typeof inputReminded>;

export const inputSteered = defineEvent({
type: 'input.steered',
schema: z.object({ id: z.string(), message: userMessageSchema }),
});
export type InputSteered = ReturnType<typeof inputSteered>;

export const inputCancelled = defineEvent({
type: 'input.cancelled',
schema: z.object({ id: z.string() }),
});
export type InputCancelled = ReturnType<typeof inputCancelled>;

export const queueDrained = defineEvent({
type: 'queue.drained',
schema: z.object({ id: z.string().optional() }),
});
export type QueueDrained = ReturnType<typeof queueDrained>;

export const inputDrained = defineEvent({ type: 'input.drained', schema: z.object({}) });
export type InputDrained = ReturnType<typeof inputDrained>;

export const notificationsDrained = defineEvent({ type: 'notifications.drained', schema: z.object({}) });
export type NotificationsDrained = ReturnType<typeof notificationsDrained>;

export const stateUpdated = defineEvent({
type: 'state.updated',
schema: z.object({ name: z.string(), value: z.unknown() }),
});
export type StateUpdated = ReturnType<typeof stateUpdated>;
101 changes: 101 additions & 0 deletions packages/agent-core-v2/src/human/agent/historySchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { z } from 'zod';

import type { HistoryMessage } from './turn';

const textPartSchema = z.object({ type: z.literal('text'), text: z.string() });
const thinkPartSchema = z.object({
type: z.literal('think'),
think: z.string(),
encrypted: z.string().optional(),
detailsIndex: z.number().optional(),
});
const imageUrlPartSchema = z.object({
type: z.literal('image_url'),
imageUrl: z.object({ url: z.string(), id: z.string().optional(), name: z.string().optional() }),
});
const audioUrlPartSchema = z.object({
type: z.literal('audio_url'),
audioUrl: z.object({ url: z.string(), id: z.string().optional() }),
});
const videoUrlPartSchema = z.object({
type: z.literal('video_url'),
videoUrl: z.object({ url: z.string(), id: z.string().optional(), name: z.string().optional() }),
});

export const contentPartSchema = z.discriminatedUnion('type', [
textPartSchema,
thinkPartSchema,
imageUrlPartSchema,
audioUrlPartSchema,
videoUrlPartSchema,
]);

const toolDescriptionSchema = z.object({
name: z.string(),
description: z.string(),
parameters: z.record(z.string(), z.unknown()),
deferred: z.literal(true).optional(),
});

const toolCallSchema = z.object({
type: z.literal('function'),
id: z.string(),
name: z.string(),
arguments: z.string().nullable(),
extras: z.record(z.string(), z.unknown()).optional(),
rawId: z.string().optional(),
_streamIndex: z.union([z.number(), z.string()]).optional(),
});

export const systemMessageSchema = z.object({
role: z.literal('system'),
content: z.array(contentPartSchema),
tools: z.array(toolDescriptionSchema).optional(),
});

export const userMessageSchema = z.object({
role: z.literal('user'),
content: z.array(contentPartSchema),
});

export const assistantMessageSchema = z.object({
role: z.literal('assistant'),
content: z.array(contentPartSchema),
toolCalls: z.array(toolCallSchema),
});

export const toolMessageSchema = z.object({
role: z.literal('tool'),
content: z.array(contentPartSchema),
toolCallId: z.string(),
});

const tokenUsageSchema = z.object({
inputOther: z.number(),
output: z.number(),
inputCacheRead: z.number(),
inputCacheCreation: z.number(),
raw: z.record(z.string(), z.unknown()).optional(),
});

const finishInfoSchema = z.object({
finishReason: z.enum(['completed', 'tool_calls', 'truncated', 'filtered', 'paused', 'other']).nullable(),
rawFinishReason: z.string().nullable(),
});

const entryMetaSchema = z.object({ source: z.string().optional(), key: z.string().optional() });

const assistantMetaSchema = entryMetaSchema.extend({
model: z.object({ provider: z.string(), model: z.string() }).optional(),
usage: tokenUsageSchema,
headers: z.record(z.string(), z.string()).optional(),
finish: finishInfoSchema.optional(),
messageId: z.string().optional(),
});

export const historyMessageSchema = z.union([
z.object({ message: systemMessageSchema, meta: entryMetaSchema }),
z.object({ message: userMessageSchema, meta: entryMetaSchema }),
z.object({ message: assistantMessageSchema, meta: assistantMetaSchema }),
z.object({ message: toolMessageSchema, meta: entryMetaSchema }),
]) as z.ZodType<HistoryMessage>;
Loading
Loading