@armadra/agent 0.2.1 → 0.4.0
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 +99 -0
- package/README.md +142 -49
- package/dist/agent/retry.d.ts +1 -1
- package/dist/agent/retry.js +2 -1
- package/dist/agent/session-cache.js +6 -3
- package/dist/agent/session-classifier.d.ts +18 -0
- package/dist/agent/session-classifier.js +103 -0
- package/dist/agent/session-core.d.ts +13 -0
- package/dist/agent/session-state.js +2 -1
- package/dist/agent/session-sync.js +9 -2
- package/dist/agent/session-tools.js +24 -3
- package/dist/agent/session.d.ts +3 -0
- package/dist/agent/session.js +20 -10
- package/dist/agent/tool-runner.js +15 -2
- package/dist/agent/types.d.ts +9 -1
- package/dist/ai/apis/anthropic-messages.js +3 -2
- package/dist/ai/apis/google-generative-ai.js +3 -2
- package/dist/ai/apis/openai-completions.js +3 -2
- package/dist/ai/apis/openai-responses.js +5 -3
- package/dist/ai/cache/fingerprint.d.ts +1 -1
- package/dist/ai/cache/fingerprint.js +1 -1
- package/dist/ai/cache/reporting.js +2 -1
- package/dist/ai/http.d.ts +28 -6
- package/dist/ai/http.js +41 -8
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +42 -4
- package/dist/ai/providers/registry.js +195 -34
- package/dist/ai/providers/suggest.d.ts +18 -0
- package/dist/ai/providers/suggest.js +72 -0
- package/dist/ai/sse.d.ts +6 -2
- package/dist/ai/sse.js +22 -2
- package/dist/ai/types.d.ts +30 -3
- package/dist/bundle/ama.cjs +17893 -10698
- package/dist/cli/args.d.ts +23 -5
- package/dist/cli/args.js +113 -13
- package/dist/cli/bootstrap.js +28 -3
- package/dist/cli/codemode-notice.d.ts +20 -0
- package/dist/cli/codemode-notice.js +55 -0
- package/dist/cli/compose-providers.js +3 -0
- package/dist/cli/compose-session.d.ts +8 -0
- package/dist/cli/compose-session.js +32 -1
- package/dist/cli/compose-store.d.ts +1 -1
- package/dist/cli/compose-store.js +3 -1
- package/dist/cli/compose.d.ts +17 -5
- package/dist/cli/compose.js +53 -19
- package/dist/cli/default-model.d.ts +38 -1
- package/dist/cli/default-model.js +96 -9
- package/dist/cli/deps.d.ts +38 -0
- package/dist/cli/exit-codes.d.ts +2 -0
- package/dist/cli/exit-codes.js +3 -0
- package/dist/cli/fake-visibility.d.ts +13 -0
- package/dist/cli/fake-visibility.js +26 -0
- package/dist/cli/from-prompt.d.ts +18 -0
- package/dist/cli/from-prompt.js +49 -0
- package/dist/cli/main.d.ts +10 -2
- package/dist/cli/main.js +88 -2
- package/dist/cli/proxy.d.ts +51 -0
- package/dist/cli/proxy.js +135 -0
- package/dist/cli/startup-screen.d.ts +29 -0
- package/dist/cli/startup-screen.js +47 -0
- package/dist/cli/startup-steps.d.ts +1 -1
- package/dist/cli/startup-steps.js +20 -13
- package/dist/cli/subcommands/config.d.ts +29 -4
- package/dist/cli/subcommands/config.js +188 -25
- package/dist/cli/subcommands/context.js +5 -1
- package/dist/cli/subcommands/doctor.js +12 -1
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +23 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.js +1 -1
- package/dist/cli/subcommands/models-discover.d.ts +18 -6
- package/dist/cli/subcommands/models-discover.js +87 -52
- package/dist/cli/subcommands/models.js +26 -18
- package/dist/cli/subcommands/probe-runner.d.ts +96 -0
- package/dist/cli/subcommands/probe-runner.js +264 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers-probe.d.ts +34 -0
- package/dist/cli/subcommands/providers-probe.js +87 -0
- package/dist/cli/subcommands/providers.d.ts +28 -0
- package/dist/cli/subcommands/providers.js +436 -0
- package/dist/cli/subcommands/sessions-export.d.ts +10 -0
- package/dist/cli/subcommands/sessions-export.js +59 -0
- package/dist/cli/subcommands/sessions-search.d.ts +13 -0
- package/dist/cli/subcommands/sessions-search.js +103 -0
- package/dist/cli/subcommands/sessions.d.ts +3 -2
- package/dist/cli/subcommands/sessions.js +22 -1
- package/dist/cli/subcommands/stats.d.ts +17 -0
- package/dist/cli/subcommands/stats.js +198 -0
- package/dist/cli/system-prompt-arg.d.ts +11 -0
- package/dist/cli/system-prompt-arg.js +34 -0
- package/dist/codemode/modes.d.ts +4 -11
- package/dist/codemode/modes.js +5 -27
- package/dist/codemode/tool.d.ts +18 -14
- package/dist/codemode/tool.js +71 -28
- package/dist/config/init.d.ts +34 -0
- package/dist/config/init.js +99 -0
- package/dist/config/json-schema.d.ts +15 -0
- package/dist/config/json-schema.js +215 -0
- package/dist/config/key-docs.d.ts +22 -0
- package/dist/config/key-docs.js +105 -0
- package/dist/config/merge.d.ts +9 -8
- package/dist/config/merge.js +22 -7
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +97 -15
- package/dist/config/types.d.ts +57 -11
- package/dist/config/types.js +19 -1
- package/dist/modes/commands-core.js +6 -5
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +21 -5
- package/dist/modes/interactive/approval-dialog.js +106 -27
- package/dist/modes/interactive/commands.d.ts +11 -2
- package/dist/modes/interactive/commands.js +58 -19
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +70 -82
- package/dist/modes/interactive/key-dispatch.d.ts +3 -1
- package/dist/modes/interactive/key-dispatch.js +5 -6
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +9 -5
- package/dist/modes/interactive/line/line-render.d.ts +4 -0
- package/dist/modes/interactive/line/line-render.js +28 -6
- package/dist/modes/interactive/message-view.d.ts +48 -9
- package/dist/modes/interactive/message-view.js +238 -44
- package/dist/modes/interactive/panels.d.ts +18 -0
- package/dist/modes/interactive/panels.js +143 -0
- package/dist/modes/interactive/pickers.d.ts +23 -2
- package/dist/modes/interactive/pickers.js +48 -15
- package/dist/modes/interactive/run-indicator.d.ts +51 -0
- package/dist/modes/interactive/run-indicator.js +189 -0
- package/dist/modes/interactive/startup-header.d.ts +40 -0
- package/dist/modes/interactive/startup-header.js +169 -0
- package/dist/modes/interactive/startup-ui.d.ts +7 -2
- package/dist/modes/interactive/startup-ui.js +41 -11
- package/dist/modes/interactive/status-bar.d.ts +18 -15
- package/dist/modes/interactive/status-bar.js +98 -56
- package/dist/modes/interactive/tool-summary.d.ts +46 -0
- package/dist/modes/interactive/tool-summary.js +218 -0
- package/dist/modes/interactive/tool-view.d.ts +48 -15
- package/dist/modes/interactive/tool-view.js +203 -145
- package/dist/modes/print/print-mode.d.ts +33 -4
- package/dist/modes/print/print-mode.js +128 -8
- package/dist/modes/rpc/commands.js +12 -2
- package/dist/permissions/auto-safe.d.ts +60 -0
- package/dist/permissions/auto-safe.js +529 -0
- package/dist/permissions/classifier.d.ts +64 -0
- package/dist/permissions/classifier.js +184 -0
- package/dist/permissions/dangerous.d.ts +5 -0
- package/dist/permissions/dangerous.js +1 -1
- package/dist/permissions/modes.d.ts +30 -0
- package/dist/permissions/modes.js +78 -0
- package/dist/permissions/pipeline.d.ts +31 -4
- package/dist/permissions/pipeline.js +196 -6
- package/dist/permissions/protected.d.ts +19 -0
- package/dist/permissions/protected.js +74 -0
- package/dist/permissions/rules.js +3 -0
- package/dist/permissions/types.d.ts +50 -3
- package/dist/permissions/types.js +3 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/sdk.d.ts +9 -3
- package/dist/sdk.js +10 -2
- package/dist/session/export.d.ts +32 -0
- package/dist/session/export.js +187 -0
- package/dist/session/projection.js +5 -1
- package/dist/session/redact.d.ts +15 -0
- package/dist/session/redact.js +55 -0
- package/dist/session/reuse.d.ts +33 -0
- package/dist/session/reuse.js +86 -0
- package/dist/session/scan.d.ts +34 -0
- package/dist/session/scan.js +140 -0
- package/dist/session/search.d.ts +52 -0
- package/dist/session/search.js +211 -0
- package/dist/session/stats-aggregate.d.ts +63 -0
- package/dist/session/stats-aggregate.js +163 -0
- package/dist/session/stats-index.d.ts +26 -0
- package/dist/session/stats-index.js +91 -0
- package/dist/session/stats-scan.d.ts +54 -0
- package/dist/session/stats-scan.js +236 -0
- package/dist/session/types.d.ts +2 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/presets.d.ts +35 -8
- package/dist/tools/presets.js +56 -17
- package/dist/tools/read.d.ts +4 -8
- package/dist/tools/read.js +15 -64
- package/dist/tui/component.d.ts +8 -2
- package/dist/tui/component.js +3 -1
- package/dist/tui/components/box.d.ts +6 -1
- package/dist/tui/components/box.js +16 -6
- package/dist/tui/components/card.d.ts +23 -0
- package/dist/tui/components/card.js +37 -0
- package/dist/tui/components/editor-history.d.ts +6 -0
- package/dist/tui/components/editor-history.js +45 -0
- package/dist/tui/components/editor-paste.d.ts +1 -1
- package/dist/tui/components/editor-paste.js +4 -4
- package/dist/tui/components/editor.d.ts +11 -5
- package/dist/tui/components/editor.js +52 -58
- package/dist/tui/components/key-value.d.ts +3 -0
- package/dist/tui/components/key-value.js +16 -6
- package/dist/tui/components/loader.d.ts +37 -7
- package/dist/tui/components/loader.js +84 -21
- package/dist/tui/components/markdown.d.ts +5 -1
- package/dist/tui/components/markdown.js +45 -21
- package/dist/tui/components/meter.d.ts +3 -3
- package/dist/tui/components/meter.js +13 -11
- package/dist/tui/components/select-list.d.ts +23 -1
- package/dist/tui/components/select-list.js +76 -13
- package/dist/tui/glyphs.d.ts +68 -0
- package/dist/tui/glyphs.js +114 -0
- package/dist/tui/theme.d.ts +30 -6
- package/dist/tui/theme.js +103 -17
- package/dist/tui.d.ts +4 -2
- package/dist/tui.js +3 -1
- package/docs/codemode.md +23 -9
- package/docs/hooks.md +10 -10
- package/docs/permissions.md +148 -0
- package/docs/providers.md +241 -3
- package/docs/rpc.md +38 -38
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +134 -0
- package/docs/tui.md +140 -61
- package/package.json +3 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { WarmingDecisionHandler } from "../ai/cache/types.js";
|
|
8
8
|
import type { Model, ModelThinkingLevel, ProviderRegistryApi } from "../ai/types.js";
|
|
9
9
|
import type { HookDispatcherApi, HookEvent, HookEventPayload, HookOutcome } from "../hooks/types.js";
|
|
10
|
+
import type { PermissionClassifier } from "../permissions/classifier.js";
|
|
10
11
|
import type { ApprovalBroker, PermissionPipelineApi } from "../permissions/types.js";
|
|
11
12
|
import type { SessionManager } from "../session/manager.js";
|
|
12
13
|
import type { SessionEntry, SessionEntryInput } from "../session/types.js";
|
|
@@ -37,6 +38,14 @@ export interface AgentSessionOptions {
|
|
|
37
38
|
brokers?: readonly ApprovalBroker[];
|
|
38
39
|
/** 无人值守:权限管线把 ask 变 deny。 */
|
|
39
40
|
unattended?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* auto 权限模式的模型分类器(§7.4):`model` 为 `provider/model`(config `permission.autoModel`),
|
|
43
|
+
* 缺省用当前会话模型;`timeoutMs` 缺省 10 000。
|
|
44
|
+
*/
|
|
45
|
+
permissionClassifier?: {
|
|
46
|
+
model?: string;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
};
|
|
40
49
|
/** 审批超时,缺省 10 分钟(超时 deny)。 */
|
|
41
50
|
approvalTimeoutMs?: number;
|
|
42
51
|
/** 命令式 Hook(B5);缺省无 Hook。 */
|
|
@@ -59,6 +68,8 @@ export interface AgentSessionOptions {
|
|
|
59
68
|
outputDir?: string;
|
|
60
69
|
/** task 深度:主会话 0。 */
|
|
61
70
|
depth?: number;
|
|
71
|
+
/** 模型请求的空闲超时(毫秒,0 关闭);缺省由协议层取 300 000。 */
|
|
72
|
+
idleTimeoutMs?: number;
|
|
62
73
|
/** 子 Agent 的回合上限(maxTurns)。 */
|
|
63
74
|
maxTurns?: number;
|
|
64
75
|
/** 子 Agent 并发上限,缺省 4;`false` 关闭 spawnSubagent。 */
|
|
@@ -99,5 +110,7 @@ export interface SessionCore {
|
|
|
99
110
|
/** Hook 返回 continue:false 时记下,finishTurn 据此结束 run。 */
|
|
100
111
|
requestStop(reason: string | undefined): void;
|
|
101
112
|
spawnSubagent(request: SubagentRequest): Promise<SubagentResult>;
|
|
113
|
+
/** auto 权限模式的分类器(懒建,会话内缓存判定)。 */
|
|
114
|
+
autoClassifier(): PermissionClassifier;
|
|
102
115
|
log(level: "debug" | "info" | "warn" | "error", message: string): void;
|
|
103
116
|
}
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* 压缩与分支摘要请求的 usage 也计入;`usage` 条目(缓存保温等不进上下文的请求,第三波 §1.7)
|
|
6
6
|
* 计入 token 与费用但不算消息。上下文 % 用投影感知估算(§9)。
|
|
7
7
|
*/
|
|
8
|
+
import { modelRefOf } from "../ai/providers/channels.js";
|
|
8
9
|
export function buildSessionState(input) {
|
|
9
10
|
const { agent, manager, model } = input;
|
|
10
11
|
return {
|
|
11
12
|
isStreaming: agent.isRunning,
|
|
12
13
|
isCompacting: input.isCompacting,
|
|
13
14
|
isRetrying: input.isRetrying,
|
|
14
|
-
model:
|
|
15
|
+
model: modelRefOf(model),
|
|
15
16
|
thinkingLevel: input.thinkingLevel,
|
|
16
17
|
permissionMode: input.permissionMode,
|
|
17
18
|
sessionId: manager.id,
|
|
@@ -28,17 +28,24 @@ export function persistMessage(core, message) {
|
|
|
28
28
|
export function recordModelState(core, model, thinkingLevel) {
|
|
29
29
|
let provider;
|
|
30
30
|
let modelId;
|
|
31
|
+
let channel;
|
|
31
32
|
let thinking;
|
|
32
33
|
for (const entry of core.manager.branch()) {
|
|
33
34
|
if (entry.type === "model_change") {
|
|
34
35
|
provider = entry.provider;
|
|
35
36
|
modelId = entry.modelId;
|
|
37
|
+
channel = entry.channel;
|
|
36
38
|
}
|
|
37
39
|
else if (entry.type === "thinking_level_change")
|
|
38
40
|
thinking = entry.thinkingLevel;
|
|
39
41
|
}
|
|
40
|
-
if (provider !== model.provider || modelId !== model.id) {
|
|
41
|
-
core.appendEntry({
|
|
42
|
+
if (provider !== model.provider || modelId !== model.id || channel !== model.channel) {
|
|
43
|
+
core.appendEntry({
|
|
44
|
+
type: "model_change",
|
|
45
|
+
provider: model.provider,
|
|
46
|
+
modelId: model.id,
|
|
47
|
+
...(model.channel !== undefined ? { channel: model.channel } : {}),
|
|
48
|
+
});
|
|
42
49
|
}
|
|
43
50
|
if (thinking !== thinkingLevel) {
|
|
44
51
|
core.appendEntry({ type: "thinking_level_change", thinkingLevel });
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import { DEFAULT_APPROVAL_TIMEOUT_MS } from "../permissions/broker.js";
|
|
15
15
|
import { previewAction } from "../permissions/preview.js";
|
|
16
|
+
import { classifierRequest } from "./session-classifier.js";
|
|
16
17
|
import { runSingleToolCall } from "./tool-runner.js";
|
|
17
18
|
/** 外层是这个工具时,嵌套调用的 Hook 输入带 `viaCodemode: true`。 */
|
|
18
19
|
const CODEMODE_TOOL_NAME = "codemode";
|
|
@@ -36,6 +37,8 @@ export async function requestApproval(core, request, signal) {
|
|
|
36
37
|
event.hookReason = request.hookReason;
|
|
37
38
|
if (request.preview !== undefined)
|
|
38
39
|
event.preview = request.preview;
|
|
40
|
+
if (request.autoDecision !== undefined)
|
|
41
|
+
event.autoDecision = request.autoDecision;
|
|
39
42
|
core.emit(event);
|
|
40
43
|
void core.runHook("Notification", {
|
|
41
44
|
notification: { kind: "approval", message: `approval requested for ${request.toolName}` },
|
|
@@ -117,6 +120,7 @@ export async function gateToolCall(core, call, ctx) {
|
|
|
117
120
|
let decision = hookDecision === "ask" ? "ask" : "allow";
|
|
118
121
|
let approvalReason = "hook";
|
|
119
122
|
let denyMessage;
|
|
123
|
+
let autoDecision;
|
|
120
124
|
if (permission !== undefined) {
|
|
121
125
|
const checkInput = {
|
|
122
126
|
toolName: call.name,
|
|
@@ -132,12 +136,27 @@ export async function gateToolCall(core, call, ctx) {
|
|
|
132
136
|
decision = verdict.decision;
|
|
133
137
|
approvalReason = verdict.approvalReason ?? "mode";
|
|
134
138
|
denyMessage = verdict.message;
|
|
139
|
+
autoDecision = verdict.auto;
|
|
140
|
+
if (verdict.classify === true) {
|
|
141
|
+
// auto 第 3 层:规则层与静态判定都没决定,问一次独立的模型分类器(只能变 allow)
|
|
142
|
+
const judged = await core
|
|
143
|
+
.autoClassifier()
|
|
144
|
+
.classify(classifierRequest(core, call.name, input, permission.projectRoot), ctx.signal);
|
|
145
|
+
autoDecision = { layer: "classifier", decision: judged.decision, reason: judged.reason };
|
|
146
|
+
if (judged.cached)
|
|
147
|
+
autoDecision.cached = true;
|
|
148
|
+
if (judged.decision === "allow")
|
|
149
|
+
decision = "allow";
|
|
150
|
+
}
|
|
151
|
+
if (autoDecision !== undefined)
|
|
152
|
+
permission.recordAutoDecision?.(call.name, input, autoDecision);
|
|
135
153
|
}
|
|
136
154
|
else if (decision === "ask" && core.options.unattended === true) {
|
|
137
155
|
decision = "deny";
|
|
138
156
|
}
|
|
157
|
+
const withAuto = (gate) => autoDecision === undefined ? gate : { ...gate, autoDecision };
|
|
139
158
|
if (decision === "deny") {
|
|
140
|
-
return { block: true, reason: denyMessage ?? `Permission denied for ${call.name}` };
|
|
159
|
+
return withAuto({ block: true, reason: denyMessage ?? `Permission denied for ${call.name}` });
|
|
141
160
|
}
|
|
142
161
|
if (decision === "ask") {
|
|
143
162
|
const request = {
|
|
@@ -149,16 +168,18 @@ export async function gateToolCall(core, call, ctx) {
|
|
|
149
168
|
};
|
|
150
169
|
if (hookReason !== undefined)
|
|
151
170
|
request.hookReason = hookReason;
|
|
171
|
+
if (autoDecision !== undefined)
|
|
172
|
+
request.autoDecision = autoDecision;
|
|
152
173
|
const preview = safePreview(core, request);
|
|
153
174
|
if (preview !== undefined)
|
|
154
175
|
request.preview = preview;
|
|
155
176
|
const answer = await requestApproval(core, request, ctx.signal);
|
|
156
177
|
if (answer === "deny")
|
|
157
|
-
return { block: true, reason: `The user denied ${call.name}` };
|
|
178
|
+
return withAuto({ block: true, reason: `The user denied ${call.name}` });
|
|
158
179
|
if (answer === "allow_session")
|
|
159
180
|
permission?.rememberForSession(call.name, input);
|
|
160
181
|
}
|
|
161
|
-
return replaced ? { input } : {};
|
|
182
|
+
return withAuto(replaced ? { input } : {});
|
|
162
183
|
}
|
|
163
184
|
export async function afterToolCall(core, call, result, parent) {
|
|
164
185
|
if (core.options.hooks?.has("PostToolUse", call.name) !== true)
|
package/dist/agent/session.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { Model, ModelThinkingLevel } from "../ai/types.js";
|
|
14
14
|
import type { HookEvent, HookEventPayload, HookOutcome } from "../hooks/types.js";
|
|
15
|
+
import type { PermissionClassifier } from "../permissions/classifier.js";
|
|
15
16
|
import type { PermissionMode } from "../permissions/types.js";
|
|
16
17
|
import type { SessionManager } from "../session/manager.js";
|
|
17
18
|
import type { AgentMessage, BranchSummaryEntry, SessionEntry, SessionEntryInput } from "../session/types.js";
|
|
@@ -47,6 +48,7 @@ export declare class AgentSessionImpl implements AgentSession, SessionCore {
|
|
|
47
48
|
private systemInput;
|
|
48
49
|
private readonly subagentPool;
|
|
49
50
|
private disposed;
|
|
51
|
+
private classifier;
|
|
50
52
|
/** 已入队、尚未投递的消息(投递时发 queue_update)。 */
|
|
51
53
|
private readonly queuedMessages;
|
|
52
54
|
constructor(options: AgentSessionOptions);
|
|
@@ -113,5 +115,6 @@ export declare class AgentSessionImpl implements AgentSession, SessionCore {
|
|
|
113
115
|
getStats(): SessionStats;
|
|
114
116
|
dispose(): Promise<void>;
|
|
115
117
|
childBase(): Pick<AgentSessionOptions, "model" | "thinkingLevel" | "activeTools" | "system">;
|
|
118
|
+
autoClassifier(): PermissionClassifier;
|
|
116
119
|
spawnSubagent(request: SubagentRequest): Promise<SubagentResult>;
|
|
117
120
|
}
|
package/dist/agent/session.js
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* - `steer()` / `followUp()`:运行中入队(返回 "queued");空闲时直接以该消息开始一个周期("handled")。
|
|
11
11
|
* - `abort()`:中断供应商流、工具、重试等待与压缩;回到 idle 后 resolve;不清队列。
|
|
12
12
|
*/
|
|
13
|
+
import { modelRefOf } from "../ai/providers/channels.js";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
import { AmaError } from "../errors.js";
|
|
16
|
+
import { createSessionClassifier } from "./session-classifier.js";
|
|
15
17
|
import { buildProjection } from "../session/projection.js";
|
|
16
18
|
import { Agent } from "./agent.js";
|
|
17
19
|
import { queuedText } from "./queue.js";
|
|
@@ -52,6 +54,7 @@ export class AgentSessionImpl {
|
|
|
52
54
|
systemInput;
|
|
53
55
|
subagentPool;
|
|
54
56
|
disposed = false;
|
|
57
|
+
classifier;
|
|
55
58
|
/** 已入队、尚未投递的消息(投递时发 queue_update)。 */
|
|
56
59
|
queuedMessages = new WeakSet();
|
|
57
60
|
constructor(options) {
|
|
@@ -102,9 +105,12 @@ export class AgentSessionImpl {
|
|
|
102
105
|
getThinkingLevel: () => this.currentThinking,
|
|
103
106
|
streamOptions: async () => {
|
|
104
107
|
const apiKey = await this.resolveApiKey();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
const idle = options.idleTimeoutMs;
|
|
109
|
+
return {
|
|
110
|
+
sessionId: this.manager.id,
|
|
111
|
+
...(apiKey === undefined ? {} : { apiKey }),
|
|
112
|
+
...(idle === undefined ? {} : { idleTimeoutMs: idle }),
|
|
113
|
+
};
|
|
108
114
|
},
|
|
109
115
|
beforeRequest: async () => {
|
|
110
116
|
this.syncSystem();
|
|
@@ -141,7 +147,8 @@ export class AgentSessionImpl {
|
|
|
141
147
|
}
|
|
142
148
|
async resolveApiKey() {
|
|
143
149
|
try {
|
|
144
|
-
|
|
150
|
+
const { provider, channel } = this.currentModel;
|
|
151
|
+
return (await this.options.providers.resolveApiKey(provider, channel)).apiKey;
|
|
145
152
|
}
|
|
146
153
|
catch {
|
|
147
154
|
return undefined;
|
|
@@ -358,15 +365,14 @@ export class AgentSessionImpl {
|
|
|
358
365
|
}
|
|
359
366
|
this.currentModel = lookup.model;
|
|
360
367
|
this.compaction.refresh();
|
|
368
|
+
const next = modelRefOf(lookup.model);
|
|
361
369
|
this.appendEntry({
|
|
362
370
|
type: "model_change",
|
|
363
|
-
provider:
|
|
364
|
-
modelId:
|
|
365
|
-
|
|
366
|
-
this.emit({
|
|
367
|
-
type: "model_changed",
|
|
368
|
-
model: { provider: lookup.model.provider, id: lookup.model.id },
|
|
371
|
+
provider: next.provider,
|
|
372
|
+
modelId: next.id,
|
|
373
|
+
...(next.channel !== undefined ? { channel: next.channel } : {}),
|
|
369
374
|
});
|
|
375
|
+
this.emit({ type: "model_changed", model: next });
|
|
370
376
|
}
|
|
371
377
|
setThinkingLevel(level) {
|
|
372
378
|
this.currentThinking = level;
|
|
@@ -478,6 +484,10 @@ export class AgentSessionImpl {
|
|
|
478
484
|
system: this.systemInput,
|
|
479
485
|
};
|
|
480
486
|
}
|
|
487
|
+
autoClassifier() {
|
|
488
|
+
this.classifier ??= createSessionClassifier(this);
|
|
489
|
+
return this.classifier;
|
|
490
|
+
}
|
|
481
491
|
spawnSubagent(request) {
|
|
482
492
|
return runSubagent(this, request, this.subagentPool, (options) => new AgentSessionImpl(options));
|
|
483
493
|
}
|
|
@@ -25,6 +25,10 @@ export const DEFAULT_MAX_TOOL_RESULT_CHARS = 30_000;
|
|
|
25
25
|
export const DEFAULT_ABORT_GRACE_MS = 3000;
|
|
26
26
|
/** 嵌套调用结果的截断上限(给脚本,不进上下文)。 */
|
|
27
27
|
export const NESTED_MAX_RESULT_CHARS = 1024 * 1024;
|
|
28
|
+
/** 门禁给出的 auto 判定,随 tool_execution_end 发出(按调用对象记,调用结束后随之回收)。 */
|
|
29
|
+
const autoDecisions = new WeakMap();
|
|
30
|
+
/** 门禁拒绝(gate.block)而没有执行的调用:tool_execution_end 带 `denied`。 */
|
|
31
|
+
const deniedCalls = new WeakSet();
|
|
28
32
|
export function errorResult(text) {
|
|
29
33
|
return { content: text, isError: true };
|
|
30
34
|
}
|
|
@@ -80,9 +84,12 @@ async function prepare(call, assistant, options, signal, parent) {
|
|
|
80
84
|
try {
|
|
81
85
|
const gateContext = parent === undefined ? { signal, assistant, tool } : { signal, assistant, tool, parent };
|
|
82
86
|
const gate = await options.beforeToolCall(call, gateContext);
|
|
87
|
+
if (gate.autoDecision !== undefined)
|
|
88
|
+
autoDecisions.set(call, gate.autoDecision);
|
|
83
89
|
if (signal.aborted)
|
|
84
90
|
return { kind: "immediate", call, result: errorResult(ABORTED_TOOL_TEXT) };
|
|
85
91
|
if (gate.block === true) {
|
|
92
|
+
deniedCalls.add(call);
|
|
86
93
|
return {
|
|
87
94
|
kind: "immediate",
|
|
88
95
|
call,
|
|
@@ -193,13 +200,19 @@ async function execute(prepared, options, signal, emit, parent) {
|
|
|
193
200
|
return truncateResult(call, result, options, parent !== undefined);
|
|
194
201
|
}
|
|
195
202
|
async function emitEnd(call, result, emit) {
|
|
196
|
-
|
|
203
|
+
const event = {
|
|
197
204
|
type: "tool_execution_end",
|
|
198
205
|
toolCallId: call.id,
|
|
199
206
|
toolName: call.name,
|
|
200
207
|
result,
|
|
201
208
|
isError: result.isError === true,
|
|
202
|
-
}
|
|
209
|
+
};
|
|
210
|
+
const auto = autoDecisions.get(call);
|
|
211
|
+
if (auto !== undefined)
|
|
212
|
+
event.autoDecision = auto;
|
|
213
|
+
if (deniedCalls.has(call))
|
|
214
|
+
event.denied = true;
|
|
215
|
+
await emit(event);
|
|
203
216
|
}
|
|
204
217
|
async function emitResultMessages(finalized, emit) {
|
|
205
218
|
const messages = [];
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import type { AssistantEvent, AssistantMessage, CacheRetention, ImageBlock, Message, MessageOrigin, Model, ModelRef, ModelThinkingLevel, ToolCallBlock, ToolResultMessage, TranscriptContext, Usage } from "../ai/types.js";
|
|
22
22
|
import type { CacheMiss, CacheMissReason, CacheReporting, WarmerStatus, WarmingMode } from "../ai/cache/types.js";
|
|
23
23
|
import type { HookEvent } from "../hooks/types.js";
|
|
24
|
-
import type { ActionPreview, ApprovalDecision, ApprovalReason, PermissionMode } from "../permissions/types.js";
|
|
24
|
+
import type { ActionPreview, ApprovalDecision, ApprovalReason, AutoDecision, PermissionMode } from "../permissions/types.js";
|
|
25
25
|
import type { AgentMessage, SessionEntry } from "../session/types.js";
|
|
26
26
|
import type { ToolDefinition, ToolResult } from "../tools/types.js";
|
|
27
27
|
export type { AssistantContentBlock, AssistantMessage, ContentBlock, ImageBlock, Message, StopReason, SystemMessage, TextBlock, ThinkingBlock, ToolCallBlock, ToolResultMessage, Usage, UserMessage, } from "../ai/types.js";
|
|
@@ -56,6 +56,8 @@ export interface ToolCallGate {
|
|
|
56
56
|
reason?: string;
|
|
57
57
|
/** Hook 的 updatedInput 替换后的输入。 */
|
|
58
58
|
input?: unknown;
|
|
59
|
+
/** auto 权限模式的判定(随 tool_execution_end 发出)。 */
|
|
60
|
+
autoDecision?: AutoDecision;
|
|
59
61
|
}
|
|
60
62
|
export interface LoopHooks {
|
|
61
63
|
/** 档一裁剪、Hook 的 additionalContext 注入。 */
|
|
@@ -175,6 +177,10 @@ export type SessionEvent = {
|
|
|
175
177
|
result: ToolResult;
|
|
176
178
|
isError: boolean;
|
|
177
179
|
parentToolCallId?: string;
|
|
180
|
+
/** auto 权限模式下这次调用的判定(层、结论、原因);其它模式缺省。 */
|
|
181
|
+
autoDecision?: AutoDecision;
|
|
182
|
+
/** 被拒(权限管线、PreToolUse Hook、审批或无人值守)而没有执行;原因是 `result` 的文本。 */
|
|
183
|
+
denied?: true;
|
|
178
184
|
} | {
|
|
179
185
|
type: "queue_update";
|
|
180
186
|
steering: string[];
|
|
@@ -210,6 +216,8 @@ export type SessionEvent = {
|
|
|
210
216
|
timeoutMs: number;
|
|
211
217
|
/** [W3-C0] 执行前预览(同 `ApprovalRequest.preview`)。 */
|
|
212
218
|
preview?: ActionPreview;
|
|
219
|
+
/** auto 权限模式下为什么询问(同 `ApprovalRequest.autoDecision`)。 */
|
|
220
|
+
autoDecision?: AutoDecision;
|
|
213
221
|
} | {
|
|
214
222
|
type: "permission_resolved";
|
|
215
223
|
requestId: string;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 任一出现过(含 0)即 `cacheReported: true`,之后的事件不会把它改回 false。
|
|
13
13
|
*/
|
|
14
14
|
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
15
|
-
import {
|
|
15
|
+
import { authHeaders, describeErrorJson, idleTimeoutOf, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
16
16
|
import { readSseEvents } from "../sse.js";
|
|
17
17
|
import { postWithCacheFallback } from "./cache-params.js";
|
|
18
18
|
import { ANTHROPIC_VERSION, anthropicMessagesUrl, buildAnthropicRequest, detectAnthropicCompat, } from "./anthropic-request.js";
|
|
@@ -168,11 +168,12 @@ async function run(stream, model, context, options) {
|
|
|
168
168
|
body,
|
|
169
169
|
signal: options.signal,
|
|
170
170
|
timeoutMs: options.timeoutMs,
|
|
171
|
+
idleTimeoutMs: idleTimeoutOf(options),
|
|
171
172
|
onResponse: options.onResponse,
|
|
172
173
|
});
|
|
173
174
|
stream.push({ type: "start", partial: tracker.output });
|
|
174
175
|
const state = { blocks: new Map(), stop: undefined, sawMessageStop: false };
|
|
175
|
-
for await (const sse of readSseEvents(response.body, options.signal)) {
|
|
176
|
+
for await (const sse of readSseEvents(response.body, options.signal, idleTimeoutOf(options))) {
|
|
176
177
|
if (sse.event === "ping" || sse.data === "")
|
|
177
178
|
continue;
|
|
178
179
|
let data;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - 错误体 `{error:{code,status,message}}` 重排为 `429 RESOURCE_EXHAUSTED: …`。
|
|
17
17
|
*/
|
|
18
18
|
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
19
|
-
import {
|
|
19
|
+
import { authHeaders, HttpError, idleTimeoutOf, joinUrl, mergeHeaders, postJson, USER_AGENT, } from "../http.js";
|
|
20
20
|
import { readSseEvents } from "../sse.js";
|
|
21
21
|
import { GOOGLE_BASE_URL, buildGoogleRequest, detectGoogleCompat } from "./google-request.js";
|
|
22
22
|
import { BlockTracker, ProviderStopError, STREAM_ENDED_MESSAGE, createOutput, finishDone, finishError, requireApiKey, } from "./shared.js";
|
|
@@ -196,6 +196,7 @@ async function run(stream, model, context, options) {
|
|
|
196
196
|
body: replaced === undefined ? request.body : replaced,
|
|
197
197
|
signal: options.signal,
|
|
198
198
|
timeoutMs: options.timeoutMs,
|
|
199
|
+
idleTimeoutMs: idleTimeoutOf(options),
|
|
199
200
|
onResponse: options.onResponse,
|
|
200
201
|
});
|
|
201
202
|
}
|
|
@@ -209,7 +210,7 @@ async function run(stream, model, context, options) {
|
|
|
209
210
|
idSeed: Date.now().toString(36),
|
|
210
211
|
};
|
|
211
212
|
const body = response.body;
|
|
212
|
-
for await (const sse of readSseEvents(body, options.signal)) {
|
|
213
|
+
for await (const sse of readSseEvents(body, options.signal, idleTimeoutOf(options))) {
|
|
213
214
|
const data = sse.data.trim();
|
|
214
215
|
if (data === "" || data === "[DONE]")
|
|
215
216
|
continue;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 内容推断。
|
|
15
15
|
*/
|
|
16
16
|
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
17
|
-
import {
|
|
17
|
+
import { authHeaders, describeErrorJson, idleTimeoutOf, joinUrl, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
18
18
|
import { readSseEvents } from "../sse.js";
|
|
19
19
|
import { affinityHeaders, postWithCacheFallback, resolveCacheRetention } from "./cache-params.js";
|
|
20
20
|
import { detectCompat } from "./openai-compat.js";
|
|
@@ -190,6 +190,7 @@ async function run(stream, model, context, options) {
|
|
|
190
190
|
body: replaced === undefined ? request.body : replaced,
|
|
191
191
|
signal: options.signal,
|
|
192
192
|
timeoutMs: options.timeoutMs,
|
|
193
|
+
idleTimeoutMs: idleTimeoutOf(options),
|
|
193
194
|
onResponse: options.onResponse,
|
|
194
195
|
});
|
|
195
196
|
stream.push({ type: "start", partial: tracker.output });
|
|
@@ -203,7 +204,7 @@ async function run(stream, model, context, options) {
|
|
|
203
204
|
sawDone: false,
|
|
204
205
|
};
|
|
205
206
|
const body = response.body;
|
|
206
|
-
for await (const sse of readSseEvents(body, options.signal)) {
|
|
207
|
+
for await (const sse of readSseEvents(body, options.signal, idleTimeoutOf(options))) {
|
|
207
208
|
const data = sse.data.trim();
|
|
208
209
|
if (data === "")
|
|
209
210
|
continue;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `cacheReported: true`。
|
|
16
16
|
*/
|
|
17
17
|
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
18
|
-
import {
|
|
18
|
+
import { authHeaders, describeErrorJson, idleTimeoutOf, joinUrl, mergeHeaders, USER_AGENT, } from "../http.js";
|
|
19
19
|
import { readSseEvents } from "../sse.js";
|
|
20
20
|
import { affinityHeaders, postWithCacheFallback, resolveCacheRetention } from "./cache-params.js";
|
|
21
21
|
import { buildResponsesRequest, detectResponsesCompat, encodeMessageSignature, } from "./openai-responses-request.js";
|
|
@@ -50,7 +50,8 @@ export function parseResponsesUsage(raw) {
|
|
|
50
50
|
}
|
|
51
51
|
export function mapResponsesStatus(status, incomplete) {
|
|
52
52
|
if (status === "incomplete") {
|
|
53
|
-
|
|
53
|
+
// 中转转发的 DeepSeek 等上游写 `length`(实测 2026-10-02),与 `max_output_tokens` 同义。
|
|
54
|
+
if (incomplete === "max_output_tokens" || incomplete === "length")
|
|
54
55
|
return { reason: "length" };
|
|
55
56
|
return {
|
|
56
57
|
reason: "error",
|
|
@@ -248,6 +249,7 @@ async function run(stream, model, context, options) {
|
|
|
248
249
|
body: replaced === undefined ? request.body : replaced,
|
|
249
250
|
signal: options.signal,
|
|
250
251
|
timeoutMs: options.timeoutMs,
|
|
252
|
+
idleTimeoutMs: idleTimeoutOf(options),
|
|
251
253
|
onResponse: options.onResponse,
|
|
252
254
|
});
|
|
253
255
|
stream.push({ type: "start", partial: tracker.output });
|
|
@@ -259,7 +261,7 @@ async function run(stream, model, context, options) {
|
|
|
259
261
|
};
|
|
260
262
|
let finished = false;
|
|
261
263
|
const body = response.body;
|
|
262
|
-
for await (const sse of readSseEvents(body, options.signal)) {
|
|
264
|
+
for await (const sse of readSseEvents(body, options.signal, idleTimeoutOf(options))) {
|
|
263
265
|
const raw = sse.data.trim();
|
|
264
266
|
if (raw === "" || raw === "[DONE]")
|
|
265
267
|
continue;
|
|
@@ -11,7 +11,7 @@ import type { Model, ModelRef, TranscriptContext } from "../types.js";
|
|
|
11
11
|
import type { PrefixFingerprint } from "./types.js";
|
|
12
12
|
/** sha256 的前 16 位 hex(64 bit)。 */
|
|
13
13
|
export declare function hash16(text: string): string;
|
|
14
|
-
export declare function modelKey(model: Pick<Model, "provider" | "id"> | ModelRef): string;
|
|
14
|
+
export declare function modelKey(model: Pick<Model, "provider" | "id" | "channel"> | ModelRef): string;
|
|
15
15
|
export declare function fingerprintContext(context: TranscriptContext, model: Pick<Model, "provider" | "id">): PrefixFingerprint;
|
|
16
16
|
/** 两个指纹的差异(按归因顺序:system → tools → model);相同返回 undefined。 */
|
|
17
17
|
export declare function fingerprintChange(prev: PrefixFingerprint, cur: PrefixFingerprint): "system" | "tools" | "model" | undefined;
|
|
@@ -14,7 +14,7 @@ export function hash16(text) {
|
|
|
14
14
|
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
15
15
|
}
|
|
16
16
|
export function modelKey(model) {
|
|
17
|
-
return `${model.provider}/${model.id}`;
|
|
17
|
+
return `${model.provider}/${model.id}${model.channel !== undefined ? `@${model.channel}` : ""}`;
|
|
18
18
|
}
|
|
19
19
|
function sortedTools(tools) {
|
|
20
20
|
return [...tools].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
@@ -41,7 +41,8 @@ function hostOf(baseUrl) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
export function endpointKey(record) {
|
|
44
|
-
|
|
44
|
+
const channel = record.model.channel !== undefined ? `@${record.model.channel}` : "";
|
|
45
|
+
return `${record.model.provider}|${hostOf(record.baseUrl)}|${record.model.id}${channel}`;
|
|
45
46
|
}
|
|
46
47
|
function sameFingerprint(a, b) {
|
|
47
48
|
return a.system === b.system && a.tools === b.tools && a.model === b.model;
|
package/dist/ai/http.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fetch 包装(设计 §1.2 ai/http.ts):超时、头合并(值为 null 删除)、错误体读取。
|
|
3
3
|
*
|
|
4
|
-
* 代理:Node 的全局 fetch 缺省不读 HTTP(S)_PROXY。
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 代理:Node 的全局 fetch 缺省不读 HTTP(S)_PROXY。CLI 启动时由 cli/proxy.ts 调 Node 内置的
|
|
5
|
+
* `http.setGlobalProxyFromEnv()`(等价于 `NODE_USE_ENV_PROXY=1`);SDK 嵌入方自行决定。
|
|
6
|
+
* 本模块不自行实现代理。
|
|
7
|
+
*
|
|
8
|
+
* 超时:`timeoutMs` 只管到拿到响应头为止;`idleTimeoutMs`(缺省 300 s,0 关闭)既管等响应头,
|
|
9
|
+
* 也管流式读取期间两次收到数据之间的间隔(每收到一块字节即重新计时,见 sse.ts)。空闲超时抛
|
|
10
|
+
* `IdleTimeoutError`,文案含 `idle timeout`,会话层按可重试错误处理。
|
|
7
11
|
*
|
|
8
12
|
* 协议层自身**不重试**(重试在会话层,§3.6)。
|
|
9
13
|
*/
|
|
@@ -29,6 +33,21 @@ export declare class HttpError extends Error {
|
|
|
29
33
|
export declare class RequestTimeoutError extends Error {
|
|
30
34
|
constructor(timeoutMs: number);
|
|
31
35
|
}
|
|
36
|
+
/** 流空闲超时缺省值:与 `request.idleTimeoutMs` / `AMA_IDLE_TIMEOUT_MS` 的缺省一致。 */
|
|
37
|
+
export declare const DEFAULT_IDLE_TIMEOUT_MS = 300000;
|
|
38
|
+
/** 请求选项里的空闲超时;未给出用缺省值,0 或负数表示关闭(返回 undefined)。 */
|
|
39
|
+
export declare function idleTimeoutOf(options: {
|
|
40
|
+
idleTimeoutMs?: number | undefined;
|
|
41
|
+
}): number | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* 服务端在 `idleTimeoutMs` 内没有任何字节:`response` = 发出请求后迟迟没有响应头,
|
|
44
|
+
* `stream` = 流读到一半停住。文案含 `idle timeout`(重试分类据此判为可重试)。
|
|
45
|
+
*/
|
|
46
|
+
export declare class IdleTimeoutError extends Error {
|
|
47
|
+
readonly idleTimeoutMs: number;
|
|
48
|
+
readonly phase: "response" | "stream";
|
|
49
|
+
constructor(idleTimeoutMs: number, phase: "response" | "stream");
|
|
50
|
+
}
|
|
32
51
|
/**
|
|
33
52
|
* 从错误体提取文本:识别 `{error:{type,message,code}}`、`{error:"..."}`、`{message}`、
|
|
34
53
|
* `{detail}` 等常见形状;不是 JSON 则截断原文。结果形如 `429 rate_limit_error: ...`。
|
|
@@ -41,12 +60,15 @@ export interface PostOptions {
|
|
|
41
60
|
body: unknown;
|
|
42
61
|
signal: AbortSignal;
|
|
43
62
|
timeoutMs?: number | undefined;
|
|
63
|
+
/** 等响应头的空闲上限(`timeoutMs` 未给时生效);undefined 不限。 */
|
|
64
|
+
idleTimeoutMs?: number | undefined;
|
|
44
65
|
onResponse?: ((status: number, headers: Headers) => void) | undefined;
|
|
45
66
|
}
|
|
46
67
|
/**
|
|
47
|
-
* POST JSON 并返回 2xx 响应(body 是 SSE 字节流)。非 2xx → HttpError;超时 → RequestTimeoutError
|
|
48
|
-
*
|
|
49
|
-
*
|
|
68
|
+
* POST JSON 并返回 2xx 响应(body 是 SSE 字节流)。非 2xx → HttpError;超时 → RequestTimeoutError
|
|
69
|
+
* (`timeoutMs`)或 IdleTimeoutError(`idleTimeoutMs`);调用方的 signal 中止 → 原样抛 AbortError
|
|
70
|
+
* (调用方据 signal.aborted 判 aborted)。超时只覆盖到拿到响应头为止,流式读取期间的空闲由
|
|
71
|
+
* `readSseEvents` 的 `idleTimeoutMs` 控制。
|
|
50
72
|
*/
|
|
51
73
|
export declare function postJson(url: string, options: PostOptions): Promise<Response>;
|
|
52
74
|
/** undici 的 `fetch failed` 把真正原因藏在 cause 里;拼出来便于重试分类与排错。 */
|
package/dist/ai/http.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fetch 包装(设计 §1.2 ai/http.ts):超时、头合并(值为 null 删除)、错误体读取。
|
|
3
3
|
*
|
|
4
|
-
* 代理:Node 的全局 fetch 缺省不读 HTTP(S)_PROXY。
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 代理:Node 的全局 fetch 缺省不读 HTTP(S)_PROXY。CLI 启动时由 cli/proxy.ts 调 Node 内置的
|
|
5
|
+
* `http.setGlobalProxyFromEnv()`(等价于 `NODE_USE_ENV_PROXY=1`);SDK 嵌入方自行决定。
|
|
6
|
+
* 本模块不自行实现代理。
|
|
7
|
+
*
|
|
8
|
+
* 超时:`timeoutMs` 只管到拿到响应头为止;`idleTimeoutMs`(缺省 300 s,0 关闭)既管等响应头,
|
|
9
|
+
* 也管流式读取期间两次收到数据之间的间隔(每收到一块字节即重新计时,见 sse.ts)。空闲超时抛
|
|
10
|
+
* `IdleTimeoutError`,文案含 `idle timeout`,会话层按可重试错误处理。
|
|
7
11
|
*
|
|
8
12
|
* 协议层自身**不重试**(重试在会话层,§3.6)。
|
|
9
13
|
*/
|
|
@@ -76,6 +80,33 @@ export class RequestTimeoutError extends Error {
|
|
|
76
80
|
this.name = "RequestTimeoutError";
|
|
77
81
|
}
|
|
78
82
|
}
|
|
83
|
+
/** 流空闲超时缺省值:与 `request.idleTimeoutMs` / `AMA_IDLE_TIMEOUT_MS` 的缺省一致。 */
|
|
84
|
+
export const DEFAULT_IDLE_TIMEOUT_MS = 300_000;
|
|
85
|
+
/** 请求选项里的空闲超时;未给出用缺省值,0 或负数表示关闭(返回 undefined)。 */
|
|
86
|
+
export function idleTimeoutOf(options) {
|
|
87
|
+
const value = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
88
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
89
|
+
}
|
|
90
|
+
function formatDuration(ms) {
|
|
91
|
+
return ms >= 1000 && ms % 1000 === 0 ? `${ms / 1000} s` : `${ms} ms`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 服务端在 `idleTimeoutMs` 内没有任何字节:`response` = 发出请求后迟迟没有响应头,
|
|
95
|
+
* `stream` = 流读到一半停住。文案含 `idle timeout`(重试分类据此判为可重试)。
|
|
96
|
+
*/
|
|
97
|
+
export class IdleTimeoutError extends Error {
|
|
98
|
+
idleTimeoutMs;
|
|
99
|
+
phase;
|
|
100
|
+
constructor(idleTimeoutMs, phase) {
|
|
101
|
+
const what = phase === "response"
|
|
102
|
+
? `No response from the server within ${formatDuration(idleTimeoutMs)}`
|
|
103
|
+
: `Stream stalled: no data from the server for ${formatDuration(idleTimeoutMs)}`;
|
|
104
|
+
super(`${what} (idle timeout; adjust with request.idleTimeoutMs or AMA_IDLE_TIMEOUT_MS)`);
|
|
105
|
+
this.name = "IdleTimeoutError";
|
|
106
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
107
|
+
this.phase = phase;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
79
110
|
const MAX_ERROR_BODY = 4000;
|
|
80
111
|
/**
|
|
81
112
|
* 从错误体提取文本:识别 `{error:{type,message,code}}`、`{error:"..."}`、`{message}`、
|
|
@@ -141,12 +172,14 @@ function parseRetryAfter(headers) {
|
|
|
141
172
|
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
|
|
142
173
|
}
|
|
143
174
|
/**
|
|
144
|
-
* POST JSON 并返回 2xx 响应(body 是 SSE 字节流)。非 2xx → HttpError;超时 → RequestTimeoutError
|
|
145
|
-
*
|
|
146
|
-
*
|
|
175
|
+
* POST JSON 并返回 2xx 响应(body 是 SSE 字节流)。非 2xx → HttpError;超时 → RequestTimeoutError
|
|
176
|
+
* (`timeoutMs`)或 IdleTimeoutError(`idleTimeoutMs`);调用方的 signal 中止 → 原样抛 AbortError
|
|
177
|
+
* (调用方据 signal.aborted 判 aborted)。超时只覆盖到拿到响应头为止,流式读取期间的空闲由
|
|
178
|
+
* `readSseEvents` 的 `idleTimeoutMs` 控制。
|
|
147
179
|
*/
|
|
148
180
|
export async function postJson(url, options) {
|
|
149
|
-
const
|
|
181
|
+
const explicit = options.timeoutMs !== undefined && options.timeoutMs > 0;
|
|
182
|
+
const timeout = explicit ? options.timeoutMs : options.idleTimeoutMs;
|
|
150
183
|
const timer = new AbortController();
|
|
151
184
|
const handle = timeout !== undefined && timeout > 0 ? setTimeout(() => timer.abort(), timeout) : undefined;
|
|
152
185
|
const signal = AbortSignal.any([options.signal, timer.signal]);
|
|
@@ -161,7 +194,7 @@ export async function postJson(url, options) {
|
|
|
161
194
|
}
|
|
162
195
|
catch (error) {
|
|
163
196
|
if (timer.signal.aborted && !options.signal.aborted && timeout !== undefined) {
|
|
164
|
-
throw new RequestTimeoutError(timeout);
|
|
197
|
+
throw explicit ? new RequestTimeoutError(timeout) : new IdleTimeoutError(timeout, "response");
|
|
165
198
|
}
|
|
166
199
|
throw normalizeNetworkError(error);
|
|
167
200
|
}
|