@kuralle-agents/core 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -4
- package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
- package/dist/ai-sdk/uiMessageStream.js +42 -29
- package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
- package/dist/capabilities/LivePromptAssembler.js +2 -1
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/eval/EvalRunner.js +4 -4
- package/dist/eval/simulation.js +4 -3
- package/dist/events/TurnHandle.d.ts +3 -3
- package/dist/flow/collectDigression.d.ts +5 -2
- package/dist/flow/collectDigression.js +48 -10
- package/dist/flow/collectUntilComplete.js +13 -5
- package/dist/flow/degrade.js +8 -4
- package/dist/flow/emitInteractive.d.ts +2 -2
- package/dist/flow/emitInteractive.js +6 -3
- package/dist/flow/extraction.d.ts +3 -2
- package/dist/flow/extraction.js +14 -4
- package/dist/flow/reduceTransition.d.ts +2 -2
- package/dist/flow/reduceTransition.js +7 -3
- package/dist/flow/runFlow.js +97 -17
- package/dist/foundation/AgentStateController.d.ts +1 -1
- package/dist/foundation/ToolExecutor.d.ts +2 -2
- package/dist/foundation/index.d.ts +0 -11
- package/dist/foundation/index.js +0 -5
- package/dist/index.d.ts +15 -15
- package/dist/index.js +9 -6
- package/dist/outcomes/streamPart.d.ts +2 -2
- package/dist/outcomes/streamPart.js +2 -3
- package/dist/prompts/PromptBuilder.d.ts +3 -3
- package/dist/prompts/PromptBuilder.js +13 -6
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/types.d.ts +9 -2
- package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
- package/dist/runtime/InMemoryRetrievalCache.js +59 -0
- package/dist/runtime/KnowledgeProvider.d.ts +3 -2
- package/dist/runtime/KnowledgeProvider.js +60 -31
- package/dist/runtime/Runtime.d.ts +47 -1
- package/dist/runtime/Runtime.js +273 -36
- package/dist/runtime/TokenAccumulator.d.ts +7 -0
- package/dist/runtime/TokenAccumulator.js +7 -0
- package/dist/runtime/TraceRecorder.d.ts +51 -0
- package/dist/runtime/TraceRecorder.js +326 -0
- package/dist/runtime/buildAgentToolSurface.js +1 -1
- package/dist/runtime/channels/TextDriver.js +57 -31
- package/dist/runtime/channels/executeModelTool.d.ts +14 -1
- package/dist/runtime/channels/executeModelTool.js +132 -3
- package/dist/runtime/channels/extractionTurn.js +1 -1
- package/dist/runtime/channels/index.d.ts +0 -3
- package/dist/runtime/channels/index.js +0 -6
- package/dist/runtime/channels/inputBuffer.d.ts +1 -0
- package/dist/runtime/channels/inputBuffer.js +9 -0
- package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
- package/dist/runtime/channels/streaming/speakGated.js +24 -12
- package/dist/runtime/citations/index.d.ts +3 -3
- package/dist/runtime/closeRun.js +14 -10
- package/dist/runtime/compaction.d.ts +2 -0
- package/dist/runtime/compaction.js +3 -2
- package/dist/runtime/controlFlowSignal.d.ts +24 -0
- package/dist/runtime/controlFlowSignal.js +29 -0
- package/dist/runtime/ctx.d.ts +3 -4
- package/dist/runtime/ctx.js +147 -60
- package/dist/runtime/durable/RunStore.d.ts +16 -0
- package/dist/runtime/durable/RunStore.js +7 -1
- package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
- package/dist/runtime/durable/SessionRunStore.js +136 -34
- package/dist/runtime/durable/idempotency.d.ts +1 -0
- package/dist/runtime/durable/idempotency.js +3 -0
- package/dist/runtime/durable/replay.js +7 -2
- package/dist/runtime/durable/types.d.ts +11 -0
- package/dist/runtime/goals.d.ts +18 -0
- package/dist/runtime/goals.js +158 -0
- package/dist/runtime/grounding/gather.d.ts +1 -1
- package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.js +1 -1
- package/dist/runtime/handoffContinuation.d.ts +20 -0
- package/dist/runtime/handoffContinuation.js +30 -0
- package/dist/runtime/handoffOscillation.d.ts +6 -0
- package/dist/runtime/handoffOscillation.js +17 -0
- package/dist/runtime/hostLoop.js +37 -6
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/openRun.d.ts +2 -0
- package/dist/runtime/openRun.js +46 -17
- package/dist/runtime/outcomeMarking.d.ts +2 -2
- package/dist/runtime/outcomeMarking.js +2 -1
- package/dist/runtime/policies/agentTurn.d.ts +1 -1
- package/dist/runtime/policies/limits.d.ts +1 -0
- package/dist/runtime/policies/limits.js +3 -0
- package/dist/runtime/runHookSafely.d.ts +3 -0
- package/dist/runtime/runHookSafely.js +19 -0
- package/dist/runtime/select.d.ts +5 -1
- package/dist/runtime/select.js +30 -1
- package/dist/runtime/turnTokenUsage.d.ts +28 -0
- package/dist/runtime/turnTokenUsage.js +70 -0
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/services/TracingService.js +2 -3
- package/dist/session/SessionStore.d.ts +6 -0
- package/dist/session/SessionStore.js +12 -1
- package/dist/session/stores/MemoryStore.d.ts +1 -1
- package/dist/session/stores/MemoryStore.js +16 -2
- package/dist/session/testing.d.ts +6 -1
- package/dist/session/testing.js +34 -0
- package/dist/session/utils.d.ts +3 -0
- package/dist/session/utils.js +23 -0
- package/dist/skills/collectSkills.d.ts +11 -3
- package/dist/skills/collectSkills.js +51 -8
- package/dist/skills/compositeSkillStore.d.ts +26 -0
- package/dist/skills/compositeSkillStore.js +54 -0
- package/dist/skills/defineSkill.d.ts +21 -0
- package/dist/skills/defineSkill.js +26 -0
- package/dist/skills/fsSkillStore.d.ts +3 -0
- package/dist/skills/fsSkillStore.js +79 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
- package/dist/skills/parseSkillFrontmatter.js +183 -0
- package/dist/skills/wireAgentSkills.d.ts +2 -1
- package/dist/skills/wireAgentSkills.js +2 -2
- package/dist/testing/mocks.d.ts +3 -3
- package/dist/testing/mocks.js +7 -3
- package/dist/tools/controlResults.d.ts +14 -0
- package/dist/tools/controlResults.js +15 -0
- package/dist/tools/effect/ToolExecutor.d.ts +6 -0
- package/dist/tools/effect/ToolExecutor.js +75 -36
- package/dist/tools/effect/defineTool.d.ts +11 -0
- package/dist/tools/effect/defineTool.js +3 -0
- package/dist/tools/effect/errors.d.ts +2 -5
- package/dist/tools/effect/errors.js +2 -5
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tracing/MemoryTraceStore.d.ts +16 -0
- package/dist/tracing/MemoryTraceStore.js +56 -0
- package/dist/tracing/OtelTraceSink.d.ts +127 -0
- package/dist/tracing/OtelTraceSink.js +101 -0
- package/dist/tracing/TraceStore.d.ts +19 -0
- package/dist/tracing/TraceStore.js +32 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.js +3 -0
- package/dist/tracing/testing.d.ts +3 -0
- package/dist/tracing/testing.js +45 -0
- package/dist/types/agentConfig.d.ts +2 -1
- package/dist/types/channel.d.ts +14 -1
- package/dist/types/effectTool.d.ts +6 -0
- package/dist/types/flow.d.ts +1 -1
- package/dist/types/guardrails.d.ts +5 -0
- package/dist/types/hooks.d.ts +2 -2
- package/dist/types/index.d.ts +25 -15
- package/dist/types/index.js +8 -12
- package/dist/types/knowledge.d.ts +213 -0
- package/dist/types/processors.d.ts +0 -58
- package/dist/types/run-context.d.ts +16 -7
- package/dist/types/runtime.d.ts +3 -74
- package/dist/types/session.d.ts +11 -1
- package/dist/types/skills.d.ts +15 -1
- package/dist/types/stream.d.ts +138 -68
- package/dist/types/stream.js +34 -1
- package/dist/types/trace.d.ts +52 -0
- package/guides/AGENTS.md +3 -3
- package/guides/EXAMPLE_VERIFICATION.md +4 -4
- package/guides/FLOWS.md +1 -1
- package/guides/GETTING_STARTED.md +1 -1
- package/guides/RUNTIME.md +20 -15
- package/guides/TOOLS.md +1 -1
- package/package.json +13 -14
- package/dist/foundation/AgentDefinition.d.ts +0 -17
- package/dist/foundation/ConversationEventLog.d.ts +0 -71
- package/dist/foundation/ConversationState.d.ts +0 -33
- package/dist/foundation/ConversationState.js +0 -1
- package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
- package/dist/foundation/DefaultConversationEventLog.js +0 -205
- package/dist/foundation/DefaultConversationState.d.ts +0 -36
- package/dist/foundation/DefaultConversationState.js +0 -103
- package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
- package/dist/foundation/DefaultToolExecutor.js +0 -132
- package/dist/foundation/createFoundation.d.ts +0 -32
- package/dist/foundation/createFoundation.js +0 -33
- package/dist/hooks/HookRunner.d.ts +0 -37
- package/dist/hooks/HookRunner.js +0 -111
- package/dist/hooks/builtin/logging.d.ts +0 -5
- package/dist/hooks/builtin/logging.js +0 -98
- package/dist/hooks/builtin/metrics.d.ts +0 -7
- package/dist/hooks/builtin/metrics.js +0 -60
- package/dist/hooks/builtin/observability.d.ts +0 -20
- package/dist/hooks/builtin/observability.js +0 -572
- package/dist/hooks/helpers.d.ts +0 -22
- package/dist/hooks/helpers.js +0 -183
- package/dist/hooks/index.d.ts +0 -8
- package/dist/hooks/index.js +0 -6
- package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
- package/dist/realtime/RealtimeAudioClient.js +0 -14
- package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
- package/dist/realtime/RealtimeSessionHandle.js +0 -1
- package/dist/realtime/index.d.ts +0 -2
- package/dist/realtime/index.js +0 -1
- package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
- package/dist/runtime/channels/VoiceDriver.js +0 -321
- package/dist/runtime/channels/voiceTools.d.ts +0 -9
- package/dist/runtime/channels/voiceTools.js +0 -51
- package/dist/tools/errorHandling.d.ts +0 -35
- package/dist/tools/errorHandling.js +0 -145
- package/dist/types/voice.d.ts +0 -517
- package/dist/types/voice.js +0 -14
- package/dist/utils/isRecord.d.ts +0 -1
- package/dist/utils/isRecord.js +0 -3
- package/dist/utils/messageNormalization.d.ts +0 -2
- package/dist/utils/messageNormalization.js +0 -137
- /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
- /package/dist/{foundation/ConversationEventLog.js → types/trace.js} +0 -0
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
export type { ChannelDriver } from '../../types/channel.js';
|
|
2
2
|
export { TextDriver, buildNodePrompt } from './TextDriver.js';
|
|
3
3
|
export type { TextDriverConfig } from './TextDriver.js';
|
|
4
|
-
export { VoiceDriver } from './VoiceDriver.js';
|
|
5
|
-
export type { VoiceDriverConfig } from './VoiceDriver.js';
|
|
6
|
-
export { resolveVoiceGeminiTools, v2ToolsToGemini } from './voiceTools.js';
|
|
7
4
|
export { setPendingUserInput, consumePendingUserInput, consumeAllPendingUserInput, peekPendingUserInput, hasPendingUserInput, } from './inputBuffer.js';
|
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
export { TextDriver, buildNodePrompt } from './TextDriver.js';
|
|
2
|
-
// PAUSED: the realtime VoiceDriver is not on the primary (text) path and is not
|
|
3
|
-
// re-exported from the package's headline API. It stays here for the realtime
|
|
4
|
-
// stack (`@kuralle-agents/realtime-audio`) via the `/runtime` subpath. Text is
|
|
5
|
-
// the primary primitive; cascaded voice runs over text (see livekit-plugin).
|
|
6
|
-
export { VoiceDriver } from './VoiceDriver.js';
|
|
7
|
-
export { resolveVoiceGeminiTools, v2ToolsToGemini } from './voiceTools.js';
|
|
8
2
|
export { setPendingUserInput, consumePendingUserInput, consumeAllPendingUserInput, peekPendingUserInput, hasPendingUserInput, } from './inputBuffer.js';
|
|
@@ -7,3 +7,4 @@ export declare function consumePendingUserInput(session: Session): UserInputCont
|
|
|
7
7
|
export declare function consumeAllPendingUserInput(session: Session): UserInputContent | undefined;
|
|
8
8
|
export declare function peekPendingUserInput(session: Session): UserInputContent | undefined;
|
|
9
9
|
export declare function hasPendingUserInput(session: Session): boolean;
|
|
10
|
+
export declare function syncPendingUserInput(source: Session, target: Session): void;
|
|
@@ -33,3 +33,12 @@ export function peekPendingUserInput(session) {
|
|
|
33
33
|
export function hasPendingUserInput(session) {
|
|
34
34
|
return queue(session).length > 0;
|
|
35
35
|
}
|
|
36
|
+
export function syncPendingUserInput(source, target) {
|
|
37
|
+
const pending = queue(source);
|
|
38
|
+
if (pending.length > 0) {
|
|
39
|
+
target.workingMemory[PENDING_INPUT_KEY] = [...pending];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
delete target.workingMemory[PENDING_INPUT_KEY];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -72,7 +72,11 @@ export async function speakWithHostControl(args) {
|
|
|
72
72
|
toolControl = getToolControl();
|
|
73
73
|
if (toolControl) {
|
|
74
74
|
if (spoken.text) {
|
|
75
|
-
ctx.emit({
|
|
75
|
+
ctx.emit({
|
|
76
|
+
channel: 'client',
|
|
77
|
+
type: 'text-cancel',
|
|
78
|
+
payload: { id: turnId, reason: 'host-control' },
|
|
79
|
+
});
|
|
76
80
|
}
|
|
77
81
|
return { text: '', control: toolControl };
|
|
78
82
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { SentenceAggregator } from './SentenceAggregator.js';
|
|
3
3
|
function emitMessage(ctx, id, text) {
|
|
4
|
-
ctx.emit({ type: 'text-start', id });
|
|
5
|
-
ctx.emit({ type: 'text-delta', id, delta: text });
|
|
6
|
-
ctx.emit({ type: 'text-end', id });
|
|
4
|
+
ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
5
|
+
ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
|
|
6
|
+
ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
7
7
|
}
|
|
8
8
|
async function emitBlockedSafeMessage(ctx, turnId, started, outcome) {
|
|
9
9
|
if (started) {
|
|
10
10
|
ctx.emit({
|
|
11
|
+
channel: 'client',
|
|
11
12
|
type: 'text-cancel',
|
|
12
|
-
id: turnId,
|
|
13
|
-
reason: outcome.reason ?? 'blocked',
|
|
13
|
+
payload: { id: turnId, reason: outcome.reason ?? 'blocked' },
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
const safeId = randomUUID();
|
|
@@ -32,7 +32,7 @@ export async function speakGated(args) {
|
|
|
32
32
|
}
|
|
33
33
|
catch (err) {
|
|
34
34
|
const message = err instanceof Error ? err.message : String(err);
|
|
35
|
-
ctx.emit({ type: 'error', error: message });
|
|
35
|
+
ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
36
36
|
throw err instanceof Error ? err : new Error(message);
|
|
37
37
|
}
|
|
38
38
|
const decision = await runGate(full, true);
|
|
@@ -48,13 +48,17 @@ export async function speakGated(args) {
|
|
|
48
48
|
let emitted = '';
|
|
49
49
|
const openOnce = () => {
|
|
50
50
|
if (!started) {
|
|
51
|
-
ctx.emit({ type: 'text-start', id: turnId });
|
|
51
|
+
ctx.emit({ channel: 'client', type: 'text-start', payload: { id: turnId } });
|
|
52
52
|
started = true;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
const emitCleared = (chunk) => {
|
|
56
56
|
openOnce();
|
|
57
|
-
ctx.emit({
|
|
57
|
+
ctx.emit({
|
|
58
|
+
channel: 'client',
|
|
59
|
+
type: 'text-delta',
|
|
60
|
+
payload: { id: turnId, delta: chunk },
|
|
61
|
+
});
|
|
58
62
|
emitted += chunk;
|
|
59
63
|
};
|
|
60
64
|
const gateSentence = async (sentence, final) => {
|
|
@@ -69,7 +73,11 @@ export async function speakGated(args) {
|
|
|
69
73
|
for await (const { delta } of source) {
|
|
70
74
|
if (mode === 'token') {
|
|
71
75
|
openOnce();
|
|
72
|
-
ctx.emit({
|
|
76
|
+
ctx.emit({
|
|
77
|
+
channel: 'client',
|
|
78
|
+
type: 'text-delta',
|
|
79
|
+
payload: { id: turnId, delta },
|
|
80
|
+
});
|
|
73
81
|
emitted += delta;
|
|
74
82
|
continue;
|
|
75
83
|
}
|
|
@@ -88,15 +96,19 @@ export async function speakGated(args) {
|
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
if (started) {
|
|
91
|
-
ctx.emit({ type: 'text-end', id: turnId });
|
|
99
|
+
ctx.emit({ channel: 'client', type: 'text-end', payload: { id: turnId } });
|
|
92
100
|
}
|
|
93
101
|
return { text: emitted };
|
|
94
102
|
}
|
|
95
103
|
catch (err) {
|
|
96
104
|
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
-
ctx.emit({ type: 'error', error: message });
|
|
105
|
+
ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
98
106
|
if (started) {
|
|
99
|
-
ctx.emit({
|
|
107
|
+
ctx.emit({
|
|
108
|
+
channel: 'client',
|
|
109
|
+
type: 'text-cancel',
|
|
110
|
+
payload: { id: turnId, reason: message },
|
|
111
|
+
});
|
|
100
112
|
}
|
|
101
113
|
throw err instanceof Error ? err : new Error(message);
|
|
102
114
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { AgentKnowledgeOverrides,
|
|
1
|
+
import type { AgentKnowledgeOverrides, StreamPart, KnowledgeRetrievalResult, RetrievalCacheAdapter, SourceRef } from '../../types/index.js';
|
|
2
2
|
export interface CitationRetrievalProvider {
|
|
3
3
|
retrieve(query: string, cache: RetrievalCacheAdapter | undefined, agentOverrides?: AgentKnowledgeOverrides, isVoice?: boolean): Promise<{
|
|
4
4
|
results: KnowledgeRetrievalResult[];
|
|
5
5
|
citations?: SourceRef[];
|
|
6
|
-
events:
|
|
6
|
+
events: StreamPart[];
|
|
7
7
|
}>;
|
|
8
8
|
}
|
|
9
9
|
export interface CitationRetrievalResult {
|
|
10
10
|
results: KnowledgeRetrievalResult[];
|
|
11
11
|
citations: SourceRef[];
|
|
12
|
-
events:
|
|
12
|
+
events: StreamPart[];
|
|
13
13
|
}
|
|
14
14
|
export declare function retrieveWithCitations(provider: CitationRetrievalProvider, query: string, cache: RetrievalCacheAdapter | undefined, agentOverrides?: AgentKnowledgeOverrides, isVoice?: boolean): Promise<CitationRetrievalResult>;
|
|
15
15
|
export declare function normalizeCitations(results: KnowledgeRetrievalResult[], nativeCitations?: readonly SourceRef[]): SourceRef[];
|
package/dist/runtime/closeRun.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { isTerminalOutcome, markSessionOutcome } from './outcomeMarking.js';
|
|
2
|
+
import { mutateSessionWithRetry } from '../session/utils.js';
|
|
3
|
+
import { syncPendingUserInput } from './channels/inputBuffer.js';
|
|
4
|
+
import { runHookSafely } from './runHookSafely.js';
|
|
2
5
|
export async function closeRun(options) {
|
|
3
6
|
const { session, runState, runStore, sessionStore, hooks, ctx } = options;
|
|
4
7
|
runState.updatedAt = Date.now();
|
|
@@ -14,17 +17,18 @@ export async function closeRun(options) {
|
|
|
14
17
|
outcomeRecord = await markSessionOutcome(sessionStore, session, options.terminalOutcome, { reason: options.outcomeReason, markedBy: 'hook' }, ctx.emit);
|
|
15
18
|
}
|
|
16
19
|
if (outcomeRecord && isTerminalOutcome(outcomeRecord.outcome)) {
|
|
17
|
-
await hooks?.onConversationEnd?.({
|
|
20
|
+
await runHookSafely('onConversationEnd', () => hooks?.onConversationEnd?.({
|
|
18
21
|
session,
|
|
19
22
|
outcome: outcomeRecord,
|
|
20
|
-
});
|
|
23
|
+
}));
|
|
21
24
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
await mutateSessionWithRetry(sessionStore, session.id, (latest) => {
|
|
26
|
+
latest.currentAgent = runState.activeAgentId;
|
|
27
|
+
latest.activeAgentId = runState.activeAgentId;
|
|
28
|
+
// Sync the session-level message mirror to the canonical run record — it
|
|
29
|
+
// otherwise lacks assistant turns and keeps pre-guardrail (unredacted)
|
|
30
|
+
// user input written at openRun.
|
|
31
|
+
latest.messages = [...runState.messages];
|
|
32
|
+
syncPendingUserInput(ctx.session, latest);
|
|
33
|
+
});
|
|
30
34
|
}
|
|
@@ -42,6 +42,8 @@ export interface CompactMessagesOptions {
|
|
|
42
42
|
/** Skip the threshold check (overflow recovery). */
|
|
43
43
|
force?: boolean;
|
|
44
44
|
abortSignal?: AbortSignal;
|
|
45
|
+
/** Real last-turn prompt tokens when available (replaces chars/4 for threshold). */
|
|
46
|
+
lastPromptTokens?: number;
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* Pure compaction step: returns a new message list when compaction applied.
|
|
@@ -64,10 +64,11 @@ function truncate(text, max) {
|
|
|
64
64
|
* pairs are never split across the summary boundary.
|
|
65
65
|
*/
|
|
66
66
|
export async function compactMessages(options) {
|
|
67
|
-
const { messages, model, config, force, abortSignal } = options;
|
|
67
|
+
const { messages, model, config, force, abortSignal, lastPromptTokens } = options;
|
|
68
68
|
const triggerTokens = config.triggerTokens ?? DEFAULT_COMPACTION_TRIGGER_TOKENS;
|
|
69
69
|
const keepRecent = config.keepRecentMessages ?? DEFAULT_COMPACTION_KEEP_RECENT;
|
|
70
|
-
const
|
|
70
|
+
const estimatedTokens = estimateMessagesTokens(messages);
|
|
71
|
+
const beforeTokens = lastPromptTokens ?? estimatedTokens;
|
|
71
72
|
if (!force && beforeTokens < triggerTokens) {
|
|
72
73
|
return { compacted: false, reason: 'under-threshold', beforeTokens };
|
|
73
74
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The run is not finished: stop the turn, keep the journal, resume when a signal arrives.
|
|
4
|
+
* It unwinds the stack like an error but is not a failure, so it must never be reported to
|
|
5
|
+
* the model as a tool error, shown to the user as an error, or journaled as one.
|
|
6
|
+
*
|
|
7
|
+
* Only `SuspendError` qualifies. An approval *denial* is deliberately not a signal — see
|
|
8
|
+
* `isApprovalDenial`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isControlFlowSignal(error: unknown): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* A human answered "no". Neither a failure nor a suspend — the opposite of a suspend, in
|
|
13
|
+
* fact: a suspend defers the decision, a denial resolves it.
|
|
14
|
+
*
|
|
15
|
+
* Two rules follow, and they differ by who is driving the call:
|
|
16
|
+
*
|
|
17
|
+
* - It must never be degraded into "something went wrong on my side". Nothing went wrong;
|
|
18
|
+
* saying so would be a lie to the user.
|
|
19
|
+
* - On the **model** path it becomes a tool result, because there is no author code to
|
|
20
|
+
* catch it and the agent needs to be able to tell the user the request was declined.
|
|
21
|
+
* On the **flow** path it keeps propagating, because an `action` node's author chose to
|
|
22
|
+
* call the tool and can catch it or branch on `ctx.approve()` instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isApprovalDenial(error: unknown): error is ToolApprovalDeniedError;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SuspendError } from './durable/RunStore.js';
|
|
2
|
+
import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The run is not finished: stop the turn, keep the journal, resume when a signal arrives.
|
|
5
|
+
* It unwinds the stack like an error but is not a failure, so it must never be reported to
|
|
6
|
+
* the model as a tool error, shown to the user as an error, or journaled as one.
|
|
7
|
+
*
|
|
8
|
+
* Only `SuspendError` qualifies. An approval *denial* is deliberately not a signal — see
|
|
9
|
+
* `isApprovalDenial`.
|
|
10
|
+
*/
|
|
11
|
+
export function isControlFlowSignal(error) {
|
|
12
|
+
return error instanceof SuspendError;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A human answered "no". Neither a failure nor a suspend — the opposite of a suspend, in
|
|
16
|
+
* fact: a suspend defers the decision, a denial resolves it.
|
|
17
|
+
*
|
|
18
|
+
* Two rules follow, and they differ by who is driving the call:
|
|
19
|
+
*
|
|
20
|
+
* - It must never be degraded into "something went wrong on my side". Nothing went wrong;
|
|
21
|
+
* saying so would be a lie to the user.
|
|
22
|
+
* - On the **model** path it becomes a tool result, because there is no author code to
|
|
23
|
+
* catch it and the agent needs to be able to tell the user the request was declined.
|
|
24
|
+
* On the **flow** path it keeps propagating, because an `action` node's author chose to
|
|
25
|
+
* call the tool and can catch it or branch on `ctx.approve()` instead.
|
|
26
|
+
*/
|
|
27
|
+
export function isApprovalDenial(error) {
|
|
28
|
+
return error instanceof ToolApprovalDeniedError;
|
|
29
|
+
}
|
package/dist/runtime/ctx.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { LanguageModel } from 'ai';
|
|
2
2
|
import type { Session } from '../types/session.js';
|
|
3
|
-
import type { EffectToolExecutor,
|
|
4
|
-
import type {
|
|
3
|
+
import type { EffectToolExecutor, MemoryService, AutoRetrieveProvider, RunContext } from '../types/run-context.js';
|
|
4
|
+
import type { StreamPart } from '../types/stream.js';
|
|
5
5
|
import type { RefinementCapability } from '../capabilities/RefinementCapability.js';
|
|
6
6
|
import type { ValidationCapability } from '../capabilities/ValidationCapability.js';
|
|
7
7
|
import type { InputProcessor, OutputProcessor } from '../types/processors.js';
|
|
@@ -19,7 +19,6 @@ export interface CtxDeps {
|
|
|
19
19
|
runStore: RunStore;
|
|
20
20
|
steps: StepRecord[];
|
|
21
21
|
toolExecutor: EffectToolExecutor;
|
|
22
|
-
hookRunner?: HookRunner;
|
|
23
22
|
model: LanguageModel;
|
|
24
23
|
controlModel?: LanguageModel;
|
|
25
24
|
outOfBandControl?: boolean;
|
|
@@ -34,7 +33,7 @@ export interface CtxDeps {
|
|
|
34
33
|
bargeIn?: AbortSignal;
|
|
35
34
|
abortSignal?: AbortSignal;
|
|
36
35
|
clock?: EffectClock;
|
|
37
|
-
emit?: (part:
|
|
36
|
+
emit?: (part: StreamPart) => void;
|
|
38
37
|
}
|
|
39
38
|
export declare function createRunContext(deps: CtxDeps): Promise<RunContext>;
|
|
40
39
|
export {};
|
package/dist/runtime/ctx.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { SuspendError } from './durable/RunStore.js';
|
|
3
|
-
import { clockEffectKey,
|
|
3
|
+
import { clockEffectKey, idempotencyKey, logicalRunId, pauseEffectKey, } from './durable/idempotency.js';
|
|
4
4
|
import { findStepByKey } from './durable/replay.js';
|
|
5
5
|
import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
|
|
6
6
|
const APPROVAL_SIGNAL = '__approval';
|
|
@@ -12,58 +12,149 @@ function makeCtx(deps) {
|
|
|
12
12
|
uuid: () => randomUUID(),
|
|
13
13
|
};
|
|
14
14
|
const emit = deps.emit ?? (() => { });
|
|
15
|
+
// Mutable holder: handoff reassigns runCtx.toolExecutor; ctx.tool must see the swap.
|
|
16
|
+
const toolExecutorHolder = { executor: deps.toolExecutor };
|
|
17
|
+
let pendingAppendTail = Promise.resolve();
|
|
18
|
+
const serializePendingAppend = (operation) => {
|
|
19
|
+
const previous = pendingAppendTail;
|
|
20
|
+
let release;
|
|
21
|
+
pendingAppendTail = new Promise((resolve) => {
|
|
22
|
+
release = resolve;
|
|
23
|
+
});
|
|
24
|
+
return previous.then(async () => {
|
|
25
|
+
try {
|
|
26
|
+
return await operation();
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
release();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
15
33
|
const consumeCallsite = () => {
|
|
16
34
|
const site = String(effectOrdinal);
|
|
17
35
|
effectOrdinal += 1;
|
|
18
36
|
return site;
|
|
19
37
|
};
|
|
20
|
-
const
|
|
38
|
+
const isFinishedStep = (hit) => {
|
|
39
|
+
if (hit.status === 'finished') {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (hit.status === 'running') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (hit.status === 'error' || hit.error) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return hit.result !== undefined;
|
|
49
|
+
};
|
|
50
|
+
const updateLocalStep = (key, patch) => {
|
|
51
|
+
const idx = steps.findIndex((step) => step.key === key);
|
|
52
|
+
if (idx >= 0) {
|
|
53
|
+
steps[idx] = { ...steps[idx], ...patch };
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const appendPendingStep = async (key, kind, name, index, signalId) => {
|
|
21
57
|
const startedAt = Date.now();
|
|
22
58
|
const record = {
|
|
23
|
-
index
|
|
59
|
+
index,
|
|
24
60
|
key,
|
|
25
61
|
kind,
|
|
26
62
|
name,
|
|
27
63
|
signalId,
|
|
28
|
-
|
|
64
|
+
status: 'running',
|
|
29
65
|
startedAt,
|
|
30
|
-
|
|
66
|
+
epoch: deps.runState.runEpoch ?? 0,
|
|
31
67
|
};
|
|
32
68
|
await deps.runStore.appendStep(deps.runState.runId, record);
|
|
33
|
-
steps.
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
const localIdx = steps.findIndex((step) => step.index === index);
|
|
70
|
+
if (localIdx >= 0) {
|
|
71
|
+
steps[localIdx] = record;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
while (steps.length < index) {
|
|
75
|
+
steps.push({
|
|
76
|
+
index: steps.length,
|
|
77
|
+
key: `__reserve:${deps.runState.runId}:${steps.length}`,
|
|
78
|
+
kind: 'tool',
|
|
79
|
+
name: '__reserve',
|
|
80
|
+
status: 'running',
|
|
81
|
+
startedAt,
|
|
82
|
+
epoch: deps.runState.runEpoch ?? 0,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (steps.length === index) {
|
|
86
|
+
steps.push(record);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
steps[index] = record;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
36
92
|
};
|
|
37
|
-
const replayOrExecute = async (key, kind, name, execute) => {
|
|
93
|
+
const replayOrExecute = async (key, kind, name, execute, options) => {
|
|
38
94
|
const hit = findStepByKey(steps, key);
|
|
39
95
|
if (hit) {
|
|
40
|
-
if (hit.error) {
|
|
96
|
+
if (hit.status === 'error' || hit.error) {
|
|
41
97
|
throw Object.assign(new Error(hit.error.message), { name: hit.error.name });
|
|
42
98
|
}
|
|
43
|
-
|
|
99
|
+
if (isFinishedStep(hit)) {
|
|
100
|
+
return hit.result;
|
|
101
|
+
}
|
|
44
102
|
}
|
|
103
|
+
const isRetry = hit?.status === 'running';
|
|
104
|
+
const stepIndex = options?.index ?? hit?.index;
|
|
105
|
+
if (!isRetry) {
|
|
106
|
+
const append = async () => {
|
|
107
|
+
let index = stepIndex;
|
|
108
|
+
if (index === undefined) {
|
|
109
|
+
if (deps.runStore.reserveSteps) {
|
|
110
|
+
index = (await deps.runStore.reserveSteps(deps.runState.runId, 1))[0];
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// ctx.tool owns ordinal assignment: use the store's atomic reservation when available,
|
|
114
|
+
// and serialize legacy stores that do not expose reserveSteps within this context.
|
|
115
|
+
index = steps.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await appendPendingStep(key, kind, name, index);
|
|
119
|
+
};
|
|
120
|
+
if (stepIndex === undefined && !deps.runStore.reserveSteps) {
|
|
121
|
+
await serializePendingAppend(append);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await append();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
let result;
|
|
45
128
|
try {
|
|
46
|
-
|
|
47
|
-
await appendLiveStep(key, kind, name, result);
|
|
48
|
-
return result;
|
|
129
|
+
result = await execute();
|
|
49
130
|
}
|
|
50
131
|
catch (error) {
|
|
51
132
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
key,
|
|
56
|
-
kind,
|
|
57
|
-
name,
|
|
133
|
+
const finishedAt = Date.now();
|
|
134
|
+
await deps.runStore.finalizeStep(deps.runState.runId, key, {
|
|
135
|
+
status: 'error',
|
|
58
136
|
error: { name: err.name, message: err.message },
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
137
|
+
finishedAt,
|
|
138
|
+
});
|
|
139
|
+
updateLocalStep(key, {
|
|
140
|
+
status: 'error',
|
|
141
|
+
error: { name: err.name, message: err.message },
|
|
142
|
+
finishedAt,
|
|
143
|
+
});
|
|
64
144
|
throw err;
|
|
65
145
|
}
|
|
146
|
+
const finishedAt = Date.now();
|
|
147
|
+
await deps.runStore.finalizeStep(deps.runState.runId, key, {
|
|
148
|
+
status: 'finished',
|
|
149
|
+
result,
|
|
150
|
+
finishedAt,
|
|
151
|
+
});
|
|
152
|
+
updateLocalStep(key, { status: 'finished', result, finishedAt });
|
|
153
|
+
deps.runState.updatedAt = finishedAt;
|
|
154
|
+
await deps.runStore.putRunState(deps.runState);
|
|
155
|
+
return result;
|
|
66
156
|
};
|
|
157
|
+
const effectRunId = () => logicalRunId(deps.runState.runId, deps.runState.runEpoch);
|
|
67
158
|
const suspendForSignal = async (signalName, callsite, meta) => {
|
|
68
159
|
deps.runState.waitingFor = {
|
|
69
160
|
signalName,
|
|
@@ -75,18 +166,20 @@ function makeCtx(deps) {
|
|
|
75
166
|
deps.runState.status = 'paused';
|
|
76
167
|
deps.runState.updatedAt = Date.now();
|
|
77
168
|
await deps.runStore.putRunState(deps.runState);
|
|
78
|
-
emit({ type: 'paused', waitingFor: signalName });
|
|
169
|
+
emit({ channel: 'internal', type: 'paused', payload: { waitingFor: signalName } });
|
|
79
170
|
throw new SuspendError(signalName);
|
|
80
171
|
};
|
|
81
172
|
const pauseEffect = async (signalName, meta) => {
|
|
82
173
|
const callsite = consumeCallsite();
|
|
83
|
-
const key = pauseEffectKey(
|
|
174
|
+
const key = pauseEffectKey(effectRunId(), callsite, signalName);
|
|
84
175
|
const hit = findStepByKey(steps, key);
|
|
85
176
|
if (hit) {
|
|
86
|
-
if (hit.error) {
|
|
177
|
+
if (hit.status === 'error' || hit.error) {
|
|
87
178
|
throw Object.assign(new Error(hit.error.message), { name: hit.error.name });
|
|
88
179
|
}
|
|
89
|
-
|
|
180
|
+
if (hit.status === 'finished' || hit.result !== undefined) {
|
|
181
|
+
return hit.result;
|
|
182
|
+
}
|
|
90
183
|
}
|
|
91
184
|
await suspendForSignal(signalName, callsite, meta);
|
|
92
185
|
throw new Error('unreachable');
|
|
@@ -96,8 +189,12 @@ function makeCtx(deps) {
|
|
|
96
189
|
runState: deps.runState,
|
|
97
190
|
runStore: deps.runStore,
|
|
98
191
|
emit,
|
|
99
|
-
toolExecutor
|
|
100
|
-
|
|
192
|
+
get toolExecutor() {
|
|
193
|
+
return toolExecutorHolder.executor;
|
|
194
|
+
},
|
|
195
|
+
set toolExecutor(executor) {
|
|
196
|
+
toolExecutorHolder.executor = executor;
|
|
197
|
+
},
|
|
101
198
|
model: deps.model,
|
|
102
199
|
controlModel: deps.controlModel ?? deps.model,
|
|
103
200
|
outOfBandControl: deps.outOfBandControl ?? false,
|
|
@@ -121,6 +218,13 @@ function makeCtx(deps) {
|
|
|
121
218
|
resetCallsites: () => {
|
|
122
219
|
effectOrdinal = 0;
|
|
123
220
|
},
|
|
221
|
+
reserveCallsites: (count) => {
|
|
222
|
+
const sites = [];
|
|
223
|
+
for (let i = 0; i < count; i++) {
|
|
224
|
+
sites.push(consumeCallsite());
|
|
225
|
+
}
|
|
226
|
+
return sites;
|
|
227
|
+
},
|
|
124
228
|
tool: async (name, args, options) => {
|
|
125
229
|
// needsApproval gate: a tool flagged `needsApproval` must be approved by a human
|
|
126
230
|
// before it runs. Approval is a durable pause (the `__approval` signal); on resume
|
|
@@ -129,7 +233,7 @@ function makeCtx(deps) {
|
|
|
129
233
|
// ordering is deterministic across replays. NOTE: the surrounding agent turn is not
|
|
130
234
|
// itself a replayable effect — this is fully deterministic for flow `action` tools;
|
|
131
235
|
// for model-issued tool calls, resume re-enters the agent turn.
|
|
132
|
-
const def = options?.def ??
|
|
236
|
+
const def = options?.def ?? toolExecutorHolder.executor.getTool?.(name);
|
|
133
237
|
if (def?.needsApproval) {
|
|
134
238
|
const decision = (await pauseEffect(APPROVAL_SIGNAL, {
|
|
135
239
|
approval: { title: `Approve tool: ${name}` },
|
|
@@ -138,9 +242,12 @@ function makeCtx(deps) {
|
|
|
138
242
|
throw new ToolApprovalDeniedError(name, decision.by);
|
|
139
243
|
}
|
|
140
244
|
}
|
|
141
|
-
const callsite = consumeCallsite();
|
|
142
|
-
const
|
|
143
|
-
const
|
|
245
|
+
const callsite = options?.callsite ?? consumeCallsite();
|
|
246
|
+
const logicalId = effectRunId();
|
|
247
|
+
const key = def?.idempotencyKey != null
|
|
248
|
+
? def.idempotencyKey(args)
|
|
249
|
+
: idempotencyKey(logicalId, callsite, { name, args });
|
|
250
|
+
const executeTool = () => toolExecutorHolder.executor.execute({
|
|
144
251
|
name,
|
|
145
252
|
args,
|
|
146
253
|
session: deps.session,
|
|
@@ -150,30 +257,10 @@ function makeCtx(deps) {
|
|
|
150
257
|
toolCtx: options?.toolCtx,
|
|
151
258
|
});
|
|
152
259
|
if (def?.replay === false) {
|
|
153
|
-
const auditKey = `${key}:${steps.length}`;
|
|
154
|
-
|
|
155
|
-
const result = await executeTool();
|
|
156
|
-
await appendLiveStep(auditKey, 'tool', name, result);
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
161
|
-
const startedAt = Date.now();
|
|
162
|
-
const record = {
|
|
163
|
-
index: steps.length,
|
|
164
|
-
key: auditKey,
|
|
165
|
-
kind: 'tool',
|
|
166
|
-
name,
|
|
167
|
-
error: { name: err.name, message: err.message },
|
|
168
|
-
startedAt,
|
|
169
|
-
finishedAt: startedAt,
|
|
170
|
-
};
|
|
171
|
-
await deps.runStore.appendStep(deps.runState.runId, record);
|
|
172
|
-
steps.push(record);
|
|
173
|
-
throw err;
|
|
174
|
-
}
|
|
260
|
+
const auditKey = `${key}:${steps.length}:${options?.index ?? callsite}`;
|
|
261
|
+
return replayOrExecute(auditKey, 'tool', name, executeTool, { index: options?.index });
|
|
175
262
|
}
|
|
176
|
-
return replayOrExecute(key, 'tool', name, executeTool);
|
|
263
|
+
return replayOrExecute(key, 'tool', name, executeTool, { index: options?.index });
|
|
177
264
|
},
|
|
178
265
|
approve: async (req) => {
|
|
179
266
|
return pauseEffect(APPROVAL_SIGNAL, { approval: req });
|
|
@@ -186,12 +273,12 @@ function makeCtx(deps) {
|
|
|
186
273
|
},
|
|
187
274
|
now: async () => {
|
|
188
275
|
const callsite = consumeCallsite();
|
|
189
|
-
const key = clockEffectKey(
|
|
276
|
+
const key = clockEffectKey(effectRunId(), callsite, 'now');
|
|
190
277
|
return replayOrExecute(key, 'now', 'now', async () => clock.now());
|
|
191
278
|
},
|
|
192
279
|
uuid: async () => {
|
|
193
280
|
const callsite = consumeCallsite();
|
|
194
|
-
const key = clockEffectKey(
|
|
281
|
+
const key = clockEffectKey(effectRunId(), callsite, 'uuid');
|
|
195
282
|
return replayOrExecute(key, 'uuid', 'uuid', async () => clock.uuid());
|
|
196
283
|
},
|
|
197
284
|
};
|
|
@@ -12,9 +12,25 @@ export declare class SuspendError extends Error {
|
|
|
12
12
|
export declare class RunNotFoundError extends Error {
|
|
13
13
|
constructor(runId: string);
|
|
14
14
|
}
|
|
15
|
+
export declare class StepNotFoundError extends Error {
|
|
16
|
+
constructor(runId: string, key: string);
|
|
17
|
+
}
|
|
18
|
+
export interface StepFinalizePatch {
|
|
19
|
+
status: 'finished' | 'error';
|
|
20
|
+
result?: unknown;
|
|
21
|
+
error?: {
|
|
22
|
+
name: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
finishedAt?: number;
|
|
26
|
+
}
|
|
15
27
|
export interface RunStore {
|
|
16
28
|
appendStep(runId: string, record: StepRecord): Promise<void>;
|
|
29
|
+
finalizeStep(runId: string, key: string, patch: StepFinalizePatch): Promise<void>;
|
|
17
30
|
getSteps(runId: string): Promise<StepRecord[]>;
|
|
18
31
|
getRunState(runId: string): Promise<RunState | null>;
|
|
19
32
|
putRunState(state: RunState): Promise<void>;
|
|
33
|
+
initRun?(state: RunState): Promise<void>;
|
|
34
|
+
pruneStepsBeforeEpoch?(runId: string, keepEpoch: number): Promise<void>;
|
|
35
|
+
reserveSteps?(runId: string, count: number): Promise<number[]>;
|
|
20
36
|
}
|
|
@@ -3,7 +3,7 @@ export class LogConflictError extends Error {
|
|
|
3
3
|
expectedIndex;
|
|
4
4
|
actualIndex;
|
|
5
5
|
constructor(runId, expectedIndex, actualIndex) {
|
|
6
|
-
super(`Log conflict for run ${runId}: expected append at index ${expectedIndex}, current length is ${actualIndex}
|
|
6
|
+
super(`Log conflict for run ${runId}: expected append at index ${expectedIndex}, current length is ${actualIndex}. For parallel durable effects, use ctx.tool directly or reserve callsites with ctx.reserveCallsites(count) before supplying explicit indices.`);
|
|
7
7
|
this.name = 'LogConflictError';
|
|
8
8
|
this.runId = runId;
|
|
9
9
|
this.expectedIndex = expectedIndex;
|
|
@@ -24,3 +24,9 @@ export class RunNotFoundError extends Error {
|
|
|
24
24
|
this.name = 'RunNotFoundError';
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
export class StepNotFoundError extends Error {
|
|
28
|
+
constructor(runId, key) {
|
|
29
|
+
super(`Step not found for run ${runId}: ${key}`);
|
|
30
|
+
this.name = 'StepNotFoundError';
|
|
31
|
+
}
|
|
32
|
+
}
|