agent-accelerator 0.3.0 → 0.4.5
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/package.json +1 -1
- package/src/agent/agent.ts +845 -116
- package/src/agent/context.ts +35 -0
- package/src/agent/delegation.ts +390 -36
- package/src/agent/loop.ts +785 -110
- package/src/agent/subagent.ts +59 -0
- package/src/index.ts +173 -59
- package/src/models/catalog-cache.ts +68 -25
- package/src/models/catalog.ts +123 -18
- package/src/providers/google.ts +152 -43
- package/src/providers/openai-compat.ts +246 -96
- package/src/providers/openai.ts +274 -79
- package/src/providers/openrouter.ts +191 -82
- package/src/providers/registry.ts +16 -12
- package/src/providers/shared.ts +190 -0
- package/src/providers.ts +259 -9
- package/src/session/store.ts +521 -33
- package/src/streaming/event-stream.ts +156 -41
- package/src/tools/executor.ts +248 -15
- package/src/tools/tool.ts +147 -3
- package/src/types/agent.ts +244 -2
- package/src/types/errors.ts +133 -0
- package/src/types/model.ts +22 -1
- package/src/types/response.ts +133 -3
- package/src/types/tool.ts +69 -0
- package/src/update-models.ts +8 -2
- package/src/utils/cache.ts +68 -1
- package/src/utils/env.ts +23 -13
- package/src/utils/errors.ts +15 -1
- package/src/utils/headers.ts +153 -19
- package/src/utils/retry.ts +27 -13
- package/src/utils/session.ts +284 -0
- package/src/providers/openrouter-responses.ts +0 -949
- package/src/types/provider-payloads.ts +0 -352
package/src/agent/subagent.ts
CHANGED
|
@@ -7,13 +7,44 @@ import { getSubModel, getModel } from "../utils/env.ts";
|
|
|
7
7
|
/** AgentConfig alias for a delegated worker agent. */
|
|
8
8
|
export interface SubAgentConfig extends AgentConfig {}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Canonical env-based construction (Q-56): builds an `Agent` reading
|
|
12
|
+
* `MODEL`/`SUB_AGENT_MODEL` only for the fields the caller omits, via
|
|
13
|
+
* `getModel()`/`getSubModel()`. Explicit config always wins; constructing an
|
|
14
|
+
* `Agent` with an explicit `model` reads no env for routing. The static
|
|
15
|
+
* `Agent.fromEnv()` (attached below without touching `agent.ts`, owned by
|
|
16
|
+
* another audit lane) delegates here.
|
|
17
|
+
*/
|
|
18
|
+
export function agentFromEnv(
|
|
19
|
+
config?: Omit<AgentConfig, "model"> & { model?: AgentConfig["model"] }
|
|
20
|
+
): Agent {
|
|
21
|
+
return new Agent({
|
|
22
|
+
...config,
|
|
23
|
+
model: config?.model ?? getModel() ?? getSubModel(),
|
|
24
|
+
} as AgentConfig);
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
/**
|
|
11
28
|
* SubAgent is a specialized Agent designed for modular delegation,
|
|
12
29
|
* evaluation, reviewing, or parallel task execution.
|
|
13
30
|
*
|
|
31
|
+
* @deprecated Prefer composing a plain `Agent` and exposing it via
|
|
32
|
+
* `agentToTool()`/`buildAgentTools()` (or `subagents: [...]`) instead of
|
|
33
|
+
* subclassing. `SubAgent` stays for compat and receives no new features.
|
|
34
|
+
*
|
|
14
35
|
* It inherits 100% of the capabilities of Agent, defaults to SUB_AGENT_MODEL,
|
|
15
36
|
* and provides .asTool() and .toTool() for seamless registration into parent agents.
|
|
16
37
|
*
|
|
38
|
+
* Model fallback chain (Q-56, canonical): explicit `config.model` ??
|
|
39
|
+
* `SUB_AGENT_MODEL` (`getSubModel()`) ?? `MODEL` (`getModel()`). Missing
|
|
40
|
+
* everything throws. Constructing any `Agent`/`SubAgent` with an explicit
|
|
41
|
+
* `model` reads no env for routing.
|
|
42
|
+
*
|
|
43
|
+
* Q-47 decomposition boundary: this class stays a thin wrapper. The run/stream
|
|
44
|
+
* loops live in `agent.ts`/`loop.ts`, spawning in `delegation.ts`
|
|
45
|
+
* (`agentToTool`), and model parsing in `utils/session.ts` (`parseModelRef`).
|
|
46
|
+
* No loop, transport, or formatting logic is duplicated here.
|
|
47
|
+
*
|
|
17
48
|
* @example
|
|
18
49
|
* ```ts
|
|
19
50
|
* const researcher = new SubAgent({
|
|
@@ -81,3 +112,31 @@ export class SubAgent extends Agent {
|
|
|
81
112
|
return this.asTool(nameOverride, descriptionOverride);
|
|
82
113
|
}
|
|
83
114
|
}
|
|
115
|
+
|
|
116
|
+
// Q-56: `Agent.fromEnv()` static without touching `agent.ts` (owned by
|
|
117
|
+
// another lane). Wired at module load; typed via the declaration below so
|
|
118
|
+
// `Agent.fromEnv({...})` typechecks wherever this module is imported.
|
|
119
|
+
declare module "./agent.ts" {
|
|
120
|
+
namespace Agent {
|
|
121
|
+
/**
|
|
122
|
+
* Builds an `Agent` from env (`MODEL`/`SUB_AGENT_MODEL`) for omitted
|
|
123
|
+
* fields. Explicit config always wins. See `agentFromEnv`.
|
|
124
|
+
*/
|
|
125
|
+
function fromEnv(
|
|
126
|
+
config?: Omit<import("../types/agent.ts").AgentConfig, "model"> & {
|
|
127
|
+
model?: import("../types/agent.ts").AgentConfig["model"];
|
|
128
|
+
}
|
|
129
|
+
): Agent;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const Ctor = Agent as unknown as {
|
|
135
|
+
fromEnv?: (config?: SubAgentConfig) => Agent;
|
|
136
|
+
};
|
|
137
|
+
if (typeof Ctor.fromEnv !== "function") {
|
|
138
|
+
Ctor.fromEnv = (config?: SubAgentConfig) => agentFromEnv(config);
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// why: static attachment must never break module load (host may freeze Agent).
|
|
142
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { AssistantMessageEventStream as AssistantMessageEventStreamT } from "./streaming/event-stream.ts";
|
|
3
|
+
import type { AgentResponse as AgentResponseT } from "./types/response.ts";
|
|
4
|
+
|
|
5
|
+
// Q-50 public API budget: ~25 core runtime exports + compat/deprecation shims.
|
|
6
|
+
// Core = Agent, tool, SubAgent (deprecated alias), AgentResponse,
|
|
7
|
+
// AssistantMessageEventStream, document conversion, delegation primitives,
|
|
8
|
+
// provider registry, catalog, errors, and option/normalization helpers.
|
|
9
|
+
// Everything else below is a shim kept for compat and marked @deprecated
|
|
10
|
+
// toward a future `/internal` entry. Nothing is deleted (would break tests).
|
|
11
|
+
//
|
|
12
|
+
// Q-27 node split (deferred): this root entry pulls `session/store.ts`, which
|
|
13
|
+
// imports `node:fs` for session persistence. A future `/node` subpath will
|
|
14
|
+
// host the fs-backed persistence/telemetry surface; the root entry stays
|
|
15
|
+
// runtime-agnostic. Until then, browser/bundler consumers should import the
|
|
16
|
+
// documented core shims only.
|
|
17
|
+
//
|
|
18
|
+
// Q-51 one way to run: `run()` is canonical. `ask()` and `run/stream: true`
|
|
19
|
+
// remain for compat but are deprecated — prefer `run()` for single-shot and
|
|
20
|
+
// `stream()` for event iteration. `Run` (below) is the typed streaming handle.
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Typed streaming run handle (Q-51): the `AssistantMessageEventStream`
|
|
24
|
+
* returned by `stream()`/`run(stream:true)`, with its terminal `result()` and
|
|
25
|
+
* `cancel()` surfaced on the type for callers that only need the handle.
|
|
26
|
+
*/
|
|
27
|
+
export type Run = AssistantMessageEventStreamT & {
|
|
28
|
+
result: () => Promise<AgentResponseT>;
|
|
29
|
+
cancel: (reason?: unknown) => void;
|
|
30
|
+
};
|
|
2
31
|
|
|
3
32
|
// Core Agent & Tool Classes
|
|
4
33
|
export { Agent, SubAgentModelError } from "./agent/agent.ts";
|
|
5
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Compat alias: prefer composing a plain `Agent` and exposing it
|
|
36
|
+
* via `agentToTool()`/`buildAgentTools()` (or `subagents: [...]`). `SubAgent`
|
|
37
|
+
* stays for compat and receives no new features (Q-56).
|
|
38
|
+
*/
|
|
39
|
+
export { SubAgent, agentFromEnv } from "./agent/subagent.ts";
|
|
6
40
|
export type { SubAgentConfig } from "./agent/subagent.ts";
|
|
7
|
-
export {
|
|
41
|
+
export {
|
|
42
|
+
tool,
|
|
43
|
+
toStandardToolDeclarations,
|
|
44
|
+
assertValidToolName,
|
|
45
|
+
TOOL_NAME_PATTERN,
|
|
46
|
+
normalizeThinkingV2,
|
|
47
|
+
resolveWhenBusy,
|
|
48
|
+
normalizeWorkerTimeout,
|
|
49
|
+
normalizeMaxAttempts,
|
|
50
|
+
} from "./tools/tool.ts";
|
|
8
51
|
export type { CreateToolOptions } from "./tools/tool.ts";
|
|
9
52
|
export { zodToJsonSchema, cleanJsonSchema } from "./tools/schema.ts";
|
|
53
|
+
/**
|
|
54
|
+
* @deprecated Internal executor surface. Prefer `Agent` runs; for direct
|
|
55
|
+
* execution import from the future `/internal` entry (Q-50).
|
|
56
|
+
*/
|
|
10
57
|
export { executeToolCalls } from "./tools/executor.ts";
|
|
11
58
|
|
|
12
59
|
// Client-side document conversion (optional anydoc peer — see utils/documents.ts)
|
|
@@ -35,10 +82,23 @@ export {
|
|
|
35
82
|
createFixedChildSessionId,
|
|
36
83
|
isSessionDescendant,
|
|
37
84
|
getSubAgentTrace,
|
|
85
|
+
getSubAgentTraceScoped,
|
|
38
86
|
listSubAgentTraceIds,
|
|
39
87
|
subscribeToSubAgent,
|
|
88
|
+
appendSubAgentStep,
|
|
89
|
+
pruneTraces,
|
|
90
|
+
trimTraceSteps,
|
|
91
|
+
checkBudget,
|
|
92
|
+
chargeBudgetUsage,
|
|
93
|
+
reserveSpawn,
|
|
94
|
+
isStreamingLoop,
|
|
95
|
+
escapeXml as escapeXmlFromDelegation,
|
|
96
|
+
MAX_SUBAGENT_TRACES,
|
|
97
|
+
SUBAGENT_TRACE_TTL_MS,
|
|
98
|
+
MAX_SUBAGENT_STEPS_PER_TRACE,
|
|
99
|
+
SUBAGENT_SUBSCRIBE_TTL_MS,
|
|
40
100
|
} from "./agent/delegation.ts";
|
|
41
|
-
export type { DynamicSubagentTask, AgentAsToolTarget, SubAgentTrace } from "./agent/delegation.ts";
|
|
101
|
+
export type { DynamicSubagentTask, AgentAsToolTarget, SubAgentTrace, StreamingLoopHandle } from "./agent/delegation.ts";
|
|
42
102
|
|
|
43
103
|
// Providers & Registry — unified multi-provider layer, all native REST
|
|
44
104
|
export {
|
|
@@ -57,6 +117,9 @@ export {
|
|
|
57
117
|
getModelThinkingInfo,
|
|
58
118
|
validateModelThinking,
|
|
59
119
|
getModelsForProvider,
|
|
120
|
+
registerModel,
|
|
121
|
+
clearCustomModels,
|
|
122
|
+
getModelSource,
|
|
60
123
|
ThinkingLevelError,
|
|
61
124
|
refreshModelCatalog,
|
|
62
125
|
ensureModelCatalogFresh,
|
|
@@ -74,34 +137,56 @@ export {
|
|
|
74
137
|
export { resolveEffectiveThinking } from "./agent/agent.ts";
|
|
75
138
|
export { withRetries, isTransientError } from "./utils/retry.ts";
|
|
76
139
|
export { toConciseProviderError, assertModalitiesSupported, assertNoVideoPartsOnResponses } from "./utils/errors.ts";
|
|
77
|
-
// Native provider implementations.
|
|
78
|
-
|
|
140
|
+
// Native provider implementations.
|
|
141
|
+
/** @deprecated The discontinued beta-Responses transport was removed
|
|
142
|
+
* directly for frozen wire-contract evidence only. It is wired nowhere and
|
|
143
|
+
* must not be extended or re-exported here (Q-46). */
|
|
79
144
|
export {
|
|
80
145
|
OpenAICompatibleChatProvider,
|
|
81
|
-
OpenAICompatibleChatProvider as OpenAICompatibleProvider,
|
|
82
146
|
createOpenAICompatibleProvider,
|
|
83
147
|
createCustomProvider,
|
|
84
148
|
CustomProvider,
|
|
85
149
|
} from "./providers/openai-compat.ts";
|
|
150
|
+
/**
|
|
151
|
+
* @deprecated Compat alias of `OpenAICompatibleChatProvider` (canonical).
|
|
152
|
+
* Kept for back-compat only; new code uses the canonical name (Q-50).
|
|
153
|
+
*/
|
|
154
|
+
export { OpenAICompatibleChatProvider as OpenAICompatibleProvider } from "./providers/openai-compat.ts";
|
|
86
155
|
export type { CustomProviderOptions } from "./providers/openai-compat.ts";
|
|
87
156
|
|
|
88
157
|
// Canonical provider contract (provider-agnostic) + native adapters.
|
|
89
158
|
export {
|
|
90
159
|
GoogleInteractionsProvider,
|
|
91
|
-
GoogleInteractionsProvider as GoogleAIStudioProvider,
|
|
92
160
|
clearInteractionChains,
|
|
93
161
|
} from "./providers/google.ts";
|
|
162
|
+
/**
|
|
163
|
+
* @deprecated Compat alias of `GoogleInteractionsProvider` (canonical).
|
|
164
|
+
* Kept for back-compat only; new code uses the canonical name (Q-50).
|
|
165
|
+
*/
|
|
166
|
+
export { GoogleInteractionsProvider as GoogleAIStudioProvider } from "./providers/google.ts";
|
|
94
167
|
export {
|
|
95
168
|
OpenRouterChatCompletionsProvider,
|
|
96
|
-
OpenRouterChatCompletionsProvider as OpenRouterProvider,
|
|
97
169
|
} from "./providers/openrouter.ts";
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
170
|
+
/**
|
|
171
|
+
* @deprecated Compat alias of `OpenRouterChatCompletionsProvider`
|
|
172
|
+
* (canonical). Kept for back-compat only (Q-50).
|
|
173
|
+
*/
|
|
174
|
+
export { OpenRouterChatCompletionsProvider as OpenRouterProvider } from "./providers/openrouter.ts";
|
|
175
|
+
// Discontinued beta-Responses transport, removed from core
|
|
176
|
+
// (was frozen migration evidence, wired nowhere): OpenRouterResponsesProvider.
|
|
101
177
|
export {
|
|
102
178
|
OpenAIResponsesProvider,
|
|
103
|
-
OpenAIResponsesProvider as OpenAIProvider,
|
|
104
179
|
} from "./providers/openai.ts";
|
|
180
|
+
/**
|
|
181
|
+
* @deprecated Compat alias of `OpenAIResponsesProvider` (canonical).
|
|
182
|
+
* Kept for back-compat only; new code uses the canonical name (Q-50).
|
|
183
|
+
*/
|
|
184
|
+
export { OpenAIResponsesProvider as OpenAIProvider } from "./providers/openai.ts";
|
|
185
|
+
/**
|
|
186
|
+
* @deprecated Internal provider mappers/routing helpers. Prefer the
|
|
187
|
+
* provider-agnostic registry (`resolveModel`/`getProvider`); direct mapper
|
|
188
|
+
* use moves to the future `/internal` entry (Q-50).
|
|
189
|
+
*/
|
|
105
190
|
export {
|
|
106
191
|
emitProviderWarning,
|
|
107
192
|
clearEmittedWarnings,
|
|
@@ -125,6 +210,15 @@ export {
|
|
|
125
210
|
mapToolChoiceToOpenAI,
|
|
126
211
|
normalizeToolChoice,
|
|
127
212
|
parseStreamedToolArguments,
|
|
213
|
+
normalizeStructuredOutput,
|
|
214
|
+
extractOutputValidator,
|
|
215
|
+
parseStructuredOutput,
|
|
216
|
+
mapOutputToOpenAI,
|
|
217
|
+
mapOutputToGoogle,
|
|
218
|
+
mapOutputToOpenRouterChat,
|
|
219
|
+
mapOutputToCompatChat,
|
|
220
|
+
buildStructuredOutputInstruction,
|
|
221
|
+
augmentSystemPromptWithStructuredOutput,
|
|
128
222
|
} from "./providers.ts";
|
|
129
223
|
export type {
|
|
130
224
|
ProviderCapabilityStatus,
|
|
@@ -132,13 +226,21 @@ export type {
|
|
|
132
226
|
OpenRouterToolChoice,
|
|
133
227
|
OpenRouterChatToolChoice,
|
|
134
228
|
OpenAIToolChoice,
|
|
229
|
+
ChatCompletionsResponseFormat,
|
|
135
230
|
} from "./providers.ts";
|
|
136
231
|
|
|
137
232
|
export {
|
|
138
233
|
GOOGLE_MODELS,
|
|
139
234
|
OPENAI_MODELS,
|
|
140
235
|
OPENROUTER_MODELS,
|
|
236
|
+
modelSupportsStructuredOutput,
|
|
141
237
|
} from "./models/catalog.ts";
|
|
238
|
+
export {
|
|
239
|
+
hasAuthHeader,
|
|
240
|
+
clampToolCallId,
|
|
241
|
+
hasTruncatedToolArguments,
|
|
242
|
+
shortHashId,
|
|
243
|
+
} from "./providers/shared.ts";
|
|
142
244
|
|
|
143
245
|
export {
|
|
144
246
|
isValidThoughtSignature,
|
|
@@ -161,11 +263,34 @@ export type {
|
|
|
161
263
|
|
|
162
264
|
// Streaming & Events
|
|
163
265
|
export { AssistantMessageEventStream } from "./streaming/event-stream.ts";
|
|
266
|
+
/**
|
|
267
|
+
* @deprecated Internal SSE plumbing. Prefer `AssistantMessageEventStream`;
|
|
268
|
+
* direct parser use moves to the future `/internal` entry (Q-50).
|
|
269
|
+
*/
|
|
164
270
|
export { SSEParser } from "./streaming/sse-parser.ts";
|
|
271
|
+
export type { SteerEntry } from "./agent/loop.ts";
|
|
165
272
|
|
|
166
273
|
// Responses
|
|
167
274
|
export { AgentResponse } from "./types/response.ts";
|
|
168
275
|
|
|
276
|
+
// Core error taxonomy (Q-50 core surface)
|
|
277
|
+
export {
|
|
278
|
+
AgentAccelError,
|
|
279
|
+
ConfigError,
|
|
280
|
+
ProviderError,
|
|
281
|
+
TimeoutError,
|
|
282
|
+
BudgetExceededError,
|
|
283
|
+
ToolError,
|
|
284
|
+
ValidationError,
|
|
285
|
+
SessionLoadError,
|
|
286
|
+
SecurityPolicyError,
|
|
287
|
+
isAbortError,
|
|
288
|
+
formatErrorPlain,
|
|
289
|
+
} from "./types/errors.ts";
|
|
290
|
+
export type { AgentAccelErrorCode, AgentAccelErrorContext } from "./types/errors.ts";
|
|
291
|
+
// Normalized run limits (Q-22; sub-agents inherit unless overridden)
|
|
292
|
+
export type { AgentRunLimits } from "./agent/agent.ts";
|
|
293
|
+
|
|
169
294
|
// Session persistence & telemetry (conversation history + cost rollup)
|
|
170
295
|
export {
|
|
171
296
|
SessionTelemetry,
|
|
@@ -191,14 +316,29 @@ export {
|
|
|
191
316
|
export type { PersistedAgentSession, SessionTotals, SessionAgentLike, LoadedSessionDir, PersistedSubAgentTrace } from "./session/store.ts";
|
|
192
317
|
|
|
193
318
|
// Utilities
|
|
194
|
-
|
|
319
|
+
/**
|
|
320
|
+
* @deprecated Session-routing internals. Prefer agent-level `sessionId`;
|
|
321
|
+
* direct use moves to the future `/internal` entry (Q-50).
|
|
322
|
+
*/
|
|
323
|
+
export { createSessionId, hashSessionPart, createTrackingId, nowMs, newRunId, newTurnId, previewPrompt, parseModelRef, resolveAgentConfig } from "./utils/session.ts";
|
|
324
|
+
export type { ParsedModelRef, ResolvedAgentConfig } from "./utils/session.ts";
|
|
325
|
+
/**
|
|
326
|
+
* @deprecated Raw env access. Prefer explicit `Agent` config (which wins over
|
|
327
|
+
* env) and `Agent.fromEnv()`/`agentFromEnv()`; direct use moves to the future
|
|
328
|
+
* `/internal` entry (Q-50).
|
|
329
|
+
*/
|
|
195
330
|
export { getApiKey, getEnv } from "./utils/env.ts";
|
|
196
331
|
export { buildSessionHeaders } from "./utils/headers.ts";
|
|
197
332
|
export { normalizeMediaInput, inferMimeType } from "./utils/media.ts";
|
|
198
333
|
export { base64ToBytes, bytesToBase64 } from "./utils/base64.ts";
|
|
199
334
|
export { toJsonSafe, safeStringify, escapeXml } from "./utils/serialization.ts";
|
|
200
335
|
|
|
201
|
-
// Re-export Zod
|
|
336
|
+
// Re-export Zod (compat only)
|
|
337
|
+
/**
|
|
338
|
+
* @deprecated Compat re-export: prefer adding `zod` as a direct (peer)
|
|
339
|
+
* dependency and importing from `"zod"` (Q-50). Kept so existing
|
|
340
|
+
* `import { z } from "agent-accelerator"` code keeps working.
|
|
341
|
+
*/
|
|
202
342
|
export { z };
|
|
203
343
|
|
|
204
344
|
// TypeScript Type Exports
|
|
@@ -233,6 +373,8 @@ export type {
|
|
|
233
373
|
ToolCallRecord,
|
|
234
374
|
ToolResultRecord,
|
|
235
375
|
StandardToolDeclaration,
|
|
376
|
+
ToolLogger,
|
|
377
|
+
ToolEmitFn,
|
|
236
378
|
} from "./types/tool.ts";
|
|
237
379
|
|
|
238
380
|
export type {
|
|
@@ -243,6 +385,7 @@ export type {
|
|
|
243
385
|
ProviderRequestOptions,
|
|
244
386
|
ProviderGenerateResult,
|
|
245
387
|
ProviderRawData,
|
|
388
|
+
StructuredOutputSpec,
|
|
246
389
|
} from "./types/model.ts";
|
|
247
390
|
|
|
248
391
|
export type {
|
|
@@ -258,50 +401,21 @@ export type {
|
|
|
258
401
|
AgentRunOptions,
|
|
259
402
|
AgentTurnEvent,
|
|
260
403
|
DynamicSubagentsConfig,
|
|
404
|
+
MidSessionConfig,
|
|
405
|
+
MidSessionMode,
|
|
406
|
+
WhenBusyMode,
|
|
407
|
+
ModelRef,
|
|
408
|
+
ThinkingOption,
|
|
409
|
+
StructuredOutputOption,
|
|
410
|
+
SamplingConfig,
|
|
411
|
+
RetryPolicyConfig,
|
|
412
|
+
AgentLogger,
|
|
413
|
+
ToolChoiceOption,
|
|
414
|
+
StopOption,
|
|
415
|
+
Budget,
|
|
416
|
+
BudgetState,
|
|
417
|
+
SubAgentRunIds,
|
|
261
418
|
} from "./types/agent.ts";
|
|
262
419
|
|
|
263
|
-
|
|
264
|
-
//
|
|
265
|
-
// NOTE: wire `OpenAIToolChoice` intentionally omitted — it duplicates the
|
|
266
|
-
// canonical `OpenAIToolChoice` exported above (same shape); use that one.
|
|
267
|
-
export type {
|
|
268
|
-
GoogleThinkingLevel,
|
|
269
|
-
GoogleThinkingConfig,
|
|
270
|
-
GoogleFunctionCallingMode,
|
|
271
|
-
GoogleFunctionCallingConfig,
|
|
272
|
-
GoogleToolConfig,
|
|
273
|
-
GoogleFunctionDeclaration,
|
|
274
|
-
GoogleTool,
|
|
275
|
-
GoogleBlob,
|
|
276
|
-
GooglePart,
|
|
277
|
-
GoogleContent,
|
|
278
|
-
GoogleGenerationConfig,
|
|
279
|
-
GoogleCandidate,
|
|
280
|
-
GoogleUsageMetadata,
|
|
281
|
-
GoogleGenerateContentRequest,
|
|
282
|
-
GoogleGenerateContentResponse,
|
|
283
|
-
OpenRouterProviderRouting,
|
|
284
|
-
OpenRouterReasoning,
|
|
285
|
-
OpenRouterParameters,
|
|
286
|
-
OpenRouterUsage,
|
|
287
|
-
OpenRouterChatRequest,
|
|
288
|
-
OpenRouterResponse,
|
|
289
|
-
OpenAIMessageRole,
|
|
290
|
-
OpenAIReasoningEffort,
|
|
291
|
-
OpenAIServiceTier,
|
|
292
|
-
OpenAITextPart,
|
|
293
|
-
OpenAIImageUrlPart,
|
|
294
|
-
OpenAIInputAudioPart,
|
|
295
|
-
OpenAIVideoUrlPart,
|
|
296
|
-
OpenAIContentPart,
|
|
297
|
-
OpenAIToolCall,
|
|
298
|
-
OpenAIMessage,
|
|
299
|
-
OpenAITool,
|
|
300
|
-
OpenAIUsage,
|
|
301
|
-
OpenAIChoice,
|
|
302
|
-
OpenAIDelta,
|
|
303
|
-
OpenAIChunkChoice,
|
|
304
|
-
OpenAIChatCompletionRequest,
|
|
305
|
-
OpenAIChatCompletionResponse,
|
|
306
|
-
OpenAIChatCompletionChunk,
|
|
307
|
-
} from "./types/provider-payloads.ts";
|
|
420
|
+
// Minimalist core: harness modules (RLM/RSI, OTel, testing, CLI doctor, hooks,
|
|
421
|
+
// context-ops, run-tree/record, session-store, legacy wire types) live in userland.
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import { getEnv } from "../utils/env.ts";
|
|
4
5
|
|
|
5
6
|
/** Default TTL: 12 Hours (in milliseconds). */
|
|
6
7
|
export const DEFAULT_CATALOG_TTL_MS = 12 * 60 * 60 * 1000;
|
|
7
8
|
|
|
9
|
+
/** Max accepted models.dev payload size: 15MB (Q-16a). Larger payloads are rejected before parse. */
|
|
10
|
+
export const MAX_CATALOG_PAYLOAD_BYTES = 15 * 1024 * 1024;
|
|
11
|
+
|
|
8
12
|
let globalCatalogTtlMs = DEFAULT_CATALOG_TTL_MS;
|
|
9
13
|
|
|
10
|
-
/** Sets the global model catalog cache TTL in milliseconds.
|
|
14
|
+
/** Sets the global model catalog cache TTL in milliseconds.
|
|
15
|
+
* Note (Q-16a): cache files carry their own `ttlMs`. A global `setCatalogTTL`
|
|
16
|
+
* does not rewrite in-flight file TTLs; it takes effect on the next refresh
|
|
17
|
+
* (fresh download) or when the cached file has no `ttlMs` of its own.
|
|
18
|
+
*/
|
|
11
19
|
export function setCatalogTTL(ttlMs: number): void {
|
|
12
20
|
if (Number.isFinite(ttlMs) && ttlMs > 0) {
|
|
13
21
|
globalCatalogTtlMs = ttlMs;
|
|
@@ -77,9 +85,9 @@ export function getCacheDir(): string {
|
|
|
77
85
|
return path.resolve(process.cwd(), "src/data");
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
/**
|
|
88
|
+
/** Cache file path: `<cacheDir>/models.dev.json`, honoring AGENT_CACHE_DIR (Q-16a/Q-34). */
|
|
81
89
|
export function getCacheFilePath(): string {
|
|
82
|
-
return path.
|
|
90
|
+
return path.join(getCacheDir(), "models.dev.json");
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
function readJsonFileSync(filePath: string): any {
|
|
@@ -97,7 +105,20 @@ function writeJsonFileSync(filePath: string, data: any): void {
|
|
|
97
105
|
if (!fs.existsSync(dir)) {
|
|
98
106
|
fs.mkdirSync(dir, { recursive: true });
|
|
99
107
|
}
|
|
100
|
-
|
|
108
|
+
// Atomic tmp+rename with 0600 so a crash never leaves half-written JSON (Q-16a).
|
|
109
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
110
|
+
const tmp = `${filePath}.tmp-${process.pid}-${rand}`;
|
|
111
|
+
fs.writeFileSync(tmp, JSON.stringify(data), { encoding: "utf-8", mode: 0o600 });
|
|
112
|
+
try { fs.chmodSync(tmp, 0o600); } catch {}
|
|
113
|
+
fs.renameSync(tmp, filePath);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Verifies optional sha256 pin from MODELS_DEV_SHA256 (Q-16a). Returns false on mismatch. */
|
|
117
|
+
export function verifyCatalogSha256(text: string): boolean {
|
|
118
|
+
const pin = getEnv("MODELS_DEV_SHA256")?.trim().toLowerCase();
|
|
119
|
+
if (!pin) return true;
|
|
120
|
+
const hex = createHash("sha256").update(text, "utf8").digest("hex").toLowerCase();
|
|
121
|
+
return hex === pin;
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
// In-memory catalog state
|
|
@@ -225,6 +246,17 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
225
246
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
226
247
|
let freshData: Record<string, any>;
|
|
227
248
|
|
|
249
|
+
const fallbackToDiskCache = (): void => {
|
|
250
|
+
const existing = readJsonFileSync(cachePath);
|
|
251
|
+
if (existing && existing.data) {
|
|
252
|
+
activeCatalog = existing.data;
|
|
253
|
+
activeFetchedAt = existing.fetchedAt;
|
|
254
|
+
activeTtlMs = existing.ttlMs || effectiveTtl;
|
|
255
|
+
activeFromCache = true;
|
|
256
|
+
notifyUpdateListeners();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
228
260
|
try {
|
|
229
261
|
const res = await fetch(apiUrl, {
|
|
230
262
|
headers: {
|
|
@@ -238,33 +270,40 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
238
270
|
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
239
271
|
}
|
|
240
272
|
|
|
241
|
-
|
|
273
|
+
// Q-16a: size-cap before parse (check text length, reject >15MB).
|
|
274
|
+
const text = await res.text();
|
|
275
|
+
if (text.length > MAX_CATALOG_PAYLOAD_BYTES) {
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
fallbackToDiskCache();
|
|
278
|
+
return getCatalogStatus();
|
|
279
|
+
}
|
|
280
|
+
// Q-16a: optional sha256 pin via MODELS_DEV_SHA256.
|
|
281
|
+
if (!verifyCatalogSha256(text)) {
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
fallbackToDiskCache();
|
|
284
|
+
return getCatalogStatus();
|
|
285
|
+
}
|
|
286
|
+
let parsed: unknown;
|
|
287
|
+
try {
|
|
288
|
+
parsed = JSON.parse(text);
|
|
289
|
+
} catch {
|
|
290
|
+
clearTimeout(timer);
|
|
291
|
+
fallbackToDiskCache();
|
|
292
|
+
return getCatalogStatus();
|
|
293
|
+
}
|
|
294
|
+
freshData = parsed as Record<string, any>;
|
|
242
295
|
|
|
243
296
|
// Never let a non-catalog payload (mocked fetch, proxy error page,
|
|
244
297
|
// chat-completion stub) overwrite the good on-disk cache.
|
|
245
298
|
if (!isValidCatalogPayload(freshData)) {
|
|
246
299
|
clearTimeout(timer);
|
|
247
|
-
|
|
248
|
-
if (existing && existing.data) {
|
|
249
|
-
activeCatalog = existing.data;
|
|
250
|
-
activeFetchedAt = existing.fetchedAt;
|
|
251
|
-
activeTtlMs = existing.ttlMs || effectiveTtl;
|
|
252
|
-
activeFromCache = true;
|
|
253
|
-
notifyUpdateListeners();
|
|
254
|
-
}
|
|
300
|
+
fallbackToDiskCache();
|
|
255
301
|
return getCatalogStatus();
|
|
256
302
|
}
|
|
257
303
|
} catch (err: any) {
|
|
258
304
|
clearTimeout(timer);
|
|
259
305
|
// If download fails, retain disk cache if available
|
|
260
|
-
|
|
261
|
-
if (existing && existing.data) {
|
|
262
|
-
activeCatalog = existing.data;
|
|
263
|
-
activeFetchedAt = existing.fetchedAt;
|
|
264
|
-
activeTtlMs = existing.ttlMs || effectiveTtl;
|
|
265
|
-
activeFromCache = true;
|
|
266
|
-
notifyUpdateListeners();
|
|
267
|
-
}
|
|
306
|
+
fallbackToDiskCache();
|
|
268
307
|
return getCatalogStatus();
|
|
269
308
|
} finally {
|
|
270
309
|
clearTimeout(timer);
|
|
@@ -281,19 +320,23 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
281
320
|
|
|
282
321
|
// Update in-memory state FIRST so a read-only filesystem still serves the
|
|
283
322
|
// fresh catalog for this process; disk persistence below is best-effort.
|
|
323
|
+
// Q-34: a real download is NOT from cache.
|
|
284
324
|
activeCatalog = freshData;
|
|
285
325
|
activeFetchedAt = fetchedAt;
|
|
286
326
|
activeTtlMs = effectiveTtl;
|
|
287
|
-
activeFromCache =
|
|
327
|
+
activeFromCache = false;
|
|
288
328
|
|
|
289
329
|
// Save to src/data/models.dev.json (or the AGENT_CACHE_DIR override)
|
|
290
330
|
try {
|
|
291
331
|
writeJsonFileSync(cachePath, cachePayload);
|
|
292
332
|
} catch (err) {
|
|
293
333
|
activeFromCache = false;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
334
|
+
try {
|
|
335
|
+
(globalThis as any).__agentAccelTelemetry?.emitWarning?.({
|
|
336
|
+
code: "catalog_cache_write_failed",
|
|
337
|
+
message: `[Agent Accelerator] Could not write model catalog cache (${err instanceof Error ? err.message : String(err)}). Continuing with the in-memory catalog.`,
|
|
338
|
+
});
|
|
339
|
+
} catch {}
|
|
297
340
|
}
|
|
298
341
|
|
|
299
342
|
notifyUpdateListeners();
|