@kuralle-agents/core 0.3.5 → 0.3.6
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/dist/flow/nodeBuilders.d.ts +5 -0
- package/dist/flow/nodeBuilders.js +8 -0
- package/dist/runtime/Runtime.js +3 -0
- package/dist/runtime/channels/TextDriver.js +7 -6
- package/dist/runtime/channels/VoiceDriver.js +5 -4
- package/dist/runtime/channels/extractionTurn.js +3 -2
- package/dist/types/agentConfig.d.ts +6 -0
- package/dist/types/run-context.d.ts +7 -0
- package/package.json +2 -2
|
@@ -4,5 +4,10 @@ import type { ResolvedNode } from '../types/channel.js';
|
|
|
4
4
|
import type { Tool } from '../types/effectTool.js';
|
|
5
5
|
export declare function resolveInstructions(instructions: Instructions, state: FlowState): string;
|
|
6
6
|
export declare function buildNodePrompt(node: ReplyNode, state: FlowState): string;
|
|
7
|
+
/** Compose the agent base layer (ADR 0001) into a node's system prompt: the
|
|
8
|
+
* agent's base instructions (persona / safety / grounding) prefix the node's
|
|
9
|
+
* own instructions. Node instructions layer ON TOP — they never replace the
|
|
10
|
+
* base. Base resolves against the current state so dynamic base prompts work. */
|
|
11
|
+
export declare function composeSystem(base: Instructions | undefined, nodeSystem: string, state: FlowState): string;
|
|
7
12
|
export declare function resolveReplyNode(node: ReplyNode, state: FlowState): ResolvedNode;
|
|
8
13
|
export declare function resolveCollectExtractionNode(collectNode: CollectNode, missing: string[], state: FlowState, submitTool: Tool): ResolvedNode;
|
|
@@ -15,6 +15,14 @@ export function resolveInstructions(instructions, state) {
|
|
|
15
15
|
export function buildNodePrompt(node, state) {
|
|
16
16
|
return resolveInstructions(node.instructions, state);
|
|
17
17
|
}
|
|
18
|
+
/** Compose the agent base layer (ADR 0001) into a node's system prompt: the
|
|
19
|
+
* agent's base instructions (persona / safety / grounding) prefix the node's
|
|
20
|
+
* own instructions. Node instructions layer ON TOP — they never replace the
|
|
21
|
+
* base. Base resolves against the current state so dynamic base prompts work. */
|
|
22
|
+
export function composeSystem(base, nodeSystem, state) {
|
|
23
|
+
const baseText = base ? resolveInstructions(base, state) : '';
|
|
24
|
+
return [baseText, nodeSystem].filter((s) => s && s.trim()).join('\n\n');
|
|
25
|
+
}
|
|
18
26
|
function buildNodeTools(node, state) {
|
|
19
27
|
if (!node.tools) {
|
|
20
28
|
return {};
|
package/dist/runtime/Runtime.js
CHANGED
|
@@ -97,6 +97,9 @@ export class Runtime {
|
|
|
97
97
|
? buildMemoryService(this.config.memoryService, opened.agent)
|
|
98
98
|
: undefined,
|
|
99
99
|
});
|
|
100
|
+
// Agent base layer (ADR 0001): composed into every node turn by the drivers.
|
|
101
|
+
runCtx.baseInstructions = opened.agent.instructions;
|
|
102
|
+
runCtx.globalTools = opened.agent.globalTools;
|
|
100
103
|
await this.hooks?.onStart?.(runCtx);
|
|
101
104
|
const driver = opts.driver ?? new TextDriver();
|
|
102
105
|
let activeAgent = opened.agent;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { streamText, generateObject } from 'ai';
|
|
2
|
-
import { buildNodePrompt, resolveInstructions } from '../../flow/nodeBuilders.js';
|
|
2
|
+
import { buildNodePrompt, resolveInstructions, composeSystem } from '../../flow/nodeBuilders.js';
|
|
3
3
|
import { buildToolSet } from '../../tools/effect/index.js';
|
|
4
4
|
import { classifyControl } from '../../flow/classifyControl.js';
|
|
5
5
|
import { consumePendingUserInput } from './inputBuffer.js';
|
|
@@ -29,10 +29,11 @@ export class TextDriver {
|
|
|
29
29
|
const gather = await runGatherPhase(ctx);
|
|
30
30
|
const out = { text: '', toolResults: [] };
|
|
31
31
|
const model = replyNode.model ?? ctx.model;
|
|
32
|
-
const
|
|
32
|
+
const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
|
|
33
|
+
const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
|
|
33
34
|
const system = appendGatherBlocks(baseSystem, [gather.retrievalBlock, gather.memoryBlock]);
|
|
34
35
|
const messages = [...ctx.runState.messages];
|
|
35
|
-
const aiTools = this.resolveTools(node);
|
|
36
|
+
const aiTools = this.resolveTools(node, ctx.globalTools);
|
|
36
37
|
const maxSteps = resolveMaxSteps(ctx.limits, this.maxSteps);
|
|
37
38
|
const toolCallsMade = [];
|
|
38
39
|
let draftText = '';
|
|
@@ -135,7 +136,7 @@ export class TextDriver {
|
|
|
135
136
|
return runSilentExtraction(node, ctx, model, resolveMaxSteps(ctx.limits, this.maxSteps));
|
|
136
137
|
}
|
|
137
138
|
async runStructured(node, ctx) {
|
|
138
|
-
const base = resolveInstructions(node.instructions, ctx.runState.state);
|
|
139
|
+
const base = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state);
|
|
139
140
|
const schema = node.schema;
|
|
140
141
|
// When the node offers choices (e.g. via withChoices), constrain the model
|
|
141
142
|
// to return exactly one option id. Otherwise an unconstrained string schema
|
|
@@ -159,8 +160,8 @@ export class TextDriver {
|
|
|
159
160
|
const input = consumePendingUserInput(ctx.session);
|
|
160
161
|
return { type: 'message', input };
|
|
161
162
|
}
|
|
162
|
-
resolveTools(resolved) {
|
|
163
|
-
const merged = { ...this.toolDefs, ...(resolved.localTools ?? {}) };
|
|
163
|
+
resolveTools(resolved, globalTools) {
|
|
164
|
+
const merged = { ...this.toolDefs, ...(globalTools ?? {}), ...(resolved.localTools ?? {}) };
|
|
164
165
|
const aiTools = { ...resolved.tools };
|
|
165
166
|
for (const [name, tool] of Object.entries(merged)) {
|
|
166
167
|
if (tool && !aiTools[name]) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateObject } from 'ai';
|
|
2
2
|
import { runSilentExtraction } from './extractionTurn.js';
|
|
3
|
-
import { buildNodePrompt, resolveInstructions } from '../../flow/nodeBuilders.js';
|
|
3
|
+
import { buildNodePrompt, resolveInstructions, composeSystem } from '../../flow/nodeBuilders.js';
|
|
4
4
|
import { classifyControl } from '../../flow/classifyControl.js';
|
|
5
5
|
import { applyPreTurnPolicies, applyPostTurnPolicies } from '../policies/agentTurn.js';
|
|
6
6
|
import { resolveMaxSteps } from '../policies/limits.js';
|
|
@@ -31,9 +31,10 @@ export class VoiceDriver {
|
|
|
31
31
|
}
|
|
32
32
|
const gather = await runGatherPhase(ctx);
|
|
33
33
|
const out = { text: '', toolResults: [] };
|
|
34
|
-
const
|
|
34
|
+
const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
|
|
35
|
+
const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
|
|
35
36
|
const system = appendGatherBlocks(baseSystem, [gather.retrievalBlock, gather.memoryBlock]);
|
|
36
|
-
const geminiTools = resolveVoiceGeminiTools(node, this.toolDefs);
|
|
37
|
+
const geminiTools = resolveVoiceGeminiTools(node, { ...this.toolDefs, ...(ctx.globalTools ?? {}) });
|
|
37
38
|
const toolCallsMade = [];
|
|
38
39
|
const maxSteps = resolveMaxSteps(ctx.limits, this.maxSteps);
|
|
39
40
|
let draftText = '';
|
|
@@ -63,7 +64,7 @@ export class VoiceDriver {
|
|
|
63
64
|
return out;
|
|
64
65
|
}
|
|
65
66
|
async runStructured(node, ctx) {
|
|
66
|
-
const system = resolveInstructions(node.instructions, ctx.runState.state);
|
|
67
|
+
const system = composeSystem(ctx.baseInstructions, resolveInstructions(node.instructions, ctx.runState.state), ctx.runState.state);
|
|
67
68
|
const schema = node.schema;
|
|
68
69
|
const { object } = await generateObject({
|
|
69
70
|
model: ctx.model,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { streamText } from 'ai';
|
|
2
2
|
import { buildToolSet } from '../../tools/effect/index.js';
|
|
3
|
-
import { buildNodePrompt } from '../../flow/nodeBuilders.js';
|
|
3
|
+
import { buildNodePrompt, composeSystem } from '../../flow/nodeBuilders.js';
|
|
4
4
|
/**
|
|
5
5
|
* Shared, NON-SPEAKING field extraction for `collect` nodes, used by every
|
|
6
6
|
* ChannelDriver so text and voice behave identically. It runs the model with the
|
|
@@ -13,7 +13,8 @@ import { buildNodePrompt } from '../../flow/nodeBuilders.js';
|
|
|
13
13
|
*/
|
|
14
14
|
export async function runSilentExtraction(node, ctx, model, maxSteps) {
|
|
15
15
|
const replyNode = node.node;
|
|
16
|
-
const
|
|
16
|
+
const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
|
|
17
|
+
const system = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state);
|
|
17
18
|
const messages = [...ctx.runState.messages];
|
|
18
19
|
const aiTools = resolveExtractionTools(node);
|
|
19
20
|
const out = { text: '', toolResults: [] };
|
|
@@ -16,6 +16,12 @@ export interface AgentConfig {
|
|
|
16
16
|
model?: LanguageModel;
|
|
17
17
|
tools?: ToolSet;
|
|
18
18
|
effectTools?: Record<string, AnyTool>;
|
|
19
|
+
/** Safe, always-available tools made model-visible in EVERY speaking node turn
|
|
20
|
+
* (the agent "base layer", ADR 0001) — e.g. a returns/FAQ knowledge-base
|
|
21
|
+
* lookup the user might ask for mid-flow. This is an explicit allow-list:
|
|
22
|
+
* NEVER put consequential/mutating tools here (they must stay flow-gated), and
|
|
23
|
+
* they are not exposed during non-speaking collect extraction. */
|
|
24
|
+
globalTools?: Record<string, AnyTool>;
|
|
19
25
|
flows?: Flow[];
|
|
20
26
|
routes?: Route[];
|
|
21
27
|
routing?: RoutingPolicy;
|
|
@@ -8,6 +8,7 @@ import type { RefinementCapability } from '../capabilities/RefinementCapability.
|
|
|
8
8
|
import type { ValidationCapability } from '../capabilities/ValidationCapability.js';
|
|
9
9
|
import type { Limits } from './guardrails.js';
|
|
10
10
|
import type { AnyTool } from './effectTool.js';
|
|
11
|
+
import type { Instructions } from './agentConfig.js';
|
|
11
12
|
export interface EffectToolExecutor {
|
|
12
13
|
execute(args: {
|
|
13
14
|
name: string;
|
|
@@ -57,6 +58,12 @@ export interface RunContext {
|
|
|
57
58
|
* context. Reset to false on every `createRunContext` (i.e. every turn).
|
|
58
59
|
*/
|
|
59
60
|
turnInputConsumed?: boolean;
|
|
61
|
+
/** Agent base layer (ADR 0001), set when entering a flow. `baseInstructions`
|
|
62
|
+
* is composed as a prefix into every node turn's system prompt (persona /
|
|
63
|
+
* safety / grounding floor); `globalTools` are safe tools made model-visible
|
|
64
|
+
* in every speaking turn. */
|
|
65
|
+
baseInstructions?: Instructions;
|
|
66
|
+
globalTools?: Record<string, AnyTool>;
|
|
60
67
|
tool(name: string, args: unknown, options?: {
|
|
61
68
|
toolCallId?: string;
|
|
62
69
|
def?: AnyTool;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"url": "git+https://github.com/kuralle/kuralle-agents.git",
|
|
7
7
|
"directory": "packages/kuralle-core"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.6",
|
|
10
10
|
"description": "A framework for structured conversational AI agents",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"dotenv": "^16.4.0",
|
|
98
98
|
"typescript": "^5.3.0",
|
|
99
99
|
"zod": "^3.23.0",
|
|
100
|
-
"@kuralle-agents/realtime-audio": "0.3.
|
|
100
|
+
"@kuralle-agents/realtime-audio": "0.3.6"
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
103
|
"chrono-node": "^2.6.0",
|