refactor(agent): unify sourced message delivery
This commit is contained in:
@@ -8,8 +8,9 @@
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import type { AdditionalContext, Agent, PromptDecision } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent, PromptDecision } from '@deepseek-ai/dsh-agent'
|
||||
import type { MessageSource } from '@deepseek-ai/dsh-llm'
|
||||
import type { UserMessageData } from '@deepseek-ai/dsh-session'
|
||||
import type { PostToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'repeat-tool-guard'
|
||||
@@ -142,7 +143,7 @@ function validateThresholds(values: number[]): number[] {
|
||||
* Prepend the guard's reminder while preserving every downstream context's
|
||||
* source and metadata.
|
||||
*/
|
||||
function prependContext(ours: AdditionalContext, theirs: AdditionalContext[] | undefined): AdditionalContext[] {
|
||||
function prependContext(ours: UserMessageData, theirs: UserMessageData[] | undefined): UserMessageData[] {
|
||||
return [ours, ...theirs ?? []]
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
* same pipeline), and a model hammering a denied call is exactly the loop
|
||||
* worth breaking.
|
||||
*/
|
||||
function observe(exec: ToolExecution): AdditionalContext | undefined {
|
||||
function observe(exec: ToolExecution): UserMessageData | undefined {
|
||||
// A direct `ctx.tools.execute()` caller has no model to remind and no id
|
||||
// to key on; only agent-loop calls participate.
|
||||
if (!exec.agent) return undefined
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('threshold escalation', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
@@ -77,7 +77,7 @@ describe('threshold escalation', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
@@ -99,7 +99,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
@@ -123,7 +123,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(reminders(agent)).toHaveLength(1)
|
||||
@@ -141,7 +141,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
@@ -162,7 +162,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
@@ -178,7 +178,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(reminders(agent)).toHaveLength(1) // probe was NOT excluded
|
||||
@@ -194,7 +194,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(reminders(agent)).toHaveLength(1) // all three canonicalize identically
|
||||
@@ -215,8 +215,8 @@ describe('chain semantics', () => {
|
||||
]))
|
||||
const agentA = ctx.agentLoop.create(SessionId('a'), { provider: 'mock-a', model: 'model-a' })
|
||||
const agentB = ctx.agentLoop.create(SessionId('b'), { provider: 'mock-b', model: 'model-b' })
|
||||
agentA.followup([{ type: 'text', text: 'go' }])
|
||||
agentB.followup([{ type: 'text', text: 'go' }])
|
||||
agentA.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
agentB.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await Promise.all([waitForIdle(ctx, agentA), waitForIdle(ctx, agentB)])
|
||||
|
||||
expect(reminders(agentA)).toHaveLength(0) // 2 repeats < 3, despite B's 3 in the same registry
|
||||
@@ -234,9 +234,9 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
agent.followup([{ type: 'text', text: 'again' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'again' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(reminders(agent)).toHaveLength(0)
|
||||
@@ -256,13 +256,13 @@ describe('chain semantics', () => {
|
||||
const fiber = await ctx.plugin(Object.assign((inner: Context) => {
|
||||
first = inner.agentLoop.create(SessionId('reused'), { provider: 'mock', model: 'mock' })
|
||||
}, { inject: ['agentLoop'] }))
|
||||
first.followup([{ type: 'text', text: 'go' }])
|
||||
first.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, first)
|
||||
await fiber.dispose()
|
||||
await first.whenIdle()
|
||||
|
||||
const second = ctx.agentLoop.create(SessionId('reused'), { provider: 'mock', model: 'mock' })
|
||||
second.followup([{ type: 'text', text: 'go' }])
|
||||
second.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, second)
|
||||
|
||||
expect(reminders(second)).toHaveLength(0)
|
||||
@@ -278,7 +278,7 @@ describe('chain semantics', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(reminders(agent)).toHaveLength(1)
|
||||
@@ -294,7 +294,7 @@ describe('chain semantics', () => {
|
||||
textResponse('done'),
|
||||
]))
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(reminders(agent)).toHaveLength(0)
|
||||
@@ -316,7 +316,7 @@ describe('fold onto the downstream decision', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
@@ -347,7 +347,7 @@ describe('fold onto the downstream decision', () => {
|
||||
])
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
agent.followup([{ type: 'text', text: 'go' }])
|
||||
agent.followup({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const found = reminders(agent)
|
||||
|
||||
Reference in New Issue
Block a user