Merge origin/master into parallel-tool-call
This commit is contained in:
432 files changed
+28392
-3077
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { agentEvents } from '@deepseek-ai/dsh-agent'
|
||||
import type { AgentId, AgentOptions, AgentStatus, SendOptions } from '@deepseek-ai/dsh-agent'
|
||||
import type { AgentId, AgentOptions, AgentStatus, InjectOptions, SendOptions } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { deepFreeze } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
|
||||
@@ -230,14 +230,20 @@ export class ReactLoopAgent implements Agent {
|
||||
agentEvents(this.loopCtx, this).emit('agent/queued', accepted.content, info)
|
||||
}
|
||||
|
||||
inject(content: ContentBlock[], options?: SendOptions): void {
|
||||
inject(content: ContentBlock[], options?: InjectOptions): void {
|
||||
this.assertNotDisposed()
|
||||
const source = this.resolveSource(options)
|
||||
const context = {
|
||||
content,
|
||||
source,
|
||||
...options?.envelope !== undefined ? { envelope: options.envelope } : {},
|
||||
...options?.meta !== undefined ? { meta: options.meta } : {},
|
||||
}
|
||||
if (isTurnOpen(this.session)) {
|
||||
// A turn is open in the LOG (decided from the log, not agent status —
|
||||
// status can be `running` with no turn open): the context/message is
|
||||
// turn-enclosed by that turn, so append it directly.
|
||||
this.session.append('context/message', { content, source }, { surfaceOp: 'append' })
|
||||
this.session.append('context/message', context, { surfaceOp: 'append' })
|
||||
return
|
||||
}
|
||||
// No turn open: wrap the injection in a one-shot turn so every event stays
|
||||
@@ -249,7 +255,7 @@ export class ReactLoopAgent implements Agent {
|
||||
// are contained by Session and cannot create a false append failure.
|
||||
try {
|
||||
this.session.append('turn/start', { turn, trigger: { kind: 'injection', source } })
|
||||
this.session.append('context/message', { content, source }, { surfaceOp: 'append' })
|
||||
this.session.append('context/message', context, { surfaceOp: 'append' })
|
||||
} finally {
|
||||
// Close the turn if turn/start made it into the log. A pre-commit veto
|
||||
// must escape rather than being mistaken for a committed turn/end.
|
||||
|
||||
@@ -237,10 +237,15 @@ async function runTurn(
|
||||
// `allow.content` REPLACES the prompt bytes (a rewrite); absent keeps them.
|
||||
const content = decision.content ?? message.content
|
||||
session.append('user/message', { content, source: message.source }, { surfaceOp: 'append' })
|
||||
// `allow.additionalContext` is a SEPARATE context/message the next request
|
||||
// also sees. The turn is open, so inject() appends it into THIS turn.
|
||||
if (decision.additionalContext) {
|
||||
agent.inject(decision.additionalContext.content, { source: decision.additionalContext.source })
|
||||
// Every `allow.additionalContexts` entry is a separate context/message the
|
||||
// next request also sees. The turn is open, so inject() appends each one
|
||||
// into THIS turn without flattening provenance, framing, or metadata.
|
||||
for (const context of decision.additionalContexts ?? []) {
|
||||
agent.inject(context.content, {
|
||||
source: context.source,
|
||||
...context.envelope !== undefined ? { envelope: context.envelope } : {},
|
||||
...context.meta !== undefined ? { meta: context.meta } : {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,7 +573,11 @@ async function runStep(
|
||||
|
||||
// Append context after the complete result batch to preserve call/result adjacency.
|
||||
for (const context of pendingContext) {
|
||||
agent.inject(context.content, { source: context.source })
|
||||
agent.inject(context.content, {
|
||||
source: context.source,
|
||||
...context.envelope !== undefined ? { envelope: context.envelope } : {},
|
||||
...context.meta !== undefined ? { meta: context.meta } : {},
|
||||
})
|
||||
}
|
||||
|
||||
return { hadToolCalls: toolCalls.length > 0, finish: assembler.finish }
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* The session log stays the source of truth and is reconstructable regardless
|
||||
* of dispatch timing: each STARTED call appends its own `tool/call` before its
|
||||
* body runs, `tool/result` events are appended in MODEL order (slot-buffered
|
||||
* behind a commit cursor), and buffered `additionalContext` is injected in model
|
||||
* behind a commit cursor), and buffered `additionalContexts` are injected in model
|
||||
* call order after every result. A `tool/call`'s log position may interleave
|
||||
* with a sibling's `tool/result` as the pool replenishes; that is safe because
|
||||
* `tool/call` is log-only and derived history pairs the assistant message's
|
||||
@@ -24,7 +24,7 @@ import type { Context } from 'cordis'
|
||||
import { assertNever, type ToolCallBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { HookContext } from '@deepseek-ai/dsh-agent'
|
||||
import type { Session } from '@deepseek-ai/dsh-session'
|
||||
import { TOOL_REGISTRY_SCHEDULER, type ToolExecution, type ToolExecutionInput, type ToolExecutionMode, type ToolExecutionResult } from '@deepseek-ai/dsh-tools'
|
||||
import { TOOL_REGISTRY_SCHEDULER, type ToolExecutionInput, type ToolExecutionMode, type ToolExecutionResult, type ToolRunContext } from '@deepseek-ai/dsh-tools'
|
||||
import type { ReactLoopAgent } from './agent.ts'
|
||||
|
||||
/** One tool call after argument parsing, ready to schedule. */
|
||||
@@ -38,7 +38,7 @@ interface PlannedCall {
|
||||
/** A settled call's slot, filled in model order before ordered finalization. */
|
||||
interface Slot {
|
||||
/** The registry-minted execution object, carrying this call's token. */
|
||||
exec: ToolExecution
|
||||
exec: ToolRunContext
|
||||
/** The raw dispatch/pre result. */
|
||||
result: ToolExecutionResult
|
||||
/** Whether the result still needs ordered `tools/post-execute` finalization. */
|
||||
@@ -49,7 +49,7 @@ interface Slot {
|
||||
* Execute one assistant step's tool calls, honoring per-call concurrency safety.
|
||||
*
|
||||
* Appends `tool/call` (per started call) and `tool/result` (in model order) to
|
||||
* the session, and returns the ordered `additionalContext` buffer for the loop
|
||||
* the session, and returns the ordered `additionalContexts` buffer for the loop
|
||||
* to inject after the batch. On abort it drains only already-started calls to
|
||||
* results, drops buffered context, and throws the abort error so `runTurn` owns
|
||||
* the turn-end reason.
|
||||
@@ -62,7 +62,7 @@ interface Slot {
|
||||
* @param toolCalls - the assistant message's `tool-call` blocks, in model order.
|
||||
* @param signal - the step's abort signal (shared by every call).
|
||||
* @param maxParallel - the already-validated cap snapshot for parallel groups.
|
||||
* @returns the per-step `additionalContext` buffer in model call order.
|
||||
* @returns the per-step `additionalContexts` buffer in model call order.
|
||||
*/
|
||||
export async function executeToolCalls(
|
||||
ctx: Context,
|
||||
@@ -121,7 +121,7 @@ function parseArguments(raw: string): unknown {
|
||||
* it against the live registry. An exclusive result stops replenishment, drains
|
||||
* the current run, and remains for the caller's next singleton group. Settled
|
||||
* dispatches land in model-order slots; a commit cursor appends `tool/result`
|
||||
* (and collects `additionalContext`) only while the next slot is ready, so the
|
||||
* (and collects `additionalContexts`) only while the next slot is ready, so the
|
||||
* log stays model-ordered regardless of completion order.
|
||||
*
|
||||
* Abort: an already-aborted signal starts nothing and throws before any
|
||||
@@ -152,7 +152,7 @@ async function runGroup(
|
||||
let aborted: boolean = signal.aborted
|
||||
|
||||
// Advance the commit cursor over contiguous settled slots: run post-execute in
|
||||
// model order, append each tool/result, and collect its additionalContext.
|
||||
// model order, append each tool/result, and collect its additionalContexts.
|
||||
const commitReady = async (): Promise<void> => {
|
||||
while (committed < group.length) {
|
||||
const slot = slots[committed]
|
||||
@@ -164,7 +164,7 @@ async function runGroup(
|
||||
// committed < group.length, so call and its callSeq (set at start) exist.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounded index
|
||||
appendToolResult(session, turn, step, call!.block, result, callSeqs[committed]!)
|
||||
if (result.additionalContext) pendingContext.push(result.additionalContext)
|
||||
pendingContext.push(...result.additionalContexts ?? [])
|
||||
committed++
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user