agent-accelerator 0.6.5 → 0.6.6
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 +0 -2
- package/package.json +1 -1
- package/src/agent/agent.ts +97 -670
- package/src/agent/architecture.md +4 -1
- package/src/agent/loop.ts +208 -213
- package/src/agent/named-sessions.ts +93 -0
- package/src/agent/options.ts +166 -0
- package/src/agent/run-control.ts +248 -0
- package/src/index.ts +2 -2
- package/src/providers/google.ts +1 -2
- package/src/streaming/event-stream.ts +1 -1
- package/src/types/agent.ts +0 -2
- package/src/utils/headers.ts +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Option handling for an Agent: the normalized run limits, the thinking level, the tool choice and the one function
|
|
3
|
+
* that turns agent config plus per-run options into what every provider call receives.
|
|
4
|
+
*/
|
|
5
|
+
import type { AgentConfig, AgentRunOptions, RetryPolicyConfig, ToolChoiceOption } from "../types/agent.ts";
|
|
6
|
+
import type { ProviderRequestOptions, StructuredOutputSpec } from "../types/model.ts";
|
|
7
|
+
import type { CacheOption, ServiceTier, ThinkingConfig, ThinkingLevel } from "../types/core.ts";
|
|
8
|
+
import type { Prompts } from "../prompts/prompts.ts";
|
|
9
|
+
import { normalizeThinkingV2 } from "../tools/tool.ts";
|
|
10
|
+
import { validateModelThinking } from "../models/catalog.ts";
|
|
11
|
+
import { telemetryBus } from "../telemetry.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a per-run thinking override without mutating agent config.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveEffectiveThinking(base?: ThinkingConfig, overrideLevel?: string): ThinkingConfig | undefined {
|
|
17
|
+
if (!overrideLevel) return base;
|
|
18
|
+
if (overrideLevel === "none") return { enabled: false, level: "none", budgetTokens: 0 };
|
|
19
|
+
if (overrideLevel === "dynamic") return { enabled: true, level: "dynamic", budgetTokens: -1 };
|
|
20
|
+
return { enabled: true, level: overrideLevel as ThinkingConfig["level"] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Normalized per-run limits. Sub-agents inherit these unless overridden. */
|
|
24
|
+
export interface AgentRunLimits {
|
|
25
|
+
maxTurns?: number;
|
|
26
|
+
deadlineMs?: number;
|
|
27
|
+
maxToolCallsPerRun?: number;
|
|
28
|
+
maxTotalTokens?: number;
|
|
29
|
+
requestTimeoutMs: number;
|
|
30
|
+
firstByteTimeoutMs: number;
|
|
31
|
+
streamIdleTimeoutMs: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Resolves `(config as any).limits` with defaults (timeouts tolerate reasoning). */
|
|
35
|
+
export function resolveAgentLimits(config: { limits?: Record<string, unknown> }): AgentRunLimits {
|
|
36
|
+
const raw = (config as { limits?: Record<string, unknown> }).limits ?? {};
|
|
37
|
+
const num = (v: unknown): number | undefined => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
|
|
38
|
+
// A network timeout: `0` or `Infinity` turns it off, any other number is milliseconds, anything else is unset (the default applies).
|
|
39
|
+
const timeout = (v: unknown): number | undefined => (typeof v === "number" && !Number.isNaN(v) ? v : undefined);
|
|
40
|
+
return {
|
|
41
|
+
...(num(raw.maxTurns) !== undefined ? { maxTurns: Math.max(1, Math.floor(num(raw.maxTurns)!)) } : {}),
|
|
42
|
+
...(num(raw.deadlineMs) !== undefined ? { deadlineMs: num(raw.deadlineMs)! } : {}),
|
|
43
|
+
...(num(raw.maxToolCallsPerRun) !== undefined
|
|
44
|
+
? { maxToolCallsPerRun: Math.max(1, Math.floor(num(raw.maxToolCallsPerRun)!)) }
|
|
45
|
+
: {}),
|
|
46
|
+
...(num(raw.maxTotalTokens) !== undefined
|
|
47
|
+
? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) }
|
|
48
|
+
: {}),
|
|
49
|
+
requestTimeoutMs: timeout(raw.requestTimeoutMs) ?? 300_000,
|
|
50
|
+
firstByteTimeoutMs: timeout(raw.firstByteTimeoutMs) ?? 120_000,
|
|
51
|
+
streamIdleTimeoutMs: timeout(raw.streamIdleTimeoutMs) ?? 300_000,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The Agent settings `buildProviderOptions` reads. */
|
|
56
|
+
export interface ProviderOptionSource {
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
baseUrl?: string;
|
|
59
|
+
customHeaders?: Record<string, string>;
|
|
60
|
+
thinkingConfig?: ThinkingConfig;
|
|
61
|
+
cache?: CacheOption;
|
|
62
|
+
serviceTier?: ServiceTier;
|
|
63
|
+
sessionId: string;
|
|
64
|
+
outputSpec?: StructuredOutputSpec;
|
|
65
|
+
prompts: Prompts;
|
|
66
|
+
googleTier?: "free" | "paid";
|
|
67
|
+
callDefaults: {
|
|
68
|
+
toolChoice?: ToolChoiceOption;
|
|
69
|
+
maxOutputTokens?: number;
|
|
70
|
+
retry?: RetryPolicyConfig;
|
|
71
|
+
env?: Record<string, string>;
|
|
72
|
+
fetch?: typeof fetch;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Single place that turns agent config + per-run options into the options
|
|
78
|
+
* every provider call receives. Per-run values override agent defaults
|
|
79
|
+
* (objects such as `retry` and `env` merge key by key).
|
|
80
|
+
*/
|
|
81
|
+
export function buildProviderOptions(
|
|
82
|
+
agent: ProviderOptionSource,
|
|
83
|
+
runOptions: AgentRunOptions | undefined
|
|
84
|
+
): ProviderRequestOptions {
|
|
85
|
+
const d = agent.callDefaults;
|
|
86
|
+
const sessionId = runOptions?.sessionId || agent.sessionId;
|
|
87
|
+
const toolChoice = normalizeToolChoiceOption(runOptions?.toolChoice ?? d.toolChoice);
|
|
88
|
+
const retry = { ...d.retry, ...runOptions?.retry };
|
|
89
|
+
const env = { ...d.env, ...runOptions?.env };
|
|
90
|
+
const maxOutputTokens = runOptions?.maxOutputTokens ?? d.maxOutputTokens;
|
|
91
|
+
const fetchImpl = runOptions?.fetch ?? d.fetch;
|
|
92
|
+
return {
|
|
93
|
+
apiKey: agent.apiKey,
|
|
94
|
+
baseUrl: agent.baseUrl,
|
|
95
|
+
headers: { ...(agent.customHeaders ?? {}), ...(runOptions?.headers ?? {}) },
|
|
96
|
+
thinking: resolveEffectiveThinking(agent.thinkingConfig, runOptions?.thinkingLevel),
|
|
97
|
+
...(agent.cache ? { cache: agent.cache } : {}),
|
|
98
|
+
serviceTier: agent.serviceTier,
|
|
99
|
+
sessionId,
|
|
100
|
+
...(agent.outputSpec ? { output: agent.outputSpec } : {}),
|
|
101
|
+
prompts: { structuredOutput: agent.prompts.structuredOutput, toolBridge: agent.prompts.toolBridge },
|
|
102
|
+
...(agent.googleTier ? { googleTier: agent.googleTier } : {}),
|
|
103
|
+
...(toolChoice !== undefined ? { toolChoice } : {}),
|
|
104
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
105
|
+
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
106
|
+
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
107
|
+
...(retry.maxRetries !== undefined ? { maxRetries: retry.maxRetries } : {}),
|
|
108
|
+
...(retry.maxDelayMs !== undefined ? { maxRetryDelayMs: retry.maxDelayMs } : {}),
|
|
109
|
+
...(retry.baseDelayMs !== undefined ? { retryBaseDelayMs: retry.baseDelayMs } : {}),
|
|
110
|
+
...(retry.budgetMs !== undefined ? { retryBudgetMs: retry.budgetMs } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Normalizes the public `thinkingLevel` flag into provider-neutral thinking settings. */
|
|
115
|
+
export function normalizeThinking(config: AgentConfig, fallbackLevel?: string): ThinkingConfig | undefined {
|
|
116
|
+
// DX4: only thinkingLevel, values: none, dynamic, minimal, low, medium, high, xhigh, max
|
|
117
|
+
// `thinking` (canonical) wins over legacy `thinkingLevel`; `{ level }` objects are unwrapped.
|
|
118
|
+
const rawLevel = normalizeThinkingV2(config.thinking, config.thinkingLevel) ?? fallbackLevel;
|
|
119
|
+
if (typeof config.thinking === "object" && config.thinking?.budgetTokens !== undefined) {
|
|
120
|
+
// No provider maps an explicit token budget yet: say so instead of ignoring it silently.
|
|
121
|
+
telemetryBus.emitWarning(
|
|
122
|
+
"config.thinking_budget_ignored",
|
|
123
|
+
"[Agent Accelerator] thinking.budgetTokens is not forwarded by any provider adapter yet; the level alone is used.",
|
|
124
|
+
{ budgetTokens: config.thinking.budgetTokens }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const level = rawLevel as ThinkingLevel | undefined;
|
|
128
|
+
|
|
129
|
+
if (!level) return undefined;
|
|
130
|
+
|
|
131
|
+
// Validate model thinking from catalog if model is configured
|
|
132
|
+
if (config.model) {
|
|
133
|
+
try {
|
|
134
|
+
const rawModel: any = (config.model as any)?.model ? (config.model as any).model : config.model;
|
|
135
|
+
const modelStr = typeof rawModel === "string" ? rawModel : rawModel?.id || "";
|
|
136
|
+
const provStr =
|
|
137
|
+
typeof rawModel === "object" && rawModel?.provider
|
|
138
|
+
? rawModel.provider
|
|
139
|
+
: modelStr.includes("/")
|
|
140
|
+
? modelStr.split("/")[0]
|
|
141
|
+
: "";
|
|
142
|
+
const actualModelId = modelStr.includes("/") ? modelStr.split("/").slice(1).join("/") : modelStr;
|
|
143
|
+
if (actualModelId) {
|
|
144
|
+
validateModelThinking(provStr || "openrouter", actualModelId, level);
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
throw e;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (level === "none") {
|
|
152
|
+
return { enabled: false, level: "none", budgetTokens: 0 };
|
|
153
|
+
}
|
|
154
|
+
if (level === "dynamic") {
|
|
155
|
+
return { enabled: true, level: "dynamic", budgetTokens: -1 };
|
|
156
|
+
}
|
|
157
|
+
return { enabled: true, level };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Maps the public `toolChoice` option onto the provider wire-neutral form. */
|
|
161
|
+
export function normalizeToolChoiceOption(choice: ToolChoiceOption | undefined): ProviderRequestOptions["toolChoice"] {
|
|
162
|
+
if (!choice) return undefined;
|
|
163
|
+
if (typeof choice === "string") return choice;
|
|
164
|
+
if (choice.tool) return { type: "function", function: { name: choice.tool } };
|
|
165
|
+
return choice.mode;
|
|
166
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mid-session control of one Agent: the run that is active now, the follow-ups waiting behind it, and what happens when
|
|
3
|
+
* a new prompt arrives while busy (`steer`, `queue`, `interrupt`). `Agent` keeps the public methods and calls into this
|
|
4
|
+
* class; the class only needs the few things in {@link RunHost}.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentRunOptions, MidSessionConfig } from "../types/agent.ts";
|
|
7
|
+
import type { ContentPart } from "../types/message.ts";
|
|
8
|
+
import type { AgentResponse, StreamEvent } from "../types/response.ts";
|
|
9
|
+
import type { AssistantMessageEventStream } from "../streaming/event-stream.ts";
|
|
10
|
+
import { ValidationError } from "../types/errors.ts";
|
|
11
|
+
import type { SteerEntry } from "./loop.ts";
|
|
12
|
+
|
|
13
|
+
/** The run that is active right now. */
|
|
14
|
+
export interface ActiveRun {
|
|
15
|
+
steerInbox: SteerEntry[];
|
|
16
|
+
outerStream?: AssistantMessageEventStream;
|
|
17
|
+
runPromise?: Promise<AgentResponse>;
|
|
18
|
+
abortController: AbortController;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A prompt waiting for the active run to finish. */
|
|
22
|
+
export interface QueuedPrompt {
|
|
23
|
+
prompt: string | ContentPart[];
|
|
24
|
+
options?: AgentRunOptions;
|
|
25
|
+
isStream: boolean;
|
|
26
|
+
deferredStream?: AssistantMessageEventStream;
|
|
27
|
+
resolve: (res: AgentResponse) => void;
|
|
28
|
+
reject: (err: unknown) => void;
|
|
29
|
+
/** Put here by `interrupt(message)`: runs before every other waiting prompt. */
|
|
30
|
+
urgent?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** What the run control needs from its Agent. */
|
|
34
|
+
export interface RunHost {
|
|
35
|
+
persistNow(): void;
|
|
36
|
+
run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse>;
|
|
37
|
+
startNonStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse>;
|
|
38
|
+
startStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream;
|
|
39
|
+
readonly midSessionConfig: Required<Pick<MidSessionConfig, "mode">> & MidSessionConfig;
|
|
40
|
+
readonly maxSteers: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The active run and the waiting queue of one Agent. */
|
|
44
|
+
export class RunControl {
|
|
45
|
+
/** The run in flight, or null when idle. */
|
|
46
|
+
current: ActiveRun | null = null;
|
|
47
|
+
/** Prompts waiting behind it (urgent ones first). */
|
|
48
|
+
readonly queue: QueuedPrompt[] = [];
|
|
49
|
+
|
|
50
|
+
constructor(private readonly host: RunHost) {}
|
|
51
|
+
|
|
52
|
+
/** Throws when the steer inbox is at its bound. */
|
|
53
|
+
assertSteerRoom(inbox: SteerEntry[]): void {
|
|
54
|
+
const max = this.host.maxSteers ?? 20;
|
|
55
|
+
if (inbox.length >= max) {
|
|
56
|
+
throw new ValidationError(
|
|
57
|
+
`[Agent Accelerator] Steer inbox is full (${inbox.length}/${max}). Wait for the active run to drain or increase midSession.maxSteers.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Picks steer or queue for a call that arrives while busy: the developer's enforced midSession mode wins over the per-call request; in: run()/stream() `enqueue`, out: their busy branch.
|
|
63
|
+
resolveEnqueueMode(requested?: "steer" | "queue"): "steer" | "queue" {
|
|
64
|
+
const enforced = this.host.midSessionConfig.mode;
|
|
65
|
+
if (enforced === "steer" || enforced === "queue") return enforced;
|
|
66
|
+
return requested === "steer" ? "steer" : "queue";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Aborts the active run (provider request, tools, workers, stream) through its own controller; no-op when idle; in: interrupt and queueUrgent, out: the run settles as AbortError.
|
|
70
|
+
abortActive(reason?: Error): void {
|
|
71
|
+
const active = this.current;
|
|
72
|
+
if (!active) return;
|
|
73
|
+
try {
|
|
74
|
+
active.abortController.abort(reason ?? new Error("Agent interrupted"));
|
|
75
|
+
} catch {}
|
|
76
|
+
try {
|
|
77
|
+
active.outerStream?.cancel(reason);
|
|
78
|
+
} catch {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Puts a prompt ahead of everything waiting (behind earlier interrupts) and aborts the active run, so it starts as soon as that run has settled; ignores midSession mode and maxQueued; in: run()/stream() with enqueue "interrupt", out: pendingQueue + the abort.
|
|
82
|
+
queueUrgent(entry: QueuedPrompt): void {
|
|
83
|
+
entry.urgent = true;
|
|
84
|
+
const firstOrdinary = this.queue.findIndex((e) => !e.urgent);
|
|
85
|
+
this.queue.splice(firstOrdinary < 0 ? this.queue.length : firstOrdinary, 0, entry);
|
|
86
|
+
this.host.persistNow();
|
|
87
|
+
this.abortActive();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Buffers a steer prompt into the active run and returns a promise for ITS
|
|
92
|
+
* reply: the active run's response when the prompt is injected, or (if the
|
|
93
|
+
* run ends first and the prompt becomes a queued follow-up) the follow-up
|
|
94
|
+
* run's response, so a late steer's answer is never lost.
|
|
95
|
+
*/
|
|
96
|
+
steerInto(active: ActiveRun, prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
97
|
+
this.assertSteerRoom(active.steerInbox);
|
|
98
|
+
const runDone = active.runPromise ?? active.outerStream?.result();
|
|
99
|
+
if (!runDone) return this.host.run(prompt, options) as Promise<AgentResponse>;
|
|
100
|
+
const entry: SteerEntry = { prompt, options };
|
|
101
|
+
const reply = new Promise<AgentResponse>((resolve, reject) => {
|
|
102
|
+
entry.settle = { resolve, reject };
|
|
103
|
+
});
|
|
104
|
+
reply.catch(() => {}); // a fire-and-forget caller must not trigger an unhandled rejection
|
|
105
|
+
active.steerInbox.push(entry);
|
|
106
|
+
this.host.persistNow();
|
|
107
|
+
runDone.then(
|
|
108
|
+
(res) => {
|
|
109
|
+
if (!entry.settle?.deferredToQueue) entry.settle?.resolve(res);
|
|
110
|
+
},
|
|
111
|
+
(err) => {
|
|
112
|
+
if (!entry.settle?.deferredToQueue) entry.settle?.reject(err);
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
return reply;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Starts the next waiting prompt (urgent ones first, aborted ones rejected) as a streaming or non-streaming run once the agent is idle; in: finishCurrentRun and queue changes, out: a new run whose result settles the queued caller.
|
|
119
|
+
pumpQueue(): void {
|
|
120
|
+
if (this.current !== null) return;
|
|
121
|
+
const next = this.queue.shift();
|
|
122
|
+
if (!next) return;
|
|
123
|
+
this.host.persistNow();
|
|
124
|
+
// Reject queued items whose signal aborted before start.
|
|
125
|
+
try {
|
|
126
|
+
if ((next.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
|
|
127
|
+
const reason = ((next.options as { signal?: AbortSignal }).signal as unknown as { reason?: unknown })?.reason;
|
|
128
|
+
const abortErr = Object.assign(
|
|
129
|
+
reason instanceof Error ? reason : new Error("Queued run aborted before start"),
|
|
130
|
+
{ name: "AbortError" }
|
|
131
|
+
);
|
|
132
|
+
try {
|
|
133
|
+
next.reject(abortErr);
|
|
134
|
+
} catch {}
|
|
135
|
+
try {
|
|
136
|
+
next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr)));
|
|
137
|
+
} catch {}
|
|
138
|
+
queueMicrotask(() => this.pumpQueue());
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
} catch {}
|
|
142
|
+
if (next.isStream && next.deferredStream) {
|
|
143
|
+
const deferred = next.deferredStream;
|
|
144
|
+
try {
|
|
145
|
+
const real = this.host.startStreamingRun(next.prompt, next.options);
|
|
146
|
+
try {
|
|
147
|
+
(deferred as any).__piped = true;
|
|
148
|
+
} catch {}
|
|
149
|
+
const forward = (e: StreamEvent) => {
|
|
150
|
+
try {
|
|
151
|
+
deferred.push(e);
|
|
152
|
+
} catch {}
|
|
153
|
+
};
|
|
154
|
+
real.on("*", forward as any);
|
|
155
|
+
try {
|
|
156
|
+
deferred.onCancel(() => {
|
|
157
|
+
try {
|
|
158
|
+
real.cancel();
|
|
159
|
+
} catch {}
|
|
160
|
+
});
|
|
161
|
+
} catch {}
|
|
162
|
+
real.result().then(
|
|
163
|
+
(res) => {
|
|
164
|
+
try {
|
|
165
|
+
next.resolve(res);
|
|
166
|
+
} catch {}
|
|
167
|
+
try {
|
|
168
|
+
deferred.end(res);
|
|
169
|
+
} catch {}
|
|
170
|
+
},
|
|
171
|
+
(err) => {
|
|
172
|
+
try {
|
|
173
|
+
next.reject(err);
|
|
174
|
+
} catch {}
|
|
175
|
+
// Pipe failures into the deferred stream so for-await sees them.
|
|
176
|
+
try {
|
|
177
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
178
|
+
} catch {}
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
try {
|
|
183
|
+
next.reject(err);
|
|
184
|
+
} catch {}
|
|
185
|
+
try {
|
|
186
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
187
|
+
} catch {}
|
|
188
|
+
// Keep draining even if starting failed.
|
|
189
|
+
queueMicrotask(() => this.pumpQueue());
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.host.startNonStreamingRun(next.prompt, next.options).then(next.resolve, next.reject);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Ends the active run slot: leftover steers become follow-ups at the FRONT of the queue (behind urgent ones), then the queue is pumped; in: a settled run, out: pendingQueue + persistNow.
|
|
197
|
+
finishCurrentRun(): void {
|
|
198
|
+
// Leftover steer entries become queued follow-ups instead of dropping.
|
|
199
|
+
let leftover: SteerEntry[] = [];
|
|
200
|
+
try {
|
|
201
|
+
leftover = this.current?.steerInbox?.splice(0) ?? [];
|
|
202
|
+
} catch {
|
|
203
|
+
leftover = [];
|
|
204
|
+
}
|
|
205
|
+
this.current = null;
|
|
206
|
+
if (leftover.length > 0) {
|
|
207
|
+
try {
|
|
208
|
+
const max = this.host.midSessionConfig.maxQueued ?? 20;
|
|
209
|
+
// Late steers were typed BEFORE anything already queued: they go to the front, in the order they arrived.
|
|
210
|
+
const lateSteers: QueuedPrompt[] = [];
|
|
211
|
+
for (const entry of leftover) {
|
|
212
|
+
try {
|
|
213
|
+
if ((entry.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
|
|
214
|
+
entry.settle?.reject(
|
|
215
|
+
Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" })
|
|
216
|
+
);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (this.queue.length + lateSteers.length >= max) {
|
|
223
|
+
entry.settle?.reject(
|
|
224
|
+
new ValidationError("[Agent Accelerator] Steer dropped: the follow-up queue is full.")
|
|
225
|
+
);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
// The steer was never injected: it now runs as a follow-up, and the
|
|
229
|
+
// caller's promise resolves with that run's response.
|
|
230
|
+
if (entry.settle) entry.settle.deferredToQueue = true;
|
|
231
|
+
lateSteers.push({
|
|
232
|
+
prompt: entry.prompt,
|
|
233
|
+
options: entry.options,
|
|
234
|
+
isStream: false,
|
|
235
|
+
resolve: entry.settle?.resolve ?? (() => {}),
|
|
236
|
+
reject: entry.settle?.reject ?? (() => {}),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// ...but behind an interrupt(message), which is the newest and most urgent instruction.
|
|
240
|
+
const firstOrdinary = this.queue.findIndex((e) => !e.urgent);
|
|
241
|
+
this.queue.splice(firstOrdinary < 0 ? this.queue.length : firstOrdinary, 0, ...lateSteers);
|
|
242
|
+
} catch {}
|
|
243
|
+
}
|
|
244
|
+
this.host.persistNow();
|
|
245
|
+
// Defer pump so the completing run's .then handlers settle first.
|
|
246
|
+
queueMicrotask(() => this.pumpQueue());
|
|
247
|
+
}
|
|
248
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -115,7 +115,7 @@ export {
|
|
|
115
115
|
type CatalogStatus,
|
|
116
116
|
type RefreshCatalogOptions,
|
|
117
117
|
} from "./models/catalog.ts";
|
|
118
|
-
export { resolveEffectiveThinking } from "./agent/
|
|
118
|
+
export { resolveEffectiveThinking } from "./agent/options.ts";
|
|
119
119
|
export { withRetries, isTransientError } from "./utils/retry.ts";
|
|
120
120
|
export { toConciseProviderError, assertModalitiesSupported, assertNoVideoPartsOnResponses } from "./utils/errors.ts";
|
|
121
121
|
// Native provider implementations.
|
|
@@ -222,7 +222,7 @@ export {
|
|
|
222
222
|
} from "./types/errors.ts";
|
|
223
223
|
export type { AgentAccelErrorCode, AgentAccelErrorContext } from "./types/errors.ts";
|
|
224
224
|
// Normalized run limits (sub-agents inherit unless overridden)
|
|
225
|
-
export type { AgentRunLimits } from "./agent/
|
|
225
|
+
export type { AgentRunLimits } from "./agent/options.ts";
|
|
226
226
|
|
|
227
227
|
// Telemetry bus: warnings, anomalies and per-turn thinking traces (attach with `telemetryBus.onEvent`)
|
|
228
228
|
export { telemetryBus } from "./telemetry.ts";
|
package/src/providers/google.ts
CHANGED
|
@@ -1559,8 +1559,7 @@ export class GoogleInteractionsProvider implements Provider {
|
|
|
1559
1559
|
// (JSON mocks) where no SSE frames were ever seen.
|
|
1560
1560
|
const hasTerminal = completedBody !== undefined || sawFinishReason || sawUsage;
|
|
1561
1561
|
// Trim provider trailing blank lines from finalized thinking (live
|
|
1562
|
-
// deltas already emitted raw
|
|
1563
|
-
// wrapThinking presentation layer). Stateful bodies unaffected.
|
|
1562
|
+
// deltas already emitted raw). Stateful bodies unaffected.
|
|
1564
1563
|
const cleanThinkingEarly = thinking.replace(/\n{3,}/g, "\n\n").trim();
|
|
1565
1564
|
const ctEntry = Object.entries(responseHeaders).find(([k]) => k.toLowerCase() === "content-type");
|
|
1566
1565
|
const isSSEPayload =
|
|
@@ -10,7 +10,7 @@ export interface EventStreamOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* When false, `partialText`/`partialThinking` cumulative fields are stripped
|
|
12
12
|
* from events before delivery. Default true for compat (providers attach
|
|
13
|
-
* cumulative partials
|
|
13
|
+
* cumulative partials).
|
|
14
14
|
*/
|
|
15
15
|
includePartial?: boolean;
|
|
16
16
|
/** Max buffered events when no iterator is attached. Default 1000. Oldest dropped. */
|
package/src/types/agent.ts
CHANGED
|
@@ -392,8 +392,6 @@ export interface AgentRunOptions {
|
|
|
392
392
|
onThinkingDelta?: (delta: string, event: import("./response.ts").StreamEvent) => void;
|
|
393
393
|
/** Called for every stream event (text_delta, thinking_delta, tool_call_complete, subagent_complete, etc.). */
|
|
394
394
|
onEvent?: (event: import("./response.ts").StreamEvent) => void;
|
|
395
|
-
/** When true, wraps reasoning stream as <think>\n...\n</think>\n\n — no manual isThinking needed. */
|
|
396
|
-
wrapThinking?: boolean;
|
|
397
395
|
/** Called after every model/tool turn with a turn summary (also drives sub-agent step logs + durable persistence). */
|
|
398
396
|
onTurn?: (turn: AgentTurnEvent) => void;
|
|
399
397
|
// Per-run knobs (all optional, additive; `output` intentionally
|
package/src/utils/headers.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function getPackageVersion(): string {
|
|
|
24
24
|
const v = (globalThis as any).process?.env?.npm_package_version;
|
|
25
25
|
if (typeof v === "string" && v) return v;
|
|
26
26
|
} catch {}
|
|
27
|
-
return "0.6.
|
|
27
|
+
return "0.6.6";
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// User-Agent string with version and platform; in: AGENT_ACCEL_USER_AGENT, out: a string.
|