@kuralle-agents/core 0.7.0 → 0.7.2

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/index.d.ts CHANGED
@@ -55,6 +55,8 @@ export { resolveAgentWorkspace, type ResolvedAgentWorkspace, } from './runtime/r
55
55
  export { DEFAULT_CONTEXT_BUDGET, VOICE_CONTEXT_BUDGET, computeMessageHistoryBudget, truncateToTokenBudget, formatMemoryWithBudget, estimateTokenCount, ContextBudget, } from './runtime/ContextBudget.js';
56
56
  export type { ContextBudgetConfig } from './runtime/ContextBudget.js';
57
57
  export { TokenAccumulator } from './runtime/TokenAccumulator.js';
58
+ export { applyPromptCache, applyAnthropicCacheControl, buildOpenAIResponsesProviderOptions, isAnthropicLanguageModel, isOpenAIResponsesModel, } from './runtime/promptCache.js';
59
+ export type { AnthropicCacheTtl, OpenAIResponsesCompactOptions } from './runtime/promptCache.js';
58
60
  export { handoffFilters, removeToolHistory, keepRecentMessages, removeKeys, composeFilters, } from './runtime/handoffFilters.js';
59
61
  export type { HandoffInputFilter, HandoffInputData, HandoffInputResult, } from './runtime/handoffFilters.js';
60
62
  export { DefaultToolExecutor, DefaultConversationState, DefaultConversationEventLog, DefaultAgentStateController, createFoundation, } from './foundation/index.js';
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection,
34
34
  export { resolveAgentWorkspace, } from './runtime/resolveAgentWorkspace.js';
35
35
  export { DEFAULT_CONTEXT_BUDGET, VOICE_CONTEXT_BUDGET, computeMessageHistoryBudget, truncateToTokenBudget, formatMemoryWithBudget, estimateTokenCount, ContextBudget, } from './runtime/ContextBudget.js';
36
36
  export { TokenAccumulator } from './runtime/TokenAccumulator.js';
37
+ export { applyPromptCache, applyAnthropicCacheControl, buildOpenAIResponsesProviderOptions, isAnthropicLanguageModel, isOpenAIResponsesModel, } from './runtime/promptCache.js';
37
38
  export { handoffFilters, removeToolHistory, keepRecentMessages, removeKeys, composeFilters, } from './runtime/handoffFilters.js';
38
39
  export { DefaultToolExecutor, DefaultConversationState, DefaultConversationEventLog, DefaultAgentStateController, createFoundation, } from './foundation/index.js';
39
40
  export { CapabilityHost, ExtractionCapability, GuardrailCapability, AutoRetrieveCapability, PassThroughRefinement, PassThroughValidation, toGeminiDeclarations, toAISDKTools, } from './capabilities/index.js';
@@ -5,8 +5,7 @@ import { TextDriver } from './channels/TextDriver.js';
5
5
  import { createRunContext } from './ctx.js';
6
6
  import { createEventBus, createTurnHandle } from '../events/TurnHandle.js';
7
7
  import { CoreToolExecutor } from '../tools/effect/index.js';
8
- import { createFsTool } from '../tools/fs/createFsTool.js';
9
- import { wireAgentSkills } from '../skills/wireAgentSkills.js';
8
+ import { buildAgentToolSurface } from './buildAgentToolSurface.js';
10
9
  import { hostLoop } from './hostLoop.js';
11
10
  import { isDegradableRuntimeError } from '../flow/degradableErrors.js';
12
11
  import { SAFE_DEGRADED_MESSAGE } from '../flow/degrade.js';
@@ -17,8 +16,7 @@ import { SessionRunStore } from './durable/SessionRunStore.js';
17
16
  import { loadRecordedSteps } from './durable/replay.js';
18
17
  import { markSessionOutcome } from './outcomeMarking.js';
19
18
  import { resolveAgentPolicies } from './policies/resolvePolicies.js';
20
- import { buildAutoRetrieveProvider, buildKnowledgeProvider, buildMemoryService, runMemoryIngest, wireWorkingMemory, } from './grounding/index.js';
21
- import { resolveAgentWorkspace } from './resolveAgentWorkspace.js';
19
+ import { buildAutoRetrieveProvider, buildKnowledgeProvider, buildMemoryService, runMemoryIngest, } from './grounding/index.js';
22
20
  import { SessionMutex } from './SessionMutex.js';
23
21
  export class Runtime {
24
22
  config;
@@ -66,38 +64,16 @@ export class Runtime {
66
64
  sessionStore: this.sessionStore,
67
65
  });
68
66
  const policies = resolveAgentPolicies(opened.agent);
69
- const agentTools = {
70
- ...(this.config.tools ?? {}),
71
- ...(opened.agent.tools ?? {}),
72
- // Global tools (ADR 0001) are model-visible in speaking turns via the
73
- // drivers; register their executors here too so a model call can actually
74
- // run them. Visibility stays gated (not exposed during collect extraction).
75
- ...(opened.agent.globalTools ?? {}),
76
- };
77
- const resolvedWorkspace = resolveAgentWorkspace(opened.agent.workspace);
78
- if (resolvedWorkspace) {
79
- agentTools.workspace = createFsTool({
80
- fs: resolvedWorkspace.fs,
81
- readOnly: resolvedWorkspace.readOnly,
82
- });
83
- }
84
- const wiredWorkingMemory = await wireWorkingMemory(opened.agent, opened.session, this.config.defaultWorkingMemoryStore);
85
- if (wiredWorkingMemory) {
86
- agentTools.memory_block = wiredWorkingMemory.memoryBlockTool;
87
- }
88
- let skillPrompt;
89
- let skillTools = {};
90
- if (opened.agent.skills) {
91
- const wired = await wireAgentSkills(opened.agent);
92
- if (wired) {
93
- skillTools = wired.tools;
94
- Object.assign(agentTools, wired.tools);
95
- skillPrompt = wired.promptSections.map((s) => s.content).join('\n\n');
96
- }
97
- }
98
- const workspaceTool = agentTools.workspace;
67
+ const knowledgeProvider = this.config.knowledge
68
+ ? buildKnowledgeProvider(this.config.knowledge)
69
+ : undefined;
70
+ const openingSurface = await buildAgentToolSurface(opened.agent, opened.session, {
71
+ configTools: this.config.tools,
72
+ knowledgeProvider,
73
+ defaultWorkingMemoryStore: this.config.defaultWorkingMemoryStore,
74
+ });
99
75
  const toolExecutor = new CoreToolExecutor({
100
- tools: agentTools,
76
+ tools: openingSurface.executorTools,
101
77
  enforcer: policies.enforcer,
102
78
  agentId: opened.agent.id,
103
79
  onInterim: (message) => {
@@ -113,9 +89,6 @@ export class Runtime {
113
89
  if (!model) {
114
90
  throw new Error('Runtime requires agent.model or config.defaultModel');
115
91
  }
116
- const knowledgeProvider = this.config.knowledge
117
- ? buildKnowledgeProvider(this.config.knowledge)
118
- : undefined;
119
92
  runCtx = await createRunContext({
120
93
  session: opened.session,
121
94
  runState: freshRunState,
@@ -137,23 +110,15 @@ export class Runtime {
137
110
  memoryService: this.config.memoryService
138
111
  ? buildMemoryService(this.config.memoryService, opened.agent)
139
112
  : undefined,
140
- fs: resolvedWorkspace?.fs,
113
+ fs: openingSurface.resolvedWorkspace?.fs,
141
114
  });
142
115
  // Agent base layer (ADR 0001): composed into every node turn by the drivers.
143
116
  runCtx.baseInstructions = opened.agent.instructions;
144
- runCtx.globalTools = {
145
- ...(opened.agent.globalTools ?? {}),
146
- ...(workspaceTool && resolvedWorkspace?.readOnly !== false
147
- ? { workspace: workspaceTool }
148
- : {}),
149
- ...skillTools,
150
- };
117
+ runCtx.globalTools = openingSurface.globalTools;
151
118
  runCtx.outOfBandControl = opened.agent.experimental?.outOfBandControl ?? false;
152
- runCtx.skillPrompt = skillPrompt;
153
- runCtx.workingMemoryPrompt = wiredWorkingMemory?.promptSection;
154
- runCtx.workingMemoryTools = wiredWorkingMemory
155
- ? { memory_block: wiredWorkingMemory.memoryBlockTool }
156
- : undefined;
119
+ runCtx.skillPrompt = openingSurface.skillPrompt;
120
+ runCtx.workingMemoryPrompt = openingSurface.workingMemoryPrompt;
121
+ runCtx.workingMemoryTools = openingSurface.workingMemoryTools;
157
122
  await this.hooks?.onStart?.(runCtx);
158
123
  const driver = opts.driver ?? new TextDriver();
159
124
  let activeAgent = opened.agent;
@@ -193,17 +158,22 @@ export class Runtime {
193
158
  });
194
159
  runCtx.runState.activeAgentId = loopResult.to;
195
160
  activeAgent = target;
161
+ const targetSurface = await buildAgentToolSurface(target, opened.session, {
162
+ configTools: this.config.tools,
163
+ knowledgeProvider,
164
+ defaultWorkingMemoryStore: this.config.defaultWorkingMemoryStore,
165
+ });
196
166
  runCtx.autoRetrieve = knowledgeProvider
197
167
  ? buildAutoRetrieveProvider(knowledgeProvider, target)
198
168
  : undefined;
169
+ runCtx.globalTools = targetSurface.globalTools;
170
+ runCtx.skillPrompt = targetSurface.skillPrompt;
171
+ runCtx.workingMemoryPrompt = targetSurface.workingMemoryPrompt;
172
+ runCtx.workingMemoryTools = targetSurface.workingMemoryTools;
173
+ runCtx.fs = targetSurface.resolvedWorkspace?.fs;
199
174
  runCtx.memoryService = this.config.memoryService
200
175
  ? buildMemoryService(this.config.memoryService, target)
201
176
  : undefined;
202
- const targetWorkingMemory = await wireWorkingMemory(target, opened.session, this.config.defaultWorkingMemoryStore);
203
- runCtx.workingMemoryPrompt = targetWorkingMemory?.promptSection;
204
- runCtx.workingMemoryTools = targetWorkingMemory
205
- ? { memory_block: targetWorkingMemory.memoryBlockTool }
206
- : undefined;
207
177
  await runCtx.runStore.putRunState(runCtx.runState);
208
178
  continue;
209
179
  }
@@ -0,0 +1,20 @@
1
+ import type { AgentConfig } from '../types/agentConfig.js';
2
+ import type { Session } from '../types/session.js';
3
+ import type { AnyTool } from '../types/effectTool.js';
4
+ import type { PersistentMemoryStore } from '../memory/blocks/types.js';
5
+ import type { KnowledgeProvider } from './KnowledgeProvider.js';
6
+ import { type ResolvedAgentWorkspace } from './resolveAgentWorkspace.js';
7
+ export interface AgentToolSurface {
8
+ executorTools: Record<string, AnyTool>;
9
+ globalTools: Record<string, AnyTool>;
10
+ workingMemoryTools?: Record<string, AnyTool>;
11
+ workingMemoryPrompt?: string;
12
+ skillPrompt?: string;
13
+ resolvedWorkspace?: ResolvedAgentWorkspace;
14
+ }
15
+ export interface BuildAgentToolSurfaceDeps {
16
+ configTools?: Record<string, AnyTool>;
17
+ knowledgeProvider?: KnowledgeProvider;
18
+ defaultWorkingMemoryStore?: PersistentMemoryStore;
19
+ }
20
+ export declare function buildAgentToolSurface(agent: AgentConfig, session: Session, deps: BuildAgentToolSurfaceDeps): Promise<AgentToolSurface>;
@@ -0,0 +1,56 @@
1
+ import { createFsTool } from '../tools/fs/createFsTool.js';
2
+ import { wireAgentSkills } from '../skills/wireAgentSkills.js';
3
+ import { buildKnowledgeTool, wireWorkingMemory } from './grounding/index.js';
4
+ import { resolveAgentWorkspace, } from './resolveAgentWorkspace.js';
5
+ export async function buildAgentToolSurface(agent, session, deps) {
6
+ const executorTools = {
7
+ ...(deps.configTools ?? {}),
8
+ ...(agent.tools ?? {}),
9
+ ...(agent.globalTools ?? {}),
10
+ };
11
+ const resolvedWorkspace = resolveAgentWorkspace(agent.workspace);
12
+ let workspaceTool;
13
+ if (resolvedWorkspace) {
14
+ workspaceTool = createFsTool({
15
+ fs: resolvedWorkspace.fs,
16
+ readOnly: resolvedWorkspace.readOnly,
17
+ });
18
+ executorTools.workspace = workspaceTool;
19
+ }
20
+ const wiredWorkingMemory = await wireWorkingMemory(agent, session, deps.defaultWorkingMemoryStore);
21
+ if (wiredWorkingMemory) {
22
+ executorTools.memory_block = wiredWorkingMemory.memoryBlockTool;
23
+ }
24
+ let skillPrompt;
25
+ let skillTools = {};
26
+ if (agent.skills) {
27
+ const wired = await wireAgentSkills(agent);
28
+ if (wired) {
29
+ skillTools = wired.tools;
30
+ Object.assign(executorTools, wired.tools);
31
+ skillPrompt = wired.promptSections.map((s) => s.content).join('\n\n');
32
+ }
33
+ }
34
+ const knowledgeTool = deps.knowledgeProvider
35
+ ? buildKnowledgeTool(deps.knowledgeProvider, agent)
36
+ : undefined;
37
+ if (knowledgeTool) {
38
+ executorTools.knowledge_search = knowledgeTool;
39
+ }
40
+ const globalTools = {
41
+ ...(agent.globalTools ?? {}),
42
+ ...(workspaceTool && resolvedWorkspace?.readOnly !== false ? { workspace: workspaceTool } : {}),
43
+ ...skillTools,
44
+ ...(knowledgeTool ? { knowledge_search: knowledgeTool } : {}),
45
+ };
46
+ return {
47
+ executorTools,
48
+ globalTools,
49
+ workingMemoryPrompt: wiredWorkingMemory?.promptSection,
50
+ workingMemoryTools: wiredWorkingMemory
51
+ ? { memory_block: wiredWorkingMemory.memoryBlockTool }
52
+ : undefined,
53
+ skillPrompt,
54
+ resolvedWorkspace,
55
+ };
56
+ }
@@ -9,6 +9,7 @@ import { resolveMaxSteps } from '../policies/limits.js';
9
9
  import { speakWithHostControl } from './streaming/hostControlSpeak.js';
10
10
  import { resolveStreamMode } from './streaming/mode.js';
11
11
  import { appendGatherBlocks, resolveNodeGatherScope, runGatherPhase } from '../grounding/index.js';
12
+ import { applyPromptCache } from '../promptCache.js';
12
13
  import { isFlowTransitionControlTool } from '../../flow/flowControlTools.js';
13
14
  import { resolveStructuredDecide } from '../../flow/choiceMatch.js';
14
15
  export class TextDriver {
@@ -50,12 +51,14 @@ export class TextDriver {
50
51
  const source = {
51
52
  async *[Symbol.asyncIterator]() {
52
53
  for (let step = 0; step < maxSteps; step += 1) {
54
+ const cached = applyPromptCache(model, ctx.session.id, messages);
53
55
  const result = streamText({
54
56
  model,
55
57
  system,
56
- messages,
58
+ messages: cached.messages,
57
59
  tools: aiTools,
58
60
  abortSignal: ctx.abortSignal,
61
+ ...(cached.providerOptions ? { providerOptions: cached.providerOptions } : {}),
59
62
  });
60
63
  for await (const part of result.fullStream) {
61
64
  if (part.type === 'text-delta') {
@@ -82,7 +85,11 @@ export class TextDriver {
82
85
  args: call.input,
83
86
  toolCallId: call.toolCallId,
84
87
  });
85
- const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, node.localTools);
88
+ const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, {
89
+ ...ctx.globalTools,
90
+ ...(ctx.workingMemoryTools ?? {}),
91
+ ...node.localTools,
92
+ });
86
93
  out.toolResults.push({
87
94
  name: call.toolName,
88
95
  args: call.input,
@@ -202,7 +202,11 @@ export class VoiceDriver {
202
202
  const onToolCall = (id, name, args) => {
203
203
  void (async () => {
204
204
  ctx.emit({ type: 'tool-call', toolName: name, args, toolCallId: id });
205
- const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: name, input: args, toolCallId: id }, localTools);
205
+ const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: name, input: args, toolCallId: id }, {
206
+ ...ctx.globalTools,
207
+ ...(ctx.workingMemoryTools ?? {}),
208
+ ...localTools,
209
+ });
206
210
  out.toolResults.push({ name, args, result: toolResult, toolCallId: id });
207
211
  toolCallsMade.push({
208
212
  toolCallId: id,
@@ -2,6 +2,7 @@ import { streamText } from 'ai';
2
2
  import { executeModelToolCall, toolResultMessage } from './executeModelTool.js';
3
3
  import { buildToolSet } from '../../tools/effect/index.js';
4
4
  import { buildNodePrompt, composeSystem } from '../../flow/nodeBuilders.js';
5
+ import { applyPromptCache } from '../promptCache.js';
5
6
  /**
6
7
  * Shared, NON-SPEAKING field extraction for `collect` nodes, used by every
7
8
  * ChannelDriver so text and voice behave identically. It runs the model with the
@@ -20,13 +21,15 @@ export async function runSilentExtraction(node, ctx, model, maxSteps) {
20
21
  const aiTools = resolveExtractionTools(node);
21
22
  const out = { text: '', toolResults: [] };
22
23
  for (let step = 0; step < maxSteps; step += 1) {
24
+ const cached = applyPromptCache(model, ctx.session.id, messages);
23
25
  const result = streamText({
24
26
  model,
25
27
  system,
26
- messages,
28
+ messages: cached.messages,
27
29
  tools: aiTools,
28
30
  temperature: 0,
29
31
  abortSignal: ctx.abortSignal,
32
+ ...(cached.providerOptions ? { providerOptions: cached.providerOptions } : {}),
30
33
  });
31
34
  for await (const part of result.fullStream) {
32
35
  // Intentionally NOT handling 'text-delta' — extraction never speaks.
@@ -1,4 +1,4 @@
1
- export { appendGatherBlocks, buildAutoRetrieveProvider, buildKnowledgeProvider, } from './knowledge.js';
1
+ export { appendGatherBlocks, buildAutoRetrieveProvider, buildKnowledgeProvider, buildKnowledgeTool, } from './knowledge.js';
2
2
  export { buildMemoryService, resetMissingUserIdWarningsForTests, runMemoryIngest, warnMissingUserId, } from './memory.js';
3
3
  export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection, resolveWorkingMemoryStore, resolveWorkingMemoryOwner, type LoadedWorkingMemoryBlock, type WiredWorkingMemory, } from './workingMemory.js';
4
4
  export { runGatherPhase, type GatherResult, type GatherScope } from './gather.js';
@@ -1,4 +1,4 @@
1
- export { appendGatherBlocks, buildAutoRetrieveProvider, buildKnowledgeProvider, } from './knowledge.js';
1
+ export { appendGatherBlocks, buildAutoRetrieveProvider, buildKnowledgeProvider, buildKnowledgeTool, } from './knowledge.js';
2
2
  export { buildMemoryService, resetMissingUserIdWarningsForTests, runMemoryIngest, warnMissingUserId, } from './memory.js';
3
3
  export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection, resolveWorkingMemoryStore, resolveWorkingMemoryOwner, } from './workingMemory.js';
4
4
  export { runGatherPhase } from './gather.js';
@@ -1,7 +1,9 @@
1
1
  import type { AgentConfig } from '../../types/agentConfig.js';
2
2
  import type { AutoRetrieveProvider } from '../../types/run-context.js';
3
+ import type { AnyTool } from '../../types/effectTool.js';
3
4
  import type { KnowledgeProviderConfig } from '../../types/voice.js';
4
5
  import { KnowledgeProvider } from '../KnowledgeProvider.js';
5
6
  export declare function buildKnowledgeProvider(config: KnowledgeProviderConfig): KnowledgeProvider;
6
7
  export declare function buildAutoRetrieveProvider(provider: KnowledgeProvider, agent: AgentConfig): AutoRetrieveProvider | undefined;
8
+ export declare function buildKnowledgeTool(provider: KnowledgeProvider, agent: AgentConfig): AnyTool | undefined;
7
9
  export declare function appendGatherBlocks(system: string, blocks: Array<string | undefined>): string;
@@ -1,3 +1,5 @@
1
+ import { z } from 'zod';
2
+ import { defineTool } from '../../tools/effect/defineTool.js';
1
3
  import { normalizeCitations } from '../citations/index.js';
2
4
  import { KnowledgeProvider } from '../KnowledgeProvider.js';
3
5
  function latestUserMessage(ctx) {
@@ -61,6 +63,41 @@ export function buildAutoRetrieveProvider(provider, agent) {
61
63
  },
62
64
  };
63
65
  }
66
+ export function buildKnowledgeTool(provider, agent) {
67
+ if (!agent.knowledge || agent.knowledge.autoRetrieve !== false) {
68
+ return undefined;
69
+ }
70
+ if (!provider.hasRetriever && !provider.hasCompiled) {
71
+ return undefined;
72
+ }
73
+ const overrides = agent.knowledge;
74
+ return defineTool({
75
+ name: 'knowledge_search',
76
+ description: 'Search the knowledge base for facts needed to answer the user. Call this whenever you need grounded information before answering. Returns relevant document snippets.',
77
+ input: z.object({
78
+ query: z
79
+ .string()
80
+ .trim()
81
+ .min(1, 'Query must not be empty.')
82
+ .describe("What to look up, in the user's language."),
83
+ }),
84
+ execute: async ({ query }, ctx) => {
85
+ const { results, events } = await provider.retrieve(query, undefined, overrides, false);
86
+ if (ctx?.emit) {
87
+ for (const event of events) {
88
+ ctx.emit(event);
89
+ }
90
+ }
91
+ const compiled = provider.getCompiledKnowledge(overrides);
92
+ return {
93
+ documents: [
94
+ ...(compiled ? [compiled] : []),
95
+ ...results.map((result) => result.text),
96
+ ],
97
+ };
98
+ },
99
+ });
100
+ }
64
101
  export function appendGatherBlocks(system, blocks) {
65
102
  const extras = blocks.filter((block) => Boolean(block?.trim()));
66
103
  if (extras.length === 0) {
@@ -23,7 +23,7 @@
23
23
  * but extends caching across short user idle periods + cross-session
24
24
  * within an hour.
25
25
  */
26
- import type { ModelMessage } from 'ai';
26
+ import type { JSONValue, ModelMessage } from 'ai';
27
27
  export type AnthropicCacheTtl = '5m' | '1h';
28
28
  /**
29
29
  * Returns a SHALLOW-COPIED message list with cache_control markers
@@ -90,3 +90,19 @@ export interface OpenAIResponsesCompactOptions {
90
90
  * branch but ignored otherwise.
91
91
  */
92
92
  export declare function buildOpenAIResponsesProviderOptions(opts: OpenAIResponsesCompactOptions, sessionId: string): Record<string, unknown> | null;
93
+ /**
94
+ * Single wiring point for provider prompt caching, called by the driver
95
+ * `streamText` sites. Gated by the conservative detectors so non-matching
96
+ * providers are untouched:
97
+ * - Anthropic → applies `cache_control` breakpoints (caches the system+tools
98
+ * prefix + recent history; ~75% input-cost + TTFT off multi-turn turns).
99
+ * - OpenAI Responses → sets `promptCacheKey` (= sessionId, pins same-session
100
+ * turns to one cache slot) + `truncation: 'auto'` overflow safety net.
101
+ * Returns the (possibly transformed) messages + an optional `providerOptions`
102
+ * bag ready to spread into `streamText`. Default-on: both are no-ops on
103
+ * providers that ignore the fields.
104
+ */
105
+ export declare function applyPromptCache(model: unknown, sessionId: string, messages: ModelMessage[]): {
106
+ messages: ModelMessage[];
107
+ providerOptions?: Record<string, Record<string, JSONValue>>;
108
+ };
@@ -110,6 +110,30 @@ export function buildOpenAIResponsesProviderOptions(opts, sessionId) {
110
110
  }
111
111
  return Object.keys(out).length > 0 ? out : null;
112
112
  }
113
+ /**
114
+ * Single wiring point for provider prompt caching, called by the driver
115
+ * `streamText` sites. Gated by the conservative detectors so non-matching
116
+ * providers are untouched:
117
+ * - Anthropic → applies `cache_control` breakpoints (caches the system+tools
118
+ * prefix + recent history; ~75% input-cost + TTFT off multi-turn turns).
119
+ * - OpenAI Responses → sets `promptCacheKey` (= sessionId, pins same-session
120
+ * turns to one cache slot) + `truncation: 'auto'` overflow safety net.
121
+ * Returns the (possibly transformed) messages + an optional `providerOptions`
122
+ * bag ready to spread into `streamText`. Default-on: both are no-ops on
123
+ * providers that ignore the fields.
124
+ */
125
+ export function applyPromptCache(model, sessionId, messages) {
126
+ const outMessages = isAnthropicLanguageModel(model)
127
+ ? applyAnthropicCacheControl(messages)
128
+ : messages;
129
+ if (isOpenAIResponsesModel(model)) {
130
+ const openai = buildOpenAIResponsesProviderOptions({ useSessionAsPromptCacheKey: true, truncationFallback: 'auto' }, sessionId);
131
+ if (openai) {
132
+ return { messages: outMessages, providerOptions: { openai: openai } };
133
+ }
134
+ }
135
+ return { messages: outMessages };
136
+ }
113
137
  function withProviderOptions(msg, cacheControl) {
114
138
  const existing = msg.providerOptions ?? {};
115
139
  const existingAnthropic = existing.anthropic ?? {};
@@ -1,5 +1,14 @@
1
1
  import type { MemoryBlockScope, PersistentMemoryConfig } from '../memory/blocks/types.js';
2
2
  export interface AgentKnowledge {
3
+ /** Whether the runtime retrieves automatically. Default: `true`.
4
+ * - `true` (guaranteed): pre-inject retrieved knowledge before every
5
+ * answering turn — always grounded; a fused routing turn pays the
6
+ * retrieval cost (the price of a kept grounding promise).
7
+ * - `false` (on-demand): the runtime does not auto-inject; instead the model
8
+ * is given a `knowledge_search` tool and retrieves only when it answers, so
9
+ * routing/dispatch turns pay zero retrieval tax (grounding becomes
10
+ * model-discretion). `autoRetrieve` and the tool are mutually exclusive —
11
+ * the boolean picks the invoker (runtime vs model), not a separate mode. */
3
12
  autoRetrieve?: boolean;
4
13
  sources?: string[];
5
14
  }
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.7.0",
9
+ "version": "0.7.2",
10
10
  "description": "A framework for structured conversational AI agents",
11
11
  "publishConfig": {
12
12
  "access": "public"
@@ -103,7 +103,7 @@
103
103
  "typescript": "^5.3.0",
104
104
  "vitest": "^3.2.4",
105
105
  "zod": "^4.0.0",
106
- "@kuralle-agents/realtime-audio": "0.7.0"
106
+ "@kuralle-agents/realtime-audio": "0.7.2"
107
107
  },
108
108
  "dependencies": {
109
109
  "chrono-node": "^2.6.0"