@pencil-agent/nano-pencil 2.0.0-beta.2 → 2.0.0-beta.3
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/build-meta.json +3 -3
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-continuations.d.ts +17 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-continuations.js +60 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-stream-events.d.ts +19 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-stream-events.js +55 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-tool-results.d.ts +10 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-tool-results.js +137 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-tool-summaries.d.ts +22 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop-tool-summaries.js +64 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop.d.ts +26 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop.js +913 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-run-result.d.ts +9 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-run-result.js +32 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent.d.ts +215 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent.js +522 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/errors.d.ts +62 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/errors.js +146 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/index.d.ts +14 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/index.js +19 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/proxy.d.ts +91 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/proxy.js +279 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-agent-loop.d.ts +15 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-agent-loop.js +625 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-streaming-tool-executor.d.ts +33 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-streaming-tool-executor.js +189 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-tool-orchestration.d.ts +35 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-tool-orchestration.js +319 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/types.d.ts +417 -0
- package/dist/node_modules/@pencil-agent/agent-core/dist/types.js +13 -0
- package/dist/node_modules/@pencil-agent/agent-core/package.json +28 -0
- package/dist/node_modules/@pencil-agent/ai/dist/api-registry.d.ts +27 -0
- package/dist/node_modules/@pencil-agent/ai/dist/api-registry.js +152 -0
- package/dist/node_modules/@pencil-agent/ai/dist/cli.d.ts +2 -0
- package/dist/node_modules/@pencil-agent/ai/dist/cli.js +121 -0
- package/dist/node_modules/@pencil-agent/ai/dist/config-path.d.ts +1 -0
- package/dist/node_modules/@pencil-agent/ai/dist/config-path.js +17 -0
- package/dist/node_modules/@pencil-agent/ai/dist/debug-logger.d.ts +94 -0
- package/dist/node_modules/@pencil-agent/ai/dist/debug-logger.js +218 -0
- package/dist/node_modules/@pencil-agent/ai/dist/env-api-keys.d.ts +8 -0
- package/dist/node_modules/@pencil-agent/ai/dist/env-api-keys.js +107 -0
- package/dist/node_modules/@pencil-agent/ai/dist/env.d.ts +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/env.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/events.d.ts +8 -0
- package/dist/node_modules/@pencil-agent/ai/dist/events.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/index.d.ts +27 -0
- package/dist/node_modules/@pencil-agent/ai/dist/index.js +20 -0
- package/dist/node_modules/@pencil-agent/ai/dist/json.d.ts +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/json.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.d.ts +31 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.generated.d.ts +15159 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +14928 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.js +60 -0
- package/dist/node_modules/@pencil-agent/ai/dist/overflow.d.ts +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/overflow.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/amazon-bedrock.d.ts +20 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/amazon-bedrock.js +606 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/anthropic.d.ts +38 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/anthropic.js +737 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/azure-openai-responses.d.ts +21 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/azure-openai-responses.js +193 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/github-copilot-headers.d.ts +13 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/github-copilot-headers.js +34 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google-gemini-cli.d.ts +79 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google-gemini-cli.js +753 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google-shared.d.ts +70 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google-shared.js +311 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google-vertex.d.ts +20 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google-vertex.js +380 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google.d.ts +18 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/google.js +360 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-codex-responses.d.ts +8 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-codex-responses.js +704 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-completions.d.ts +20 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-completions.js +870 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses-shared.d.ts +22 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses-shared.js +432 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses.d.ts +19 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses.js +207 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/register-builtins.d.ts +8 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/register-builtins.js +86 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/simple-options.d.ts +13 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/simple-options.js +40 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/transform-messages.d.ts +13 -0
- package/dist/node_modules/@pencil-agent/ai/dist/providers/transform-messages.js +175 -0
- package/dist/node_modules/@pencil-agent/ai/dist/registry.d.ts +8 -0
- package/dist/node_modules/@pencil-agent/ai/dist/registry.js +8 -0
- package/dist/node_modules/@pencil-agent/ai/dist/schema.d.ts +10 -0
- package/dist/node_modules/@pencil-agent/ai/dist/schema.js +9 -0
- package/dist/node_modules/@pencil-agent/ai/dist/stream.d.ts +25 -0
- package/dist/node_modules/@pencil-agent/ai/dist/stream.js +324 -0
- package/dist/node_modules/@pencil-agent/ai/dist/types.d.ts +306 -0
- package/dist/node_modules/@pencil-agent/ai/dist/types.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/event-stream-types.d.ts +12 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/event-stream-types.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/event-stream.d.ts +31 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/event-stream.js +98 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/http-proxy.d.ts +13 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/http-proxy.js +20 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/json-parse.d.ts +14 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/json-parse.js +34 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/anthropic.d.ts +22 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/anthropic.js +109 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/decode-credential.d.ts +12 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/decode-credential.js +25 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/github-copilot.d.ts +35 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/github-copilot.js +286 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/google-antigravity.d.ts +31 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/google-antigravity.js +378 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/google-gemini-cli.d.ts +31 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/google-gemini-cli.js +483 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/index.d.ts +60 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/index.js +131 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/openai-codex.d.ts +39 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/openai-codex.js +385 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/pkce.d.ts +18 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/pkce.js +36 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/types.d.ts +52 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/oauth/types.js +7 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/overflow.d.ts +57 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/overflow.js +120 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/sanitize-unicode.d.ts +16 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/sanitize-unicode.js +20 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/typebox-helpers.d.ts +22 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/typebox-helpers.js +26 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/validation.d.ts +23 -0
- package/dist/node_modules/@pencil-agent/ai/dist/utils/validation.js +78 -0
- package/dist/node_modules/@pencil-agent/ai/package.json +94 -0
- package/dist/node_modules/@pencil-agent/tui/dist/autocomplete.d.ts +62 -0
- package/dist/node_modules/@pencil-agent/tui/dist/autocomplete.js +624 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/box.d.ts +27 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/box.js +109 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/cancellable-loader.d.ts +27 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/cancellable-loader.js +40 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/editor.d.ts +218 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/editor.js +1697 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/image.d.ts +33 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/image.js +74 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/input.d.ts +42 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/input.js +438 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/loader.d.ts +26 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/loader.js +54 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/markdown.d.ts +100 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/markdown.js +634 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/select-list.d.ts +37 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/select-list.js +157 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/settings-list.d.ts +55 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/settings-list.js +190 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/spacer.d.ts +17 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/spacer.js +28 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/text.d.ts +24 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/text.js +94 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/truncated-text.d.ts +18 -0
- package/dist/node_modules/@pencil-agent/tui/dist/components/truncated-text.js +56 -0
- package/dist/node_modules/@pencil-agent/tui/dist/editor-component.d.ts +51 -0
- package/dist/node_modules/@pencil-agent/tui/dist/editor-component.js +7 -0
- package/dist/node_modules/@pencil-agent/tui/dist/fuzzy.d.ts +32 -0
- package/dist/node_modules/@pencil-agent/tui/dist/fuzzy.js +152 -0
- package/dist/node_modules/@pencil-agent/tui/dist/index.d.ts +28 -0
- package/dist/node_modules/@pencil-agent/tui/dist/index.js +37 -0
- package/dist/node_modules/@pencil-agent/tui/dist/keybindings.d.ts +44 -0
- package/dist/node_modules/@pencil-agent/tui/dist/keybindings.js +119 -0
- package/dist/node_modules/@pencil-agent/tui/dist/keys.d.ts +149 -0
- package/dist/node_modules/@pencil-agent/tui/dist/keys.js +948 -0
- package/dist/node_modules/@pencil-agent/tui/dist/kill-ring.d.ts +33 -0
- package/dist/node_modules/@pencil-agent/tui/dist/kill-ring.js +49 -0
- package/dist/node_modules/@pencil-agent/tui/dist/stdin-buffer.d.ts +38 -0
- package/dist/node_modules/@pencil-agent/tui/dist/stdin-buffer.js +307 -0
- package/dist/node_modules/@pencil-agent/tui/dist/terminal-image.d.ts +73 -0
- package/dist/node_modules/@pencil-agent/tui/dist/terminal-image.js +287 -0
- package/dist/node_modules/@pencil-agent/tui/dist/terminal.d.ts +86 -0
- package/dist/node_modules/@pencil-agent/tui/dist/terminal.js +266 -0
- package/dist/node_modules/@pencil-agent/tui/dist/tui.d.ts +219 -0
- package/dist/node_modules/@pencil-agent/tui/dist/tui.js +1001 -0
- package/dist/node_modules/@pencil-agent/tui/dist/undo-stack.d.ts +22 -0
- package/dist/node_modules/@pencil-agent/tui/dist/undo-stack.js +30 -0
- package/dist/node_modules/@pencil-agent/tui/dist/utils.d.ts +83 -0
- package/dist/node_modules/@pencil-agent/tui/dist/utils.js +811 -0
- package/dist/node_modules/@pencil-agent/tui/package.json +37 -0
- package/package.json +3 -2
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured-adaptive agent loop for nanoPencil.
|
|
3
|
+
* Keeps the public AgentMessage/EventStream contract while using a query-loop
|
|
4
|
+
* state machine with ordered tool-result pairing and safe tool batching.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* [WHO]: structuredAdaptiveAgentLoop, structuredAdaptiveAgentLoopContinue
|
|
8
|
+
* [FROM]: Depends on @pencil-agent/ai, ./types, ./errors, structured-adaptive tool executors, and shared loop helpers.
|
|
9
|
+
* [TO]: Consumed by core/lib/agent-core/src/agent.ts and index.ts
|
|
10
|
+
* [HERE]: core/lib/agent-core/src/structured-adaptive-agent-loop.ts - selectable structured-adaptive query loop framework with recovered-error tombstoning
|
|
11
|
+
*/
|
|
12
|
+
import { EventStream } from "@pencil-agent/ai/events";
|
|
13
|
+
import { isContextOverflow } from "@pencil-agent/ai/overflow";
|
|
14
|
+
import { streamSimple } from "@pencil-agent/ai/stream";
|
|
15
|
+
import { resolveMaxToolConcurrency, runStructuredAdaptiveTools, } from "./structured-adaptive-tool-orchestration.js";
|
|
16
|
+
import { StructuredAdaptiveStreamingToolExecutor } from "./structured-adaptive-streaming-tool-executor.js";
|
|
17
|
+
import { ValidationError } from "./errors.js";
|
|
18
|
+
import { computeRecoveryMaxTokens, createOutputTokenRecoveryMessage, createTokenBudgetContinuation, } from "./agent-loop-continuations.js";
|
|
19
|
+
import { createInterruptedToolResults, createSkippedToolCallLimitResults, enforceToolResultBatchSize, } from "./agent-loop-tool-results.js";
|
|
20
|
+
import { flushReadyToolUseSummaries, startToolUseSummary, } from "./agent-loop-tool-summaries.js";
|
|
21
|
+
import { buildAgentRunPolicy, resolveAgentRunLoopFramework } from "./agent-run-result.js";
|
|
22
|
+
import { waitForAbortableOperation, waitForAssistantStream, waitForAssistantStreamEvent, } from "./agent-loop-stream-events.js";
|
|
23
|
+
const DEFAULT_MAX_TURNS_PER_PROMPT = 64;
|
|
24
|
+
const DEFAULT_MAX_TOOL_CALLS_PER_PROMPT = 128;
|
|
25
|
+
const DEFAULT_MAX_OUTPUT_TOKEN_RECOVERY_ATTEMPTS = 1;
|
|
26
|
+
const DEFAULT_MAX_STOP_HOOK_CONTINUATIONS = 3;
|
|
27
|
+
const DEFAULT_MAX_MODEL_ERROR_RECOVERY_ATTEMPTS = 1;
|
|
28
|
+
export function structuredAdaptiveAgentLoop(prompts, context, config, signal, streamFn) {
|
|
29
|
+
const stream = createAgentStream();
|
|
30
|
+
(async () => {
|
|
31
|
+
const newMessages = [...prompts];
|
|
32
|
+
const currentContext = {
|
|
33
|
+
...context,
|
|
34
|
+
messages: [...context.messages, ...prompts],
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
stream.push({ type: "agent_start" });
|
|
38
|
+
stream.push({ type: "turn_start" });
|
|
39
|
+
for (const prompt of prompts) {
|
|
40
|
+
stream.push({ type: "message_start", message: prompt });
|
|
41
|
+
stream.push({ type: "message_end", message: prompt });
|
|
42
|
+
}
|
|
43
|
+
await runStructuredAdaptiveQueryLoop(currentContext, newMessages, config, signal, stream, streamFn);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
endWithLoopError(stream, newMessages, config, error, signal);
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
return stream;
|
|
50
|
+
}
|
|
51
|
+
export function structuredAdaptiveAgentLoopContinue(context, config, signal, streamFn) {
|
|
52
|
+
if (context.messages.length === 0) {
|
|
53
|
+
throw new ValidationError("Cannot continue: no messages in context");
|
|
54
|
+
}
|
|
55
|
+
if (context.messages[context.messages.length - 1].role === "assistant") {
|
|
56
|
+
throw new ValidationError("Cannot continue from message role: assistant");
|
|
57
|
+
}
|
|
58
|
+
const stream = createAgentStream();
|
|
59
|
+
(async () => {
|
|
60
|
+
const newMessages = [];
|
|
61
|
+
const currentContext = { ...context };
|
|
62
|
+
try {
|
|
63
|
+
stream.push({ type: "agent_start" });
|
|
64
|
+
stream.push({ type: "turn_start" });
|
|
65
|
+
await runStructuredAdaptiveQueryLoop(currentContext, newMessages, config, signal, stream, streamFn);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
endWithLoopError(stream, newMessages, config, error, signal);
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
return stream;
|
|
72
|
+
}
|
|
73
|
+
function createAgentStream() {
|
|
74
|
+
return new EventStream((event) => event.type === "agent_end", (event) => (event.type === "agent_end" ? event.messages : []));
|
|
75
|
+
}
|
|
76
|
+
async function runStructuredAdaptiveQueryLoop(currentContext, newMessages, config, signal, stream, streamFn) {
|
|
77
|
+
const maxTurns = config.maxTurnsPerPrompt ?? DEFAULT_MAX_TURNS_PER_PROMPT;
|
|
78
|
+
const maxToolCalls = config.maxToolCallsPerPrompt ?? DEFAULT_MAX_TOOL_CALLS_PER_PROMPT;
|
|
79
|
+
const maxOutputTokenRecoveryAttempts = config.maxOutputTokenRecoveryAttempts ?? DEFAULT_MAX_OUTPUT_TOKEN_RECOVERY_ATTEMPTS;
|
|
80
|
+
const maxStopHookContinuations = config.maxStopHookContinuations ?? DEFAULT_MAX_STOP_HOOK_CONTINUATIONS;
|
|
81
|
+
const maxModelErrorRecoveryAttempts = config.maxModelErrorRecoveryAttempts ?? DEFAULT_MAX_MODEL_ERROR_RECOVERY_ATTEMPTS;
|
|
82
|
+
const initialSteeringMessages = await waitForAbortableOperation(config.getSteeringMessages ? config.getSteeringMessages() : [], signal);
|
|
83
|
+
const state = {
|
|
84
|
+
config,
|
|
85
|
+
turnCount: 0,
|
|
86
|
+
toolCallCount: 0,
|
|
87
|
+
transition: { reason: "start" },
|
|
88
|
+
transitions: [],
|
|
89
|
+
pendingMessages: initialSteeringMessages.type === "resolved" ? initialSteeringMessages.value || [] : [],
|
|
90
|
+
pendingToolUseSummaries: [],
|
|
91
|
+
stopHookActive: false,
|
|
92
|
+
stopHookContinuationCount: 0,
|
|
93
|
+
maxOutputTokensRecoveryCount: 0,
|
|
94
|
+
modelErrorRecoveryCount: 0,
|
|
95
|
+
tokenBudgetContinuationCount: 0,
|
|
96
|
+
hasAttemptedReactiveCompact: false,
|
|
97
|
+
startedAt: Date.now(),
|
|
98
|
+
usage: emptyUsage(),
|
|
99
|
+
permissionDenials: [],
|
|
100
|
+
};
|
|
101
|
+
if (initialSteeringMessages.type === "aborted") {
|
|
102
|
+
finishStructuredAdaptiveWithAbortedTurn(stream, currentContext, newMessages, state);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let firstTurn = true;
|
|
106
|
+
while (true) {
|
|
107
|
+
if (!firstTurn) {
|
|
108
|
+
stream.push({ type: "turn_start" });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
firstTurn = false;
|
|
112
|
+
}
|
|
113
|
+
state.pendingToolUseSummaries = flushReadyToolUseSummaries(state.pendingToolUseSummaries, currentContext.messages, newMessages, stream);
|
|
114
|
+
if (state.pendingMessages.length > 0) {
|
|
115
|
+
for (const message of state.pendingMessages) {
|
|
116
|
+
stream.push({ type: "message_start", message });
|
|
117
|
+
stream.push({ type: "message_end", message });
|
|
118
|
+
currentContext.messages.push(message);
|
|
119
|
+
newMessages.push(message);
|
|
120
|
+
}
|
|
121
|
+
state.pendingMessages = [];
|
|
122
|
+
}
|
|
123
|
+
state.turnCount += 1;
|
|
124
|
+
if (state.turnCount > maxTurns) {
|
|
125
|
+
const limitMessage = createLoopLimitMessage(config, `max_turns_reached: stopped after ${maxTurns} assistant turns to prevent a runaway agent loop.`);
|
|
126
|
+
currentContext.messages.push(limitMessage);
|
|
127
|
+
newMessages.push(limitMessage);
|
|
128
|
+
stream.push({ type: "message_start", message: { ...limitMessage } });
|
|
129
|
+
stream.push({ type: "message_end", message: limitMessage });
|
|
130
|
+
stream.push({ type: "turn_end", message: limitMessage, toolResults: [] });
|
|
131
|
+
recordTransition(state, {
|
|
132
|
+
reason: "max_turns_reached",
|
|
133
|
+
maxTurns,
|
|
134
|
+
turnCount: state.turnCount,
|
|
135
|
+
});
|
|
136
|
+
state.finalStopReason = "error";
|
|
137
|
+
state.finalErrorMessage = limitMessage.errorMessage;
|
|
138
|
+
state.finalErrorSubtype = "max_turns_reached";
|
|
139
|
+
finish(stream, newMessages, state);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const streamingToolExecutor = new StructuredAdaptiveStreamingToolExecutor(currentContext.tools, signal, stream, resolveMaxToolConcurrency(config.maxToolConcurrency), config.canUseTool);
|
|
143
|
+
const responseStartMessageCount = newMessages.length;
|
|
144
|
+
const message = await streamAssistantResponse(currentContext, config, signal, stream, streamFn, state.maxOutputTokensOverride, streamingToolExecutor);
|
|
145
|
+
newMessages.push(message);
|
|
146
|
+
addUsage(state.usage, message.usage);
|
|
147
|
+
state.maxOutputTokensOverride = undefined;
|
|
148
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
149
|
+
const errorSubtype = message.stopReason === "aborted"
|
|
150
|
+
? "aborted"
|
|
151
|
+
: isContextOverflow(message, config.model.contextWindow)
|
|
152
|
+
? "context_overflow"
|
|
153
|
+
: "model_error";
|
|
154
|
+
const failedToolExecution = streamingToolExecutor.size > 0
|
|
155
|
+
? await streamingToolExecutor.discardAndDrain(message.stopReason === "aborted" ? "assistant stream aborted" : "assistant stream error", message.stopReason === "aborted" ? "cancel" : "all")
|
|
156
|
+
: { toolResults: [], contextMessages: [], permissionDenials: [] };
|
|
157
|
+
const toolResults = enforceToolResultBatchSize(failedToolExecution.toolResults, config.maxToolResultBatchSizeChars);
|
|
158
|
+
const interruptedToolResults = createInterruptedToolResults(message, new Set(toolResults.map((result) => result.toolCallId)));
|
|
159
|
+
const allToolResults = [...toolResults, ...interruptedToolResults];
|
|
160
|
+
for (const result of allToolResults) {
|
|
161
|
+
currentContext.messages.push(result);
|
|
162
|
+
newMessages.push(result);
|
|
163
|
+
stream.push({ type: "message_start", message: result });
|
|
164
|
+
stream.push({ type: "message_end", message: result });
|
|
165
|
+
}
|
|
166
|
+
for (const contextMessage of failedToolExecution.contextMessages) {
|
|
167
|
+
currentContext.messages.push(contextMessage);
|
|
168
|
+
newMessages.push(contextMessage);
|
|
169
|
+
stream.push({ type: "message_start", message: contextMessage });
|
|
170
|
+
stream.push({ type: "message_end", message: contextMessage });
|
|
171
|
+
}
|
|
172
|
+
if (message.stopReason === "error" &&
|
|
173
|
+
config.recoverModelError &&
|
|
174
|
+
state.modelErrorRecoveryCount < maxModelErrorRecoveryAttempts) {
|
|
175
|
+
const attempt = state.modelErrorRecoveryCount + 1;
|
|
176
|
+
const recovery = await config.recoverModelError({
|
|
177
|
+
message,
|
|
178
|
+
messages: currentContext.messages,
|
|
179
|
+
errorSubtype,
|
|
180
|
+
attempt,
|
|
181
|
+
});
|
|
182
|
+
if (recovery.action === "retry") {
|
|
183
|
+
stream.push({ type: "turn_end", message, toolResults: allToolResults });
|
|
184
|
+
state.modelErrorRecoveryCount = attempt;
|
|
185
|
+
newMessages.splice(responseStartMessageCount);
|
|
186
|
+
currentContext.messages = recovery.messages;
|
|
187
|
+
recordTransition(state, recovery.transition ?? {
|
|
188
|
+
reason: "model_error_recovery",
|
|
189
|
+
subtype: errorSubtype,
|
|
190
|
+
attempt,
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
state.permissionDenials.push(...failedToolExecution.permissionDenials);
|
|
196
|
+
stream.push({ type: "turn_end", message, toolResults: allToolResults });
|
|
197
|
+
state.finalStopReason = message.stopReason;
|
|
198
|
+
state.finalErrorMessage = message.errorMessage;
|
|
199
|
+
state.finalErrorSubtype = errorSubtype;
|
|
200
|
+
finish(stream, newMessages, state);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const toolCalls = message.content.filter((c) => c.type === "toolCall");
|
|
204
|
+
if (toolCalls.length === 0) {
|
|
205
|
+
stream.push({ type: "turn_end", message, toolResults: [] });
|
|
206
|
+
if (message.stopReason === "length" &&
|
|
207
|
+
state.maxOutputTokensRecoveryCount < maxOutputTokenRecoveryAttempts) {
|
|
208
|
+
state.maxOutputTokensRecoveryCount += 1;
|
|
209
|
+
state.maxOutputTokensOverride = computeRecoveryMaxTokens(config, message);
|
|
210
|
+
state.pendingMessages = [createOutputTokenRecoveryMessage(state.maxOutputTokensRecoveryCount)];
|
|
211
|
+
recordTransition(state, {
|
|
212
|
+
reason: "max_output_tokens_recovery",
|
|
213
|
+
attempt: state.maxOutputTokensRecoveryCount,
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (config.runStopHooks && !state.stopHookActive) {
|
|
218
|
+
state.stopHookActive = true;
|
|
219
|
+
const stopHookResult = await waitForAbortableOperation(config.runStopHooks({
|
|
220
|
+
message,
|
|
221
|
+
messages: currentContext.messages,
|
|
222
|
+
}), signal);
|
|
223
|
+
state.stopHookActive = false;
|
|
224
|
+
if (stopHookResult.type === "aborted") {
|
|
225
|
+
finishStructuredAdaptiveWithAbortedTurn(stream, currentContext, newMessages, state);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const resolvedStopHookResult = stopHookResult.value;
|
|
229
|
+
if (resolvedStopHookResult.action === "continue" && resolvedStopHookResult.messages.length > 0) {
|
|
230
|
+
if (state.stopHookContinuationCount >= maxStopHookContinuations) {
|
|
231
|
+
const limitMessage = createLoopLimitMessage(config, `stop_hook_limit_reached: stopped after ${maxStopHookContinuations} stop-hook continuation turns.`);
|
|
232
|
+
currentContext.messages.push(limitMessage);
|
|
233
|
+
newMessages.push(limitMessage);
|
|
234
|
+
stream.push({ type: "message_start", message: { ...limitMessage } });
|
|
235
|
+
stream.push({ type: "message_end", message: limitMessage });
|
|
236
|
+
stream.push({ type: "turn_end", message: limitMessage, toolResults: [] });
|
|
237
|
+
recordTransition(state, {
|
|
238
|
+
reason: "stop_hook_limit_reached",
|
|
239
|
+
maxContinuations: maxStopHookContinuations,
|
|
240
|
+
continuationCount: state.stopHookContinuationCount,
|
|
241
|
+
});
|
|
242
|
+
state.finalStopReason = "error";
|
|
243
|
+
state.finalErrorMessage = limitMessage.errorMessage;
|
|
244
|
+
state.finalErrorSubtype = "stop_hook_limit_reached";
|
|
245
|
+
finish(stream, newMessages, state);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
state.stopHookContinuationCount += 1;
|
|
249
|
+
state.pendingMessages = resolvedStopHookResult.messages;
|
|
250
|
+
recordTransition(state, {
|
|
251
|
+
reason: "stop_hook_blocking",
|
|
252
|
+
continuationCount: state.stopHookContinuationCount,
|
|
253
|
+
});
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const tokenBudgetContinuation = createTokenBudgetContinuation(config, state.usage.output, state.tokenBudgetContinuationCount);
|
|
258
|
+
if (tokenBudgetContinuation) {
|
|
259
|
+
state.tokenBudgetContinuationCount += 1;
|
|
260
|
+
state.pendingMessages = [tokenBudgetContinuation.message];
|
|
261
|
+
recordTransition(state, {
|
|
262
|
+
reason: "token_budget_continuation",
|
|
263
|
+
continuationCount: state.tokenBudgetContinuationCount,
|
|
264
|
+
outputTokens: tokenBudgetContinuation.outputTokens,
|
|
265
|
+
targetTokens: tokenBudgetContinuation.targetTokens,
|
|
266
|
+
});
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const followUpMessagesResult = await waitForAbortableOperation(config.getFollowUpMessages ? config.getFollowUpMessages() : [], signal);
|
|
270
|
+
if (followUpMessagesResult.type === "aborted") {
|
|
271
|
+
finishStructuredAdaptiveWithAbortedTurn(stream, currentContext, newMessages, state);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const followUpMessages = followUpMessagesResult.value || [];
|
|
275
|
+
if (followUpMessages.length === 0) {
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
state.pendingMessages = followUpMessages;
|
|
279
|
+
recordTransition(state, { reason: "follow_up" });
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (state.toolCallCount + toolCalls.length > maxToolCalls) {
|
|
283
|
+
const skippedToolResults = createSkippedToolCallLimitResults(message, `Tool call skipped because this prompt reached the ${maxToolCalls} tool-call limit.`);
|
|
284
|
+
for (const result of skippedToolResults) {
|
|
285
|
+
currentContext.messages.push(result);
|
|
286
|
+
newMessages.push(result);
|
|
287
|
+
stream.push({ type: "message_start", message: result });
|
|
288
|
+
stream.push({ type: "message_end", message: result });
|
|
289
|
+
}
|
|
290
|
+
const limitMessage = createLoopLimitMessage(config, `tool_call_limit_reached: stopped before executing ${toolCalls.length} tool call${toolCalls.length === 1 ? "" : "s"} because this prompt reached the ${maxToolCalls} tool-call limit.`);
|
|
291
|
+
currentContext.messages.push(limitMessage);
|
|
292
|
+
newMessages.push(limitMessage);
|
|
293
|
+
stream.push({ type: "message_start", message: { ...limitMessage } });
|
|
294
|
+
stream.push({ type: "message_end", message: limitMessage });
|
|
295
|
+
stream.push({ type: "turn_end", message: limitMessage, toolResults: skippedToolResults });
|
|
296
|
+
recordTransition(state, {
|
|
297
|
+
reason: "tool_call_limit_reached",
|
|
298
|
+
maxToolCalls,
|
|
299
|
+
requestedToolCalls: toolCalls.length,
|
|
300
|
+
toolCallCount: state.toolCallCount,
|
|
301
|
+
});
|
|
302
|
+
state.finalStopReason = "error";
|
|
303
|
+
state.finalErrorMessage = limitMessage.errorMessage;
|
|
304
|
+
state.finalErrorSubtype = "tool_call_limit_reached";
|
|
305
|
+
finish(stream, newMessages, state);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
state.toolCallCount += toolCalls.length;
|
|
309
|
+
const toolExecution = streamingToolExecutor.size > 0
|
|
310
|
+
? await streamingToolExecutor.drain()
|
|
311
|
+
: await runStructuredAdaptiveTools(toolCalls, currentContext.tools, signal, stream, config.getSteeringMessages, config.maxToolConcurrency, config.canUseTool);
|
|
312
|
+
const toolResults = enforceToolResultBatchSize(toolExecution.toolResults, config.maxToolResultBatchSizeChars);
|
|
313
|
+
state.permissionDenials.push(...toolExecution.permissionDenials);
|
|
314
|
+
for (const result of toolResults) {
|
|
315
|
+
currentContext.messages.push(result);
|
|
316
|
+
newMessages.push(result);
|
|
317
|
+
stream.push({ type: "message_start", message: result });
|
|
318
|
+
stream.push({ type: "message_end", message: result });
|
|
319
|
+
}
|
|
320
|
+
for (const contextMessage of toolExecution.contextMessages) {
|
|
321
|
+
currentContext.messages.push(contextMessage);
|
|
322
|
+
newMessages.push(contextMessage);
|
|
323
|
+
stream.push({ type: "message_start", message: contextMessage });
|
|
324
|
+
stream.push({ type: "message_end", message: contextMessage });
|
|
325
|
+
}
|
|
326
|
+
const pendingSummary = startToolUseSummary(config, {
|
|
327
|
+
assistantMessage: message,
|
|
328
|
+
toolResults,
|
|
329
|
+
contextMessages: toolExecution.contextMessages,
|
|
330
|
+
messages: currentContext.messages,
|
|
331
|
+
});
|
|
332
|
+
if (pendingSummary) {
|
|
333
|
+
state.pendingToolUseSummaries.push(pendingSummary);
|
|
334
|
+
}
|
|
335
|
+
stream.push({ type: "turn_end", message, toolResults });
|
|
336
|
+
if (toolExecution.steeringMessages && toolExecution.steeringMessages.length > 0) {
|
|
337
|
+
state.pendingMessages = toolExecution.steeringMessages;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
state.pendingMessages = (await config.getSteeringMessages?.()) || [];
|
|
341
|
+
}
|
|
342
|
+
recordTransition(state, { reason: "tool_result", toolCallCount: toolCalls.length });
|
|
343
|
+
}
|
|
344
|
+
state.finalStopReason = state.finalStopReason ?? inferStopReason(newMessages);
|
|
345
|
+
finish(stream, newMessages, state);
|
|
346
|
+
}
|
|
347
|
+
async function streamAssistantResponse(context, config, signal, stream, streamFn, maxTokensOverride, streamingToolExecutor) {
|
|
348
|
+
let messages = context.messages;
|
|
349
|
+
if (config.transformContext) {
|
|
350
|
+
const transformedMessages = await waitForAbortableOperation(config.transformContext(messages, signal), signal);
|
|
351
|
+
if (transformedMessages.type === "aborted") {
|
|
352
|
+
return pushAbortedAssistantMessage(context, stream, config);
|
|
353
|
+
}
|
|
354
|
+
messages = transformedMessages.value;
|
|
355
|
+
}
|
|
356
|
+
const convertedMessages = await waitForAbortableOperation(config.convertToLlm(messages), signal);
|
|
357
|
+
if (convertedMessages.type === "aborted") {
|
|
358
|
+
return pushAbortedAssistantMessage(context, stream, config);
|
|
359
|
+
}
|
|
360
|
+
const llmMessages = convertedMessages.value;
|
|
361
|
+
const llmContext = {
|
|
362
|
+
systemPrompt: context.systemPrompt,
|
|
363
|
+
messages: llmMessages,
|
|
364
|
+
tools: context.tools,
|
|
365
|
+
};
|
|
366
|
+
const streamFunction = streamFn || streamSimple;
|
|
367
|
+
const apiKeyResult = await waitForAbortableOperation(config.getApiKey ? config.getApiKey(config.model.provider) : undefined, signal);
|
|
368
|
+
if (apiKeyResult.type === "aborted") {
|
|
369
|
+
return pushAbortedAssistantMessage(context, stream, config);
|
|
370
|
+
}
|
|
371
|
+
const resolvedApiKey = apiKeyResult.value || config.apiKey;
|
|
372
|
+
stream.push({
|
|
373
|
+
type: "stream_request_start",
|
|
374
|
+
model: config.model.id,
|
|
375
|
+
provider: config.model.provider,
|
|
376
|
+
api: config.model.api,
|
|
377
|
+
messageCount: llmMessages.length,
|
|
378
|
+
maxTokens: maxTokensOverride ?? config.maxTokens,
|
|
379
|
+
});
|
|
380
|
+
const response = await waitForAssistantStream(streamFunction(config.model, llmContext, {
|
|
381
|
+
...config,
|
|
382
|
+
maxTokens: maxTokensOverride ?? config.maxTokens,
|
|
383
|
+
apiKey: resolvedApiKey,
|
|
384
|
+
signal,
|
|
385
|
+
}), signal);
|
|
386
|
+
if (response === "aborted") {
|
|
387
|
+
const finalMessage = createLoopLimitMessage(config, "Request was aborted");
|
|
388
|
+
finalMessage.stopReason = "aborted";
|
|
389
|
+
context.messages.push(finalMessage);
|
|
390
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
391
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
392
|
+
return finalMessage;
|
|
393
|
+
}
|
|
394
|
+
let partialMessage = null;
|
|
395
|
+
let addedPartial = false;
|
|
396
|
+
const responseIterator = response[Symbol.asyncIterator]();
|
|
397
|
+
while (true) {
|
|
398
|
+
let nextEvent;
|
|
399
|
+
try {
|
|
400
|
+
nextEvent = await waitForAssistantStreamEvent(responseIterator, signal);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
const finalMessage = createLoopLimitMessage(config, error instanceof Error ? error.message : String(error));
|
|
404
|
+
if (addedPartial) {
|
|
405
|
+
context.messages[context.messages.length - 1] = finalMessage;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
context.messages.push(finalMessage);
|
|
409
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
410
|
+
}
|
|
411
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
412
|
+
return finalMessage;
|
|
413
|
+
}
|
|
414
|
+
if (nextEvent === "aborted") {
|
|
415
|
+
void responseIterator.return?.();
|
|
416
|
+
const finalMessage = createLoopLimitMessage(config, "Request was aborted");
|
|
417
|
+
finalMessage.stopReason = "aborted";
|
|
418
|
+
if (addedPartial) {
|
|
419
|
+
context.messages[context.messages.length - 1] = finalMessage;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
context.messages.push(finalMessage);
|
|
423
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
424
|
+
}
|
|
425
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
426
|
+
return finalMessage;
|
|
427
|
+
}
|
|
428
|
+
if (nextEvent.done) {
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
const event = nextEvent.value;
|
|
432
|
+
switch (event.type) {
|
|
433
|
+
case "start":
|
|
434
|
+
partialMessage = event.partial;
|
|
435
|
+
context.messages.push(partialMessage);
|
|
436
|
+
addedPartial = true;
|
|
437
|
+
stream.push({ type: "message_start", message: { ...partialMessage } });
|
|
438
|
+
break;
|
|
439
|
+
case "text_start":
|
|
440
|
+
case "text_delta":
|
|
441
|
+
case "text_end":
|
|
442
|
+
case "thinking_start":
|
|
443
|
+
case "thinking_delta":
|
|
444
|
+
case "thinking_end":
|
|
445
|
+
case "toolcall_start":
|
|
446
|
+
case "toolcall_delta":
|
|
447
|
+
if (partialMessage) {
|
|
448
|
+
partialMessage = event.partial;
|
|
449
|
+
context.messages[context.messages.length - 1] = partialMessage;
|
|
450
|
+
stream.push({
|
|
451
|
+
type: "message_update",
|
|
452
|
+
assistantMessageEvent: event,
|
|
453
|
+
message: { ...partialMessage },
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case "toolcall_end":
|
|
458
|
+
if (partialMessage) {
|
|
459
|
+
partialMessage = event.partial;
|
|
460
|
+
context.messages[context.messages.length - 1] = partialMessage;
|
|
461
|
+
stream.push({
|
|
462
|
+
type: "message_update",
|
|
463
|
+
assistantMessageEvent: event,
|
|
464
|
+
message: { ...partialMessage },
|
|
465
|
+
});
|
|
466
|
+
streamingToolExecutor?.addTool(event.toolCall);
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
469
|
+
case "done":
|
|
470
|
+
case "error": {
|
|
471
|
+
const finalMessage = event.type === "done" ? event.message : event.error;
|
|
472
|
+
if (addedPartial) {
|
|
473
|
+
context.messages[context.messages.length - 1] = finalMessage;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
context.messages.push(finalMessage);
|
|
477
|
+
}
|
|
478
|
+
if (!addedPartial) {
|
|
479
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
480
|
+
}
|
|
481
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
482
|
+
return finalMessage;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const finalMessage = response.resultIfResolved() ??
|
|
487
|
+
createLoopLimitMessage(config, "Provider stream ended without a final assistant message");
|
|
488
|
+
if (addedPartial) {
|
|
489
|
+
context.messages[context.messages.length - 1] = finalMessage;
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
context.messages.push(finalMessage);
|
|
493
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
494
|
+
}
|
|
495
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
496
|
+
return finalMessage;
|
|
497
|
+
}
|
|
498
|
+
function pushAbortedAssistantMessage(context, stream, config) {
|
|
499
|
+
const finalMessage = createLoopLimitMessage(config, "Request was aborted");
|
|
500
|
+
finalMessage.stopReason = "aborted";
|
|
501
|
+
context.messages.push(finalMessage);
|
|
502
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
503
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
504
|
+
return finalMessage;
|
|
505
|
+
}
|
|
506
|
+
function finishStructuredAdaptiveWithAbortedTurn(stream, context, newMessages, state) {
|
|
507
|
+
const finalMessage = createLoopLimitMessage(state.config, "Request was aborted");
|
|
508
|
+
finalMessage.stopReason = "aborted";
|
|
509
|
+
context.messages.push(finalMessage);
|
|
510
|
+
newMessages.push(finalMessage);
|
|
511
|
+
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
512
|
+
stream.push({ type: "message_end", message: finalMessage });
|
|
513
|
+
stream.push({ type: "turn_end", message: finalMessage, toolResults: [] });
|
|
514
|
+
state.finalStopReason = "aborted";
|
|
515
|
+
state.finalErrorMessage = finalMessage.errorMessage;
|
|
516
|
+
state.finalErrorSubtype = "aborted";
|
|
517
|
+
finish(stream, newMessages, state);
|
|
518
|
+
}
|
|
519
|
+
function createLoopLimitMessage(config, errorMessage) {
|
|
520
|
+
return {
|
|
521
|
+
role: "assistant",
|
|
522
|
+
content: [{ type: "text", text: "" }],
|
|
523
|
+
api: config.model.api,
|
|
524
|
+
provider: config.model.provider,
|
|
525
|
+
model: config.model.id,
|
|
526
|
+
usage: {
|
|
527
|
+
input: 0,
|
|
528
|
+
output: 0,
|
|
529
|
+
cacheRead: 0,
|
|
530
|
+
cacheWrite: 0,
|
|
531
|
+
totalTokens: 0,
|
|
532
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
533
|
+
},
|
|
534
|
+
stopReason: "error",
|
|
535
|
+
errorMessage,
|
|
536
|
+
timestamp: Date.now(),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function endWithLoopError(stream, newMessages, config, error, signal) {
|
|
540
|
+
const errorMessage = createLoopLimitMessage(config, error instanceof Error ? error.message : String(error));
|
|
541
|
+
if (signal?.aborted) {
|
|
542
|
+
errorMessage.stopReason = "aborted";
|
|
543
|
+
}
|
|
544
|
+
newMessages.push(errorMessage);
|
|
545
|
+
stream.push({ type: "message_start", message: { ...errorMessage } });
|
|
546
|
+
stream.push({ type: "message_end", message: errorMessage });
|
|
547
|
+
stream.push({ type: "turn_end", message: errorMessage, toolResults: [] });
|
|
548
|
+
const state = {
|
|
549
|
+
config,
|
|
550
|
+
turnCount: 0,
|
|
551
|
+
toolCallCount: 0,
|
|
552
|
+
transition: { reason: "start" },
|
|
553
|
+
transitions: [],
|
|
554
|
+
pendingMessages: [],
|
|
555
|
+
pendingToolUseSummaries: [],
|
|
556
|
+
stopHookActive: false,
|
|
557
|
+
stopHookContinuationCount: 0,
|
|
558
|
+
maxOutputTokensRecoveryCount: 0,
|
|
559
|
+
modelErrorRecoveryCount: 0,
|
|
560
|
+
tokenBudgetContinuationCount: 0,
|
|
561
|
+
hasAttemptedReactiveCompact: false,
|
|
562
|
+
startedAt: Date.now(),
|
|
563
|
+
usage: emptyUsage(),
|
|
564
|
+
permissionDenials: [],
|
|
565
|
+
finalStopReason: errorMessage.stopReason,
|
|
566
|
+
finalErrorMessage: errorMessage.errorMessage,
|
|
567
|
+
finalErrorSubtype: signal?.aborted ? "aborted" : "loop_error",
|
|
568
|
+
};
|
|
569
|
+
finish(stream, newMessages, state);
|
|
570
|
+
}
|
|
571
|
+
function finish(stream, newMessages, state) {
|
|
572
|
+
stream.push({
|
|
573
|
+
type: "agent_result",
|
|
574
|
+
stopReason: state.finalStopReason ?? inferStopReason(newMessages),
|
|
575
|
+
loopFramework: resolveAgentRunLoopFramework(state.config),
|
|
576
|
+
loopPolicy: buildAgentRunPolicy(state.config),
|
|
577
|
+
turnCount: state.turnCount,
|
|
578
|
+
toolCallCount: state.toolCallCount,
|
|
579
|
+
durationMs: Date.now() - state.startedAt,
|
|
580
|
+
usage: state.usage,
|
|
581
|
+
permissionDenialCount: state.permissionDenials.length,
|
|
582
|
+
permissionDenials: state.permissionDenials,
|
|
583
|
+
transitions: state.transitions.length > 0 ? state.transitions : undefined,
|
|
584
|
+
lastTransition: state.transition,
|
|
585
|
+
errorMessage: state.finalErrorMessage,
|
|
586
|
+
errorSubtype: state.finalErrorSubtype,
|
|
587
|
+
});
|
|
588
|
+
stream.push({ type: "agent_end", messages: newMessages });
|
|
589
|
+
stream.end(newMessages);
|
|
590
|
+
}
|
|
591
|
+
function recordTransition(state, transition) {
|
|
592
|
+
state.transition = transition;
|
|
593
|
+
state.transitions.push(transition);
|
|
594
|
+
}
|
|
595
|
+
function emptyUsage() {
|
|
596
|
+
return {
|
|
597
|
+
input: 0,
|
|
598
|
+
output: 0,
|
|
599
|
+
cacheRead: 0,
|
|
600
|
+
cacheWrite: 0,
|
|
601
|
+
totalTokens: 0,
|
|
602
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function addUsage(target, usage) {
|
|
606
|
+
target.input += usage.input;
|
|
607
|
+
target.output += usage.output;
|
|
608
|
+
target.cacheRead += usage.cacheRead;
|
|
609
|
+
target.cacheWrite += usage.cacheWrite;
|
|
610
|
+
target.totalTokens += usage.totalTokens;
|
|
611
|
+
target.cost.input += usage.cost.input;
|
|
612
|
+
target.cost.output += usage.cost.output;
|
|
613
|
+
target.cost.cacheRead += usage.cost.cacheRead;
|
|
614
|
+
target.cost.cacheWrite += usage.cost.cacheWrite;
|
|
615
|
+
target.cost.total += usage.cost.total;
|
|
616
|
+
}
|
|
617
|
+
function inferStopReason(messages) {
|
|
618
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
619
|
+
const message = messages[i];
|
|
620
|
+
if (message?.role === "assistant") {
|
|
621
|
+
return message.stopReason;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return "stop";
|
|
625
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured-adaptive streaming tool executor.
|
|
3
|
+
* Starts complete streamed tool calls before the assistant response finishes,
|
|
4
|
+
* while preserving ordered tool_result emission for the next model turn.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* [WHO]: StructuredAdaptiveStreamingToolExecutor
|
|
8
|
+
* [FROM]: Depends on @pencil-agent/ai, ./types, ./structured-adaptive-tool-orchestration
|
|
9
|
+
* [TO]: Consumed by ./structured-adaptive-agent-loop.ts
|
|
10
|
+
* [HERE]: core/lib/agent-core/src/structured-adaptive-streaming-tool-executor.ts - streaming tool scheduling for weak-model-compatible loop
|
|
11
|
+
*/
|
|
12
|
+
import { EventStream } from "@pencil-agent/ai/events";
|
|
13
|
+
import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentTool } from "./types.js";
|
|
14
|
+
import { type StructuredAdaptiveToolCall, type StructuredAdaptiveToolRunResult } from "./structured-adaptive-tool-orchestration.js";
|
|
15
|
+
export declare class StructuredAdaptiveStreamingToolExecutor {
|
|
16
|
+
private readonly stream;
|
|
17
|
+
private readonly canUseTool?;
|
|
18
|
+
private readonly toolByName;
|
|
19
|
+
private readonly records;
|
|
20
|
+
private readonly maxConcurrency;
|
|
21
|
+
private discardedReason;
|
|
22
|
+
private parentAborted;
|
|
23
|
+
private parentAbortReason;
|
|
24
|
+
constructor(tools: AgentTool<any>[] | undefined, signal: AbortSignal | undefined, stream: EventStream<AgentEvent, AgentMessage[]>, maxConcurrency: number, canUseTool?: AgentLoopConfig["canUseTool"]);
|
|
25
|
+
get size(): number;
|
|
26
|
+
addTool(toolCall: StructuredAdaptiveToolCall): void;
|
|
27
|
+
drain(): Promise<StructuredAdaptiveToolRunResult>;
|
|
28
|
+
discardAndDrain(reason: string, abortMode?: "all" | "cancel"): Promise<StructuredAdaptiveToolRunResult>;
|
|
29
|
+
private processQueue;
|
|
30
|
+
private canStart;
|
|
31
|
+
private start;
|
|
32
|
+
private completeWithSyntheticError;
|
|
33
|
+
}
|