@kuralle-agents/core 0.13.0 → 0.15.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 +4 -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 +1 -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/collectUntilComplete.js +30 -7
- 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 +21 -2
- package/dist/flow/extraction.js +62 -7
- package/dist/flow/reduceTransition.d.ts +2 -2
- package/dist/flow/reduceTransition.js +7 -3
- package/dist/flow/runFlow.d.ts +11 -0
- package/dist/flow/runFlow.js +68 -12
- 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 +9 -14
- package/dist/index.js +5 -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 +6 -6
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/types.d.ts +7 -2
- package/dist/runtime/InMemoryRetrievalCache.d.ts +1 -1
- package/dist/runtime/KnowledgeProvider.d.ts +3 -2
- package/dist/runtime/KnowledgeProvider.js +54 -30
- package/dist/runtime/Runtime.d.ts +9 -1
- package/dist/runtime/Runtime.js +123 -30
- package/dist/runtime/TraceRecorder.d.ts +22 -4
- package/dist/runtime/TraceRecorder.js +65 -29
- package/dist/runtime/buildAgentToolSurface.js +1 -1
- package/dist/runtime/channels/TextDriver.js +12 -9
- package/dist/runtime/channels/executeModelTool.d.ts +6 -0
- package/dist/runtime/channels/executeModelTool.js +70 -10
- 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/streaming/hostControlSpeak.js +5 -1
- package/dist/runtime/channels/streaming/speakGated.js +33 -12
- package/dist/runtime/citations/index.d.ts +3 -3
- package/dist/runtime/closeRun.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 +39 -5
- package/dist/runtime/durable/RunStore.js +1 -1
- 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/hostLoop.js +39 -10
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -1
- 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/runHookSafely.d.ts +3 -0
- package/dist/runtime/runHookSafely.js +19 -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/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 +71 -35
- package/dist/tools/effect/defineTool.d.ts +9 -0
- package/dist/tools/effect/defineTool.js +1 -0
- package/dist/tools/effect/errors.d.ts +2 -5
- package/dist/tools/effect/errors.js +2 -5
- package/dist/tools/fs/createFsTool.js +2 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/types/effectTool.d.ts +2 -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 -16
- package/dist/types/index.js +8 -13
- package/dist/types/knowledge.d.ts +213 -0
- package/dist/types/processors.d.ts +0 -58
- package/dist/types/run-context.d.ts +3 -7
- package/dist/types/runtime.d.ts +3 -74
- package/dist/types/session.d.ts +1 -1
- package/dist/types/skills.d.ts +15 -1
- package/dist/types/stream.d.ts +133 -71
- package/dist/types/stream.js +34 -1
- package/dist/types/trace.d.ts +2 -0
- package/guides/AGENTS.md +3 -3
- 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 +3 -13
- package/dist/foundation/AgentDefinition.d.ts +0 -17
- package/dist/foundation/ConversationEventLog.d.ts +0 -71
- package/dist/foundation/ConversationEventLog.js +0 -1
- 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/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @kuralle-agents/core
|
|
2
2
|
|
|
3
|
-
The runtime and primitives for building conversational AI agents
|
|
3
|
+
The runtime and primitives for building conversational AI agents with structured flows, routing, and durable tool execution.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -54,8 +54,8 @@ const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });
|
|
|
54
54
|
|
|
55
55
|
const handle = runtime.run({ input: 'Hello', sessionId: 'demo' });
|
|
56
56
|
for await (const part of handle.events) { // events is a property, not a method
|
|
57
|
-
if (part.type === 'text-delta') process.stdout.write(part.delta);
|
|
58
|
-
if (part.type === 'done') console.log('\nSession:', part.sessionId);
|
|
57
|
+
if (part.type === 'text-delta') process.stdout.write(part.payload.delta);
|
|
58
|
+
if (part.type === 'done') console.log('\nSession:', part.payload.sessionId);
|
|
59
59
|
}
|
|
60
60
|
await handle; // resolves to TurnResult once the stream is consumed
|
|
61
61
|
```
|
|
@@ -230,7 +230,7 @@ return handle.toUIMessageStreamResponse({ sessionId: 'demo' });
|
|
|
230
230
|
|
|
231
231
|
Kuralle orchestration events (flow telemetry, safety blocks, interactive choices) arrive as typed `data-kuralle-*` parts. Import `KuralleUIMessage` and `KuralleDataParts` for compile-time-safe `message.parts` and `useChat({ onData })` handlers.
|
|
232
232
|
|
|
233
|
-
For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `
|
|
233
|
+
For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `StreamPart` JSON-SSE. Or use `@kuralle-agents/hono-server` — `POST /api/chat/sse` defaults to native `UIMessageStream`; append `?format=raw` for the legacy wire.
|
|
234
234
|
|
|
235
235
|
## Related
|
|
236
236
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type UIMessage } from 'ai';
|
|
2
2
|
import type { ConversationOutcome } from '../outcomes/types.js';
|
|
3
3
|
import type { ChoiceOption } from '../types/selection.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { StreamPart } from '../types/stream.js';
|
|
5
5
|
export type KuralleMetadata = {
|
|
6
6
|
sessionId?: string;
|
|
7
7
|
};
|
|
@@ -46,6 +46,6 @@ export type KuralleDataParts = {
|
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
export type KuralleUIMessage = UIMessage<KuralleMetadata, KuralleDataParts>;
|
|
49
|
-
export declare function harnessToUIMessageStream(source: AsyncIterable<
|
|
49
|
+
export declare function harnessToUIMessageStream(source: AsyncIterable<StreamPart>, opts?: {
|
|
50
50
|
sessionId?: string;
|
|
51
51
|
}): ReadableStream;
|
|
@@ -2,64 +2,64 @@ import { createUIMessageStream, generateId, } from 'ai';
|
|
|
2
2
|
function writeHarnessPart(part, writer) {
|
|
3
3
|
switch (part.type) {
|
|
4
4
|
case 'text-start':
|
|
5
|
-
writer.write({ type: 'text-start', id: part.id });
|
|
5
|
+
writer.write({ type: 'text-start', id: part.payload.id });
|
|
6
6
|
break;
|
|
7
7
|
case 'text-delta':
|
|
8
|
-
writer.write({ type: 'text-delta', id: part.id, delta: part.delta });
|
|
8
|
+
writer.write({ type: 'text-delta', id: part.payload.id, delta: part.payload.delta });
|
|
9
9
|
break;
|
|
10
10
|
case 'text-end':
|
|
11
|
-
writer.write({ type: 'text-end', id: part.id });
|
|
11
|
+
writer.write({ type: 'text-end', id: part.payload.id });
|
|
12
12
|
break;
|
|
13
13
|
case 'text-cancel':
|
|
14
|
-
writer.write({ type: 'text-end', id: part.id });
|
|
14
|
+
writer.write({ type: 'text-end', id: part.payload.id });
|
|
15
15
|
break;
|
|
16
16
|
case 'tool-call':
|
|
17
17
|
writer.write({
|
|
18
18
|
type: 'tool-input-available',
|
|
19
|
-
toolCallId: part.toolCallId ?? generateId(),
|
|
20
|
-
toolName: part.toolName,
|
|
21
|
-
input: part.args,
|
|
19
|
+
toolCallId: part.payload.toolCallId ?? generateId(),
|
|
20
|
+
toolName: part.payload.toolName,
|
|
21
|
+
input: part.payload.args,
|
|
22
22
|
});
|
|
23
23
|
break;
|
|
24
24
|
case 'tool-result':
|
|
25
25
|
writer.write({
|
|
26
26
|
type: 'tool-output-available',
|
|
27
|
-
toolCallId: part.toolCallId ?? 'unknown',
|
|
28
|
-
output: part.result,
|
|
27
|
+
toolCallId: part.payload.toolCallId ?? 'unknown',
|
|
28
|
+
output: part.payload.result,
|
|
29
29
|
});
|
|
30
30
|
break;
|
|
31
31
|
case 'node-enter':
|
|
32
32
|
writer.write({
|
|
33
33
|
type: 'data-kuralle-node',
|
|
34
|
-
data: { event: 'enter', node: part.nodeName },
|
|
34
|
+
data: { event: 'enter', node: part.payload.nodeName },
|
|
35
35
|
transient: true,
|
|
36
36
|
});
|
|
37
37
|
break;
|
|
38
38
|
case 'node-exit':
|
|
39
39
|
writer.write({
|
|
40
40
|
type: 'data-kuralle-node',
|
|
41
|
-
data: { event: 'exit', node: part.nodeName },
|
|
41
|
+
data: { event: 'exit', node: part.payload.nodeName },
|
|
42
42
|
transient: true,
|
|
43
43
|
});
|
|
44
44
|
break;
|
|
45
45
|
case 'flow-enter':
|
|
46
46
|
writer.write({
|
|
47
47
|
type: 'data-kuralle-flow',
|
|
48
|
-
data: { event: 'enter', flow: part.flow },
|
|
48
|
+
data: { event: 'enter', flow: part.payload.flow },
|
|
49
49
|
transient: true,
|
|
50
50
|
});
|
|
51
51
|
break;
|
|
52
52
|
case 'flow-end':
|
|
53
53
|
writer.write({
|
|
54
54
|
type: 'data-kuralle-flow',
|
|
55
|
-
data: { event: 'end', flow: part.flow, reason: part.reason },
|
|
55
|
+
data: { event: 'end', flow: part.payload.flow, reason: part.payload.reason },
|
|
56
56
|
transient: true,
|
|
57
57
|
});
|
|
58
58
|
break;
|
|
59
59
|
case 'flow-transition':
|
|
60
60
|
writer.write({
|
|
61
61
|
type: 'data-kuralle-flow',
|
|
62
|
-
data: { event: 'transition', from: part.from, to: part.to },
|
|
62
|
+
data: { event: 'transition', from: part.payload.from, to: part.payload.to },
|
|
63
63
|
transient: true,
|
|
64
64
|
});
|
|
65
65
|
break;
|
|
@@ -67,14 +67,18 @@ function writeHarnessPart(part, writer) {
|
|
|
67
67
|
writer.write({
|
|
68
68
|
type: 'data-kuralle-handoff',
|
|
69
69
|
id: generateId(),
|
|
70
|
-
data: { targetAgent: part.targetAgent, reason: part.reason },
|
|
70
|
+
data: { targetAgent: part.payload.targetAgent, reason: part.payload.reason },
|
|
71
71
|
});
|
|
72
72
|
break;
|
|
73
73
|
case 'interactive':
|
|
74
74
|
writer.write({
|
|
75
75
|
type: 'data-kuralle-interactive',
|
|
76
|
-
id: part.nodeId,
|
|
77
|
-
data: {
|
|
76
|
+
id: part.payload.nodeId,
|
|
77
|
+
data: {
|
|
78
|
+
nodeId: part.payload.nodeId,
|
|
79
|
+
prompt: part.payload.prompt,
|
|
80
|
+
options: part.payload.options,
|
|
81
|
+
},
|
|
78
82
|
});
|
|
79
83
|
break;
|
|
80
84
|
case 'safety-blocked':
|
|
@@ -83,9 +87,9 @@ function writeHarnessPart(part, writer) {
|
|
|
83
87
|
id: generateId(),
|
|
84
88
|
data: {
|
|
85
89
|
kind: 'safety-blocked',
|
|
86
|
-
moderator: part.moderator,
|
|
87
|
-
rationale: part.rationale,
|
|
88
|
-
userFacingMessage: part.userFacingMessage,
|
|
90
|
+
moderator: part.payload.moderator,
|
|
91
|
+
rationale: part.payload.rationale,
|
|
92
|
+
userFacingMessage: part.payload.userFacingMessage,
|
|
89
93
|
},
|
|
90
94
|
});
|
|
91
95
|
break;
|
|
@@ -95,8 +99,8 @@ function writeHarnessPart(part, writer) {
|
|
|
95
99
|
id: generateId(),
|
|
96
100
|
data: {
|
|
97
101
|
kind: 'pipeline-validation-block',
|
|
98
|
-
rationale: part.rationale,
|
|
99
|
-
userFacingMessage: part.userFacingMessage,
|
|
102
|
+
rationale: part.payload.rationale,
|
|
103
|
+
userFacingMessage: part.payload.userFacingMessage,
|
|
100
104
|
},
|
|
101
105
|
});
|
|
102
106
|
break;
|
|
@@ -104,35 +108,44 @@ function writeHarnessPart(part, writer) {
|
|
|
104
108
|
writer.write({
|
|
105
109
|
type: 'data-kuralle-outcome',
|
|
106
110
|
id: generateId(),
|
|
107
|
-
data: { outcome: part.outcome },
|
|
111
|
+
data: { outcome: part.payload.outcome },
|
|
108
112
|
});
|
|
109
113
|
break;
|
|
110
114
|
case 'interrupted':
|
|
111
115
|
writer.write({
|
|
112
116
|
type: 'data-kuralle-control',
|
|
113
|
-
data: { event: 'interrupted', reason: part.reason },
|
|
117
|
+
data: { event: 'interrupted', reason: part.payload.reason },
|
|
114
118
|
transient: true,
|
|
115
119
|
});
|
|
116
120
|
break;
|
|
117
121
|
case 'paused':
|
|
118
122
|
writer.write({
|
|
119
123
|
type: 'data-kuralle-control',
|
|
120
|
-
data: { event: 'paused', waitingFor: part.waitingFor },
|
|
124
|
+
data: { event: 'paused', waitingFor: part.payload.waitingFor },
|
|
121
125
|
transient: true,
|
|
122
126
|
});
|
|
123
127
|
break;
|
|
124
128
|
case 'custom':
|
|
125
129
|
writer.write({
|
|
126
130
|
type: 'data-kuralle-custom',
|
|
127
|
-
data: { name: part.name, data: part.data },
|
|
131
|
+
data: { name: part.payload.name, data: part.payload.data },
|
|
128
132
|
transient: true,
|
|
129
133
|
});
|
|
130
134
|
break;
|
|
131
135
|
case 'error':
|
|
132
|
-
throw new Error(part.error);
|
|
136
|
+
throw new Error(part.payload.error);
|
|
133
137
|
case 'done':
|
|
134
138
|
case 'turn-end':
|
|
135
139
|
break;
|
|
140
|
+
default: {
|
|
141
|
+
// Client-channel exhaustiveness guard. Internal-channel parts are
|
|
142
|
+
// deliberately unmapped here — they never reach a UI client. But a new
|
|
143
|
+
// `client` variant added without a case above would be silently dropped
|
|
144
|
+
// on the ADR-0005 default output path, so pin the residual to internal:
|
|
145
|
+
// adding a client variant without a case fails this assignment.
|
|
146
|
+
const _internalOnly = part;
|
|
147
|
+
void _internalOnly;
|
|
148
|
+
}
|
|
136
149
|
}
|
|
137
150
|
}
|
|
138
151
|
export function harnessToUIMessageStream(source, opts) {
|
|
@@ -146,8 +159,8 @@ export function harnessToUIMessageStream(source, opts) {
|
|
|
146
159
|
});
|
|
147
160
|
}
|
|
148
161
|
for await (const part of source) {
|
|
149
|
-
if (part.type === 'done' && part.sessionId) {
|
|
150
|
-
doneSessionId = doneSessionId ?? part.sessionId;
|
|
162
|
+
if (part.type === 'done' && part.payload.sessionId) {
|
|
163
|
+
doneSessionId = doneSessionId ?? part.payload.sessionId;
|
|
151
164
|
}
|
|
152
165
|
else {
|
|
153
166
|
writeHarnessPart(part, writer);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* CapabilityHost with Runtime context (working memory, long-term memory,
|
|
8
8
|
* policy injections) and structures the output per audio-model best practices.
|
|
9
9
|
*
|
|
10
|
-
* Adapters
|
|
10
|
+
* Adapters can extend or replace
|
|
11
11
|
* the default with backend-specific voice rules and guardrails.
|
|
12
12
|
*
|
|
13
13
|
* Prompt structure follows Google's Live API best practices and
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* CapabilityHost with Runtime context (working memory, long-term memory,
|
|
8
8
|
* policy injections) and structures the output per audio-model best practices.
|
|
9
9
|
*
|
|
10
|
-
* Adapters
|
|
10
|
+
* Adapters can extend or replace
|
|
11
11
|
* the default with backend-specific voice rules and guardrails.
|
|
12
12
|
*
|
|
13
13
|
* Prompt structure follows Google's Live API best practices and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ZodTypeAny } from 'zod';
|
|
2
2
|
/**
|
|
3
3
|
* Backend-agnostic tool declaration. Produced by capabilities,
|
|
4
|
-
* consumed by adapters (
|
|
4
|
+
* consumed by adapters (AI SDK and others).
|
|
5
5
|
*/
|
|
6
6
|
export interface ToolDeclaration<TInput = unknown, TOutput = unknown> {
|
|
7
7
|
name: string;
|
package/dist/eval/EvalRunner.js
CHANGED
|
@@ -25,16 +25,16 @@ export class EvalRunner {
|
|
|
25
25
|
const handle = runtime.run({ input: turn.input, sessionId });
|
|
26
26
|
for await (const part of handle.events) {
|
|
27
27
|
if (part.type === 'text-delta') {
|
|
28
|
-
response += part.delta;
|
|
28
|
+
response += part.payload.delta;
|
|
29
29
|
}
|
|
30
30
|
if (part.type === 'tool-call') {
|
|
31
|
-
toolsCalled.push(part.toolName);
|
|
31
|
+
toolsCalled.push(part.payload.toolName);
|
|
32
32
|
}
|
|
33
33
|
if (part.type === 'flow-transition') {
|
|
34
|
-
transitions.push({ from: part.from, to: part.to });
|
|
34
|
+
transitions.push({ from: part.payload.from, to: part.payload.to });
|
|
35
35
|
}
|
|
36
36
|
if (part.type === 'done') {
|
|
37
|
-
sessionId = part.sessionId;
|
|
37
|
+
sessionId = part.payload.sessionId;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
await handle;
|
package/dist/eval/simulation.js
CHANGED
|
@@ -66,10 +66,11 @@ export async function simulateConversation(options) {
|
|
|
66
66
|
let reply = '';
|
|
67
67
|
for await (const part of handle.events) {
|
|
68
68
|
if (part.type === 'text-delta')
|
|
69
|
-
reply += part.delta;
|
|
69
|
+
reply += part.payload.delta;
|
|
70
70
|
if (part.type === 'tool-call')
|
|
71
|
-
toolsCalled.push(part.toolName);
|
|
72
|
-
if (part.type === 'escalation' ||
|
|
71
|
+
toolsCalled.push(part.payload.toolName);
|
|
72
|
+
if (part.type === 'escalation' ||
|
|
73
|
+
(part.type === 'handoff' && part.payload.targetAgent === 'human')) {
|
|
73
74
|
escalated = true;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StreamPart, TurnHandle } from '../types/stream.js';
|
|
2
2
|
export interface EventBus {
|
|
3
|
-
emit(part:
|
|
4
|
-
events(): AsyncIterable<
|
|
3
|
+
emit(part: StreamPart): void;
|
|
4
|
+
events(): AsyncIterable<StreamPart>;
|
|
5
5
|
close(): void;
|
|
6
6
|
}
|
|
7
7
|
export declare function createEventBus(): EventBus;
|
|
@@ -3,7 +3,7 @@ import { persistTurnUsageFromTurn } from '../runtime/turnTokenUsage.js';
|
|
|
3
3
|
import { hasPendingUserInput } from '../runtime/channels/inputBuffer.js';
|
|
4
4
|
import { userInputToText } from '../runtime/userInput.js';
|
|
5
5
|
import { resolveCollectExtractionNode } from './nodeBuilders.js';
|
|
6
|
-
import { computeMissingFields, createExtractionSubmitTool, getCollectData, incrementCollectTurns, emitExtractionTelemetry, mergeTurnExtraction, projectCollectData, schemaSatisfied, } from './extraction.js';
|
|
6
|
+
import { computeMissingFields, createExtractionSubmitTool, getCollectData, incrementCollectTurns, emitExtractionTelemetry, mergeTurnExtraction, projectCollectData, schemaSatisfied, invalidCollectFields, setCollectData, } from './extraction.js';
|
|
7
7
|
import { normalizeTransition } from './normalizeTransition.js';
|
|
8
8
|
function appendAssistantMessage(run, text) {
|
|
9
9
|
if (!text.trim()) {
|
|
@@ -50,8 +50,31 @@ export async function collectUntilComplete(node, run, driver, ctx, options) {
|
|
|
50
50
|
const turns = incrementCollectTurns(run.state, node.id);
|
|
51
51
|
const maxTurns = node.maxTurns ?? 10;
|
|
52
52
|
if (turns > maxTurns) {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// Running out of turns is not the same as finishing. Calling onComplete here with
|
|
54
|
+
// required fields still missing hands the rest of the flow a half-filled record and
|
|
55
|
+
// lets an action node act on it — for an intake SOP that means creating a record
|
|
56
|
+
// against whatever happened to be in state. Complete only if the node is genuinely
|
|
57
|
+
// satisfied; otherwise this is a failure to collect, and a human should see it.
|
|
58
|
+
if (schemaSatisfied(node, run.state)) {
|
|
59
|
+
const data = projectCollectData(node, run.state);
|
|
60
|
+
return normalizeTransition(await node.onComplete(data, run.state));
|
|
61
|
+
}
|
|
62
|
+
const missing = computeMissingFields(node, getCollectData(run.state, node.id));
|
|
63
|
+
return normalizeTransition({
|
|
64
|
+
escalate: `Could not collect ${missing.join(', ')} for "${node.id}" after ${maxTurns} turns.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Drop any collected value the schema rejects before working out what to ask for.
|
|
68
|
+
// Without this a value that is present-but-invalid is neither missing (so it is never
|
|
69
|
+
// re-asked) nor satisfying (so the node never completes) — the loop would spin to
|
|
70
|
+
// maxTurns. Purging turns an invalid value back into a missing one, which the existing
|
|
71
|
+
// ask/extract machinery already knows how to chase.
|
|
72
|
+
const collected = getCollectData(run.state, node.id);
|
|
73
|
+
const invalid = invalidCollectFields(node, collected);
|
|
74
|
+
if (invalid.length > 0) {
|
|
75
|
+
for (const field of invalid)
|
|
76
|
+
delete collected[field];
|
|
77
|
+
setCollectData(run.state, node.id, collected);
|
|
55
78
|
}
|
|
56
79
|
const missingBefore = computeMissingFields(node, getCollectData(run.state, node.id));
|
|
57
80
|
const submitTool = createExtractionSubmitTool(node, missingBefore, {
|
|
@@ -121,10 +144,10 @@ export function emitCollectAsk(node, run, ctx) {
|
|
|
121
144
|
return;
|
|
122
145
|
}
|
|
123
146
|
const id = crypto.randomUUID();
|
|
124
|
-
ctx.emit({ type: 'text-start', id });
|
|
125
|
-
ctx.emit({ type: 'text-delta', id, delta: text });
|
|
126
|
-
ctx.emit({ type: 'text-end', id });
|
|
127
|
-
ctx.emit({ type: 'turn-end' });
|
|
147
|
+
ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
148
|
+
ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
|
|
149
|
+
ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
150
|
+
ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
|
|
128
151
|
appendAssistantMessage(run, text);
|
|
129
152
|
}
|
|
130
153
|
function mergeExtractionFromTurn(node, run, turn, ctx) {
|
package/dist/flow/degrade.js
CHANGED
|
@@ -7,9 +7,9 @@ export function appendSafeAssistantMessage(run, ctx, text = SAFE_DEGRADED_MESSAG
|
|
|
7
7
|
const message = { role: 'assistant', content: text };
|
|
8
8
|
run.messages = [...run.messages, message];
|
|
9
9
|
const id = crypto.randomUUID();
|
|
10
|
-
ctx.emit({ type: 'text-start', id });
|
|
11
|
-
ctx.emit({ type: 'text-delta', id, delta: text });
|
|
12
|
-
ctx.emit({ type: 'text-end', id });
|
|
10
|
+
ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
11
|
+
ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
|
|
12
|
+
ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
13
13
|
}
|
|
14
14
|
export async function degradeFlowError(flow, registry, run, driver, ctx, dispatchNode) {
|
|
15
15
|
appendSafeAssistantMessage(run, ctx);
|
|
@@ -29,6 +29,10 @@ export async function degradeFlowError(flow, registry, run, driver, ctx, dispatc
|
|
|
29
29
|
// Fall through to graceful end if the escalate node also fails.
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
ctx.emit({
|
|
32
|
+
ctx.emit({
|
|
33
|
+
channel: 'internal',
|
|
34
|
+
type: 'flow-end',
|
|
35
|
+
payload: { flow: flow.name, reason: 'error_degraded' },
|
|
36
|
+
});
|
|
33
37
|
return { kind: 'ended', reason: 'error_degraded' };
|
|
34
38
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { FlowNode, FlowState } from '../types/flow.js';
|
|
2
|
-
import type {
|
|
3
|
-
export declare function emitInteractiveOnNodeEnter(node: FlowNode, state: FlowState, emit: (part:
|
|
2
|
+
import type { StreamPart } from '../types/stream.js';
|
|
3
|
+
export declare function emitInteractiveOnNodeEnter(node: FlowNode, state: FlowState, emit: (part: StreamPart) => void): void;
|
|
@@ -35,9 +35,12 @@ export function emitInteractiveOnNodeEnter(node, state, emit) {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
emit({
|
|
38
|
+
channel: 'internal',
|
|
38
39
|
type: 'interactive',
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
payload: {
|
|
41
|
+
nodeId: node.id,
|
|
42
|
+
options: node.choices,
|
|
43
|
+
prompt: interactivePrompt(node, state),
|
|
44
|
+
},
|
|
42
45
|
});
|
|
43
46
|
}
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '../types/standard-schema.js';
|
|
1
2
|
import type { CollectNode, FlowState } from '../types/flow.js';
|
|
2
3
|
import type { Tool } from '../types/effectTool.js';
|
|
3
|
-
import type {
|
|
4
|
+
import type { StreamPart } from '../types/stream.js';
|
|
4
5
|
export declare function resetCollect(state: FlowState, nodeId: string): void;
|
|
5
6
|
export declare function getCollectData(state: FlowState, nodeId: string): Record<string, unknown>;
|
|
7
|
+
export declare function setCollectData(state: FlowState, nodeId: string, data: Record<string, unknown>): void;
|
|
6
8
|
export declare function incrementCollectTurns(state: FlowState, nodeId: string): number;
|
|
7
9
|
export declare function computeMissingFields(node: CollectNode, data: Record<string, unknown>): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Fields whose collected value is present but does NOT satisfy the node's schema.
|
|
12
|
+
*
|
|
13
|
+
* Completion used to be presence-only: any non-empty value passed, so a model that invented
|
|
14
|
+
* an enum member (`urgency: "high"` against `z.enum(['emergency','urgent','routine'])`) or
|
|
15
|
+
* supplied the wrong primitive completed the node and the flow acted on it. The schema was
|
|
16
|
+
* only ever used to derive key names, never to check a value.
|
|
17
|
+
*
|
|
18
|
+
* Only validates synchronously — StandardSchema permits an async validator, and the
|
|
19
|
+
* completion gate is sync. Zod is sync, which covers every schema in this repo; an async
|
|
20
|
+
* validator degrades to the old presence-only behaviour rather than blocking.
|
|
21
|
+
*
|
|
22
|
+
* NOTE this catches SHAPE, not REFERENT. `unitId: "12B"` is a perfectly valid string; that a
|
|
23
|
+
* unit by that id does not exist is a domain fact only a tool boundary can know.
|
|
24
|
+
*/
|
|
25
|
+
export declare function invalidCollectFields(node: CollectNode, data: Record<string, unknown>): string[];
|
|
8
26
|
export declare function schemaSatisfied(node: CollectNode, state: FlowState): boolean;
|
|
9
27
|
export declare function projectCollectData(node: CollectNode, state: FlowState): unknown;
|
|
10
28
|
export declare function mergeExtractionData(current: Record<string, unknown>, incoming: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -20,4 +38,5 @@ export declare function mergeTurnExtraction(node: CollectNode, state: FlowState,
|
|
|
20
38
|
name: string;
|
|
21
39
|
result: unknown;
|
|
22
40
|
}>): MergeTurnExtractionResult;
|
|
23
|
-
export declare function emitExtractionTelemetry(node: CollectNode, state: FlowState, incoming: Record<string, unknown>, emit: (part:
|
|
41
|
+
export declare function emitExtractionTelemetry(node: CollectNode, state: FlowState, incoming: Record<string, unknown>, emit: (part: StreamPart) => void): void;
|
|
42
|
+
export declare function inferRequiredFields(schema: StandardSchemaV1): string[];
|
package/dist/flow/extraction.js
CHANGED
|
@@ -17,7 +17,7 @@ export function getCollectData(state, nodeId) {
|
|
|
17
17
|
}
|
|
18
18
|
return {};
|
|
19
19
|
}
|
|
20
|
-
function setCollectData(state, nodeId, data) {
|
|
20
|
+
export function setCollectData(state, nodeId, data) {
|
|
21
21
|
state[collectDataKey(nodeId)] = data;
|
|
22
22
|
}
|
|
23
23
|
function getCollectTurns(state, nodeId) {
|
|
@@ -33,9 +33,58 @@ export function computeMissingFields(node, data) {
|
|
|
33
33
|
const required = node.required ?? inferRequiredFields(node.schema);
|
|
34
34
|
return required.filter((field) => !fieldPopulated(data[field]));
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Fields whose collected value is present but does NOT satisfy the node's schema.
|
|
38
|
+
*
|
|
39
|
+
* Completion used to be presence-only: any non-empty value passed, so a model that invented
|
|
40
|
+
* an enum member (`urgency: "high"` against `z.enum(['emergency','urgent','routine'])`) or
|
|
41
|
+
* supplied the wrong primitive completed the node and the flow acted on it. The schema was
|
|
42
|
+
* only ever used to derive key names, never to check a value.
|
|
43
|
+
*
|
|
44
|
+
* Only validates synchronously — StandardSchema permits an async validator, and the
|
|
45
|
+
* completion gate is sync. Zod is sync, which covers every schema in this repo; an async
|
|
46
|
+
* validator degrades to the old presence-only behaviour rather than blocking.
|
|
47
|
+
*
|
|
48
|
+
* NOTE this catches SHAPE, not REFERENT. `unitId: "12B"` is a perfectly valid string; that a
|
|
49
|
+
* unit by that id does not exist is a domain fact only a tool boundary can know.
|
|
50
|
+
*/
|
|
51
|
+
export function invalidCollectFields(node, data) {
|
|
52
|
+
const validate = node.schema['~standard']?.validate;
|
|
53
|
+
if (typeof validate !== 'function')
|
|
54
|
+
return [];
|
|
55
|
+
let result;
|
|
56
|
+
try {
|
|
57
|
+
result = validate(data);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
if (result instanceof Promise)
|
|
63
|
+
return [];
|
|
64
|
+
const issues = result
|
|
65
|
+
?.issues;
|
|
66
|
+
if (!issues?.length)
|
|
67
|
+
return [];
|
|
68
|
+
const fields = new Set();
|
|
69
|
+
for (const issue of issues) {
|
|
70
|
+
const first = issue.path?.[0];
|
|
71
|
+
const key = typeof first === 'string'
|
|
72
|
+
? first
|
|
73
|
+
: typeof first?.key === 'string'
|
|
74
|
+
? (first.key)
|
|
75
|
+
: undefined;
|
|
76
|
+
// Only report fields the node actually collected. An issue on a field that was never
|
|
77
|
+
// supplied is a missing-field problem, which computeMissingFields already owns.
|
|
78
|
+
if (key && key in data)
|
|
79
|
+
fields.add(key);
|
|
80
|
+
}
|
|
81
|
+
return [...fields];
|
|
82
|
+
}
|
|
36
83
|
export function schemaSatisfied(node, state) {
|
|
37
84
|
const data = getCollectData(state, node.id);
|
|
38
|
-
|
|
85
|
+
if (computeMissingFields(node, data).length > 0)
|
|
86
|
+
return false;
|
|
87
|
+
return invalidCollectFields(node, data).length === 0;
|
|
39
88
|
}
|
|
40
89
|
export function projectCollectData(node, state) {
|
|
41
90
|
// Hand onComplete EVERY field the node collected (all schema keys present in
|
|
@@ -129,16 +178,22 @@ export function emitExtractionTelemetry(node, state, incoming, emit) {
|
|
|
129
178
|
}
|
|
130
179
|
}
|
|
131
180
|
emit({
|
|
181
|
+
channel: 'internal',
|
|
132
182
|
type: 'custom',
|
|
133
|
-
|
|
134
|
-
|
|
183
|
+
payload: {
|
|
184
|
+
name: 'flow.extraction.submission',
|
|
185
|
+
data: { node: node.id, fieldsAccepted, fieldsRejected },
|
|
186
|
+
},
|
|
135
187
|
});
|
|
136
188
|
const collected = getCollectData(state, node.id);
|
|
137
189
|
const missing = computeMissingFields(node, collected);
|
|
138
190
|
emit({
|
|
191
|
+
channel: 'internal',
|
|
139
192
|
type: 'custom',
|
|
140
|
-
|
|
141
|
-
|
|
193
|
+
payload: {
|
|
194
|
+
name: 'flow.extraction.update',
|
|
195
|
+
data: { nodeId: node.id, collected, missing },
|
|
196
|
+
},
|
|
142
197
|
});
|
|
143
198
|
}
|
|
144
199
|
function fieldPopulated(value) {
|
|
@@ -150,7 +205,7 @@ function fieldPopulated(value) {
|
|
|
150
205
|
}
|
|
151
206
|
return true;
|
|
152
207
|
}
|
|
153
|
-
function inferRequiredFields(schema) {
|
|
208
|
+
export function inferRequiredFields(schema) {
|
|
154
209
|
const zodSchema = schema;
|
|
155
210
|
if (typeof zodSchema?.shape === 'object') {
|
|
156
211
|
return Object.keys(zodSchema.shape);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { LanguageModel } from 'ai';
|
|
2
2
|
import type { Flow, FlowNode } from '../types/flow.js';
|
|
3
3
|
import type { RunState } from '../runtime/durable/types.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { StreamPart } from '../types/stream.js';
|
|
5
5
|
export interface ReduceTransitionInput {
|
|
6
6
|
fromNodeId: string;
|
|
7
7
|
toNode: FlowNode;
|
|
@@ -9,7 +9,7 @@ export interface ReduceTransitionInput {
|
|
|
9
9
|
flow: Flow;
|
|
10
10
|
model: LanguageModel;
|
|
11
11
|
data?: Record<string, unknown>;
|
|
12
|
-
emit: (part:
|
|
12
|
+
emit: (part: StreamPart) => void;
|
|
13
13
|
abortSignal?: AbortSignal;
|
|
14
14
|
}
|
|
15
15
|
export declare function reduceTransition(input: ReduceTransitionInput): Promise<void>;
|
|
@@ -8,9 +8,13 @@ function resolveNodeContext(toNode, flow) {
|
|
|
8
8
|
}
|
|
9
9
|
export async function reduceTransition(input) {
|
|
10
10
|
const { fromNodeId, toNode, run, flow, model, data, emit, abortSignal } = input;
|
|
11
|
-
emit({ type: 'node-exit', nodeName: fromNodeId });
|
|
12
|
-
emit({
|
|
13
|
-
|
|
11
|
+
emit({ channel: 'internal', type: 'node-exit', payload: { nodeName: fromNodeId } });
|
|
12
|
+
emit({
|
|
13
|
+
channel: 'internal',
|
|
14
|
+
type: 'flow-transition',
|
|
15
|
+
payload: { from: fromNodeId, to: toNode.id },
|
|
16
|
+
});
|
|
17
|
+
emit({ channel: 'internal', type: 'node-enter', payload: { nodeName: toNode.id } });
|
|
14
18
|
emitInteractiveOnNodeEnter(toNode, run.state, emit);
|
|
15
19
|
await applyContextStrategy({
|
|
16
20
|
strategy: resolveNodeContext(toNode, flow),
|
package/dist/flow/runFlow.d.ts
CHANGED
|
@@ -16,4 +16,15 @@ export type FlowResult = {
|
|
|
16
16
|
export declare class FlowOscillationError extends Error {
|
|
17
17
|
constructor(from: string, to: string);
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Drop everything a previous run of `flow` collected. Namespaced cache keys AND the
|
|
21
|
+
* un-namespaced copies `reduceTransition` promotes onto `run.state` via Object.assign.
|
|
22
|
+
*
|
|
23
|
+
* Clearing only the cache was not enough. The promoted fields are a plain merge, and
|
|
24
|
+
* `projectCollectData` omits any field the new extraction did not supply — so an optional
|
|
25
|
+
* field answered on report #1 (say `accessNotes: "key under the mat"`) and left blank on
|
|
26
|
+
* report #2 survives the merge and is read by the next action node as if it belonged to
|
|
27
|
+
* report #2. A work order then ships another unit's access instructions.
|
|
28
|
+
*/
|
|
29
|
+
export declare function clearFlowCollectCache(state: Record<string, unknown>, flow: Flow): void;
|
|
19
30
|
export declare function runFlow(flow: Flow, run: RunState, driver: ChannelDriver, ctx: RunContext, agent?: AgentConfig): Promise<FlowResult>;
|