klaus-agent 0.3.1 → 0.4.1
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 +36 -2
- package/README.zh-CN.md +35 -1
- package/dist/approval/approval.d.ts +8 -3
- package/dist/approval/approval.js +23 -23
- package/dist/approval/approval.js.map +1 -1
- package/dist/approval/types.d.ts +1 -0
- package/dist/background/task-manager.d.ts +2 -0
- package/dist/background/task-manager.js +14 -0
- package/dist/background/task-manager.js.map +1 -1
- package/dist/compaction/compaction.d.ts +0 -1
- package/dist/compaction/compaction.js +21 -35
- package/dist/compaction/compaction.js.map +1 -1
- package/dist/compaction/summarizer.js +2 -7
- package/dist/compaction/summarizer.js.map +1 -1
- package/dist/core/agent-loop.d.ts +2 -1
- package/dist/core/agent-loop.js +14 -9
- package/dist/core/agent-loop.js.map +1 -1
- package/dist/core/agent.d.ts +7 -0
- package/dist/core/agent.js +35 -10
- package/dist/core/agent.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/injection/history-normalizer.js +20 -10
- package/dist/injection/history-normalizer.js.map +1 -1
- package/dist/llm/types.d.ts +2 -0
- package/dist/multi-agent/task-executor.d.ts +2 -1
- package/dist/multi-agent/types.d.ts +3 -0
- package/dist/planning/planning-manager.d.ts +2 -0
- package/dist/planning/planning-manager.js +6 -0
- package/dist/planning/planning-manager.js.map +1 -1
- package/dist/providers/anthropic.js +14 -6
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/openai-codex.js +5 -8
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/openai.js +3 -2
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/shared.d.ts +2 -2
- package/dist/providers/shared.js +11 -6
- package/dist/providers/shared.js.map +1 -1
- package/dist/session/session-manager.d.ts +1 -0
- package/dist/session/session-manager.js +11 -1
- package/dist/session/session-manager.js.map +1 -1
- package/dist/task-graph/result-injection.d.ts +8 -0
- package/dist/task-graph/result-injection.js +26 -0
- package/dist/task-graph/result-injection.js.map +1 -0
- package/dist/task-graph/task-graph.d.ts +41 -0
- package/dist/task-graph/task-graph.js +266 -0
- package/dist/task-graph/task-graph.js.map +1 -0
- package/dist/task-graph/tools.d.ts +3 -0
- package/dist/task-graph/tools.js +106 -0
- package/dist/task-graph/tools.js.map +1 -0
- package/dist/task-graph/types.d.ts +44 -0
- package/dist/task-graph/types.js +9 -0
- package/dist/task-graph/types.js.map +1 -0
- package/dist/tools/executor.d.ts +3 -2
- package/dist/tools/mcp-adapter.js +22 -5
- package/dist/tools/mcp-adapter.js.map +1 -1
- package/dist/utils/id.js +2 -6
- package/dist/utils/id.js.map +1 -1
- package/dist/wire/wire.d.ts +2 -1
- package/package.json +1 -1
- package/src/approval/approval.ts +29 -23
- package/src/approval/types.ts +1 -0
- package/src/background/task-manager.ts +17 -0
- package/src/compaction/compaction.ts +23 -36
- package/src/compaction/summarizer.ts +2 -7
- package/src/core/agent-loop.ts +17 -10
- package/src/core/agent.ts +41 -9
- package/src/index.ts +15 -0
- package/src/injection/history-normalizer.ts +22 -12
- package/src/llm/types.ts +2 -0
- package/src/multi-agent/task-executor.ts +1 -1
- package/src/multi-agent/types.ts +3 -0
- package/src/planning/planning-manager.ts +8 -0
- package/src/providers/anthropic.ts +70 -57
- package/src/providers/google.ts +1 -1
- package/src/providers/openai-codex.ts +7 -2
- package/src/providers/openai.ts +8 -3
- package/src/providers/shared.ts +11 -6
- package/src/session/session-manager.ts +15 -4
- package/src/task-graph/result-injection.ts +29 -0
- package/src/task-graph/task-graph.ts +298 -0
- package/src/task-graph/tools.ts +109 -0
- package/src/task-graph/types.ts +52 -0
- package/src/tools/executor.ts +2 -2
- package/src/tools/mcp-adapter.ts +23 -7
- package/src/utils/id.ts +3 -6
- package/src/wire/wire.ts +1 -1
package/src/core/agent.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { SkillSource } from "../skills/types.js";
|
|
|
22
22
|
import type { MCPServerConfig, MCPClient } from "../tools/mcp-adapter.js";
|
|
23
23
|
import type { TaskFactory } from "../background/types.js";
|
|
24
24
|
import type { PlanningConfig } from "../planning/types.js";
|
|
25
|
+
import type { TaskGraphConfig } from "../task-graph/types.js";
|
|
25
26
|
import { SessionManager } from "../session/session-manager.js";
|
|
26
27
|
import { CheckpointManager } from "../checkpoint/checkpoint-manager.js";
|
|
27
28
|
import { InjectionManager } from "../injection/injection-manager.js";
|
|
@@ -39,6 +40,10 @@ import { createBackgroundTaskTools } from "../background/tools.js";
|
|
|
39
40
|
import { PlanningManager } from "../planning/planning-manager.js";
|
|
40
41
|
import { createPlanningTools } from "../planning/tools.js";
|
|
41
42
|
import { PlanningNagProvider } from "../planning/nag-injection.js";
|
|
43
|
+
import { TaskGraph } from "../task-graph/task-graph.js";
|
|
44
|
+
import { createTaskGraphTools } from "../task-graph/tools.js";
|
|
45
|
+
import { TaskResultInjectionProvider } from "../task-graph/result-injection.js";
|
|
46
|
+
import { resolveProvider } from "../llm/provider.js";
|
|
42
47
|
import { runAgentLoop } from "./agent-loop.js";
|
|
43
48
|
|
|
44
49
|
export interface AgentConfig {
|
|
@@ -64,6 +69,7 @@ export interface AgentConfig {
|
|
|
64
69
|
mcp?: { servers: MCPServerConfig[]; clientFactory: (config: MCPServerConfig) => MCPClient };
|
|
65
70
|
wire?: { bufferSize?: number };
|
|
66
71
|
backgroundTasks?: { factories?: Record<string, TaskFactory> };
|
|
72
|
+
taskGraph?: TaskGraphConfig;
|
|
67
73
|
planning?: PlanningConfig;
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -89,6 +95,8 @@ export class Agent {
|
|
|
89
95
|
private _wire: Wire;
|
|
90
96
|
private _backgroundTaskManager: BackgroundTaskManager | undefined;
|
|
91
97
|
private _planningManager: PlanningManager | undefined;
|
|
98
|
+
private _taskGraph: TaskGraph;
|
|
99
|
+
private _createdSubagents: Agent[] = [];
|
|
92
100
|
private _initialized = false;
|
|
93
101
|
|
|
94
102
|
constructor(config: AgentConfig) {
|
|
@@ -156,6 +164,9 @@ export class Agent {
|
|
|
156
164
|
if (config.planning) {
|
|
157
165
|
this._planningManager = new PlanningManager(config.planning);
|
|
158
166
|
}
|
|
167
|
+
|
|
168
|
+
// Task graph
|
|
169
|
+
this._taskGraph = new TaskGraph(config.taskGraph ?? {});
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
// --- Public API ---
|
|
@@ -253,6 +264,10 @@ export class Agent {
|
|
|
253
264
|
return this._planningManager;
|
|
254
265
|
}
|
|
255
266
|
|
|
267
|
+
get taskGraph(): TaskGraph {
|
|
268
|
+
return this._taskGraph;
|
|
269
|
+
}
|
|
270
|
+
|
|
256
271
|
setSystemPrompt(prompt: string): void {
|
|
257
272
|
this._state.systemPrompt = prompt;
|
|
258
273
|
}
|
|
@@ -280,8 +295,12 @@ export class Agent {
|
|
|
280
295
|
this._listeners.clear();
|
|
281
296
|
this._steeringQueue = [];
|
|
282
297
|
this._followUpQueue = [];
|
|
298
|
+
await Promise.allSettled(this._createdSubagents.map((sub) => sub.dispose()));
|
|
299
|
+
this._createdSubagents = [];
|
|
300
|
+
this._approval.dispose();
|
|
283
301
|
await this._mcpAdapter?.dispose();
|
|
284
302
|
this._backgroundTaskManager?.dispose();
|
|
303
|
+
this._taskGraph.dispose();
|
|
285
304
|
this._wire.dispose();
|
|
286
305
|
}
|
|
287
306
|
|
|
@@ -289,7 +308,6 @@ export class Agent {
|
|
|
289
308
|
|
|
290
309
|
private async _ensureInitialized(): Promise<void> {
|
|
291
310
|
if (this._initialized) return;
|
|
292
|
-
this._initialized = true;
|
|
293
311
|
|
|
294
312
|
// Init session and restore messages
|
|
295
313
|
if (this._sessionManager) {
|
|
@@ -348,14 +366,17 @@ export class Agent {
|
|
|
348
366
|
// Init fixed subagents and create TaskTool
|
|
349
367
|
if (this._config.subagents && this._laborMarket && this._taskExecutor) {
|
|
350
368
|
for (const [name, subConfig] of Object.entries(this._config.subagents)) {
|
|
369
|
+
const subModel = subConfig.model ?? this._config.model;
|
|
370
|
+
const subProvider = subConfig.model ? resolveProvider(subConfig.model) : this._provider;
|
|
351
371
|
const subAgent = new Agent({
|
|
352
|
-
model:
|
|
372
|
+
model: subModel,
|
|
353
373
|
systemPrompt: subConfig.systemPrompt,
|
|
354
374
|
tools: subConfig.tools ?? [],
|
|
355
|
-
provider:
|
|
375
|
+
provider: subProvider,
|
|
356
376
|
approval: this._approval.share(),
|
|
357
377
|
name,
|
|
358
378
|
});
|
|
379
|
+
this._createdSubagents.push(subAgent);
|
|
359
380
|
this._laborMarket.addFixed(name, subAgent, subConfig.description);
|
|
360
381
|
}
|
|
361
382
|
// Add built-in TaskTool so LLM can delegate to subagents
|
|
@@ -373,12 +394,23 @@ export class Agent {
|
|
|
373
394
|
this._state.tools = [...this._state.tools, ...createPlanningTools(this._planningManager)];
|
|
374
395
|
|
|
375
396
|
// Register nag provider into injection manager (create one if needed)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
397
|
+
this._addInjectionProvider(new PlanningNagProvider(this._planningManager));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Task graph tools + result auto-injection
|
|
401
|
+
this._state.tools = [...this._state.tools, ...createTaskGraphTools(this._taskGraph)];
|
|
402
|
+
if (this._config.taskGraph?.autoInjectResults !== false) {
|
|
403
|
+
this._addInjectionProvider(new TaskResultInjectionProvider(this._taskGraph));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this._initialized = true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private _addInjectionProvider(provider: DynamicInjectionProvider): void {
|
|
410
|
+
if (this._injectionManager) {
|
|
411
|
+
this._injectionManager.addProvider(provider);
|
|
412
|
+
} else {
|
|
413
|
+
this._injectionManager = new InjectionManager([provider]);
|
|
382
414
|
}
|
|
383
415
|
}
|
|
384
416
|
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { SkillSource } from "./skills/types.js";
|
|
|
16
16
|
import type { MCPServerConfig, MCPClient } from "./tools/mcp-adapter.js";
|
|
17
17
|
import type { TaskFactory } from "./background/types.js";
|
|
18
18
|
import type { PlanningConfig } from "./planning/types.js";
|
|
19
|
+
import type { TaskGraphConfig } from "./task-graph/types.js";
|
|
19
20
|
|
|
20
21
|
export interface CreateAgentConfig {
|
|
21
22
|
// Required
|
|
@@ -43,6 +44,7 @@ export interface CreateAgentConfig {
|
|
|
43
44
|
wire?: { bufferSize?: number };
|
|
44
45
|
backgroundTasks?: { factories?: Record<string, TaskFactory> };
|
|
45
46
|
planning?: PlanningConfig;
|
|
47
|
+
taskGraph?: TaskGraphConfig;
|
|
46
48
|
|
|
47
49
|
// Advanced: provide your own LLM provider
|
|
48
50
|
provider?: LLMProvider;
|
|
@@ -74,6 +76,7 @@ export function createAgent(config: CreateAgentConfig): Agent {
|
|
|
74
76
|
wire: config.wire,
|
|
75
77
|
backgroundTasks: config.backgroundTasks,
|
|
76
78
|
planning: config.planning,
|
|
79
|
+
taskGraph: config.taskGraph,
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -107,6 +110,9 @@ export { createBackgroundTaskTools } from "./background/tools.js";
|
|
|
107
110
|
export { PlanningManager } from "./planning/planning-manager.js";
|
|
108
111
|
export { createPlanningTools } from "./planning/tools.js";
|
|
109
112
|
export { PlanningNagProvider } from "./planning/nag-injection.js";
|
|
113
|
+
export { TaskGraph } from "./task-graph/task-graph.js";
|
|
114
|
+
export { createTaskGraphTools } from "./task-graph/tools.js";
|
|
115
|
+
export { TaskResultInjectionProvider } from "./task-graph/result-injection.js";
|
|
110
116
|
|
|
111
117
|
// Core types
|
|
112
118
|
export type {
|
|
@@ -243,3 +249,12 @@ export type {
|
|
|
243
249
|
} from "./planning/types.js";
|
|
244
250
|
|
|
245
251
|
export { PLANNING_TOOL_NAMES } from "./planning/types.js";
|
|
252
|
+
export { TASK_GRAPH_TOOL_NAMES } from "./task-graph/types.js";
|
|
253
|
+
|
|
254
|
+
// Task graph types
|
|
255
|
+
export type {
|
|
256
|
+
TaskGraphConfig,
|
|
257
|
+
TaskNode,
|
|
258
|
+
TaskStatus,
|
|
259
|
+
CompletedTaskResult,
|
|
260
|
+
} from "./task-graph/types.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// History normalizer — merge adjacent user messages
|
|
2
2
|
|
|
3
|
-
import type { AgentMessage, Message } from "../types.js";
|
|
3
|
+
import type { AgentMessage, Message, ContentBlock } from "../types.js";
|
|
4
4
|
|
|
5
5
|
export function normalizeHistory(messages: AgentMessage[]): AgentMessage[] {
|
|
6
6
|
if (messages.length <= 1) return messages;
|
|
@@ -23,17 +23,27 @@ export function normalizeHistory(messages: AgentMessage[]): AgentMessage[] {
|
|
|
23
23
|
(prev as Message).role === "user"
|
|
24
24
|
) {
|
|
25
25
|
const prevMsg = prev as Message & { role: "user" };
|
|
26
|
-
const
|
|
27
|
-
? prevMsg.content
|
|
28
|
-
: prevMsg.content
|
|
29
|
-
const
|
|
30
|
-
? current.content
|
|
31
|
-
: current.content
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
const prevBlocks = typeof prevMsg.content === "string"
|
|
27
|
+
? [{ type: "text" as const, text: prevMsg.content }]
|
|
28
|
+
: prevMsg.content;
|
|
29
|
+
const currentBlocks = typeof current.content === "string"
|
|
30
|
+
? [{ type: "text" as const, text: current.content }]
|
|
31
|
+
: current.content;
|
|
32
|
+
|
|
33
|
+
const merged: ContentBlock[] = [...prevBlocks, ...currentBlocks];
|
|
34
|
+
|
|
35
|
+
// Optimize: if all blocks are text, collapse to a single string
|
|
36
|
+
if (merged.every((b) => b.type === "text")) {
|
|
37
|
+
result[result.length - 1] = {
|
|
38
|
+
role: "user",
|
|
39
|
+
content: merged.map((b) => (b as { text: string }).text).join("\n"),
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
result[result.length - 1] = {
|
|
43
|
+
role: "user",
|
|
44
|
+
content: merged,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
37
47
|
continue;
|
|
38
48
|
}
|
|
39
49
|
|
package/src/llm/types.ts
CHANGED
|
@@ -76,6 +76,8 @@ export interface TextBlock {
|
|
|
76
76
|
export interface ThinkingBlock {
|
|
77
77
|
type: "thinking";
|
|
78
78
|
thinking: string;
|
|
79
|
+
/** Opaque signature returned by the provider; must be echoed back in subsequent requests. */
|
|
80
|
+
signature?: string;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
export type AssistantContentBlock = TextBlock | ToolCallBlock | ThinkingBlock;
|
|
@@ -4,7 +4,7 @@ import type { Agent } from "../core/agent.js";
|
|
|
4
4
|
import type { LaborMarket } from "./labor-market.js";
|
|
5
5
|
import type { AgentMessage, AgentEvent, AssistantMessage } from "../types.js";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
interface TaskResult {
|
|
8
8
|
messages: AgentMessage[];
|
|
9
9
|
lastAssistantMessage?: AssistantMessage;
|
|
10
10
|
}
|
package/src/multi-agent/types.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// Multi-agent types
|
|
2
2
|
|
|
3
3
|
import type { AgentTool } from "../tools/types.js";
|
|
4
|
+
import type { ModelConfig } from "../llm/types.js";
|
|
4
5
|
|
|
5
6
|
export interface SubagentConfig {
|
|
6
7
|
name: string;
|
|
7
8
|
systemPrompt: string | (() => string | Promise<string>);
|
|
8
9
|
tools?: AgentTool[];
|
|
9
10
|
description: string;
|
|
11
|
+
/** Override the parent agent's model config. If omitted, inherits the parent's model. */
|
|
12
|
+
model?: ModelConfig;
|
|
10
13
|
}
|
|
@@ -4,6 +4,9 @@ import type { TodoItem, TodoStatus, PlanPhase, PlanningState, PlanningConfig } f
|
|
|
4
4
|
import { PLANNING_TOOL_NAMES } from "./types.js";
|
|
5
5
|
import { generateId } from "../utils/id.js";
|
|
6
6
|
|
|
7
|
+
/** Number of built-in tools always added to allowedInPlanning (todo + plan_mode). */
|
|
8
|
+
const BUILT_IN_PLANNING_TOOL_COUNT = Object.keys(PLANNING_TOOL_NAMES).length;
|
|
9
|
+
|
|
7
10
|
export class PlanningManager {
|
|
8
11
|
private _state: PlanningState;
|
|
9
12
|
private _config: PlanningConfig;
|
|
@@ -43,6 +46,11 @@ export class PlanningManager {
|
|
|
43
46
|
return this._allowedInPlanning;
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/** Whether user configured read-only tools beyond the built-in planning tools. */
|
|
50
|
+
get hasConfiguredReadOnlyTools(): boolean {
|
|
51
|
+
return this._allowedInPlanning.size > BUILT_IN_PLANNING_TOOL_COUNT;
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
// --- Phase control ---
|
|
47
55
|
|
|
48
56
|
startExecution(): string {
|
|
@@ -11,6 +11,12 @@ import type {
|
|
|
11
11
|
} from "../llm/types.js";
|
|
12
12
|
import { withRetry, RETRYABLE_PATTERNS, mapThinkingBudget } from "./shared.js";
|
|
13
13
|
|
|
14
|
+
// Anthropic SDK type extension not yet in published typings
|
|
15
|
+
interface ContentBlockDeltaSignature {
|
|
16
|
+
type: "signature_delta";
|
|
17
|
+
signature: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
export class AnthropicProvider implements LLMProvider {
|
|
15
21
|
private client: Anthropic;
|
|
16
22
|
|
|
@@ -72,70 +78,77 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
72
78
|
const stream = this.client.messages.stream(params, { signal });
|
|
73
79
|
|
|
74
80
|
for await (const event of stream) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
yield { type: "text", text: delta.text };
|
|
94
|
-
} else if (delta.type === "input_json_delta") {
|
|
95
|
-
const buf = (toolInputBuffers.get(event.index) ?? "") + delta.partial_json;
|
|
96
|
-
toolInputBuffers.set(event.index, buf);
|
|
97
|
-
const block = contentBlocks[event.index];
|
|
98
|
-
if (block && block.type === "tool_call") {
|
|
99
|
-
yield { type: "tool_call_delta", id: block.id, input: delta.partial_json };
|
|
100
|
-
}
|
|
101
|
-
} else if (delta.type === "thinking_delta") {
|
|
102
|
-
const block = contentBlocks[event.index];
|
|
103
|
-
if (block && block.type === "thinking") {
|
|
104
|
-
block.thinking += delta.thinking;
|
|
105
|
-
}
|
|
106
|
-
yield { type: "thinking", thinking: delta.thinking };
|
|
81
|
+
if (event.type === "content_block_start") {
|
|
82
|
+
const block = event.content_block;
|
|
83
|
+
if (block.type === "text") {
|
|
84
|
+
contentBlocks.push({ type: "text", text: "" });
|
|
85
|
+
} else if (block.type === "tool_use") {
|
|
86
|
+
contentBlocks.push({ type: "tool_call", id: block.id, name: block.name, input: {} });
|
|
87
|
+
toolInputBuffers.set(event.index, "");
|
|
88
|
+
yield { type: "tool_call_start", id: block.id, name: block.name };
|
|
89
|
+
} else if (block.type === "thinking") {
|
|
90
|
+
contentBlocks.push({ type: "thinking", thinking: "" });
|
|
91
|
+
}
|
|
92
|
+
} else if (event.type === "content_block_delta") {
|
|
93
|
+
const delta = event.delta;
|
|
94
|
+
if (delta.type === "text_delta") {
|
|
95
|
+
const block = contentBlocks[event.index];
|
|
96
|
+
if (block && block.type === "text") {
|
|
97
|
+
block.text += delta.text;
|
|
107
98
|
}
|
|
108
|
-
|
|
99
|
+
yield { type: "text", text: delta.text };
|
|
100
|
+
} else if (delta.type === "input_json_delta") {
|
|
101
|
+
const buf = (toolInputBuffers.get(event.index) ?? "") + delta.partial_json;
|
|
102
|
+
toolInputBuffers.set(event.index, buf);
|
|
109
103
|
const block = contentBlocks[event.index];
|
|
110
104
|
if (block && block.type === "tool_call") {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
toolInputBuffers.delete(event.index);
|
|
105
|
+
yield { type: "tool_call_delta", id: block.id, input: delta.partial_json };
|
|
106
|
+
}
|
|
107
|
+
} else if (delta.type === "thinking_delta") {
|
|
108
|
+
const block = contentBlocks[event.index];
|
|
109
|
+
if (block && block.type === "thinking") {
|
|
110
|
+
block.thinking += delta.thinking;
|
|
118
111
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
};
|
|
112
|
+
yield { type: "thinking", thinking: delta.thinking };
|
|
113
|
+
} else if ((delta as unknown as ContentBlockDeltaSignature).type === "signature_delta") {
|
|
114
|
+
const sigDelta = delta as unknown as ContentBlockDeltaSignature;
|
|
115
|
+
const block = contentBlocks[event.index];
|
|
116
|
+
if (block && block.type === "thinking") {
|
|
117
|
+
block.signature = (block.signature ?? "") + sigDelta.signature;
|
|
126
118
|
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
119
|
+
}
|
|
120
|
+
} else if (event.type === "content_block_stop") {
|
|
121
|
+
const block = contentBlocks[event.index];
|
|
122
|
+
if (block && block.type === "tool_call") {
|
|
123
|
+
const buf = toolInputBuffers.get(event.index) ?? "{}";
|
|
124
|
+
try {
|
|
125
|
+
block.input = JSON.parse(buf || "{}");
|
|
126
|
+
} catch {
|
|
127
|
+
block.input = {};
|
|
136
128
|
}
|
|
129
|
+
toolInputBuffers.delete(event.index);
|
|
130
|
+
}
|
|
131
|
+
} else if (event.type === "message_delta") {
|
|
132
|
+
if (event.usage) {
|
|
133
|
+
usage = {
|
|
134
|
+
inputTokens: usage.inputTokens,
|
|
135
|
+
outputTokens: event.usage.output_tokens,
|
|
136
|
+
totalTokens: usage.inputTokens + event.usage.output_tokens,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
} else if (event.type === "message_start") {
|
|
140
|
+
if (event.message.usage) {
|
|
141
|
+
const u = event.message.usage;
|
|
142
|
+
usage = {
|
|
143
|
+
inputTokens: u.input_tokens,
|
|
144
|
+
outputTokens: u.output_tokens,
|
|
145
|
+
totalTokens: u.input_tokens + u.output_tokens,
|
|
146
|
+
cacheReadTokens: u.cache_read_input_tokens ?? undefined,
|
|
147
|
+
cacheWriteTokens: u.cache_creation_input_tokens ?? undefined,
|
|
148
|
+
};
|
|
137
149
|
}
|
|
138
150
|
}
|
|
151
|
+
}
|
|
139
152
|
|
|
140
153
|
const message: AssistantMessage = { role: "assistant", content: contentBlocks };
|
|
141
154
|
yield { type: "done", message, usage };
|
|
@@ -179,7 +192,7 @@ function mapAssistantBlock(block: AssistantContentBlock): Anthropic.ContentBlock
|
|
|
179
192
|
return { type: "tool_use", id: block.id, name: block.name, input: block.input };
|
|
180
193
|
}
|
|
181
194
|
if (block.type === "thinking") {
|
|
182
|
-
return { type: "thinking", thinking: block.thinking, signature: "" };
|
|
195
|
+
return { type: "thinking", thinking: block.thinking, signature: block.signature ?? "" };
|
|
183
196
|
}
|
|
184
197
|
return { type: "text", text: JSON.stringify(block) };
|
|
185
198
|
}
|
package/src/providers/google.ts
CHANGED
|
@@ -49,7 +49,7 @@ export class GeminiProvider implements LLMProvider {
|
|
|
49
49
|
maxOutputTokens: maxTokens ?? 8192,
|
|
50
50
|
...(thinkingBudget ? {
|
|
51
51
|
thinkingConfig: { thinkingBudget },
|
|
52
|
-
} as
|
|
52
|
+
} as Record<string, unknown> : {}),
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
this.baseUrl ? { baseUrl: this.baseUrl } : undefined,
|
|
@@ -291,6 +291,8 @@ function mapReasoningEffort(modelId: string, level?: ThinkingLevel): string | un
|
|
|
291
291
|
|
|
292
292
|
// --- SSE parsing ---
|
|
293
293
|
|
|
294
|
+
const MAX_SSE_BUFFER_BYTES = 1024 * 1024; // 1MB cap to prevent unbounded accumulation
|
|
295
|
+
|
|
294
296
|
async function* parseSSE(response: Response): AsyncGenerator<Record<string, unknown>> {
|
|
295
297
|
if (!response.body) return;
|
|
296
298
|
|
|
@@ -304,6 +306,10 @@ async function* parseSSE(response: Response): AsyncGenerator<Record<string, unkn
|
|
|
304
306
|
if (done) break;
|
|
305
307
|
buffer += decoder.decode(value, { stream: true });
|
|
306
308
|
|
|
309
|
+
if (buffer.length > MAX_SSE_BUFFER_BYTES) {
|
|
310
|
+
throw new Error(`SSE buffer exceeded ${MAX_SSE_BUFFER_BYTES} bytes — aborting to prevent unbounded memory growth`);
|
|
311
|
+
}
|
|
312
|
+
|
|
307
313
|
let idx = buffer.indexOf("\n\n");
|
|
308
314
|
while (idx !== -1) {
|
|
309
315
|
const chunk = buffer.slice(0, idx);
|
|
@@ -326,8 +332,7 @@ async function* parseSSE(response: Response): AsyncGenerator<Record<string, unkn
|
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
} finally {
|
|
329
|
-
|
|
330
|
-
try { reader.releaseLock(); } catch { /* ignore */ }
|
|
335
|
+
reader.cancel().catch(() => {});
|
|
331
336
|
}
|
|
332
337
|
}
|
|
333
338
|
|
package/src/providers/openai.ts
CHANGED
|
@@ -8,11 +8,15 @@ import type {
|
|
|
8
8
|
AssistantMessage,
|
|
9
9
|
AssistantContentBlock,
|
|
10
10
|
TokenUsage,
|
|
11
|
-
ThinkingLevel,
|
|
12
11
|
Message,
|
|
13
12
|
} from "../llm/types.js";
|
|
14
13
|
import { withRetry, RETRYABLE_PATTERNS, mapReasoningEffort } from "./shared.js";
|
|
15
14
|
|
|
15
|
+
// OpenAI o1/o3 reasoning content not yet in published typings
|
|
16
|
+
interface DeltaWithReasoning {
|
|
17
|
+
reasoning_content?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
export class OpenAIProvider implements LLMProvider {
|
|
17
21
|
private client: OpenAI;
|
|
18
22
|
|
|
@@ -75,8 +79,9 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
// Reasoning/thinking content (o1/o3 series)
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
const reasoningContent = (delta as unknown as DeltaWithReasoning).reasoning_content;
|
|
83
|
+
if (reasoningContent) {
|
|
84
|
+
const thinking = reasoningContent;
|
|
80
85
|
if (contentBlocks.length === 0 || contentBlocks[contentBlocks.length - 1].type !== "thinking") {
|
|
81
86
|
contentBlocks.push({ type: "thinking", thinking: "" });
|
|
82
87
|
}
|
package/src/providers/shared.ts
CHANGED
|
@@ -12,13 +12,14 @@ export const RETRYABLE_PATTERNS: Record<string, string[]> = {
|
|
|
12
12
|
codex: [...COMMON_RETRYABLE, "rate_limit", "usage_limit", "overloaded"],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
function isRetryableError(error: Error, patterns: string[]): boolean {
|
|
16
16
|
return patterns.some((p) => error.message.includes(p));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Wraps a streaming generator with exponential backoff retry logic.
|
|
21
|
-
* Only retries on connection-level failures before
|
|
21
|
+
* Only retries on connection-level failures before any events have been yielded.
|
|
22
|
+
* Once streaming starts (first event yielded), errors are not retried to avoid duplicate output.
|
|
22
23
|
*/
|
|
23
24
|
export async function* withRetry(
|
|
24
25
|
streamOnce: () => AsyncIterable<AssistantMessageEvent>,
|
|
@@ -33,15 +34,19 @@ export async function* withRetry(
|
|
|
33
34
|
await new Promise((r) => setTimeout(r, delay));
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
let hasYielded = false;
|
|
36
38
|
try {
|
|
37
|
-
|
|
39
|
+
for await (const event of streamOnce()) {
|
|
40
|
+
hasYielded = true;
|
|
41
|
+
yield event;
|
|
42
|
+
}
|
|
38
43
|
return;
|
|
39
44
|
} catch (err) {
|
|
40
45
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
// If we already yielded events, don't retry — caller has consumed partial output
|
|
48
|
+
if (hasYielded || !isRetryableError(lastError, retryablePatterns) || attempt === maxRetries) {
|
|
49
|
+
throw lastError;
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
}
|
|
@@ -247,12 +247,23 @@ export class SessionManager {
|
|
|
247
247
|
this._sessionId = (header as SessionHeader).id;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
// Rest are entries
|
|
250
|
+
// Rest are entries — validate required fields before accepting
|
|
251
251
|
for (let i = 1; i < records.length; i++) {
|
|
252
|
-
const entry = records[i]
|
|
253
|
-
this.
|
|
254
|
-
this.
|
|
252
|
+
const entry = records[i];
|
|
253
|
+
if (!this._isValidEntry(entry)) continue;
|
|
254
|
+
this._entries.push(entry as SessionEntry);
|
|
255
|
+
this._entriesById.set(entry.id, entry as SessionEntry);
|
|
255
256
|
this._leafId = entry.id;
|
|
256
257
|
}
|
|
257
258
|
}
|
|
259
|
+
|
|
260
|
+
private _isValidEntry(record: unknown): record is SessionEntry {
|
|
261
|
+
if (!record || typeof record !== "object") return false;
|
|
262
|
+
const r = record as Record<string, unknown>;
|
|
263
|
+
return (
|
|
264
|
+
typeof r.type === "string" &&
|
|
265
|
+
typeof r.id === "string" &&
|
|
266
|
+
typeof r.timestamp === "string"
|
|
267
|
+
);
|
|
268
|
+
}
|
|
258
269
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Auto-inject completed background task results before each LLM call
|
|
2
|
+
|
|
3
|
+
import type { DynamicInjectionProvider, DynamicInjection } from "../injection/types.js";
|
|
4
|
+
import type { AgentMessage } from "../types.js";
|
|
5
|
+
import type { TaskGraph } from "./task-graph.js";
|
|
6
|
+
|
|
7
|
+
export class TaskResultInjectionProvider implements DynamicInjectionProvider {
|
|
8
|
+
constructor(private _graph: TaskGraph) {}
|
|
9
|
+
|
|
10
|
+
async getInjections(_history: AgentMessage[]): Promise<DynamicInjection[]> {
|
|
11
|
+
const completed = this._graph.drainCompleted();
|
|
12
|
+
if (completed.length === 0) return [];
|
|
13
|
+
|
|
14
|
+
const lines = completed.map((c) => {
|
|
15
|
+
const status = c.status === "completed" ? "completed" : "FAILED";
|
|
16
|
+
const unblocked = c.unblockedTasks.length > 0
|
|
17
|
+
? ` → unblocked: ${c.unblockedTasks.join(", ")}`
|
|
18
|
+
: "";
|
|
19
|
+
return `[task:${c.taskId}] ${c.subject} — ${status}: ${c.result}${unblocked}`;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
type: "task-results",
|
|
25
|
+
content: `<background-results>\n${lines.join("\n")}\n</background-results>`,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
}
|