@armadra/agent 0.7.2 → 0.7.4
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/CHANGELOG.md +41 -0
- package/CHANGELOG.zh-CN.md +41 -0
- package/dist/agent/retry.d.ts +14 -5
- package/dist/agent/retry.js +30 -8
- package/dist/agent/session-cache-key.d.ts +8 -0
- package/dist/agent/session-cache-key.js +46 -0
- package/dist/agent/session-cache.d.ts +2 -3
- package/dist/agent/session-cache.js +19 -37
- package/dist/agent/session-classifier.d.ts +3 -2
- package/dist/agent/session-classifier.js +36 -10
- package/dist/agent/session-compaction.d.ts +30 -5
- package/dist/agent/session-compaction.js +69 -29
- package/dist/agent/session-core.d.ts +2 -0
- package/dist/agent/session-run.d.ts +12 -0
- package/dist/agent/session-run.js +29 -10
- package/dist/agent/session-subagent.d.ts +8 -1
- package/dist/agent/session-subagent.js +37 -14
- package/dist/agent/session-sync.d.ts +8 -2
- package/dist/agent/session-sync.js +7 -10
- package/dist/agent/session-tools.js +3 -2
- package/dist/agent/subagent-fork.d.ts +67 -0
- package/dist/agent/subagent-fork.js +87 -0
- package/dist/agent/subagent-registry.d.ts +2 -2
- package/dist/agent/subagent-registry.js +2 -2
- package/dist/agent/tool-availability.d.ts +8 -0
- package/dist/agent/tool-availability.js +8 -0
- package/dist/agent/tool-runner.js +13 -4
- package/dist/agent/worktree.d.ts +8 -0
- package/dist/agent/worktree.js +51 -4
- package/dist/agents/parse.js +6 -1
- package/dist/agents/task-record.js +3 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/ai/apis/anthropic-messages.js +10 -5
- package/dist/ai/apis/anthropic-request.d.ts +2 -1
- package/dist/ai/apis/anthropic-request.js +9 -3
- package/dist/ai/apis/google-generative-ai.js +4 -2
- package/dist/ai/apis/max-tokens.d.ts +42 -0
- package/dist/ai/apis/max-tokens.js +121 -0
- package/dist/ai/apis/openai-completions.js +10 -5
- package/dist/ai/apis/openai-request.js +1 -1
- package/dist/ai/apis/openai-responses-request.js +1 -1
- package/dist/ai/apis/openai-responses.js +9 -5
- package/dist/ai/apis/shared.d.ts +3 -1
- package/dist/ai/apis/shared.js +21 -3
- package/dist/ai/cache/fingerprint.d.ts +4 -1
- package/dist/ai/cache/fingerprint.js +18 -1
- package/dist/ai/cache/miss.d.ts +2 -1
- package/dist/ai/cache/miss.js +9 -3
- package/dist/ai/cache/reporting.js +5 -1
- package/dist/ai/cache/types.d.ts +10 -2
- package/dist/ai/cache/warmer.d.ts +6 -1
- package/dist/ai/cache/warmer.js +7 -0
- package/dist/ai/context.d.ts +12 -4
- package/dist/ai/context.js +43 -24
- package/dist/ai/fake/fake-provider.d.ts +8 -1
- package/dist/ai/fake/fake-provider.js +19 -7
- package/dist/ai/fake/fake-script.d.ts +2 -0
- package/dist/ai/http.d.ts +17 -9
- package/dist/ai/http.js +33 -13
- package/dist/ai/image-intern.d.ts +29 -0
- package/dist/ai/image-intern.js +58 -0
- package/dist/ai/json-body.d.ts +31 -0
- package/dist/ai/json-body.js +208 -0
- package/dist/ai/overflow.js +5 -0
- package/dist/ai/providers/catalog-data.js +13 -13
- package/dist/ai/providers/catalog.d.ts +33 -0
- package/dist/ai/providers/catalog.js +105 -6
- package/dist/ai/providers/enrich.d.ts +13 -3
- package/dist/ai/providers/enrich.js +87 -31
- package/dist/ai/sse.d.ts +3 -2
- package/dist/ai/sse.js +3 -2
- package/dist/ai/types.d.ts +9 -2
- package/dist/bundle/ama.cjs +3142 -1805
- package/dist/cli/compose-agents.js +3 -0
- package/dist/cli/compose-extensions.js +2 -0
- package/dist/cli/compose-request.d.ts +20 -0
- package/dist/cli/compose-request.js +37 -0
- package/dist/cli/compose-session.js +2 -2
- package/dist/cli/subcommands/model-meta.d.ts +2 -1
- package/dist/cli/subcommands/model-meta.js +3 -2
- package/dist/codemode/host-side.d.ts +11 -4
- package/dist/codemode/host-side.js +25 -6
- package/dist/codemode/tool.d.ts +2 -0
- package/dist/codemode/tool.js +4 -0
- package/dist/compaction/prefix-estimate.d.ts +3 -1
- package/dist/compaction/prefix-estimate.js +7 -3
- package/dist/config/json-schema.js +9 -2
- package/dist/config/key-docs.js +13 -4
- package/dist/config/schema-w5.js +9 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +19 -3
- package/dist/config/settings-registry.js +6 -0
- package/dist/config/types-w5.d.ts +2 -0
- package/dist/config/types.d.ts +19 -1
- package/dist/config/types.js +4 -0
- package/dist/i18n/catalog.d.ts +15 -2
- package/dist/i18n/messages/cli.d.ts +2 -0
- package/dist/i18n/messages/cli.js +2 -0
- package/dist/i18n/messages/config-keys.d.ts +10 -0
- package/dist/i18n/messages/config-keys.js +12 -2
- package/dist/i18n/messages/config.d.ts +14 -0
- package/dist/i18n/messages/config.js +4 -0
- package/dist/i18n/messages/errors.d.ts +3 -2
- package/dist/i18n/messages/errors.js +15 -2
- package/dist/i18n/messages/report-context.d.ts +4 -0
- package/dist/i18n/messages/report-context.js +4 -0
- package/dist/i18n/messages/report.d.ts +6 -0
- package/dist/i18n/messages/report.js +2 -0
- package/dist/i18n/messages/settings.d.ts +4 -0
- package/dist/i18n/messages/settings.js +6 -2
- package/dist/i18n/messages/subcommands.d.ts +2 -2
- package/dist/i18n/messages/subcommands.js +4 -2
- package/dist/modes/acp/acp-server.js +15 -5
- package/dist/modes/acp/acp-sessions.d.ts +9 -0
- package/dist/modes/acp/acp-sessions.js +25 -0
- package/dist/modes/context-report.d.ts +2 -0
- package/dist/modes/context-report.js +25 -8
- package/dist/modes/print/json-event.d.ts +12 -1
- package/dist/modes/print/json-event.js +48 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -0
- package/dist/modes/rpc/rpc-mode.js +9 -2
- package/dist/modes/session-report.d.ts +1 -1
- package/dist/modes/session-report.js +12 -6
- package/dist/rpc.d.ts +5 -2
- package/dist/session/line-reader.d.ts +28 -0
- package/dist/session/line-reader.js +82 -0
- package/dist/session/list.d.ts +14 -0
- package/dist/session/list.js +150 -0
- package/dist/session/manager.d.ts +7 -3
- package/dist/session/manager.js +21 -48
- package/dist/session/migrate.d.ts +2 -1
- package/dist/session/migrate.js +2 -1
- package/dist/session/projection.d.ts +16 -3
- package/dist/session/projection.js +96 -29
- package/dist/session/scan.d.ts +3 -3
- package/dist/session/scan.js +9 -18
- package/dist/session/store.d.ts +3 -0
- package/dist/session/store.js +37 -31
- package/dist/session/types.d.ts +7 -2
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.js +5 -4
- package/dist/tools/glob.d.ts +2 -2
- package/dist/tools/glob.js +2 -2
- package/dist/tools/grep.js +4 -6
- package/dist/tools/image-file.js +2 -1
- package/dist/tools/read-lines.d.ts +32 -0
- package/dist/tools/read-lines.js +147 -0
- package/dist/tools/read.d.ts +6 -2
- package/dist/tools/read.js +47 -27
- package/dist/tools/task.d.ts +9 -0
- package/dist/tools/task.js +21 -8
- package/dist/tools/truncate.d.ts +7 -0
- package/dist/tools/truncate.js +11 -0
- package/dist/tools/types.d.ts +13 -0
- package/dist/trace/build-index.js +13 -1
- package/dist/tui/ansi.d.ts +2 -0
- package/dist/tui/ansi.js +7 -3
- package/dist/tui/components/editor-buffer.js +2 -2
- package/dist/tui/components/editor.js +5 -6
- package/dist/tui/keys.js +2 -2
- package/dist/tui/stdin-buffer.js +3 -3
- package/docs/acp.md +5 -1
- package/docs/agents.md +42 -6
- package/docs/codemode.md +4 -2
- package/docs/en/acp.md +3 -2
- package/docs/en/host-api.md +1 -1
- package/docs/en/permissions.md +2 -2
- package/docs/en/providers.md +54 -20
- package/docs/en/rpc.md +23 -7
- package/docs/en/sessions.md +4 -2
- package/docs/en/tui.md +3 -3
- package/docs/permissions.md +4 -1
- package/docs/providers.md +85 -22
- package/docs/rpc.md +22 -6
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +3 -1
- package/docs/tui.md +2 -2
- package/package.json +2 -2
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* 内容推断。
|
|
15
15
|
*/
|
|
16
16
|
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
17
|
-
import { authHeaders, describeErrorJson, idleTimeoutOf, joinUrl, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
17
|
+
import { authHeaders, describeErrorJson, idleTimeoutOf, streamIdleTimeoutOf, joinUrl, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
18
18
|
import { readSseEvents } from "../sse.js";
|
|
19
|
-
import { affinityHeaders,
|
|
19
|
+
import { affinityHeaders, resolveCacheRetention } from "./cache-params.js";
|
|
20
|
+
import { clampRequestMaxTokens, postWithMaxTokensFallback } from "./max-tokens.js";
|
|
20
21
|
import { detectCompat } from "./openai-compat.js";
|
|
21
22
|
import { REASONING_FIELDS, buildOpenAIRequest } from "./openai-request.js";
|
|
22
23
|
import { BlockTracker, STREAM_ENDED_MESSAGE, ProviderStopError, createOutput, finishDone, finishError, requireApiKey, } from "./shared.js";
|
|
@@ -183,16 +184,20 @@ async function run(stream, model, context, options) {
|
|
|
183
184
|
if (request.providerThinkingLevel !== undefined) {
|
|
184
185
|
tracker.output.providerThinkingLevel = request.providerThinkingLevel;
|
|
185
186
|
}
|
|
187
|
+
const field = request.compat.maxTokensField;
|
|
188
|
+
clampRequestMaxTokens(request.body, field, model.contextWindow);
|
|
186
189
|
const replaced = options.onPayload?.(request.body);
|
|
187
190
|
const baseUrl = model.baseUrl ?? "https://api.openai.com/v1";
|
|
188
|
-
const
|
|
191
|
+
const url = joinUrl(baseUrl, "/chat/completions");
|
|
192
|
+
const post = {
|
|
189
193
|
headers: buildHeaders(model, options),
|
|
190
194
|
body: replaced === undefined ? request.body : replaced,
|
|
191
195
|
signal: options.signal,
|
|
192
196
|
timeoutMs: options.timeoutMs,
|
|
193
197
|
idleTimeoutMs: idleTimeoutOf(options),
|
|
194
198
|
onResponse: options.onResponse,
|
|
195
|
-
}
|
|
199
|
+
};
|
|
200
|
+
const response = await postWithMaxTokensFallback(model, url, post, field);
|
|
196
201
|
stream.push({ type: "start", partial: tracker.output });
|
|
197
202
|
const state = {
|
|
198
203
|
text: undefined,
|
|
@@ -204,7 +209,7 @@ async function run(stream, model, context, options) {
|
|
|
204
209
|
sawDone: false,
|
|
205
210
|
};
|
|
206
211
|
const body = response.body;
|
|
207
|
-
for await (const sse of readSseEvents(body, options.signal,
|
|
212
|
+
for await (const sse of readSseEvents(body, options.signal, streamIdleTimeoutOf(options))) {
|
|
208
213
|
const data = sse.data.trim();
|
|
209
214
|
if (data === "")
|
|
210
215
|
continue;
|
|
@@ -67,7 +67,7 @@ function convertAssistant(model, message, compat) {
|
|
|
67
67
|
out["tool_calls"] = calls.map((call) => ({
|
|
68
68
|
id: normalizeCallId(model, call.id),
|
|
69
69
|
type: "function",
|
|
70
|
-
function: { name: call.name, arguments: JSON.stringify(call.arguments) },
|
|
70
|
+
function: { name: call.name, arguments: call.rawArguments ?? JSON.stringify(call.arguments) },
|
|
71
71
|
}));
|
|
72
72
|
}
|
|
73
73
|
if (out["content"] === null && calls.length === 0)
|
|
@@ -162,7 +162,7 @@ function convertAssistant(model, message) {
|
|
|
162
162
|
...(sameModel && itemId?.startsWith("fc") ? { id: itemId } : {}),
|
|
163
163
|
call_id: block.id,
|
|
164
164
|
name: block.name,
|
|
165
|
-
arguments: JSON.stringify(block.arguments),
|
|
165
|
+
arguments: block.rawArguments ?? JSON.stringify(block.arguments),
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
* 错误映射与订阅用量见 chatgpt-backend.ts。
|
|
18
18
|
*/
|
|
19
19
|
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
20
|
-
import { HttpError, authHeaders, describeErrorJson, idleTimeoutOf, joinUrl, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
20
|
+
import { HttpError, authHeaders, describeErrorJson, idleTimeoutOf, streamIdleTimeoutOf, joinUrl, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
21
21
|
import { readSseEvents } from "../sse.js";
|
|
22
|
-
import { affinityHeaders,
|
|
22
|
+
import { affinityHeaders, resolveCacheRetention } from "./cache-params.js";
|
|
23
|
+
import { clampRequestMaxTokens, postWithMaxTokensFallback } from "./max-tokens.js";
|
|
23
24
|
import { chatgptHeaders, chatgptPrecheck, handleChatGptEvent, subscriptionModel, quotaOnResponse, recoverChatGptError, } from "./chatgpt-backend.js";
|
|
24
25
|
import { buildResponsesRequest, detectResponsesCompat, encodeMessageSignature, } from "./openai-responses-request.js";
|
|
25
26
|
import { BlockTracker, ProviderStopError, STREAM_ENDED_MESSAGE, createOutput, finishDone, finishError, requireApiKey, } from "./shared.js";
|
|
@@ -251,17 +252,20 @@ async function post(model, context, initial, tracker) {
|
|
|
251
252
|
const chatgpt = compat.chatgptBackend !== undefined;
|
|
252
253
|
if (chatgpt)
|
|
253
254
|
chatgptPrecheck(model, compat, options.apiKey);
|
|
255
|
+
clampRequestMaxTokens(request.body, "max_output_tokens", model.contextWindow);
|
|
254
256
|
const replaced = options.onPayload?.(request.body);
|
|
255
257
|
const baseUrl = model.baseUrl ?? "https://api.openai.com/v1";
|
|
256
258
|
try {
|
|
257
|
-
const
|
|
259
|
+
const url = joinUrl(baseUrl, "/responses");
|
|
260
|
+
const post = {
|
|
258
261
|
headers: buildHeaders(model, options, compat),
|
|
259
262
|
body: replaced === undefined ? request.body : replaced,
|
|
260
263
|
signal: options.signal,
|
|
261
264
|
timeoutMs: options.timeoutMs,
|
|
262
265
|
idleTimeoutMs: idleTimeoutOf(options),
|
|
263
266
|
onResponse: chatgpt ? quotaOnResponse(compat, options) : options.onResponse,
|
|
264
|
-
}
|
|
267
|
+
};
|
|
268
|
+
const response = await postWithMaxTokensFallback(model, url, post, "max_output_tokens");
|
|
265
269
|
return { response, options, compat };
|
|
266
270
|
}
|
|
267
271
|
catch (error) {
|
|
@@ -292,7 +296,7 @@ async function run(stream, model, context, options) {
|
|
|
292
296
|
};
|
|
293
297
|
let finished = false;
|
|
294
298
|
const body = response.body;
|
|
295
|
-
for await (const sse of readSseEvents(body, options.signal,
|
|
299
|
+
for await (const sse of readSseEvents(body, options.signal, streamIdleTimeoutOf(options))) {
|
|
296
300
|
const raw = sse.data.trim();
|
|
297
301
|
if (raw === "" || raw === "[DONE]")
|
|
298
302
|
continue;
|
package/dist/ai/apis/shared.d.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* - 每个 `*_start` 恰好对应一个 `*_end`;终止事件之前所有打开的块都会被关闭(出错 / 中止时也
|
|
6
6
|
* 先关块再发 error,消费者不必处理悬空块);
|
|
7
7
|
* - `toolcall_end` 时 `toolCall.arguments` 一定是对象(容错解析,坏 JSON 退化为 `{}`);
|
|
8
|
-
* - 流式期间 `arguments`
|
|
8
|
+
* - 流式期间 `arguments` 随增量更新为「到目前为止」的部分对象;
|
|
9
|
+
* - [ME-C] 拼接出的参数串能严格 `JSON.parse` 成对象时原样记进 `rawArguments`,同协议回放时逐字节发回;
|
|
10
|
+
* - [ME-C] 失败响应带 `Retry-After` 时写 `retryAfterMs`(会话层退避取两者较大值)。
|
|
9
11
|
*/
|
|
10
12
|
import type { AssistantEventStreamImpl } from "../event-stream.js";
|
|
11
13
|
import type { AssistantMessage, Model, StopReason, StreamOptions, TextBlock, ThinkingBlock, ToolCallBlock } from "../types.js";
|
package/dist/ai/apis/shared.js
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* - 每个 `*_start` 恰好对应一个 `*_end`;终止事件之前所有打开的块都会被关闭(出错 / 中止时也
|
|
6
6
|
* 先关块再发 error,消费者不必处理悬空块);
|
|
7
7
|
* - `toolcall_end` 时 `toolCall.arguments` 一定是对象(容错解析,坏 JSON 退化为 `{}`);
|
|
8
|
-
* - 流式期间 `arguments`
|
|
8
|
+
* - 流式期间 `arguments` 随增量更新为「到目前为止」的部分对象;
|
|
9
|
+
* - [ME-C] 拼接出的参数串能严格 `JSON.parse` 成对象时原样记进 `rawArguments`,同协议回放时逐字节发回;
|
|
10
|
+
* - [ME-C] 失败响应带 `Retry-After` 时写 `retryAfterMs`(会话层退避取两者较大值)。
|
|
9
11
|
*/
|
|
10
12
|
import { AmaError } from "../../errors.js";
|
|
11
13
|
import { finalizeUsage, emptyUsage } from "../cost.js";
|
|
12
|
-
import { errorText, hasAuthHeader } from "../http.js";
|
|
14
|
+
import { HttpError, errorText, hasAuthHeader } from "../http.js";
|
|
13
15
|
import { parseToolArguments } from "../json-partial.js";
|
|
14
16
|
export function createOutput(model) {
|
|
15
17
|
return {
|
|
@@ -128,8 +130,11 @@ export class BlockTracker {
|
|
|
128
130
|
this.stream.push({ type: "thinking_end", contentIndex: index, partial: this.output });
|
|
129
131
|
}
|
|
130
132
|
else {
|
|
131
|
-
if (entry.json.length > 0)
|
|
133
|
+
if (entry.json.length > 0) {
|
|
132
134
|
entry.block.arguments = parseToolArguments(entry.json);
|
|
135
|
+
if (isStrictObject(entry.json))
|
|
136
|
+
entry.block.rawArguments = entry.json;
|
|
137
|
+
}
|
|
133
138
|
this.stream.push({
|
|
134
139
|
type: "toolcall_end",
|
|
135
140
|
contentIndex: index,
|
|
@@ -150,6 +155,16 @@ export class BlockTracker {
|
|
|
150
155
|
return this.output.content.length - 1;
|
|
151
156
|
}
|
|
152
157
|
}
|
|
158
|
+
/** 严格 JSON 且是对象(数组 / 标量容错解析后是 `{}`,回放原串会与执行的参数不一致)。 */
|
|
159
|
+
function isStrictObject(json) {
|
|
160
|
+
try {
|
|
161
|
+
const value = JSON.parse(json);
|
|
162
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
153
168
|
export const ABORTED_MESSAGE = "Request was aborted";
|
|
154
169
|
export const STREAM_ENDED_MESSAGE = "Stream ended before completion";
|
|
155
170
|
/** 正常结束:关块、算 usage / cost、发 done。 */
|
|
@@ -167,6 +182,9 @@ export function finishError(stream, tracker, model, error, signal) {
|
|
|
167
182
|
const aborted = signal.aborted;
|
|
168
183
|
output.stopReason = aborted ? "aborted" : "error";
|
|
169
184
|
output.errorMessage = aborted ? ABORTED_MESSAGE : errorText(error);
|
|
185
|
+
if (!aborted && error instanceof HttpError && error.retryAfterMs !== undefined) {
|
|
186
|
+
output.retryAfterMs = error.retryAfterMs;
|
|
187
|
+
}
|
|
170
188
|
finalizeUsage(model, output.usage);
|
|
171
189
|
stream.push({ type: "error", reason: aborted ? "aborted" : "error", message: output });
|
|
172
190
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `system` = sha256(normalizeContext().systemPrompt) 前 64 bit;
|
|
5
5
|
* - `tools` = sha256(JSON.stringify(工具表按名排序)) 前 64 bit;
|
|
6
|
-
* - `model` = `${provider}/${id}`
|
|
6
|
+
* - `model` = `${provider}/${id}` 原文;
|
|
7
|
+
* - `sections` = 折叠后每个非空节的 hash16([ME-B] D15:归因时说出哪一节变了)。
|
|
7
8
|
* 纯函数、零请求;每次真实请求算一次(system + tools 通常 < 20 KB)。未命中归因时与上一条
|
|
8
9
|
* 记录比对,说出「变了什么」。
|
|
9
10
|
*/
|
|
@@ -13,5 +14,7 @@ import type { PrefixFingerprint } from "./types.js";
|
|
|
13
14
|
export declare function hash16(text: string): string;
|
|
14
15
|
export declare function modelKey(model: Pick<Model, "provider" | "id" | "channel"> | ModelRef): string;
|
|
15
16
|
export declare function fingerprintContext(context: TranscriptContext, model: Pick<Model, "provider" | "id">): PrefixFingerprint;
|
|
17
|
+
/** 变化的节名:先按 `cur` 的节顺序列新增 / 改动的,再列 `prev` 里消失的;任一方没有按节指纹 → []。 */
|
|
18
|
+
export declare function changedSections(prev: PrefixFingerprint, cur: PrefixFingerprint): string[];
|
|
16
19
|
/** 两个指纹的差异(按归因顺序:system → tools → model);相同返回 undefined。 */
|
|
17
20
|
export declare function fingerprintChange(prev: PrefixFingerprint, cur: PrefixFingerprint): "system" | "tools" | "model" | undefined;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `system` = sha256(normalizeContext().systemPrompt) 前 64 bit;
|
|
5
5
|
* - `tools` = sha256(JSON.stringify(工具表按名排序)) 前 64 bit;
|
|
6
|
-
* - `model` = `${provider}/${id}`
|
|
6
|
+
* - `model` = `${provider}/${id}` 原文;
|
|
7
|
+
* - `sections` = 折叠后每个非空节的 hash16([ME-B] D15:归因时说出哪一节变了)。
|
|
7
8
|
* 纯函数、零请求;每次真实请求算一次(system + tools 通常 < 20 KB)。未命中归因时与上一条
|
|
8
9
|
* 记录比对,说出「变了什么」。
|
|
9
10
|
*/
|
|
@@ -21,12 +22,28 @@ function sortedTools(tools) {
|
|
|
21
22
|
}
|
|
22
23
|
export function fingerprintContext(context, model) {
|
|
23
24
|
const normalized = normalizeContext(context);
|
|
25
|
+
const sections = {};
|
|
26
|
+
for (const section of normalized.systemSections)
|
|
27
|
+
sections[section.name] = hash16(section.text);
|
|
24
28
|
return {
|
|
25
29
|
system: hash16(normalized.systemPrompt),
|
|
26
30
|
tools: hash16(JSON.stringify(sortedTools(normalized.tools))),
|
|
27
31
|
model: modelKey(model),
|
|
32
|
+
sections,
|
|
28
33
|
};
|
|
29
34
|
}
|
|
35
|
+
/** 变化的节名:先按 `cur` 的节顺序列新增 / 改动的,再列 `prev` 里消失的;任一方没有按节指纹 → []。 */
|
|
36
|
+
export function changedSections(prev, cur) {
|
|
37
|
+
if (prev.sections === undefined || cur.sections === undefined)
|
|
38
|
+
return [];
|
|
39
|
+
const before = prev.sections;
|
|
40
|
+
const after = cur.sections;
|
|
41
|
+
const changed = Object.keys(after).filter((name) => before[name] !== after[name]);
|
|
42
|
+
for (const name of Object.keys(before))
|
|
43
|
+
if (!(name in after))
|
|
44
|
+
changed.push(name);
|
|
45
|
+
return changed;
|
|
46
|
+
}
|
|
30
47
|
/** 两个指纹的差异(按归因顺序:system → tools → model);相同返回 undefined。 */
|
|
31
48
|
export function fingerprintChange(prev, cur) {
|
|
32
49
|
if (prev.system !== cur.system)
|
package/dist/ai/cache/miss.d.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* 4. 成本:用本条实付反推——`(cost.input + cost.cacheWrite) / (input + cacheWrite)` 减去读价
|
|
11
11
|
* (本条有读用实付,否则用目录价);无价格 → `missedCost` 缺省。
|
|
12
12
|
* 5. 重置点(压缩 / 分支摘要 / 档一裁剪之后的首个请求)由调用方清空 prev;模型切换不豁免。
|
|
13
|
-
* 6. 归因:指纹 system / tools 变 → `prefix_changed
|
|
13
|
+
* 6. 归因:指纹 system / tools 变 → `prefix_changed`(system 变时 `detail` 带变化的节名,如
|
|
14
|
+
* `system:hooks,memory`,[ME-B] D15);模型变 → `model_changed`;
|
|
14
15
|
* `idleMs > ttl`(无承诺 TTL 的隐式缓存按 600 s)→ `idle`;task 运行占间隔 ≥ 80% →
|
|
15
16
|
* `subtask`;其余 → `evicted`。
|
|
16
17
|
*/
|
package/dist/ai/cache/miss.js
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
* 4. 成本:用本条实付反推——`(cost.input + cost.cacheWrite) / (input + cacheWrite)` 减去读价
|
|
11
11
|
* (本条有读用实付,否则用目录价);无价格 → `missedCost` 缺省。
|
|
12
12
|
* 5. 重置点(压缩 / 分支摘要 / 档一裁剪之后的首个请求)由调用方清空 prev;模型切换不豁免。
|
|
13
|
-
* 6. 归因:指纹 system / tools 变 → `prefix_changed
|
|
13
|
+
* 6. 归因:指纹 system / tools 变 → `prefix_changed`(system 变时 `detail` 带变化的节名,如
|
|
14
|
+
* `system:hooks,memory`,[ME-B] D15);模型变 → `model_changed`;
|
|
14
15
|
* `idleMs > ttl`(无承诺 TTL 的隐式缓存按 600 s)→ `idle`;task 运行占间隔 ≥ 80% →
|
|
15
16
|
* `subtask`;其余 → `evicted`。
|
|
16
17
|
*/
|
|
17
|
-
import { fingerprintChange } from "./fingerprint.js";
|
|
18
|
+
import { changedSections, fingerprintChange } from "./fingerprint.js";
|
|
18
19
|
/** 无承诺 TTL 的端点(隐式缓存)按 10 分钟估。 */
|
|
19
20
|
export const IMPLICIT_CACHE_TTL_MS = 600_000;
|
|
20
21
|
/** 没有目录 `minTokens` 时的最小可缓存长度与噪声下限。 */
|
|
@@ -76,10 +77,15 @@ export function detectMiss(prev, cur, ttlMs, options = {}) {
|
|
|
76
77
|
if (cost !== undefined)
|
|
77
78
|
miss.missedCost = cost;
|
|
78
79
|
const change = fingerprintChange(prev.fingerprint, cur.fingerprint);
|
|
79
|
-
if (change === "
|
|
80
|
+
if (change === "tools") {
|
|
80
81
|
miss.reason = "prefix_changed";
|
|
81
82
|
miss.detail = change;
|
|
82
83
|
}
|
|
84
|
+
else if (change === "system") {
|
|
85
|
+
miss.reason = "prefix_changed";
|
|
86
|
+
const names = changedSections(prev.fingerprint, cur.fingerprint);
|
|
87
|
+
miss.detail = names.length > 0 ? `system:${names.join(",")}` : "system";
|
|
88
|
+
}
|
|
83
89
|
else if (change === "model")
|
|
84
90
|
miss.reason = "model_changed";
|
|
85
91
|
else if (idleMs > (ttlMs ?? IMPLICIT_CACHE_TTL_MS))
|
|
@@ -64,7 +64,11 @@ export class CacheReportingTracker {
|
|
|
64
64
|
};
|
|
65
65
|
this.endpoints.set(key, entry);
|
|
66
66
|
const last = entry.last;
|
|
67
|
-
entry.last =
|
|
67
|
+
entry.last = {
|
|
68
|
+
at: record.at,
|
|
69
|
+
promptTokens: record.promptTokens,
|
|
70
|
+
fingerprint: record.fingerprint,
|
|
71
|
+
};
|
|
68
72
|
const { cacheRead, cacheWrite, cacheReported } = record.usage;
|
|
69
73
|
if (cacheRead > 0 && Number.isInteger(cacheRead)) {
|
|
70
74
|
entry.divisor = gcd(entry.divisor, cacheRead);
|
package/dist/ai/cache/types.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface PrefixFingerprint {
|
|
|
18
18
|
tools: string;
|
|
19
19
|
/** `${provider}/${id}`(原文,不哈希)。 */
|
|
20
20
|
model: string;
|
|
21
|
+
/** 节名 → hash16,归因时说出哪一节变了。 */
|
|
22
|
+
sections?: Record<string, string>;
|
|
21
23
|
}
|
|
22
24
|
/** 每次真实请求在会话层记一条(内存,不落盘)。 */
|
|
23
25
|
export interface RequestRecord {
|
|
@@ -36,6 +38,12 @@ export interface RequestRecord {
|
|
|
36
38
|
contextRef: TranscriptContext;
|
|
37
39
|
options: Omit<StreamOptions, "signal">;
|
|
38
40
|
}
|
|
41
|
+
/** [M-A] 端点上一条可比请求的摘要;只存比较需要的字段,不持有转录与回调。 */
|
|
42
|
+
export interface LastRequest {
|
|
43
|
+
at: number;
|
|
44
|
+
promptTokens: number;
|
|
45
|
+
fingerprint: PrefixFingerprint;
|
|
46
|
+
}
|
|
39
47
|
/** 「供应商不报缓存」三态(§1.6);按 `(provider, baseUrl 主机名, model)` 维护。 */
|
|
40
48
|
export type CacheReporting = "unknown" | "reported" | "silent";
|
|
41
49
|
/** 未命中原因(§1.5 第 6 步的归因顺序)。 */
|
|
@@ -47,8 +55,8 @@ export interface CacheMiss {
|
|
|
47
55
|
/** 重计费金额(美元);模型无价格时缺省(显示 `$?`)。 */
|
|
48
56
|
missedCost?: number;
|
|
49
57
|
reason: CacheMissReason;
|
|
50
|
-
/** `prefix_changed`
|
|
51
|
-
detail?: "system" | "tools"
|
|
58
|
+
/** `prefix_changed` 时哪一段变了;[ME-B] system 变时带节名:`system:hooks,memory`。 */
|
|
59
|
+
detail?: "system" | "tools" | `system:${string}`;
|
|
52
60
|
/** 与上一条可比请求的间隔。 */
|
|
53
61
|
idleMs: number;
|
|
54
62
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - `pause()`:真实请求发出时静默挂起(新请求本身就刷新了缓存);`cancel()` 同时丢弃记录。
|
|
15
15
|
* 只有已排期之后的异常停止(截止、上限、零命中、过期、否决、失败)回调 `onStopped`。
|
|
16
16
|
*/
|
|
17
|
-
import type { AssistantMessage, Model, StreamOptions } from "../types.js";
|
|
17
|
+
import type { Api, AssistantMessage, Model, StreamOptions } from "../types.js";
|
|
18
18
|
import type { CacheReporting, RequestRecord, WarmDecision, WarmerStatus, WarmingMode, WarmingPhase } from "./types.js";
|
|
19
19
|
export declare const WARM_MIN_TTL_MS = 10000;
|
|
20
20
|
export declare const WARM_STREAMING_CAP_MS: number;
|
|
@@ -42,6 +42,11 @@ export interface WarmerDeps {
|
|
|
42
42
|
onScheduled?(nextWarmAt: number): void;
|
|
43
43
|
onStopped(reason: string): void;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* 保温重放的输出上限([ME-B] D17):Responses 的 `max_output_tokens` 下限是 16,其余协议 1。
|
|
47
|
+
* 官方下限无法实测,按文档取值;中转同样适用。
|
|
48
|
+
*/
|
|
49
|
+
export declare function warmReplayMaxTokens(api: Api): number;
|
|
45
50
|
export declare function warmDelay(ttlMs: number): number;
|
|
46
51
|
export interface ReplayCheck {
|
|
47
52
|
model: Pick<Model, "api" | "reasoning" | "compat">;
|
package/dist/ai/cache/warmer.js
CHANGED
|
@@ -30,6 +30,13 @@ export const REAL_TIMERS = {
|
|
|
30
30
|
clearTimeout(handle);
|
|
31
31
|
},
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* 保温重放的输出上限([ME-B] D17):Responses 的 `max_output_tokens` 下限是 16,其余协议 1。
|
|
35
|
+
* 官方下限无法实测,按文档取值;中转同样适用。
|
|
36
|
+
*/
|
|
37
|
+
export function warmReplayMaxTokens(api) {
|
|
38
|
+
return api === "openai-responses" ? 16 : 1;
|
|
39
|
+
}
|
|
33
40
|
export function warmDelay(ttlMs) {
|
|
34
41
|
return Math.max(1000, Math.floor(Math.min(0.9 * ttlMs, ttlMs - 10_000)));
|
|
35
42
|
}
|
package/dist/ai/context.d.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* 得到 `systemSections`(保持节首次出现的顺序)与拼好的 `systemPrompt`(节间空一行);
|
|
7
7
|
* - 中途节补丁(设计 §9.1「会话中途变化的上下文只追加」):对话开始之后的 system 补丁不改写开头,
|
|
8
8
|
* 渲染成 `<system-reminder>` 包裹的 user 消息按位置插回,开头与之前的消息逐字节不变;
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* - 工具表([ME-B] D5):对话开始前按顺序重放 `toolsRemoved`(先)与 `toolsAdded`(后),同名后者覆盖;
|
|
10
|
+
* 开始之后声明冻结——移除不删声明、只在尾部提醒(执行层拒绝调用),已有名字不覆盖(加回只提醒
|
|
11
|
+
* 「available again」),新名字追加在末尾。开头与工具表在会话内只写一次;
|
|
11
12
|
* - 模态过滤:模型不收图片时,用户消息与工具结果里的图片块换成文字占位。
|
|
12
13
|
*
|
|
13
14
|
* 支持「中途 system 消息」的协议(compat.supportsMidConvoSystemMessages)用
|
|
@@ -20,11 +21,18 @@ export interface NormalizeOptions {
|
|
|
20
21
|
model?: Pick<Model, "input"> | undefined;
|
|
21
22
|
}
|
|
22
23
|
export declare const IMAGE_OMITTED_TEXT = "[image omitted: the model does not accept image input]";
|
|
24
|
+
/** 对话开始后移除工具:声明保留,尾部提醒(固定英文;执行层拒绝文案见 agent/tool-availability.ts)。 */
|
|
25
|
+
export declare const toolRemovedReminder: (name: string) => string;
|
|
26
|
+
/** 移除后又加回(声明沿用首次版本)。 */
|
|
27
|
+
export declare const toolRestoredReminder: (name: string) => string;
|
|
23
28
|
export declare function normalizeContext(context: TranscriptContext, options?: NormalizeOptions): NormalizedContext;
|
|
24
29
|
/** 中途节补丁作为尾部上下文消息送达时的文本(固定英文,与界面语言无关)。 */
|
|
25
30
|
export declare function systemReminderText(updates: readonly string[]): string;
|
|
26
|
-
/**
|
|
27
|
-
|
|
31
|
+
/**
|
|
32
|
+
* 渲染一条中途 system 补丁的文本:节变更全文;移除的工具各一句提醒;`removed` 里的名字被加回时
|
|
33
|
+
* 一句「available again」(新工具的声明由请求工具表末尾体现,不另写)。`removed` 随之更新。
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderSystemUpdate(message: SystemMessage, removed: Set<string>): string;
|
|
28
36
|
export interface InlineSystemUpdate {
|
|
29
37
|
/** 插在 `messages[beforeIndex]` 之前;等于 messages.length 表示放在末尾。 */
|
|
30
38
|
beforeIndex: number;
|
package/dist/ai/context.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* 得到 `systemSections`(保持节首次出现的顺序)与拼好的 `systemPrompt`(节间空一行);
|
|
7
7
|
* - 中途节补丁(设计 §9.1「会话中途变化的上下文只追加」):对话开始之后的 system 补丁不改写开头,
|
|
8
8
|
* 渲染成 `<system-reminder>` 包裹的 user 消息按位置插回,开头与之前的消息逐字节不变;
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* - 工具表([ME-B] D5):对话开始前按顺序重放 `toolsRemoved`(先)与 `toolsAdded`(后),同名后者覆盖;
|
|
10
|
+
* 开始之后声明冻结——移除不删声明、只在尾部提醒(执行层拒绝调用),已有名字不覆盖(加回只提醒
|
|
11
|
+
* 「available again」),新名字追加在末尾。开头与工具表在会话内只写一次;
|
|
11
12
|
* - 模态过滤:模型不收图片时,用户消息与工具结果里的图片块换成文字占位。
|
|
12
13
|
*
|
|
13
14
|
* 支持「中途 system 消息」的协议(compat.supportsMidConvoSystemMessages)用
|
|
@@ -15,16 +16,27 @@
|
|
|
15
16
|
* 渲染为文本、按位置插回对话。
|
|
16
17
|
*/
|
|
17
18
|
export const IMAGE_OMITTED_TEXT = "[image omitted: the model does not accept image input]";
|
|
19
|
+
/** 对话开始后移除工具:声明保留,尾部提醒(固定英文;执行层拒绝文案见 agent/tool-availability.ts)。 */
|
|
20
|
+
export const toolRemovedReminder = (name) => `Tool "${name}" is no longer available in this session; calls to it are rejected.`;
|
|
21
|
+
/** 移除后又加回(声明沿用首次版本)。 */
|
|
22
|
+
export const toolRestoredReminder = (name) => `Tool "${name}" is available again.`;
|
|
18
23
|
class SystemState {
|
|
19
24
|
sections = new Map();
|
|
20
25
|
tools = new Map();
|
|
21
|
-
|
|
26
|
+
/** `started`:对话已开始——工具声明冻结(移除不删、已有名字不覆盖、新名字追加)。 */
|
|
27
|
+
apply(message, started = false) {
|
|
22
28
|
for (const [name, value] of Object.entries(message.sections)) {
|
|
23
29
|
if (value === null)
|
|
24
30
|
this.sections.delete(name);
|
|
25
31
|
else
|
|
26
32
|
this.sections.set(name, value);
|
|
27
33
|
}
|
|
34
|
+
if (started) {
|
|
35
|
+
for (const tool of message.toolsAdded ?? [])
|
|
36
|
+
if (!this.tools.has(tool.name))
|
|
37
|
+
this.tools.set(tool.name, tool);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
28
40
|
for (const name of message.toolsRemoved ?? [])
|
|
29
41
|
this.tools.delete(name);
|
|
30
42
|
for (const tool of message.toolsAdded ?? [])
|
|
@@ -62,7 +74,7 @@ export function normalizeContext(context, options) {
|
|
|
62
74
|
const tools = new SystemState();
|
|
63
75
|
const messages = [];
|
|
64
76
|
const allowImages = allowsImages(options);
|
|
65
|
-
const
|
|
77
|
+
const removed = new Set();
|
|
66
78
|
let pending = [];
|
|
67
79
|
const flush = () => {
|
|
68
80
|
if (pending.length === 0)
|
|
@@ -76,42 +88,47 @@ export function normalizeContext(context, options) {
|
|
|
76
88
|
messages.push(filterMessage(message, allowImages));
|
|
77
89
|
continue;
|
|
78
90
|
}
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
const started = messages.length > 0;
|
|
92
|
+
tools.apply(message, started);
|
|
93
|
+
if (!started) {
|
|
81
94
|
head.apply(message);
|
|
82
95
|
continue;
|
|
83
96
|
}
|
|
84
|
-
const text = renderSystemUpdate(message);
|
|
97
|
+
const text = renderSystemUpdate(message, removed);
|
|
85
98
|
if (text.length > 0)
|
|
86
99
|
pending.push(text);
|
|
87
100
|
}
|
|
88
101
|
flush();
|
|
89
102
|
return { ...head.snapshot(), tools: tools.snapshot().tools, messages };
|
|
90
103
|
}
|
|
91
|
-
/** 对话开始之后有补丁移除了工具(这时整段前缀必然失效,补丁全部折回开头)。 */
|
|
92
|
-
function removesToolsMidway(transcript) {
|
|
93
|
-
let started = false;
|
|
94
|
-
for (const message of transcript) {
|
|
95
|
-
if (message.role !== "system")
|
|
96
|
-
started = true;
|
|
97
|
-
else if (started && (message.toolsRemoved?.length ?? 0) > 0)
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
104
|
/** 中途节补丁作为尾部上下文消息送达时的文本(固定英文,与界面语言无关)。 */
|
|
103
105
|
export function systemReminderText(updates) {
|
|
104
106
|
return (`<system-reminder>\n${updates.join("\n\n")}\n\n` +
|
|
105
|
-
"These updates replace the earlier versions of those system prompt sections
|
|
107
|
+
"These updates replace the earlier versions of those system prompt sections; " +
|
|
108
|
+
"tool availability notes above are current.\n</system-reminder>");
|
|
106
109
|
}
|
|
107
|
-
/**
|
|
108
|
-
|
|
110
|
+
/**
|
|
111
|
+
* 渲染一条中途 system 补丁的文本:节变更全文;移除的工具各一句提醒;`removed` 里的名字被加回时
|
|
112
|
+
* 一句「available again」(新工具的声明由请求工具表末尾体现,不另写)。`removed` 随之更新。
|
|
113
|
+
*/
|
|
114
|
+
export function renderSystemUpdate(message, removed) {
|
|
109
115
|
const parts = [];
|
|
110
116
|
for (const [name, value] of Object.entries(message.sections)) {
|
|
111
117
|
parts.push(value === null
|
|
112
118
|
? `System prompt section "${name}" was removed.`
|
|
113
119
|
: `System prompt section "${name}" was updated:\n\n${value}`);
|
|
114
120
|
}
|
|
121
|
+
for (const tool of message.toolsAdded ?? []) {
|
|
122
|
+
if (!removed.delete(tool.name))
|
|
123
|
+
continue;
|
|
124
|
+
parts.push(toolRestoredReminder(tool.name));
|
|
125
|
+
}
|
|
126
|
+
for (const name of message.toolsRemoved ?? []) {
|
|
127
|
+
if (removed.has(name))
|
|
128
|
+
continue;
|
|
129
|
+
removed.add(name);
|
|
130
|
+
parts.push(toolRemovedReminder(name));
|
|
131
|
+
}
|
|
115
132
|
return parts.join("\n\n");
|
|
116
133
|
}
|
|
117
134
|
export function normalizeContextInline(context, options) {
|
|
@@ -120,17 +137,19 @@ export function normalizeContextInline(context, options) {
|
|
|
120
137
|
const messages = [];
|
|
121
138
|
const systemUpdates = [];
|
|
122
139
|
const allowImages = allowsImages(options);
|
|
140
|
+
const removed = new Set();
|
|
123
141
|
for (const message of context.messages) {
|
|
124
142
|
if (message.role !== "system") {
|
|
125
143
|
messages.push(filterMessage(message, allowImages));
|
|
126
144
|
continue;
|
|
127
145
|
}
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
const started = messages.length > 0;
|
|
147
|
+
tools.apply(message, started);
|
|
148
|
+
if (!started) {
|
|
130
149
|
head.apply(message);
|
|
131
150
|
continue;
|
|
132
151
|
}
|
|
133
|
-
const text = renderSystemUpdate(message);
|
|
152
|
+
const text = renderSystemUpdate(message, removed);
|
|
134
153
|
if (text.length > 0)
|
|
135
154
|
systemUpdates.push({ beforeIndex: messages.length, text });
|
|
136
155
|
}
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
* `responses[n]` 产出文本 / 思考 / 工具调用 / 429 / 溢出 / 断流 / 延迟。
|
|
5
5
|
*
|
|
6
6
|
* 事件形状与真实协议完全一致(同一个 BlockTracker),契约测试对两条协议与 fake 跑同一套断言。
|
|
7
|
+
* [ME-C] 工具参数按 JSON 串分块喂给 BlockTracker,所以消息同样带 `rawArguments`;start 之前的错误
|
|
8
|
+
* 以 `HttpError` 抛出(状态 ≥ 400 时),脚本的 `error.retryAfterMs` 进失败消息的 `retryAfterMs`。
|
|
7
9
|
*
|
|
8
10
|
* 用法:
|
|
9
11
|
* - 测试:`const fake = new FakeProvider([...])`,把 `fake.api` 注册进 ApiRegistry(或直接
|
|
10
12
|
* `fake.api.stream(...)`),`fake.calls` 记录每次调用的上下文与选项;
|
|
11
|
-
* - CLI:`--provider fake`;`AMA_FAKE_SCRIPT=<path.json>`
|
|
13
|
+
* - CLI:`--provider fake`;`AMA_FAKE_SCRIPT=<path.json>` 指定脚本(首次调用时读取)。缺省实例
|
|
14
|
+
* `keepCalls: false`:只计数、不留 `calls`(否则整个进程的转录都被它引用,内存测量多出几十 MB)。
|
|
12
15
|
* - 录制(第三波 §2.4,bundle 级缓存测试):`AMA_FAKE_RECORD=<file>` 时每次请求向文件追加一行
|
|
13
16
|
* JSON `{ index, purpose, model, system, tools, messagesCount }`——`system` 是折叠后的系统提示
|
|
14
17
|
* 原文,`tools` 是发给供应商的工具表(按注册顺序),与真实协议请求体的前缀同口径。
|
|
@@ -31,6 +34,8 @@ export interface FakeRecordLine {
|
|
|
31
34
|
export interface FakeProviderOptions {
|
|
32
35
|
/** 录制文件路径(每次请求取一次;undefined = 不录)。 */
|
|
33
36
|
recordFile?: string | (() => string | undefined);
|
|
37
|
+
/** false:只计数,不往 `calls` 里留上下文与选项(CLI 缺省实例);缺省 true。 */
|
|
38
|
+
keepCalls?: boolean;
|
|
34
39
|
}
|
|
35
40
|
export declare const FAKE_MODELS: readonly Model[];
|
|
36
41
|
export declare function fakeProviderData(): ProviderData;
|
|
@@ -47,6 +52,8 @@ export declare class FakeProvider {
|
|
|
47
52
|
private readonly scriptLoader;
|
|
48
53
|
private loaded;
|
|
49
54
|
private readonly recordFile;
|
|
55
|
+
private readonly keepCalls;
|
|
56
|
+
private count;
|
|
50
57
|
constructor(script?: FakeScript | FakeResponse[] | (() => FakeScript | undefined), options?: FakeProviderOptions);
|
|
51
58
|
readonly api: ApiImplementation;
|
|
52
59
|
get callCount(): number;
|